From e46128e7ece9755e0b11d1737e39e3e21435d747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Wed, 20 Dec 2023 23:57:22 -0600 Subject: [PATCH 1/8] Document `markers.default_environment()` --- docs/conf.py | 1 + docs/markers.rst | 17 ++++++- docs/requirements.txt | 2 + pyproject.toml | 1 + src/packaging/_compat.py | 35 +++++++++++++++ src/packaging/markers.py | 95 ++++++++++++++++++++++++++++++++++++--- src/packaging/metadata.py | 19 +------- 7 files changed, 145 insertions(+), 25 deletions(-) create mode 100644 src/packaging/_compat.py diff --git a/docs/conf.py b/docs/conf.py index d89b26aa..b4da79ee 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,6 +21,7 @@ "sphinx.ext.doctest", "sphinx.ext.extlinks", "sphinx.ext.intersphinx", + "sphinx_toolbox.more_autodoc.autotypeddict", ] # General information about the project. diff --git a/docs/markers.rst b/docs/markers.rst index d3b7676d..dc010c72 100644 --- a/docs/markers.rst +++ b/docs/markers.rst @@ -62,8 +62,8 @@ Reference Evaluate the marker given the context of the current Python process. - :param dict environment: A dictionary containing keys and values to - override the detected environment. + :param Environment environment: A dictionary containing keys and values to + override the detected environment. :raises: UndefinedComparison: If the marker uses a comparison on strings which are not valid versions per the :ref:`specification of version specifiers @@ -71,6 +71,19 @@ Reference :raises: UndefinedEnvironmentName: If the marker accesses a value that isn't present inside of the environment dictionary. + :rtype: bool + +.. autotypeddict:: packaging.markers.Environment + + A dictionary that represents a Python environment. + +.. function:: default_environment() + + Returns a dictionary representing the current Python process. This is the + base environment that is used when evaluating markers in + :meth:`Marker.evaluate`. + + :rtype: Environment .. exception:: InvalidMarker diff --git a/docs/requirements.txt b/docs/requirements.txt index a95ae18b..299a951c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,3 @@ furo +sphinx-toolbox +typing-extensions>=4.1.0; python_version < "3.9" diff --git a/pyproject.toml b/pyproject.toml index 9fa0fb17..0b3783aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ exclude_lines = ["pragma: no cover", "@abc.abstractmethod", "@abc.abstractproper strict = true show_error_codes = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] +warn_unused_ignores = true [[tool.mypy.overrides]] module = ["_manylinux"] diff --git a/src/packaging/_compat.py b/src/packaging/_compat.py new file mode 100644 index 00000000..c00b639c --- /dev/null +++ b/src/packaging/_compat.py @@ -0,0 +1,35 @@ +import sys +import typing + +if sys.version_info[:2] >= (3, 8): # pragma: no cover + from typing import Literal +elif typing.TYPE_CHECKING: # pragma: no cover + from typing_extensions import Literal +else: # pragma: no cover + try: + from typing_extensions import Literal + except ImportError: + + class Literal: + def __init_subclass__(*_args, **_kwargs): + pass + + +if sys.version_info[:2] >= (3, 9): # pragma: no cover + from typing import TypedDict +elif typing.TYPE_CHECKING: # pragma: no cover + from typing_extensions import TypedDict +else: # pragma: no cover + try: + from typing_extensions import TypedDict + except ImportError: + + class TypedDict: + def __init_subclass__(*_args, **_kwargs): + pass + + +__all__ = [ + "Literal", + "TypedDict", +] diff --git a/src/packaging/markers.py b/src/packaging/markers.py index 8b98fca7..3c1d62fa 100644 --- a/src/packaging/markers.py +++ b/src/packaging/markers.py @@ -8,6 +8,7 @@ import sys from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from ._compat import TypedDict from ._parser import ( MarkerAtom, MarkerList, @@ -50,6 +51,89 @@ class UndefinedEnvironmentName(ValueError): """ +class Environment(TypedDict, total=False): + implementation_name: str + """The implementation's identifier, e.g. ``'cpython'``.""" + + implementation_version: str + """ + The implementation's version, e.g. ``'3.13.0a2'`` for CPython 3.13.0a2, or + ``'7.3.13'`` for PyPy3.10 v7.3.13. + """ + + os_name: str + """ + The value of :py:data:`os.name`. The name of the operating system dependent module + imported, e.g. ``'posix'``. + """ + + platform_machine: str + """ + Returns the machine type, e.g. ``'i386'``. + + An empty string if the value cannot be determined. + """ + + platform_release: str + """ + The system's release, e.g. ``'2.2.0'`` or ``'NT'``. + + An empty string if the value cannot be determined. + """ + + platform_system: str + """ + The system/OS name, e.g. ``'Linux'``, ``'Windows'`` or ``'Java'``. + + An empty string if the value cannot be determined. + """ + + platform_version: str + """ + The system's release version, e.g. ``'#3 on degas'``. + + An empty string if the value cannot be determined. + """ + + python_full_version: str + """ + The Python version as string ``'major.minor.patchlevel'``. + + Note that unlike the Python :py:data:`sys.version`, this value will always include + the patchlevel (it defaults to 0). + """ + + platform_python_implementation: str + """ + A string identifying the Python implementation. + + Currently, the following implementations are identified: ``'CPython'`` (C + implementation of Python), ``'IronPython'`` (.NET implementation of Python), + ``'Jython'`` (Java implementation of Python), ``'PyPy'`` (Python implementation of + Python). + """ + + python_version: str + """The Python version as string ``'major.minor'``.""" + + sys_platform: str + """ + This string contains a platform identifier that can be used to append + platform-specific components to :py:data:`sys.path`, for instance. + + For Unix systems, except on Linux and AIX, this is the lowercased OS name as + returned by ``uname -s`` with the first part of the version as returned by + ``uname -r`` appended, e.g. ``'sunos5'`` or ``'freebsd8'``, at the time when Python + was built. + """ + + extra: Optional[str] + """ + An optional string used by wheels to signal which specifications apply to a given + extra in the wheel ``METADATA`` file. + """ + + def _normalize_extra_values(results: Any) -> Any: """ Normalize extra values. @@ -134,10 +218,11 @@ def _normalize(*values: str, key: str) -> Tuple[str, ...]: return values -def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) -> bool: +def _evaluate_markers(markers: MarkerList, environment: Environment) -> bool: groups: List[List[bool]] = [[]] for marker in markers: + assert isinstance(marker, (list, tuple, str)) if isinstance(marker, list): @@ -147,12 +232,12 @@ def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) -> bool: if isinstance(lhs, Variable): environment_key = lhs.value - lhs_value = environment[environment_key] + lhs_value = environment[environment_key] # type: ignore[literal-required] # noqa: E501 rhs_value = rhs.value else: lhs_value = lhs.value environment_key = rhs.value - rhs_value = environment[environment_key] + rhs_value = environment[environment_key] # type: ignore[literal-required] # noqa: E501 lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key) groups[-1].append(_eval_op(lhs_value, op, rhs_value)) @@ -172,7 +257,7 @@ def format_full_version(info: "sys._version_info") -> str: return version -def default_environment() -> Dict[str, str]: +def default_environment() -> Environment: iver = format_full_version(sys.implementation.version) implementation_name = sys.implementation.name return { @@ -231,7 +316,7 @@ def __eq__(self, other: Any) -> bool: return str(self) == str(other) - def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool: + def evaluate(self, environment: Optional[Environment] = None) -> bool: """Evaluate a marker. Return the boolean from evaluating the given marker against the diff --git a/src/packaging/metadata.py b/src/packaging/metadata.py index fb274930..e471d266 100644 --- a/src/packaging/metadata.py +++ b/src/packaging/metadata.py @@ -3,7 +3,6 @@ import email.message import email.parser import email.policy -import sys import typing from typing import ( Any, @@ -19,25 +18,9 @@ ) from . import requirements, specifiers, utils, version as version_module +from ._compat import Literal, TypedDict T = typing.TypeVar("T") -if sys.version_info[:2] >= (3, 8): # pragma: no cover - from typing import Literal, TypedDict -else: # pragma: no cover - if typing.TYPE_CHECKING: - from typing_extensions import Literal, TypedDict - else: - try: - from typing_extensions import Literal, TypedDict - except ImportError: - - class Literal: - def __init_subclass__(*_args, **_kwargs): - pass - - class TypedDict: - def __init_subclass__(*_args, **_kwargs): - pass try: From 5dca39a3cf2a0ab019cac7ae44a021f8665c95c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Thu, 4 Jan 2024 19:39:24 -0600 Subject: [PATCH 2/8] Revert incorrect annotations --- docs/markers.rst | 4 ++-- src/packaging/markers.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/markers.rst b/docs/markers.rst index dc010c72..5c5bd448 100644 --- a/docs/markers.rst +++ b/docs/markers.rst @@ -62,8 +62,8 @@ Reference Evaluate the marker given the context of the current Python process. - :param Environment environment: A dictionary containing keys and values to - override the detected environment. + :param dict environment: A dictionary containing keys and values to + override the detected environment. :raises: UndefinedComparison: If the marker uses a comparison on strings which are not valid versions per the :ref:`specification of version specifiers diff --git a/src/packaging/markers.py b/src/packaging/markers.py index 3c1d62fa..034ffb79 100644 --- a/src/packaging/markers.py +++ b/src/packaging/markers.py @@ -218,7 +218,7 @@ def _normalize(*values: str, key: str) -> Tuple[str, ...]: return values -def _evaluate_markers(markers: MarkerList, environment: Environment) -> bool: +def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) -> bool: groups: List[List[bool]] = [[]] for marker in markers: @@ -232,12 +232,12 @@ def _evaluate_markers(markers: MarkerList, environment: Environment) -> bool: if isinstance(lhs, Variable): environment_key = lhs.value - lhs_value = environment[environment_key] # type: ignore[literal-required] # noqa: E501 + lhs_value = environment[environment_key] rhs_value = rhs.value else: lhs_value = lhs.value environment_key = rhs.value - rhs_value = environment[environment_key] # type: ignore[literal-required] # noqa: E501 + rhs_value = environment[environment_key] lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key) groups[-1].append(_eval_op(lhs_value, op, rhs_value)) @@ -316,7 +316,7 @@ def __eq__(self, other: Any) -> bool: return str(self) == str(other) - def evaluate(self, environment: Optional[Environment] = None) -> bool: + def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool: """Evaluate a marker. Return the boolean from evaluating the given marker against the @@ -328,10 +328,10 @@ def evaluate(self, environment: Optional[Environment] = None) -> bool: current_environment = default_environment() current_environment["extra"] = "" if environment is not None: - current_environment.update(environment) + current_environment.update(environment) # type: ignore[typeddict-item] # The API used to allow setting extra to None. We need to handle this # case for backwards compatibility. if current_environment["extra"] is None: current_environment["extra"] = "" - return _evaluate_markers(self._markers, current_environment) + return _evaluate_markers(self._markers, current_environment) # type: ignore[arg-type] # noqa: E501 From a61412c8947bd3cf929c4d1880327d96027ba30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Thu, 4 Jan 2024 19:42:07 -0600 Subject: [PATCH 3/8] Remove added newline --- src/packaging/markers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/packaging/markers.py b/src/packaging/markers.py index 034ffb79..239d69a7 100644 --- a/src/packaging/markers.py +++ b/src/packaging/markers.py @@ -222,7 +222,6 @@ def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) -> bool: groups: List[List[bool]] = [[]] for marker in markers: - assert isinstance(marker, (list, tuple, str)) if isinstance(marker, list): From bd241b05d4ed317146e95c491724741d8340ce5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= <16805946+edgarrmondragon@users.noreply.github.com> Date: Mon, 8 Jan 2024 18:57:22 -0600 Subject: [PATCH 4/8] Update src/packaging/markers.py Co-authored-by: Brett Cannon --- src/packaging/markers.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/packaging/markers.py b/src/packaging/markers.py index 239d69a7..e7f81aad 100644 --- a/src/packaging/markers.py +++ b/src/packaging/markers.py @@ -105,12 +105,7 @@ class Environment(TypedDict, total=False): platform_python_implementation: str """ - A string identifying the Python implementation. - - Currently, the following implementations are identified: ``'CPython'`` (C - implementation of Python), ``'IronPython'`` (.NET implementation of Python), - ``'Jython'`` (Java implementation of Python), ``'PyPy'`` (Python implementation of - Python). + A string identifying the Python implementation, e.g. ``'CPython'``. """ python_version: str From eaa432280b40ccd8e87d5d33d44cd330ccf61f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Wed, 10 Jan 2024 19:46:51 -0600 Subject: [PATCH 5/8] Cast `current_environment` to dict --- src/packaging/markers.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/packaging/markers.py b/src/packaging/markers.py index e7f81aad..6e220bf4 100644 --- a/src/packaging/markers.py +++ b/src/packaging/markers.py @@ -6,7 +6,7 @@ import os import platform import sys -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast from ._compat import TypedDict from ._parser import ( @@ -122,12 +122,6 @@ class Environment(TypedDict, total=False): was built. """ - extra: Optional[str] - """ - An optional string used by wheels to signal which specifications apply to a given - extra in the wheel ``METADATA`` file. - """ - def _normalize_extra_values(results: Any) -> Any: """ @@ -319,13 +313,13 @@ def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool: The environment is determined from the current Python process. """ - current_environment = default_environment() + current_environment = cast("dict[str, str]", default_environment()) current_environment["extra"] = "" if environment is not None: - current_environment.update(environment) # type: ignore[typeddict-item] + current_environment.update(environment) # The API used to allow setting extra to None. We need to handle this # case for backwards compatibility. if current_environment["extra"] is None: current_environment["extra"] = "" - return _evaluate_markers(self._markers, current_environment) # type: ignore[arg-type] # noqa: E501 + return _evaluate_markers(self._markers, current_environment) From 08fc194fcf0fcd08b5296839ab7b0af83be81c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Thu, 18 Jan 2024 22:09:05 -0600 Subject: [PATCH 6/8] Drop `total=False` --- src/packaging/markers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packaging/markers.py b/src/packaging/markers.py index 6e220bf4..66b09615 100644 --- a/src/packaging/markers.py +++ b/src/packaging/markers.py @@ -51,7 +51,7 @@ class UndefinedEnvironmentName(ValueError): """ -class Environment(TypedDict, total=False): +class Environment(TypedDict): implementation_name: str """The implementation's identifier, e.g. ``'cpython'``.""" From c78d3d88b32f58c6f866801aab60046aed049bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Sat, 20 Jan 2024 14:04:26 -0600 Subject: [PATCH 7/8] Rename packaging._compat -> packaging._typing --- src/packaging/{_compat.py => _typing.py} | 0 src/packaging/markers.py | 2 +- src/packaging/metadata.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/packaging/{_compat.py => _typing.py} (100%) diff --git a/src/packaging/_compat.py b/src/packaging/_typing.py similarity index 100% rename from src/packaging/_compat.py rename to src/packaging/_typing.py diff --git a/src/packaging/markers.py b/src/packaging/markers.py index 66b09615..dc8d073d 100644 --- a/src/packaging/markers.py +++ b/src/packaging/markers.py @@ -8,7 +8,6 @@ import sys from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast -from ._compat import TypedDict from ._parser import ( MarkerAtom, MarkerList, @@ -18,6 +17,7 @@ parse_marker as _parse_marker, ) from ._tokenizer import ParserSyntaxError +from ._typing import TypedDict from .specifiers import InvalidSpecifier, Specifier from .utils import canonicalize_name diff --git a/src/packaging/metadata.py b/src/packaging/metadata.py index e471d266..2def1809 100644 --- a/src/packaging/metadata.py +++ b/src/packaging/metadata.py @@ -18,7 +18,7 @@ ) from . import requirements, specifiers, utils, version as version_module -from ._compat import Literal, TypedDict +from ._typing import Literal, TypedDict T = typing.TypeVar("T") From 89c091720a59cf4c4ad24ec233fb334b77d50f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= <16805946+edgarrmondragon@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:58:37 -0600 Subject: [PATCH 8/8] Update docs/markers.rst Co-authored-by: Brett Cannon --- docs/markers.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/markers.rst b/docs/markers.rst index 5c5bd448..e802dac0 100644 --- a/docs/markers.rst +++ b/docs/markers.rst @@ -75,7 +75,8 @@ Reference .. autotypeddict:: packaging.markers.Environment - A dictionary that represents a Python environment. + A dictionary that represents a Python environment as captured by + :func:`default_environment`. .. function:: default_environment()