From 80fe3420d1b5b47bbef380438f816ca056eb814b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 20 Sep 2026 13:56:36 +0200 Subject: [PATCH] Add ty type checking and fix typing issues --- .github/workflows/ci.yml | 2 ++ .gitignore | 6 ++--- Justfile | 6 +++-- clr_loader/ffi/__init__.py | 3 ++- clr_loader/hostfxr.py | 10 ++++--- clr_loader/mono.py | 7 ++++- clr_loader/netfx.py | 5 ++-- clr_loader/util/find.py | 28 ++++++++++---------- pyproject.toml | 2 ++ uv.lock | 53 +++++++++++++++++++++++++++++++++++++- 10 files changed, 94 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf91443..ceb1668 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,8 @@ jobs: run: ruff format --check - name: Check lints run: ruff check + - name: Check types + run: uv run --group dev ty check clr_loader test: runs-on: ${{ matrix.os.instance }} diff --git a/.gitignore b/.gitignore index 79e80d2..ba019a3 100644 --- a/.gitignore +++ b/.gitignore @@ -64,10 +64,8 @@ target/ # pyenv .python-version -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json +# ty +.ty_cache/ # Pyre type checker .pyre/ diff --git a/Justfile b/Justfile index ea6d425..a3d2cde 100644 --- a/Justfile +++ b/Justfile @@ -13,13 +13,15 @@ test: lint: uv run ruff check +typecheck: + uv run ty check clr_loader + format: uv run ruff format -check: lint test +check: lint typecheck test uv run ruff format --check docs output="doc/html/": uv run --group doc sphinx-build doc/ doc/html/ - diff --git a/clr_loader/ffi/__init__.py b/clr_loader/ffi/__init__.py index e5afd4d..f8de6f5 100644 --- a/clr_loader/ffi/__init__.py +++ b/clr_loader/ffi/__init__.py @@ -72,7 +72,8 @@ def _path_to_version(path: Path) -> tuple[int, int, int]: # Handle pre-release versions like "10.0.0-rc.1" by taking only the version part version_part = name.split("-")[0] res = list(map(int, version_part.split("."))) - return tuple(res + [0, 0, 0])[:3] + v = (res + [0, 0, 0])[:3] + return (v[0], v[1], v[2]) except Exception: # noqa return (0, 0, 0) diff --git a/clr_loader/hostfxr.py b/clr_loader/hostfxr.py index ae8eec0..48b88e4 100644 --- a/clr_loader/hostfxr.py +++ b/clr_loader/hostfxr.py @@ -1,6 +1,7 @@ import sys from collections.abc import Generator from pathlib import Path +from typing import Any from .ffi import ffi, load_hostfxr from .types import Runtime, RuntimeInfo, StrOrPath @@ -144,7 +145,7 @@ def info(self): def _get_handle_for_runtime_config( dll, dotnet_root: StrOrPath, runtime_config: StrOrPath ): - params = ffi.new("hostfxr_initialize_parameters*") + params: Any = ffi.new("hostfxr_initialize_parameters*") params.size = ffi.sizeof("hostfxr_initialize_parameters") # params.host_path = ffi.new("char_t[]", encode(sys.executable)) params.host_path = ffi.NULL @@ -164,7 +165,7 @@ def _get_handle_for_runtime_config( def _get_handle_for_dotnet_command_line( dll, dotnet_root: StrOrPath, entry_dll: StrOrPath ): - params = ffi.new("hostfxr_initialize_parameters*") + params: Any = ffi.new("hostfxr_initialize_parameters*") params.size = ffi.sizeof("hostfxr_initialize_parameters") params.host_path = ffi.NULL dotnet_root_p = ffi.new("char_t[]", encode(str(Path(dotnet_root)))) @@ -209,4 +210,7 @@ def encode(string: str): return string.encode("utf8") def decode(char_ptr) -> str: - return ffi.string(char_ptr).decode("utf8") + res = ffi.string(char_ptr) + if isinstance(res, bytes): + return res.decode("utf8") + return str(res) diff --git a/clr_loader/mono.py b/clr_loader/mono.py index 20c522a..03983e6 100644 --- a/clr_loader/mono.py +++ b/clr_loader/mono.py @@ -186,7 +186,12 @@ def initialize( build = _MONO.mono_get_runtime_build_info() _check_result(build, "Failed to get Mono version") - ver_str = ffi.string(build).decode("utf8") # e.g. '6.12.0.122 (tarball)' + build_bytes = ffi.string(build) + ver_str = ( + build_bytes.decode("utf8") + if isinstance(build_bytes, bytes) + else str(build_bytes) + ) # e.g. '6.12.0.122 (tarball)' ver = re.match(r"^(?P\d+)\.(?P\d+)\.[\d.]+", ver_str) if ver is not None: diff --git a/clr_loader/netfx.py b/clr_loader/netfx.py index 38ee664..c772903 100644 --- a/clr_loader/netfx.py +++ b/clr_loader/netfx.py @@ -10,15 +10,16 @@ class NetFx(Runtime): def __init__(self, domain: str | None = None, config_file: Path | None = None): - self._domain: str | None = None + self._domain: Any = None initialize() + config_file_s: Any if config_file is not None: config_file_s = str(config_file).encode("utf8") else: config_file_s = ffi.NULL - domain_s = domain.encode("utf8") if domain else ffi.NULL + domain_s: Any = domain.encode("utf8") if domain else ffi.NULL self._domain_name: str | None = domain self._config_file: Path | None = config_file diff --git a/clr_loader/util/find.py b/clr_loader/util/find.py index 1c9ce9a..cfa4a79 100644 --- a/clr_loader/util/find.py +++ b/clr_loader/util/find.py @@ -161,31 +161,31 @@ def _find_mono_unix( if candidate.exists(): return candidate - if res := find_library(unix_name): - return Path(res) + if lib_path := find_library(unix_name): + return Path(lib_path) if macos: - res = ( + candidate = ( Path("/Library/Frameworks/Mono.framework/Versions/Current/lib") / lib_filename ) - if res.exists(): - return res + if candidate.exists(): + return candidate # Use HOMEBREW_PREFIX environment variable if available if homebrew_prefix := os.environ.get("HOMEBREW_PREFIX"): - res = Path(homebrew_prefix) / "opt/mono/lib" / lib_filename - if res.exists(): - return res + candidate = Path(homebrew_prefix) / "opt/mono/lib" / lib_filename + if candidate.exists(): + return candidate # Check for native Apple Silicon (arm64) if platform.machine() == "arm64": - res = Path("/opt/homebrew/opt/mono/lib") / lib_filename - if res.exists(): - return res + candidate = Path("/opt/homebrew/opt/mono/lib") / lib_filename + if candidate.exists(): + return candidate else: - res = Path("/usr/local/opt/mono/lib") / lib_filename - if res.exists(): - return res + candidate = Path("/usr/local/opt/mono/lib") / lib_filename + if candidate.exists(): + return candidate raise RuntimeError("Could not find libmono") diff --git a/pyproject.toml b/pyproject.toml index a35bc33..a8bf2c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,8 +33,10 @@ Documentation = "https://pythonnet.github.io/clr-loader/" [dependency-groups] dev = [ + "ty >=0.0.1", "pytest >=9.0, <10.0", "ruff >=0.15", + "types-cffi", ] doc = ["furo >=2025.0", "sphinx >=9.0, <10.0"] diff --git a/uv.lock b/uv.lock index 1ed5596..95bb663 100644 --- a/uv.lock +++ b/uv.lock @@ -2,7 +2,8 @@ version = 1 revision = 3 requires-python = ">=3.10" resolution-markers = [ - "python_full_version >= '3.12'", + "python_full_version >= '3.15'", + "python_full_version >= '3.12' and python_full_version < '3.15'", "python_full_version < '3.12'", ] @@ -342,6 +343,8 @@ dependencies = [ dev = [ { name = "pytest" }, { name = "ruff" }, + { name = "ty" }, + { name = "types-cffi" }, ] doc = [ { name = "furo", marker = "python_full_version >= '3.12'" }, @@ -355,6 +358,8 @@ requires-dist = [{ name = "cffi", specifier = ">=1.17" }] dev = [ { name = "pytest", specifier = ">=9.0,<10.0" }, { name = "ruff", specifier = ">=0.15" }, + { name = "ty", specifier = ">=0.0.1" }, + { name = "types-cffi" }, ] doc = [ { name = "furo", marker = "python_full_version >= '3.12'", specifier = ">=2025.0" }, @@ -800,6 +805,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] +[[package]] +name = "ty" +version = "0.0.82" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/df/b9f35d0b8860f1bbfcb852fbef363301b5ca4b5590cadc6f2a810646d579/ty-0.0.82.tar.gz", hash = "sha256:586e3bf784cece42113929bb64b4761bed5c2127ae6cea294bb82da670066356", size = 7325958, upload-time = "2026-09-17T19:42:12.243Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/0e/259760638b7b1a875743f1f3975253392932ff74397c8e174df7bfb143c3/ty-0.0.82-py3-none-linux_armv6l.whl", hash = "sha256:be58e089e317e1ada7a655216828e76d369386dc958f7a3b3d455ab6dc7f02b6", size = 13833734, upload-time = "2026-09-17T19:41:35.134Z" }, + { url = "https://files.pythonhosted.org/packages/70/f6/c8a1f70989cf44cf2cada5527e268b4216015a7f28f1d8817e4ef1e84a52/ty-0.0.82-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c080ba9a816cfbb3e34c0d3a0071829c602d14bb992bc799d885d4b30008b4ab", size = 13449689, upload-time = "2026-09-17T19:41:37.65Z" }, + { url = "https://files.pythonhosted.org/packages/d5/eb/31fc4bdecb2ff0053443d4ec1e6719a3f3171636ad6da7a8a642c879a4f4/ty-0.0.82-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4f08bd5b751c3b2c46eabe8aee890e7fa54e3bcd97ffa4b8338b0a2508d928c2", size = 13410140, upload-time = "2026-09-17T19:41:40.024Z" }, + { url = "https://files.pythonhosted.org/packages/e7/14/e2af8c64004cd891691915b1522f105a5d601dd8d76a635fec112911f251/ty-0.0.82-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f5ef0401743c2eeac8039ef9ff1bc5dd612bd17ed0259b9b0794e19696548a6", size = 13435552, upload-time = "2026-09-17T19:41:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/52/92/b9519a7854189aa8da4309ecb9c6ff5ab8ac91a433e201322b15c98f9388/ty-0.0.82-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c899643f2a4c1782c06a4bf4959909d1aa2d9d2cac7c1ac3de3161dfcb6909f8", size = 13601427, upload-time = "2026-09-17T19:41:44.242Z" }, + { url = "https://files.pythonhosted.org/packages/ce/be/0c7a39e277a25bca21bbd2dcf640eb4d80cf98f318ef611092cda1222399/ty-0.0.82-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aa6e4e157c6e0b770c327a5d966371b2a7bc7ad4f22d662ac068623cba00eb", size = 14457370, upload-time = "2026-09-17T19:41:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/6f/33/86ff8f1e15c71096838eb0fdfe0f5b578dc717575de6cba591d96baf3b06/ty-0.0.82-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28c141ab481748f1b889eb572e803172ea452af621e514c30a2f30884753b020", size = 14888674, upload-time = "2026-09-17T19:41:48.6Z" }, + { url = "https://files.pythonhosted.org/packages/0a/d2/a1c6315907afd080b4e98d2972441b4803a70c9663a8c7a7f6fd554c7361/ty-0.0.82-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16fff2729fbaa3998ba1cab536dc7d176aef28927b09200588e50982394c56df", size = 14670369, upload-time = "2026-09-17T19:41:50.963Z" }, + { url = "https://files.pythonhosted.org/packages/ae/cd/956ceae4dbe0b116ebc9ecb75dcbe9a0294c50146f76c4c59004e6eb2d77/ty-0.0.82-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2024a33209b37525ffa85a29036985e9ef53a013c0ff321231bd8b23987cf71", size = 14107030, upload-time = "2026-09-17T19:41:53.343Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f8/6025627cec4d6370fb34a2c5cdcf36d89dd4e4ac75b023bda68231e17dfc/ty-0.0.82-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14a0ae8591e08aca5911b545d100e51c9b796350a85b2b36d82219820552d662", size = 14485892, upload-time = "2026-09-17T19:41:55.745Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b6/016f9b36096a9ccd0492ee000380bd75fb51dc4ec7bfef4d6d93f6b3ec2e/ty-0.0.82-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2964202b09108c72232760119c2680cefc33191c428b09e6f620b6c0ab9c6b0f", size = 13383907, upload-time = "2026-09-17T19:41:57.937Z" }, + { url = "https://files.pythonhosted.org/packages/b4/19/1ccc2429399d6ef3d98180340c7abc6793bd90c5039ad41a9003cb8de6df/ty-0.0.82-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f4ed8fc714e35dc11ef4209832e73ad461cf29c9161099eb6c2c541c6b9a3e0e", size = 13617005, upload-time = "2026-09-17T19:42:00.018Z" }, + { url = "https://files.pythonhosted.org/packages/d7/9d/a4a487db72e78413bba2ee9cd49f79b8f461be93e7f6c4dd790febae1cd7/ty-0.0.82-py3-none-musllinux_1_2_i686.whl", hash = "sha256:32a3fe7b1a50f20679fe76c85c0087280419365d900300898a7b95cbcfe510fc", size = 13906545, upload-time = "2026-09-17T19:42:01.942Z" }, + { url = "https://files.pythonhosted.org/packages/0c/4e/a58d530c0f5abe7b846e2d05af9e3f1fb44a1b620ae0abe3892cd3f9596c/ty-0.0.82-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f751044ae63ea9e73ce2eae4c23a839efa4a04d6487d6ea411a590fbe43360b6", size = 14248518, upload-time = "2026-09-17T19:42:04.164Z" }, + { url = "https://files.pythonhosted.org/packages/a6/db/5505229835171d19adcaaeec3a15e0b533b578d24de1329db5da9306c4c6/ty-0.0.82-py3-none-win32.whl", hash = "sha256:0eb66c5ce58434a48d2cae2780d25285c7ca19e07a0d38b1fe39d1a0f75d6fa3", size = 13059890, upload-time = "2026-09-17T19:42:06.161Z" }, + { url = "https://files.pythonhosted.org/packages/13/22/faa427b65baf39915f52a6fa079ece4253e831f3364c567bf91bc1eef65a/ty-0.0.82-py3-none-win_amd64.whl", hash = "sha256:1e4a6c1ca2dc1a6929517d73a47cd626989bb8bd67811425fbe022d06eb2bf73", size = 14003730, upload-time = "2026-09-17T19:42:08.268Z" }, + { url = "https://files.pythonhosted.org/packages/8b/fc/3b95100993d28acb2f3bcf3a49846985f5b2aafab6562627d018f6d23788/ty-0.0.82-py3-none-win_arm64.whl", hash = "sha256:a6f0e8a1f961518746edbccc5986945c01a56a917ebe37117aa028a038d54ae3", size = 13567713, upload-time = "2026-09-17T19:42:10.248Z" }, +] + +[[package]] +name = "types-cffi" +version = "2.1.0.20260827" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "types-setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/bb/dbb844365103e4dc6ea52cdae0946c3ca55a7b8587e6f351710d7ee5bb78/types_cffi-2.1.0.20260827.tar.gz", hash = "sha256:53d1604771ad4ff09a1a90d6f6980ab36bebb6c39476dcc5bbf4896fa510d736", size = 18150, upload-time = "2026-08-27T12:07:21.754Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/fc/690c243ddac627901f1448f5cb29ca3b5463774a00ac932862c60d7ce5cf/types_cffi-2.1.0.20260827-py3-none-any.whl", hash = "sha256:851b6ffa5b962c577a2330b98ac0b60c56c904d086726276946388f58a0cffaf", size = 20525, upload-time = "2026-08-27T12:07:20.836Z" }, +] + +[[package]] +name = "types-setuptools" +version = "84.0.0.20260812" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/cd/3b2a3362a526f91c33f785a291462b2ec448ae531101c62372fc30a21f53/types_setuptools-84.0.0.20260812.tar.gz", hash = "sha256:09bedc248ebbb7a232c9419dfcdca329706e61bf2aa5743e9424d027f1d956b4", size = 46545, upload-time = "2026-08-12T03:52:42.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/e5/3a41cab4066465593facd660b2683c017f3d88d3297cdd26db0677b8e479/types_setuptools-84.0.0.20260812-py3-none-any.whl", hash = "sha256:799c08e4bc6a288e8a0b538afb5f5ff320a08927ba8dbedb30c74ee3ba5d867b", size = 70320, upload-time = "2026-08-12T03:52:41.154Z" }, +] + [[package]] name = "typing-extensions" version = "4.16.0"