From 27bf5c5eb313aa2c01f70cc625c71f625b4f5fcf Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 25 Sep 2025 12:10:12 -0600 Subject: [PATCH 1/9] Move static metadata to a pyproject.toml file --- pyproject.toml | 25 +++++++++++++++++++++++++ setup.py | 22 ---------------------- 2 files changed, 25 insertions(+), 22 deletions(-) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..cb04bae --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[build-system] +build-backend = "setuptools.build_meta" +requires = ["setuptools"] + +[project] +name = "httptools" +dynamic = ["version"] +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Operating System :: POSIX", + "Operating System :: MacOS :: MacOS X", + "Environment :: Web Environment", + "Development Status :: 5 - Production/Stable", +] +requires-python = ">=3.8" +authors = [ + {name = "Yury Selivanov", email="yury@magic.io"}, +] +license = "MIT" +description = "A collection of framework independent HTTP protocol utils." +readme = "README.md" + +[project.urls] +Homepage = "https://github.com/MagicStack/httptools" diff --git a/setup.py b/setup.py index adca1f8..7423dd9 100644 --- a/setup.py +++ b/setup.py @@ -145,10 +145,6 @@ def build_extensions(self): super().build_extensions() -with open(str(ROOT / 'README.md')) as f: - long_description = f.read() - - with open(str(ROOT / 'httptools' / '_version.py')) as f: for line in f: if line.startswith('__version__ ='): @@ -169,27 +165,9 @@ def build_extensions(self): setup( - name='httptools', version=VERSION, - description='A collection of framework independent HTTP protocol utils.', - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/MagicStack/httptools', - classifiers=[ - 'License :: OSI Approved :: MIT License', - 'Intended Audience :: Developers', - 'Programming Language :: Python :: 3', - 'Operating System :: POSIX', - 'Operating System :: MacOS :: MacOS X', - 'Environment :: Web Environment', - 'Development Status :: 5 - Production/Stable', - ], platforms=['macOS', 'POSIX', 'Windows'], - python_requires='>=3.8.0', zip_safe=False, - author='Yury Selivanov', - author_email='yury@magic.io', - license='MIT', packages=['httptools', 'httptools.parser'], cmdclass={ 'build_ext': httptools_build_ext, From 5a852cdb304a1d39a79e47c63d26b87335de42dc Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 25 Sep 2025 12:16:58 -0600 Subject: [PATCH 2/9] drop editable install in CI --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7c85de3..aaf1a81 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -46,5 +46,5 @@ jobs: if: steps.release.outputs.version == 0 run: | python -m pip install -U pip setuptools wheel - python -m pip install -e .[test] + python -m pip install .[test] python -m unittest -v tests.suite From 6045853b3704e5a85c3d0e30e0140c1141bbbaa1 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 25 Sep 2025 12:35:26 -0600 Subject: [PATCH 3/9] add cython as a build dependency --- .github/workflows/tests.yml | 3 +- pyproject.toml | 4 +-- setup.py | 66 +++---------------------------------- 3 files changed, 9 insertions(+), 64 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index aaf1a81..fc3fb1f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,4 +47,5 @@ jobs: run: | python -m pip install -U pip setuptools wheel python -m pip install .[test] - python -m unittest -v tests.suite + cd tests + python -m unittest -v diff --git a/pyproject.toml b/pyproject.toml index cb04bae..8f36cbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] build-backend = "setuptools.build_meta" -requires = ["setuptools"] +requires = ["setuptools", "cython"] [project] name = "httptools" -dynamic = ["version"] +dynamic = ["version", "optional-dependencies"] classifiers = [ "Intended Audience :: Developers", "Programming Language :: Python :: 3", diff --git a/setup.py b/setup.py index 7423dd9..7f5a923 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,7 @@ import sys +from Cython.Build import cythonize + vi = sys.version_info if vi < (3, 8): raise RuntimeError('httptools require Python 3.8 or greater') @@ -15,17 +17,11 @@ ROOT = pathlib.Path(__file__).parent -CYTHON_DEPENDENCY = 'Cython>=0.29.24' +CYTHON_DEPENDENCY = 'Cython>=3.1.0' 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, @@ -33,8 +29,6 @@ class httptools_build_ext(build_ext): ] boolean_options = build_ext.boolean_options + [ - 'cython-always', - 'cython-annotate', 'use-system-llhttp', 'use-system-http-parser', ] @@ -49,9 +43,6 @@ 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 def finalize_options(self): # finalize_options() may be called multiple times on the @@ -60,53 +51,6 @@ 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: - raise RuntimeError( - 'please install Cython to compile httptools from source') - - if Cython.__version__ < '0.29': - raise RuntimeError( - 'httptools requires Cython version 0.29 or greater') - - 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 @@ -172,7 +116,7 @@ def build_extensions(self): cmdclass={ 'build_ext': httptools_build_ext, }, - ext_modules=[ + ext_modules=cythonize([ Extension( "httptools.parser.parser", sources=[ @@ -187,7 +131,7 @@ def build_extensions(self): ], extra_compile_args=CFLAGS, ), - ], + ]), include_package_data=True, exclude_package_data={"": ["*.c", "*.h"]}, test_suite='tests.suite', From 6fe9f2061c02b73a69178f00a61b4063b5636dcf Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 25 Sep 2025 12:37:40 -0600 Subject: [PATCH 4/9] add license-files --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 8f36cbd..bb88f12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ authors = [ {name = "Yury Selivanov", email="yury@magic.io"}, ] license = "MIT" +license-files = ["LICENSE"] description = "A collection of framework independent HTTP protocol utils." readme = "README.md" From 75fa978ec77303b6916994682be4f0049cfd2c5a Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 25 Sep 2025 12:39:38 -0600 Subject: [PATCH 5/9] drop Python 3.8 from builds --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fc3fb1f..d08ce7c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] os: [windows-latest, ubuntu-latest, macos-latest] env: From 725a9996f1b2b32800baab66c10d6b1391843cf9 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Wed, 8 Oct 2025 11:39:47 +0200 Subject: [PATCH 6/9] Update min Python to 3.9 in pyproject.toml As per-review, this was not updated. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bb88f12..422b2dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ classifiers = [ "Environment :: Web Environment", "Development Status :: 5 - Production/Stable", ] -requires-python = ">=3.8" +requires-python = ">=3.9" authors = [ {name = "Yury Selivanov", email="yury@magic.io"}, ] From 3385cfdebfb840bda7fb62d4c80956b9de34630b Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Wed, 8 Oct 2025 11:42:43 +0200 Subject: [PATCH 7/9] also bump testing up to 3.14 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d08ce7c..20c9145 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] os: [windows-latest, ubuntu-latest, macos-latest] env: From 783f866ba20a769f949a236d9baecb15b7becf24 Mon Sep 17 00:00:00 2001 From: Fantix King Date: Thu, 9 Oct 2025 22:39:08 -0400 Subject: [PATCH 8/9] build: preserve optional Cython behavior * Updated Makefile, support uv * Pinned setuptools as we use SetupRequirementsError * Updated release workflow * Dropped outdated stuff --- .github/workflows/release.yml | 11 ++--- Makefile | 23 +++++---- README.md | 4 +- pyproject.toml | 7 ++- setup.py | 90 ++++++++++++++++++++++++----------- 5 files changed, 85 insertions(+), 50 deletions(-) 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 - ] - } ) From 6b2358389644d8989fbefa86cbf3c158d0c4f90e Mon Sep 17 00:00:00 2001 From: Fantix King Date: Thu, 9 Oct 2025 22:53:05 -0400 Subject: [PATCH 9/9] Bump llhttp to 9.3.0 --- vendor/llhttp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llhttp b/vendor/llhttp index 610a87d..86b83a5 160000 --- a/vendor/llhttp +++ b/vendor/llhttp @@ -1 +1 @@ -Subproject commit 610a87d755f6bae466cd871c2ba97574ccac5483 +Subproject commit 86b83a59786caebd581f38d613c64c9e8c52c79e