Skip to content

Commit af427c8

Browse files
committed
WIP
1 parent 3385cfd commit af427c8

File tree

5 files changed

+116
-47
lines changed

5 files changed

+116
-47
lines changed

Makefile

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@
33

44
PYTHON ?= python3
55
ROOT = $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
6-
6+
UV := $(shell command -v uv 2> /dev/null)
7+
ifdef UV
8+
PYTHON := uv run
9+
PIP := uv pip
10+
else
11+
PIP := pip
12+
endif
713

814
compile:
9-
python3 setup.py build_ext --inplace
10-
11-
12-
release: compile test
13-
python3 setup.py sdist upload
14-
15+
$(PIP) install -e .
1516

1617
test: compile
17-
python3 -m unittest -v
18+
$(PYTHON) -m unittest -v
1819

1920
clean:
2021
find $(ROOT)/httptools/parser -name '*.c' | xargs rm -f
@@ -23,7 +24,3 @@ clean:
2324
distclean: clean
2425
git --git-dir="$(ROOT)/vendor/http-parser/.git" clean -dfx
2526
git --git-dir="$(ROOT)/vendor/llhttp/.git" clean -dfx
26-
27-
28-
testinstalled:
29-
cd /tmp && $(PYTHON) $(ROOT)/tests/__init__.py

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,7 @@ def parse_url(url: bytes):
9999

100100
3. Activate the environment with `source envname/bin/activate`
101101

102-
4. Install development requirements with `pip install -e .[test]`
103-
104-
5. Run `make` and `make test`.
102+
4. Run `make` and `make test`.
105103

106104

107105
# License

_custom_build/backend.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# https://setuptools.pypa.io/en/latest/build_meta.html#dynamic-build-dependencies-and-other-build-meta-tweaks
2+
3+
from setuptools import build_meta as _orig
4+
from setuptools.build_meta import * # noqa
5+
6+
7+
def _strtobool(val: str) -> bool:
8+
val = val.lower()
9+
if val in ('y', 'yes', 't', 'true', 'on', '1'):
10+
return True
11+
elif val in ('n', 'no', 'f', 'false', 'off', '0'):
12+
return False
13+
else:
14+
raise ValueError(f"invalid truth value {val!r}")
15+
16+
17+
def _get_build_requires(config_settings=None):
18+
config_settings = config_settings or {}
19+
need_cython = _strtobool(config_settings.get('--cython-always', '1'))
20+
if not need_cython:
21+
import pathlib
22+
23+
pkg_dir = pathlib.Path(__file__).parent / "httptools"
24+
for cython_file in pkg_dir.glob("**/*.pyx"):
25+
if not cython_file.with_suffix(".c").exists():
26+
need_cython = True
27+
break
28+
29+
build_requires = ["setuptools"]
30+
if need_cython:
31+
build_requires.append("cython>=3.1.0")
32+
return build_requires
33+
34+
35+
def get_requires_for_build_sdist(config_settings=None):
36+
return _get_build_requires(config_settings)
37+
38+
39+
def get_requires_for_build_wheel(config_settings=None):
40+
return _get_build_requires(config_settings)
41+
42+
43+
def get_requires_for_build_editable(config_settings=None):
44+
return _get_build_requires(config_settings)

pyproject.toml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
[build-system]
2-
build-backend = "setuptools.build_meta"
3-
requires = ["setuptools", "cython"]
2+
# Pr
3+
build-backend = "backend"
4+
backend-path = ["_custom_build"]
5+
requires = ["setuptools"]
46

57
[project]
68
name = "httptools"
7-
dynamic = ["version", "optional-dependencies"]
9+
dynamic = ["version"]
810
classifiers = [
911
"Intended Audience :: Developers",
1012
"Programming Language :: Python :: 3",
@@ -24,3 +26,6 @@ readme = "README.md"
2426

2527
[project.urls]
2628
Homepage = "https://github.com/MagicStack/httptools"
29+
30+
[project.optional-dependencies]
31+
test = [] # for backward compatibility

setup.py

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
11
import sys
22

3-
from Cython.Build import cythonize
3+
import os.path
4+
import pathlib
45

5-
vi = sys.version_info
6-
if vi < (3, 8):
7-
raise RuntimeError('httptools require Python 3.8 or greater')
8-
else:
9-
import os.path
10-
import pathlib
11-
12-
from setuptools import setup, Extension
13-
from setuptools.command.build_ext import build_ext as build_ext
6+
from setuptools import setup, Extension
7+
from setuptools.command.build_ext import build_ext as build_ext
148

159

1610
CFLAGS = ['-O2']
1711

1812
ROOT = pathlib.Path(__file__).parent
1913

20-
CYTHON_DEPENDENCY = 'Cython>=3.1.0'
21-
2214

2315
class httptools_build_ext(build_ext):
2416
user_options = build_ext.user_options + [
17+
('cython-always', None,
18+
'run cythonize() even if .c files are present'),
19+
('cython-annotate', None,
20+
'Produce a colorized HTML version of the Cython source.'),
21+
('cython-directives=', None,
22+
'Cythion compiler directives'),
2523
('use-system-llhttp', None,
2624
'Use the system provided llhttp, instead of the bundled one'),
2725
('use-system-http-parser', None,
2826
'Use the system provided http-parser, instead of the bundled one'),
2927
]
3028

3129
boolean_options = build_ext.boolean_options + [
30+
'cython-always',
31+
'cython-annotate',
3232
'use-system-llhttp',
3333
'use-system-http-parser',
3434
]
@@ -43,6 +43,9 @@ def initialize_options(self):
4343
super().initialize_options()
4444
self.use_system_llhttp = False
4545
self.use_system_http_parser = False
46+
self.cython_always = False
47+
self.cython_annotate = None
48+
self.cython_directives = None
4649

4750
def finalize_options(self):
4851
# finalize_options() may be called multiple times on the
@@ -51,6 +54,43 @@ def finalize_options(self):
5154
if getattr(self, '_initialized', False):
5255
return
5356

57+
need_cythonize = self.cython_always
58+
cfiles = {}
59+
60+
for extension in self.distribution.ext_modules:
61+
for i, sfile in enumerate(extension.sources):
62+
if sfile.endswith('.pyx'):
63+
prefix, ext = os.path.splitext(sfile)
64+
cfile = prefix + '.c'
65+
66+
if os.path.exists(cfile) and not self.cython_always:
67+
extension.sources[i] = cfile
68+
else:
69+
if os.path.exists(cfile):
70+
cfiles[cfile] = os.path.getmtime(cfile)
71+
else:
72+
cfiles[cfile] = 0
73+
need_cythonize = True
74+
75+
if need_cythonize:
76+
from Cython.Build import cythonize
77+
78+
directives = {}
79+
if self.cython_directives:
80+
for directive in self.cython_directives.split(','):
81+
k, _, v = directive.partition('=')
82+
if v.lower() == 'false':
83+
v = False
84+
if v.lower() == 'true':
85+
v = True
86+
87+
directives[k] = v
88+
89+
self.distribution.ext_modules[:] = cythonize(
90+
self.distribution.ext_modules,
91+
compiler_directives=directives,
92+
annotate=self.cython_annotate)
93+
5494
super().finalize_options()
5595

5696
self._initialized = True
@@ -100,14 +140,6 @@ def build_extensions(self):
100140
'unable to read the version from httptools/_version.py')
101141

102142

103-
setup_requires = []
104-
105-
if (not (ROOT / 'httptools' / 'parser' / 'parser.c').exists() or
106-
'--cython-always' in sys.argv):
107-
# No Cython output, require Cython to build.
108-
setup_requires.append(CYTHON_DEPENDENCY)
109-
110-
111143
setup(
112144
version=VERSION,
113145
platforms=['macOS', 'POSIX', 'Windows'],
@@ -116,7 +148,7 @@ def build_extensions(self):
116148
cmdclass={
117149
'build_ext': httptools_build_ext,
118150
},
119-
ext_modules=cythonize([
151+
ext_modules=[
120152
Extension(
121153
"httptools.parser.parser",
122154
sources=[
@@ -131,14 +163,7 @@ def build_extensions(self):
131163
],
132164
extra_compile_args=CFLAGS,
133165
),
134-
]),
166+
],
135167
include_package_data=True,
136168
exclude_package_data={"": ["*.c", "*.h"]},
137-
test_suite='tests.suite',
138-
setup_requires=setup_requires,
139-
extras_require={
140-
'test': [
141-
CYTHON_DEPENDENCY
142-
]
143-
}
144169
)

0 commit comments

Comments
 (0)