pydepinject 0.0.1.dev1__tar.gz → 0.0.2.dev0__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.
- pydepinject-0.0.2.dev0/MANIFEST.in +4 -0
- {pydepinject-0.0.1.dev1/src/pydepinject.egg-info → pydepinject-0.0.2.dev0}/PKG-INFO +4 -1
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/pyproject.toml +9 -1
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/pydepinject/__init__.py +68 -36
- pydepinject-0.0.2.dev0/src/pydepinject/backends.py +167 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0/src/pydepinject.egg-info}/PKG-INFO +4 -1
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/pydepinject.egg-info/SOURCES.txt +2 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/pydepinject.egg-info/requires.txt +3 -0
- pydepinject-0.0.2.dev0/tests/conftest.py +19 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/tests/test_pydepinject.py +61 -20
- pydepinject-0.0.1.dev1/MANIFEST.in +0 -3
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/LICENSE +0 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/Readme.md +0 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/setup.cfg +0 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/pydepinject.egg-info/dependency_links.txt +0 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/pydepinject.egg-info/top_level.txt +0 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/requirementmanager.egg-info/PKG-INFO +0 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/requirementmanager.egg-info/SOURCES.txt +0 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/requirementmanager.egg-info/dependency_links.txt +0 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/requirementmanager.egg-info/requires.txt +0 -0
- {pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/requirementmanager.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pydepinject
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2.dev0
|
|
4
4
|
Summary: A package to dynamically inject requirements into a virtual environment.
|
|
5
5
|
Author: pydepinject
|
|
6
6
|
License: MIT
|
|
@@ -21,6 +21,8 @@ Classifier: Typing :: Typed
|
|
|
21
21
|
Requires-Python: >=3.9
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
|
+
Requires-Dist: packaging>=22.0
|
|
25
|
+
Requires-Dist: typing_extensions
|
|
24
26
|
Provides-Extra: lint
|
|
25
27
|
Requires-Dist: ruff==0.4.7; extra == "lint"
|
|
26
28
|
Requires-Dist: pyright==1.1.365; extra == "lint"
|
|
@@ -28,6 +30,7 @@ Requires-Dist: isort==5.13.2; extra == "lint"
|
|
|
28
30
|
Provides-Extra: test
|
|
29
31
|
Requires-Dist: pytest==8.2.1; extra == "test"
|
|
30
32
|
Requires-Dist: pytest-cov==5.0.0; extra == "test"
|
|
33
|
+
Requires-Dist: pytest-xdist==3.6.1; extra == "test"
|
|
31
34
|
Provides-Extra: build
|
|
32
35
|
Requires-Dist: check-manifest==0.49; extra == "build"
|
|
33
36
|
Requires-Dist: build==1.2.1; extra == "build"
|
|
@@ -15,6 +15,10 @@ authors = [
|
|
|
15
15
|
license = { text="MIT" }
|
|
16
16
|
keywords = ["virtualenv", "requirements", "dependency management"]
|
|
17
17
|
requires-python = ">=3.9"
|
|
18
|
+
dependencies = [
|
|
19
|
+
"packaging>=22.0",
|
|
20
|
+
"typing_extensions",
|
|
21
|
+
]
|
|
18
22
|
classifiers = [
|
|
19
23
|
"Development Status :: 4 - Beta",
|
|
20
24
|
"Intended Audience :: Developers",
|
|
@@ -42,6 +46,7 @@ lint = [
|
|
|
42
46
|
test = [
|
|
43
47
|
"pytest==8.2.1",
|
|
44
48
|
"pytest-cov==5.0.0",
|
|
49
|
+
"pytest-xdist==3.6.1",
|
|
45
50
|
]
|
|
46
51
|
build = [
|
|
47
52
|
"check-manifest==0.49",
|
|
@@ -62,7 +67,9 @@ log_level = "DEBUG"
|
|
|
62
67
|
|
|
63
68
|
[tool.pytest.ini_options]
|
|
64
69
|
pythonpath = "src"
|
|
65
|
-
|
|
70
|
+
testpaths = ["tests"]
|
|
71
|
+
addopts = "--durations=10 -n auto"
|
|
72
|
+
# Estimated test times: 74s with auto (2), 76s with 6 workers, 113se with no workers.
|
|
66
73
|
|
|
67
74
|
[tool.ruff]
|
|
68
75
|
preview = true
|
|
@@ -130,3 +137,4 @@ twine = {version = "5.1.0"}
|
|
|
130
137
|
|
|
131
138
|
[tool.custom.ci]
|
|
132
139
|
python_versions = ["3.9", "3.10", "3.11", "3.12"]
|
|
140
|
+
packaging_versions = ["22", "23", "24"]
|
|
@@ -6,50 +6,71 @@ import logging
|
|
|
6
6
|
import os
|
|
7
7
|
import pathlib
|
|
8
8
|
import shutil
|
|
9
|
-
import subprocess
|
|
10
9
|
import sys
|
|
11
10
|
import tempfile
|
|
12
11
|
import typing
|
|
13
|
-
import venv
|
|
14
12
|
|
|
15
13
|
if typing.TYPE_CHECKING:
|
|
16
14
|
from collections.abc import Callable
|
|
17
15
|
from types import TracebackType
|
|
18
16
|
from typing import Any
|
|
17
|
+
from .backends import VenvBackend
|
|
19
18
|
|
|
19
|
+
from .backends import VenvBackendRegistry
|
|
20
20
|
|
|
21
|
-
VERSION = "0.0.
|
|
21
|
+
VERSION = "0.0.2dev0"
|
|
22
22
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
24
24
|
logger.setLevel(logging.DEBUG)
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
VENV_ROOT = pathlib.Path(
|
|
27
|
+
os.environ.get("PYDEPINJECT_VENV_ROOT", None)
|
|
28
|
+
or pathlib.Path(tempfile.gettempdir()) / __name__.split(".")[0] / "venvs"
|
|
29
|
+
)
|
|
28
30
|
logger.debug("VENV_ROOT: %s", VENV_ROOT)
|
|
29
31
|
|
|
32
|
+
VENV_BACKENDS = "|".join(VenvBackendRegistry.get_backends())
|
|
33
|
+
logger.debug("VENV_BACKENDS: %s", VENV_BACKENDS)
|
|
34
|
+
|
|
30
35
|
|
|
31
36
|
def is_requirements_satisfied(*packages: str):
|
|
32
37
|
"""Check if the requirements are already satisfied. Return None if it cannot be determined."""
|
|
33
38
|
try:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
import
|
|
39
|
+
from importlib.metadata import PackageNotFoundError, distribution
|
|
40
|
+
|
|
41
|
+
from packaging.requirements import Requirement
|
|
37
42
|
except ImportError:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
try:
|
|
44
|
+
from importlib.metadata import PackageNotFoundError, distribution
|
|
45
|
+
|
|
46
|
+
from packaging.requirements import Requirement
|
|
47
|
+
except ImportError:
|
|
48
|
+
logger.warning(
|
|
49
|
+
"importlib.metadata and packaging not found. Cannot check if requirements are satisfied."
|
|
50
|
+
)
|
|
51
|
+
return None
|
|
42
52
|
|
|
43
53
|
try:
|
|
44
54
|
for package in packages:
|
|
45
55
|
logger.debug("Checking package: %s", package)
|
|
46
|
-
req =
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
req = Requirement(package)
|
|
57
|
+
try:
|
|
58
|
+
dist = distribution(req.name)
|
|
59
|
+
if dist.version not in req.specifier:
|
|
60
|
+
logger.debug(
|
|
61
|
+
"Requirement %s is not satisfied. Version conflict.", package
|
|
62
|
+
)
|
|
63
|
+
return False
|
|
64
|
+
logger.debug("Requirement %s is satisfied", package)
|
|
65
|
+
except PackageNotFoundError:
|
|
66
|
+
logger.debug(
|
|
67
|
+
"Requirement %s is not satisfied. Distribution not found.", package
|
|
68
|
+
)
|
|
69
|
+
return False
|
|
49
70
|
return True
|
|
50
|
-
except
|
|
51
|
-
logger.
|
|
52
|
-
return
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.warning("An error occurred while checking requirements: %s", str(e))
|
|
73
|
+
return None
|
|
53
74
|
|
|
54
75
|
|
|
55
76
|
class RequirementManager:
|
|
@@ -60,6 +81,7 @@ class RequirementManager:
|
|
|
60
81
|
*packages: str,
|
|
61
82
|
venv_name: str | None = None,
|
|
62
83
|
venv_root: pathlib.Path = VENV_ROOT,
|
|
84
|
+
venv_backend: str | None = None,
|
|
63
85
|
recreate: bool = False,
|
|
64
86
|
ephemeral: bool = False,
|
|
65
87
|
):
|
|
@@ -70,6 +92,7 @@ class RequirementManager:
|
|
|
70
92
|
venv_name: The name of the virtual environment. If not provided,
|
|
71
93
|
a unique name will be generated based on the package requirements.
|
|
72
94
|
venv_root: The root directory for virtual environments.
|
|
95
|
+
venv_backend: The virtual environment backend to use. Defaults to $PYDEPINJECT_VENV_NAME or "uv|venv".
|
|
73
96
|
recreate: If True, the virtual environment will be recreated if it exists.
|
|
74
97
|
ephemeral: If True, the virtual environment will be deleted after use.
|
|
75
98
|
"""
|
|
@@ -80,10 +103,31 @@ class RequirementManager:
|
|
|
80
103
|
self.original_syspath = sys.path.copy()
|
|
81
104
|
self._venv_path = venv_root / self.venv_name if self.venv_name else None
|
|
82
105
|
self._venv_root = venv_root
|
|
106
|
+
|
|
107
|
+
venv_backend = (
|
|
108
|
+
venv_backend
|
|
109
|
+
or os.environ.get("PYDEPINJECT_VENV_BACKEND", None)
|
|
110
|
+
or VENV_BACKENDS
|
|
111
|
+
)
|
|
112
|
+
self._venv_backends = [item.strip() for item in venv_backend.split("|")]
|
|
113
|
+
invalid_backends = set(self._venv_backends) - set(VENV_BACKENDS.split("|"))
|
|
114
|
+
if invalid_backends:
|
|
115
|
+
raise ValueError(f"Invalid venv_backend: {','.join(invalid_backends)}")
|
|
116
|
+
|
|
83
117
|
self.ephemeral = ephemeral
|
|
84
118
|
self.recreate = recreate
|
|
85
119
|
self._activated = False
|
|
86
120
|
|
|
121
|
+
@property
|
|
122
|
+
def venv_backend_cls(self) -> type[VenvBackend]:
|
|
123
|
+
"""Returns the virtual environment backend class."""
|
|
124
|
+
supported_backends = VenvBackendRegistry.get_supported_backends()
|
|
125
|
+
for backend in self._venv_backends:
|
|
126
|
+
if backend not in supported_backends:
|
|
127
|
+
continue
|
|
128
|
+
return supported_backends[backend]
|
|
129
|
+
raise ValueError("No supported venv backend found")
|
|
130
|
+
|
|
87
131
|
@property
|
|
88
132
|
def venv_path(self):
|
|
89
133
|
if self._venv_path:
|
|
@@ -101,21 +145,11 @@ class RequirementManager:
|
|
|
101
145
|
if self.venv_path.exists() and not self.recreate:
|
|
102
146
|
return
|
|
103
147
|
logger.debug("Creating virtualenv: %s", self.venv_path)
|
|
104
|
-
|
|
148
|
+
self.venv_backend_cls(self.venv_path).create(clear=self.recreate)
|
|
105
149
|
|
|
106
150
|
def _install_packages(self):
|
|
107
151
|
logger.info("Installing packages: %s", self.packages)
|
|
108
|
-
|
|
109
|
-
pip_args = [
|
|
110
|
-
str(pip_executable),
|
|
111
|
-
"install",
|
|
112
|
-
"--quiet",
|
|
113
|
-
"--no-python-version-warning",
|
|
114
|
-
"--disable-pip-version-check",
|
|
115
|
-
*self.packages,
|
|
116
|
-
]
|
|
117
|
-
logger.debug("Running command: %s", " ".join(pip_args))
|
|
118
|
-
subprocess.check_call(pip_args)
|
|
152
|
+
self.venv_backend_cls(self.venv_path).install(*self.packages)
|
|
119
153
|
|
|
120
154
|
def _activate_venv(self):
|
|
121
155
|
if is_requirements_satisfied(*self.packages):
|
|
@@ -131,11 +165,10 @@ class RequirementManager:
|
|
|
131
165
|
|
|
132
166
|
self._create_virtualenv()
|
|
133
167
|
|
|
134
|
-
venv_site_packages = (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
/ "site-packages"
|
|
168
|
+
venv_site_packages = pathlib.Path(self.venv_path).joinpath(
|
|
169
|
+
"lib",
|
|
170
|
+
f"python{sys.version_info.major}.{sys.version_info.minor}",
|
|
171
|
+
"site-packages",
|
|
139
172
|
)
|
|
140
173
|
os.environ["PYTHONPATH"] = str(venv_site_packages) + (
|
|
141
174
|
os.pathsep + self.original_pythonpath if self.original_pythonpath else ""
|
|
@@ -194,7 +227,6 @@ class RequirementManager:
|
|
|
194
227
|
@functools.wraps(func)
|
|
195
228
|
def wrapper(*args: Any, **kwargs: Any):
|
|
196
229
|
with self:
|
|
197
|
-
print("returning func()")
|
|
198
230
|
return func(*args, **kwargs)
|
|
199
231
|
|
|
200
232
|
return wrapper
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import collections
|
|
4
|
+
import logging
|
|
5
|
+
import pathlib
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import typing
|
|
10
|
+
from typing import Protocol
|
|
11
|
+
|
|
12
|
+
if typing.TYPE_CHECKING:
|
|
13
|
+
from collections.abc import MutableMapping
|
|
14
|
+
|
|
15
|
+
_PYTHON_BIN = sys.executable
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class VenvBackend(Protocol):
|
|
21
|
+
"""Protocol for virtual environment backends."""
|
|
22
|
+
|
|
23
|
+
_path: pathlib.Path
|
|
24
|
+
_PRIORITY: int
|
|
25
|
+
|
|
26
|
+
def __init__(self, path: str | pathlib.Path): ...
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def name(self) -> str: ...
|
|
30
|
+
|
|
31
|
+
def create(self, clear: bool = False) -> None: ...
|
|
32
|
+
|
|
33
|
+
def install(self, *packages: str) -> None: ...
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def is_supported(cls) -> bool: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class VenvBackendRegistry:
|
|
40
|
+
"""Registry of virtual environment backends."""
|
|
41
|
+
|
|
42
|
+
_registry: MutableMapping[str, type[VenvBackend]] = {}
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def register_backend(cls, backend_cls: type[VenvBackend]) -> None:
|
|
46
|
+
instance = backend_cls(
|
|
47
|
+
pathlib.Path()
|
|
48
|
+
) # Create an instance to access the name property
|
|
49
|
+
cls._registry[instance.name] = backend_cls
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def get_backend(cls, name: str) -> type[VenvBackend] | None:
|
|
53
|
+
return cls._registry.get(name)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def has_backend(cls, name: str) -> bool:
|
|
57
|
+
return name in cls._registry
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def get_backends(cls) -> MutableMapping[str, type[VenvBackend]]:
|
|
61
|
+
result: MutableMapping[str, type[VenvBackend]] = collections.OrderedDict()
|
|
62
|
+
for name in sorted(cls._registry, key=lambda x: cls._registry[x]._PRIORITY): # pyright: ignore[reportPrivateUsage]
|
|
63
|
+
result[name] = cls._registry[name]
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def get_supported_backends(cls) -> dict[str, type[VenvBackend]]:
|
|
68
|
+
return {
|
|
69
|
+
name: backend_cls
|
|
70
|
+
for name, backend_cls in cls._registry.items()
|
|
71
|
+
if backend_cls.is_supported()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class VenvBackendVenv:
|
|
76
|
+
"""Virtual environment backend using the venv module."""
|
|
77
|
+
|
|
78
|
+
_NAME = "venv"
|
|
79
|
+
_PRIORITY: int = 0
|
|
80
|
+
|
|
81
|
+
def __init__(self, path: str | pathlib.Path):
|
|
82
|
+
self._path = pathlib.Path(path)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def name(self) -> str:
|
|
86
|
+
return self._NAME
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def is_supported(cls) -> bool:
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
def create(self, clear: bool = False) -> None:
|
|
93
|
+
clear_opt = ["--clear"] if clear else []
|
|
94
|
+
venv_args = [_PYTHON_BIN, "-m", "venv"] + clear_opt + [str(self._path)]
|
|
95
|
+
logger.debug("Running command: %s", " ".join(venv_args))
|
|
96
|
+
subprocess.check_call(venv_args)
|
|
97
|
+
|
|
98
|
+
def install(self, *packages: str) -> None:
|
|
99
|
+
if not self._path.exists():
|
|
100
|
+
raise FileNotFoundError(f"Virtual environment not found: {self._path}")
|
|
101
|
+
|
|
102
|
+
pip_executable = self._path / "bin" / "pip"
|
|
103
|
+
if not pip_executable.exists():
|
|
104
|
+
raise FileNotFoundError(f"pip executable not found: {pip_executable}")
|
|
105
|
+
|
|
106
|
+
pip_args = [
|
|
107
|
+
str(pip_executable),
|
|
108
|
+
"install",
|
|
109
|
+
"--quiet",
|
|
110
|
+
"--no-python-version-warning",
|
|
111
|
+
"--disable-pip-version-check",
|
|
112
|
+
"--upgrade",
|
|
113
|
+
*packages,
|
|
114
|
+
]
|
|
115
|
+
logger.debug("Running command: %s", " ".join(pip_args))
|
|
116
|
+
subprocess.check_call(pip_args)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class VenvBackendUV:
|
|
120
|
+
"""Virtual environment backend using the uv tool."""
|
|
121
|
+
|
|
122
|
+
_NAME = "uv"
|
|
123
|
+
_PRIORITY: int = 1
|
|
124
|
+
_CMD = "uv"
|
|
125
|
+
|
|
126
|
+
def __init__(self, path: str | pathlib.Path):
|
|
127
|
+
self._path = pathlib.Path(path)
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def name(self) -> str:
|
|
131
|
+
return self._NAME
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def is_supported(cls) -> bool:
|
|
135
|
+
return bool(shutil.which(cls._CMD))
|
|
136
|
+
|
|
137
|
+
def create(self, clear: bool = False) -> None:
|
|
138
|
+
if clear and self._path.exists():
|
|
139
|
+
shutil.rmtree(self._path)
|
|
140
|
+
subprocess.check_call([
|
|
141
|
+
f"{self._CMD}",
|
|
142
|
+
"venv",
|
|
143
|
+
"--python",
|
|
144
|
+
_PYTHON_BIN,
|
|
145
|
+
self._path.as_posix(),
|
|
146
|
+
])
|
|
147
|
+
|
|
148
|
+
def install(self, *packages: str) -> None:
|
|
149
|
+
if not self._path.exists():
|
|
150
|
+
raise FileNotFoundError(f"Virtual environment not found: {self._path}")
|
|
151
|
+
pip_args = [
|
|
152
|
+
f"{self._CMD}",
|
|
153
|
+
"pip",
|
|
154
|
+
"install",
|
|
155
|
+
"--python",
|
|
156
|
+
(self._path / "bin" / "python").as_posix(),
|
|
157
|
+
"--quiet",
|
|
158
|
+
"--upgrade",
|
|
159
|
+
*packages,
|
|
160
|
+
]
|
|
161
|
+
logger.debug("Running command: %s", " ".join(pip_args))
|
|
162
|
+
subprocess.check_call(pip_args)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# Register backends
|
|
166
|
+
VenvBackendRegistry.register_backend(VenvBackendVenv)
|
|
167
|
+
VenvBackendRegistry.register_backend(VenvBackendUV)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pydepinject
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2.dev0
|
|
4
4
|
Summary: A package to dynamically inject requirements into a virtual environment.
|
|
5
5
|
Author: pydepinject
|
|
6
6
|
License: MIT
|
|
@@ -21,6 +21,8 @@ Classifier: Typing :: Typed
|
|
|
21
21
|
Requires-Python: >=3.9
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
|
+
Requires-Dist: packaging>=22.0
|
|
25
|
+
Requires-Dist: typing_extensions
|
|
24
26
|
Provides-Extra: lint
|
|
25
27
|
Requires-Dist: ruff==0.4.7; extra == "lint"
|
|
26
28
|
Requires-Dist: pyright==1.1.365; extra == "lint"
|
|
@@ -28,6 +30,7 @@ Requires-Dist: isort==5.13.2; extra == "lint"
|
|
|
28
30
|
Provides-Extra: test
|
|
29
31
|
Requires-Dist: pytest==8.2.1; extra == "test"
|
|
30
32
|
Requires-Dist: pytest-cov==5.0.0; extra == "test"
|
|
33
|
+
Requires-Dist: pytest-xdist==3.6.1; extra == "test"
|
|
31
34
|
Provides-Extra: build
|
|
32
35
|
Requires-Dist: check-manifest==0.49; extra == "build"
|
|
33
36
|
Requires-Dist: build==1.2.1; extra == "build"
|
|
@@ -3,6 +3,7 @@ MANIFEST.in
|
|
|
3
3
|
Readme.md
|
|
4
4
|
pyproject.toml
|
|
5
5
|
src/pydepinject/__init__.py
|
|
6
|
+
src/pydepinject/backends.py
|
|
6
7
|
src/pydepinject.egg-info/PKG-INFO
|
|
7
8
|
src/pydepinject.egg-info/SOURCES.txt
|
|
8
9
|
src/pydepinject.egg-info/dependency_links.txt
|
|
@@ -13,4 +14,5 @@ src/requirementmanager.egg-info/SOURCES.txt
|
|
|
13
14
|
src/requirementmanager.egg-info/dependency_links.txt
|
|
14
15
|
src/requirementmanager.egg-info/requires.txt
|
|
15
16
|
src/requirementmanager.egg-info/top_level.txt
|
|
17
|
+
tests/conftest.py
|
|
16
18
|
tests/test_pydepinject.py
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
PROJECT_DIR = pathlib.Path(__file__).parent.parent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(autouse=True)
|
|
11
|
+
def check_test_leftovers():
|
|
12
|
+
"""Checks if the test left any files in the project directory."""
|
|
13
|
+
items_before = list(PROJECT_DIR.iterdir())
|
|
14
|
+
yield
|
|
15
|
+
items_after = list(PROJECT_DIR.iterdir())
|
|
16
|
+
new_items = set(items_after) - set(items_before)
|
|
17
|
+
new_items = {item for item in new_items if not item.name.startswith(".coverage")}
|
|
18
|
+
if new_items:
|
|
19
|
+
pytest.fail(f"New items in the project directory: {new_items}")
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import functools
|
|
4
|
+
|
|
3
5
|
import pytest
|
|
4
6
|
|
|
5
7
|
from pydepinject import requires # noqa: E402
|
|
@@ -13,14 +15,19 @@ def venv_root(tmp_path):
|
|
|
13
15
|
return path
|
|
14
16
|
|
|
15
17
|
|
|
18
|
+
@pytest.mark.parametrize("backend", ["uv", "venv"], ids=["uv", "venv"])
|
|
16
19
|
@pytest.mark.parametrize("ephemeral", [True, False], ids=["ephemeral", "non-ephemeral"])
|
|
17
|
-
def test_decorator(venv_root, ephemeral):
|
|
20
|
+
def test_decorator(venv_root, ephemeral, backend):
|
|
18
21
|
assert not list(venv_root.iterdir())
|
|
19
22
|
|
|
20
23
|
with pytest.raises(ImportError):
|
|
21
24
|
import six
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
requires_ = functools.partial(
|
|
27
|
+
requires, venv_root=venv_root, ephemeral=ephemeral, venv_backend=backend
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@requires_("six")
|
|
24
31
|
def examplefn():
|
|
25
32
|
print("examplefn")
|
|
26
33
|
import six
|
|
@@ -37,15 +44,24 @@ def test_decorator(venv_root, ephemeral):
|
|
|
37
44
|
assert len(list(venv_root.iterdir())) == 1
|
|
38
45
|
|
|
39
46
|
|
|
47
|
+
@pytest.mark.parametrize("backend", ["uv", "venv"], ids=["uv", "venv"])
|
|
40
48
|
@pytest.mark.parametrize("ephemeral", [True, False], ids=["ephemeral", "non-ephemeral"])
|
|
41
|
-
def test_venv_name_predefined(venv_root, ephemeral):
|
|
49
|
+
def test_venv_name_predefined(venv_root, ephemeral, backend):
|
|
42
50
|
assert not list(venv_root.iterdir())
|
|
43
51
|
|
|
44
52
|
venv_name = "test_venv_name_predefined"
|
|
45
53
|
with pytest.raises(ImportError):
|
|
46
54
|
import six
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
requires_ = functools.partial(
|
|
57
|
+
requires,
|
|
58
|
+
venv_root=venv_root,
|
|
59
|
+
venv_name=venv_name,
|
|
60
|
+
ephemeral=ephemeral,
|
|
61
|
+
venv_backend=backend,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@requires_("six")
|
|
49
65
|
def examplefn():
|
|
50
66
|
print("examplefn")
|
|
51
67
|
import six
|
|
@@ -60,7 +76,7 @@ def test_venv_name_predefined(venv_root, ephemeral):
|
|
|
60
76
|
assert (venv_root / venv_name).exists() is (not ephemeral)
|
|
61
77
|
assert len(list(venv_root.iterdir())) == (1 if not ephemeral else 0)
|
|
62
78
|
|
|
63
|
-
with
|
|
79
|
+
with requires_("six"):
|
|
64
80
|
import six
|
|
65
81
|
|
|
66
82
|
assert six.__version__
|
|
@@ -73,8 +89,9 @@ def test_venv_name_predefined(venv_root, ephemeral):
|
|
|
73
89
|
assert len(list(venv_root.iterdir())) == (1 if not ephemeral else 0)
|
|
74
90
|
|
|
75
91
|
|
|
92
|
+
@pytest.mark.parametrize("backend", ["uv", "venv"], ids=["uv", "venv"])
|
|
76
93
|
@pytest.mark.parametrize("ephemeral", [True, False], ids=["ephemeral", "non-ephemeral"])
|
|
77
|
-
def test_venv_name_predefined_env(venv_root, monkeypatch, ephemeral):
|
|
94
|
+
def test_venv_name_predefined_env(venv_root, monkeypatch, ephemeral, backend):
|
|
78
95
|
assert not list(venv_root.iterdir())
|
|
79
96
|
|
|
80
97
|
venv_name = "test_venv_name_predefined_env"
|
|
@@ -83,7 +100,11 @@ def test_venv_name_predefined_env(venv_root, monkeypatch, ephemeral):
|
|
|
83
100
|
with pytest.raises(ImportError):
|
|
84
101
|
import six
|
|
85
102
|
|
|
86
|
-
|
|
103
|
+
requires_ = functools.partial(
|
|
104
|
+
requires, venv_root=venv_root, ephemeral=ephemeral, venv_backend=backend
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@requires_("six")
|
|
87
108
|
def examplefn():
|
|
88
109
|
print("examplefn")
|
|
89
110
|
import six
|
|
@@ -98,7 +119,7 @@ def test_venv_name_predefined_env(venv_root, monkeypatch, ephemeral):
|
|
|
98
119
|
assert (venv_root / venv_name).exists() is (not ephemeral)
|
|
99
120
|
assert len(list(venv_root.iterdir())) == (1 if not ephemeral else 0)
|
|
100
121
|
|
|
101
|
-
with
|
|
122
|
+
with requires_("six"):
|
|
102
123
|
import six
|
|
103
124
|
|
|
104
125
|
assert six.__version__
|
|
@@ -111,14 +132,18 @@ def test_venv_name_predefined_env(venv_root, monkeypatch, ephemeral):
|
|
|
111
132
|
assert len(list(venv_root.iterdir())) == (1 if not ephemeral else 0)
|
|
112
133
|
|
|
113
134
|
|
|
135
|
+
@pytest.mark.parametrize("backend", ["uv", "venv"], ids=["uv", "venv"])
|
|
114
136
|
@pytest.mark.parametrize("ephemeral", [True, False], ids=["ephemeral", "non-ephemeral"])
|
|
115
|
-
def test_context_manager(venv_root, ephemeral):
|
|
137
|
+
def test_context_manager(venv_root, ephemeral, backend):
|
|
116
138
|
assert not list(venv_root.iterdir())
|
|
117
139
|
|
|
118
140
|
with pytest.raises(ImportError):
|
|
119
141
|
import six
|
|
120
142
|
|
|
121
|
-
|
|
143
|
+
requires_ = functools.partial(
|
|
144
|
+
requires, venv_root=venv_root, ephemeral=ephemeral, venv_backend=backend
|
|
145
|
+
)
|
|
146
|
+
with requires_("six"):
|
|
122
147
|
import six
|
|
123
148
|
|
|
124
149
|
assert six.__version__
|
|
@@ -132,13 +157,14 @@ def test_context_manager(venv_root, ephemeral):
|
|
|
132
157
|
assert len(list(venv_root.iterdir())) == 1
|
|
133
158
|
|
|
134
159
|
|
|
135
|
-
|
|
160
|
+
@pytest.mark.parametrize("backend", ["uv", "venv"], ids=["uv", "venv"])
|
|
161
|
+
def test_function_call(venv_root, backend):
|
|
136
162
|
assert not list(venv_root.iterdir())
|
|
137
163
|
|
|
138
164
|
with pytest.raises(ImportError):
|
|
139
165
|
import six
|
|
140
166
|
|
|
141
|
-
requires_instance = requires("six", venv_root=venv_root)
|
|
167
|
+
requires_instance = requires("six", venv_root=venv_root, venv_backend=backend)
|
|
142
168
|
requires_instance()
|
|
143
169
|
import six
|
|
144
170
|
|
|
@@ -151,10 +177,11 @@ def test_function_call(venv_root):
|
|
|
151
177
|
assert len(list(venv_root.iterdir())) == 1
|
|
152
178
|
|
|
153
179
|
|
|
154
|
-
|
|
180
|
+
@pytest.mark.parametrize("backend", ["uv", "venv"], ids=["uv", "venv"])
|
|
181
|
+
def test_no_installs(venv_root, backend):
|
|
155
182
|
assert not list(venv_root.iterdir())
|
|
156
183
|
|
|
157
|
-
@requires("pytest", venv_root=venv_root, ephemeral=False)
|
|
184
|
+
@requires("pytest", venv_root=venv_root, ephemeral=False, venv_backend=backend)
|
|
158
185
|
def examplefn():
|
|
159
186
|
print("examplefn")
|
|
160
187
|
|
|
@@ -162,17 +189,22 @@ def test_no_installs(venv_root):
|
|
|
162
189
|
assert not list(venv_root.iterdir())
|
|
163
190
|
|
|
164
191
|
|
|
165
|
-
|
|
192
|
+
@pytest.mark.parametrize("backend", ["uv", "venv"], ids=["uv", "venv"])
|
|
193
|
+
def test_reuse_venv(venv_root, backend):
|
|
166
194
|
assert not list(venv_root.iterdir())
|
|
167
195
|
|
|
168
|
-
|
|
196
|
+
requires_ = functools.partial(
|
|
197
|
+
requires, venv_root=venv_root, ephemeral=False, venv_backend=backend
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
@requires_("six")
|
|
169
201
|
def examplea():
|
|
170
202
|
import six
|
|
171
203
|
|
|
172
204
|
assert six.__version__
|
|
173
205
|
examplea.called = True
|
|
174
206
|
|
|
175
|
-
@
|
|
207
|
+
@requires_("six")
|
|
176
208
|
def exampleb():
|
|
177
209
|
import six
|
|
178
210
|
|
|
@@ -192,19 +224,28 @@ def test_reuse_venv(venv_root):
|
|
|
192
224
|
assert len(list(venv_root.iterdir())) == 1
|
|
193
225
|
|
|
194
226
|
|
|
195
|
-
|
|
227
|
+
@pytest.mark.parametrize("backend", ["uv", "venv"], ids=["uv", "venv"])
|
|
228
|
+
def test_one_venv_multiple_packages(venv_root, backend):
|
|
196
229
|
assert not list(venv_root.iterdir())
|
|
197
230
|
|
|
198
231
|
venv_name = "test_one_venv_multiple_packages"
|
|
199
232
|
|
|
200
|
-
|
|
233
|
+
requires_ = functools.partial(
|
|
234
|
+
requires,
|
|
235
|
+
venv_root=venv_root,
|
|
236
|
+
venv_name=venv_name,
|
|
237
|
+
ephemeral=False,
|
|
238
|
+
venv_backend=backend,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
@requires_("six")
|
|
201
242
|
def examplefn():
|
|
202
243
|
import six
|
|
203
244
|
|
|
204
245
|
assert six.__version__
|
|
205
246
|
assert (venv_root / venv_name).exists()
|
|
206
247
|
|
|
207
|
-
@
|
|
248
|
+
@requires_("pyparsing")
|
|
208
249
|
def examplefn2():
|
|
209
250
|
import pyparsing
|
|
210
251
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/pydepinject.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/requirementmanager.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/requirementmanager.egg-info/requires.txt
RENAMED
|
File without changes
|
{pydepinject-0.0.1.dev1 → pydepinject-0.0.2.dev0}/src/requirementmanager.egg-info/top_level.txt
RENAMED
|
File without changes
|