-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild_mypyc.py
More file actions
115 lines (98 loc) · 4.99 KB
/
build_mypyc.py
File metadata and controls
115 lines (98 loc) · 4.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""mypyc compilation entry point for HawkAPI hot-path modules.
Compilation is OPT-IN. Set the environment variable ``HAWKAPI_BUILD_MYPYC=1``
when building (e.g. ``HAWKAPI_BUILD_MYPYC=1 uv build --wheel`` or
``HAWKAPI_BUILD_MYPYC=1 pip install hawkapi --no-binary hawkapi``).
When the env var is unset, this module returns an empty extension list and the
package installs as pure Python — preserving the default ``pip install hawkapi``
behaviour and PyPy compatibility.
The selected modules are pure-typed hot paths: route lookup (radix tree, route
record, param converters), response writers (Response, JSONResponse) and the
ASGI middleware pipeline builder. Each remains importable as plain Python when
the compiled ``.so`` is absent — there are no compile-only constructs.
"""
from __future__ import annotations
import os
import sys
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from collections.abc import Sequence
# Modules selected for mypyc compilation. Paths are relative to the project root
# (the directory containing ``pyproject.toml``).
#
# IMPORTANT: ``responses/response.py`` and ``responses/json_response.py`` are
# intentionally EXCLUDED because user code (and the bundled
# ``PlainTextResponse``/``HTMLResponse``/``RedirectResponse`` helpers) subclasses
# them. mypyc rejects ``interpreted classes cannot inherit from compiled``
# subclassing at runtime, so compiling these would break the public API.
# Compiling the radix tree, route record, param converters and middleware
# pipeline still captures the dominant request-routing hot path.
#
# MSVC RESERVED IDENTIFIERS — when adding attributes to compiled classes,
# avoid any name that starts with ``__is_`` or ``__has_`` and matches a C++11
# type-trait keyword. mypyc transpiles ``_private`` attribute names to
# ``__private`` in generated C, and MSVC interprets tokens like ``__is_trivial``,
# ``__is_class``, ``__is_base_of``, ``__is_constructible``,
# ``__has_trivial_destructor``, ``__has_virtual_destructor`` as built-in
# compiler intrinsics — resulting in ``error C4233: nonstandard extension
# used`` on Windows wheel builds. Prefer ``_trivial`` / ``_class`` /
# ``_base_of`` / ``_constructible`` over the ``is_``/``has_`` prefix form.
HOT_MODULES: tuple[str, ...] = (
"src/hawkapi/routing/_radix_tree.py",
"src/hawkapi/routing/route.py",
"src/hawkapi/routing/param_converters.py",
"src/hawkapi/middleware/_pipeline.py",
# NOTE: routing/router.py and di/resolver.py were considered for Wave 3
# expansion but are EXCLUDED — mypyc type-checks transitive imports which
# pull in the entire codebase (including optional deps like structlog,
# prometheus_client). Keep them interpreted until we have a narrower
# mypyc invocation that only checks the selected modules.
# NOTE: app.py is intentionally EXCLUDED — HawkAPI(Router) is subclassed
# by user code and mypyc does not allow interpreted classes to inherit from
# compiled ones at runtime.
# NOTE: requests/request.py is intentionally EXCLUDED — Request is also
# subclassed by user code via TestClient and custom request overrides.
)
def is_enabled() -> bool:
"""Return True when mypyc compilation has been opted into.
mypyc-compiled extensions require the GIL. On a PEP 703 free-threaded
CPython build (``python3.13t``), ``sys._is_gil_enabled()`` returns
``False`` — we skip compilation in that case even when the env var is set,
and warn on stderr so the build log explains the decision.
"""
if os.environ.get("HAWKAPI_BUILD_MYPYC", "").strip().lower() not in {
"1",
"true",
"yes",
"on",
}:
return False
is_gil_enabled = getattr(sys, "_is_gil_enabled", None)
if is_gil_enabled is not None and not is_gil_enabled():
print(
"HAWKAPI_BUILD_MYPYC is set but the interpreter is free-threaded; "
"skipping mypyc compilation.",
file=sys.stderr,
)
return False
return True
def build_extensions() -> Sequence[Any]:
"""Return the list of mypyc-built ``Extension`` objects.
Returns an empty list when ``HAWKAPI_BUILD_MYPYC`` is not set so the build
backend can skip the C compilation step entirely.
"""
if not is_enabled():
return []
# Import lazily so pure-Python builds never need ``mypy`` installed.
from mypyc.build import mypycify # noqa: PLC0415
# Type-check scope is limited via [tool.mypy] in pyproject.toml
# (follow_imports = silent, ignore_missing_imports = true) so mypy does not
# walk the whole package when type-checking the compiled modules, and so
# optional runtime deps absent from build isolation do not fail the build.
return mypycify(
list(HOT_MODULES),
strip_asserts=False,
# Give the shared mypyc helper module a stable, namespaced name so it
# cannot collide with other mypyc-compiled packages on the same
# interpreter (otherwise mypyc generates a random hash-based name).
group_name="hawkapi_hot",
)