tgzr.package_management 0.100__tar.gz → 0.102__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.
- {tgzr_package_management-0.100 → tgzr_package_management-0.102}/PKG-INFO +4 -1
- {tgzr_package_management-0.100 → tgzr_package_management-0.102}/pyproject.toml +12 -18
- {tgzr_package_management-0.100 → tgzr_package_management-0.102}/tgzr/package_management/_version.py +2 -2
- tgzr_package_management-0.102/tgzr/package_management/distribution.py +8 -0
- {tgzr_package_management-0.100 → tgzr_package_management-0.102}/tgzr/package_management/package_manager.py +39 -0
- {tgzr_package_management-0.100 → tgzr_package_management-0.102}/tgzr/package_management/plugin_manager.py +20 -3
- tgzr_package_management-0.102/tgzr/package_management/pyproject.py +262 -0
- {tgzr_package_management-0.100 → tgzr_package_management-0.102}/tgzr/package_management/venv.py +87 -40
- tgzr_package_management-0.102/tgzr/package_management/workspace.py +264 -0
- {tgzr_package_management-0.100 → tgzr_package_management-0.102}/.gitignore +0 -0
- {tgzr_package_management-0.100 → tgzr_package_management-0.102}/LICENSE +0 -0
- {tgzr_package_management-0.100 → tgzr_package_management-0.102}/README.md +0 -0
- {tgzr_package_management-0.100 → tgzr_package_management-0.102}/tgzr/package_management/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tgzr.package_management
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.102
|
|
4
4
|
Summary: tgzr package_management engine
|
|
5
5
|
Project-URL: Documentation, https://github.com/open-tgzr/tgzr.package_management#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/open-tgzr/tgzr.package_management/issues
|
|
@@ -20,6 +20,9 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
20
20
|
Requires-Python: >=3.9
|
|
21
21
|
Requires-Dist: hatch
|
|
22
22
|
Requires-Dist: importlib-metadata
|
|
23
|
+
Requires-Dist: msgspec
|
|
24
|
+
Requires-Dist: packaging
|
|
25
|
+
Requires-Dist: toml
|
|
23
26
|
Requires-Dist: uv
|
|
24
27
|
Description-Content-Type: text/markdown
|
|
25
28
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling", "hatch-vcs"]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
@@ -10,9 +10,7 @@ readme = "README.md"
|
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = "GPL-3.0-or-later"
|
|
12
12
|
keywords = []
|
|
13
|
-
authors = [
|
|
14
|
-
{ name = "Dee", email = "dee.sometech@gmail.com" },
|
|
15
|
-
]
|
|
13
|
+
authors = [{ name = "Dee", email = "dee.sometech@gmail.com" }]
|
|
16
14
|
classifiers = [
|
|
17
15
|
"Development Status :: 4 - Beta",
|
|
18
16
|
"Programming Language :: Python",
|
|
@@ -26,8 +24,11 @@ classifiers = [
|
|
|
26
24
|
]
|
|
27
25
|
|
|
28
26
|
dependencies = [
|
|
27
|
+
"packaging",
|
|
29
28
|
"uv",
|
|
30
29
|
"hatch",
|
|
30
|
+
'msgspec',
|
|
31
|
+
"toml",
|
|
31
32
|
"importlib-metadata", # needed to support modern Distribution api with py3.9
|
|
32
33
|
]
|
|
33
34
|
|
|
@@ -55,9 +56,7 @@ artifacts = ['_version.py']
|
|
|
55
56
|
packages = ["src/tgzr"]
|
|
56
57
|
|
|
57
58
|
[tool.hatch.envs.types]
|
|
58
|
-
extra-dependencies = [
|
|
59
|
-
"mypy>=1.0.0",
|
|
60
|
-
]
|
|
59
|
+
extra-dependencies = ["mypy>=1.0.0"]
|
|
61
60
|
|
|
62
61
|
[tool.hatch.envs.types.scripts]
|
|
63
62
|
check = "mypy --install-types --non-interactive {args:src/tgzr/package_management tests}"
|
|
@@ -66,19 +65,14 @@ check = "mypy --install-types --non-interactive {args:src/tgzr/package_managemen
|
|
|
66
65
|
source_pkgs = ["tgzr.package_management", "tests"]
|
|
67
66
|
branch = true
|
|
68
67
|
parallel = true
|
|
69
|
-
omit = [
|
|
70
|
-
"src/tgzr/package_management/_version.py",
|
|
71
|
-
]
|
|
68
|
+
omit = ["src/tgzr/package_management/_version.py"]
|
|
72
69
|
|
|
73
70
|
[tool.coverage.paths]
|
|
74
|
-
tgzr.package_management = [
|
|
71
|
+
tgzr.package_management = [
|
|
72
|
+
"src/tgzr/package_management",
|
|
73
|
+
"*/tgzr.package_management/src/tgzr/package_management",
|
|
74
|
+
]
|
|
75
75
|
tests = ["tests", "*/tgzr/package_management/tests"]
|
|
76
76
|
|
|
77
77
|
[tool.coverage.report]
|
|
78
|
-
exclude_lines = [
|
|
79
|
-
"no cov",
|
|
80
|
-
"if __name__ == .__main__.:",
|
|
81
|
-
"if TYPE_CHECKING:",
|
|
82
|
-
]
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
|
{tgzr_package_management-0.100 → tgzr_package_management-0.102}/tgzr/package_management/_version.py
RENAMED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.102'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 102)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
from typing import Literal
|
|
2
3
|
|
|
3
4
|
import os
|
|
4
5
|
import platform
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
from .venv import Venv
|
|
9
|
+
from .workspace import Workspace
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class PackageManager:
|
|
11
13
|
def __init__(self, root: Path) -> None:
|
|
14
|
+
"""
|
|
15
|
+
The venvs and workspace created by this manager will
|
|
16
|
+
be located in their group folder:
|
|
17
|
+
root/
|
|
18
|
+
group1/
|
|
19
|
+
venv1
|
|
20
|
+
venv2
|
|
21
|
+
workspace1
|
|
22
|
+
group2/
|
|
23
|
+
venv3
|
|
24
|
+
venv4
|
|
25
|
+
workspace2
|
|
26
|
+
group3/
|
|
27
|
+
workspace3
|
|
28
|
+
"""
|
|
29
|
+
|
|
12
30
|
self._root = root
|
|
13
31
|
|
|
14
32
|
@property
|
|
@@ -73,3 +91,24 @@ class PackageManager:
|
|
|
73
91
|
venv.create(prompt, clear_existing=exist_ok)
|
|
74
92
|
venv.install_uv()
|
|
75
93
|
return venv
|
|
94
|
+
|
|
95
|
+
def get_workspace_path(self, workspace_name: str, group: str) -> Path:
|
|
96
|
+
return self.root / group / workspace_name
|
|
97
|
+
|
|
98
|
+
def get_workspace(self, workspace_name: str, group: str) -> Workspace:
|
|
99
|
+
return Workspace(self.get_workspace_path(workspace_name, group))
|
|
100
|
+
|
|
101
|
+
def create_workspace(
|
|
102
|
+
self,
|
|
103
|
+
workspace_name: str,
|
|
104
|
+
group: str,
|
|
105
|
+
description: str | None = None,
|
|
106
|
+
python_version: str | None = None,
|
|
107
|
+
vcs: Literal["git", "none"] | None = None,
|
|
108
|
+
exist_ok: bool = False,
|
|
109
|
+
):
|
|
110
|
+
workspace = self.get_workspace(workspace_name, group)
|
|
111
|
+
if workspace.exists() and not exist_ok:
|
|
112
|
+
raise ValueError(f"The workspace {workspace.path} already exists!")
|
|
113
|
+
|
|
114
|
+
workspace.create(description, python_version, vcs)
|
|
@@ -10,12 +10,10 @@ from typing import (
|
|
|
10
10
|
Generic,
|
|
11
11
|
)
|
|
12
12
|
|
|
13
|
-
import sys
|
|
14
13
|
import importlib_metadata
|
|
15
14
|
import inspect
|
|
16
15
|
import logging
|
|
17
16
|
|
|
18
|
-
import rich
|
|
19
17
|
|
|
20
18
|
T = TypeVar("T", bound="Plugin")
|
|
21
19
|
|
|
@@ -59,6 +57,23 @@ class Plugin:
|
|
|
59
57
|
PluginType = TypeVar("PluginType", bound=Plugin)
|
|
60
58
|
|
|
61
59
|
|
|
60
|
+
class PluginManagerRegistry:
|
|
61
|
+
"""
|
|
62
|
+
A Registry containing all the PluginManager instances.
|
|
63
|
+
This is used for documenting/inspecting plugins usage.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
_PLUGIN_MANAGERS: set[PluginManager] = set()
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def register(cls, plugin_manager: PluginManager):
|
|
70
|
+
cls._PLUGIN_MANAGERS.add(plugin_manager)
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def get_plugin_managers(cls) -> set[PluginManager]:
|
|
74
|
+
return cls._PLUGIN_MANAGERS.copy()
|
|
75
|
+
|
|
76
|
+
|
|
62
77
|
class PluginManager(Generic[PluginType]):
|
|
63
78
|
EP_GROUP = "your_plugin_entry_point_group"
|
|
64
79
|
|
|
@@ -67,6 +82,8 @@ class PluginManager(Generic[PluginType]):
|
|
|
67
82
|
return get_args(cls.__orig_bases__[0])[0] # type: ignore __orig_bases__ trust me bro.
|
|
68
83
|
|
|
69
84
|
def __init__(self):
|
|
85
|
+
PluginManagerRegistry.register(self)
|
|
86
|
+
|
|
70
87
|
self._broken: list[tuple[importlib_metadata.EntryPoint, Exception]] = []
|
|
71
88
|
self._loaded: list[PluginType] = []
|
|
72
89
|
self._needs_loading: bool = True
|
|
@@ -107,7 +124,7 @@ class PluginManager(Generic[PluginType]):
|
|
|
107
124
|
plugin_or_list_of_plugins = loaded() # type: ignore
|
|
108
125
|
except Exception as err:
|
|
109
126
|
raise ValueError(
|
|
110
|
-
f"Error while executing callable entry point value (ep={entry_point}): {err}"
|
|
127
|
+
f"Error while executing callable entry point value (ep={entry_point}): {err} ({loaded=}, {ManagedPluginType=})"
|
|
111
128
|
)
|
|
112
129
|
return self._resolve_plugins(
|
|
113
130
|
loaded=plugin_or_list_of_plugins, entry_point=entry_point
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
API to edit pyproject.toml files.
|
|
4
|
+
|
|
5
|
+
Originally from https://jcristharif.com/msgspec/examples/pyproject-toml.html
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import msgspec
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Base(
|
|
18
|
+
msgspec.Struct,
|
|
19
|
+
omit_defaults=True,
|
|
20
|
+
forbid_unknown_fields=True,
|
|
21
|
+
rename="kebab",
|
|
22
|
+
):
|
|
23
|
+
"""A base class holding some common settings.
|
|
24
|
+
|
|
25
|
+
- We set ``omit_defaults = True`` to omit any fields containing only their
|
|
26
|
+
default value from the output when encoding.
|
|
27
|
+
- We set ``forbid_unknown_fields = True`` to error nicely if an unknown
|
|
28
|
+
field is present in the input TOML. This helps catch typo errors early,
|
|
29
|
+
and is also required per PEP 621.
|
|
30
|
+
- We set ``rename = "kebab"`` to rename all fields to use kebab case when
|
|
31
|
+
encoding/decoding, as this is the convention used in pyproject.toml. For
|
|
32
|
+
example, this will rename ``requires_python`` to ``requires-python``.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BuildSystem(Base):
|
|
39
|
+
requires: list[str] = []
|
|
40
|
+
build_backend: str | None = None
|
|
41
|
+
backend_path: list[str] = []
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Readme(Base):
|
|
45
|
+
file: str | None = None
|
|
46
|
+
text: str | None = None
|
|
47
|
+
content_type: str | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class License(Base):
|
|
51
|
+
file: str | None = None
|
|
52
|
+
text: str | None = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Contributor(Base):
|
|
56
|
+
name: str | None = None
|
|
57
|
+
email: str | None = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Project(Base):
|
|
61
|
+
name: str | None = None
|
|
62
|
+
version: str | None = None
|
|
63
|
+
description: str | None = None
|
|
64
|
+
readme: str | Readme | None = None
|
|
65
|
+
license: str | License | None = None
|
|
66
|
+
authors: list[Contributor] = []
|
|
67
|
+
maintainers: list[Contributor] = []
|
|
68
|
+
keywords: list[str] | None = None
|
|
69
|
+
classifiers: list[str] = []
|
|
70
|
+
urls: dict[str, str] = {}
|
|
71
|
+
requires_python: str | None = None
|
|
72
|
+
dependencies: list[str] = []
|
|
73
|
+
optional_dependencies: dict[str, list[str]] = {}
|
|
74
|
+
scripts: dict[str, str] = {}
|
|
75
|
+
gui_scripts: dict[str, str] = {}
|
|
76
|
+
entry_points: dict[str, dict[str, str]] = {}
|
|
77
|
+
dynamic: list[str] = []
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ToolUVSource(Base):
|
|
81
|
+
index: str | None = None
|
|
82
|
+
workspace: bool | None = False
|
|
83
|
+
path: str | None = None
|
|
84
|
+
editable: bool | None = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ToolUVIndex(Base):
|
|
88
|
+
name: str
|
|
89
|
+
url: str
|
|
90
|
+
explicit: bool | None = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ToolUVWorkspace(Base):
|
|
94
|
+
members: list[str] = []
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class ToolUV(Base):
|
|
98
|
+
sources: dict[str, ToolUVSource] = {}
|
|
99
|
+
index: list[ToolUVIndex] = []
|
|
100
|
+
workspace: ToolUVWorkspace | None = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ToolHatchMetadata(Base):
|
|
104
|
+
allow_custom_classifiers: bool | None = False
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ToolHatchVersion(Base):
|
|
108
|
+
path: str | None = None
|
|
109
|
+
source: str | None = None
|
|
110
|
+
fallback_version: str | None = None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class ToolHatchBuildTarget(Base):
|
|
114
|
+
artifacts: list[str] = []
|
|
115
|
+
packages: list[str] = []
|
|
116
|
+
hooks: dict[str, Any] = {}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ToolHatchBuild(Base):
|
|
120
|
+
artifacts: list[str] = []
|
|
121
|
+
packages: list[str] = []
|
|
122
|
+
hooks: dict[str, Any] = {}
|
|
123
|
+
targets: dict[str, ToolHatchBuildTarget] = {}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ToolHatch(Base):
|
|
127
|
+
metadata: dict[str, str | bool] = {}
|
|
128
|
+
envs: dict[str, Any] | None = None
|
|
129
|
+
version: ToolHatchVersion | None = None
|
|
130
|
+
build: ToolHatchBuild | None = None
|
|
131
|
+
# publish: dict[str, str] | None = {}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class ToolCoverageRun(Base):
|
|
135
|
+
source_pkgs: list[str] = msgspec.field(default=[], name="source_pkgs")
|
|
136
|
+
branch: bool = True
|
|
137
|
+
parallel: bool = True
|
|
138
|
+
omit: list[str] = []
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class ToolCoverage(Base):
|
|
142
|
+
run: Any | None = None
|
|
143
|
+
paths: dict[str, Any] = {}
|
|
144
|
+
report: Any | None = None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class ToolSetuptoolsScme(Base):
|
|
148
|
+
version_file: str | None = msgspec.field(default=None, name="version_file")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class ToolRuffLintISort(Base):
|
|
152
|
+
force_sort_within_sections: bool = False
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class ToolRuffLint(Base):
|
|
156
|
+
isort: ToolRuffLintISort | None = None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class ToolRuff(Base):
|
|
160
|
+
lint: ToolRuffLint | None = None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class ToolMypy(Base):
|
|
164
|
+
check_untyped_defs: bool = False
|
|
165
|
+
disallow_untyped_defs: bool = False
|
|
166
|
+
disallow_untyped_calls: bool = False
|
|
167
|
+
overrides: list[Any] = []
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class Tools(Base):
|
|
171
|
+
"""
|
|
172
|
+
We can't specify a Tool subclass depending on the Tool name,
|
|
173
|
+
so this Tool struct needs to handle ALL the tools we need at
|
|
174
|
+
once :/
|
|
175
|
+
Sucks, but good enough for me :p
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
hatch: ToolHatch | None = None
|
|
179
|
+
uv: ToolUV | None = None
|
|
180
|
+
coverage: ToolCoverage | None = None
|
|
181
|
+
setuptools_scm: ToolSetuptoolsScme | None = msgspec.field(
|
|
182
|
+
default=None, name="setuptools_scm"
|
|
183
|
+
)
|
|
184
|
+
ruff: ToolRuff | None = None
|
|
185
|
+
mypy: ToolMypy | None = None
|
|
186
|
+
setuptools: Any | None = None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class PyProject(Base):
|
|
190
|
+
|
|
191
|
+
build_system: BuildSystem | None = None
|
|
192
|
+
project: Project | None = None
|
|
193
|
+
dependency_groups: dict[str, list[str]] = {}
|
|
194
|
+
tool: Tools | None = None
|
|
195
|
+
|
|
196
|
+
def set_filepath(self, filepath: Path):
|
|
197
|
+
self._filepath = filepath
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def load_pyproject(filepath: Path | str):
|
|
201
|
+
filepath = Path(filepath)
|
|
202
|
+
with filepath.open() as fp:
|
|
203
|
+
data = fp.read()
|
|
204
|
+
pyproject = msgspec.toml.decode(data, type=PyProject)
|
|
205
|
+
return pyproject
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def save_pyproject(pyproject: PyProject, filepath: Path | str):
|
|
209
|
+
filepath = Path(filepath)
|
|
210
|
+
data = msgspec.toml.encode(pyproject)
|
|
211
|
+
with filepath.open("wb") as fp:
|
|
212
|
+
fp.write(data)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test():
|
|
216
|
+
import toml
|
|
217
|
+
import json
|
|
218
|
+
import rich
|
|
219
|
+
import dictdiffer
|
|
220
|
+
|
|
221
|
+
stats = dict(
|
|
222
|
+
tested=0,
|
|
223
|
+
failed=0,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def assert_roundtrip(path: str | Path):
|
|
227
|
+
stats["tested"] += 1
|
|
228
|
+
path = Path(path)
|
|
229
|
+
try:
|
|
230
|
+
pp = load_pyproject(path)
|
|
231
|
+
except Exception as err:
|
|
232
|
+
raise Exception(f"pyproject load failed for {path}: {err}")
|
|
233
|
+
pp_dict = json.loads(msgspec.json.encode(pp))
|
|
234
|
+
toml_dict = toml.load(path)
|
|
235
|
+
try:
|
|
236
|
+
assert pp_dict == toml_dict
|
|
237
|
+
except AssertionError:
|
|
238
|
+
stats["failed"] += 1
|
|
239
|
+
if 0:
|
|
240
|
+
print(10 * "##")
|
|
241
|
+
rich.print(pp_dict)
|
|
242
|
+
print(10 * "--")
|
|
243
|
+
rich.print(toml_dict)
|
|
244
|
+
|
|
245
|
+
print(f"Diff found for {str(path)}:")
|
|
246
|
+
for diff in dictdiffer.diff(pp_dict, toml_dict):
|
|
247
|
+
print(diff)
|
|
248
|
+
|
|
249
|
+
print(f"Roundtrip failed for {str(path)}!")
|
|
250
|
+
|
|
251
|
+
root_path = Path("/home/dee/DEV/_OPEN-TGZR_")
|
|
252
|
+
for folder in root_path.iterdir():
|
|
253
|
+
pyproject_filename = folder / "pyproject.toml"
|
|
254
|
+
if pyproject_filename.exists():
|
|
255
|
+
print("Testing", pyproject_filename)
|
|
256
|
+
assert_roundtrip(pyproject_filename)
|
|
257
|
+
|
|
258
|
+
print(f"Stats: {stats}")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if __name__ == "__main__":
|
|
262
|
+
test()
|
{tgzr_package_management-0.100 → tgzr_package_management-0.102}/tgzr/package_management/venv.py
RENAMED
|
@@ -194,26 +194,26 @@ class Venv:
|
|
|
194
194
|
ret.append(dist)
|
|
195
195
|
return ret
|
|
196
196
|
|
|
197
|
-
def get_packages_slow(self) -> list[tuple[str, str, str]]:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
197
|
+
# def get_packages_slow(self) -> list[tuple[str, str, str]]:
|
|
198
|
+
# stdout, stderr = self.get_cmd_output(
|
|
199
|
+
# cmd_name="uv",
|
|
200
|
+
# # cmd_args=["pip", "tree", "-d", "0"],
|
|
201
|
+
# cmd_args=["pip", "list", "--format", "json"],
|
|
202
|
+
# )
|
|
203
|
+
# try:
|
|
204
|
+
# data = json.loads(stdout)
|
|
205
|
+
# except Exception as err:
|
|
206
|
+
# raise ValueError(f"Error parsing pip list output: {err}")
|
|
207
|
+
# ret = []
|
|
208
|
+
# for entry in data:
|
|
209
|
+
# ret.append(
|
|
210
|
+
# (
|
|
211
|
+
# entry["name"],
|
|
212
|
+
# entry["version"],
|
|
213
|
+
# entry.get("editable_project_location"),
|
|
214
|
+
# )
|
|
215
|
+
# )
|
|
216
|
+
# return ret
|
|
217
217
|
|
|
218
218
|
def get_plugins(
|
|
219
219
|
self, group_filter: str | None
|
|
@@ -230,23 +230,70 @@ class Venv:
|
|
|
230
230
|
plugins.append([ep, dist])
|
|
231
231
|
return plugins
|
|
232
232
|
|
|
233
|
-
def get_plugins_slow(
|
|
234
|
-
|
|
235
|
-
) -> list[importlib_metadata.EntryPoint]:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
233
|
+
# def get_plugins_slow(
|
|
234
|
+
# self, group_filter: str | None
|
|
235
|
+
# ) -> list[importlib_metadata.EntryPoint]:
|
|
236
|
+
# cmd_args = ["studio", "plugins-here", "--format", "json"]
|
|
237
|
+
# if group_filter:
|
|
238
|
+
# cmd_args.extend(["--group-filter", group_filter])
|
|
239
|
+
# stdout, stderr = self.get_cmd_output("tgzr", cmd_args)
|
|
240
|
+
# # print("???", [stdout, stderr])
|
|
241
|
+
# stdout = stdout.split(">>> JSON:", 1)[-1]
|
|
242
|
+
|
|
243
|
+
# data = json.loads(stdout)
|
|
244
|
+
# # print("-->", data)
|
|
245
|
+
# ret = []
|
|
246
|
+
# for entry in data:
|
|
247
|
+
# # print(entry)
|
|
248
|
+
# ep = importlib_metadata.EntryPoint(
|
|
249
|
+
# entry["name"], entry["value"], entry["group"]
|
|
250
|
+
# )
|
|
251
|
+
# ret.append(ep)
|
|
252
|
+
# return ret
|
|
253
|
+
|
|
254
|
+
def hatch_version_bump(self, package_path: Path, bump_type: str):
|
|
255
|
+
hatch_exe = self.get_exe("hatch")
|
|
256
|
+
subprocess.call(
|
|
257
|
+
[hatch_exe, "version", bump_type],
|
|
258
|
+
cwd=package_path,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def hatch_build(
|
|
262
|
+
self,
|
|
263
|
+
package_path: str | Path,
|
|
264
|
+
dist_path: str | Path,
|
|
265
|
+
allow_custom_classifiers=True,
|
|
266
|
+
):
|
|
267
|
+
env = None
|
|
268
|
+
if allow_custom_classifiers:
|
|
269
|
+
env = os.environ.copy()
|
|
270
|
+
# This is needed to build a package with custom classifiers:
|
|
271
|
+
env["HATCH_METADATA_CLASSIFIERS_NO_VERIFY"] = "1"
|
|
272
|
+
|
|
273
|
+
hatch_exe = self.get_exe("hatch")
|
|
274
|
+
subprocess.call(
|
|
275
|
+
[hatch_exe, "build", "-t", "sdist", dist_path],
|
|
276
|
+
cwd=package_path,
|
|
277
|
+
env=env,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def hatch_publish(
|
|
281
|
+
self, package_path: str | Path, dist_path: Path, repo_url: str, **options: str
|
|
282
|
+
):
|
|
283
|
+
hatch_options = sum([["-o", f"{k}={v}"] for k, v in options.items()], [])
|
|
284
|
+
hatch_exe = self.get_exe("hatch")
|
|
285
|
+
cmd = [
|
|
286
|
+
hatch_exe,
|
|
287
|
+
"publish",
|
|
288
|
+
# "--publisher",
|
|
289
|
+
# "tgzr-pipeline-asset",
|
|
290
|
+
"--repo",
|
|
291
|
+
repo_url,
|
|
292
|
+
*hatch_options,
|
|
293
|
+
*dist_path.iterdir(),
|
|
294
|
+
]
|
|
295
|
+
# print("--->", cmd)
|
|
296
|
+
subprocess.call(
|
|
297
|
+
cmd,
|
|
298
|
+
cwd=package_path,
|
|
299
|
+
)
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Literal, Any
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import subprocess
|
|
7
|
+
import subprocess
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from packaging.requirements import Requirement
|
|
11
|
+
import uv
|
|
12
|
+
|
|
13
|
+
from . import pyproject
|
|
14
|
+
from .pyproject import (
|
|
15
|
+
PyProject,
|
|
16
|
+
Project,
|
|
17
|
+
Tools,
|
|
18
|
+
ToolUV,
|
|
19
|
+
ToolUVIndex,
|
|
20
|
+
ToolUVSource,
|
|
21
|
+
ToolUVWorkspace,
|
|
22
|
+
)
|
|
23
|
+
from .venv import Venv
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Workspace:
|
|
29
|
+
"""
|
|
30
|
+
A uv workspace
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
path: Path | str,
|
|
36
|
+
) -> None:
|
|
37
|
+
self._path = Path(path)
|
|
38
|
+
self._pyproject_filename = self._path / "pyproject.toml"
|
|
39
|
+
self._pyproject: PyProject | None = None
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def path(self) -> Path:
|
|
43
|
+
"""The Path of the workspace."""
|
|
44
|
+
return self._path
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def name(self) -> str:
|
|
48
|
+
"""The name of the workspace folder."""
|
|
49
|
+
return self._path.name
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def group(self) -> str:
|
|
53
|
+
"""The name of the parent folder (sometime representing a group of workspaces)."""
|
|
54
|
+
return self._path.parent.name
|
|
55
|
+
|
|
56
|
+
def exists(self) -> bool:
|
|
57
|
+
"""
|
|
58
|
+
Returns True if the folder exists and contains a pyproject.toml
|
|
59
|
+
NB: It could be True for non-workspace folders, we assume you don't mess up your project paths.
|
|
60
|
+
"""
|
|
61
|
+
return self._path.exists() and self._pyproject_filename.exists()
|
|
62
|
+
|
|
63
|
+
def venv(self) -> Venv:
|
|
64
|
+
venv_name = ".venv"
|
|
65
|
+
return Venv(self.path / venv_name)
|
|
66
|
+
|
|
67
|
+
def create(
|
|
68
|
+
self,
|
|
69
|
+
description: str | None = None,
|
|
70
|
+
python_version: str | None = None,
|
|
71
|
+
vcs: Literal["git", "none"] | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
description = (
|
|
74
|
+
description
|
|
75
|
+
or "A UV workspace, managed by tgzr.package_management.workspcace."
|
|
76
|
+
)
|
|
77
|
+
descr_args = []
|
|
78
|
+
if description is not None:
|
|
79
|
+
descr_args = ["--description", description]
|
|
80
|
+
|
|
81
|
+
py_args = []
|
|
82
|
+
if python_version is not None:
|
|
83
|
+
py_args = ["-p", python_version]
|
|
84
|
+
|
|
85
|
+
uv_exe = uv.find_uv_bin()
|
|
86
|
+
cmd = [
|
|
87
|
+
uv_exe,
|
|
88
|
+
"init",
|
|
89
|
+
"--no-package",
|
|
90
|
+
"--vcs",
|
|
91
|
+
vcs,
|
|
92
|
+
*py_args,
|
|
93
|
+
"--no-workspace",
|
|
94
|
+
*descr_args,
|
|
95
|
+
"--author-from",
|
|
96
|
+
"auto",
|
|
97
|
+
str(self.path),
|
|
98
|
+
]
|
|
99
|
+
print(f"Creating workspace {self.path}: {cmd}")
|
|
100
|
+
subprocess.check_call(cmd)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def pyproject(self) -> PyProject:
|
|
104
|
+
if self._pyproject is None:
|
|
105
|
+
self._pyproject = pyproject.load_pyproject(self._pyproject_filename)
|
|
106
|
+
if self.pyproject.tool is None:
|
|
107
|
+
self.pyproject.tool = Tools(uv=ToolUV())
|
|
108
|
+
if self.pyproject.tool.uv is None:
|
|
109
|
+
self.pyproject.tool.uv = ToolUV()
|
|
110
|
+
|
|
111
|
+
return self._pyproject
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def tool_uv(self) -> ToolUV:
|
|
115
|
+
"""The PyProject.tool.uv configuration."""
|
|
116
|
+
return self.pyproject.tool.uv # type: ignore self.pyproject ensure it's not None!
|
|
117
|
+
|
|
118
|
+
def save_pyproject(self) -> None:
|
|
119
|
+
pyproject.save_pyproject(self.pyproject, self._pyproject_filename)
|
|
120
|
+
|
|
121
|
+
def get_index(self, name: str) -> ToolUVIndex | None:
|
|
122
|
+
for index in self.tool_uv.index:
|
|
123
|
+
if index.name == name:
|
|
124
|
+
return index
|
|
125
|
+
|
|
126
|
+
def ensure_index(self, name: str, url: str, explicit: bool | None = None) -> None:
|
|
127
|
+
"""
|
|
128
|
+
Will create or update the index with name `name`.
|
|
129
|
+
"""
|
|
130
|
+
index_to_set = ToolUVIndex(name=name, url=url, explicit=explicit)
|
|
131
|
+
found = False
|
|
132
|
+
for index in self.tool_uv.index:
|
|
133
|
+
if index == index_to_set:
|
|
134
|
+
# it is already set exactly as requested
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
if index.name == index_to_set.name:
|
|
138
|
+
found = True
|
|
139
|
+
index.url = index_to_set.url
|
|
140
|
+
index.explicit = index_to_set.explicit
|
|
141
|
+
break
|
|
142
|
+
if not found:
|
|
143
|
+
self.tool_uv.index.append(index_to_set)
|
|
144
|
+
self.save_pyproject()
|
|
145
|
+
|
|
146
|
+
def set_source(
|
|
147
|
+
self,
|
|
148
|
+
source_name: str,
|
|
149
|
+
index_name: str | None = None,
|
|
150
|
+
workspace: bool | None = None,
|
|
151
|
+
path: Path | str | None = None,
|
|
152
|
+
editable: bool | None = None,
|
|
153
|
+
) -> None:
|
|
154
|
+
"""
|
|
155
|
+
Beware: not all combinations of index/workspace/path/editable are valid!
|
|
156
|
+
|
|
157
|
+
When index_name is given, an index with that name must already
|
|
158
|
+
have been defined. You can use `self.ensure_index()` if needed.
|
|
159
|
+
"""
|
|
160
|
+
source = self.tool_uv.sources.get(source_name)
|
|
161
|
+
if source is None:
|
|
162
|
+
source = ToolUVSource()
|
|
163
|
+
self.tool_uv.sources[source_name] = source
|
|
164
|
+
|
|
165
|
+
if index_name is not None:
|
|
166
|
+
source.index = index_name
|
|
167
|
+
|
|
168
|
+
if workspace is not None:
|
|
169
|
+
source.workspace = workspace
|
|
170
|
+
|
|
171
|
+
if path is not None:
|
|
172
|
+
source.path = str(path)
|
|
173
|
+
|
|
174
|
+
if editable is not None:
|
|
175
|
+
source.editable = editable
|
|
176
|
+
|
|
177
|
+
self.save_pyproject()
|
|
178
|
+
|
|
179
|
+
def add_dependencies(self, group: str = "", *new_requirements):
|
|
180
|
+
if self.pyproject.project is None:
|
|
181
|
+
self.pyproject.project = Project()
|
|
182
|
+
|
|
183
|
+
if group is None:
|
|
184
|
+
deps = self.pyproject.project.dependencies
|
|
185
|
+
else:
|
|
186
|
+
try:
|
|
187
|
+
deps = self.pyproject.dependency_groups[group]
|
|
188
|
+
except KeyError:
|
|
189
|
+
deps = []
|
|
190
|
+
self.pyproject.dependency_groups[group] = deps
|
|
191
|
+
|
|
192
|
+
to_remove = []
|
|
193
|
+
for new in new_requirements:
|
|
194
|
+
new_req = Requirement(new)
|
|
195
|
+
for old in deps:
|
|
196
|
+
if Requirement(old).name == new_req.name:
|
|
197
|
+
to_remove.append(old)
|
|
198
|
+
break
|
|
199
|
+
deps.append(new)
|
|
200
|
+
|
|
201
|
+
for obsolet in to_remove:
|
|
202
|
+
deps.remove(obsolet)
|
|
203
|
+
|
|
204
|
+
self.save_pyproject()
|
|
205
|
+
|
|
206
|
+
def add_member(self, member: str):
|
|
207
|
+
if self.tool_uv.workspace is None:
|
|
208
|
+
self.tool_uv.workspace = ToolUVWorkspace()
|
|
209
|
+
self.tool_uv.workspace.members.append(member)
|
|
210
|
+
self.save_pyproject()
|
|
211
|
+
|
|
212
|
+
def run(self, console_script_name: str, *args, **extra_env: str):
|
|
213
|
+
uv_exe = uv.find_uv_bin()
|
|
214
|
+
cmd = [
|
|
215
|
+
uv_exe,
|
|
216
|
+
"run",
|
|
217
|
+
# "--python",
|
|
218
|
+
# str(python),
|
|
219
|
+
"--directory",
|
|
220
|
+
str(self.path),
|
|
221
|
+
console_script_name,
|
|
222
|
+
*args,
|
|
223
|
+
]
|
|
224
|
+
env = None
|
|
225
|
+
if extra_env:
|
|
226
|
+
env = os.environ.copy()
|
|
227
|
+
env.update(extra_env)
|
|
228
|
+
|
|
229
|
+
print(f"Workspace run: {self.path}: {cmd}")
|
|
230
|
+
subprocess.check_call(cmd)
|
|
231
|
+
|
|
232
|
+
def run_python_command(self, command: str) -> None:
|
|
233
|
+
uv_exe = uv.find_uv_bin()
|
|
234
|
+
cmd = [
|
|
235
|
+
uv_exe,
|
|
236
|
+
"run",
|
|
237
|
+
# "--python",
|
|
238
|
+
# str(python),
|
|
239
|
+
"--directory",
|
|
240
|
+
str(self.path),
|
|
241
|
+
"python",
|
|
242
|
+
"-c",
|
|
243
|
+
command,
|
|
244
|
+
]
|
|
245
|
+
print(f"Workspace run python cmd: {self.path}: {cmd}")
|
|
246
|
+
subprocess.check_call(cmd)
|
|
247
|
+
|
|
248
|
+
def sync(
|
|
249
|
+
self, allow_upgrade: bool = True, allow_custom_classifiers: bool = False
|
|
250
|
+
) -> None:
|
|
251
|
+
env = None
|
|
252
|
+
if allow_custom_classifiers:
|
|
253
|
+
env = os.environ.copy()
|
|
254
|
+
# This is needed to build the packages with custom classifiers:
|
|
255
|
+
env["HATCH_METADATA_CLASSIFIERS_NO_VERIFY"] = "1"
|
|
256
|
+
|
|
257
|
+
more_options = []
|
|
258
|
+
if allow_upgrade:
|
|
259
|
+
more_options.append("--upgrade")
|
|
260
|
+
|
|
261
|
+
uv_exe = uv.find_uv_bin()
|
|
262
|
+
cmd = [uv_exe, "--project", self.path, "sync", *more_options]
|
|
263
|
+
print(f"Sync workspace {self.path}: {cmd}")
|
|
264
|
+
subprocess.check_call(cmd, env=env)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tgzr_package_management-0.100 → tgzr_package_management-0.102}/tgzr/package_management/__init__.py
RENAMED
|
File without changes
|