wexample-wex-addon-dev-python 10.1.1__tar.gz → 10.2.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.
Files changed (75) hide show
  1. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/PKG-INFO +10 -10
  2. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/README.md +5 -5
  3. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/pyproject.toml +5 -5
  4. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/python_addon_manager.py +12 -1
  5. {wexample_wex_addon_dev_python-10.1.1/src/wexample_wex_addon_dev_python/services → wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/selection}/__init__.py +0 -0
  6. wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/selection/abstract_python_code_selection.py +45 -0
  7. wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/selection/python_code_performance_selection.py +172 -0
  8. {wexample_wex_addon_dev_python-10.1.1/tests/helpers → wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/workdir/mixin}/__init__.py +0 -0
  9. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/workdir/python_package_workdir.py +38 -51
  10. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/workdir/python_workdir.py +6 -10
  11. wexample_wex_addon_dev_python-10.2.0/tests/helpers/__init__.py +0 -0
  12. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/__init__.py +0 -0
  13. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/__pycache__/__init__.py +0 -0
  14. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/__init__.py +0 -0
  15. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/code/__init__.py +0 -0
  16. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/code/check/__init__.py +0 -0
  17. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/code/check/mypy.py +0 -0
  18. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/code/check/pylint.py +0 -0
  19. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/code/check/pyright.py +0 -0
  20. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/code/check.py +0 -0
  21. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/code/format/__init__.py +0 -0
  22. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/code/format/black.py +0 -0
  23. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/code/format/isort.py +0 -0
  24. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/code/format.py +0 -0
  25. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/examples/__init__.py +0 -0
  26. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/examples/utils/__init__.py +0 -0
  27. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/examples/utils/some_example_type.py +0 -0
  28. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/examples/validate.py +0 -0
  29. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/commands/release/__init__.py +0 -0
  30. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/common/__init__.py +0 -0
  31. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/common/pypi_registry_gateway.py +0 -0
  32. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/config_value/__init__.py +0 -0
  33. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/config_value/__pycache__/__init__.py +0 -0
  34. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/config_value/python_package_readme_config_value.py +0 -0
  35. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/const/__init__.py +0 -0
  36. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/const/__pycache__/__init__.py +0 -0
  37. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/const/package.py +0 -0
  38. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/const/python.py +0 -0
  39. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/const/tags.py +0 -0
  40. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/file/__init__.py +0 -0
  41. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/file/__pycache__/__init__.py +0 -0
  42. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/file/python_app_iml_file.py +0 -0
  43. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/file/python_pyproject_toml_file.py +0 -0
  44. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/helpers/__init__.py +0 -0
  45. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/helpers/pdm.py +0 -0
  46. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/middleware/__init__.py +0 -0
  47. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/middleware/__pycache__/__init__.py +0 -0
  48. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/middleware/each_python_file_middleware.py +0 -0
  49. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/py.typed +0 -0
  50. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/resources/.wex.yml +0 -0
  51. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/resources/docker/Dockerfile.python-profiling +0 -0
  52. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/resources/package_publish_gitlab.yml +0 -0
  53. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/resources/readme_templates/tests.md.j2 +0 -0
  54. {wexample_wex_addon_dev_python-10.1.1/src/wexample_wex_addon_dev_python/services/python → wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/services}/__init__.py +0 -0
  55. {wexample_wex_addon_dev_python-10.1.1/src/wexample_wex_addon_dev_python/services/python/commands → wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/services/python}/__init__.py +0 -0
  56. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/services/python/app_service.py +0 -0
  57. {wexample_wex_addon_dev_python-10.1.1/src/wexample_wex_addon_dev_python/services/python/commands/service → wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/services/python/commands}/__init__.py +0 -0
  58. {wexample_wex_addon_dev_python-10.1.1/src/wexample_wex_addon_dev_python/services/python/samples → wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/services/python/commands/service}/__init__.py +0 -0
  59. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/services/python/commands/service/install.py +0 -0
  60. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/services/python/commands/service/setup.py +0 -0
  61. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/services/python/docker/.wex.yml +0 -0
  62. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/services/python/docker/docker-compose.yml +0 -0
  63. {wexample_wex_addon_dev_python-10.1.1/src/wexample_wex_addon_dev_python/services/python/samples/docker → wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/services/python/samples}/__init__.py +0 -0
  64. {wexample_wex_addon_dev_python-10.1.1/src/wexample_wex_addon_dev_python/services/python/samples/docker/images → wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/services/python/samples/docker}/__init__.py +0 -0
  65. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/services/python/samples/docker/docker-compose.yml +0 -0
  66. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/services/python/samples/docker/entrypoint.sh +0 -0
  67. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/services/python/samples/docker/images/Dockerfile.base +0 -0
  68. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/services/python/samples/docker/images/Dockerfile.develop +0 -0
  69. {wexample_wex_addon_dev_python-10.1.1/src/wexample_wex_addon_dev_python/workdir → wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/services/python/samples/docker/images}/__init__.py +0 -0
  70. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/services/python/service.yml +0 -0
  71. {wexample_wex_addon_dev_python-10.1.1/src/wexample_wex_addon_dev_python/workdir/__pycache__ → wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/workdir}/__init__.py +0 -0
  72. {wexample_wex_addon_dev_python-10.1.1/src/wexample_wex_addon_dev_python/workdir/mixin → wexample_wex_addon_dev_python-10.2.0/src/wexample_wex_addon_dev_python/workdir/__pycache__}/__init__.py +0 -0
  73. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/workdir/mixin/with_profiling_python_workdir_mixin.py +0 -0
  74. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/src/wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py +0 -0
  75. {wexample_wex_addon_dev_python-10.1.1 → wexample_wex_addon_dev_python-10.2.0}/tests/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: 10.1.1
3
+ Version: 10.2.0
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.4.0
19
- Requires-Dist: wexample-filestate-python>=7.0.0
20
- Requires-Dist: wexample-wex-addon-ai>=10.0.0
21
- Requires-Dist: wexample-wex-addon-app>=25.2.0
18
+ Requires-Dist: wexample-api>=6.5.0
19
+ Requires-Dist: wexample-filestate-python>=8.0.0
20
+ Requires-Dist: wexample-wex-addon-ai>=11.0.0
21
+ Requires-Dist: wexample-wex-addon-app>=25.3.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: 10.1.1
29
+ Version: 10.2.0
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.4.0
116
- - wexample-filestate-python: >=7.0.0
117
- - wexample-wex-addon-ai: >=10.0.0
118
- - wexample-wex-addon-app: >=25.2.0
115
+ - wexample-api: >=6.5.0
116
+ - wexample-filestate-python: >=8.0.0
117
+ - wexample-wex-addon-ai: >=11.0.0
118
+ - wexample-wex-addon-app: >=25.3.0
119
119
 
120
120
  ## Versioning & Compatibility Policy
121
121
 
@@ -1,6 +1,6 @@
1
1
  # wex_addon_dev_python
2
2
 
3
- Version: 10.1.1
3
+ Version: 10.2.0
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.4.0
90
- - wexample-filestate-python: >=7.0.0
91
- - wexample-wex-addon-ai: >=10.0.0
92
- - wexample-wex-addon-app: >=25.2.0
89
+ - wexample-api: >=6.5.0
90
+ - wexample-filestate-python: >=8.0.0
91
+ - wexample-wex-addon-ai: >=11.0.0
92
+ - wexample-wex-addon-app: >=25.3.0
93
93
 
94
94
  ## Versioning & Compatibility Policy
95
95
 
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "wexample-wex-addon-dev-python"
9
- version = "10.1.1"
9
+ version = "10.2.0"
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.4.0",
28
- "wexample-filestate-python>=7.0.0",
29
- "wexample-wex-addon-ai>=10.0.0",
30
- "wexample-wex-addon-app>=25.2.0",
27
+ "wexample-api>=6.5.0",
28
+ "wexample-filestate-python>=8.0.0",
29
+ "wexample-wex-addon-ai>=11.0.0",
30
+ "wexample-wex-addon-app>=25.3.0",
31
31
  ]
32
32
 
33
33
  [project.readme]
@@ -2,13 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
+ from wexample_wex_addon_ai.selection.selection_contributing_addon_mixin import (
6
+ SelectionContributingAddonMixin,
7
+ )
5
8
  from wexample_wex_core.common.abstract_addon_manager import AbstractAddonManager
6
9
 
7
10
  if TYPE_CHECKING:
8
11
  from wexample_cli.middleware.abstract_middleware import AbstractMiddleware
12
+ from wexample_wex_addon_ai.selection.abstract_selection import AbstractSelection
9
13
 
10
14
 
11
- class PythonAddonManager(AbstractAddonManager):
15
+ class PythonAddonManager(SelectionContributingAddonMixin, AbstractAddonManager):
12
16
  def get_local_configurable_keys(self) -> list[dict]:
13
17
  from wexample_wex_addon_dev_python.helpers.pdm import (
14
18
  apply_pdm_bin_dir,
@@ -32,3 +36,10 @@ class PythonAddonManager(AbstractAddonManager):
32
36
  return [
33
37
  EachPythonFileMiddleware,
34
38
  ]
39
+
40
+ def get_selection_classes(self) -> list[type[AbstractSelection]]:
41
+ from wexample_wex_addon_dev_python.selection.python_code_performance_selection import (
42
+ PythonCodePerformanceSelection,
43
+ )
44
+
45
+ return [PythonCodePerformanceSelection]
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import ClassVar
4
+
5
+ from wexample_helpers.decorator.base_class import base_class
6
+ from wexample_wex_addon_ai.selection.abstract_code_from_directory_selection import (
7
+ AbstractCodeFromDirectorySelection,
8
+ )
9
+
10
+
11
+ @base_class
12
+ class AbstractPythonCodeSelection(AbstractCodeFromDirectorySelection):
13
+ """Shared base for every Python-source Selection (perf, tests, …).
14
+
15
+ Encapsulates the criteria that are constant across every Python use case in
16
+ this codebase:
17
+ - `.py` files only.
18
+ - Skip `__init__.py` regardless of size — re-export modules carry no logic
19
+ for any agent we're building today (perf optimization, test writing,
20
+ docstring extraction would all start by looking at real modules).
21
+ - Skip Python ecosystem non-source dirs: `__pycache__`, `.venv/`,
22
+ `.pdm-build/`, plus the `/tests/`/`/build/`/`/dist/` conventions.
23
+ - 50-byte floor: drops empty stubs (chosen empirically in the shell sweep,
24
+ kept verbatim until we have a reason to change it).
25
+
26
+ Concrete subclasses just pick a `SELECTION_NAME` and, if needed, extend
27
+ `get_excluded_path_fragments()` / `get_excluded_file_names()` with their
28
+ use-case specifics via `super() + (…)`.
29
+ """
30
+
31
+ EXTENSIONS: ClassVar[tuple[str, ...]] = (".py",)
32
+ MIN_SIZE_BYTES: ClassVar[int] = 50
33
+
34
+ def get_excluded_file_names(self) -> tuple[str, ...]:
35
+ return super().get_excluded_file_names() + ("__init__.py",)
36
+
37
+ def get_excluded_path_fragments(self) -> tuple[str, ...]:
38
+ return super().get_excluded_path_fragments() + (
39
+ "__pycache__",
40
+ "/tests/",
41
+ "/build/",
42
+ "/dist/",
43
+ "/.venv/",
44
+ "/.pdm-build/",
45
+ )
@@ -0,0 +1,172 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, ClassVar
4
+
5
+ from wexample_helpers.decorator.base_class import base_class
6
+
7
+ from wexample_wex_addon_dev_python.selection.abstract_python_code_selection import (
8
+ AbstractPythonCodeSelection,
9
+ )
10
+
11
+ if TYPE_CHECKING:
12
+ from pathlib import Path
13
+
14
+
15
+ @base_class
16
+ class PythonCodePerformanceSelection(AbstractPythonCodeSelection):
17
+ """Python source files candidate for the performance sweep.
18
+
19
+ Inherits every Python-side filter from `AbstractPythonCodeSelection` —
20
+ nothing perf-specific to declare today beyond the name. If a future need
21
+ emerges (e.g. include `.pyx` Cython modules, exclude a perf-noisy
22
+ `vendor/`), extend the inherited methods via `super() + (…)`.
23
+ """
24
+
25
+ SELECTION_NAME: ClassVar[str] = "python-performance"
26
+
27
+ def get_required_path_fragments(self) -> tuple[str, ...]:
28
+ """Only library code under `/src/` — the modern Python layout for any
29
+ package that uses PEP 517. Drops package-root scripts, `examples/`,
30
+ `benchmarks/`, `.wex/python/app_manager/*` (wex configuration code, not
31
+ target of optimization), and anything else outside the package's published
32
+ surface.
33
+
34
+ Symmetric with the exclusion list — defined here rather than in
35
+ `AbstractPythonCodeSelection` because the `/src/` requirement is opinionated
36
+ for the perf use case. A test-candidate Selection might want a different
37
+ positive scope (e.g. include `conftest.py` siblings), so we keep this knob
38
+ at the concrete level.
39
+ """
40
+ return super().get_required_path_fragments() + ("/src/",)
41
+
42
+ def is_candidate(self, item: Path) -> bool:
43
+ """Drop modules that have no executable code for the perf agent to optimize.
44
+
45
+ Proxy: "module contains at least one NON-TRIVIAL `FunctionDef` /
46
+ `AsyncFunctionDef` somewhere in its AST". A trivial function is one
47
+ whose body is just a stub (`pass`, `...`, docstring only, `raise
48
+ NotImplementedError`, or `super().__init__()` for `__init__`), or any
49
+ function decorated with `@abstractmethod`. See `_is_trivial_function`
50
+ for the exact rules.
51
+
52
+ Catches:
53
+ - Re-export modules (only `from X import Y`).
54
+ - Pure constant / type-alias modules.
55
+ - Empty subclasses (`class Foo(Bar): pass`).
56
+ - Dataclasses without methods, plain Enums, exception classes.
57
+ - Abstract base classes (every method `@abstractmethod` or `raise
58
+ NotImplementedError`).
59
+ - Classes whose only method is `__init__` that just calls `super()`.
60
+
61
+ Cost: ~5ms per file (Python's C `ast.parse`). On the ~1200 files of the
62
+ current sweep that's a few seconds total — orders of magnitude cheaper
63
+ than one LLM call against any single trivial file, and saves N of them.
64
+
65
+ Files we can't decode or parse (UnicodeDecodeError, SyntaxError) are
66
+ treated as non-candidates — perf optimization on unreadable / broken
67
+ files isn't what this sweep is for, the operator fixes those first.
68
+ Other I/O errors (FileNotFoundError, PermissionError) propagate.
69
+ """
70
+ import ast
71
+
72
+ if not super().is_candidate(item):
73
+ return False
74
+ try:
75
+ source = item.read_text(encoding="utf-8")
76
+ except UnicodeDecodeError:
77
+ return False
78
+ try:
79
+ tree = ast.parse(source)
80
+ except SyntaxError:
81
+ return False
82
+ return any(
83
+ isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
84
+ and not self._is_trivial_function(node)
85
+ for node in ast.walk(tree)
86
+ )
87
+
88
+ def _is_trivial_function(self, node) -> bool:
89
+ """True when `node`'s body has no logic worth optimizing.
90
+
91
+ Recognized trivial bodies (leading docstring stripped first):
92
+ - empty (just a docstring and nothing else).
93
+ - `pass`.
94
+ - `...` (Ellipsis statement — modern stub idiom).
95
+ - `raise NotImplementedError` or `raise NotImplementedError(...)`.
96
+ - `super().__init__(...)` only, when the function IS `__init__`.
97
+
98
+ Plus: any decorator named `abstractmethod` (bare or qualified like
99
+ `abc.abstractmethod`) → trivial regardless of body. Abstract definitions
100
+ are stubs by contract; the concrete subclass is where the optimizable
101
+ code lives.
102
+
103
+ Doesn't recurse into nested functions — `ast.walk` in `is_candidate`
104
+ already visits every descendant function independently. Keeps each
105
+ check decoupled from its container.
106
+ """
107
+ import ast
108
+
109
+ for deco in node.decorator_list:
110
+ deco_name = (
111
+ deco.id
112
+ if isinstance(deco, ast.Name)
113
+ else deco.attr
114
+ if isinstance(deco, ast.Attribute)
115
+ else None
116
+ )
117
+ if deco_name == "abstractmethod":
118
+ return True
119
+
120
+ body = list(node.body)
121
+ if (
122
+ body
123
+ and isinstance(body[0], ast.Expr)
124
+ and isinstance(body[0].value, ast.Constant)
125
+ and isinstance(body[0].value.value, str)
126
+ ):
127
+ body = body[1:]
128
+
129
+ if not body:
130
+ return True
131
+ if len(body) != 1:
132
+ return False
133
+
134
+ stmt = body[0]
135
+
136
+ if isinstance(stmt, ast.Pass):
137
+ return True
138
+
139
+ if (
140
+ isinstance(stmt, ast.Expr)
141
+ and isinstance(stmt.value, ast.Constant)
142
+ and stmt.value.value is Ellipsis
143
+ ):
144
+ return True
145
+
146
+ if isinstance(stmt, ast.Raise) and stmt.exc is not None:
147
+ exc = stmt.exc
148
+ if isinstance(exc, ast.Name) and exc.id == "NotImplementedError":
149
+ return True
150
+ if (
151
+ isinstance(exc, ast.Call)
152
+ and isinstance(exc.func, ast.Name)
153
+ and exc.func.id == "NotImplementedError"
154
+ ):
155
+ return True
156
+
157
+ if node.name == "__init__" and isinstance(stmt, ast.Expr):
158
+ call = stmt.value
159
+ if (
160
+ isinstance(call, ast.Call)
161
+ and isinstance(call.func, ast.Attribute)
162
+ and call.func.attr == "__init__"
163
+ ):
164
+ base = call.func.value
165
+ if (
166
+ isinstance(base, ast.Call)
167
+ and isinstance(base.func, ast.Name)
168
+ and base.func.id == "super"
169
+ ):
170
+ return True
171
+
172
+ return False
@@ -49,7 +49,6 @@ class PythonPackageWorkdir(PythonWorkdir):
49
49
  )
50
50
 
51
51
  def prepare_value(self, raw_value: DictConfig | None = None) -> DictConfig:
52
- from wexample_helpers.helpers.array import array_dict_get_by
53
52
  from wexample_helpers.helpers.file import file_read
54
53
  from wexample_helpers.helpers.module import module_get_path
55
54
 
@@ -102,56 +101,44 @@ class PythonPackageWorkdir(PythonWorkdir):
102
101
  },
103
102
  )
104
103
 
105
- # Retrieve the '.gitignore' configuration or create it if it doesn't exist
106
- config_gitignore = array_dict_get_by("name", ".gitignore", children)
107
- if config_gitignore is not None:
108
- generic_gitignore_rules = {
109
- "Python artifacts": [
110
- "*.egg-info",
111
- "__pycache__/",
112
- "*.py[cod]",
113
- "*.pyo",
114
- ],
115
- "Build directories": [
116
- "/build/",
117
- "/dist/",
118
- "/pip-wheel-metadata/",
119
- "/.pdm-build/",
120
- ],
121
- "Virtual environments": [
122
- ".env",
123
- ".venv",
124
- "venv/",
125
- ],
126
- "Test and coverage artifacts": [
127
- ".tox/",
128
- ".mypy_cache/",
129
- "pytest_cache/",
130
- ".coverage",
131
- "htmlcov/",
132
- ],
133
- "Editor and IDE settings": [
134
- ".vscode/",
135
- ".idea/",
136
- "*.swp",
137
- "*~",
138
- ],
139
- }
140
-
141
- should_contain_lines = config_gitignore.setdefault(
142
- "should_contain_lines", []
143
- )
144
- if not isinstance(should_contain_lines, list):
145
- raise ValueError("'should_contain_lines' must be a list")
146
-
147
- for category, rules in generic_gitignore_rules.items():
148
- category_header = f"# {category}"
149
- if category_header not in should_contain_lines:
150
- should_contain_lines.append(category_header)
151
-
152
- for rule in rules:
153
- if rule not in should_contain_lines:
154
- should_contain_lines.append(rule)
104
+ # Sectioned Python-package gitignore rules. The rectifier (when it
105
+ # runs on globless files) preserves section headers and sorts
106
+ # entries within each section; here we glob-heavy patterns make most
107
+ # of these no-ops on the rectifier side, but the lines themselves
108
+ # are still ensured by `should_contain_lines`.
109
+ for section, rules in {
110
+ "Python artifacts": [
111
+ "*.egg-info",
112
+ "__pycache__/",
113
+ "*.py[cod]",
114
+ "*.pyo",
115
+ ],
116
+ "Build directories": [
117
+ "/build/",
118
+ "/dist/",
119
+ "/pip-wheel-metadata/",
120
+ "/.pdm-build/",
121
+ ],
122
+ "Virtual environments": [
123
+ ".env",
124
+ ".venv",
125
+ "venv/",
126
+ ],
127
+ "Test and coverage artifacts": [
128
+ ".tox/",
129
+ ".mypy_cache/",
130
+ "pytest_cache/",
131
+ ".coverage",
132
+ "htmlcov/",
133
+ ],
134
+ "Editor and IDE settings": [
135
+ ".vscode/",
136
+ ".idea/",
137
+ "*.swp",
138
+ "*~",
139
+ ],
140
+ }.items():
141
+ self.add_gitignore_rules(children, *rules, section=section)
155
142
 
156
143
  return raw_value
157
144
 
@@ -183,7 +183,6 @@ class PythonWorkdir(
183
183
  from wexample_filestate.option.children_filter_option import (
184
184
  ChildrenFilterOption,
185
185
  )
186
- from wexample_helpers.helpers.array import array_dict_get_by
187
186
 
188
187
  from wexample_wex_addon_dev_python.file.python_pyproject_toml_file import (
189
188
  PythonPyprojectTomlFile,
@@ -195,15 +194,12 @@ class PythonWorkdir(
195
194
 
196
195
  children = raw_value["children"]
197
196
 
198
- # Add rules to .gitignore
199
- array_dict_get_by("name", ".gitignore", raw_value["children"]).setdefault(
200
- "should_contain_lines", []
201
- ).extend(
202
- [
203
- ".pdm-python",
204
- ".python-version",
205
- f"/{PYTHON_FILE_PYTEST_COVERAGE_JSON}",
206
- ]
197
+ self.add_gitignore_rules(
198
+ raw_value["children"],
199
+ ".pdm-python",
200
+ ".python-version",
201
+ f"/{PYTHON_FILE_PYTEST_COVERAGE_JSON}",
202
+ section="Python",
207
203
  )
208
204
 
209
205
  children.extend(