wexample-wex-addon-dev-python 11.2.0__tar.gz → 12.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/PKG-INFO +6 -6
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/README.md +3 -3
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/pyproject.toml +3 -3
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/code/rename.py +52 -16
- wexample_wex_addon_dev_python-12.0.0/src/wexample_wex_addon_dev_python/refactor/applier.py +46 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/refactor/python_package_rename_handler.py +103 -62
- wexample_wex_addon_dev_python-12.0.0/src/wexample_wex_addon_dev_python/refactor/rename_plan.py +43 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/selection/python_code_performance_selection.py +39 -21
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/code/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/code/check/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/code/check/mypy.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/code/check/pylint.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/code/check/pyright.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/code/check.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/code/format/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/code/format/black.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/code/format/isort.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/code/format.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/examples/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/examples/utils/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/examples/utils/some_example_type.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/examples/validate.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/commands/release/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/common/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/common/pypi_registry_gateway.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/config_value/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/config_value/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/config_value/python_package_readme_config_value.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/const/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/const/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/const/package.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/const/python.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/const/tags.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/file/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/file/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/file/python_app_iml_file.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/file/python_pyproject_toml_file.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/helpers/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/helpers/pdm.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/middleware/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/middleware/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/middleware/each_python_file_middleware.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/py.typed +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/python_addon_manager.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/refactor/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/refactor/symbol_kind.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/resources/.wex.yml +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/resources/docker/Dockerfile.python-profiling +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/resources/package_publish_gitlab.yml +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/resources/readme_templates/tests.md.j2 +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/selection/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/selection/abstract_python_code_selection.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/app_service.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/commands/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/commands/service/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/commands/service/install.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/commands/service/setup.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/docker/.wex.yml +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/docker/docker-compose.yml +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/samples/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/samples/docker/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/samples/docker/docker-compose.yml +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/samples/docker/entrypoint.sh +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/samples/docker/images/Dockerfile.base +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/samples/docker/images/Dockerfile.develop +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/samples/docker/images/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/services/python/service.yml +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/workdir/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/workdir/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/workdir/mixin/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/workdir/mixin/with_profiling_python_workdir_mixin.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/workdir/python_package_workdir.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/src/wexample_wex_addon_dev_python/workdir/python_workdir.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/tests/helpers/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/tests/helpers/test_pdm.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/tests/unit/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/tests/unit/helpers/__init__.py +0 -0
- {wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/tests/unit/helpers/test_pdm.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wexample-wex-addon-dev-python
|
|
3
|
-
Version:
|
|
3
|
+
Version: 12.0.0
|
|
4
4
|
Summary: Python dev addon for wex
|
|
5
5
|
Author-Email: weeger <contact@wexample.com>
|
|
6
6
|
License: MIT
|
|
@@ -17,8 +17,8 @@ Requires-Dist: pylint
|
|
|
17
17
|
Requires-Dist: pyright
|
|
18
18
|
Requires-Dist: wexample-api>=6.6.0
|
|
19
19
|
Requires-Dist: wexample-filestate-python>=8.2.0
|
|
20
|
-
Requires-Dist: wexample-wex-addon-ai>=11.
|
|
21
|
-
Requires-Dist: wexample-wex-addon-app>=26.
|
|
20
|
+
Requires-Dist: wexample-wex-addon-ai>=11.3.0
|
|
21
|
+
Requires-Dist: wexample-wex-addon-app>=26.1.0
|
|
22
22
|
Provides-Extra: dev
|
|
23
23
|
Requires-Dist: pytest; extra == "dev"
|
|
24
24
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -26,7 +26,7 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
|
|
27
27
|
# wex_addon_dev_python
|
|
28
28
|
|
|
29
|
-
Version:
|
|
29
|
+
Version: 12.0.0
|
|
30
30
|
|
|
31
31
|
Python dev addon for wex
|
|
32
32
|
|
|
@@ -114,8 +114,8 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
|
|
|
114
114
|
- pyright:
|
|
115
115
|
- wexample-api: >=6.6.0
|
|
116
116
|
- wexample-filestate-python: >=8.2.0
|
|
117
|
-
- wexample-wex-addon-ai: >=11.
|
|
118
|
-
- wexample-wex-addon-app: >=26.
|
|
117
|
+
- wexample-wex-addon-ai: >=11.3.0
|
|
118
|
+
- wexample-wex-addon-app: >=26.1.0
|
|
119
119
|
|
|
120
120
|
## Versioning & Compatibility Policy
|
|
121
121
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# wex_addon_dev_python
|
|
2
2
|
|
|
3
|
-
Version:
|
|
3
|
+
Version: 12.0.0
|
|
4
4
|
|
|
5
5
|
Python dev addon for wex
|
|
6
6
|
|
|
@@ -88,8 +88,8 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
|
|
|
88
88
|
- pyright:
|
|
89
89
|
- wexample-api: >=6.6.0
|
|
90
90
|
- wexample-filestate-python: >=8.2.0
|
|
91
|
-
- wexample-wex-addon-ai: >=11.
|
|
92
|
-
- wexample-wex-addon-app: >=26.
|
|
91
|
+
- wexample-wex-addon-ai: >=11.3.0
|
|
92
|
+
- wexample-wex-addon-app: >=26.1.0
|
|
93
93
|
|
|
94
94
|
## Versioning & Compatibility Policy
|
|
95
95
|
|
{wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/pyproject.toml
RENAMED
|
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "wexample-wex-addon-dev-python"
|
|
9
|
-
version = "
|
|
9
|
+
version = "12.0.0"
|
|
10
10
|
description = "Python dev addon for wex"
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "weeger", email = "contact@wexample.com" },
|
|
@@ -26,8 +26,8 @@ dependencies = [
|
|
|
26
26
|
"pyright",
|
|
27
27
|
"wexample-api>=6.6.0",
|
|
28
28
|
"wexample-filestate-python>=8.2.0",
|
|
29
|
-
"wexample-wex-addon-ai>=11.
|
|
30
|
-
"wexample-wex-addon-app>=26.
|
|
29
|
+
"wexample-wex-addon-ai>=11.3.0",
|
|
30
|
+
"wexample-wex-addon-app>=26.1.0",
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
[project.readme]
|
|
@@ -94,26 +94,24 @@ def python__code__rename(
|
|
|
94
94
|
),
|
|
95
95
|
)
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
scope_roots, suite_info = _resolve_scope_roots(context.kernel, app_workdir)
|
|
98
|
+
|
|
98
99
|
handler = PythonPackageRenameHandler(
|
|
99
|
-
|
|
100
|
+
scope_roots=scope_roots,
|
|
100
101
|
source=source,
|
|
101
102
|
target=target,
|
|
102
103
|
dry_run=dry_run,
|
|
103
104
|
)
|
|
104
105
|
try:
|
|
105
106
|
report = handler.run()
|
|
106
|
-
except FileNotFoundError:
|
|
107
|
-
return FailureResponse(
|
|
108
|
-
kernel=context.kernel,
|
|
109
|
-
message=(
|
|
110
|
-
f"No Python package matching '{source}' under {workdir_path}. "
|
|
111
|
-
"Check the dotted name (e.g. 'mylib.helpers') and that you're "
|
|
112
|
-
"in the right workdir."
|
|
113
|
-
),
|
|
114
|
-
)
|
|
107
|
+
except FileNotFoundError as e:
|
|
108
|
+
return FailureResponse(kernel=context.kernel, message=str(e))
|
|
115
109
|
except ValueError as e:
|
|
116
|
-
#
|
|
110
|
+
# Ambiguous match (multiple packages) or invalid dotted name.
|
|
111
|
+
return FailureResponse(kernel=context.kernel, message=str(e))
|
|
112
|
+
except RuntimeError as e:
|
|
113
|
+
# Preflight failure (permissions, target exists). Plan was thrown away,
|
|
114
|
+
# no file has been written — safe to surface as a clean failure.
|
|
117
115
|
return FailureResponse(kernel=context.kernel, message=str(e))
|
|
118
116
|
|
|
119
117
|
mode = "APPLIED" if not dry_run else "DRY-RUN"
|
|
@@ -121,14 +119,52 @@ def python__code__rename(
|
|
|
121
119
|
f"[{mode}] {report.package_dir_old} → {report.package_dir_new}"
|
|
122
120
|
)
|
|
123
121
|
context.io.log(
|
|
124
|
-
f"[{mode}]
|
|
122
|
+
f"[{mode}] scope={suite_info} scanned={report.files_scanned} "
|
|
123
|
+
f"changed={len(report.files_changed)}"
|
|
125
124
|
)
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
]
|
|
126
|
+
# Display paths relative to the matched scope root they live under, so the
|
|
127
|
+
# output stays compact regardless of how many roots are involved.
|
|
128
|
+
rows = [[str(_shortest_rel(p, scope_roots))] for p in report.files_changed]
|
|
130
129
|
return TableResponse(
|
|
131
130
|
kernel=context.kernel,
|
|
132
131
|
content=rows,
|
|
133
132
|
headers=["File"],
|
|
134
133
|
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _resolve_scope_roots(kernel, app_workdir):
|
|
137
|
+
"""Return (scope_roots, info_string).
|
|
138
|
+
|
|
139
|
+
Looks up a containing suite via `find_suite_workdir_path()`; if found,
|
|
140
|
+
materialises the suite workdir and returns the path of every ordered
|
|
141
|
+
sibling package. Otherwise falls back to the current workdir alone.
|
|
142
|
+
"""
|
|
143
|
+
from wexample_wex_addon_app.app_addon_manager import AppAddonManager
|
|
144
|
+
|
|
145
|
+
suite_path = app_workdir.find_suite_workdir_path()
|
|
146
|
+
if not suite_path:
|
|
147
|
+
return [app_workdir.get_path()], f"workdir={app_workdir.get_path()}"
|
|
148
|
+
|
|
149
|
+
suite_workdir = AppAddonManager.from_kernel(kernel).create_app_workdir(
|
|
150
|
+
path=suite_path
|
|
151
|
+
)
|
|
152
|
+
packages = suite_workdir.get_ordered_packages()
|
|
153
|
+
if not packages:
|
|
154
|
+
return [app_workdir.get_path()], f"workdir={app_workdir.get_path()}"
|
|
155
|
+
|
|
156
|
+
roots = [p.get_path() for p in packages]
|
|
157
|
+
return roots, f"suite={suite_path} (packages={len(roots)})"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _shortest_rel(path, roots):
|
|
161
|
+
"""Return `path` relative to the longest matching root, else absolute."""
|
|
162
|
+
best = None
|
|
163
|
+
for root in roots:
|
|
164
|
+
try:
|
|
165
|
+
rel = path.relative_to(root)
|
|
166
|
+
except ValueError:
|
|
167
|
+
continue
|
|
168
|
+
if best is None or len(rel.parts) < len(best.parts):
|
|
169
|
+
best = rel
|
|
170
|
+
return best if best is not None else path
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from wexample_wex_addon_dev_python.refactor.rename_plan import RenamePlan
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RenameApplier(ABC):
|
|
10
|
+
"""Strategy that actually mutates the filesystem from a `RenamePlan`.
|
|
11
|
+
|
|
12
|
+
Kept abstract so we can swap a naive write/rename today for a
|
|
13
|
+
filestate-backed applier later (observability, rollback, dry-run unified
|
|
14
|
+
with the rest of the framework) without touching the handler/commands.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def apply(self, plan: RenamePlan) -> None:
|
|
18
|
+
"""Default sequencing: all file writes first, then directory renames.
|
|
19
|
+
|
|
20
|
+
Subclasses may override for a different ordering or to wrap the whole
|
|
21
|
+
batch in a transaction.
|
|
22
|
+
"""
|
|
23
|
+
for path, content in plan.rewrites.items():
|
|
24
|
+
self.write_file(path, content)
|
|
25
|
+
for old_dir, new_dir in plan.dir_renames:
|
|
26
|
+
self.rename_dir(old_dir, new_dir)
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def rename_dir(self, old: Path, new: Path) -> None: ...
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def write_file(self, path: Path, content: str) -> None: ...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class NaiveRenameApplier(RenameApplier):
|
|
36
|
+
"""Direct os-level applier. No rollback, no observability beyond the OS.
|
|
37
|
+
|
|
38
|
+
Sufficient for V2; deeper integration with filestate is deferred until we
|
|
39
|
+
measure that it pays off (volume, types of errors actually encountered).
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def rename_dir(self, old: Path, new: Path) -> None:
|
|
43
|
+
old.rename(new)
|
|
44
|
+
|
|
45
|
+
def write_file(self, path: Path, content: str) -> None:
|
|
46
|
+
path.write_text(content, encoding="utf-8")
|
|
@@ -5,6 +5,12 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
import libcst as cst
|
|
7
7
|
|
|
8
|
+
from wexample_wex_addon_dev_python.refactor.applier import (
|
|
9
|
+
NaiveRenameApplier,
|
|
10
|
+
RenameApplier,
|
|
11
|
+
)
|
|
12
|
+
from wexample_wex_addon_dev_python.refactor.rename_plan import RenamePlan
|
|
13
|
+
|
|
8
14
|
# Directories never scanned for source files (build/venv artefacts, VCS).
|
|
9
15
|
_SKIP_DIR_NAMES: frozenset[str] = frozenset({
|
|
10
16
|
"__pycache__",
|
|
@@ -52,6 +58,7 @@ class RenameReport:
|
|
|
52
58
|
`package_dir_old`/`new` describe the move (or proposed move in dry-run).
|
|
53
59
|
`files_changed` lists every .py whose imports were rewritten.
|
|
54
60
|
"""
|
|
61
|
+
|
|
55
62
|
files_changed: list[Path] = field(default_factory=list)
|
|
56
63
|
files_scanned: int = 0
|
|
57
64
|
package_dir_new: Path | None = None
|
|
@@ -59,25 +66,36 @@ class RenameReport:
|
|
|
59
66
|
|
|
60
67
|
|
|
61
68
|
class PythonPackageRenameHandler:
|
|
62
|
-
"""Rename a Python package
|
|
69
|
+
"""Rename a Python package and propagate the change to importers.
|
|
70
|
+
|
|
71
|
+
Three phases, fully separated:
|
|
72
|
+
1. compute_plan() — read-only scan, builds a `RenamePlan` in memory.
|
|
73
|
+
2. plan.preflight() — validation (permissions, target existence).
|
|
74
|
+
3. applier.apply(plan) — single batch of writes + the directory rename.
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
Splitting plan/apply is the durability strategy: a permission error mid-run
|
|
77
|
+
can't leave the repo half-rewritten. Splitting from the applier lets us
|
|
78
|
+
swap the write backend (naive → filestate-backed) without touching this
|
|
79
|
+
handler.
|
|
67
80
|
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
`scope_roots` = the set of roots to sweep for both the source package
|
|
82
|
+
directory AND import rewrites. Caller passes the sibling-package roots
|
|
83
|
+
when running suite-wide (typically obtained via the framework's
|
|
84
|
+
`FrameworkPackageSuiteWorkdir.get_ordered_packages()`), or a single
|
|
85
|
+
workdir when running standalone.
|
|
70
86
|
"""
|
|
71
87
|
|
|
72
88
|
def __init__(
|
|
73
89
|
self,
|
|
74
|
-
|
|
90
|
+
scope_roots: list[Path],
|
|
75
91
|
source: str,
|
|
76
92
|
target: str,
|
|
77
93
|
*,
|
|
78
94
|
dry_run: bool = True,
|
|
79
95
|
) -> None:
|
|
80
|
-
|
|
96
|
+
if not scope_roots:
|
|
97
|
+
raise ValueError("scope_roots must contain at least one path")
|
|
98
|
+
self.scope_roots = scope_roots
|
|
81
99
|
self.source = source
|
|
82
100
|
self.target = target
|
|
83
101
|
self.dry_run = dry_run
|
|
@@ -86,67 +104,96 @@ class PythonPackageRenameHandler:
|
|
|
86
104
|
if not all(self._source_parts) or not all(self._target_parts):
|
|
87
105
|
raise ValueError("source and target must be dotted names without empty parts")
|
|
88
106
|
|
|
107
|
+
def compute_plan(self) -> RenamePlan:
|
|
108
|
+
"""Phase 1: scan and build the plan. No write yet."""
|
|
109
|
+
plan = RenamePlan()
|
|
110
|
+
package_dir = self.find_package_dir()
|
|
111
|
+
plan.dir_renames.append(
|
|
112
|
+
(package_dir, package_dir.parent / self._target_parts[-1])
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
for py_file in self._iter_python_files():
|
|
116
|
+
plan.files_scanned += 1
|
|
117
|
+
new_source = self._rewrite_imports(py_file)
|
|
118
|
+
if new_source is not None:
|
|
119
|
+
plan.rewrites[py_file] = new_source
|
|
120
|
+
|
|
121
|
+
return plan
|
|
122
|
+
|
|
89
123
|
def find_package_dir(self) -> Path:
|
|
90
|
-
"""Return the unique directory matching `source`
|
|
91
|
-
n = len(self._source_parts)
|
|
124
|
+
"""Return the unique directory matching `source` across all scope roots."""
|
|
125
|
+
n = len(self._source_parts)
|
|
92
126
|
matches: list[Path] = []
|
|
93
|
-
for
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
127
|
+
for root in self.scope_roots:
|
|
128
|
+
for init_file in root.rglob("__init__.py"):
|
|
129
|
+
if any(part in _SKIP_DIR_NAMES for part in init_file.parent.parts):
|
|
130
|
+
continue
|
|
131
|
+
pkg_dir = init_file.parent
|
|
132
|
+
if len(pkg_dir.parts) < n:
|
|
133
|
+
continue
|
|
134
|
+
if pkg_dir.parts[-n:] == self._source_parts:
|
|
135
|
+
matches.append(pkg_dir)
|
|
136
|
+
|
|
137
|
+
# Same path discovered through two overlapping roots → dedupe.
|
|
138
|
+
unique = list({str(m): m for m in matches}.values())
|
|
101
139
|
|
|
102
|
-
if not
|
|
140
|
+
if not unique:
|
|
141
|
+
roots_str = ", ".join(str(r) for r in self.scope_roots)
|
|
103
142
|
raise FileNotFoundError(
|
|
104
|
-
f"No package directory matching '{self.source}' under {
|
|
143
|
+
f"No package directory matching '{self.source}' under: {roots_str}"
|
|
105
144
|
)
|
|
106
|
-
if len(
|
|
107
|
-
joined = "\n ".join(str(m) for m in
|
|
145
|
+
if len(unique) > 1:
|
|
146
|
+
joined = "\n ".join(str(m) for m in unique)
|
|
108
147
|
raise ValueError(
|
|
109
148
|
f"Multiple packages match '{self.source}', refine the dotted name:\n {joined}"
|
|
110
149
|
)
|
|
111
|
-
return
|
|
150
|
+
return unique[0]
|
|
112
151
|
|
|
113
|
-
def run(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
152
|
+
def run(
|
|
153
|
+
self,
|
|
154
|
+
applier: RenameApplier | None = None,
|
|
155
|
+
) -> RenameReport:
|
|
156
|
+
"""Compute plan → preflight → apply (or stop after compute in dry-run)."""
|
|
157
|
+
plan = self.compute_plan()
|
|
158
|
+
old_dir, new_dir = plan.dir_renames[0]
|
|
159
|
+
report = RenameReport(
|
|
160
|
+
files_changed=list(plan.rewrites.keys()),
|
|
161
|
+
files_scanned=plan.files_scanned,
|
|
162
|
+
package_dir_new=new_dir,
|
|
163
|
+
package_dir_old=old_dir,
|
|
164
|
+
)
|
|
118
165
|
|
|
119
|
-
|
|
120
|
-
report
|
|
121
|
-
new_source = self._rewrite_imports(py_file)
|
|
122
|
-
if new_source is None:
|
|
123
|
-
continue
|
|
124
|
-
report.files_changed.append(py_file)
|
|
125
|
-
if not self.dry_run:
|
|
126
|
-
py_file.write_text(new_source, encoding="utf-8")
|
|
166
|
+
if self.dry_run:
|
|
167
|
+
return report
|
|
127
168
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
package_dir.rename(report.package_dir_new)
|
|
169
|
+
problems = plan.preflight()
|
|
170
|
+
if problems:
|
|
171
|
+
joined = "\n - ".join(problems)
|
|
172
|
+
raise RuntimeError(f"Plan preflight failed:\n - {joined}")
|
|
133
173
|
|
|
174
|
+
(applier or NaiveRenameApplier()).apply(plan)
|
|
134
175
|
return report
|
|
135
176
|
|
|
136
177
|
def _iter_python_files(self):
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
178
|
+
"""Iterate every .py file under any scope root, skipping build/VCS dirs."""
|
|
179
|
+
seen: set[str] = set()
|
|
180
|
+
for root in self.scope_roots:
|
|
181
|
+
stack: list[Path] = [root]
|
|
182
|
+
while stack:
|
|
183
|
+
current = stack.pop()
|
|
184
|
+
try:
|
|
185
|
+
for entry in current.iterdir():
|
|
186
|
+
if entry.is_dir():
|
|
187
|
+
if entry.name not in _SKIP_DIR_NAMES:
|
|
188
|
+
stack.append(entry)
|
|
189
|
+
elif entry.suffix == ".py":
|
|
190
|
+
key = str(entry.resolve())
|
|
191
|
+
if key in seen:
|
|
192
|
+
continue
|
|
193
|
+
seen.add(key)
|
|
194
|
+
yield entry
|
|
195
|
+
except (PermissionError, FileNotFoundError):
|
|
196
|
+
continue
|
|
150
197
|
|
|
151
198
|
def _rewrite_imports(self, py_file: Path) -> str | None:
|
|
152
199
|
"""Return rewritten source, or None when no import touched this file."""
|
|
@@ -173,8 +220,7 @@ class _ImportRenameTransformer(cst.CSTTransformer):
|
|
|
173
220
|
super().__init__()
|
|
174
221
|
self.old_parts = old_parts
|
|
175
222
|
self.new_parts = new_parts
|
|
176
|
-
self._n = len(old_parts)
|
|
177
|
-
self._old_parts_0 = old_parts[0] # fast first-element guard in _starts_with
|
|
223
|
+
self._n = len(old_parts)
|
|
178
224
|
self.changed = False
|
|
179
225
|
|
|
180
226
|
def leave_Import(
|
|
@@ -202,7 +248,6 @@ class _ImportRenameTransformer(cst.CSTTransformer):
|
|
|
202
248
|
original_node: cst.ImportFrom,
|
|
203
249
|
updated_node: cst.ImportFrom,
|
|
204
250
|
) -> cst.ImportFrom:
|
|
205
|
-
# `from . import X` has module=None; relative imports are out of scope.
|
|
206
251
|
if updated_node.module is None:
|
|
207
252
|
return updated_node
|
|
208
253
|
parts = _dotted_to_parts(updated_node.module)
|
|
@@ -213,8 +258,4 @@ class _ImportRenameTransformer(cst.CSTTransformer):
|
|
|
213
258
|
return updated_node.with_changes(module=_parts_to_dotted(new_parts))
|
|
214
259
|
|
|
215
260
|
def _starts_with(self, parts: tuple[str, ...]) -> bool:
|
|
216
|
-
return (
|
|
217
|
-
len(parts) >= self._n
|
|
218
|
-
and parts[0] == self._old_parts_0 # fast guard: avoid slice alloc on mismatch
|
|
219
|
-
and parts[: self._n] == self.old_parts
|
|
220
|
-
)
|
|
261
|
+
return len(parts) >= self._n and parts[: self._n] == self.old_parts
|
wexample_wex_addon_dev_python-12.0.0/src/wexample_wex_addon_dev_python/refactor/rename_plan.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class RenamePlan:
|
|
10
|
+
"""Everything a rename will touch, fully computed before any disk write.
|
|
11
|
+
|
|
12
|
+
The plan is collected, validated (`preflight`), then applied as one batch.
|
|
13
|
+
Splitting the phases means a permission error or a target-already-exists
|
|
14
|
+
error stops the run before any partial change reaches the filesystem.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
rewrites: dict[Path, str] = field(default_factory=dict)
|
|
18
|
+
"""Per-file new source code. Keys = absolute file paths."""
|
|
19
|
+
|
|
20
|
+
dir_renames: list[tuple[Path, Path]] = field(default_factory=list)
|
|
21
|
+
"""(old_dir, new_dir) pairs. Ordered: applied last, after rewrites."""
|
|
22
|
+
|
|
23
|
+
files_scanned: int = 0
|
|
24
|
+
"""Total .py files inspected (for reporting)."""
|
|
25
|
+
|
|
26
|
+
def preflight(self) -> list[str]:
|
|
27
|
+
"""Return a list of human-readable problems, empty if plan can run.
|
|
28
|
+
|
|
29
|
+
Checks (cheap, no writes):
|
|
30
|
+
- each file in `rewrites` is writable
|
|
31
|
+
- each `dir_rename` target doesn't already exist
|
|
32
|
+
- each `dir_rename` source exists
|
|
33
|
+
"""
|
|
34
|
+
problems: list[str] = []
|
|
35
|
+
for path in self.rewrites:
|
|
36
|
+
if not os.access(path, os.W_OK):
|
|
37
|
+
problems.append(f"Not writable: {path}")
|
|
38
|
+
for old_dir, new_dir in self.dir_renames:
|
|
39
|
+
if not old_dir.exists():
|
|
40
|
+
problems.append(f"Source directory missing: {old_dir}")
|
|
41
|
+
if new_dir.exists():
|
|
42
|
+
problems.append(f"Target directory already exists: {new_dir}")
|
|
43
|
+
return problems
|
|
@@ -12,6 +12,10 @@ from wexample_wex_addon_dev_python.selection.abstract_python_code_selection impo
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
|
+
# Hoisted to module scope so the generator inside `is_candidate` captures it via
|
|
16
|
+
# a single LOAD_DEREF (closure) instead of rebuilding the tuple on each call.
|
|
17
|
+
_FUNC_TYPES: tuple[type, ...] = (ast.FunctionDef, ast.AsyncFunctionDef)
|
|
18
|
+
|
|
15
19
|
|
|
16
20
|
@base_class
|
|
17
21
|
class PythonCodePerformanceSelection(AbstractPythonCodeSelection):
|
|
@@ -78,9 +82,14 @@ class PythonCodePerformanceSelection(AbstractPythonCodeSelection):
|
|
|
78
82
|
tree = ast.parse(source)
|
|
79
83
|
except SyntaxError:
|
|
80
84
|
return False
|
|
85
|
+
# Local bindings keep hot-path overhead minimal:
|
|
86
|
+
# - `func_types`: one LOAD_DEREF instead of LOAD_GLOBAL per generator tick.
|
|
87
|
+
# - `is_trivial`: one LOAD_DEREF instead of LOAD_DEREF(self)+LOAD_ATTR
|
|
88
|
+
# for every function-def node that passes the isinstance guard.
|
|
89
|
+
func_types = _FUNC_TYPES
|
|
90
|
+
is_trivial = self._is_trivial_function
|
|
81
91
|
return any(
|
|
82
|
-
isinstance(node, (
|
|
83
|
-
and not self._is_trivial_function(node)
|
|
92
|
+
isinstance(node, func_types) and not is_trivial(node)
|
|
84
93
|
for node in ast.walk(tree)
|
|
85
94
|
)
|
|
86
95
|
|
|
@@ -92,7 +101,11 @@ class PythonCodePerformanceSelection(AbstractPythonCodeSelection):
|
|
|
92
101
|
- `pass`.
|
|
93
102
|
- `...` (Ellipsis statement — modern stub idiom).
|
|
94
103
|
- `raise NotImplementedError` or `raise NotImplementedError(...)`.
|
|
95
|
-
- `super()
|
|
104
|
+
- `super().<same_name>(...)` (with or without `return`) — pure
|
|
105
|
+
pass-through to the parent. Matches when the called super-method
|
|
106
|
+
name equals the wrapping function's name; a mismatched name is
|
|
107
|
+
almost certainly a bug or unusual API and we let the perf agent
|
|
108
|
+
look at it.
|
|
96
109
|
|
|
97
110
|
Plus: any decorator named `abstractmethod` (bare or qualified like
|
|
98
111
|
`abc.abstractmethod`) → trivial regardless of body. Abstract definitions
|
|
@@ -104,13 +117,9 @@ class PythonCodePerformanceSelection(AbstractPythonCodeSelection):
|
|
|
104
117
|
check decoupled from its container.
|
|
105
118
|
"""
|
|
106
119
|
for deco in node.decorator_list:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
else deco.attr
|
|
111
|
-
if isinstance(deco, ast.Attribute)
|
|
112
|
-
else None
|
|
113
|
-
)
|
|
120
|
+
# `ast.Name` has `id`; `ast.Attribute` has `attr`; other node types
|
|
121
|
+
# have neither — two C-level getattr probes beat two isinstance calls.
|
|
122
|
+
deco_name = getattr(deco, "id", None) or getattr(deco, "attr", None)
|
|
114
123
|
if deco_name == "abstractmethod":
|
|
115
124
|
return True
|
|
116
125
|
|
|
@@ -153,19 +162,28 @@ class PythonCodePerformanceSelection(AbstractPythonCodeSelection):
|
|
|
153
162
|
):
|
|
154
163
|
return True
|
|
155
164
|
|
|
156
|
-
|
|
165
|
+
# Same-name super pass-through: `super().<node.name>(...)` as either a
|
|
166
|
+
# bare expression or a return value. Matches `def foo(...): super().foo(...)`,
|
|
167
|
+
# `def foo(...): return super().foo(...)`, and the historical `__init__`
|
|
168
|
+
# case (which is subsumed). Different-name super calls (`def cleanup():
|
|
169
|
+
# super().close()`) intentionally fall through — that's not a textbook
|
|
170
|
+
# pass-through and might be hiding intent.
|
|
171
|
+
call = None
|
|
172
|
+
if isinstance(stmt, ast.Expr):
|
|
173
|
+
call = stmt.value
|
|
174
|
+
elif isinstance(stmt, ast.Return) and stmt.value is not None:
|
|
157
175
|
call = stmt.value
|
|
176
|
+
if (
|
|
177
|
+
isinstance(call, ast.Call)
|
|
178
|
+
and isinstance(call.func, ast.Attribute)
|
|
179
|
+
and call.func.attr == node.name
|
|
180
|
+
):
|
|
181
|
+
base = call.func.value
|
|
158
182
|
if (
|
|
159
|
-
isinstance(
|
|
160
|
-
and isinstance(
|
|
161
|
-
and
|
|
183
|
+
isinstance(base, ast.Call)
|
|
184
|
+
and isinstance(base.func, ast.Name)
|
|
185
|
+
and base.func.id == "super"
|
|
162
186
|
):
|
|
163
|
-
|
|
164
|
-
if (
|
|
165
|
-
isinstance(base, ast.Call)
|
|
166
|
-
and isinstance(base.func, ast.Name)
|
|
167
|
-
and base.func.id == "super"
|
|
168
|
-
):
|
|
169
|
-
return True
|
|
187
|
+
return True
|
|
170
188
|
|
|
171
189
|
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wexample_wex_addon_dev_python-11.2.0 → wexample_wex_addon_dev_python-12.0.0}/tests/unit/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|