diff --git a/CHANGELOG.md b/CHANGELOG.md index 0820a42c..9e2c4a59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -131,6 +131,12 @@ archived by series under [docs/changelog/](docs/changelog/); see the `the_ffi_crate_builds_no_rust_library` refuses a crate type that would turn it off again. +- **Python: type checkers read the package's annotations.** The package + shipped no PEP 561 `py.typed` marker, so mypy skipped it as untyped + ("missing library stubs or py.typed marker") and every value it returned + was `Any` in the application, although nearly every public function is + annotated. The wheel now carries the marker, and the wheel build fails + if it goes missing. - **Python: a Linux box that could no longer advertise recovers on its own.** Linux loses track of the controller's advertising when a central-role connection completes while advertising is on: the controller keeps diff --git a/bindings/python/offline_protocol_sdk/py.typed b/bindings/python/offline_protocol_sdk/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml index 8edcd347..62e779b7 100644 --- a/bindings/python/pyproject.toml +++ b/bindings/python/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Rust", "Topic :: Communications", "Topic :: Security :: Cryptography", + "Typing :: Typed", ] # Audited dependencies — see plan for supply chain security audit results. @@ -86,7 +87,9 @@ Issues = "https://github.com/Offline-Protocol/offline-protocol-sdk/issues" include = ["offline_protocol_sdk*"] [tool.setuptools.package-data] -offline_protocol_sdk = ["*.dylib", "*.so", "*.dll", "libuniffi.*", "uniffi.dll"] +# `py.typed` is the PEP 561 marker. Without it mypy skips the package as +# untyped and every annotation in it is lost to the application. +offline_protocol_sdk = ["py.typed", "*.dylib", "*.so", "*.dll", "libuniffi.*", "uniffi.dll"] [tool.pytest.ini_options] asyncio_mode = "auto" diff --git a/bindings/python/scripts/build-wheel.sh b/bindings/python/scripts/build-wheel.sh index 72557deb..e0d6cccb 100755 --- a/bindings/python/scripts/build-wheel.sh +++ b/bindings/python/scripts/build-wheel.sh @@ -266,12 +266,17 @@ WHEEL="$PY_ROOT/dist/offline_protocol_sdk-$PY_VERSION-py3-none-$PLAT_TAG.whl" die "dist/ holds more than the retagged wheel" # What the wheel has to carry: the library under the loader's name, the -# bindings that open it, and the licences. +# bindings that open it, the PEP 561 marker that lets a type checker read +# them, and the licences. python - "$WHEEL" "$LOADER_NAME" <<'PYTHON' import sys, zipfile wheel, loader = sys.argv[1], sys.argv[2] names = set(zipfile.ZipFile(wheel).namelist()) -required = [f"offline_protocol_sdk/{loader}", "offline_protocol_sdk/offline_protocol.py"] +required = [ + f"offline_protocol_sdk/{loader}", + "offline_protocol_sdk/offline_protocol.py", + "offline_protocol_sdk/py.typed", +] missing = [n for n in required if n not in names] libraries = [n for n in names if n.endswith((".so", ".dylib", ".dll"))] licences = [n for n in names if n.endswith(("/LICENSE", "/EXPORT.md"))]