wexample-wex-addon-dev-python 0.0.53__py3-none-any.whl → 0.0.61__py3-none-any.whl
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.
- wexample_wex_addon_dev_python/const/python.py +7 -0
- wexample_wex_addon_dev_python/file/python_package_toml_file.py +97 -56
- wexample_wex_addon_dev_python/resources/readme_templates/tests.md.j2 +1 -1
- wexample_wex_addon_dev_python/workdir/python_package_workdir.py +226 -164
- wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py +6 -1
- wexample_wex_addon_dev_python/workdir/python_workdir.py +273 -127
- {wexample_wex_addon_dev_python-0.0.53.dist-info → wexample_wex_addon_dev_python-0.0.61.dist-info}/METADATA +6 -6
- {wexample_wex_addon_dev_python-0.0.53.dist-info → wexample_wex_addon_dev_python-0.0.61.dist-info}/RECORD +10 -9
- {wexample_wex_addon_dev_python-0.0.53.dist-info → wexample_wex_addon_dev_python-0.0.61.dist-info}/WHEEL +1 -1
- {wexample_wex_addon_dev_python-0.0.53.dist-info → wexample_wex_addon_dev_python-0.0.61.dist-info}/entry_points.txt +0 -0
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
from wexample_filestate.item.file.toml_file import TomlFile
|
|
6
6
|
from wexample_helpers.decorator.base_class import base_class
|
|
7
|
+
from wexample_wex_addon_app.const.path import APP_PATH_README
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
10
|
from tomlkit import TOMLDocument
|
|
@@ -22,19 +23,21 @@ class PythonPackageTomlFile(TomlFile):
|
|
|
22
23
|
from wexample_filestate_python.helpers.toml import toml_sort_string_array
|
|
23
24
|
|
|
24
25
|
deps = self._get_deps_array(optional=optional, group=group)
|
|
25
|
-
|
|
26
|
-
new_name = canonicalize_name(
|
|
27
|
-
removed = self.remove_dependency_by_name(
|
|
28
|
-
new_name, optional=optional, group=group
|
|
29
|
-
)
|
|
26
|
+
new_req = Requirement(spec)
|
|
27
|
+
new_name = canonicalize_name(new_req.name)
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
old_spec = None
|
|
30
|
+
for dep in deps:
|
|
31
|
+
if canonicalize_name(Requirement(dep).name) == new_name:
|
|
32
|
+
old_spec = dep
|
|
33
|
+
break
|
|
34
|
+
|
|
35
|
+
self.remove_dependency_by_name(new_name, optional=optional, group=group)
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
deps.append(spec)
|
|
38
|
+
toml_sort_string_array(deps)
|
|
39
|
+
|
|
40
|
+
return old_spec != spec
|
|
38
41
|
|
|
39
42
|
def dumps(self, content: TOMLDocument | dict | None = None) -> str:
|
|
40
43
|
"""Serialize a TOMLDocument (preferred) or a plain dict to TOML.
|
|
@@ -54,6 +57,7 @@ class PythonPackageTomlFile(TomlFile):
|
|
|
54
57
|
self._enforce_project_metadata(content, project_name, project_version)
|
|
55
58
|
self._normalize_dependencies(content)
|
|
56
59
|
self._ensure_dev_dependencies(content)
|
|
60
|
+
self._enforce_pytest_coverage_config(content, import_name)
|
|
57
61
|
self._reorder_toml_sections(content)
|
|
58
62
|
|
|
59
63
|
result = dumps(content)
|
|
@@ -96,6 +100,19 @@ class PythonPackageTomlFile(TomlFile):
|
|
|
96
100
|
continue
|
|
97
101
|
return names
|
|
98
102
|
|
|
103
|
+
def optional_group_array(self, group: str):
|
|
104
|
+
"""Ensure and return project.optional-dependencies[group] as multi-line array."""
|
|
105
|
+
from wexample_filestate_python.helpers.toml import (
|
|
106
|
+
toml_ensure_array,
|
|
107
|
+
toml_ensure_table,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
project = self._project_table()
|
|
111
|
+
opt, _ = toml_ensure_table(project, ["optional-dependencies"])
|
|
112
|
+
arr, _ = toml_ensure_array(opt, group)
|
|
113
|
+
arr.multiline(True)
|
|
114
|
+
return arr
|
|
115
|
+
|
|
99
116
|
def remove_dependency_by_name(
|
|
100
117
|
self, package_name: str, optional: bool = False, group: str = "dev"
|
|
101
118
|
) -> bool:
|
|
@@ -218,20 +235,55 @@ class PythonPackageTomlFile(TomlFile):
|
|
|
218
235
|
|
|
219
236
|
# Add README if it exists
|
|
220
237
|
if package:
|
|
221
|
-
|
|
222
|
-
WithReadmeWorkdirMixin,
|
|
223
|
-
)
|
|
238
|
+
pass
|
|
224
239
|
|
|
225
|
-
readme_file = package.find_by_name(
|
|
240
|
+
readme_file = package.find_by_name(APP_PATH_README)
|
|
226
241
|
if readme_file:
|
|
227
242
|
readme_tbl, _ = toml_ensure_table(project_tbl, ["readme"])
|
|
228
|
-
readme_tbl["file"] =
|
|
243
|
+
readme_tbl["file"] = str(APP_PATH_README)
|
|
229
244
|
readme_tbl["content-type"] = "text/markdown"
|
|
230
245
|
|
|
231
246
|
# Add MIT license
|
|
232
247
|
license_tbl, _ = toml_ensure_table(project_tbl, ["license"])
|
|
233
248
|
license_tbl["text"] = "MIT"
|
|
234
249
|
|
|
250
|
+
def _enforce_pytest_coverage_config(
|
|
251
|
+
self, content: dict, import_name: str | None
|
|
252
|
+
) -> None:
|
|
253
|
+
"""Add pytest and coverage configuration to limit coverage to the package only."""
|
|
254
|
+
if not import_name:
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
from wexample_filestate_python.helpers.toml import toml_ensure_table
|
|
258
|
+
|
|
259
|
+
tool_tbl, _ = toml_ensure_table(content, ["tool"])
|
|
260
|
+
|
|
261
|
+
# Add pytest configuration
|
|
262
|
+
pytest_tbl, _ = toml_ensure_table(tool_tbl, ["pytest", "ini_options"])
|
|
263
|
+
pytest_tbl["testpaths"] = ["tests"]
|
|
264
|
+
pytest_tbl["pythonpath"] = ["src"]
|
|
265
|
+
|
|
266
|
+
# Add coverage.run configuration to limit source to the package
|
|
267
|
+
coverage_run_tbl, _ = toml_ensure_table(tool_tbl, ["coverage", "run"])
|
|
268
|
+
coverage_run_tbl["source"] = [import_name]
|
|
269
|
+
coverage_run_tbl["omit"] = [
|
|
270
|
+
"*/tests/*",
|
|
271
|
+
"*/.venv/*",
|
|
272
|
+
"*/venv/*",
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
# Add coverage.report configuration
|
|
276
|
+
coverage_report_tbl, _ = toml_ensure_table(tool_tbl, ["coverage", "report"])
|
|
277
|
+
coverage_report_tbl["exclude_lines"] = [
|
|
278
|
+
"pragma: no cover",
|
|
279
|
+
"def __repr__",
|
|
280
|
+
"raise AssertionError",
|
|
281
|
+
"raise NotImplementedError",
|
|
282
|
+
"if __name__ == .__main__.:",
|
|
283
|
+
"if TYPE_CHECKING:",
|
|
284
|
+
"@abstractmethod",
|
|
285
|
+
]
|
|
286
|
+
|
|
235
287
|
def _ensure_dev_dependencies(self, content: dict) -> None:
|
|
236
288
|
from wexample_filestate_python.helpers.package import package_normalize_name
|
|
237
289
|
from wexample_filestate_python.helpers.toml import (
|
|
@@ -239,7 +291,7 @@ class PythonPackageTomlFile(TomlFile):
|
|
|
239
291
|
toml_sort_string_array,
|
|
240
292
|
)
|
|
241
293
|
|
|
242
|
-
dev_arr = self.
|
|
294
|
+
dev_arr = self.optional_group_array("dev")
|
|
243
295
|
deps_arr = self._dependencies_array()
|
|
244
296
|
|
|
245
297
|
runtime_pkgs = {
|
|
@@ -257,9 +309,7 @@ class PythonPackageTomlFile(TomlFile):
|
|
|
257
309
|
def _get_deps_array(self, optional: bool = False, group: str = "dev"):
|
|
258
310
|
"""Return TOML array for runtime deps or optional group (multiline)."""
|
|
259
311
|
return (
|
|
260
|
-
self.
|
|
261
|
-
if optional
|
|
262
|
-
else self._dependencies_array()
|
|
312
|
+
self.optional_group_array(group) if optional else self._dependencies_array()
|
|
263
313
|
)
|
|
264
314
|
|
|
265
315
|
def _normalize_dependencies(self, content: dict) -> None:
|
|
@@ -276,9 +326,24 @@ class PythonPackageTomlFile(TomlFile):
|
|
|
276
326
|
deps_arr = self._dependencies_array()
|
|
277
327
|
toml_sort_string_array(deps_arr)
|
|
278
328
|
|
|
329
|
+
# Read the keep list from [tool.filestate].keep
|
|
330
|
+
keep_packages: set[str] = set()
|
|
331
|
+
if "tool" in content and isinstance(content["tool"], dict):
|
|
332
|
+
tool_tbl = content["tool"]
|
|
333
|
+
if "filestate" in tool_tbl and isinstance(tool_tbl["filestate"], dict):
|
|
334
|
+
filestate_tbl = tool_tbl["filestate"]
|
|
335
|
+
if "keep" in filestate_tbl and isinstance(filestate_tbl["keep"], list):
|
|
336
|
+
keep_packages = {
|
|
337
|
+
package_normalize_name(str(pkg))
|
|
338
|
+
for pkg in filestate_tbl["keep"]
|
|
339
|
+
}
|
|
340
|
+
|
|
279
341
|
# filter unwanted deps
|
|
280
342
|
def _should_remove(item: object) -> bool:
|
|
281
343
|
name = package_normalize_name(toml_get_string_value(item))
|
|
344
|
+
# Don't remove if in keep list
|
|
345
|
+
if name in keep_packages:
|
|
346
|
+
return False
|
|
282
347
|
return name in RUNTIME_DEPENDENCY_REMOVE_NAMES or (
|
|
283
348
|
name == "typing-extensions"
|
|
284
349
|
)
|
|
@@ -288,29 +353,6 @@ class PythonPackageTomlFile(TomlFile):
|
|
|
288
353
|
deps_arr.extend(filtered)
|
|
289
354
|
toml_sort_string_array(deps_arr)
|
|
290
355
|
|
|
291
|
-
# normalize attrs/cattrs
|
|
292
|
-
normalized = []
|
|
293
|
-
for it in list(deps_arr):
|
|
294
|
-
base = package_normalize_name(toml_get_string_value(it).strip())
|
|
295
|
-
if base == "attrs":
|
|
296
|
-
normalized.append("attrs>=23.1.0")
|
|
297
|
-
elif base == "cattrs":
|
|
298
|
-
normalized.append("cattrs>=23.1.0")
|
|
299
|
-
else:
|
|
300
|
-
normalized.append(it)
|
|
301
|
-
if normalized:
|
|
302
|
-
deps_arr.clear()
|
|
303
|
-
deps_arr.extend(normalized)
|
|
304
|
-
toml_sort_string_array(deps_arr)
|
|
305
|
-
|
|
306
|
-
# ensure they are present
|
|
307
|
-
names = {package_normalize_name(toml_get_string_value(it)) for it in deps_arr}
|
|
308
|
-
if "attrs" not in names:
|
|
309
|
-
deps_arr.append("attrs>=23.1.0")
|
|
310
|
-
if "cattrs" not in names:
|
|
311
|
-
deps_arr.append("cattrs>=23.1.0")
|
|
312
|
-
toml_sort_string_array(deps_arr)
|
|
313
|
-
|
|
314
356
|
def _normalize_toml_formatting(self, content: str) -> str:
|
|
315
357
|
"""Normalize TOML formatting:
|
|
316
358
|
- No empty lines at the beginning
|
|
@@ -330,19 +372,6 @@ class PythonPackageTomlFile(TomlFile):
|
|
|
330
372
|
|
|
331
373
|
return content
|
|
332
374
|
|
|
333
|
-
def _optional_group_array(self, group: str):
|
|
334
|
-
"""Ensure and return project.optional-dependencies[group] as multi-line array."""
|
|
335
|
-
from wexample_filestate_python.helpers.toml import (
|
|
336
|
-
toml_ensure_array,
|
|
337
|
-
toml_ensure_table,
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
project = self._project_table()
|
|
341
|
-
opt, _ = toml_ensure_table(project, ["optional-dependencies"])
|
|
342
|
-
arr, _ = toml_ensure_array(opt, group)
|
|
343
|
-
arr.multiline(True)
|
|
344
|
-
return arr
|
|
345
|
-
|
|
346
375
|
def _project_table(self):
|
|
347
376
|
"""Ensure and return the [project] table."""
|
|
348
377
|
from wexample_filestate_python.helpers.toml import toml_ensure_table
|
|
@@ -392,9 +421,21 @@ class PythonPackageTomlFile(TomlFile):
|
|
|
392
421
|
"optional-dependencies",
|
|
393
422
|
]
|
|
394
423
|
|
|
424
|
+
# Define the desired order for keys within [tool]
|
|
425
|
+
tool_key_order = [
|
|
426
|
+
"setuptools",
|
|
427
|
+
"pdm",
|
|
428
|
+
"pytest",
|
|
429
|
+
"coverage",
|
|
430
|
+
]
|
|
431
|
+
|
|
395
432
|
# Reorder top-level sections
|
|
396
433
|
self._reorder_dict_keys(content, section_order)
|
|
397
434
|
|
|
398
435
|
# Reorder keys within [project] if it exists
|
|
399
436
|
if "project" in content:
|
|
400
437
|
self._reorder_dict_keys(content["project"], project_key_order)
|
|
438
|
+
|
|
439
|
+
# Reorder keys within [tool] if it exists
|
|
440
|
+
if "tool" in content:
|
|
441
|
+
self._reorder_dict_keys(content["tool"], tool_key_order)
|
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
+
from wexample_filestate.const.disk import DiskItemType
|
|
6
|
+
|
|
5
7
|
from wexample_wex_addon_dev_python.workdir.python_workdir import PythonWorkdir
|
|
6
8
|
|
|
7
9
|
if TYPE_CHECKING:
|
|
@@ -18,88 +20,17 @@ if TYPE_CHECKING:
|
|
|
18
20
|
class PythonPackageWorkdir(PythonWorkdir):
|
|
19
21
|
_project_info_cache = None
|
|
20
22
|
|
|
21
|
-
def _get_children_package_workdir_class(self) -> type[FrameworkPackageSuiteWorkdir]:
|
|
22
|
-
from wexample_wex_addon_dev_python.workdir.python_packages_suite_workdir import (
|
|
23
|
-
PythonPackagesSuiteWorkdir,
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
return PythonPackagesSuiteWorkdir
|
|
27
|
-
|
|
28
|
-
def depends_from(self, package: PythonPackageWorkdir) -> bool:
|
|
29
|
-
for dependence_name in self.get_dependencies():
|
|
30
|
-
if package.get_package_name() == dependence_name:
|
|
31
|
-
return True
|
|
32
|
-
return False
|
|
33
|
-
|
|
34
|
-
def prepare_value(self, raw_value: DictConfig | None = None) -> DictConfig:
|
|
35
|
-
from wexample_helpers.helpers.array import array_dict_get_by
|
|
36
|
-
|
|
37
|
-
raw_value = super().prepare_value(raw_value=raw_value)
|
|
38
|
-
|
|
39
|
-
# Retrieve the '.gitignore' configuration or create it if it doesn't exist
|
|
40
|
-
config_gitignore = array_dict_get_by(
|
|
41
|
-
"name", ".gitignore", raw_value["children"]
|
|
42
|
-
)
|
|
43
|
-
if config_gitignore is not None:
|
|
44
|
-
generic_gitignore_rules = {
|
|
45
|
-
"Python artifacts": [
|
|
46
|
-
"*.egg-info",
|
|
47
|
-
"__pycache__/",
|
|
48
|
-
"*.py[cod]",
|
|
49
|
-
"*.pyo",
|
|
50
|
-
],
|
|
51
|
-
"Build directories": [
|
|
52
|
-
"/build/",
|
|
53
|
-
"/dist/",
|
|
54
|
-
"/pip-wheel-metadata/",
|
|
55
|
-
],
|
|
56
|
-
"Virtual environments": [
|
|
57
|
-
".env",
|
|
58
|
-
".venv",
|
|
59
|
-
"venv/",
|
|
60
|
-
],
|
|
61
|
-
"Test and coverage artifacts": [
|
|
62
|
-
".tox/",
|
|
63
|
-
".mypy_cache/",
|
|
64
|
-
"pytest_cache/",
|
|
65
|
-
".coverage",
|
|
66
|
-
"htmlcov/",
|
|
67
|
-
],
|
|
68
|
-
"Editor and IDE settings": [
|
|
69
|
-
".vscode/",
|
|
70
|
-
".idea/",
|
|
71
|
-
"*.swp",
|
|
72
|
-
"*~",
|
|
73
|
-
],
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
should_contain_lines = config_gitignore.setdefault(
|
|
77
|
-
"should_contain_lines", []
|
|
78
|
-
)
|
|
79
|
-
if not isinstance(should_contain_lines, list):
|
|
80
|
-
raise ValueError("'should_contain_lines' must be a list")
|
|
81
|
-
|
|
82
|
-
for category, rules in generic_gitignore_rules.items():
|
|
83
|
-
category_header = f"# {category}"
|
|
84
|
-
if category_header not in should_contain_lines:
|
|
85
|
-
should_contain_lines.append(category_header)
|
|
86
|
-
|
|
87
|
-
for rule in rules:
|
|
88
|
-
if rule not in should_contain_lines:
|
|
89
|
-
should_contain_lines.append(rule)
|
|
90
|
-
|
|
91
|
-
return raw_value
|
|
92
|
-
|
|
93
23
|
def app_install(self, env: str | None = None, force: bool = False) -> bool:
|
|
94
24
|
from wexample_app.const.env import ENV_NAME_LOCAL
|
|
95
25
|
from wexample_helpers.helpers.shell import shell_run
|
|
96
26
|
|
|
27
|
+
# In local env, installs packages using pip.
|
|
97
28
|
if env == ENV_NAME_LOCAL:
|
|
29
|
+
toml_file = self.get_project_config_file()
|
|
98
30
|
# Get all dependencies from pyproject.toml
|
|
99
|
-
pyproject_toml_dependencies = (
|
|
100
|
-
|
|
101
|
-
)
|
|
102
|
-
suite_workdir = self.get_suite_workdir()
|
|
31
|
+
pyproject_toml_dependencies = toml_file.list_dependency_names()
|
|
32
|
+
|
|
33
|
+
suite_workdir = self.get_shallow_suite_workdir()
|
|
103
34
|
|
|
104
35
|
# Ensure venv is created and configured
|
|
105
36
|
app_path = self.get_path()
|
|
@@ -112,7 +43,7 @@ class PythonPackageWorkdir(PythonWorkdir):
|
|
|
112
43
|
if not venv_is_valid:
|
|
113
44
|
# Remove corrupted/empty venv if it exists
|
|
114
45
|
if venv_path.exists():
|
|
115
|
-
self.
|
|
46
|
+
self.log(f"Removing invalid venv at {venv_path}", indentation=1)
|
|
116
47
|
import shutil
|
|
117
48
|
|
|
118
49
|
shutil.rmtree(venv_path)
|
|
@@ -143,6 +74,7 @@ class PythonPackageWorkdir(PythonWorkdir):
|
|
|
143
74
|
inherit_stdio=True,
|
|
144
75
|
)
|
|
145
76
|
|
|
77
|
+
# The package is a part of a workdir, so we install manually individual package.
|
|
146
78
|
if suite_workdir:
|
|
147
79
|
# Get all packages from the suite ordered by dependencies (leaf -> trunk)
|
|
148
80
|
suite_packages = suite_workdir.get_ordered_packages()
|
|
@@ -162,12 +94,12 @@ class PythonPackageWorkdir(PythonWorkdir):
|
|
|
162
94
|
|
|
163
95
|
# Install external packages first (normal install)
|
|
164
96
|
if external_dependencies:
|
|
165
|
-
self.
|
|
97
|
+
self.subtitle(
|
|
166
98
|
f"Installing {len(external_dependencies)} external packages",
|
|
167
99
|
indentation=1,
|
|
168
100
|
)
|
|
169
101
|
for dep in external_dependencies:
|
|
170
|
-
self.
|
|
102
|
+
self.log(f"Installing {dep}", indentation=2)
|
|
171
103
|
shell_run(
|
|
172
104
|
cmd=[
|
|
173
105
|
".venv/bin/python",
|
|
@@ -182,7 +114,7 @@ class PythonPackageWorkdir(PythonWorkdir):
|
|
|
182
114
|
|
|
183
115
|
# Install suite packages in editable mode (leaf -> trunk order)
|
|
184
116
|
if suite_dependencies_ordered:
|
|
185
|
-
self.
|
|
117
|
+
self.subtitle(
|
|
186
118
|
f"Installing {len(suite_dependencies_ordered)} suite packages in editable mode (leaf -> trunk)",
|
|
187
119
|
indentation=1,
|
|
188
120
|
)
|
|
@@ -194,13 +126,13 @@ class PythonPackageWorkdir(PythonWorkdir):
|
|
|
194
126
|
if not force and self._is_package_installed_editable(
|
|
195
127
|
app_path, package_name, package_path
|
|
196
128
|
):
|
|
197
|
-
self.
|
|
129
|
+
self.log(
|
|
198
130
|
f"Skipping {package_name} (already installed in editable mode)",
|
|
199
131
|
indentation=2,
|
|
200
132
|
)
|
|
201
133
|
continue
|
|
202
134
|
|
|
203
|
-
self.
|
|
135
|
+
self.log(f"Installing {package_name}", indentation=2)
|
|
204
136
|
shell_run(
|
|
205
137
|
cmd=[
|
|
206
138
|
".venv/bin/python",
|
|
@@ -213,6 +145,26 @@ class PythonPackageWorkdir(PythonWorkdir):
|
|
|
213
145
|
cwd=app_path,
|
|
214
146
|
inherit_stdio=True,
|
|
215
147
|
)
|
|
148
|
+
|
|
149
|
+
# Avoid error using -G
|
|
150
|
+
dev_group_name = "dev"
|
|
151
|
+
if (
|
|
152
|
+
len(
|
|
153
|
+
self.get_project_config_file().optional_group_array(
|
|
154
|
+
group=dev_group_name
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
> 0
|
|
158
|
+
):
|
|
159
|
+
self._pdm_update_lock_if_needed()
|
|
160
|
+
|
|
161
|
+
self.log(f"Installing dev group dependencies")
|
|
162
|
+
self._pdm_run_command(command=["install", "-G", dev_group_name])
|
|
163
|
+
else:
|
|
164
|
+
self.log(
|
|
165
|
+
"Skipping dev group install: group 'dev' not defined in pyproject.toml"
|
|
166
|
+
)
|
|
167
|
+
|
|
216
168
|
return True
|
|
217
169
|
|
|
218
170
|
# For non-local environments, use standard PDM install
|
|
@@ -221,49 +173,125 @@ class PythonPackageWorkdir(PythonWorkdir):
|
|
|
221
173
|
force=force,
|
|
222
174
|
)
|
|
223
175
|
|
|
224
|
-
def
|
|
225
|
-
self
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
) -> bool:
|
|
230
|
-
"""Check if a package is already installed in editable mode at the correct path."""
|
|
231
|
-
import subprocess
|
|
176
|
+
def depends_from(self, package: PythonPackageWorkdir) -> bool:
|
|
177
|
+
for dependence_name in self.get_dependencies():
|
|
178
|
+
if package.get_package_name() == dependence_name:
|
|
179
|
+
return True
|
|
180
|
+
return False
|
|
232
181
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
182
|
+
def prepare_value(self, raw_value: DictConfig | None = None) -> DictConfig:
|
|
183
|
+
from wexample_helpers.helpers.array import array_dict_get_by
|
|
184
|
+
|
|
185
|
+
raw_value = super().prepare_value(raw_value=raw_value)
|
|
186
|
+
children = raw_value.get("children")
|
|
187
|
+
|
|
188
|
+
children.append(
|
|
189
|
+
{
|
|
190
|
+
"name": "examples",
|
|
191
|
+
"type": DiskItemType.DIRECTORY,
|
|
192
|
+
"should_exist": True,
|
|
193
|
+
"children": [
|
|
194
|
+
{
|
|
195
|
+
"name": "__main__.py",
|
|
196
|
+
"type": DiskItemType.FILE,
|
|
197
|
+
"should_exist": True,
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Retrieve the '.gitignore' configuration or create it if it doesn't exist
|
|
204
|
+
config_gitignore = array_dict_get_by("name", ".gitignore", children)
|
|
205
|
+
if config_gitignore is not None:
|
|
206
|
+
generic_gitignore_rules = {
|
|
207
|
+
"Python artifacts": [
|
|
208
|
+
"*.egg-info",
|
|
209
|
+
"__pycache__/",
|
|
210
|
+
"*.py[cod]",
|
|
211
|
+
"*.pyo",
|
|
212
|
+
],
|
|
213
|
+
"Build directories": [
|
|
214
|
+
"/build/",
|
|
215
|
+
"/dist/",
|
|
216
|
+
"/pip-wheel-metadata/",
|
|
217
|
+
],
|
|
218
|
+
"Virtual environments": [
|
|
219
|
+
".env",
|
|
220
|
+
".venv",
|
|
221
|
+
"venv/",
|
|
222
|
+
],
|
|
223
|
+
"Test and coverage artifacts": [
|
|
224
|
+
".tox/",
|
|
225
|
+
".mypy_cache/",
|
|
226
|
+
"pytest_cache/",
|
|
227
|
+
".coverage",
|
|
228
|
+
"htmlcov/",
|
|
229
|
+
],
|
|
230
|
+
"Editor and IDE settings": [
|
|
231
|
+
".vscode/",
|
|
232
|
+
".idea/",
|
|
233
|
+
"*.swp",
|
|
234
|
+
"*~",
|
|
235
|
+
],
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
should_contain_lines = config_gitignore.setdefault(
|
|
239
|
+
"should_contain_lines", []
|
|
240
240
|
)
|
|
241
|
+
if not isinstance(should_contain_lines, list):
|
|
242
|
+
raise ValueError("'should_contain_lines' must be a list")
|
|
241
243
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
+
for category, rules in generic_gitignore_rules.items():
|
|
245
|
+
category_header = f"# {category}"
|
|
246
|
+
if category_header not in should_contain_lines:
|
|
247
|
+
should_contain_lines.append(category_header)
|
|
244
248
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
editable_location = None
|
|
249
|
+
for rule in rules:
|
|
250
|
+
if rule not in should_contain_lines:
|
|
251
|
+
should_contain_lines.append(rule)
|
|
249
252
|
|
|
250
|
-
|
|
251
|
-
if line.startswith("Location:"):
|
|
252
|
-
location = line.split(":", 1)[1].strip()
|
|
253
|
-
elif line.startswith("Editable project location:"):
|
|
254
|
-
editable_location = line.split(":", 1)[1].strip()
|
|
253
|
+
return raw_value
|
|
255
254
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
255
|
+
def search_imports_in_codebase(
|
|
256
|
+
self, searched_package: PythonPackageWorkdir
|
|
257
|
+
) -> list[SearchResult]:
|
|
258
|
+
"""Find import statements that reference the given package.
|
|
259
259
|
|
|
260
|
-
|
|
260
|
+
Supports common Python forms:
|
|
261
|
+
- from <pkg>(.<sub>)* import ...
|
|
262
|
+
- import <pkg>(.<sub>)* [as alias]
|
|
261
263
|
|
|
262
|
-
|
|
264
|
+
Returns a list of SearchResult with file, line and column for each match.
|
|
265
|
+
"""
|
|
266
|
+
import re
|
|
263
267
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
268
|
+
pkg = searched_package.get_package_import_name()
|
|
269
|
+
pattern = (
|
|
270
|
+
rf"(?m)^\s*(?:"
|
|
271
|
+
rf"from\s+{re.escape(pkg)}(?:\.[\w\.]+)?\s+import\s+"
|
|
272
|
+
rf"|import\s+{re.escape(pkg)}(?:\.[\w\.]+)?(?:\s+as\s+\w+)?\b"
|
|
273
|
+
rf")"
|
|
274
|
+
)
|
|
275
|
+
return self.search_in_codebase(pattern, regex=True, flags=re.MULTILINE)
|
|
276
|
+
|
|
277
|
+
def search_in_codebase(
|
|
278
|
+
self, string: str, *, regex: bool = False, flags: int = 0
|
|
279
|
+
) -> list[SearchResult]:
|
|
280
|
+
from wexample_filestate.utils.search_result import SearchResult
|
|
281
|
+
from wexample_filestate_python.file.python_file import PythonFile
|
|
282
|
+
|
|
283
|
+
found = []
|
|
284
|
+
|
|
285
|
+
def _search(item: PythonFile) -> None:
|
|
286
|
+
found.extend(
|
|
287
|
+
SearchResult.create_for_all_matches(
|
|
288
|
+
string, item, regex=regex, flags=flags
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
self.for_each_child_of_type_recursive(callback=_search, class_type=PythonFile)
|
|
293
|
+
|
|
294
|
+
return found
|
|
267
295
|
|
|
268
296
|
def _collect_suite_dependencies(
|
|
269
297
|
self,
|
|
@@ -306,6 +334,88 @@ class PythonPackageWorkdir(PythonWorkdir):
|
|
|
306
334
|
|
|
307
335
|
return suite_deps_ordered
|
|
308
336
|
|
|
337
|
+
def _get_children_package_workdir_class(self) -> type[FrameworkPackageSuiteWorkdir]:
|
|
338
|
+
from wexample_wex_addon_dev_python.workdir.python_packages_suite_workdir import (
|
|
339
|
+
PythonPackagesSuiteWorkdir,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return PythonPackagesSuiteWorkdir
|
|
343
|
+
|
|
344
|
+
def _get_readme_content(self) -> ReadmeContentConfigValue | None:
|
|
345
|
+
from wexample_wex_addon_dev_python.config_value.python_package_readme_config_value import (
|
|
346
|
+
PythonPackageReadmeContentConfigValue,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return PythonPackageReadmeContentConfigValue(workdir=self)
|
|
350
|
+
|
|
351
|
+
def _is_package_installed_editable(
|
|
352
|
+
self,
|
|
353
|
+
app_path,
|
|
354
|
+
package_name: str,
|
|
355
|
+
package_path,
|
|
356
|
+
) -> bool:
|
|
357
|
+
"""Check if a package is already installed in editable mode at the correct path."""
|
|
358
|
+
import subprocess
|
|
359
|
+
|
|
360
|
+
try:
|
|
361
|
+
result = subprocess.run(
|
|
362
|
+
[".venv/bin/python", "-m", "pip", "show", package_name],
|
|
363
|
+
cwd=app_path,
|
|
364
|
+
capture_output=True,
|
|
365
|
+
text=True,
|
|
366
|
+
timeout=5,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if result.returncode != 0:
|
|
370
|
+
return False
|
|
371
|
+
|
|
372
|
+
# Parse pip show output
|
|
373
|
+
output_lines = result.stdout.strip().split("\n")
|
|
374
|
+
location = None
|
|
375
|
+
editable_location = None
|
|
376
|
+
|
|
377
|
+
for line in output_lines:
|
|
378
|
+
if line.startswith("Location:"):
|
|
379
|
+
location = line.split(":", 1)[1].strip()
|
|
380
|
+
elif line.startswith("Editable project location:"):
|
|
381
|
+
editable_location = line.split(":", 1)[1].strip()
|
|
382
|
+
|
|
383
|
+
# Check if installed in editable mode at the correct path
|
|
384
|
+
if editable_location:
|
|
385
|
+
from pathlib import Path
|
|
386
|
+
|
|
387
|
+
return Path(editable_location).resolve() == Path(package_path).resolve()
|
|
388
|
+
|
|
389
|
+
return False
|
|
390
|
+
|
|
391
|
+
except Exception:
|
|
392
|
+
# If any error occurs, assume not installed
|
|
393
|
+
return False
|
|
394
|
+
|
|
395
|
+
def _pdm_run_command(self, command: list[str]) -> None:
|
|
396
|
+
from wexample_helpers.helpers.shell import shell_run
|
|
397
|
+
|
|
398
|
+
# Install dev group
|
|
399
|
+
shell_run(
|
|
400
|
+
cmd=["pdm"] + command,
|
|
401
|
+
cwd=self.get_path(),
|
|
402
|
+
inherit_stdio=True,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
def _pdm_update_lock_if_needed(self) -> None:
|
|
406
|
+
try:
|
|
407
|
+
self._pdm_run_command(command=["lock", "--check"])
|
|
408
|
+
self.log("pdm.lock is up to date")
|
|
409
|
+
|
|
410
|
+
except Exception:
|
|
411
|
+
self.log("pdm.lock is out of date")
|
|
412
|
+
|
|
413
|
+
try:
|
|
414
|
+
self._pdm_run_command(command=["lock"])
|
|
415
|
+
self.success("pdm.lock updated")
|
|
416
|
+
except Exception:
|
|
417
|
+
self.failure("pdm.lock updated")
|
|
418
|
+
|
|
309
419
|
def _publish(self, force: bool = False) -> None:
|
|
310
420
|
from wexample_filestate_python.common.pipy_gateway import PipyGateway
|
|
311
421
|
from wexample_helpers.helpers.shell import shell_run
|
|
@@ -331,51 +441,3 @@ class PythonPackageWorkdir(PythonWorkdir):
|
|
|
331
441
|
publish_cmd += ["--password", password]
|
|
332
442
|
|
|
333
443
|
shell_run(publish_cmd, inherit_stdio=True, cwd=self.get_path())
|
|
334
|
-
|
|
335
|
-
def search_imports_in_codebase(
|
|
336
|
-
self, searched_package: PythonPackageWorkdir
|
|
337
|
-
) -> list[SearchResult]:
|
|
338
|
-
"""Find import statements that reference the given package.
|
|
339
|
-
|
|
340
|
-
Supports common Python forms:
|
|
341
|
-
- from <pkg>(.<sub>)* import ...
|
|
342
|
-
- import <pkg>(.<sub>)* [as alias]
|
|
343
|
-
|
|
344
|
-
Returns a list of SearchResult with file, line and column for each match.
|
|
345
|
-
"""
|
|
346
|
-
import re
|
|
347
|
-
|
|
348
|
-
pkg = searched_package.get_package_import_name()
|
|
349
|
-
pattern = (
|
|
350
|
-
rf"(?m)^\s*(?:"
|
|
351
|
-
rf"from\s+{re.escape(pkg)}(?:\.[\w\.]+)?\s+import\s+"
|
|
352
|
-
rf"|import\s+{re.escape(pkg)}(?:\.[\w\.]+)?(?:\s+as\s+\w+)?\b"
|
|
353
|
-
rf")"
|
|
354
|
-
)
|
|
355
|
-
return self.search_in_codebase(pattern, regex=True, flags=re.MULTILINE)
|
|
356
|
-
|
|
357
|
-
def search_in_codebase(
|
|
358
|
-
self, string: str, *, regex: bool = False, flags: int = 0
|
|
359
|
-
) -> list[SearchResult]:
|
|
360
|
-
from wexample_filestate.utils.search_result import SearchResult
|
|
361
|
-
from wexample_filestate_python.file.python_file import PythonFile
|
|
362
|
-
|
|
363
|
-
found = []
|
|
364
|
-
|
|
365
|
-
def _search(item: PythonFile) -> None:
|
|
366
|
-
found.extend(
|
|
367
|
-
SearchResult.create_for_all_matches(
|
|
368
|
-
string, item, regex=regex, flags=flags
|
|
369
|
-
)
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
self.for_each_child_of_type_recursive(callback=_search, class_type=PythonFile)
|
|
373
|
-
|
|
374
|
-
return found
|
|
375
|
-
|
|
376
|
-
def _get_readme_content(self) -> ReadmeContentConfigValue | None:
|
|
377
|
-
from wexample_wex_addon_dev_python.config_value.python_package_readme_config_value import (
|
|
378
|
-
PythonPackageReadmeContentConfigValue,
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
return PythonPackageReadmeContentConfigValue(workdir=self)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
4
5
|
from wexample_wex_addon_app.workdir.framework_packages_suite_workdir import (
|
|
5
6
|
FrameworkPackageSuiteWorkdir,
|
|
6
7
|
)
|
|
@@ -11,6 +12,7 @@ if TYPE_CHECKING:
|
|
|
11
12
|
from wexample_wex_addon_app.workdir.code_base_workdir import (
|
|
12
13
|
CodeBaseWorkdir,
|
|
13
14
|
)
|
|
15
|
+
|
|
14
16
|
from wexample_wex_addon_dev_python.workdir.python_package_workdir import (
|
|
15
17
|
PythonPackageWorkdir,
|
|
16
18
|
)
|
|
@@ -89,7 +91,10 @@ class PythonPackagesSuiteWorkdir(FrameworkPackageSuiteWorkdir):
|
|
|
89
91
|
return [by_name[n] for n in order]
|
|
90
92
|
|
|
91
93
|
def packages_validate_internal_dependencies_declarations(self) -> None:
|
|
92
|
-
from wexample_wex_addon_app.exception.dependency_violation_exception import
|
|
94
|
+
from wexample_wex_addon_app.exception.dependency_violation_exception import (
|
|
95
|
+
DependencyViolationException,
|
|
96
|
+
)
|
|
97
|
+
|
|
93
98
|
dependencies_map = self.build_dependencies_map()
|
|
94
99
|
|
|
95
100
|
self.io.log("Checking packages dependencies consistency...")
|
|
@@ -4,14 +4,30 @@ from pathlib import Path
|
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from tomlkit import TOMLDocument
|
|
7
|
-
|
|
7
|
+
from wexample_app.item.file.iml_file import ImlFile
|
|
8
|
+
from wexample_event.dataclass.event import Event
|
|
9
|
+
from wexample_event.dataclass.listener_record import EventCallback
|
|
10
|
+
from wexample_filestate.const.types_state_items import TargetFileOrDirectoryType
|
|
8
11
|
from wexample_filestate.item.file.json_file import JsonFile
|
|
9
|
-
from
|
|
12
|
+
from wexample_filestate.operation.abstract_operation import AbstractOperation
|
|
13
|
+
from wexample_filestate.operation.file_rename_operation import FileRenameOperation
|
|
14
|
+
from wexample_filestate_python.const.path import PATH_DIR_SRC, PATH_DIR_TESTS
|
|
15
|
+
from wexample_filestate_python.const.python_file import (
|
|
16
|
+
PYTHON_FILE_EXTENSION,
|
|
17
|
+
PYTHON_FILE_PYTEST_COVERAGE_JSON,
|
|
18
|
+
)
|
|
10
19
|
from wexample_wex_addon_app.helpers.python import python_install_environment
|
|
20
|
+
from wexample_wex_addon_app.item.file.python_app_iml_file import PythonAppImlFile
|
|
11
21
|
from wexample_wex_addon_app.workdir.code_base_workdir import (
|
|
12
22
|
CodeBaseWorkdir,
|
|
13
23
|
)
|
|
14
24
|
|
|
25
|
+
from wexample_wex_addon_dev_python.const.python import (
|
|
26
|
+
PYTHON_PYTEST_COV_FORMAT_HTML,
|
|
27
|
+
PYTHON_PYTEST_COV_FORMAT_JSON,
|
|
28
|
+
PYTHON_PYTEST_COV_REPORT_DIR,
|
|
29
|
+
)
|
|
30
|
+
|
|
15
31
|
if TYPE_CHECKING:
|
|
16
32
|
from wexample_config.const.types import DictConfig
|
|
17
33
|
from wexample_config.options_provider.abstract_options_provider import (
|
|
@@ -24,57 +40,13 @@ if TYPE_CHECKING:
|
|
|
24
40
|
ChildrenFileFactoryOption,
|
|
25
41
|
)
|
|
26
42
|
from wexample_helpers.const.types import StructuredData
|
|
43
|
+
|
|
27
44
|
from wexample_wex_addon_dev_python.file.python_package_toml_file import (
|
|
28
45
|
PythonPackageTomlFile,
|
|
29
46
|
)
|
|
30
47
|
|
|
31
48
|
|
|
32
49
|
class PythonWorkdir(CodeBaseWorkdir):
|
|
33
|
-
def get_venv_path(self) -> Path:
|
|
34
|
-
return self.get_path() / ".venv"
|
|
35
|
-
|
|
36
|
-
def get_venv_bin_path(self) -> Path:
|
|
37
|
-
return self.get_venv_path() / "bin"
|
|
38
|
-
|
|
39
|
-
def get_python_path(self) -> Path:
|
|
40
|
-
return self.get_venv_bin_path() / "python"
|
|
41
|
-
|
|
42
|
-
def get_python_exec_module_command(self, module_name: str) -> list[str]:
|
|
43
|
-
return [self.get_python_path(), "-m", module_name]
|
|
44
|
-
|
|
45
|
-
def test_run(self) -> None:
|
|
46
|
-
self.shell_run_for_app(cmd=self.test_get_command())
|
|
47
|
-
|
|
48
|
-
json_file = JsonFile.create_from_path(
|
|
49
|
-
path=self.get_path() / PYTHON_FILE_PYTEST_COVERAGE_JSON
|
|
50
|
-
)
|
|
51
|
-
totals = json_file.read_config().search("totals", default={}).get_dict()
|
|
52
|
-
|
|
53
|
-
config_file = self.get_config_file()
|
|
54
|
-
config = config_file.read_config()
|
|
55
|
-
config.set_by_path(
|
|
56
|
-
"test.coverage.last_report",
|
|
57
|
-
{
|
|
58
|
-
"covered": totals.get("covered_lines", 0),
|
|
59
|
-
"excluded": totals.get("excluded_lines", 0),
|
|
60
|
-
"missing": totals.get("missing_lines", 0),
|
|
61
|
-
"percent": totals.get("percent_covered", 0),
|
|
62
|
-
"total": totals.get("num_statements", 0),
|
|
63
|
-
},
|
|
64
|
-
)
|
|
65
|
-
config_file.write_config()
|
|
66
|
-
|
|
67
|
-
def test_get_command(self) -> list[str]:
|
|
68
|
-
cmd = self.get_python_exec_module_command("pytest")
|
|
69
|
-
cmd.extend(
|
|
70
|
-
[
|
|
71
|
-
"--cov",
|
|
72
|
-
"--cov-report=json",
|
|
73
|
-
]
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
return cmd
|
|
77
|
-
|
|
78
50
|
def app_install(self, env: str | None = None, force: bool = False) -> bool:
|
|
79
51
|
# Use standard PDM install
|
|
80
52
|
return python_install_environment(path=self.get_path())
|
|
@@ -87,6 +59,9 @@ class PythonWorkdir(CodeBaseWorkdir):
|
|
|
87
59
|
dependencies.append(Requirement(dependency).name)
|
|
88
60
|
return dependencies
|
|
89
61
|
|
|
62
|
+
def get_main_code_file_extension(self) -> str:
|
|
63
|
+
return PYTHON_FILE_EXTENSION
|
|
64
|
+
|
|
90
65
|
def get_options_providers(self) -> list[type[AbstractOptionsProvider]]:
|
|
91
66
|
from wexample_filestate_python.options_provider.python_options_provider import (
|
|
92
67
|
PythonOptionsProvider,
|
|
@@ -111,6 +86,61 @@ class PythonWorkdir(CodeBaseWorkdir):
|
|
|
111
86
|
|
|
112
87
|
return string_to_kebab_case(self.get_package_import_name())
|
|
113
88
|
|
|
89
|
+
def get_project_config(self, reload: bool = True) -> TOMLDocument:
|
|
90
|
+
"""
|
|
91
|
+
Fetch the data from the pyproject.toml file.
|
|
92
|
+
"""
|
|
93
|
+
return self.get_project_config_file(reload=reload).read_parsed()
|
|
94
|
+
|
|
95
|
+
def get_project_config_file(self, reload: bool = True) -> PythonPackageTomlFile:
|
|
96
|
+
from wexample_wex_addon_dev_python.file.python_package_toml_file import (
|
|
97
|
+
PythonPackageTomlFile,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
config_file = self.find_by_name("pyproject.toml")
|
|
101
|
+
assert isinstance(config_file, PythonPackageTomlFile)
|
|
102
|
+
# Read once to populate content with file source.
|
|
103
|
+
config_file.read_text(reload=reload)
|
|
104
|
+
return config_file
|
|
105
|
+
|
|
106
|
+
def get_python_exec_module_command(self, module_name: str) -> list[str]:
|
|
107
|
+
return [self.get_python_path(), "-m", module_name]
|
|
108
|
+
|
|
109
|
+
def get_python_path(self) -> Path:
|
|
110
|
+
return self.get_venv_bin_path() / "python"
|
|
111
|
+
|
|
112
|
+
def get_venv_bin_path(self) -> Path:
|
|
113
|
+
return self.get_venv_path() / "bin"
|
|
114
|
+
|
|
115
|
+
def get_venv_path(self) -> Path:
|
|
116
|
+
return self.get_path() / ".venv"
|
|
117
|
+
|
|
118
|
+
def has_coverage_changes_since_last_report(self) -> bool:
|
|
119
|
+
"""Return True if coverage has changed since last saved report."""
|
|
120
|
+
last_report = (
|
|
121
|
+
self.app_workdir.get_config()
|
|
122
|
+
.search("test.coverage.last_report")
|
|
123
|
+
.get_dict_or_default()
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if not last_report:
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
current_coverage = self._run_coverage()
|
|
130
|
+
|
|
131
|
+
return current_coverage != last_report.get("percent")
|
|
132
|
+
|
|
133
|
+
def operation_add_event_listener(
|
|
134
|
+
self,
|
|
135
|
+
operation: AbstractOperation | type[AbstractOperation],
|
|
136
|
+
callback: EventCallback,
|
|
137
|
+
suffix: str | None = None,
|
|
138
|
+
**kwargs,
|
|
139
|
+
) -> None:
|
|
140
|
+
self.add_event_listener(
|
|
141
|
+
name=operation.get_event_name(suffix=suffix), callback=callback, **kwargs
|
|
142
|
+
)
|
|
143
|
+
|
|
114
144
|
def prepare_value(self, raw_value: DictConfig | None = None) -> DictConfig:
|
|
115
145
|
from wexample_config.config_value.callback_render_config_value import (
|
|
116
146
|
CallbackRenderConfigValue,
|
|
@@ -120,6 +150,7 @@ class PythonWorkdir(CodeBaseWorkdir):
|
|
|
120
150
|
ChildrenFilterOption,
|
|
121
151
|
)
|
|
122
152
|
from wexample_helpers.helpers.array import array_dict_get_by
|
|
153
|
+
|
|
123
154
|
from wexample_wex_addon_dev_python.file.python_package_toml_file import (
|
|
124
155
|
PythonPackageTomlFile,
|
|
125
156
|
)
|
|
@@ -231,6 +262,100 @@ class PythonWorkdir(CodeBaseWorkdir):
|
|
|
231
262
|
|
|
232
263
|
return raw_value
|
|
233
264
|
|
|
265
|
+
def save_dependency(self, package_name: str, version: str) -> bool:
|
|
266
|
+
"""Add or update a dependency with strict version."""
|
|
267
|
+
config = self.get_project_config_file()
|
|
268
|
+
updated = config.add_dependency(f"{package_name}=={version}")
|
|
269
|
+
|
|
270
|
+
if updated:
|
|
271
|
+
config.write_parsed()
|
|
272
|
+
|
|
273
|
+
return updated
|
|
274
|
+
|
|
275
|
+
def save_project_config_file(self, config: StructuredData) -> None:
|
|
276
|
+
"""Save the project configuration to pyproject.toml."""
|
|
277
|
+
config_file = self.get_project_config_file()
|
|
278
|
+
config_file.write(config)
|
|
279
|
+
|
|
280
|
+
def test_get_command(
|
|
281
|
+
self, format: str = PYTHON_PYTEST_COV_FORMAT_JSON
|
|
282
|
+
) -> list[str]:
|
|
283
|
+
cmd = self.get_python_exec_module_command("pytest")
|
|
284
|
+
cmd.extend(
|
|
285
|
+
[
|
|
286
|
+
"--cov",
|
|
287
|
+
f"--cov-report={format}",
|
|
288
|
+
]
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
return cmd
|
|
292
|
+
|
|
293
|
+
def test_run(self, format: str = PYTHON_PYTEST_COV_FORMAT_JSON) -> None:
|
|
294
|
+
self.shell_run_for_app(cmd=self.test_get_command(format=format))
|
|
295
|
+
|
|
296
|
+
json_file = JsonFile.create_from_path(
|
|
297
|
+
path=self.get_path() / PYTHON_FILE_PYTEST_COVERAGE_JSON
|
|
298
|
+
)
|
|
299
|
+
totals = json_file.read_config().search("totals", default={}).get_dict()
|
|
300
|
+
|
|
301
|
+
config_file = self.get_config_file()
|
|
302
|
+
config = config_file.read_config()
|
|
303
|
+
config.set_by_path(
|
|
304
|
+
"test.coverage.last_report",
|
|
305
|
+
{
|
|
306
|
+
"covered": totals.get("covered_lines", 0),
|
|
307
|
+
"excluded": totals.get("excluded_lines", 0),
|
|
308
|
+
"missing": totals.get("missing_lines", 0),
|
|
309
|
+
"percent": totals.get("percent_covered", 0),
|
|
310
|
+
"total": totals.get("num_statements", 0),
|
|
311
|
+
},
|
|
312
|
+
)
|
|
313
|
+
config_file.write_config()
|
|
314
|
+
|
|
315
|
+
if format == PYTHON_PYTEST_COV_FORMAT_HTML:
|
|
316
|
+
report_path = self.get_path() / PYTHON_PYTEST_COV_REPORT_DIR / "index.html"
|
|
317
|
+
if report_path.exists():
|
|
318
|
+
self.info(f"Report: @path{{{report_path}}}")
|
|
319
|
+
|
|
320
|
+
def update_dependencies(self, dependencies_map: dict[str, str]) -> None:
|
|
321
|
+
"""Update dependencies versions based on the provided map.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
dependencies_map: Dictionary mapping package names to their new versions.
|
|
325
|
+
Example: {"wexample-helpers": "0.2.3", "attrs": "23.1.0"}
|
|
326
|
+
"""
|
|
327
|
+
from packaging.requirements import Requirement
|
|
328
|
+
from packaging.utils import canonicalize_name
|
|
329
|
+
|
|
330
|
+
config_file = self.get_project_config_file()
|
|
331
|
+
|
|
332
|
+
# Canonicalize the keys in dependencies_map for consistent matching
|
|
333
|
+
canonical_map = {
|
|
334
|
+
canonicalize_name(name): version
|
|
335
|
+
for name, version in dependencies_map.items()
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
# Get current dependencies
|
|
339
|
+
current_deps = config_file.list_dependencies()
|
|
340
|
+
|
|
341
|
+
# Update each dependency if it's in the map
|
|
342
|
+
for dep_spec in current_deps:
|
|
343
|
+
try:
|
|
344
|
+
req = Requirement(dep_spec)
|
|
345
|
+
canonical_name = canonicalize_name(req.name)
|
|
346
|
+
|
|
347
|
+
# If this dependency is in our update map, update it
|
|
348
|
+
if canonical_name in canonical_map:
|
|
349
|
+
new_version = canonical_map[canonical_name]
|
|
350
|
+
# Use add_dependency which handles removal of old version
|
|
351
|
+
config_file.add_dependency(f"{req.name}=={new_version}")
|
|
352
|
+
except Exception:
|
|
353
|
+
# Skip unparsable dependencies
|
|
354
|
+
continue
|
|
355
|
+
|
|
356
|
+
# Save the updated config
|
|
357
|
+
config_file.write_parsed()
|
|
358
|
+
|
|
234
359
|
def _create_init_children_factory(self) -> ChildrenFileFactoryOption:
|
|
235
360
|
from wexample_filestate.const.disk import DiskItemType
|
|
236
361
|
from wexample_filestate.const.globals import NAME_PATTERN_NO_LEADING_DOT
|
|
@@ -279,7 +404,67 @@ class PythonWorkdir(CodeBaseWorkdir):
|
|
|
279
404
|
ChildrenFilterOption,
|
|
280
405
|
)
|
|
281
406
|
from wexample_filestate_python.file.python_file import PythonFile
|
|
282
|
-
from wexample_filestate_python.option.
|
|
407
|
+
from wexample_filestate_python.option.python.add_future_annotations_option import (
|
|
408
|
+
AddFutureAnnotationsOption,
|
|
409
|
+
)
|
|
410
|
+
from wexample_filestate_python.option.python.add_return_types_option import (
|
|
411
|
+
AddReturnTypesOption,
|
|
412
|
+
)
|
|
413
|
+
from wexample_filestate_python.option.python.fix_attrs_option import (
|
|
414
|
+
FixAttrsOption,
|
|
415
|
+
)
|
|
416
|
+
from wexample_filestate_python.option.python.fix_blank_lines_option import (
|
|
417
|
+
FixBlankLinesOption,
|
|
418
|
+
)
|
|
419
|
+
from wexample_filestate_python.option.python.format_option import FormatOption
|
|
420
|
+
from wexample_filestate_python.option.python.fstringify_option import (
|
|
421
|
+
FstringifyOption,
|
|
422
|
+
)
|
|
423
|
+
from wexample_filestate_python.option.python.modernize_typing_option import (
|
|
424
|
+
ModernizeTypingOption,
|
|
425
|
+
)
|
|
426
|
+
from wexample_filestate_python.option.python.order_class_attributes_option import (
|
|
427
|
+
OrderClassAttributesOption,
|
|
428
|
+
)
|
|
429
|
+
from wexample_filestate_python.option.python.order_class_docstring_option import (
|
|
430
|
+
OrderClassDocstringOption,
|
|
431
|
+
)
|
|
432
|
+
from wexample_filestate_python.option.python.order_class_methods_option import (
|
|
433
|
+
OrderClassMethodsOption,
|
|
434
|
+
)
|
|
435
|
+
from wexample_filestate_python.option.python.order_constants_option import (
|
|
436
|
+
OrderConstantsOption,
|
|
437
|
+
)
|
|
438
|
+
from wexample_filestate_python.option.python.order_iterable_items_option import (
|
|
439
|
+
OrderIterableItemsOption,
|
|
440
|
+
)
|
|
441
|
+
from wexample_filestate_python.option.python.order_main_guard_option import (
|
|
442
|
+
OrderMainGuardOption,
|
|
443
|
+
)
|
|
444
|
+
from wexample_filestate_python.option.python.order_module_docstring_option import (
|
|
445
|
+
OrderModuleDocstringOption,
|
|
446
|
+
)
|
|
447
|
+
from wexample_filestate_python.option.python.order_module_functions_option import (
|
|
448
|
+
OrderModuleFunctionsOption,
|
|
449
|
+
)
|
|
450
|
+
from wexample_filestate_python.option.python.order_module_metadata_option import (
|
|
451
|
+
OrderModuleMetadataOption,
|
|
452
|
+
)
|
|
453
|
+
from wexample_filestate_python.option.python.order_type_checking_block_option import (
|
|
454
|
+
OrderTypeCheckingBlockOption,
|
|
455
|
+
)
|
|
456
|
+
from wexample_filestate_python.option.python.relocate_imports_option import (
|
|
457
|
+
RelocateImportsOption,
|
|
458
|
+
)
|
|
459
|
+
from wexample_filestate_python.option.python.remove_unused_option import (
|
|
460
|
+
RemoveUnusedOption,
|
|
461
|
+
)
|
|
462
|
+
from wexample_filestate_python.option.python.sort_imports_option import (
|
|
463
|
+
SortImportsOption,
|
|
464
|
+
)
|
|
465
|
+
from wexample_filestate_python.option.python.unquote_annotations_option import (
|
|
466
|
+
UnquoteAnnotationsOption,
|
|
467
|
+
)
|
|
283
468
|
|
|
284
469
|
return ChildrenFilterOption(
|
|
285
470
|
pattern={
|
|
@@ -287,96 +472,57 @@ class PythonWorkdir(CodeBaseWorkdir):
|
|
|
287
472
|
"type": DiskItemType.FILE,
|
|
288
473
|
"python": {
|
|
289
474
|
# Configured for python >= 3.12
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
475
|
+
AddFutureAnnotationsOption.get_name(): True,
|
|
476
|
+
RelocateImportsOption.get_name(): True,
|
|
477
|
+
RemoveUnusedOption.get_name(): True,
|
|
478
|
+
SortImportsOption.get_name(): True,
|
|
479
|
+
ModernizeTypingOption.get_name(): True,
|
|
480
|
+
FstringifyOption.get_name(): True,
|
|
481
|
+
AddReturnTypesOption.get_name(): True,
|
|
482
|
+
UnquoteAnnotationsOption.get_name(): True,
|
|
483
|
+
FixAttrsOption.get_name(): True,
|
|
484
|
+
OrderTypeCheckingBlockOption.get_name(): True,
|
|
485
|
+
OrderModuleDocstringOption.get_name(): True,
|
|
486
|
+
OrderModuleMetadataOption.get_name(): True,
|
|
487
|
+
OrderConstantsOption.get_name(): True,
|
|
488
|
+
OrderIterableItemsOption.get_name(): True,
|
|
489
|
+
OrderModuleFunctionsOption.get_name(): True,
|
|
490
|
+
OrderMainGuardOption.get_name(): True,
|
|
491
|
+
OrderClassDocstringOption.get_name(): True,
|
|
492
|
+
OrderClassAttributesOption.get_name(): True,
|
|
493
|
+
OrderClassMethodsOption.get_name(): True,
|
|
494
|
+
FixBlankLinesOption.get_name(): True,
|
|
495
|
+
FormatOption.get_name(): True,
|
|
311
496
|
},
|
|
312
497
|
},
|
|
313
498
|
name_pattern=r"^.*\.py$",
|
|
314
499
|
recursive=True,
|
|
315
500
|
)
|
|
316
501
|
|
|
317
|
-
def
|
|
318
|
-
|
|
319
|
-
PythonPackageTomlFile,
|
|
320
|
-
)
|
|
502
|
+
def _get_iml_file_class(self) -> type[ImlFile]:
|
|
503
|
+
return PythonAppImlFile
|
|
321
504
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
# Read once to populate content with file source.
|
|
325
|
-
config_file.read_text(reload=reload)
|
|
326
|
-
return config_file
|
|
505
|
+
def _get_source_code_directories(self) -> [TargetFileOrDirectoryType]:
|
|
506
|
+
src = self.find_by_name(PATH_DIR_SRC)
|
|
327
507
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
Fetch the data from the pyproject.toml file.
|
|
331
|
-
"""
|
|
332
|
-
return self.get_project_config_file(reload=reload).read_parsed()
|
|
508
|
+
if src:
|
|
509
|
+
return [src]
|
|
333
510
|
|
|
334
|
-
|
|
335
|
-
"""Add or update a dependency with strict version."""
|
|
336
|
-
config = self.get_project_config_file()
|
|
337
|
-
config.add_dependency(f"{package_name}=={version}")
|
|
338
|
-
config.write_parsed()
|
|
511
|
+
return []
|
|
339
512
|
|
|
340
|
-
def
|
|
341
|
-
|
|
342
|
-
config_file = self.get_project_config_file()
|
|
343
|
-
config_file.write(config)
|
|
513
|
+
def _get_test_code_directories(self) -> [TargetFileOrDirectoryType]:
|
|
514
|
+
tests = self.find_by_name(PATH_DIR_TESTS)
|
|
344
515
|
|
|
345
|
-
|
|
346
|
-
|
|
516
|
+
if tests:
|
|
517
|
+
return [tests]
|
|
347
518
|
|
|
348
|
-
|
|
349
|
-
dependencies_map: Dictionary mapping package names to their new versions.
|
|
350
|
-
Example: {"wexample-helpers": "0.2.3", "attrs": "23.1.0"}
|
|
351
|
-
"""
|
|
352
|
-
from packaging.requirements import Requirement
|
|
353
|
-
from packaging.utils import canonicalize_name
|
|
354
|
-
|
|
355
|
-
config_file = self.get_project_config_file()
|
|
519
|
+
return []
|
|
356
520
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
# Get current dependencies
|
|
364
|
-
current_deps = config_file.list_dependencies()
|
|
365
|
-
|
|
366
|
-
# Update each dependency if it's in the map
|
|
367
|
-
for dep_spec in current_deps:
|
|
368
|
-
try:
|
|
369
|
-
req = Requirement(dep_spec)
|
|
370
|
-
canonical_name = canonicalize_name(req.name)
|
|
371
|
-
|
|
372
|
-
# If this dependency is in our update map, update it
|
|
373
|
-
if canonical_name in canonical_map:
|
|
374
|
-
new_version = canonical_map[canonical_name]
|
|
375
|
-
# Use add_dependency which handles removal of old version
|
|
376
|
-
config_file.add_dependency(f"{req.name}=={new_version}")
|
|
377
|
-
except Exception:
|
|
378
|
-
# Skip unparsable dependencies
|
|
379
|
-
continue
|
|
521
|
+
def _init_listeners(self) -> None:
|
|
522
|
+
"""Add event listeners"""
|
|
523
|
+
self.operation_add_event_listener(
|
|
524
|
+
operation=FileRenameOperation, suffix="post", callback=self._on_test_event
|
|
525
|
+
)
|
|
380
526
|
|
|
381
|
-
|
|
382
|
-
|
|
527
|
+
def _on_test_event(self, event: Event) -> None:
|
|
528
|
+
self.success("A python file has been renamed")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wexample-wex-addon-dev-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.61
|
|
4
4
|
Summary: Python dev addon for wex
|
|
5
5
|
Author-Email: weeger <contact@wexample.com>
|
|
6
6
|
License: MIT
|
|
@@ -15,9 +15,9 @@ Requires-Dist: jinja2
|
|
|
15
15
|
Requires-Dist: networkx
|
|
16
16
|
Requires-Dist: pylint
|
|
17
17
|
Requires-Dist: pyright
|
|
18
|
-
Requires-Dist: wexample-filestate-python==0.0.
|
|
19
|
-
Requires-Dist: wexample-wex-addon-app==0.0.
|
|
20
|
-
Requires-Dist: wexample-wex-core==6.0.
|
|
18
|
+
Requires-Dist: wexample-filestate-python==0.0.56
|
|
19
|
+
Requires-Dist: wexample-wex-addon-app==0.0.53
|
|
20
|
+
Requires-Dist: wexample-wex-core==6.0.65
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: pytest; extra == "dev"
|
|
23
23
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -25,7 +25,7 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
|
|
26
26
|
# wexample-wex-addon-dev-python
|
|
27
27
|
|
|
28
|
-
Version: 0.0.
|
|
28
|
+
Version: 0.0.61
|
|
29
29
|
|
|
30
30
|
Python dev addon for wex
|
|
31
31
|
|
|
@@ -44,7 +44,7 @@ First, install the required testing dependencies:
|
|
|
44
44
|
|
|
45
45
|
Run all tests with coverage:
|
|
46
46
|
```bash
|
|
47
|
-
.venv/bin/python -m pytest --cov
|
|
47
|
+
.venv/bin/python -m pytest --cov --cov-report=html
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
### Common Commands
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
wexample_wex_addon_dev_python-0.0.
|
|
2
|
-
wexample_wex_addon_dev_python-0.0.
|
|
3
|
-
wexample_wex_addon_dev_python-0.0.
|
|
1
|
+
wexample_wex_addon_dev_python-0.0.61.dist-info/METADATA,sha256=yqx7BuSnRvvBEUgCXySFgzwZRJz9W_9jjXPa_E3EOF8,6559
|
|
2
|
+
wexample_wex_addon_dev_python-0.0.61.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
|
|
3
|
+
wexample_wex_addon_dev_python-0.0.61.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
|
4
4
|
wexample_wex_addon_dev_python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
wexample_wex_addon_dev_python/__pycache__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
wexample_wex_addon_dev_python/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -25,9 +25,10 @@ wexample_wex_addon_dev_python/config_value/python_package_readme_config_value.py
|
|
|
25
25
|
wexample_wex_addon_dev_python/const/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
26
|
wexample_wex_addon_dev_python/const/__pycache__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
wexample_wex_addon_dev_python/const/package.py,sha256=oRCPhaazJp5TujxF-35rrIYA4FJsqMqCns8lOFKOLeA,451
|
|
28
|
+
wexample_wex_addon_dev_python/const/python.py,sha256=jxdPt5CnD0dcp4SmobEc_c7XcCkPFfX_lk3SVHsiVpM,203
|
|
28
29
|
wexample_wex_addon_dev_python/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
30
|
wexample_wex_addon_dev_python/file/__pycache__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
-
wexample_wex_addon_dev_python/file/python_package_toml_file.py,sha256=
|
|
31
|
+
wexample_wex_addon_dev_python/file/python_package_toml_file.py,sha256=ShJrZiiJj8-HrsD92W_1WozE02fi6Avll8VuYPQ25r0,16357
|
|
31
32
|
wexample_wex_addon_dev_python/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
33
|
wexample_wex_addon_dev_python/middleware/__pycache__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
34
|
wexample_wex_addon_dev_python/middleware/each_python_file_middleware.py,sha256=UzNEpedCYf31aNONFl0SuSJnoXRzhJhgEiTCaPark6E,2311
|
|
@@ -35,11 +36,11 @@ wexample_wex_addon_dev_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
35
36
|
wexample_wex_addon_dev_python/python_addon_manager.py,sha256=Mmr9F5lOS2gbb8JTB4-vTri4Qn5Cd2Jukjk7uiTr1Hs,582
|
|
36
37
|
wexample_wex_addon_dev_python/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
38
|
wexample_wex_addon_dev_python/resources/readme_templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
wexample_wex_addon_dev_python/resources/readme_templates/tests.md.j2,sha256=
|
|
39
|
+
wexample_wex_addon_dev_python/resources/readme_templates/tests.md.j2,sha256=tKLcDwx7jhQkryXIxWr12AK-hKEaP6Rusu2MrluiABs,1289
|
|
39
40
|
wexample_wex_addon_dev_python/resources/readme_templates/title.md.j2,sha256=U-q_U_WhTTwz3enrht3UTfQ9fwioaKUuJqwYyhBtCiA,64
|
|
40
41
|
wexample_wex_addon_dev_python/workdir/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
42
|
wexample_wex_addon_dev_python/workdir/__pycache__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
-
wexample_wex_addon_dev_python/workdir/python_package_workdir.py,sha256=
|
|
43
|
-
wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py,sha256=
|
|
44
|
-
wexample_wex_addon_dev_python/workdir/python_workdir.py,sha256=
|
|
45
|
-
wexample_wex_addon_dev_python-0.0.
|
|
43
|
+
wexample_wex_addon_dev_python/workdir/python_package_workdir.py,sha256=v-6B-rz1WBO94qC-4KzIVhkIBnabmAyV76du_Od2JGU,16398
|
|
44
|
+
wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py,sha256=41bzNISqVKHirOSVkwBD9wuZokrMDrtWKUjlMRT_GyU,6664
|
|
45
|
+
wexample_wex_addon_dev_python/workdir/python_workdir.py,sha256=RE3neOVS8Nju9hTuTjalqe25Q4mZ4Uhx6x2y3XRv18s,19893
|
|
46
|
+
wexample_wex_addon_dev_python-0.0.61.dist-info/RECORD,,
|
|
File without changes
|