ducktools-pythonfinder 0.7.1__tar.gz → 0.7.3__tar.gz
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.
- {ducktools_pythonfinder-0.7.1/src/ducktools_pythonfinder.egg-info → ducktools_pythonfinder-0.7.3}/PKG-INFO +1 -1
- ducktools_pythonfinder-0.7.3/scripts/list_python_venvs.py +120 -0
- ducktools_pythonfinder-0.7.3/src/ducktools/pythonfinder/_version.py +2 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/venv.py +45 -31
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3/src/ducktools_pythonfinder.egg-info}/PKG-INFO +1 -1
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools_pythonfinder.egg-info/SOURCES.txt +1 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/test_venv_finder.py +23 -0
- ducktools_pythonfinder-0.7.1/src/ducktools/pythonfinder/_version.py +0 -2
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/.gitignore +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/LICENSE +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/MANIFEST.in +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/README.md +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/pyproject.toml +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/scripts/build_zipapp.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/scripts/detail_this_python.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/scripts/print_python_versions.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/setup.cfg +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/__init__.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/__main__.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/darwin/__init__.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/details_script.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/linux/__init__.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/linux/pyenv_search.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/pythonorg_search.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/shared.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/win32/__init__.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/win32/pyenv_search.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/win32/registry_search.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools_pythonfinder.egg-info/dependency_links.txt +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools_pythonfinder.egg-info/entry_points.txt +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools_pythonfinder.egg-info/requires.txt +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools_pythonfinder.egg-info/top_level.txt +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/conftest.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/sources/python_versions.txt +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/sources/release.json +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/sources/release_file.json +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/test_details_script.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/test_foldersearch.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/test_orgsearch.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/test_pyenv.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/test_shared.py +0 -0
- {ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/test_uv_finder.py +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# ducktools-pythonfinder
|
|
2
|
+
# MIT License
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2013-2014 David C Ellis
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
# copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
# SOFTWARE.
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
if sys.version_info < (3, 12):
|
|
28
|
+
raise RuntimeError("This script requires Python 3.12 or newer.")
|
|
29
|
+
|
|
30
|
+
from collections.abc import Iterable, Callable, Generator
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
from ducktools.classbuilder.prefab import Prefab
|
|
34
|
+
from ducktools.pythonfinder.venv import get_python_venvs
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Taken from ducktools-env's main
|
|
38
|
+
def get_columns(
|
|
39
|
+
*,
|
|
40
|
+
data: Iterable,
|
|
41
|
+
headings: list[str],
|
|
42
|
+
attributes: list[str],
|
|
43
|
+
getter: Callable[[object, str], str] = getattr,
|
|
44
|
+
) -> Generator[str]:
|
|
45
|
+
"""
|
|
46
|
+
A helper function to generate a table to print with correct column widths
|
|
47
|
+
|
|
48
|
+
:param data: input data
|
|
49
|
+
:param headings: headings for the top of the table
|
|
50
|
+
:param attributes: attribute names to use for each column
|
|
51
|
+
:param getter: attribute getter function (ex: getattr, dict.get)
|
|
52
|
+
:return: Generator of column lines
|
|
53
|
+
"""
|
|
54
|
+
if len(headings) != len(attributes):
|
|
55
|
+
raise TypeError("Must be the same number of headings as attributes")
|
|
56
|
+
|
|
57
|
+
widths = {
|
|
58
|
+
f"{attrib}": len(head) for attrib, head in zip(attributes, headings)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
data_rows = []
|
|
62
|
+
for d in data:
|
|
63
|
+
row = []
|
|
64
|
+
for attrib in attributes:
|
|
65
|
+
d_text = f"{getter(d, attrib)}"
|
|
66
|
+
d_len = len(d_text)
|
|
67
|
+
widths[f"{attrib}"] = max(widths[attrib], d_len)
|
|
68
|
+
row.append(d_text)
|
|
69
|
+
data_rows.append(row)
|
|
70
|
+
|
|
71
|
+
yield (
|
|
72
|
+
"| "
|
|
73
|
+
+ " | ".join(f"{head:<{widths[attrib]}}"
|
|
74
|
+
for head, attrib in zip(headings, attributes))
|
|
75
|
+
+ " |"
|
|
76
|
+
)
|
|
77
|
+
yield (
|
|
78
|
+
"| "
|
|
79
|
+
+ " | ".join("-" * widths[attrib]
|
|
80
|
+
for attrib in attributes)
|
|
81
|
+
+ " |"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
for row in data_rows:
|
|
85
|
+
yield (
|
|
86
|
+
"| "
|
|
87
|
+
+ " | ".join(f"{item:<{widths[attrib]}}"
|
|
88
|
+
for item, attrib in zip(row, attributes))
|
|
89
|
+
+ " |"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class DisplayVEnv(Prefab):
|
|
94
|
+
version: str
|
|
95
|
+
path: str
|
|
96
|
+
parent_path: str
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_all_venvs():
|
|
100
|
+
cwd = Path.cwd()
|
|
101
|
+
|
|
102
|
+
venvs = get_python_venvs(recursive=True, search_parent_folders=True)
|
|
103
|
+
venv_data = [
|
|
104
|
+
DisplayVEnv(
|
|
105
|
+
version=venv.version_str,
|
|
106
|
+
path=str(Path(venv.folder).relative_to(cwd, walk_up=True)),
|
|
107
|
+
parent_path=venv.parent_path,
|
|
108
|
+
)
|
|
109
|
+
for venv in venvs
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
headings = ["Version", "Path", "Base Runtime"]
|
|
113
|
+
attribs = ["version", "path", "parent_path"]
|
|
114
|
+
|
|
115
|
+
for row in get_columns(data=venv_data, headings=headings, attributes=attribs, getter=getattr):
|
|
116
|
+
print(row)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
get_all_venvs()
|
{ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/venv.py
RENAMED
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
# SOFTWARE.
|
|
23
23
|
from __future__ import annotations
|
|
24
24
|
|
|
25
|
+
try:
|
|
26
|
+
from _collections_abc import Iterable
|
|
27
|
+
except ImportError:
|
|
28
|
+
from collections.abc import Iterable
|
|
29
|
+
|
|
25
30
|
import os
|
|
26
31
|
import sys
|
|
27
32
|
|
|
@@ -103,7 +108,7 @@ class PythonVEnv(Prefab):
|
|
|
103
108
|
|
|
104
109
|
return install
|
|
105
110
|
|
|
106
|
-
def list_packages(self):
|
|
111
|
+
def list_packages(self) -> list[PythonPackage]:
|
|
107
112
|
if not self.parent_exists:
|
|
108
113
|
raise FileNotFoundError(
|
|
109
114
|
f"Parent Python at \"{self.parent_executable}\" does not exist."
|
|
@@ -138,17 +143,17 @@ class PythonVEnv(Prefab):
|
|
|
138
143
|
return packages
|
|
139
144
|
|
|
140
145
|
@classmethod
|
|
141
|
-
def from_cfg(cls, cfg_path):
|
|
146
|
+
def from_cfg(cls, cfg_path: str | os.PathLike) -> PythonVEnv:
|
|
142
147
|
"""
|
|
143
148
|
Get a PythonVEnv instance from the path to a config file
|
|
144
149
|
|
|
145
|
-
:param cfg_path:
|
|
146
|
-
:return:
|
|
150
|
+
:param cfg_path: Path to a virtualenv config file
|
|
151
|
+
:return: PythonVEnv with details relative to that config file
|
|
147
152
|
"""
|
|
148
153
|
parent_path, version_str = None, None
|
|
149
|
-
venv_base = cfg_path
|
|
154
|
+
venv_base = os.path.dirname(cfg_path)
|
|
150
155
|
|
|
151
|
-
with
|
|
156
|
+
with open(cfg_path, 'r') as f:
|
|
152
157
|
for line in f:
|
|
153
158
|
key, value = (item.strip() for item in line.split("="))
|
|
154
159
|
|
|
@@ -192,15 +197,20 @@ class PythonVEnv(Prefab):
|
|
|
192
197
|
folder=venv_base,
|
|
193
198
|
executable=venv_exe,
|
|
194
199
|
version=version_tuple,
|
|
195
|
-
parent_path=parent_path
|
|
200
|
+
parent_path=parent_path,
|
|
196
201
|
)
|
|
197
202
|
|
|
198
203
|
|
|
199
|
-
def get_python_venvs(
|
|
204
|
+
def get_python_venvs(
|
|
205
|
+
base_dir: str | os.PathLike | None = None,
|
|
206
|
+
recursive: bool = False,
|
|
207
|
+
search_parent_folders: bool = False
|
|
208
|
+
) -> Iterable[PythonVEnv]:
|
|
200
209
|
"""
|
|
201
210
|
Yield discoverable python virtual environment information
|
|
202
211
|
|
|
203
|
-
If recursive=True
|
|
212
|
+
If recursive=True and search_parent_folders=True *only* the current working
|
|
213
|
+
directory will be searched recursively. Parent folders will not be searched recursively
|
|
204
214
|
|
|
205
215
|
If you're in a project directory and are looking for a potential venv
|
|
206
216
|
search_parent_folders=True will search parents and yield installs discovered.
|
|
@@ -215,36 +225,40 @@ def get_python_venvs(base_dir=None, recursive=False, search_parent_folders=False
|
|
|
215
225
|
"""
|
|
216
226
|
base_dir = _laz.Path.cwd() if base_dir is None else _laz.Path(base_dir)
|
|
217
227
|
|
|
218
|
-
|
|
228
|
+
cwd_pattern = pattern = f"*/{VENV_CONFIG_NAME}"
|
|
219
229
|
|
|
220
|
-
# Recursive searches don't also search parent folders.
|
|
221
230
|
if recursive:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
pattern = f"*/{VENV_CONFIG_NAME}"
|
|
225
|
-
if search_parent_folders:
|
|
226
|
-
search_folders.extend(base_dir.parents)
|
|
231
|
+
# Only search cwd recursively, parents are searched non-recursively
|
|
232
|
+
cwd_pattern = "*" + pattern
|
|
227
233
|
|
|
228
|
-
for
|
|
234
|
+
for conf in base_dir.glob(cwd_pattern):
|
|
229
235
|
try:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
236
|
+
env = PythonVEnv.from_cfg(conf)
|
|
237
|
+
except InvalidVEnvError:
|
|
238
|
+
continue
|
|
239
|
+
yield env
|
|
240
|
+
|
|
241
|
+
if search_parent_folders:
|
|
242
|
+
# Search parent folders
|
|
243
|
+
for fld in base_dir.parents:
|
|
244
|
+
try:
|
|
245
|
+
for conf in fld.glob(pattern):
|
|
246
|
+
try:
|
|
247
|
+
env = PythonVEnv.from_cfg(conf)
|
|
248
|
+
except InvalidVEnvError:
|
|
249
|
+
continue
|
|
250
|
+
yield env
|
|
251
|
+
except OSError as e:
|
|
252
|
+
# MacOS can error on searching up folders with an invalid argument
|
|
253
|
+
# On Python 3.11 or earlier.
|
|
254
|
+
if e.errno == 22:
|
|
234
255
|
continue
|
|
235
256
|
|
|
236
|
-
yield env
|
|
237
|
-
except OSError as e:
|
|
238
|
-
# MacOS can error on searching up folders with an invalid argument
|
|
239
|
-
# On Python 3.11 or earlier.
|
|
240
|
-
if e.errno == 22:
|
|
241
|
-
continue
|
|
242
|
-
|
|
243
257
|
|
|
244
258
|
def list_python_venvs(
|
|
245
|
-
base_dir=None,
|
|
246
|
-
recursive=False,
|
|
247
|
-
search_parent_folders=False,
|
|
259
|
+
base_dir: str | os.PathLike | None = None,
|
|
260
|
+
recursive: bool = False,
|
|
261
|
+
search_parent_folders: bool = False,
|
|
248
262
|
) -> list[PythonVEnv]:
|
|
249
263
|
"""
|
|
250
264
|
Get a list of discoverable python virtual environment information
|
|
@@ -76,6 +76,13 @@ def test_local_found(with_venvs):
|
|
|
76
76
|
assert os.path.samefile(venvs[0].folder, os.path.join(with_venvs, ".venv"))
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
def test_parent_not_always_searched(with_venvs):
|
|
80
|
+
venvs = list_python_venvs(base_dir=os.path.join(with_venvs, "subfolder"), search_parent_folders=False)
|
|
81
|
+
|
|
82
|
+
assert len(venvs) == 1
|
|
83
|
+
assert os.path.samefile(venvs[0].folder, os.path.join(with_venvs, "subfolder/.venv"))
|
|
84
|
+
|
|
85
|
+
|
|
79
86
|
def test_found_in_parent(with_venvs):
|
|
80
87
|
venvs = list_python_venvs(base_dir=os.path.join(with_venvs, "subfolder"), search_parent_folders=True)
|
|
81
88
|
|
|
@@ -95,6 +102,22 @@ def test_all_found(with_venvs):
|
|
|
95
102
|
assert os.path.samefile(venvs[2].folder, os.path.join(with_venvs, "subfolder/subsubfolder/env"))
|
|
96
103
|
|
|
97
104
|
|
|
105
|
+
def test_recursive_parents(with_venvs):
|
|
106
|
+
venvs = sorted(
|
|
107
|
+
list_python_venvs(
|
|
108
|
+
base_dir=os.path.join(with_venvs, "subfolder"),
|
|
109
|
+
recursive=True,
|
|
110
|
+
search_parent_folders=True,
|
|
111
|
+
),
|
|
112
|
+
key=lambda x: x.folder
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
assert len(venvs) == 3
|
|
116
|
+
assert os.path.samefile(venvs[0].folder, os.path.join(with_venvs, ".venv"))
|
|
117
|
+
assert os.path.samefile(venvs[1].folder, os.path.join(with_venvs, "subfolder/.venv"))
|
|
118
|
+
assert os.path.samefile(venvs[2].folder, os.path.join(with_venvs, "subfolder/subsubfolder/env"))
|
|
119
|
+
|
|
120
|
+
|
|
98
121
|
def test_found_parent(with_venvs, this_python, this_venv):
|
|
99
122
|
venv_ex = list_python_venvs(base_dir=with_venvs, recursive=False)[0]
|
|
100
123
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/scripts/print_python_versions.py
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/__init__.py
RENAMED
|
File without changes
|
{ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/src/ducktools/pythonfinder/shared.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/sources/python_versions.txt
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.7.1 → ducktools_pythonfinder-0.7.3}/tests/sources/release_file.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|