pydepinject 0.0.1.dev0__tar.gz → 0.0.1.dev2__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.1.dev0 → pydepinject-0.0.1.dev2}/MANIFEST.in +1 -1
- {pydepinject-0.0.1.dev0/src/pydepinject.egg-info → pydepinject-0.0.1.dev2}/PKG-INFO +40 -1
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/Readme.md +37 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/pyproject.toml +8 -2
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/pydepinject/__init__.py +31 -15
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2/src/pydepinject.egg-info}/PKG-INFO +40 -1
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/pydepinject.egg-info/requires.txt +2 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/tests/test_pydepinject.py +80 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/LICENSE +0 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/setup.cfg +0 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/pydepinject.egg-info/SOURCES.txt +0 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/pydepinject.egg-info/dependency_links.txt +0 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/pydepinject.egg-info/top_level.txt +0 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/requirementmanager.egg-info/PKG-INFO +0 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/requirementmanager.egg-info/SOURCES.txt +0 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/requirementmanager.egg-info/dependency_links.txt +0 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/requirementmanager.egg-info/requires.txt +0 -0
- {pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/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.1.
|
|
3
|
+
Version: 0.0.1.dev2
|
|
4
4
|
Summary: A package to dynamically inject requirements into a virtual environment.
|
|
5
5
|
Author: pydepinject
|
|
6
6
|
License: MIT
|
|
@@ -21,6 +21,7 @@ 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
|
|
24
25
|
Provides-Extra: lint
|
|
25
26
|
Requires-Dist: ruff==0.4.7; extra == "lint"
|
|
26
27
|
Requires-Dist: pyright==1.1.365; extra == "lint"
|
|
@@ -28,6 +29,7 @@ Requires-Dist: isort==5.13.2; extra == "lint"
|
|
|
28
29
|
Provides-Extra: test
|
|
29
30
|
Requires-Dist: pytest==8.2.1; extra == "test"
|
|
30
31
|
Requires-Dist: pytest-cov==5.0.0; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-xdist==3.6.1; extra == "test"
|
|
31
33
|
Provides-Extra: build
|
|
32
34
|
Requires-Dist: check-manifest==0.49; extra == "build"
|
|
33
35
|
Requires-Dist: build==1.2.1; extra == "build"
|
|
@@ -86,6 +88,43 @@ with requires("requests", "numpy"):
|
|
|
86
88
|
print(np.__version__)
|
|
87
89
|
```
|
|
88
90
|
|
|
91
|
+
### Virtual Environment with specific name
|
|
92
|
+
|
|
93
|
+
The `requires` can create a virtual environment with a specific name:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
@requires("requests", venv_name="myenv")
|
|
97
|
+
def my_function():
|
|
98
|
+
import requests
|
|
99
|
+
print(requests.__version__)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
with requires("pylint", venv_name="myenv"):
|
|
103
|
+
import pylint
|
|
104
|
+
print(pylint.__version__)
|
|
105
|
+
import requests # This is also available here because it was installed in the same virtual environment
|
|
106
|
+
print(requests.__version__)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# The virtual environment name can also be set as PYDEPINJECT_VENV_NAME environment variable
|
|
110
|
+
import os
|
|
111
|
+
os.environ["PYDEPINJECT_VENV_NAME"] = "myenv"
|
|
112
|
+
|
|
113
|
+
@requires("requests")
|
|
114
|
+
def my_function():
|
|
115
|
+
import requests
|
|
116
|
+
print(requests.__version__)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
with requires("pylint"):
|
|
120
|
+
import pylint
|
|
121
|
+
print(pylint.__version__)
|
|
122
|
+
import requests # This is also available here because it was installed in the same virtual environment
|
|
123
|
+
print(requests.__version__)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
89
128
|
### Reusable Virtual Environments
|
|
90
129
|
|
|
91
130
|
The `requires` can create named virtual environments and reuse them across multiple functions or code blocks:
|
|
@@ -50,6 +50,43 @@ with requires("requests", "numpy"):
|
|
|
50
50
|
print(np.__version__)
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
### Virtual Environment with specific name
|
|
54
|
+
|
|
55
|
+
The `requires` can create a virtual environment with a specific name:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
@requires("requests", venv_name="myenv")
|
|
59
|
+
def my_function():
|
|
60
|
+
import requests
|
|
61
|
+
print(requests.__version__)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
with requires("pylint", venv_name="myenv"):
|
|
65
|
+
import pylint
|
|
66
|
+
print(pylint.__version__)
|
|
67
|
+
import requests # This is also available here because it was installed in the same virtual environment
|
|
68
|
+
print(requests.__version__)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# The virtual environment name can also be set as PYDEPINJECT_VENV_NAME environment variable
|
|
72
|
+
import os
|
|
73
|
+
os.environ["PYDEPINJECT_VENV_NAME"] = "myenv"
|
|
74
|
+
|
|
75
|
+
@requires("requests")
|
|
76
|
+
def my_function():
|
|
77
|
+
import requests
|
|
78
|
+
print(requests.__version__)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
with requires("pylint"):
|
|
82
|
+
import pylint
|
|
83
|
+
print(pylint.__version__)
|
|
84
|
+
import requests # This is also available here because it was installed in the same virtual environment
|
|
85
|
+
print(requests.__version__)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
53
90
|
### Reusable Virtual Environments
|
|
54
91
|
|
|
55
92
|
The `requires` can create named virtual environments and reuse them across multiple functions or code blocks:
|
|
@@ -15,6 +15,9 @@ 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
|
+
]
|
|
18
21
|
classifiers = [
|
|
19
22
|
"Development Status :: 4 - Beta",
|
|
20
23
|
"Intended Audience :: Developers",
|
|
@@ -42,6 +45,7 @@ lint = [
|
|
|
42
45
|
test = [
|
|
43
46
|
"pytest==8.2.1",
|
|
44
47
|
"pytest-cov==5.0.0",
|
|
48
|
+
"pytest-xdist==3.6.1",
|
|
45
49
|
]
|
|
46
50
|
build = [
|
|
47
51
|
"check-manifest==0.49",
|
|
@@ -62,7 +66,8 @@ log_level = "DEBUG"
|
|
|
62
66
|
|
|
63
67
|
[tool.pytest.ini_options]
|
|
64
68
|
pythonpath = "src"
|
|
65
|
-
|
|
69
|
+
addopts = "--durations=10 -n auto"
|
|
70
|
+
# Estimated test times: 74s with auto (2), 76s with 6 workers, 113se with no workers.
|
|
66
71
|
|
|
67
72
|
[tool.ruff]
|
|
68
73
|
preview = true
|
|
@@ -126,7 +131,8 @@ ignore = []
|
|
|
126
131
|
uv = {version = "0.2.5"}
|
|
127
132
|
invoke = {version = "2.2.0", preinstall = ["tomli"]}
|
|
128
133
|
nox = {version = "2024.4.15"}
|
|
129
|
-
twine = {version = "5.1.
|
|
134
|
+
twine = {version = "5.1.0"}
|
|
130
135
|
|
|
131
136
|
[tool.custom.ci]
|
|
132
137
|
python_versions = ["3.9", "3.10", "3.11", "3.12"]
|
|
138
|
+
packaging_versions = ["22", "23", "24"]
|
|
@@ -18,7 +18,7 @@ if typing.TYPE_CHECKING:
|
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
VERSION = "0.0.
|
|
21
|
+
VERSION = "0.0.1dev2"
|
|
22
22
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
24
24
|
logger.setLevel(logging.DEBUG)
|
|
@@ -31,25 +31,41 @@ logger.debug("VENV_ROOT: %s", VENV_ROOT)
|
|
|
31
31
|
def is_requirements_satisfied(*packages: str):
|
|
32
32
|
"""Check if the requirements are already satisfied. Return None if it cannot be determined."""
|
|
33
33
|
try:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
import
|
|
34
|
+
from importlib.metadata import PackageNotFoundError, distribution
|
|
35
|
+
|
|
36
|
+
from packaging.requirements import Requirement
|
|
37
37
|
except ImportError:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
try:
|
|
39
|
+
from importlib.metadata import PackageNotFoundError, distribution
|
|
40
|
+
|
|
41
|
+
from packaging.requirements import Requirement
|
|
42
|
+
except ImportError:
|
|
43
|
+
logger.warning(
|
|
44
|
+
"importlib.metadata and packaging not found. Cannot check if requirements are satisfied."
|
|
45
|
+
)
|
|
46
|
+
return None
|
|
42
47
|
|
|
43
48
|
try:
|
|
44
49
|
for package in packages:
|
|
45
50
|
logger.debug("Checking package: %s", package)
|
|
46
|
-
req =
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
req = Requirement(package)
|
|
52
|
+
try:
|
|
53
|
+
dist = distribution(req.name)
|
|
54
|
+
if dist.version not in req.specifier:
|
|
55
|
+
logger.debug(
|
|
56
|
+
"Requirement %s is not satisfied. Version conflict.", package
|
|
57
|
+
)
|
|
58
|
+
return False
|
|
59
|
+
logger.debug("Requirement %s is satisfied", package)
|
|
60
|
+
except PackageNotFoundError:
|
|
61
|
+
logger.debug(
|
|
62
|
+
"Requirement %s is not satisfied. Distribution not found.", package
|
|
63
|
+
)
|
|
64
|
+
return False
|
|
49
65
|
return True
|
|
50
|
-
except
|
|
51
|
-
logger.
|
|
52
|
-
return
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.warning("An error occurred while checking requirements: %s", str(e))
|
|
68
|
+
return None
|
|
53
69
|
|
|
54
70
|
|
|
55
71
|
class RequirementManager:
|
|
@@ -74,7 +90,7 @@ class RequirementManager:
|
|
|
74
90
|
ephemeral: If True, the virtual environment will be deleted after use.
|
|
75
91
|
"""
|
|
76
92
|
self.packages = packages
|
|
77
|
-
self.venv_name = venv_name
|
|
93
|
+
self.venv_name = venv_name or os.environ.get("PYDEPINJECT_VENV_NAME", "")
|
|
78
94
|
self.original_pythonpath = os.environ.get("PYTHONPATH", "")
|
|
79
95
|
self.original_path = os.environ.get("PATH", "")
|
|
80
96
|
self.original_syspath = sys.path.copy()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pydepinject
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev2
|
|
4
4
|
Summary: A package to dynamically inject requirements into a virtual environment.
|
|
5
5
|
Author: pydepinject
|
|
6
6
|
License: MIT
|
|
@@ -21,6 +21,7 @@ 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
|
|
24
25
|
Provides-Extra: lint
|
|
25
26
|
Requires-Dist: ruff==0.4.7; extra == "lint"
|
|
26
27
|
Requires-Dist: pyright==1.1.365; extra == "lint"
|
|
@@ -28,6 +29,7 @@ Requires-Dist: isort==5.13.2; extra == "lint"
|
|
|
28
29
|
Provides-Extra: test
|
|
29
30
|
Requires-Dist: pytest==8.2.1; extra == "test"
|
|
30
31
|
Requires-Dist: pytest-cov==5.0.0; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-xdist==3.6.1; extra == "test"
|
|
31
33
|
Provides-Extra: build
|
|
32
34
|
Requires-Dist: check-manifest==0.49; extra == "build"
|
|
33
35
|
Requires-Dist: build==1.2.1; extra == "build"
|
|
@@ -86,6 +88,43 @@ with requires("requests", "numpy"):
|
|
|
86
88
|
print(np.__version__)
|
|
87
89
|
```
|
|
88
90
|
|
|
91
|
+
### Virtual Environment with specific name
|
|
92
|
+
|
|
93
|
+
The `requires` can create a virtual environment with a specific name:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
@requires("requests", venv_name="myenv")
|
|
97
|
+
def my_function():
|
|
98
|
+
import requests
|
|
99
|
+
print(requests.__version__)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
with requires("pylint", venv_name="myenv"):
|
|
103
|
+
import pylint
|
|
104
|
+
print(pylint.__version__)
|
|
105
|
+
import requests # This is also available here because it was installed in the same virtual environment
|
|
106
|
+
print(requests.__version__)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# The virtual environment name can also be set as PYDEPINJECT_VENV_NAME environment variable
|
|
110
|
+
import os
|
|
111
|
+
os.environ["PYDEPINJECT_VENV_NAME"] = "myenv"
|
|
112
|
+
|
|
113
|
+
@requires("requests")
|
|
114
|
+
def my_function():
|
|
115
|
+
import requests
|
|
116
|
+
print(requests.__version__)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
with requires("pylint"):
|
|
120
|
+
import pylint
|
|
121
|
+
print(pylint.__version__)
|
|
122
|
+
import requests # This is also available here because it was installed in the same virtual environment
|
|
123
|
+
print(requests.__version__)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
89
128
|
### Reusable Virtual Environments
|
|
90
129
|
|
|
91
130
|
The `requires` can create named virtual environments and reuse them across multiple functions or code blocks:
|
|
@@ -37,6 +37,80 @@ def test_decorator(venv_root, ephemeral):
|
|
|
37
37
|
assert len(list(venv_root.iterdir())) == 1
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
@pytest.mark.parametrize("ephemeral", [True, False], ids=["ephemeral", "non-ephemeral"])
|
|
41
|
+
def test_venv_name_predefined(venv_root, ephemeral):
|
|
42
|
+
assert not list(venv_root.iterdir())
|
|
43
|
+
|
|
44
|
+
venv_name = "test_venv_name_predefined"
|
|
45
|
+
with pytest.raises(ImportError):
|
|
46
|
+
import six
|
|
47
|
+
|
|
48
|
+
@requires("six", venv_root=venv_root, venv_name=venv_name, ephemeral=ephemeral)
|
|
49
|
+
def examplefn():
|
|
50
|
+
print("examplefn")
|
|
51
|
+
import six
|
|
52
|
+
|
|
53
|
+
assert six.__version__
|
|
54
|
+
assert (venv_root / venv_name).exists()
|
|
55
|
+
|
|
56
|
+
examplefn()
|
|
57
|
+
with pytest.raises(ImportError):
|
|
58
|
+
import six
|
|
59
|
+
|
|
60
|
+
assert (venv_root / venv_name).exists() is (not ephemeral)
|
|
61
|
+
assert len(list(venv_root.iterdir())) == (1 if not ephemeral else 0)
|
|
62
|
+
|
|
63
|
+
with requires("six", venv_root=venv_root, venv_name=venv_name, ephemeral=ephemeral):
|
|
64
|
+
import six
|
|
65
|
+
|
|
66
|
+
assert six.__version__
|
|
67
|
+
assert (venv_root / venv_name).exists()
|
|
68
|
+
|
|
69
|
+
with pytest.raises(ImportError):
|
|
70
|
+
import six
|
|
71
|
+
|
|
72
|
+
assert (venv_root / venv_name).exists() is (not ephemeral)
|
|
73
|
+
assert len(list(venv_root.iterdir())) == (1 if not ephemeral else 0)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.mark.parametrize("ephemeral", [True, False], ids=["ephemeral", "non-ephemeral"])
|
|
77
|
+
def test_venv_name_predefined_env(venv_root, monkeypatch, ephemeral):
|
|
78
|
+
assert not list(venv_root.iterdir())
|
|
79
|
+
|
|
80
|
+
venv_name = "test_venv_name_predefined_env"
|
|
81
|
+
monkeypatch.setenv("PYDEPINJECT_VENV_NAME", venv_name)
|
|
82
|
+
|
|
83
|
+
with pytest.raises(ImportError):
|
|
84
|
+
import six
|
|
85
|
+
|
|
86
|
+
@requires("six", venv_root=venv_root, ephemeral=ephemeral)
|
|
87
|
+
def examplefn():
|
|
88
|
+
print("examplefn")
|
|
89
|
+
import six
|
|
90
|
+
|
|
91
|
+
assert six.__version__
|
|
92
|
+
assert (venv_root / venv_name).exists()
|
|
93
|
+
|
|
94
|
+
examplefn()
|
|
95
|
+
with pytest.raises(ImportError):
|
|
96
|
+
import six
|
|
97
|
+
|
|
98
|
+
assert (venv_root / venv_name).exists() is (not ephemeral)
|
|
99
|
+
assert len(list(venv_root.iterdir())) == (1 if not ephemeral else 0)
|
|
100
|
+
|
|
101
|
+
with requires("six", venv_root=venv_root, ephemeral=ephemeral):
|
|
102
|
+
import six
|
|
103
|
+
|
|
104
|
+
assert six.__version__
|
|
105
|
+
assert (venv_root / venv_name).exists()
|
|
106
|
+
|
|
107
|
+
with pytest.raises(ImportError):
|
|
108
|
+
import six
|
|
109
|
+
|
|
110
|
+
assert (venv_root / venv_name).exists() is (not ephemeral)
|
|
111
|
+
assert len(list(venv_root.iterdir())) == (1 if not ephemeral else 0)
|
|
112
|
+
|
|
113
|
+
|
|
40
114
|
@pytest.mark.parametrize("ephemeral", [True, False], ids=["ephemeral", "non-ephemeral"])
|
|
41
115
|
def test_context_manager(venv_root, ephemeral):
|
|
42
116
|
assert not list(venv_root.iterdir())
|
|
@@ -74,6 +148,7 @@ def test_function_call(venv_root):
|
|
|
74
148
|
requires_instance._deactivate_venv()
|
|
75
149
|
with pytest.raises(ImportError):
|
|
76
150
|
import six
|
|
151
|
+
assert len(list(venv_root.iterdir())) == 1
|
|
77
152
|
|
|
78
153
|
|
|
79
154
|
def test_no_installs(venv_root):
|
|
@@ -127,6 +202,7 @@ def test_one_venv_multiple_packages(venv_root):
|
|
|
127
202
|
import six
|
|
128
203
|
|
|
129
204
|
assert six.__version__
|
|
205
|
+
assert (venv_root / venv_name).exists()
|
|
130
206
|
|
|
131
207
|
@requires("pyparsing", venv_root=venv_root, venv_name=venv_name, ephemeral=False)
|
|
132
208
|
def examplefn2():
|
|
@@ -137,10 +213,14 @@ def test_one_venv_multiple_packages(venv_root):
|
|
|
137
213
|
import six
|
|
138
214
|
|
|
139
215
|
assert six.__version__
|
|
216
|
+
assert (venv_root / venv_name).exists()
|
|
140
217
|
|
|
141
218
|
examplefn()
|
|
142
219
|
examplefn2()
|
|
143
220
|
|
|
221
|
+
# Still exists as ephemeral is False.
|
|
222
|
+
assert (venv_root / venv_name).exists()
|
|
223
|
+
|
|
144
224
|
with pytest.raises(ImportError):
|
|
145
225
|
import six
|
|
146
226
|
with pytest.raises(ImportError):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/pydepinject.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/requirementmanager.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/requirementmanager.egg-info/requires.txt
RENAMED
|
File without changes
|
{pydepinject-0.0.1.dev0 → pydepinject-0.0.1.dev2}/src/requirementmanager.egg-info/top_level.txt
RENAMED
|
File without changes
|