Skip to content

Commit 4f297b4

Browse files
committed
♻️ refactor: remove Discover ABC and Builtin class
The Discover ABC existed for virtualenv's plugin system, which is not relevant to py-discovery as a standalone library. Builtin was the only concrete subclass and added no value over get_interpreter(). Folded multi-spec support into get_interpreter() (accepts str or list[str]) and removed both classes. Renamed _builtin.py to _discovery.py to reflect the new function-only module.
1 parent d2c54c9 commit 4f297b4

12 files changed

Lines changed: 87 additions & 189 deletions

docs/explanation.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ How it works
44
Where does py-discovery look?
55
-------------------------------
66

7-
When you call :class:`~py_discovery.Builtin` ``.run()``, the library checks several locations in
7+
When you call :func:`~py_discovery.get_interpreter`, the library checks several locations in
88
order. It stops as soon as it finds an interpreter that matches your spec.
99

1010
.. mermaid::
1111

1212
flowchart TD
13-
Start["Builtin.run()"] --> AbsPath{"Is spec an<br>absolute path?"}
13+
Start["get_interpreter()"] --> AbsPath{"Is spec an<br>absolute path?"}
1414
AbsPath -->|Yes| TryAbs["Use path directly"]
1515
AbsPath -->|No| TryFirst["try_first_with paths"]
1616
TryFirst --> RelPath{"Is spec a<br>relative path?"}

docs/how-to/standalone-usage.rst

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,22 @@ shell. You can override these to control exactly where the library looks.
2525
.. mermaid::
2626

2727
flowchart TD
28-
Env["Custom env dict"] --> Builtin["Builtin(spec, env=env)"]
29-
Builtin --> PATH["PATH"]
30-
Builtin --> Pyenv["PYENV_ROOT"]
31-
Builtin --> UV["UV_PYTHON_INSTALL_DIR"]
32-
Builtin --> Mise["MISE_DATA_DIR"]
28+
Env["Custom env dict"] --> Call["get_interpreter(spec, env=env)"]
29+
Call --> PATH["PATH"]
30+
Call --> Pyenv["PYENV_ROOT"]
31+
Call --> UV["UV_PYTHON_INSTALL_DIR"]
32+
Call --> Mise["MISE_DATA_DIR"]
3333

3434
style Env fill:#4a90d9,stroke:#2a5f8f,color:#fff
3535

3636
.. code-block:: python
3737
3838
import os
3939
40-
from py_discovery import Builtin
40+
from py_discovery import get_interpreter
4141
4242
env = {**os.environ, "PATH": "/usr/local/bin:/usr/bin"}
43-
discover = Builtin(python_spec=["python3.12"], env=env)
43+
result = get_interpreter("python3.12", env=env)
4444
4545
Read interpreter metadata
4646
---------------------------
@@ -67,10 +67,10 @@ Once you have a :class:`~py_discovery.PythonInfo`, you can inspect everything ab
6767
6868
from pathlib import Path
6969
70-
from py_discovery import Builtin, DiskCache
70+
from py_discovery import DiskCache, get_interpreter
7171
7272
cache = DiskCache(root=Path("~/.cache/py-discovery").expanduser())
73-
info = Builtin(python_spec=["python3.12"], cache=cache).run()
73+
info = get_interpreter("python3.12", cache=cache)
7474
7575
info.executable # Resolved path to the binary.
7676
info.system_executable # The underlying system interpreter (outside any venv).

docs/index.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ repeated lookups are fast.
1212

1313
.. code-block:: python
1414
15-
from py_discovery import Builtin, DiskCache
1615
from pathlib import Path
1716
17+
from py_discovery import DiskCache, get_interpreter
18+
1819
cache = DiskCache(root=Path("~/.cache/py-discovery").expanduser())
19-
result = Builtin(python_spec=["python3.12"], cache=cache).run()
20+
result = get_interpreter("python3.12", cache=cache)
2021
if result is not None:
2122
print(result.executable) # /usr/bin/python3.12
2223
print(result.implementation) # CPython

docs/tutorial/getting-started.rst

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,13 @@ Finding a different interpreter
5656
--------------------------------
5757

5858
Usually you need a *specific* Python version, not the one currently running. Pass a **spec** string
59-
to :class:`~py_discovery.Builtin` and call ``.run()`` to search your system.
59+
to :func:`~py_discovery.get_interpreter` to search your system.
6060

6161
.. mermaid::
6262

6363
flowchart TD
64-
Spec["Spec: python3.12"] --> Builtin["Builtin(python_spec, cache)"]
65-
Builtin --> Run[".run()"]
66-
Run --> Found{"Match found?"}
64+
Spec["Spec: python3.12"] --> Call["get_interpreter(spec, cache)"]
65+
Call --> Found{"Match found?"}
6766
Found -->|Yes| Info["PythonInfo with full metadata"]
6867
Found -->|No| Nil["None"]
6968

@@ -75,15 +74,18 @@ to :class:`~py_discovery.Builtin` and call ``.run()`` to search your system.
7574
7675
from pathlib import Path
7776
78-
from py_discovery import Builtin, DiskCache
77+
from py_discovery import DiskCache, get_interpreter
7978
8079
cache = DiskCache(root=Path("~/.cache/py-discovery").expanduser())
81-
discover = Builtin(python_spec=["python3.12"], cache=cache)
82-
result = discover.run()
80+
result = get_interpreter("python3.12", cache=cache)
8381
if result is not None:
8482
print(result.executable)
8583
86-
You can pass multiple specs -- the library tries each one in order and returns the first match.
84+
You can pass multiple specs as a list -- the library tries each one in order and returns the first match.
85+
86+
.. code-block:: python
87+
88+
result = get_interpreter(["python3.12", "python3.11"], cache=cache)
8789
8890
Writing specs
8991
-------------
@@ -173,6 +175,6 @@ Every call will run a subprocess to query the interpreter, so this is slower for
173175

174176
.. code-block:: python
175177
176-
from py_discovery import Builtin
178+
from py_discovery import get_interpreter
177179
178-
result = Builtin(python_spec=["python3.12"], cache=None).run()
180+
result = get_interpreter("python3.12")

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ urls.Source = "https://github.com/tox-dev/py-discovery"
6363
urls.Tracker = "https://github.com/tox-dev/py-discovery/issues"
6464

6565
[tool.hatch]
66-
build.hooks.vcs.version-file = "src/py_discovery/_version.py"
6766
version.source = "vcs"
6867

6968
[tool.ruff]

src/py_discovery/__init__.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@
22

33
from __future__ import annotations
44

5-
from ._builtin import Builtin, get_interpreter
5+
from importlib.metadata import version
66
from ._cache import ContentStore, DiskCache, PyInfoCache
7-
from ._discover import Discover
7+
from ._discovery import get_interpreter
88
from ._py_info import PythonInfo
99
from ._py_spec import PythonSpec
10-
from ._version import __version__
1110

11+
12+
__version__ = version("py-discovery")
1213
__all__ = [
13-
"Builtin",
1414
"ContentStore",
15-
"Discover",
1615
"DiskCache",
1716
"PyInfoCache",
1817
"PythonInfo",

src/py_discovery/_compat.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import tempfile
1010

1111
IS_WIN = sys.platform == "win32"
12-
1312
LOGGER = logging.getLogger(__name__)
1413

1514

src/py_discovery/_discover.py

Lines changed: 0 additions & 43 deletions
This file was deleted.
Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from platformdirs import user_data_path
1111

1212
from ._compat import IS_WIN, fs_path_id
13-
from ._discover import Discover
1413
from ._py_info import PythonInfo
1514
from ._py_spec import PythonSpec
1615

@@ -22,53 +21,40 @@
2221
LOGGER = logging.getLogger(__name__)
2322

2423

25-
class Builtin(Discover):
26-
python_spec: Sequence[str]
27-
cache: PyInfoCache | None
28-
try_first_with: Sequence[str]
29-
30-
def __init__(
31-
self,
32-
python_spec: Sequence[str] | None = None,
33-
cache: PyInfoCache | None = None,
34-
try_first_with: Sequence[str] | None = None,
35-
env: Mapping[str, str] | None = None,
36-
) -> None:
37-
super().__init__(env=env)
38-
self.python_spec = python_spec or [sys.executable]
39-
self.cache = cache
40-
self.try_first_with = try_first_with or []
41-
42-
def run(self) -> PythonInfo | None:
43-
for python_spec in self.python_spec:
44-
result = get_interpreter(python_spec, self.try_first_with, self.cache, self._env)
45-
if result is not None:
46-
return result
47-
return None
48-
49-
def __repr__(self) -> str:
50-
spec = self.python_spec[0] if len(self.python_spec) == 1 else self.python_spec
51-
return f"{self.__class__.__name__} discover of python_spec={spec!r}"
24+
def get_interpreter(
25+
key: str | Sequence[str],
26+
try_first_with: Iterable[str] | None = None,
27+
cache: PyInfoCache | None = None,
28+
env: Mapping[str, str] | None = None,
29+
) -> PythonInfo | None:
30+
specs = [key] if isinstance(key, str) else key
31+
for spec_str in specs:
32+
if result := _find_interpreter(spec_str, try_first_with or (), cache, env):
33+
return result
34+
return None
5235

5336

54-
def get_interpreter(
55-
key, try_first_with: Iterable[str], cache: PyInfoCache | None = None, env: Mapping[str, str] | None = None
37+
def _find_interpreter(
38+
key: str,
39+
try_first_with: Iterable[str],
40+
cache: PyInfoCache | None = None,
41+
env: Mapping[str, str] | None = None,
5642
) -> PythonInfo | None:
5743
spec = PythonSpec.from_string_spec(key)
5844
LOGGER.info("find interpreter for spec %r", spec)
59-
proposed_paths = set()
45+
proposed_paths: set[tuple[str, bool]] = set()
6046
env = os.environ if env is None else env
6147
for interpreter, impl_must_match in propose_interpreters(spec, try_first_with, cache, env):
6248
if interpreter is None: # pragma: no cover
6349
continue
64-
key = interpreter.system_executable, impl_must_match
65-
if key in proposed_paths:
50+
proposed_key = interpreter.system_executable, impl_must_match
51+
if proposed_key in proposed_paths:
6652
continue
6753
LOGGER.info("proposed %s", interpreter)
6854
if interpreter.satisfies(spec, impl_must_match):
6955
LOGGER.debug("accepted %s", interpreter)
7056
return interpreter
71-
proposed_paths.add(key)
57+
proposed_paths.add(proposed_key)
7258
return None
7359

7460

@@ -292,7 +278,6 @@ class PathPythonInfo(PythonInfo):
292278

293279

294280
__all__ = [
295-
"Builtin",
296281
"LazyPathDump",
297282
"PathPythonInfo",
298283
"get_interpreter",

tests/test_discover.py

Lines changed: 0 additions & 35 deletions
This file was deleted.

0 commit comments

Comments
 (0)