python-discovery 1.2.1__py3-none-any.whl → 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_discovery/__init__.py +6 -3
- python_discovery/_discovery.py +99 -11
- python_discovery/_py_info.py +25 -3
- python_discovery/_py_spec.py +16 -4
- {python_discovery-1.2.1.dist-info → python_discovery-1.3.0.dist-info}/METADATA +3 -2
- {python_discovery-1.2.1.dist-info → python_discovery-1.3.0.dist-info}/RECORD +8 -8
- {python_discovery-1.2.1.dist-info → python_discovery-1.3.0.dist-info}/WHEEL +0 -0
- {python_discovery-1.2.1.dist-info → python_discovery-1.3.0.dist-info}/licenses/LICENSE +0 -0
python_discovery/__init__.py
CHANGED
|
@@ -5,15 +5,16 @@ from __future__ import annotations
|
|
|
5
5
|
from importlib.metadata import version
|
|
6
6
|
|
|
7
7
|
from ._cache import ContentStore, DiskCache, PyInfoCache
|
|
8
|
-
from ._discovery import get_interpreter
|
|
9
|
-
from ._py_info import KNOWN_ARCHITECTURES, PythonInfo
|
|
10
|
-
from ._py_spec import PythonSpec
|
|
8
|
+
from ._discovery import get_interpreter, iter_interpreters
|
|
9
|
+
from ._py_info import KNOWN_ARCHITECTURES, PythonInfo, normalize_isa
|
|
10
|
+
from ._py_spec import KNOWN_IMPLEMENTATIONS, PythonSpec
|
|
11
11
|
from ._specifier import SimpleSpecifier, SimpleSpecifierSet, SimpleVersion
|
|
12
12
|
|
|
13
13
|
__version__ = version("python-discovery")
|
|
14
14
|
|
|
15
15
|
__all__ = [
|
|
16
16
|
"KNOWN_ARCHITECTURES",
|
|
17
|
+
"KNOWN_IMPLEMENTATIONS",
|
|
17
18
|
"ContentStore",
|
|
18
19
|
"DiskCache",
|
|
19
20
|
"PyInfoCache",
|
|
@@ -24,4 +25,6 @@ __all__ = [
|
|
|
24
25
|
"SimpleVersion",
|
|
25
26
|
"__version__",
|
|
26
27
|
"get_interpreter",
|
|
28
|
+
"iter_interpreters",
|
|
29
|
+
"normalize_isa",
|
|
27
30
|
]
|
python_discovery/_discovery.py
CHANGED
|
@@ -14,7 +14,7 @@ from ._py_info import PythonInfo
|
|
|
14
14
|
from ._py_spec import PythonSpec
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
|
-
from collections.abc import Callable, Generator, Iterable, Mapping, Sequence
|
|
17
|
+
from collections.abc import Callable, Generator, Iterable, Iterator, Mapping, Sequence
|
|
18
18
|
|
|
19
19
|
from ._cache import PyInfoCache
|
|
20
20
|
|
|
@@ -36,7 +36,9 @@ def get_interpreter(
|
|
|
36
36
|
the optional *predicate*.
|
|
37
37
|
|
|
38
38
|
:param key: interpreter specification string(s) — an absolute path, a version (``3.12``), an implementation prefix
|
|
39
|
-
(``cpython3.12``), or a
|
|
39
|
+
(``cpython3.12``), or a
|
|
40
|
+
`version specifier <https://packaging.python.org/en/latest/specifications/version-specifiers/>`_
|
|
41
|
+
(``>=3.10``). When a sequence is given each entry is tried in order.
|
|
40
42
|
:param try_first_with: executables to probe before the normal discovery search.
|
|
41
43
|
:param cache: interpreter metadata cache; when ``None`` results are not cached.
|
|
42
44
|
:param env: environment mapping for ``PATH`` lookup; defaults to :data:`os.environ`.
|
|
@@ -51,6 +53,73 @@ def get_interpreter(
|
|
|
51
53
|
return None
|
|
52
54
|
|
|
53
55
|
|
|
56
|
+
def iter_interpreters(
|
|
57
|
+
key: str | Sequence[str] | None = None,
|
|
58
|
+
try_first_with: Iterable[str] | None = None,
|
|
59
|
+
cache: PyInfoCache | None = None,
|
|
60
|
+
env: Mapping[str, str] | None = None,
|
|
61
|
+
predicate: Callable[[PythonInfo], bool] | None = None,
|
|
62
|
+
) -> Iterator[PythonInfo]:
|
|
63
|
+
"""
|
|
64
|
+
Yield every interpreter on the system that satisfies *key*.
|
|
65
|
+
|
|
66
|
+
Iteration order is discovery order: ``try_first_with`` paths first, then the running interpreter, then ``PATH``
|
|
67
|
+
(left to right), then UV-managed installs. Results are deduplicated by the resolved real path of the underlying
|
|
68
|
+
system interpreter, so symlinked aliases (``/bin`` vs ``/usr/bin``) and venvs that symlink to a base interpreter
|
|
69
|
+
collapse to a single entry. Callers that want a different ordering should sort the result.
|
|
70
|
+
|
|
71
|
+
:param key: interpreter specification — same syntax as :func:`get_interpreter`. ``None`` enumerates every Python
|
|
72
|
+
implementation python-discovery knows about (see :data:`KNOWN_IMPLEMENTATIONS`).
|
|
73
|
+
:param try_first_with: executables to probe before the normal discovery search.
|
|
74
|
+
:param cache: interpreter metadata cache; when ``None`` results are not cached. Strongly recommended for
|
|
75
|
+
enumeration, which interrogates every candidate as a subprocess on a cold cache.
|
|
76
|
+
:param env: environment mapping for ``PATH`` lookup; defaults to :data:`os.environ`.
|
|
77
|
+
:param predicate: optional filter applied after the spec match; return ``True`` to include the interpreter.
|
|
78
|
+
"""
|
|
79
|
+
if key is None:
|
|
80
|
+
keys: tuple[str | None, ...] = (None,)
|
|
81
|
+
elif isinstance(key, str):
|
|
82
|
+
keys = (key,)
|
|
83
|
+
else:
|
|
84
|
+
keys = tuple(key)
|
|
85
|
+
first_with = tuple(try_first_with or ())
|
|
86
|
+
env_map = os.environ if env is None else env
|
|
87
|
+
seen: set[str] = set()
|
|
88
|
+
for spec_str in keys:
|
|
89
|
+
yield from _iter_for_spec(spec_str, first_with, cache, env_map, predicate, seen)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _iter_for_spec( # noqa: PLR0913, PLR0917
|
|
93
|
+
spec_str: str | None,
|
|
94
|
+
try_first_with: tuple[str, ...],
|
|
95
|
+
cache: PyInfoCache | None,
|
|
96
|
+
env: Mapping[str, str],
|
|
97
|
+
predicate: Callable[[PythonInfo], bool] | None,
|
|
98
|
+
seen: set[str],
|
|
99
|
+
) -> Iterator[PythonInfo]:
|
|
100
|
+
if spec_str is None:
|
|
101
|
+
spec = PythonSpec("", None, None, None, None, None, None)
|
|
102
|
+
wide = True
|
|
103
|
+
else:
|
|
104
|
+
spec = PythonSpec.from_string_spec(spec_str)
|
|
105
|
+
wide = False
|
|
106
|
+
for interpreter, impl_must_match in propose_interpreters(
|
|
107
|
+
spec, try_first_with, cache, env, all_implementations=wide
|
|
108
|
+
):
|
|
109
|
+
if interpreter is None:
|
|
110
|
+
continue
|
|
111
|
+
if (anchor := interpreter.system_executable or interpreter.executable) is None:
|
|
112
|
+
continue
|
|
113
|
+
if (real_path := os.path.realpath(anchor)) in seen:
|
|
114
|
+
continue
|
|
115
|
+
if not interpreter.satisfies(spec, impl_must_match=impl_must_match):
|
|
116
|
+
continue
|
|
117
|
+
if predicate is not None and not predicate(interpreter):
|
|
118
|
+
continue
|
|
119
|
+
seen.add(real_path)
|
|
120
|
+
yield interpreter
|
|
121
|
+
|
|
122
|
+
|
|
54
123
|
def _find_interpreter(
|
|
55
124
|
key: str,
|
|
56
125
|
try_first_with: Iterable[str],
|
|
@@ -106,6 +175,8 @@ def propose_interpreters(
|
|
|
106
175
|
try_first_with: Iterable[str],
|
|
107
176
|
cache: PyInfoCache | None = None,
|
|
108
177
|
env: Mapping[str, str] | None = None,
|
|
178
|
+
*,
|
|
179
|
+
all_implementations: bool = False,
|
|
109
180
|
) -> Generator[tuple[PythonInfo | None, bool], None, None]:
|
|
110
181
|
"""
|
|
111
182
|
Yield ``(interpreter, impl_must_match)`` candidates for *spec*.
|
|
@@ -114,6 +185,8 @@ def propose_interpreters(
|
|
|
114
185
|
:param try_first_with: executable paths to probe before the standard search.
|
|
115
186
|
:param cache: interpreter metadata cache; when ``None`` results are not cached.
|
|
116
187
|
:param env: environment mapping for ``PATH`` lookup; defaults to :data:`os.environ`.
|
|
188
|
+
:param all_implementations: when ``True`` and *spec* does not constrain the implementation, also surface
|
|
189
|
+
non-CPython binaries on ``PATH`` and under UV's install directory. Used by enumeration APIs.
|
|
117
190
|
"""
|
|
118
191
|
env = os.environ if env is None else env
|
|
119
192
|
tested_exes: set[str] = set()
|
|
@@ -125,8 +198,8 @@ def propose_interpreters(
|
|
|
125
198
|
yield from _propose_explicit(spec, try_first_with, cache, env, tested_exes)
|
|
126
199
|
if spec.path is not None and spec.is_abs: # pragma: no cover # relative spec.path is never abs
|
|
127
200
|
return
|
|
128
|
-
yield from _propose_from_path(spec, cache, env, tested_exes)
|
|
129
|
-
yield from _propose_from_uv(cache, env)
|
|
201
|
+
yield from _propose_from_path(spec, cache, env, tested_exes, all_implementations=all_implementations)
|
|
202
|
+
yield from _propose_from_uv(cache, env, all_implementations=all_implementations)
|
|
130
203
|
|
|
131
204
|
|
|
132
205
|
def _propose_explicit(
|
|
@@ -170,8 +243,10 @@ def _propose_from_path(
|
|
|
170
243
|
cache: PyInfoCache | None,
|
|
171
244
|
env: Mapping[str, str],
|
|
172
245
|
tested_exes: set[str],
|
|
246
|
+
*,
|
|
247
|
+
all_implementations: bool = False,
|
|
173
248
|
) -> Generator[tuple[PythonInfo | None, bool], None, None]:
|
|
174
|
-
find_candidates = path_exe_finder(spec)
|
|
249
|
+
find_candidates = path_exe_finder(spec, all_implementations=all_implementations)
|
|
175
250
|
for pos, path in enumerate(get_paths(env)):
|
|
176
251
|
_LOGGER.debug(LazyPathDump(pos, path, env))
|
|
177
252
|
for exe, impl_must_match in find_candidates(path):
|
|
@@ -189,6 +264,8 @@ def _propose_from_path(
|
|
|
189
264
|
def _propose_from_uv(
|
|
190
265
|
cache: PyInfoCache | None,
|
|
191
266
|
env: Mapping[str, str],
|
|
267
|
+
*,
|
|
268
|
+
all_implementations: bool = False,
|
|
192
269
|
) -> Generator[tuple[PythonInfo | None, bool], None, None]:
|
|
193
270
|
if uv_python_dir := os.getenv("UV_PYTHON_INSTALL_DIR"):
|
|
194
271
|
uv_python_path = Path(uv_python_dir).expanduser()
|
|
@@ -197,10 +274,18 @@ def _propose_from_uv(
|
|
|
197
274
|
else:
|
|
198
275
|
uv_python_path = user_data_path("uv") / "python"
|
|
199
276
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
277
|
+
patterns: list[str] = ["*/bin/python", "*/python.exe"]
|
|
278
|
+
if all_implementations:
|
|
279
|
+
patterns.extend(("*/bin/pypy*", "*/bin/graalpy", "*/pypy*.exe", "*/bin/graalpy.exe"))
|
|
280
|
+
seen_uv_paths: set[str] = set()
|
|
281
|
+
for pattern in patterns:
|
|
282
|
+
for exe_path in uv_python_path.glob(pattern):
|
|
283
|
+
resolved = str(Path(exe_path).resolve())
|
|
284
|
+
if resolved in seen_uv_paths:
|
|
285
|
+
continue
|
|
286
|
+
seen_uv_paths.add(resolved)
|
|
287
|
+
if interpreter := PathPythonInfo.from_exe(str(exe_path), cache, raise_on_error=False, env=env):
|
|
288
|
+
yield interpreter, True
|
|
204
289
|
|
|
205
290
|
|
|
206
291
|
def get_paths(env: Mapping[str, str]) -> Generator[Path, None, None]:
|
|
@@ -244,9 +329,11 @@ class LazyPathDump:
|
|
|
244
329
|
return content
|
|
245
330
|
|
|
246
331
|
|
|
247
|
-
def path_exe_finder(
|
|
332
|
+
def path_exe_finder(
|
|
333
|
+
spec: PythonSpec, *, all_implementations: bool = False
|
|
334
|
+
) -> Callable[[Path], Generator[tuple[Path, bool], None, None]]:
|
|
248
335
|
"""Given a spec, return a function that can be called on a path to find all matching files in it."""
|
|
249
|
-
pat = spec.generate_re(windows=sys.platform == "win32")
|
|
336
|
+
pat = spec.generate_re(windows=sys.platform == "win32", all_implementations=all_implementations)
|
|
250
337
|
direct = spec.str_spec
|
|
251
338
|
if sys.platform == "win32": # pragma: win32 cover
|
|
252
339
|
direct = f"{direct}.exe"
|
|
@@ -331,5 +418,6 @@ __all__ = [
|
|
|
331
418
|
"PathPythonInfo",
|
|
332
419
|
"get_interpreter",
|
|
333
420
|
"get_paths",
|
|
421
|
+
"iter_interpreters",
|
|
334
422
|
"propose_interpreters",
|
|
335
423
|
]
|
python_discovery/_py_info.py
CHANGED
|
@@ -770,21 +770,43 @@ class PythonInfo: # noqa: PLR0904
|
|
|
770
770
|
|
|
771
771
|
KNOWN_ARCHITECTURES: frozenset[str] = frozenset({
|
|
772
772
|
"arm64",
|
|
773
|
-
"i686",
|
|
774
773
|
"loongarch64",
|
|
774
|
+
"ppc",
|
|
775
775
|
"ppc64",
|
|
776
776
|
"ppc64le",
|
|
777
777
|
"riscv64",
|
|
778
778
|
"s390x",
|
|
779
|
+
"sparc64",
|
|
779
780
|
"x86",
|
|
780
781
|
"x86_64",
|
|
781
782
|
})
|
|
782
|
-
"""Known CPU architecture (ISA) values after normalization.
|
|
783
|
+
"""Known CPU architecture (ISA) values after normalization.
|
|
784
|
+
|
|
785
|
+
.. deprecated::
|
|
786
|
+
Use :func:`normalize_isa` instead, which handles both known and unknown architectures.
|
|
787
|
+
"""
|
|
783
788
|
|
|
784
789
|
|
|
785
790
|
def normalize_isa(isa: str) -> str:
|
|
791
|
+
"""
|
|
792
|
+
Normalize an ISA (instruction set architecture) string to a canonical form.
|
|
793
|
+
|
|
794
|
+
Known aliases are mapped (e.g. ``amd64`` → ``x86_64``, ``aarch64`` → ``arm64``).
|
|
795
|
+
Unrecognized values are lowercased and returned as-is.
|
|
796
|
+
"""
|
|
786
797
|
low = isa.lower()
|
|
787
|
-
return {
|
|
798
|
+
return {
|
|
799
|
+
"amd64": "x86_64",
|
|
800
|
+
"aarch64": "arm64",
|
|
801
|
+
"i386": "x86",
|
|
802
|
+
"i486": "x86",
|
|
803
|
+
"i586": "x86",
|
|
804
|
+
"i686": "x86",
|
|
805
|
+
"powerpc": "ppc",
|
|
806
|
+
"powerpc64": "ppc64",
|
|
807
|
+
"powerpc64le": "ppc64le",
|
|
808
|
+
"sparcv9": "sparc64",
|
|
809
|
+
}.get(low, low)
|
|
788
810
|
|
|
789
811
|
|
|
790
812
|
def _main() -> None: # pragma: no cover
|
python_discovery/_py_spec.py
CHANGED
|
@@ -35,6 +35,8 @@ SPECIFIER_PATTERN = re.compile(
|
|
|
35
35
|
_MAX_VERSION_PARTS: Final[int] = 3
|
|
36
36
|
_SINGLE_DIGIT_MAX: Final[int] = 9
|
|
37
37
|
|
|
38
|
+
KNOWN_IMPLEMENTATIONS: Final[tuple[str, ...]] = ("python", "cpython", "pypy", "graalpy")
|
|
39
|
+
|
|
38
40
|
SpecifierSet = SimpleSpecifierSet
|
|
39
41
|
Version = SimpleVersion
|
|
40
42
|
InvalidSpecifier = ValueError
|
|
@@ -111,7 +113,9 @@ class PythonSpec:
|
|
|
111
113
|
:param path: filesystem path to a specific interpreter, or ``None``.
|
|
112
114
|
:param free_threaded: whether a free-threaded build is required, or ``None`` for any.
|
|
113
115
|
:param machine: required ISA (e.g. ``"arm64"``), or ``None`` for any.
|
|
114
|
-
:param version_specifier:
|
|
116
|
+
:param version_specifier:
|
|
117
|
+
`version specifier <https://packaging.python.org/en/latest/specifications/version-specifiers/>`_
|
|
118
|
+
constraints, or ``None``.
|
|
115
119
|
"""
|
|
116
120
|
|
|
117
121
|
def __init__( # noqa: PLR0913, PLR0917
|
|
@@ -145,7 +149,7 @@ class PythonSpec:
|
|
|
145
149
|
Parse a string specification into a :class:`PythonSpec`.
|
|
146
150
|
|
|
147
151
|
:param string_spec: an interpreter spec — an absolute path, a version string, an implementation prefix,
|
|
148
|
-
or a
|
|
152
|
+
or a `version specifier <https://packaging.python.org/en/latest/specifications/version-specifiers/>`_.
|
|
149
153
|
"""
|
|
150
154
|
if pathlib.Path(string_spec).is_absolute():
|
|
151
155
|
return cls(string_spec, None, None, None, None, None, string_spec)
|
|
@@ -155,16 +159,23 @@ class PythonSpec:
|
|
|
155
159
|
return result
|
|
156
160
|
return cls(string_spec, None, None, None, None, None, string_spec)
|
|
157
161
|
|
|
158
|
-
def generate_re(self, *, windows: bool) -> re.Pattern:
|
|
162
|
+
def generate_re(self, *, windows: bool, all_implementations: bool = False) -> re.Pattern:
|
|
159
163
|
"""
|
|
160
164
|
Generate a regular expression for matching interpreter filenames.
|
|
161
165
|
|
|
162
166
|
:param windows: if ``True``, require a ``.exe`` suffix.
|
|
167
|
+
:param all_implementations: when ``True`` and the spec does not constrain the implementation, match every
|
|
168
|
+
filename in :data:`KNOWN_IMPLEMENTATIONS` instead of only ``python``. Used by enumeration APIs.
|
|
163
169
|
"""
|
|
164
170
|
version = r"{}(\.{}(\.{})?)?".format(
|
|
165
171
|
*(r"\d+" if v is None else v for v in (self.major, self.minor, self.micro)),
|
|
166
172
|
)
|
|
167
|
-
|
|
173
|
+
if self.implementation is not None:
|
|
174
|
+
impl = f"python|{re.escape(self.implementation)}"
|
|
175
|
+
elif all_implementations:
|
|
176
|
+
impl = "|".join(re.escape(i) for i in KNOWN_IMPLEMENTATIONS)
|
|
177
|
+
else:
|
|
178
|
+
impl = "python"
|
|
168
179
|
mod = "t?" if self.free_threaded else ""
|
|
169
180
|
suffix = r"\.exe" if windows else ""
|
|
170
181
|
version_conditional = "?" if windows or self.major is None else ""
|
|
@@ -254,6 +265,7 @@ class PythonSpec:
|
|
|
254
265
|
|
|
255
266
|
|
|
256
267
|
__all__ = [
|
|
268
|
+
"KNOWN_IMPLEMENTATIONS",
|
|
257
269
|
"InvalidSpecifier",
|
|
258
270
|
"InvalidVersion",
|
|
259
271
|
"PythonSpec",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-discovery
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Python interpreter discovery
|
|
5
5
|
Project-URL: Changelog, https://github.com/tox-dev/python-discovery/releases
|
|
6
6
|
Project-URL: Documentation, https://python-discovery.readthedocs.io
|
|
@@ -99,7 +99,8 @@ The `get_interpreter()` function accepts various specification formats:
|
|
|
99
99
|
- Absolute path: `/usr/bin/python3.12`
|
|
100
100
|
- Version: `3.12`
|
|
101
101
|
- Implementation prefix: `cpython3.12`
|
|
102
|
-
-
|
|
102
|
+
- [Version specifier](https://packaging.python.org/en/latest/specifications/version-specifiers/): `>=3.10`,
|
|
103
|
+
`>=3.11,<3.13`
|
|
103
104
|
|
|
104
105
|
## Documentation
|
|
105
106
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
python_discovery/__init__.py,sha256=
|
|
1
|
+
python_discovery/__init__.py,sha256=Hi4j3pMvC8Fo8KoNL-wOHsPSCUHwY4c0qvv5sjnFzME,808
|
|
2
2
|
python_discovery/_cache.py,sha256=bG4fD-h2pdPmb4v9BldbXmJ5Tlt1boiH1Qrz8kp9Dds,5063
|
|
3
3
|
python_discovery/_cached_py_info.py,sha256=1AFEoc_-OaCi1PXmaFz9Iy_bEuqOhJdFSDFHljzD-EE,8540
|
|
4
4
|
python_discovery/_compat.py,sha256=iqxGuhK9rPaiui80G1H1KrweoMJ16gyLYEmUrSXrp5c,709
|
|
5
|
-
python_discovery/_discovery.py,sha256=
|
|
6
|
-
python_discovery/_py_info.py,sha256
|
|
7
|
-
python_discovery/_py_spec.py,sha256=
|
|
5
|
+
python_discovery/_discovery.py,sha256=tlcmGDsOL8zitSSQSws9cXYt6MKT6ENiVtfAjQBzBtw,16820
|
|
6
|
+
python_discovery/_py_info.py,sha256=MLDQJbqamOdVev9_OH1b-DcQqTJR0h3RA3dHyB-bAgM,34446
|
|
7
|
+
python_discovery/_py_spec.py,sha256=rmABAbCh0oZ48uLT-2TfOj8GDQu5rmNBIa5dq5vaNCc,10305
|
|
8
8
|
python_discovery/_specifier.py,sha256=PTh6suNTC0hMnFSxFYF_vggdciFLnwxoh1YflSbgCXA,10368
|
|
9
9
|
python_discovery/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
python_discovery/_windows/__init__.py,sha256=qthcyGh36b3WkeSbZ4ECRRYLbCOCaQu5X_DRZ98tXfk,315
|
|
11
11
|
python_discovery/_windows/_pep514.py,sha256=JYSQ5tOYbSMOKtVVfOEkyYskBhEoTsImNr3DJcv7XNE,7658
|
|
12
12
|
python_discovery/_windows/_propose.py,sha256=iMJf6l0TU4SvuV8UsCHHINAe9L5dUT949xyeyMVdong,1565
|
|
13
|
-
python_discovery-1.
|
|
14
|
-
python_discovery-1.
|
|
15
|
-
python_discovery-1.
|
|
16
|
-
python_discovery-1.
|
|
13
|
+
python_discovery-1.3.0.dist-info/METADATA,sha256=0an9XeuI3H011i75sO0571tS0rmyOG28Q66WajKbIJQ,5483
|
|
14
|
+
python_discovery-1.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
15
|
+
python_discovery-1.3.0.dist-info/licenses/LICENSE,sha256=kOJeH68qSq6o2V7o5_VziwUqGswMnFgGgQH5Mahr1Yg,1023
|
|
16
|
+
python_discovery-1.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|