From cd62efb981c57fd4aa86d159429836e058840ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Fri, 30 Oct 2026 16:03:14 -0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(py=5Finfo):=20type=20marker=20?= =?UTF-8?q?keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../+typed-marker-environment.feature.rst | 1 + docs/how-to/standalone-usage.rst | 5 +++-- src/python_discovery/__init__.py | 3 ++- src/python_discovery/_py_info.py | 19 +++++++++++++++++-- tests/py_info/test_py_info.py | 6 +++++- 5 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 docs/changelog/+typed-marker-environment.feature.rst diff --git a/docs/changelog/+typed-marker-environment.feature.rst b/docs/changelog/+typed-marker-environment.feature.rst new file mode 100644 index 0000000..12af904 --- /dev/null +++ b/docs/changelog/+typed-marker-environment.feature.rst @@ -0,0 +1 @@ +Expose the 11 marker keys through ``MarkerEnvironment`` for callers that type-check ``PythonInfo.marker_environment``. diff --git a/docs/how-to/standalone-usage.rst b/docs/how-to/standalone-usage.rst index 50a342b..aeaf37c 100644 --- a/docs/how-to/standalone-usage.rst +++ b/docs/how-to/standalone-usage.rst @@ -153,7 +153,7 @@ Once you have a :class:`~python_discovery.PythonInfo`, you can inspect everythin +platform: str +sysconfig_vars: dict +sysconfig_paths: dict - +marker_environment: dict[str, str] + +marker_environment: MarkerEnvironment +machine: str +free_threaded: bool +debug_build: bool @@ -182,7 +182,8 @@ Once you have a :class:`~python_discovery.PythonInfo`, you can inspect everythin info.marker_environment Use ``marker_environment`` to evaluate :pep:`508` dependency markers for the discovered interpreter. The mapping -contains all standard marker keys, including its implementation version and operating system. +contains all standard marker keys, including its implementation version and operating system. The exported +``MarkerEnvironment`` type names those keys for type checkers. Prefer :attr:`~python_discovery.PythonInfo.system_exe` over the raw :attr:`~python_discovery.PythonInfo.system_executable` field it reads. The field is typed ``str | None`` because it diff --git a/src/python_discovery/__init__.py b/src/python_discovery/__init__.py index 4533386..85cd9d8 100644 --- a/src/python_discovery/__init__.py +++ b/src/python_discovery/__init__.py @@ -6,7 +6,7 @@ from ._cache import ContentStore, DiskCache, PyInfoCache from ._discovery import get_interpreter, iter_interpreters -from ._py_info import KNOWN_ARCHITECTURES, PythonInfo, normalize_isa +from ._py_info import KNOWN_ARCHITECTURES, MarkerEnvironment, PythonInfo, normalize_isa from ._py_spec import KNOWN_IMPLEMENTATIONS, PythonSpec from ._specifier import SimpleSpecifier, SimpleSpecifierSet, SimpleVersion @@ -17,6 +17,7 @@ "KNOWN_IMPLEMENTATIONS", "ContentStore", "DiskCache", + "MarkerEnvironment", "PyInfoCache", "PythonInfo", "PythonSpec", diff --git a/src/python_discovery/_py_info.py b/src/python_discovery/_py_info.py index c93c67c..476d53f 100644 --- a/src/python_discovery/_py_info.py +++ b/src/python_discovery/_py_info.py @@ -11,7 +11,7 @@ from collections import OrderedDict from itertools import product from string import digits -from typing import TYPE_CHECKING, ClassVar, Final, NamedTuple +from typing import TYPE_CHECKING, ClassVar, Final, NamedTuple, TypedDict from ._py_info_collect import PythonInfoCollector @@ -57,7 +57,7 @@ class PythonInfo: # ruff:ignore[too-many-public-methods] os: str free_threaded: bool debug_build: bool - marker_environment: dict[str, str] + marker_environment: MarkerEnvironment prefix: str | None base_prefix: str | None real_prefix: str | None @@ -629,6 +629,20 @@ def _possible_base(self) -> Generator[str, None, None]: yield upper +class MarkerEnvironment(TypedDict): + implementation_name: str + implementation_version: str + os_name: str + platform_machine: str + platform_release: str + platform_system: str + platform_version: str + python_full_version: str + platform_python_implementation: str + python_version: str + sys_platform: str + + KNOWN_ARCHITECTURES: frozenset[str] = frozenset({ "arm64", "loongarch64", @@ -672,6 +686,7 @@ def normalize_isa(isa: str) -> str: __all__ = [ "KNOWN_ARCHITECTURES", + "MarkerEnvironment", "PythonInfo", "VersionInfo", "normalize_isa", diff --git a/tests/py_info/test_py_info.py b/tests/py_info/test_py_info.py index dfb43ea..b078617 100644 --- a/tests/py_info/test_py_info.py +++ b/tests/py_info/test_py_info.py @@ -15,7 +15,7 @@ from packaging.markers import default_environment from setuptools.dist import Distribution -from python_discovery import DiskCache, PythonInfo, PythonSpec +from python_discovery import DiskCache, MarkerEnvironment, PythonInfo, PythonSpec from python_discovery import _cached_py_info as cached_py_info from python_discovery._py_info import VersionInfo @@ -500,6 +500,10 @@ def test_marker_environment_matches_target_interpreter() -> None: assert info.marker_environment == default_environment() +def test_marker_environment_type_has_standard_keys() -> None: + assert MarkerEnvironment.__required_keys__ == default_environment().keys() + + def test_marker_environment_uses_target_platform(tmp_path: Path) -> None: (tmp_path / "sitecustomize.py").write_text( 'import platform\nplatform.machine = lambda: "target-machine"\nplatform.release = lambda: "target-release"\n',