ducktools-pythonfinder 0.6.8__tar.gz → 0.7.1__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.
Files changed (41) hide show
  1. {ducktools_pythonfinder-0.6.8/src/ducktools_pythonfinder.egg-info → ducktools_pythonfinder-0.7.1}/PKG-INFO +12 -1
  2. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/README.md +11 -0
  3. ducktools_pythonfinder-0.7.1/src/ducktools/pythonfinder/_version.py +2 -0
  4. ducktools_pythonfinder-0.7.1/src/ducktools/pythonfinder/venv.py +265 -0
  5. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1/src/ducktools_pythonfinder.egg-info}/PKG-INFO +12 -1
  6. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools_pythonfinder.egg-info/SOURCES.txt +2 -0
  7. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/tests/conftest.py +23 -0
  8. ducktools_pythonfinder-0.7.1/tests/test_venv_finder.py +125 -0
  9. ducktools_pythonfinder-0.6.8/src/ducktools/pythonfinder/_version.py +0 -2
  10. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/.gitignore +0 -0
  11. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/LICENSE +0 -0
  12. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/MANIFEST.in +0 -0
  13. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/pyproject.toml +0 -0
  14. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/scripts/build_zipapp.py +0 -0
  15. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/scripts/detail_this_python.py +0 -0
  16. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/scripts/print_python_versions.py +0 -0
  17. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/setup.cfg +0 -0
  18. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools/pythonfinder/__init__.py +0 -0
  19. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools/pythonfinder/__main__.py +0 -0
  20. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools/pythonfinder/darwin/__init__.py +0 -0
  21. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools/pythonfinder/details_script.py +0 -0
  22. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools/pythonfinder/linux/__init__.py +0 -0
  23. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools/pythonfinder/linux/pyenv_search.py +0 -0
  24. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools/pythonfinder/pythonorg_search.py +0 -0
  25. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools/pythonfinder/shared.py +0 -0
  26. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools/pythonfinder/win32/__init__.py +0 -0
  27. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools/pythonfinder/win32/pyenv_search.py +0 -0
  28. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools/pythonfinder/win32/registry_search.py +0 -0
  29. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools_pythonfinder.egg-info/dependency_links.txt +0 -0
  30. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools_pythonfinder.egg-info/entry_points.txt +0 -0
  31. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools_pythonfinder.egg-info/requires.txt +0 -0
  32. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/src/ducktools_pythonfinder.egg-info/top_level.txt +0 -0
  33. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/tests/sources/python_versions.txt +0 -0
  34. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/tests/sources/release.json +0 -0
  35. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/tests/sources/release_file.json +0 -0
  36. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/tests/test_details_script.py +0 -0
  37. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/tests/test_foldersearch.py +0 -0
  38. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/tests/test_orgsearch.py +0 -0
  39. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/tests/test_pyenv.py +0 -0
  40. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/tests/test_shared.py +0 -0
  41. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.1}/tests/test_uv_finder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ducktools-pythonfinder
3
- Version: 0.6.8
3
+ Version: 0.7.1
4
4
  Summary: Cross platform tool to find available python installations
5
5
  Author: David C Ellis
6
6
  Project-URL: Homepage, https://github.com/davidcellis/ducktools-pythonfinder
@@ -124,6 +124,17 @@ PythonInstall(version=(3, 8, 10, 'final', 0), executable='~\\.pyenv\\pyenv-win\\
124
124
  PythonInstall(version=(3, 13, 0, 'candidate', 1), executable='~\\.pyenv\\pyenv-win\\versions\\3.13.0rc1\\python.exe', architecture='64bit', implementation='cpython', metadata={}, shadowed=False)```
125
125
  ```
126
126
 
127
+ ### Finding venvs ###
128
+
129
+ There is now a submodule to search for virtual environments.
130
+
131
+ ```python
132
+ from ducktools.pythonfinder.venv import list_python_venvs
133
+
134
+ for venv in list_python_venvs():
135
+ print(venv.executable)
136
+ ```
137
+
127
138
  ### Python.org search ###
128
139
 
129
140
  Python.org searches are handled by the `ducktools.pythonfinder.pythonorg_search` module.
@@ -96,6 +96,17 @@ PythonInstall(version=(3, 8, 10, 'final', 0), executable='~\\.pyenv\\pyenv-win\\
96
96
  PythonInstall(version=(3, 13, 0, 'candidate', 1), executable='~\\.pyenv\\pyenv-win\\versions\\3.13.0rc1\\python.exe', architecture='64bit', implementation='cpython', metadata={}, shadowed=False)```
97
97
  ```
98
98
 
99
+ ### Finding venvs ###
100
+
101
+ There is now a submodule to search for virtual environments.
102
+
103
+ ```python
104
+ from ducktools.pythonfinder.venv import list_python_venvs
105
+
106
+ for venv in list_python_venvs():
107
+ print(venv.executable)
108
+ ```
109
+
99
110
  ### Python.org search ###
100
111
 
101
112
  Python.org searches are handled by the `ducktools.pythonfinder.pythonorg_search` module.
@@ -0,0 +1,2 @@
1
+ __version__ = "0.7.1"
2
+ __version_tuple__ = (0, 7, 1)
@@ -0,0 +1,265 @@
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 os
26
+ import sys
27
+
28
+
29
+ from ducktools.classbuilder.prefab import Prefab
30
+ from ducktools.lazyimporter import LazyImporter, FromImport, ModuleImport
31
+
32
+ from .shared import (
33
+ PythonInstall,
34
+ get_install_details,
35
+ version_str_to_tuple,
36
+ version_tuple_to_str,
37
+ )
38
+
39
+
40
+ _laz = LazyImporter(
41
+ [
42
+ ModuleImport("re"),
43
+ ModuleImport("json"),
44
+ FromImport("pathlib", "Path"),
45
+ FromImport("subprocess", "run"),
46
+ ]
47
+ )
48
+
49
+ VENV_CONFIG_NAME = "pyvenv.cfg"
50
+
51
+
52
+ # VIRTUALENV can make some invalid regexes that are just the tuple with dots.
53
+ VIRTUALENV_PY_VER_RE = (
54
+ r"(?P<major>\d+)\.(?P<minor>\d+)\.?(?P<micro>\d*)\.(?P<releaselevel>.+)\.(?P<serial>\d*)?"
55
+ )
56
+
57
+
58
+ class InvalidVEnvError(Exception):
59
+ pass
60
+
61
+
62
+ class PythonPackage(Prefab):
63
+ name: str
64
+ version: str
65
+
66
+
67
+ class PythonVEnv(Prefab):
68
+ folder: str
69
+ executable: str
70
+ version: tuple[int, int, int, str, int]
71
+ parent_path: str
72
+
73
+ @property
74
+ def version_str(self) -> str:
75
+ return version_tuple_to_str(self.version)
76
+
77
+ @property
78
+ def parent_executable(self) -> str:
79
+ if sys.platform == "win32":
80
+ return os.path.join(self.parent_path, "python.exe")
81
+ else:
82
+ return os.path.join(self.parent_path, "python")
83
+
84
+ @property
85
+ def parent_exists(self) -> bool:
86
+ return os.path.exists(self.parent_executable)
87
+
88
+ def get_parent_install(self, cache: list[PythonInstall] | None = None) -> PythonInstall | None:
89
+ install = None
90
+ cache = [] if cache is None else cache
91
+
92
+ if self.parent_exists:
93
+ exe = self.parent_executable
94
+
95
+ # Python installs may be cached, can skip querying exe.
96
+ for inst in cache:
97
+ if os.path.samefile(inst.executable, exe):
98
+ install = inst
99
+ break
100
+
101
+ if install is None:
102
+ install = get_install_details(exe)
103
+
104
+ return install
105
+
106
+ def list_packages(self):
107
+ if not self.parent_exists:
108
+ raise FileNotFoundError(
109
+ f"Parent Python at \"{self.parent_executable}\" does not exist."
110
+ )
111
+
112
+ # Should probably use sys.executable and have pip as a dependency
113
+ # We would need to look at possibly changing how ducktools-env works for that however.
114
+
115
+ data = _laz.run(
116
+ [
117
+ self.parent_executable,
118
+ "-m", "pip",
119
+ "--python", self.executable,
120
+ "list",
121
+ "--format", "json"
122
+ ],
123
+ capture_output=True,
124
+ text=True,
125
+ check=True,
126
+ )
127
+
128
+ raw_packages = _laz.json.loads(data.stdout)
129
+
130
+ packages = [
131
+ PythonPackage(
132
+ name=p["name"],
133
+ version=p["version"],
134
+ )
135
+ for p in raw_packages
136
+ ]
137
+
138
+ return packages
139
+
140
+ @classmethod
141
+ def from_cfg(cls, cfg_path):
142
+ """
143
+ Get a PythonVEnv instance from the path to a config file
144
+
145
+ :param cfg_path:
146
+ :return:
147
+ """
148
+ parent_path, version_str = None, None
149
+ venv_base = cfg_path.parent
150
+
151
+ with cfg_path.open() as f:
152
+ for line in f:
153
+ key, value = (item.strip() for item in line.split("="))
154
+
155
+ if key == "home":
156
+ parent_path = value
157
+ elif key in {"version", "version_info"}:
158
+ # venv and uv use different key names :)
159
+ version_str = value
160
+
161
+ if parent_path and version_str:
162
+ break
163
+ else:
164
+ # Not a valid venv, ignore
165
+ raise InvalidVEnvError(f"Path and version not defined in {cfg_path}")
166
+
167
+ if sys.platform == "win32":
168
+ venv_exe = os.path.join(venv_base, "Scripts", "python.exe")
169
+ else:
170
+ venv_exe = os.path.join(venv_base, "bin", "python")
171
+
172
+ try:
173
+ version_tuple = version_str_to_tuple(version_str)
174
+ except ValueError: # pragma: no cover
175
+ # Might be virtualenv putting in incorrect versions
176
+ parsed_version = _laz.re.fullmatch(VIRTUALENV_PY_VER_RE, version_str)
177
+ if parsed_version:
178
+ major, minor, micro, releaselevel, serial = parsed_version.groups()
179
+ version_tuple = (
180
+ int(major),
181
+ int(minor),
182
+ int(micro) if micro else 0,
183
+ releaselevel,
184
+ int(serial if serial != "" else 0),
185
+ )
186
+ else:
187
+ raise InvalidVEnvError(
188
+ f"Could not determine version from venv version string {version_str}"
189
+ )
190
+
191
+ return cls(
192
+ folder=venv_base,
193
+ executable=venv_exe,
194
+ version=version_tuple,
195
+ parent_path=parent_path
196
+ )
197
+
198
+
199
+ def get_python_venvs(base_dir=None, recursive=False, search_parent_folders=False):
200
+ """
201
+ Yield discoverable python virtual environment information
202
+
203
+ If recursive=True then search_parent_folders is ignored.
204
+
205
+ If you're in a project directory and are looking for a potential venv
206
+ search_parent_folders=True will search parents and yield installs discovered.
207
+
208
+ If you're in a folder of source trees and want to find venvs inside any subfolders
209
+ then use recursive=True.
210
+
211
+ :param base_dir: Base directory to search venvs
212
+ :param recursive: Also check subfolders of the base directory
213
+ :param search_parent_folders: Also search parent folders
214
+ :yield: PythonVEnv details.
215
+ """
216
+ base_dir = _laz.Path.cwd() if base_dir is None else _laz.Path(base_dir)
217
+
218
+ search_folders = [base_dir]
219
+
220
+ # Recursive searches don't also search parent folders.
221
+ if recursive:
222
+ pattern = f"**/{VENV_CONFIG_NAME}"
223
+ else:
224
+ pattern = f"*/{VENV_CONFIG_NAME}"
225
+ if search_parent_folders:
226
+ search_folders.extend(base_dir.parents)
227
+
228
+ for f in search_folders:
229
+ try:
230
+ for conf in f.glob(pattern):
231
+ try:
232
+ env = PythonVEnv.from_cfg(conf)
233
+ except InvalidVEnvError:
234
+ continue
235
+
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
+
244
+ def list_python_venvs(
245
+ base_dir=None,
246
+ recursive=False,
247
+ search_parent_folders=False,
248
+ ) -> list[PythonVEnv]:
249
+ """
250
+ Get a list of discoverable python virtual environment information
251
+
252
+ If recursive=True then search_parent_folders is ignored.
253
+
254
+ :param base_dir: Base directory to search venvs
255
+ :param recursive: Also check subfolders of the base directory
256
+ :param search_parent_folders: Also search parent folders
257
+ :returns: List of Python VEnv details.
258
+ """
259
+ return list(
260
+ get_python_venvs(
261
+ base_dir=base_dir,
262
+ recursive=recursive,
263
+ search_parent_folders=search_parent_folders,
264
+ )
265
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ducktools-pythonfinder
3
- Version: 0.6.8
3
+ Version: 0.7.1
4
4
  Summary: Cross platform tool to find available python installations
5
5
  Author: David C Ellis
6
6
  Project-URL: Homepage, https://github.com/davidcellis/ducktools-pythonfinder
@@ -124,6 +124,17 @@ PythonInstall(version=(3, 8, 10, 'final', 0), executable='~\\.pyenv\\pyenv-win\\
124
124
  PythonInstall(version=(3, 13, 0, 'candidate', 1), executable='~\\.pyenv\\pyenv-win\\versions\\3.13.0rc1\\python.exe', architecture='64bit', implementation='cpython', metadata={}, shadowed=False)```
125
125
  ```
126
126
 
127
+ ### Finding venvs ###
128
+
129
+ There is now a submodule to search for virtual environments.
130
+
131
+ ```python
132
+ from ducktools.pythonfinder.venv import list_python_venvs
133
+
134
+ for venv in list_python_venvs():
135
+ print(venv.executable)
136
+ ```
137
+
127
138
  ### Python.org search ###
128
139
 
129
140
  Python.org searches are handled by the `ducktools.pythonfinder.pythonorg_search` module.
@@ -12,6 +12,7 @@ src/ducktools/pythonfinder/_version.py
12
12
  src/ducktools/pythonfinder/details_script.py
13
13
  src/ducktools/pythonfinder/pythonorg_search.py
14
14
  src/ducktools/pythonfinder/shared.py
15
+ src/ducktools/pythonfinder/venv.py
15
16
  src/ducktools/pythonfinder/darwin/__init__.py
16
17
  src/ducktools/pythonfinder/linux/__init__.py
17
18
  src/ducktools/pythonfinder/linux/pyenv_search.py
@@ -31,6 +32,7 @@ tests/test_orgsearch.py
31
32
  tests/test_pyenv.py
32
33
  tests/test_shared.py
33
34
  tests/test_uv_finder.py
35
+ tests/test_venv_finder.py
34
36
  tests/sources/python_versions.txt
35
37
  tests/sources/release.json
36
38
  tests/sources/release_file.json
@@ -20,11 +20,14 @@
20
20
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
21
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  # SOFTWARE.
23
+ import sys
24
+
23
25
  from pathlib import Path
24
26
 
25
27
  import pytest
26
28
 
27
29
  from ducktools.pythonfinder import details_script
30
+ from ducktools.pythonfinder.shared import get_install_details
28
31
 
29
32
 
30
33
  @pytest.fixture(scope="session")
@@ -37,6 +40,26 @@ def uses_details_script(fs):
37
40
  fs.add_real_file(details_script.__file__)
38
41
 
39
42
 
43
+ @pytest.fixture(scope="session")
44
+ def this_python():
45
+ if sys.platform == "win32":
46
+ py_exe = Path(sys.base_prefix) / "python.exe"
47
+ else:
48
+ py_exe = Path(sys.base_prefix) / "bin" / "python"
49
+
50
+ return get_install_details(str(py_exe))
51
+
52
+
53
+ @pytest.fixture(scope="session")
54
+ def this_venv():
55
+ if sys.platform == "win32":
56
+ exe = sys.executable
57
+ else:
58
+ exe = str(Path(sys.executable).with_name("python"))
59
+ venv = get_install_details(exe)
60
+ return venv
61
+
62
+
40
63
  def pytest_addoption(parser):
41
64
  parser.addoption(
42
65
  "--run-uv-python",
@@ -0,0 +1,125 @@
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
+ import os
24
+ import subprocess
25
+ import sys
26
+ import tempfile
27
+
28
+ from ducktools.pythonfinder.venv import list_python_venvs
29
+
30
+ import pytest
31
+
32
+
33
+ @pytest.fixture(scope="module")
34
+ def with_venvs():
35
+ with tempfile.TemporaryDirectory() as tmpdir:
36
+ # We can't actually use venv directly here as
37
+ # Older python on linux makes invalid venvs
38
+ if sys.platform == "win32":
39
+ python_exe = os.path.join(sys.base_prefix, "python.exe")
40
+ else:
41
+ python_exe = os.path.join(sys.base_prefix, "bin", "python")
42
+
43
+ def make_venv(pth):
44
+ subprocess.run(
45
+ [
46
+ python_exe,
47
+ "-m", "venv",
48
+ "--without-pip",
49
+ os.path.join(tmpdir, pth),
50
+ ],
51
+ check=True,
52
+ capture_output=True
53
+ )
54
+
55
+ make_venv(".venv")
56
+ make_venv("subfolder/.venv")
57
+ make_venv("subfolder/subsubfolder/env")
58
+
59
+ assert os.path.exists(os.path.join(tmpdir, ".venv"))
60
+
61
+ yield tmpdir
62
+
63
+
64
+ def test_no_venvs():
65
+ # Don't use the venv directory here
66
+ with tempfile.TemporaryDirectory() as tmpdir:
67
+ venvs = list_python_venvs(base_dir=tmpdir)
68
+
69
+ assert len(venvs) == 0
70
+
71
+
72
+ def test_local_found(with_venvs):
73
+ venvs = list_python_venvs(base_dir=with_venvs, recursive=False)
74
+
75
+ assert len(venvs) == 1
76
+ assert os.path.samefile(venvs[0].folder, os.path.join(with_venvs, ".venv"))
77
+
78
+
79
+ def test_found_in_parent(with_venvs):
80
+ venvs = list_python_venvs(base_dir=os.path.join(with_venvs, "subfolder"), search_parent_folders=True)
81
+
82
+ assert os.path.samefile(venvs[0].folder, os.path.join(with_venvs, "subfolder/.venv"))
83
+ assert os.path.samefile(venvs[1].folder, os.path.join(with_venvs, ".venv"))
84
+
85
+
86
+ def test_all_found(with_venvs):
87
+ venvs = sorted(
88
+ list_python_venvs(base_dir=with_venvs, recursive=True),
89
+ key=lambda x: x.folder
90
+ )
91
+
92
+ assert len(venvs) == 3
93
+ assert os.path.samefile(venvs[0].folder, os.path.join(with_venvs, ".venv"))
94
+ assert os.path.samefile(venvs[1].folder, os.path.join(with_venvs, "subfolder/.venv"))
95
+ assert os.path.samefile(venvs[2].folder, os.path.join(with_venvs, "subfolder/subsubfolder/env"))
96
+
97
+
98
+ def test_found_parent(with_venvs, this_python, this_venv):
99
+ venv_ex = list_python_venvs(base_dir=with_venvs, recursive=False)[0]
100
+
101
+ assert os.path.samefile(this_python.executable, venv_ex.parent_executable)
102
+
103
+ # We found the base env that created this python, all details match
104
+ parent = venv_ex.get_parent_install()
105
+ assert parent == this_python
106
+
107
+ # venv version str works same as parent
108
+ assert venv_ex.version_str == parent.version_str
109
+
110
+
111
+ def test_found_parent_cache(with_venvs, this_python):
112
+ venv_ex = list_python_venvs(base_dir=with_venvs, recursive=False)[0]
113
+
114
+ parent = venv_ex.get_parent_install(cache=[this_python])
115
+ assert parent == this_python
116
+
117
+
118
+ def test_empty_packages(with_venvs):
119
+ venv_ex = list_python_venvs(base_dir=with_venvs, recursive=False)[0]
120
+
121
+ assert os.path.exists(venv_ex.parent_executable)
122
+ assert os.path.exists(venv_ex.executable)
123
+
124
+ packages = venv_ex.list_packages()
125
+ assert packages == []
@@ -1,2 +0,0 @@
1
- __version__ = "0.6.8"
2
- __version_tuple__ = (0, 6, 8)