ducktools-pythonfinder 0.6.8__tar.gz → 0.7.0__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.0}/PKG-INFO +12 -1
  2. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/README.md +11 -0
  3. ducktools_pythonfinder-0.7.0/src/ducktools/pythonfinder/_version.py +2 -0
  4. ducktools_pythonfinder-0.7.0/src/ducktools/pythonfinder/venv.py +196 -0
  5. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0/src/ducktools_pythonfinder.egg-info}/PKG-INFO +12 -1
  6. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools_pythonfinder.egg-info/SOURCES.txt +2 -0
  7. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/tests/conftest.py +23 -0
  8. ducktools_pythonfinder-0.7.0/tests/test_venv_finder.py +118 -0
  9. ducktools_pythonfinder-0.6.8/src/ducktools/pythonfinder/_version.py +0 -2
  10. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/.gitignore +0 -0
  11. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/LICENSE +0 -0
  12. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/MANIFEST.in +0 -0
  13. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/pyproject.toml +0 -0
  14. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/scripts/build_zipapp.py +0 -0
  15. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/scripts/detail_this_python.py +0 -0
  16. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/scripts/print_python_versions.py +0 -0
  17. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/setup.cfg +0 -0
  18. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools/pythonfinder/__init__.py +0 -0
  19. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools/pythonfinder/__main__.py +0 -0
  20. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools/pythonfinder/darwin/__init__.py +0 -0
  21. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools/pythonfinder/details_script.py +0 -0
  22. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools/pythonfinder/linux/__init__.py +0 -0
  23. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools/pythonfinder/linux/pyenv_search.py +0 -0
  24. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools/pythonfinder/pythonorg_search.py +0 -0
  25. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools/pythonfinder/shared.py +0 -0
  26. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools/pythonfinder/win32/__init__.py +0 -0
  27. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools/pythonfinder/win32/pyenv_search.py +0 -0
  28. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools/pythonfinder/win32/registry_search.py +0 -0
  29. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools_pythonfinder.egg-info/dependency_links.txt +0 -0
  30. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools_pythonfinder.egg-info/entry_points.txt +0 -0
  31. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools_pythonfinder.egg-info/requires.txt +0 -0
  32. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/src/ducktools_pythonfinder.egg-info/top_level.txt +0 -0
  33. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/tests/sources/python_versions.txt +0 -0
  34. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/tests/sources/release.json +0 -0
  35. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/tests/sources/release_file.json +0 -0
  36. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/tests/test_details_script.py +0 -0
  37. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/tests/test_foldersearch.py +0 -0
  38. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/tests/test_orgsearch.py +0 -0
  39. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/tests/test_pyenv.py +0 -0
  40. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/tests/test_shared.py +0 -0
  41. {ducktools_pythonfinder-0.6.8 → ducktools_pythonfinder-0.7.0}/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.0
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.0"
2
+ __version_tuple__ = (0, 7, 0)
@@ -0,0 +1,196 @@
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 PythonPackage(Prefab):
59
+ name: str
60
+ version: str
61
+
62
+
63
+ class PythonVEnv(Prefab):
64
+ folder: str
65
+ executable: str
66
+ version: tuple[int, int, int, str, int]
67
+ parent_path: str
68
+
69
+ @property
70
+ def version_str(self) -> str:
71
+ return version_tuple_to_str(self.version)
72
+
73
+ @property
74
+ def parent_executable(self) -> str:
75
+ if sys.platform == "win32":
76
+ return os.path.join(self.parent_path, "python.exe")
77
+ else:
78
+ return os.path.join(self.parent_path, "python")
79
+
80
+ @property
81
+ def parent_exists(self) -> bool:
82
+ return os.path.exists(self.parent_executable)
83
+
84
+ def get_parent_install(self, cache: list[PythonInstall] | None = None) -> PythonInstall | None:
85
+ install = None
86
+ cache = [] if cache is None else cache
87
+
88
+ if self.parent_exists:
89
+ exe = self.parent_executable
90
+
91
+ # Python installs may be cached, can skip querying exe.
92
+ for inst in cache:
93
+ if os.path.samefile(inst.executable, exe):
94
+ install = inst
95
+ break
96
+
97
+ if install is None:
98
+ install = get_install_details(exe)
99
+
100
+ return install
101
+
102
+ def list_packages(self):
103
+ if not self.parent_exists:
104
+ raise FileNotFoundError(
105
+ f"Parent Python at \"{self.parent_executable}\" does not exist."
106
+ )
107
+
108
+ # Should probably use sys.executable and have pip as a dependency
109
+ # We would need to look at possibly changing how ducktools-env works for that however.
110
+
111
+ data = _laz.run(
112
+ [
113
+ self.parent_executable,
114
+ "-m", "pip",
115
+ "--python", self.executable,
116
+ "list",
117
+ "--format", "json"
118
+ ],
119
+ capture_output=True,
120
+ text=True,
121
+ check=True,
122
+ )
123
+
124
+ raw_packages = _laz.json.loads(data.stdout)
125
+
126
+ packages = [
127
+ PythonPackage(
128
+ name=p["name"],
129
+ version=p["version"],
130
+ )
131
+ for p in raw_packages
132
+ ]
133
+
134
+ return packages
135
+
136
+
137
+ def get_python_venvs(base_dir=None, recursive=True):
138
+ base_dir = os.getcwd() if base_dir is None else base_dir
139
+
140
+ if recursive:
141
+ glob_call = _laz.Path(base_dir).glob(f"**/{VENV_CONFIG_NAME}")
142
+ else:
143
+ glob_call = _laz.Path(base_dir).glob(f"*/{VENV_CONFIG_NAME}")
144
+
145
+ for conf in glob_call:
146
+ parent_path, version_str = None, None
147
+ venv_base = conf.parent
148
+
149
+ with conf.open() as f:
150
+ for line in f:
151
+ key, value = (item.strip() for item in line.split("="))
152
+
153
+ if key == "home":
154
+ parent_path = value
155
+ elif key in {"version", "version_info"}:
156
+ # venv and uv use different key names :)
157
+ version_str = value
158
+
159
+ if parent_path and version_str:
160
+ break
161
+ else:
162
+ # Not a valid venv, ignore
163
+ continue
164
+
165
+ if sys.platform == "win32":
166
+ venv_exe = os.path.join(venv_base, "Scripts", "python.exe")
167
+ else:
168
+ venv_exe = os.path.join(venv_base, "bin", "python")
169
+
170
+ version_tuple = None
171
+ try:
172
+ version_tuple = version_str_to_tuple(version_str)
173
+ except ValueError: # pragma: no cover
174
+ # Might be virtualenv putting in incorrect versions
175
+ parsed_version = _laz.re.fullmatch(VIRTUALENV_PY_VER_RE, version_str)
176
+ if parsed_version:
177
+ major, minor, micro, releaselevel, serial = parsed_version.groups()
178
+ version_tuple = (
179
+ int(major),
180
+ int(minor),
181
+ int(micro) if micro else 0,
182
+ releaselevel,
183
+ int(serial if serial != "" else 0),
184
+ )
185
+
186
+ if version_tuple is not None:
187
+ yield PythonVEnv(
188
+ folder=venv_base,
189
+ executable=venv_exe,
190
+ version=version_tuple,
191
+ parent_path=parent_path
192
+ )
193
+
194
+
195
+ def list_python_venvs(base_dir=None, recursive=True) -> list[PythonVEnv]:
196
+ return list(get_python_venvs(base_dir=base_dir, recursive=recursive))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ducktools-pythonfinder
3
- Version: 0.6.8
3
+ Version: 0.7.0
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,118 @@
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_all_found(with_venvs):
80
+ venvs = sorted(
81
+ list_python_venvs(base_dir=with_venvs, recursive=True),
82
+ key=lambda x: x.folder
83
+ )
84
+
85
+ assert len(venvs) == 3
86
+ assert os.path.samefile(venvs[0].folder, os.path.join(with_venvs, ".venv"))
87
+ assert os.path.samefile(venvs[1].folder, os.path.join(with_venvs, "subfolder/.venv"))
88
+ assert os.path.samefile(venvs[2].folder, os.path.join(with_venvs, "subfolder/subsubfolder/env"))
89
+
90
+
91
+ def test_found_parent(with_venvs, this_python, this_venv):
92
+ venv_ex = list_python_venvs(base_dir=with_venvs, recursive=False)[0]
93
+
94
+ assert os.path.samefile(this_python.executable, venv_ex.parent_executable)
95
+
96
+ # We found the base env that created this python, all details match
97
+ parent = venv_ex.get_parent_install()
98
+ assert parent == this_python
99
+
100
+ # venv version str works same as parent
101
+ assert venv_ex.version_str == parent.version_str
102
+
103
+
104
+ def test_found_parent_cache(with_venvs, this_python):
105
+ venv_ex = list_python_venvs(base_dir=with_venvs, recursive=False)[0]
106
+
107
+ parent = venv_ex.get_parent_install(cache=[this_python])
108
+ assert parent == this_python
109
+
110
+
111
+ def test_empty_packages(with_venvs):
112
+ venv_ex = list_python_venvs(base_dir=with_venvs, recursive=False)[0]
113
+
114
+ assert os.path.exists(venv_ex.parent_executable)
115
+ assert os.path.exists(venv_ex.executable)
116
+
117
+ packages = venv_ex.list_packages()
118
+ assert packages == []
@@ -1,2 +0,0 @@
1
- __version__ = "0.6.8"
2
- __version_tuple__ = (0, 6, 8)