wexample-wex-addon-dev-python 12.2.0__tar.gz → 13.0.1__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-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/PKG-INFO +10 -10
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/README.md +5 -5
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/pyproject.toml +5 -5
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/code/rename.py +41 -19
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/file/python_pyproject_toml_file.py +11 -11
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/python_addon_manager.py +1 -1
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/abstract_python_path_rename_handler.py +104 -0
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/abstract_python_rename_handler.py +89 -0
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/abstract_python_symbol_rename_handler.py +183 -0
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/applier.py +87 -0
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/python_class_rename_handler.py +19 -0
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/python_common.py +426 -0
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/python_constant_rename_handler.py +37 -0
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/python_function_rename_handler.py +21 -0
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/python_module_rename_handler.py +74 -0
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/python_package_rename_handler.py +125 -0
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/rename_plan.py +84 -0
- wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/refactor/symbol_kind.py +34 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/commands/service/setup.py +2 -2
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/workdir/python_package_workdir.py +8 -8
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/workdir/python_workdir.py +6 -6
- {wexample_wex_addon_dev_python-12.2.0/tests/helpers → wexample_wex_addon_dev_python-13.0.1/tests/helper}/test_pdm.py +5 -5
- wexample_wex_addon_dev_python-13.0.1/tests/integration/refactor/test_python_class_rename_handler.py +121 -0
- wexample_wex_addon_dev_python-13.0.1/tests/integration/refactor/test_python_constant_rename_handler.py +68 -0
- wexample_wex_addon_dev_python-13.0.1/tests/integration/refactor/test_python_function_rename_handler.py +80 -0
- wexample_wex_addon_dev_python-13.0.1/tests/integration/refactor/test_python_module_rename_handler.py +59 -0
- wexample_wex_addon_dev_python-13.0.1/tests/integration/refactor/test_python_package_rename_handler.py +122 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/__init__.py +0 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/helper/__init__.py +0 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/__init__.py +0 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_abstract_python_path_rename_handler.py +17 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_abstract_python_rename_handler.py +44 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_abstract_python_symbol_rename_handler.py +92 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_applier.py +74 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_python_class_rename_handler.py +39 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_python_common.py +182 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_python_constant_rename_handler.py +75 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_python_function_rename_handler.py +37 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_python_module_rename_handler.py +77 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_python_package_rename_handler.py +60 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_rename_plan.py +65 -0
- wexample_wex_addon_dev_python-13.0.1/tests/unit/refactor/test_symbol_kind.py +17 -0
- wexample_wex_addon_dev_python-12.2.0/src/wexample_wex_addon_dev_python/refactor/applier.py +0 -46
- wexample_wex_addon_dev_python-12.2.0/src/wexample_wex_addon_dev_python/refactor/python_package_rename_handler.py +0 -261
- wexample_wex_addon_dev_python-12.2.0/src/wexample_wex_addon_dev_python/refactor/rename_plan.py +0 -43
- wexample_wex_addon_dev_python-12.2.0/src/wexample_wex_addon_dev_python/refactor/symbol_kind.py +0 -19
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/code/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/code/check/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/code/check/mypy.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/code/check/pylint.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/code/check/pyright.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/code/check.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/code/format/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/code/format/black.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/code/format/isort.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/code/format.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/examples/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/examples/utils/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/examples/utils/some_example_type.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/examples/validate.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/commands/release/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/common/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/common/pypi_registry_gateway.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/config_value/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/config_value/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/config_value/python_package_readme_config_value.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/const/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/const/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/const/package.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/const/python.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/const/tags.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/file/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/file/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/file/python_app_iml_file.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0/src/wexample_wex_addon_dev_python/helpers → wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/helper}/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0/src/wexample_wex_addon_dev_python/helpers → wexample_wex_addon_dev_python-13.0.1/src/wexample_wex_addon_dev_python/helper}/pdm.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/middleware/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/middleware/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/middleware/each_python_file_middleware.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/py.typed +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/refactor/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/resources/.wex.yml +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/resources/docker/Dockerfile.python-profiling +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/resources/package_publish_gitlab.yml +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/resources/readme_templates/tests.md.j2 +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/selection/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/selection/abstract_python_code_selection.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/selection/python_code_performance_selection.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/app_service.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/commands/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/commands/service/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/commands/service/install.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/docker/.wex.yml +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/docker/docker-compose.yml +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/samples/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/samples/docker/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/samples/docker/docker-compose.yml +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/samples/docker/entrypoint.sh +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/samples/docker/images/Dockerfile.base +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/samples/docker/images/Dockerfile.develop +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/samples/docker/images/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/services/python/service.yml +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/workdir/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/workdir/__pycache__/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/workdir/mixin/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/workdir/mixin/with_profiling_python_workdir_mixin.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/src/wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0/tests/helpers → wexample_wex_addon_dev_python-13.0.1/tests/helper}/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0/tests/unit → wexample_wex_addon_dev_python-13.0.1/tests/integration}/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0/tests/unit/helpers → wexample_wex_addon_dev_python-13.0.1/tests/integration/refactor}/__init__.py +0 -0
- {wexample_wex_addon_dev_python-12.2.0/tests/unit/helpers → wexample_wex_addon_dev_python-13.0.1/tests/unit/helper}/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: 13.0.1
|
|
4
4
|
Summary: Python dev addon for wex
|
|
5
5
|
Author-Email: weeger <contact@wexample.com>
|
|
6
6
|
License: MIT
|
|
@@ -15,10 +15,10 @@ Requires-Dist: griffe>=2.0.2
|
|
|
15
15
|
Requires-Dist: networkx
|
|
16
16
|
Requires-Dist: pylint
|
|
17
17
|
Requires-Dist: pyright
|
|
18
|
-
Requires-Dist: wexample-api>=6.
|
|
19
|
-
Requires-Dist: wexample-filestate-python>=
|
|
20
|
-
Requires-Dist: wexample-wex-addon-ai>=
|
|
21
|
-
Requires-Dist: wexample-wex-addon-app>=
|
|
18
|
+
Requires-Dist: wexample-api>=6.8.0
|
|
19
|
+
Requires-Dist: wexample-filestate-python>=9.0.0
|
|
20
|
+
Requires-Dist: wexample-wex-addon-ai>=12.0.0
|
|
21
|
+
Requires-Dist: wexample-wex-addon-app>=28.0.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: 13.0.1
|
|
30
30
|
|
|
31
31
|
Python dev addon for wex
|
|
32
32
|
|
|
@@ -112,10 +112,10 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
|
|
|
112
112
|
- networkx:
|
|
113
113
|
- pylint:
|
|
114
114
|
- pyright:
|
|
115
|
-
- wexample-api: >=6.
|
|
116
|
-
- wexample-filestate-python: >=
|
|
117
|
-
- wexample-wex-addon-ai: >=
|
|
118
|
-
- wexample-wex-addon-app: >=
|
|
115
|
+
- wexample-api: >=6.8.0
|
|
116
|
+
- wexample-filestate-python: >=9.0.0
|
|
117
|
+
- wexample-wex-addon-ai: >=12.0.0
|
|
118
|
+
- wexample-wex-addon-app: >=28.0.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: 13.0.1
|
|
4
4
|
|
|
5
5
|
Python dev addon for wex
|
|
6
6
|
|
|
@@ -86,10 +86,10 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
|
|
|
86
86
|
- networkx:
|
|
87
87
|
- pylint:
|
|
88
88
|
- pyright:
|
|
89
|
-
- wexample-api: >=6.
|
|
90
|
-
- wexample-filestate-python: >=
|
|
91
|
-
- wexample-wex-addon-ai: >=
|
|
92
|
-
- wexample-wex-addon-app: >=
|
|
89
|
+
- wexample-api: >=6.8.0
|
|
90
|
+
- wexample-filestate-python: >=9.0.0
|
|
91
|
+
- wexample-wex-addon-ai: >=12.0.0
|
|
92
|
+
- wexample-wex-addon-app: >=28.0.0
|
|
93
93
|
|
|
94
94
|
## Versioning & Compatibility Policy
|
|
95
95
|
|
{wexample_wex_addon_dev_python-12.2.0 → wexample_wex_addon_dev_python-13.0.1}/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 = "13.0.1"
|
|
10
10
|
description = "Python dev addon for wex"
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "weeger", email = "contact@wexample.com" },
|
|
@@ -24,10 +24,10 @@ dependencies = [
|
|
|
24
24
|
"networkx",
|
|
25
25
|
"pylint",
|
|
26
26
|
"pyright",
|
|
27
|
-
"wexample-api>=6.
|
|
28
|
-
"wexample-filestate-python>=
|
|
29
|
-
"wexample-wex-addon-ai>=
|
|
30
|
-
"wexample-wex-addon-app>=
|
|
27
|
+
"wexample-api>=6.8.0",
|
|
28
|
+
"wexample-filestate-python>=9.0.0",
|
|
29
|
+
"wexample-wex-addon-ai>=12.0.0",
|
|
30
|
+
"wexample-wex-addon-app>=28.0.0",
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
[project.readme]
|
|
@@ -32,8 +32,7 @@ if TYPE_CHECKING:
|
|
|
32
32
|
@option(
|
|
33
33
|
name="kind",
|
|
34
34
|
type=str,
|
|
35
|
-
required=
|
|
36
|
-
default="package",
|
|
35
|
+
required=True,
|
|
37
36
|
description="Symbol kind to rename. Step 1 supports only 'package'.",
|
|
38
37
|
)
|
|
39
38
|
@option(
|
|
@@ -69,6 +68,18 @@ def python__code__rename(
|
|
|
69
68
|
from wexample_app.response.failure_response import FailureResponse
|
|
70
69
|
from wexample_app.response.table_response import TableResponse
|
|
71
70
|
|
|
71
|
+
from wexample_wex_addon_dev_python.refactor.python_class_rename_handler import (
|
|
72
|
+
PythonClassRenameHandler,
|
|
73
|
+
)
|
|
74
|
+
from wexample_wex_addon_dev_python.refactor.python_constant_rename_handler import (
|
|
75
|
+
PythonConstantRenameHandler,
|
|
76
|
+
)
|
|
77
|
+
from wexample_wex_addon_dev_python.refactor.python_function_rename_handler import (
|
|
78
|
+
PythonFunctionRenameHandler,
|
|
79
|
+
)
|
|
80
|
+
from wexample_wex_addon_dev_python.refactor.python_module_rename_handler import (
|
|
81
|
+
PythonModuleRenameHandler,
|
|
82
|
+
)
|
|
72
83
|
from wexample_wex_addon_dev_python.refactor.python_package_rename_handler import (
|
|
73
84
|
PythonPackageRenameHandler,
|
|
74
85
|
)
|
|
@@ -85,38 +96,49 @@ def python__code__rename(
|
|
|
85
96
|
),
|
|
86
97
|
)
|
|
87
98
|
|
|
88
|
-
|
|
99
|
+
handler_class = {
|
|
100
|
+
SymbolKind.PACKAGE: PythonPackageRenameHandler,
|
|
101
|
+
SymbolKind.MODULE: PythonModuleRenameHandler,
|
|
102
|
+
SymbolKind.CLASS: PythonClassRenameHandler,
|
|
103
|
+
SymbolKind.FUNCTION: PythonFunctionRenameHandler,
|
|
104
|
+
SymbolKind.CONSTANT: PythonConstantRenameHandler,
|
|
105
|
+
}.get(kind_enum)
|
|
106
|
+
if handler_class is None:
|
|
89
107
|
return FailureResponse(
|
|
90
108
|
kernel=context.kernel,
|
|
91
|
-
message=
|
|
92
|
-
f"--kind {kind_enum.value} not implemented yet "
|
|
93
|
-
"(step 1 covers 'package' only)."
|
|
94
|
-
),
|
|
109
|
+
message=f"--kind {kind_enum.value} not implemented yet.",
|
|
95
110
|
)
|
|
96
111
|
|
|
97
112
|
scope_roots = app_workdir.get_code_scope_paths(context.kernel)
|
|
98
113
|
|
|
99
|
-
handler =
|
|
114
|
+
handler = handler_class(
|
|
100
115
|
scope_roots=scope_roots,
|
|
101
116
|
source=source,
|
|
102
117
|
target=target,
|
|
103
118
|
dry_run=dry_run,
|
|
104
119
|
)
|
|
120
|
+
|
|
121
|
+
spinner = context.io.spinner(
|
|
122
|
+
label=f"Scanning {len(scope_roots)} root(s) for '{source}' ({kind_enum.value})…"
|
|
123
|
+
)
|
|
105
124
|
try:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
125
|
+
try:
|
|
126
|
+
report = handler.run()
|
|
127
|
+
except FileNotFoundError as e:
|
|
128
|
+
return FailureResponse(kernel=context.kernel, message=str(e))
|
|
129
|
+
except ValueError as e:
|
|
130
|
+
# Ambiguous match (multiple matches) or invalid dotted name.
|
|
131
|
+
return FailureResponse(kernel=context.kernel, message=str(e))
|
|
132
|
+
except RuntimeError as e:
|
|
133
|
+
# Preflight failure (permissions, target exists). Plan was thrown
|
|
134
|
+
# away, no file has been written — surface as a clean failure.
|
|
135
|
+
return FailureResponse(kernel=context.kernel, message=str(e))
|
|
136
|
+
finally:
|
|
137
|
+
spinner.stop()
|
|
116
138
|
|
|
117
139
|
mode = "APPLIED" if not dry_run else "DRY-RUN"
|
|
118
140
|
context.io.log(
|
|
119
|
-
f"[{mode}] {report.
|
|
141
|
+
f"[{mode}] {report.source_path} → {report.target_path}"
|
|
120
142
|
)
|
|
121
143
|
context.io.log(
|
|
122
144
|
f"[{mode}] scope={len(scope_roots)} root(s) "
|
|
@@ -24,7 +24,7 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
24
24
|
) -> bool:
|
|
25
25
|
from packaging.requirements import Requirement
|
|
26
26
|
from packaging.utils import canonicalize_name
|
|
27
|
-
from wexample_filestate_python.
|
|
27
|
+
from wexample_filestate_python.helper.toml import toml_sort_string_array
|
|
28
28
|
|
|
29
29
|
new_name = canonicalize_name(spec.name)
|
|
30
30
|
|
|
@@ -118,7 +118,7 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
118
118
|
|
|
119
119
|
def optional_group_array(self, group: str):
|
|
120
120
|
"""Ensure and return project.optional-dependencies[group] as multi-line array."""
|
|
121
|
-
from wexample_filestate_python.
|
|
121
|
+
from wexample_filestate_python.helper.toml import (
|
|
122
122
|
toml_ensure_array,
|
|
123
123
|
toml_ensure_table,
|
|
124
124
|
)
|
|
@@ -162,7 +162,7 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
162
162
|
|
|
163
163
|
def _dependencies_array(self):
|
|
164
164
|
"""Ensure and return project.dependencies as a multi-line TOML array."""
|
|
165
|
-
from wexample_filestate_python.
|
|
165
|
+
from wexample_filestate_python.helper.toml import toml_ensure_array
|
|
166
166
|
|
|
167
167
|
project = self._project_table()
|
|
168
168
|
deps, _ = toml_ensure_array(project, "dependencies")
|
|
@@ -187,7 +187,7 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
187
187
|
if not import_name:
|
|
188
188
|
return
|
|
189
189
|
|
|
190
|
-
from wexample_filestate_python.
|
|
190
|
+
from wexample_filestate_python.helper.toml import toml_ensure_table
|
|
191
191
|
|
|
192
192
|
tool_tbl, _ = toml_ensure_table(content, ["tool"])
|
|
193
193
|
pdm_tbl, _ = toml_ensure_table(tool_tbl, ["pdm"])
|
|
@@ -207,7 +207,7 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
207
207
|
def _enforce_project_metadata(
|
|
208
208
|
self, content: dict, project_name: str | None, project_version: str | None
|
|
209
209
|
) -> None:
|
|
210
|
-
from wexample_filestate_python.
|
|
210
|
+
from wexample_filestate_python.helper.toml import toml_ensure_table
|
|
211
211
|
|
|
212
212
|
project_tbl, _ = toml_ensure_table(content, ["project"])
|
|
213
213
|
if project_name:
|
|
@@ -267,7 +267,7 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
267
267
|
if not import_name:
|
|
268
268
|
return
|
|
269
269
|
|
|
270
|
-
from wexample_filestate_python.
|
|
270
|
+
from wexample_filestate_python.helper.toml import toml_ensure_table
|
|
271
271
|
|
|
272
272
|
tool_tbl, _ = toml_ensure_table(content, ["tool"])
|
|
273
273
|
|
|
@@ -298,8 +298,8 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
298
298
|
]
|
|
299
299
|
|
|
300
300
|
def _ensure_dev_dependencies(self, content: dict) -> None:
|
|
301
|
-
from wexample_filestate_python.
|
|
302
|
-
from wexample_filestate_python.
|
|
301
|
+
from wexample_filestate_python.helper.package import package_normalize_name
|
|
302
|
+
from wexample_filestate_python.helper.toml import (
|
|
303
303
|
toml_get_string_value,
|
|
304
304
|
toml_sort_string_array,
|
|
305
305
|
)
|
|
@@ -325,8 +325,8 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
325
325
|
)
|
|
326
326
|
|
|
327
327
|
def _normalize_dependencies(self, content: dict) -> None:
|
|
328
|
-
from wexample_filestate_python.
|
|
329
|
-
from wexample_filestate_python.
|
|
328
|
+
from wexample_filestate_python.helper.package import package_normalize_name
|
|
329
|
+
from wexample_filestate_python.helper.toml import (
|
|
330
330
|
toml_get_string_value,
|
|
331
331
|
toml_sort_string_array,
|
|
332
332
|
)
|
|
@@ -385,7 +385,7 @@ class PythonPyprojectTomlFile(AppDependenciesConfigFileMixin, TomlFile):
|
|
|
385
385
|
|
|
386
386
|
def _project_table(self):
|
|
387
387
|
"""Ensure and return the [project] table."""
|
|
388
|
-
from wexample_filestate_python.
|
|
388
|
+
from wexample_filestate_python.helper.toml import toml_ensure_table
|
|
389
389
|
|
|
390
390
|
doc = self.read_parsed()
|
|
391
391
|
project, _ = toml_ensure_table(doc, ["project"])
|
|
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
|
|
14
14
|
|
|
15
15
|
class PythonAddonManager(SelectionContributingAddonMixin, AbstractAddonManager):
|
|
16
16
|
def get_local_configurable_keys(self) -> list[dict]:
|
|
17
|
-
from wexample_wex_addon_dev_python.
|
|
17
|
+
from wexample_wex_addon_dev_python.helper.pdm import (
|
|
18
18
|
apply_pdm_bin_dir,
|
|
19
19
|
detect_pdm_bin_dir,
|
|
20
20
|
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from wexample_helpers.classes.abstract_method import abstract_method
|
|
6
|
+
from wexample_helpers.classes.private_field import private_field
|
|
7
|
+
from wexample_helpers.decorator.base_class import base_class
|
|
8
|
+
|
|
9
|
+
from wexample_wex_addon_dev_python.refactor.abstract_python_rename_handler import (
|
|
10
|
+
AbstractPythonRenameHandler,
|
|
11
|
+
)
|
|
12
|
+
from wexample_wex_addon_dev_python.refactor.python_common import (
|
|
13
|
+
RenameReport,
|
|
14
|
+
iter_python_files,
|
|
15
|
+
rewrite_imports,
|
|
16
|
+
)
|
|
17
|
+
from wexample_wex_addon_dev_python.refactor.rename_plan import RenamePlan
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@base_class
|
|
21
|
+
class AbstractPythonPathRenameHandler(AbstractPythonRenameHandler):
|
|
22
|
+
"""Common shell for renames that boil down to moving one filesystem entry
|
|
23
|
+
(a package directory, a single .py file…) and rewriting every dotted
|
|
24
|
+
import statement that targets it.
|
|
25
|
+
|
|
26
|
+
Subclasses only customise *what* to locate on disk and *how* the new path
|
|
27
|
+
is derived. The plan, the report, and the per-file rewrite loop live here.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
_resolved_source_path: Path | None = private_field(
|
|
31
|
+
default=None,
|
|
32
|
+
description="Source path located by `_find_source_path` during "
|
|
33
|
+
"compute_plan; used to build the report when `path_renames` is empty "
|
|
34
|
+
"(e.g. merge mode).",
|
|
35
|
+
)
|
|
36
|
+
_resolved_target_path: Path | None = private_field(
|
|
37
|
+
default=None,
|
|
38
|
+
description="Target path derived from the source during compute_plan; "
|
|
39
|
+
"used by the report alongside `_resolved_source_path`.",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def compute_plan(self) -> RenamePlan:
|
|
43
|
+
plan = RenamePlan()
|
|
44
|
+
source_path = self._find_source_path()
|
|
45
|
+
target_path = self._derive_target_path(source_path)
|
|
46
|
+
self._resolved_source_path = source_path
|
|
47
|
+
self._resolved_target_path = target_path
|
|
48
|
+
self._plan_path_move(plan, source_path, target_path)
|
|
49
|
+
self._plan_target_parent_creation(plan, target_path)
|
|
50
|
+
|
|
51
|
+
for py_file in iter_python_files(self.scope_roots):
|
|
52
|
+
plan.files_scanned += 1
|
|
53
|
+
new_source = rewrite_imports(
|
|
54
|
+
py_file, self._source_parts, self._target_parts
|
|
55
|
+
)
|
|
56
|
+
if new_source is not None:
|
|
57
|
+
plan.rewrites[py_file] = new_source
|
|
58
|
+
|
|
59
|
+
return plan
|
|
60
|
+
|
|
61
|
+
def _build_report(self, plan: RenamePlan) -> RenameReport:
|
|
62
|
+
return RenameReport(
|
|
63
|
+
files_changed=list(plan.rewrites.keys()),
|
|
64
|
+
files_scanned=plan.files_scanned,
|
|
65
|
+
source_path=self._resolved_source_path,
|
|
66
|
+
target_path=self._resolved_target_path,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
@abstract_method
|
|
70
|
+
def _derive_target_path(self, source_path: Path) -> Path:
|
|
71
|
+
"""Return where `source_path` should be moved to on disk."""
|
|
72
|
+
|
|
73
|
+
@abstract_method
|
|
74
|
+
def _find_source_path(self) -> Path:
|
|
75
|
+
"""Locate the unique filesystem entry that matches `source`."""
|
|
76
|
+
|
|
77
|
+
def _plan_path_move(
|
|
78
|
+
self, plan: RenamePlan, source_path: Path, target_path: Path
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Default move: one atomic path rename. Subclasses may override to
|
|
81
|
+
merge into an existing target (`PythonPackageRenameHandler`)."""
|
|
82
|
+
plan.path_renames.append((source_path, target_path))
|
|
83
|
+
|
|
84
|
+
def _plan_target_parent_creation(
|
|
85
|
+
self, plan: RenamePlan, target_path: Path
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Walk up from `target_path.parent` and queue every missing ancestor
|
|
88
|
+
directory plus an empty `__init__.py` so the move can land inside a
|
|
89
|
+
valid Python package even when the user renames across parents."""
|
|
90
|
+
parent = target_path.parent
|
|
91
|
+
missing: list[Path] = []
|
|
92
|
+
while not parent.exists():
|
|
93
|
+
missing.append(parent)
|
|
94
|
+
up = parent.parent
|
|
95
|
+
if up == parent:
|
|
96
|
+
raise RuntimeError(
|
|
97
|
+
f"Cannot find any existing ancestor for {target_path}"
|
|
98
|
+
)
|
|
99
|
+
parent = up
|
|
100
|
+
|
|
101
|
+
# Outermost-first so each `mkdir` finds its own parent in place.
|
|
102
|
+
for directory in reversed(missing):
|
|
103
|
+
plan.directories_to_create.append(directory)
|
|
104
|
+
plan.rewrites[directory / "__init__.py"] = ""
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from wexample_helpers.classes.abstract_method import abstract_method
|
|
6
|
+
from wexample_helpers.classes.base_class import BaseClass
|
|
7
|
+
from wexample_helpers.classes.field import public_field
|
|
8
|
+
from wexample_helpers.classes.private_field import private_field
|
|
9
|
+
from wexample_helpers.decorator.base_class import base_class
|
|
10
|
+
|
|
11
|
+
from wexample_wex_addon_dev_python.refactor.applier import (
|
|
12
|
+
NaiveRenameApplier,
|
|
13
|
+
RenameApplier,
|
|
14
|
+
)
|
|
15
|
+
from wexample_wex_addon_dev_python.refactor.python_common import RenameReport
|
|
16
|
+
from wexample_wex_addon_dev_python.refactor.rename_plan import RenamePlan
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@base_class
|
|
20
|
+
class AbstractPythonRenameHandler(BaseClass):
|
|
21
|
+
"""Top-level shell for any Python rename, regardless of what gets renamed
|
|
22
|
+
(a package directory, a single module file, a class/function/constant
|
|
23
|
+
inside a module, …).
|
|
24
|
+
|
|
25
|
+
Subclasses decide what the plan looks like (`compute_plan`) and how the
|
|
26
|
+
report describes the operation (`_build_report`). Everything else — field
|
|
27
|
+
layout, init validation, three-phase orchestration — lives here so every
|
|
28
|
+
flavour of rename stays strictly consistent.
|
|
29
|
+
"""
|
|
30
|
+
dry_run: bool = public_field(
|
|
31
|
+
default=True,
|
|
32
|
+
description="When True, return the plan/report without touching the "
|
|
33
|
+
"filesystem.",
|
|
34
|
+
)
|
|
35
|
+
scope_roots: list[Path] = public_field(
|
|
36
|
+
description="Roots scanned for both source location and rewrites.",
|
|
37
|
+
)
|
|
38
|
+
source: str = public_field(
|
|
39
|
+
description="Dotted source name (e.g. 'mylib.helpers.Color').",
|
|
40
|
+
)
|
|
41
|
+
target: str = public_field(
|
|
42
|
+
description="Dotted target name (e.g. 'mylib.helpers.Tone').",
|
|
43
|
+
)
|
|
44
|
+
_source_parts: tuple[str, ...] = private_field(
|
|
45
|
+
factory=tuple,
|
|
46
|
+
description="Cached `source.split('.')`; set in __attrs_post_init__.",
|
|
47
|
+
)
|
|
48
|
+
_target_parts: tuple[str, ...] = private_field(
|
|
49
|
+
factory=tuple,
|
|
50
|
+
description="Cached `target.split('.')`; set in __attrs_post_init__.",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def __attrs_post_init__(self) -> None:
|
|
54
|
+
self._execute_super_attrs_post_init_if_exists()
|
|
55
|
+
if not self.scope_roots:
|
|
56
|
+
raise ValueError("scope_roots must contain at least one path")
|
|
57
|
+
self._source_parts = tuple(self.source.split("."))
|
|
58
|
+
self._target_parts = tuple(self.target.split("."))
|
|
59
|
+
if not all(self._source_parts) or not all(self._target_parts):
|
|
60
|
+
raise ValueError(
|
|
61
|
+
"source and target must be dotted names without empty parts"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@abstract_method
|
|
65
|
+
def compute_plan(self) -> RenamePlan:
|
|
66
|
+
"""Phase 1: scan and build the plan. No write yet."""
|
|
67
|
+
|
|
68
|
+
def run(
|
|
69
|
+
self,
|
|
70
|
+
applier: RenameApplier | None = None,
|
|
71
|
+
) -> RenameReport:
|
|
72
|
+
"""Compute plan → preflight → apply (or stop after compute in dry-run)."""
|
|
73
|
+
plan = self.compute_plan()
|
|
74
|
+
report = self._build_report(plan)
|
|
75
|
+
|
|
76
|
+
if self.dry_run:
|
|
77
|
+
return report
|
|
78
|
+
|
|
79
|
+
problems = plan.preflight()
|
|
80
|
+
if problems:
|
|
81
|
+
joined = "\n - ".join(problems)
|
|
82
|
+
raise RuntimeError(f"Plan preflight failed:\n - {joined}")
|
|
83
|
+
|
|
84
|
+
(applier or NaiveRenameApplier()).apply(plan)
|
|
85
|
+
return report
|
|
86
|
+
|
|
87
|
+
@abstract_method
|
|
88
|
+
def _build_report(self, plan: RenamePlan) -> RenameReport:
|
|
89
|
+
"""Compose the user-facing report once the plan is built."""
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import libcst as cst
|
|
6
|
+
from wexample_helpers.classes.abstract_method import abstract_method
|
|
7
|
+
from wexample_helpers.classes.private_field import private_field
|
|
8
|
+
from wexample_helpers.decorator.base_class import base_class
|
|
9
|
+
|
|
10
|
+
from wexample_wex_addon_dev_python.refactor.abstract_python_rename_handler import (
|
|
11
|
+
AbstractPythonRenameHandler,
|
|
12
|
+
)
|
|
13
|
+
from wexample_wex_addon_dev_python.refactor.python_common import (
|
|
14
|
+
SKIP_DIR_NAMES,
|
|
15
|
+
RenameReport,
|
|
16
|
+
iter_python_files,
|
|
17
|
+
rewrite_symbol_references,
|
|
18
|
+
)
|
|
19
|
+
from wexample_wex_addon_dev_python.refactor.rename_plan import RenamePlan
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@base_class
|
|
23
|
+
class AbstractPythonSymbolRenameHandler(AbstractPythonRenameHandler):
|
|
24
|
+
"""Common shell for renames that target a module-level symbol — a class,
|
|
25
|
+
a function, a constant. The .py file containing the definition is NOT
|
|
26
|
+
moved; only the symbol's name changes inside it, and every consumer that
|
|
27
|
+
imports or references it follows.
|
|
28
|
+
|
|
29
|
+
V1 limitation: in-place rename only — the parent module of source and
|
|
30
|
+
target must match. Cross-module symbol moves are deferred.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
_definition_file: Path | None = private_field(
|
|
34
|
+
default=None,
|
|
35
|
+
description="Set during compute_plan() once the symbol's defining file "
|
|
36
|
+
"is located; used by `_build_report` to fill source/target paths.",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def __attrs_post_init__(self) -> None:
|
|
40
|
+
super().__attrs_post_init__()
|
|
41
|
+
if len(self._source_parts) < 2 or len(self._target_parts) < 2:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"symbol source/target must be fully dotted (e.g. 'pkg.mod.Symbol')"
|
|
44
|
+
)
|
|
45
|
+
if self._source_parts[:-1] != self._target_parts[:-1]:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"symbol rename only supports in-place rename — parent module "
|
|
48
|
+
f"must match. Got source={'.'.join(self._source_parts[:-1])} "
|
|
49
|
+
f"target={'.'.join(self._target_parts[:-1])}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def compute_plan(self) -> RenamePlan:
|
|
53
|
+
plan = RenamePlan()
|
|
54
|
+
parent_parts = self._source_parts[:-1]
|
|
55
|
+
old_name = self._source_parts[-1]
|
|
56
|
+
new_name = self._target_parts[-1]
|
|
57
|
+
|
|
58
|
+
def_file = self._find_definition_file()
|
|
59
|
+
self._definition_file = def_file
|
|
60
|
+
|
|
61
|
+
for py_file in iter_python_files(self.scope_roots):
|
|
62
|
+
plan.files_scanned += 1
|
|
63
|
+
is_def = py_file.resolve() == def_file.resolve()
|
|
64
|
+
new_source = rewrite_symbol_references(
|
|
65
|
+
py_file,
|
|
66
|
+
parent_parts=parent_parts,
|
|
67
|
+
old_name=old_name,
|
|
68
|
+
new_name=new_name,
|
|
69
|
+
is_definition_file=is_def,
|
|
70
|
+
)
|
|
71
|
+
if new_source is not None:
|
|
72
|
+
plan.rewrites[py_file] = new_source
|
|
73
|
+
|
|
74
|
+
return plan
|
|
75
|
+
|
|
76
|
+
def _add_unique(
|
|
77
|
+
self, results: list[Path], seen: set[str], path: Path
|
|
78
|
+
) -> None:
|
|
79
|
+
key = str(path.resolve())
|
|
80
|
+
if key in seen:
|
|
81
|
+
return
|
|
82
|
+
seen.add(key)
|
|
83
|
+
results.append(path)
|
|
84
|
+
|
|
85
|
+
def _build_report(self, plan: RenamePlan) -> RenameReport:
|
|
86
|
+
return RenameReport(
|
|
87
|
+
files_changed=list(plan.rewrites.keys()),
|
|
88
|
+
files_scanned=plan.files_scanned,
|
|
89
|
+
source_path=self._definition_file,
|
|
90
|
+
target_path=self._definition_file,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def _candidate_parent_files(self, parent_parts: tuple[str, ...]) -> list[Path]:
|
|
94
|
+
"""Return every .py file whose dotted module name equals parent_parts.
|
|
95
|
+
|
|
96
|
+
Two layouts considered: `parent_parts/__init__.py` (package init) and
|
|
97
|
+
`<parent[:-1]>/parent[-1].py` (single-module file).
|
|
98
|
+
"""
|
|
99
|
+
if not parent_parts:
|
|
100
|
+
return []
|
|
101
|
+
leaf = parent_parts[-1]
|
|
102
|
+
outer_parts = parent_parts[:-1]
|
|
103
|
+
n_outer = len(outer_parts)
|
|
104
|
+
|
|
105
|
+
results: list[Path] = []
|
|
106
|
+
seen: set[str] = set()
|
|
107
|
+
|
|
108
|
+
for root in self.scope_roots:
|
|
109
|
+
for init_file in root.rglob("__init__.py"):
|
|
110
|
+
if any(p in SKIP_DIR_NAMES for p in init_file.parent.parts):
|
|
111
|
+
continue
|
|
112
|
+
pkg_dir = init_file.parent
|
|
113
|
+
|
|
114
|
+
# Case A: this package directory IS the parent module.
|
|
115
|
+
if (
|
|
116
|
+
len(pkg_dir.parts) >= len(parent_parts)
|
|
117
|
+
and pkg_dir.parts[-len(parent_parts):] == parent_parts
|
|
118
|
+
):
|
|
119
|
+
self._add_unique(results, seen, init_file)
|
|
120
|
+
|
|
121
|
+
# Case B: the parent module is `<pkg_dir>/<leaf>.py` and the
|
|
122
|
+
# package's dotted tail equals `outer_parts`.
|
|
123
|
+
if (
|
|
124
|
+
n_outer == 0
|
|
125
|
+
or (
|
|
126
|
+
len(pkg_dir.parts) >= n_outer
|
|
127
|
+
and pkg_dir.parts[-n_outer:] == outer_parts
|
|
128
|
+
)
|
|
129
|
+
):
|
|
130
|
+
candidate = pkg_dir / f"{leaf}.py"
|
|
131
|
+
if candidate.is_file():
|
|
132
|
+
self._add_unique(results, seen, candidate)
|
|
133
|
+
|
|
134
|
+
return results
|
|
135
|
+
|
|
136
|
+
def _find_definition_file(self) -> Path:
|
|
137
|
+
"""Locate the unique .py file that declares this symbol at module level.
|
|
138
|
+
|
|
139
|
+
The parent dotted name may resolve to either a module file
|
|
140
|
+
(`pkg/sub.py`) or a package init (`pkg/sub/__init__.py`). We check
|
|
141
|
+
both, parse them, and ask the subclass `_is_definition_node` whether
|
|
142
|
+
any top-level statement defines our symbol.
|
|
143
|
+
"""
|
|
144
|
+
parent_parts = self._source_parts[:-1]
|
|
145
|
+
old_name = self._source_parts[-1]
|
|
146
|
+
candidates = self._candidate_parent_files(parent_parts)
|
|
147
|
+
|
|
148
|
+
matches: list[Path] = []
|
|
149
|
+
for candidate in candidates:
|
|
150
|
+
try:
|
|
151
|
+
module = cst.parse_module(candidate.read_text(encoding="utf-8"))
|
|
152
|
+
except (cst.ParserSyntaxError, OSError):
|
|
153
|
+
continue
|
|
154
|
+
if any(
|
|
155
|
+
self._is_definition_node(stmt, old_name)
|
|
156
|
+
for stmt in module.body
|
|
157
|
+
):
|
|
158
|
+
matches.append(candidate)
|
|
159
|
+
|
|
160
|
+
unique = list({str(m): m for m in matches}.values())
|
|
161
|
+
if not unique:
|
|
162
|
+
roots_str = ", ".join(str(r) for r in self.scope_roots)
|
|
163
|
+
raise FileNotFoundError(
|
|
164
|
+
f"No top-level {self._kind_label()} '{old_name}' found in "
|
|
165
|
+
f"any module matching '{'.'.join(parent_parts)}' under: {roots_str}"
|
|
166
|
+
)
|
|
167
|
+
if len(unique) > 1:
|
|
168
|
+
joined = "\n ".join(str(m) for m in unique)
|
|
169
|
+
raise ValueError(
|
|
170
|
+
f"Multiple files define '{self.source}', refine the dotted name:\n {joined}"
|
|
171
|
+
)
|
|
172
|
+
return unique[0]
|
|
173
|
+
|
|
174
|
+
@abstract_method
|
|
175
|
+
def _is_definition_node(
|
|
176
|
+
self, node: cst.CSTNode, name: str
|
|
177
|
+
) -> bool:
|
|
178
|
+
"""Return True if `node` is a top-level definition of `name` of this
|
|
179
|
+
handler's kind (class / function / constant)."""
|
|
180
|
+
|
|
181
|
+
@abstract_method
|
|
182
|
+
def _kind_label(self) -> str:
|
|
183
|
+
"""Human-readable label of this rename kind for error messages."""
|