diff --git a/src/python_discovery/_cache.py b/src/python_discovery/_cache.py index 4b0fd33..4e7b390 100644 --- a/src/python_discovery/_cache.py +++ b/src/python_discovery/_cache.py @@ -113,7 +113,11 @@ def locked(self) -> Generator[None]: class DiskCache: - """File-system based Python interpreter info cache (``/py_info/4/.json``).""" + """ + File-system based Python interpreter info cache (``/py_info/4/.json``). + + :param root: root directory for the on-disk cache. + """ def __init__(self, root: Path) -> None: self._root = root diff --git a/src/python_discovery/_py_info.py b/src/python_discovery/_py_info.py index be9e6d6..aaccf3a 100644 --- a/src/python_discovery/_py_info.py +++ b/src/python_discovery/_py_info.py @@ -245,7 +245,11 @@ def _try_posix_fallback_executable(self, base_executable: str) -> str | None: return None # in this case we just can't tell easily without poking around FS and calling them, bail def install_path(self, key: str) -> str: - """Return the relative installation path for a given installation scheme *key*.""" + """ + Return the relative installation path for a given installation scheme *key*. + + :param key: sysconfig installation scheme key (e.g. ``"scripts"``, ``"purelib"``). + """ result = self.distutils_install.get(key) if result is None: # pragma: >=3.11 cover # distutils is empty when "venv" scheme is available # set prefixes to empty => result is relative from cwd @@ -308,7 +312,13 @@ def is_venv(self) -> bool: return self.base_prefix is not None def sysconfig_path(self, key: str, config_var: dict[str, str] | None = None, sep: str = os.sep) -> str: - """Return the sysconfig install path for a scheme *key*, optionally substituting config variables.""" + """ + Return the sysconfig install path for a scheme *key*, optionally substituting config variables. + + :param key: sysconfig path key (e.g. ``"purelib"``, ``"include"``). + :param config_var: replacement mapping for sysconfig variables; when ``None`` uses the interpreter's own values. + :param sep: path separator to use in the result. + """ pattern = self.sysconfig_paths.get(key) if pattern is None: return "" @@ -406,14 +416,23 @@ def spec(self) -> str: @classmethod def clear_cache(cls, cache: PyInfoCache) -> None: - """Clear all cached interpreter information from *cache*.""" + """ + Clear all cached interpreter information from *cache*. + + :param cache: the cache store to clear. + """ from ._cached_py_info import clear # noqa: PLC0415 clear(cache) cls._cache_exe_discovery.clear() def satisfies(self, spec: PythonSpec, *, impl_must_match: bool) -> bool: # noqa: PLR0911 - """Check if a given specification can be satisfied by this python interpreter instance.""" + """ + Check if a given specification can be satisfied by this python interpreter instance. + + :param spec: the specification to check against. + :param impl_must_match: when ``True``, the implementation name must match exactly. + """ if spec.path and not self._satisfies_path(spec): return False if impl_must_match and not self._satisfies_implementation(spec): @@ -462,7 +481,11 @@ def _satisfies_version_specifier(self, spec: PythonSpec) -> bool: @classmethod def current(cls, cache: PyInfoCache | None = None) -> PythonInfo: - """Locate the current host interpreter information.""" + """ + Locate the current host interpreter information. + + :param cache: interpreter metadata cache; when ``None`` results are not cached. + """ if cls._current is None: result = cls.from_exe(sys.executable, cache, raise_on_error=True, resolve_to_host=False) if result is None: @@ -473,7 +496,11 @@ def current(cls, cache: PyInfoCache | None = None) -> PythonInfo: @classmethod def current_system(cls, cache: PyInfoCache | None = None) -> PythonInfo: - """Locate the current system interpreter information, resolving through any virtualenv layers.""" + """ + Locate the current system interpreter information, resolving through any virtualenv layers. + + :param cache: interpreter metadata cache; when ``None`` results are not cached. + """ if cls._current_system is None: result = cls.from_exe(sys.executable, cache, raise_on_error=True, resolve_to_host=True) if result is None: @@ -504,7 +531,16 @@ def from_exe( # noqa: PLR0913 resolve_to_host: bool = True, env: Mapping[str, str] | None = None, ) -> PythonInfo | None: - """Get the python information for a given executable path.""" + """ + Get the python information for a given executable path. + + :param exe: path to the Python executable. + :param cache: interpreter metadata cache; when ``None`` results are not cached. + :param raise_on_error: raise on failure instead of returning ``None``. + :param ignore_cache: bypass the cache and re-query the interpreter. + :param resolve_to_host: resolve through virtualenv layers to the system interpreter. + :param env: environment mapping; defaults to :data:`os.environ`. + """ from ._cached_py_info import from_exe # noqa: PLC0415 env = os.environ if env is None else env @@ -583,7 +619,14 @@ def discover_exe( exact: bool = True, env: Mapping[str, str] | None = None, ) -> PythonInfo: - """Discover a matching Python executable under a given *prefix* directory.""" + """ + Discover a matching Python executable under a given *prefix* directory. + + :param cache: interpreter metadata cache. + :param prefix: directory prefix to search under. + :param exact: when ``True``, require an exact version match. + :param env: environment mapping; defaults to :data:`os.environ`. + """ key = prefix, exact if key in self._cache_exe_discovery and prefix: _LOGGER.debug("discover exe from cache %s - exact %s: %r", prefix, exact, self._cache_exe_discovery[key]) diff --git a/src/python_discovery/_py_spec.py b/src/python_discovery/_py_spec.py index c92ba3e..ebdea59 100644 --- a/src/python_discovery/_py_spec.py +++ b/src/python_discovery/_py_spec.py @@ -99,7 +99,20 @@ def _parse_specifier(string_spec: str) -> PythonSpec | None: class PythonSpec: - """Contains specification about a Python Interpreter.""" + """ + Contains specification about a Python Interpreter. + + :param str_spec: the raw specification string as provided by the caller. + :param implementation: interpreter implementation name (e.g. ``"cpython"``, ``"pypy"``), or ``None`` for any. + :param major: required major version, or ``None`` for any. + :param minor: required minor version, or ``None`` for any. + :param micro: required micro (patch) version, or ``None`` for any. + :param architecture: required pointer-size bitness (``32`` or ``64``), or ``None`` for any. + :param path: filesystem path to a specific interpreter, or ``None``. + :param free_threaded: whether a free-threaded build is required, or ``None`` for any. + :param machine: required ISA (e.g. ``"arm64"``), or ``None`` for any. + :param version_specifier: PEP 440 version constraints, or ``None``. + """ def __init__( # noqa: PLR0913, PLR0917 self, @@ -128,7 +141,12 @@ def __init__( # noqa: PLR0913, PLR0917 @classmethod def from_string_spec(cls, string_spec: str) -> PythonSpec: - """Parse a string specification into a PythonSpec.""" + """ + Parse a string specification into a :class:`PythonSpec`. + + :param string_spec: an interpreter spec — an absolute path, a version string, an implementation prefix, + or a PEP 440 specifier. + """ if pathlib.Path(string_spec).is_absolute(): return cls(string_spec, None, None, None, None, None, string_spec) if result := _parse_spec_pattern(string_spec): @@ -138,7 +156,11 @@ def from_string_spec(cls, string_spec: str) -> PythonSpec: return cls(string_spec, None, None, None, None, None, string_spec) def generate_re(self, *, windows: bool) -> re.Pattern: - """Generate a regular expression for matching against a filename.""" + """ + Generate a regular expression for matching interpreter filenames. + + :param windows: if ``True``, require a ``.exe`` suffix. + """ version = r"{}(\.{}(\.{})?)?".format( *(r"\d+" if v is None else v for v in (self.major, self.minor, self.micro)), ) @@ -189,7 +211,11 @@ def _get_required_precision(item: SimpleSpecifier) -> int | None: return None def satisfies(self, spec: PythonSpec) -> bool: # noqa: PLR0911 - """Check if this spec is compatible with the given *spec* (e.g. PEP-514 on Windows).""" + """ + Check if this spec is compatible with the given *spec* (e.g. PEP-514 on Windows). + + :param spec: the requirement to check against. + """ if spec.is_abs and self.is_abs and self.path != spec.path: return False if ( diff --git a/src/python_discovery/_specifier.py b/src/python_discovery/_specifier.py index e94508f..b2e4dc1 100644 --- a/src/python_discovery/_specifier.py +++ b/src/python_discovery/_specifier.py @@ -40,7 +40,17 @@ @dataclass(**_DC_KW) class SimpleVersion: - """Simple PEP 440-like version parser using only standard library.""" + """ + Simple PEP 440-like version parser using only standard library. + + :param version_str: the original version string. + :param major: major version number. + :param minor: minor version number. + :param micro: micro (patch) version number. + :param pre_type: pre-release label (``"a"``, ``"b"``, or ``"rc"``), or ``None``. + :param pre_num: pre-release sequence number, or ``None``. + :param release: the ``(major, minor, micro)`` tuple. + """ version_str: str major: int @@ -117,7 +127,16 @@ def __repr__(self) -> str: @dataclass(**_DC_KW) class SimpleSpecifier: - """Simple PEP 440-like version specifier using only standard library.""" + """ + Simple PEP 440-like version specifier using only standard library. + + :param spec_str: the original specifier string (e.g. ``>=3.10``). + :param operator: the comparison operator (``==``, ``>=``, ``<``, etc.). + :param version_str: the version portion of the specifier, without the operator. + :param is_wildcard: ``True`` if the specifier uses a wildcard suffix (``.*``). + :param wildcard_precision: number of version components before the wildcard, or ``None``. + :param version: the parsed version, or ``None`` if parsing failed. + """ spec_str: str operator: str @@ -230,7 +249,12 @@ def __repr__(self) -> str: @dataclass(**_DC_KW) class SimpleSpecifierSet: - """Simple PEP 440-like specifier set using only standard library.""" + """ + Simple PEP 440-like specifier set using only standard library. + + :param specifiers_str: the original comma-separated specifier string. + :param specifiers: the parsed individual specifiers. + """ specifiers_str: str specifiers: tuple[SimpleSpecifier, ...]