diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 036efc8..aeb0b49 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,13 +52,12 @@ jobs: fetch-depth: 50 submodules: true - - name: Set up Python - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + - name: Install the latest version of uv + uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # 7.0.0 - name: Build source distribution run: | - python -m pip install -U setuptools wheel pip - python setup.py sdist + uv build --sdist - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: @@ -73,12 +72,12 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] cibw_arch: ["auto64", "aarch64", "universal2"] cibw_python: - - "cp38" - "cp39" - "cp310" - "cp311" - "cp312" - "cp313" + - "cp314" exclude: - os: ubuntu-latest cibw_arch: universal2 @@ -108,7 +107,7 @@ jobs: with: platforms: arm64 - - uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 + - uses: pypa/cibuildwheel@7c619efba910c04005a835b110b057fc28fd6e93 # v3.2.0 env: CIBW_BUILD_VERBOSITY: 1 CIBW_BUILD: ${{ matrix.cibw_python }}-* diff --git a/Makefile b/Makefile index b34b26e..9a2596a 100644 --- a/Makefile +++ b/Makefile @@ -3,27 +3,26 @@ PYTHON ?= python3 ROOT = $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) - +UV := $(shell command -v uv 2> /dev/null) +ifdef UV + PYTHON := uv run + PIP := uv pip +else + PIP := pip +endif compile: - python3 setup.py build_ext --inplace - - -release: compile test - python3 setup.py sdist upload - + $(PIP) install -e . test: compile - python3 -m unittest -v + $(PYTHON) -m unittest -v clean: find $(ROOT)/httptools/parser -name '*.c' | xargs rm -f + find $(ROOT)/httptools/parser -name '*.so' | xargs rm -f find $(ROOT)/httptools/parser -name '*.html' | xargs rm -f + rm -rf build distclean: clean git --git-dir="$(ROOT)/vendor/http-parser/.git" clean -dfx git --git-dir="$(ROOT)/vendor/llhttp/.git" clean -dfx - - -testinstalled: - cd /tmp && $(PYTHON) $(ROOT)/tests/__init__.py \ No newline at end of file diff --git a/README.md b/README.md index 759de77..d30a324 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,7 @@ def parse_url(url: bytes): 3. Activate the environment with `source envname/bin/activate` -4. Install development requirements with `pip install -e .[test]` - -5. Run `make` and `make test`. +4. Run `make` and `make test`. # License diff --git a/pyproject.toml b/pyproject.toml index 422b2dc..659db31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] build-backend = "setuptools.build_meta" -requires = ["setuptools", "cython"] +requires = ["setuptools==80.9.0"] [project] name = "httptools" -dynamic = ["version", "optional-dependencies"] +dynamic = ["version"] classifiers = [ "Intended Audience :: Developers", "Programming Language :: Python :: 3", @@ -24,3 +24,6 @@ readme = "README.md" [project.urls] Homepage = "https://github.com/MagicStack/httptools" + +[project.optional-dependencies] +test = [] # for backward compatibility diff --git a/setup.py b/setup.py index 7f5a923..bc28b50 100644 --- a/setup.py +++ b/setup.py @@ -1,16 +1,10 @@ import sys -from Cython.Build import cythonize +import os.path +import pathlib -vi = sys.version_info -if vi < (3, 8): - raise RuntimeError('httptools require Python 3.8 or greater') -else: - import os.path - import pathlib - - from setuptools import setup, Extension - from setuptools.command.build_ext import build_ext as build_ext +from setuptools import setup, Extension +from setuptools.command.build_ext import build_ext as build_ext CFLAGS = ['-O2'] @@ -22,6 +16,12 @@ class httptools_build_ext(build_ext): user_options = build_ext.user_options + [ + ('cython-always', None, + 'run cythonize() even if .c files are present'), + ('cython-annotate', None, + 'Produce a colorized HTML version of the Cython source.'), + ('cython-directives=', None, + 'Cythion compiler directives'), ('use-system-llhttp', None, 'Use the system provided llhttp, instead of the bundled one'), ('use-system-http-parser', None, @@ -29,6 +29,8 @@ class httptools_build_ext(build_ext): ] boolean_options = build_ext.boolean_options + [ + 'cython-always', + 'cython-annotate', 'use-system-llhttp', 'use-system-http-parser', ] @@ -43,6 +45,11 @@ def initialize_options(self): super().initialize_options() self.use_system_llhttp = False self.use_system_http_parser = False + self.cython_always = False + self.cython_annotate = None + self.cython_directives = None + if 'editable_wheel' in sys.argv: + self.inplace = True def finalize_options(self): # finalize_options() may be called multiple times on the @@ -51,6 +58,50 @@ def finalize_options(self): if getattr(self, '_initialized', False): return + need_cythonize = self.cython_always + cfiles = {} + + for extension in self.distribution.ext_modules: + for i, sfile in enumerate(extension.sources): + if sfile.endswith('.pyx'): + prefix, ext = os.path.splitext(sfile) + cfile = prefix + '.c' + + if os.path.exists(cfile) and not self.cython_always: + extension.sources[i] = cfile + else: + if os.path.exists(cfile): + cfiles[cfile] = os.path.getmtime(cfile) + else: + cfiles[cfile] = 0 + need_cythonize = True + + if need_cythonize: + try: + import Cython + except ImportError: + import setuptools.build_meta + + raise setuptools.build_meta.SetupRequirementsError([CYTHON_DEPENDENCY]) + + from Cython.Build import cythonize + + directives = {} + if self.cython_directives: + for directive in self.cython_directives.split(','): + k, _, v = directive.partition('=') + if v.lower() == 'false': + v = False + if v.lower() == 'true': + v = True + + directives[k] = v + + self.distribution.ext_modules[:] = cythonize( + self.distribution.ext_modules, + compiler_directives=directives, + annotate=self.cython_annotate) + super().finalize_options() self._initialized = True @@ -100,14 +151,6 @@ def build_extensions(self): 'unable to read the version from httptools/_version.py') -setup_requires = [] - -if (not (ROOT / 'httptools' / 'parser' / 'parser.c').exists() or - '--cython-always' in sys.argv): - # No Cython output, require Cython to build. - setup_requires.append(CYTHON_DEPENDENCY) - - setup( version=VERSION, platforms=['macOS', 'POSIX', 'Windows'], @@ -116,7 +159,7 @@ def build_extensions(self): cmdclass={ 'build_ext': httptools_build_ext, }, - ext_modules=cythonize([ + ext_modules=[ Extension( "httptools.parser.parser", sources=[ @@ -131,14 +174,7 @@ def build_extensions(self): ], extra_compile_args=CFLAGS, ), - ]), + ], include_package_data=True, exclude_package_data={"": ["*.c", "*.h"]}, - test_suite='tests.suite', - setup_requires=setup_requires, - extras_require={ - 'test': [ - CYTHON_DEPENDENCY - ] - } )