pyrig 2.2.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyrig/__init__.py +1 -0
- pyrig/dev/__init__.py +6 -0
- pyrig/dev/builders/__init__.py +1 -0
- pyrig/dev/builders/base/__init__.py +5 -0
- pyrig/dev/builders/base/base.py +256 -0
- pyrig/dev/builders/pyinstaller.py +229 -0
- pyrig/dev/cli/__init__.py +5 -0
- pyrig/dev/cli/cli.py +95 -0
- pyrig/dev/cli/commands/__init__.py +1 -0
- pyrig/dev/cli/commands/build_artifacts.py +16 -0
- pyrig/dev/cli/commands/create_root.py +25 -0
- pyrig/dev/cli/commands/create_tests.py +244 -0
- pyrig/dev/cli/commands/init_project.py +160 -0
- pyrig/dev/cli/commands/make_inits.py +27 -0
- pyrig/dev/cli/commands/protect_repo.py +145 -0
- pyrig/dev/cli/shared_subcommands.py +20 -0
- pyrig/dev/cli/subcommands.py +73 -0
- pyrig/dev/configs/__init__.py +1 -0
- pyrig/dev/configs/base/__init__.py +5 -0
- pyrig/dev/configs/base/base.py +826 -0
- pyrig/dev/configs/containers/__init__.py +1 -0
- pyrig/dev/configs/containers/container_file.py +111 -0
- pyrig/dev/configs/dot_env.py +95 -0
- pyrig/dev/configs/dot_python_version.py +88 -0
- pyrig/dev/configs/git/__init__.py +5 -0
- pyrig/dev/configs/git/gitignore.py +181 -0
- pyrig/dev/configs/git/pre_commit.py +170 -0
- pyrig/dev/configs/licence.py +112 -0
- pyrig/dev/configs/markdown/__init__.py +1 -0
- pyrig/dev/configs/markdown/docs/__init__.py +1 -0
- pyrig/dev/configs/markdown/docs/index.py +38 -0
- pyrig/dev/configs/markdown/readme.py +132 -0
- pyrig/dev/configs/py_typed.py +28 -0
- pyrig/dev/configs/pyproject.py +436 -0
- pyrig/dev/configs/python/__init__.py +5 -0
- pyrig/dev/configs/python/builders_init.py +27 -0
- pyrig/dev/configs/python/configs_init.py +28 -0
- pyrig/dev/configs/python/dot_experiment.py +46 -0
- pyrig/dev/configs/python/main.py +59 -0
- pyrig/dev/configs/python/resources_init.py +27 -0
- pyrig/dev/configs/python/shared_subcommands.py +29 -0
- pyrig/dev/configs/python/src_init.py +27 -0
- pyrig/dev/configs/python/subcommands.py +27 -0
- pyrig/dev/configs/testing/__init__.py +5 -0
- pyrig/dev/configs/testing/conftest.py +64 -0
- pyrig/dev/configs/testing/fixtures_init.py +27 -0
- pyrig/dev/configs/testing/main_test.py +74 -0
- pyrig/dev/configs/testing/zero_test.py +43 -0
- pyrig/dev/configs/workflows/__init__.py +5 -0
- pyrig/dev/configs/workflows/base/__init__.py +5 -0
- pyrig/dev/configs/workflows/base/base.py +1662 -0
- pyrig/dev/configs/workflows/build.py +106 -0
- pyrig/dev/configs/workflows/health_check.py +133 -0
- pyrig/dev/configs/workflows/publish.py +68 -0
- pyrig/dev/configs/workflows/release.py +90 -0
- pyrig/dev/tests/__init__.py +5 -0
- pyrig/dev/tests/conftest.py +40 -0
- pyrig/dev/tests/fixtures/__init__.py +1 -0
- pyrig/dev/tests/fixtures/assertions.py +147 -0
- pyrig/dev/tests/fixtures/autouse/__init__.py +5 -0
- pyrig/dev/tests/fixtures/autouse/class_.py +42 -0
- pyrig/dev/tests/fixtures/autouse/module.py +40 -0
- pyrig/dev/tests/fixtures/autouse/session.py +589 -0
- pyrig/dev/tests/fixtures/factories.py +118 -0
- pyrig/dev/utils/__init__.py +1 -0
- pyrig/dev/utils/cli.py +17 -0
- pyrig/dev/utils/git.py +312 -0
- pyrig/dev/utils/packages.py +93 -0
- pyrig/dev/utils/resources.py +77 -0
- pyrig/dev/utils/testing.py +66 -0
- pyrig/dev/utils/versions.py +268 -0
- pyrig/main.py +9 -0
- pyrig/py.typed +0 -0
- pyrig/resources/GITIGNORE +216 -0
- pyrig/resources/LATEST_PYTHON_VERSION +1 -0
- pyrig/resources/MIT_LICENSE_TEMPLATE +21 -0
- pyrig/resources/__init__.py +1 -0
- pyrig/src/__init__.py +1 -0
- pyrig/src/git/__init__.py +6 -0
- pyrig/src/git/git.py +146 -0
- pyrig/src/graph.py +255 -0
- pyrig/src/iterate.py +107 -0
- pyrig/src/modules/__init__.py +22 -0
- pyrig/src/modules/class_.py +369 -0
- pyrig/src/modules/function.py +189 -0
- pyrig/src/modules/inspection.py +148 -0
- pyrig/src/modules/module.py +658 -0
- pyrig/src/modules/package.py +452 -0
- pyrig/src/os/__init__.py +6 -0
- pyrig/src/os/os.py +121 -0
- pyrig/src/project/__init__.py +5 -0
- pyrig/src/project/mgt.py +83 -0
- pyrig/src/resource.py +58 -0
- pyrig/src/string.py +100 -0
- pyrig/src/testing/__init__.py +6 -0
- pyrig/src/testing/assertions.py +66 -0
- pyrig/src/testing/convention.py +203 -0
- pyrig-2.2.6.dist-info/METADATA +174 -0
- pyrig-2.2.6.dist-info/RECORD +102 -0
- pyrig-2.2.6.dist-info/WHEEL +4 -0
- pyrig-2.2.6.dist-info/entry_points.txt +3 -0
- pyrig-2.2.6.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Utilities for automatically creating test files for the project.
|
|
2
|
+
|
|
3
|
+
This module provides functions to generate test files for all modules and classes
|
|
4
|
+
in the project, ensuring that every function and method has a corresponding test.
|
|
5
|
+
It creates the basic test structure and generates skeleton test functions with
|
|
6
|
+
NotImplementedError to indicate tests that need to be written.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from types import ModuleType
|
|
10
|
+
|
|
11
|
+
from pyrig.dev.utils.packages import get_src_package
|
|
12
|
+
from pyrig.src.modules.class_ import (
|
|
13
|
+
get_all_cls_from_module,
|
|
14
|
+
get_all_methods_from_cls,
|
|
15
|
+
)
|
|
16
|
+
from pyrig.src.modules.function import get_all_functions_from_module
|
|
17
|
+
from pyrig.src.modules.inspection import get_qualname_of_obj
|
|
18
|
+
from pyrig.src.modules.module import (
|
|
19
|
+
create_module,
|
|
20
|
+
get_isolated_obj_name,
|
|
21
|
+
get_module_content_as_str,
|
|
22
|
+
to_path,
|
|
23
|
+
)
|
|
24
|
+
from pyrig.src.modules.package import (
|
|
25
|
+
walk_package,
|
|
26
|
+
)
|
|
27
|
+
from pyrig.src.testing.convention import (
|
|
28
|
+
get_test_obj_from_obj,
|
|
29
|
+
make_test_obj_importpath_from_obj,
|
|
30
|
+
make_test_obj_name,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def make_test_skeletons() -> None:
|
|
35
|
+
"""Create all test files for the project.
|
|
36
|
+
|
|
37
|
+
This function orchestrates the test creation process by first setting up the base
|
|
38
|
+
test structure and then creating test files for all source packages.
|
|
39
|
+
"""
|
|
40
|
+
create_tests_for_package(get_src_package())
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def create_tests_for_package(package: ModuleType) -> None:
|
|
44
|
+
"""Create test files for all modules in the source package.
|
|
45
|
+
|
|
46
|
+
This function walks through the source package hierarchy and creates corresponding
|
|
47
|
+
test packages and modules for each package and module found in the source.
|
|
48
|
+
"""
|
|
49
|
+
for pkg, modules in walk_package(package):
|
|
50
|
+
create_test_package(pkg)
|
|
51
|
+
for module in modules:
|
|
52
|
+
create_test_module(module)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def create_test_package(package: ModuleType) -> None:
|
|
56
|
+
"""Create a test package for a source package.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
package: The source package module to create a test package for
|
|
60
|
+
|
|
61
|
+
This function creates a test package with the appropriate naming convention
|
|
62
|
+
if it doesn't already exist.
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
test_package_name = make_test_obj_importpath_from_obj(package)
|
|
66
|
+
# create package if it doesn't exist
|
|
67
|
+
create_module(test_package_name, is_package=True)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def create_test_module(module: ModuleType) -> None:
|
|
71
|
+
"""Create a test module for a source module.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
module: The source module to create a test module for
|
|
75
|
+
|
|
76
|
+
This function:
|
|
77
|
+
1. Creates a test module with the appropriate naming convention
|
|
78
|
+
2. Generates the test module content with skeleton test functions
|
|
79
|
+
3. Writes the content to the test module file
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
test_module_name = make_test_obj_importpath_from_obj(module)
|
|
83
|
+
test_module = create_module(test_module_name, is_package=False)
|
|
84
|
+
test_module_path = to_path(test_module, is_package=False)
|
|
85
|
+
test_module_path.write_text(get_test_module_content(module))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_test_module_content(module: ModuleType) -> str:
|
|
89
|
+
"""Generate the content for a test module.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
module: The source module to generate test content for
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
The generated test module content as a string
|
|
96
|
+
|
|
97
|
+
This function:
|
|
98
|
+
1. Gets the existing test module content if it exists
|
|
99
|
+
2. Adds test functions for all functions in the source module
|
|
100
|
+
3. Adds test classes for all classes in the source module
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
test_module = get_test_obj_from_obj(module)
|
|
104
|
+
test_module_content = get_module_content_as_str(test_module)
|
|
105
|
+
|
|
106
|
+
test_module_content = get_test_functions_content(
|
|
107
|
+
module, test_module, test_module_content
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return get_test_classes_content(module, test_module, test_module_content)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_test_functions_content(
|
|
114
|
+
module: ModuleType,
|
|
115
|
+
test_module: ModuleType,
|
|
116
|
+
test_module_content: str,
|
|
117
|
+
) -> str:
|
|
118
|
+
"""Generate test function content for a module.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
module: The source module containing functions to test
|
|
122
|
+
test_module: The test module to add function tests to
|
|
123
|
+
test_module_content: The current content of the test module
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The updated test module content with function tests added
|
|
127
|
+
|
|
128
|
+
This function:
|
|
129
|
+
1. Identifies all functions in the source module
|
|
130
|
+
2. Determines which functions don't have corresponding tests
|
|
131
|
+
3. Generates skeleton test functions for untested functions
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
funcs = get_all_functions_from_module(module)
|
|
135
|
+
test_functions = get_all_functions_from_module(test_module)
|
|
136
|
+
supposed_test_funcs_names = [make_test_obj_name(f) for f in funcs]
|
|
137
|
+
|
|
138
|
+
test_funcs_names = [get_qualname_of_obj(f) for f in test_functions]
|
|
139
|
+
|
|
140
|
+
untested_funcs_names = [
|
|
141
|
+
f for f in supposed_test_funcs_names if f not in test_funcs_names
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
for test_func_name in untested_funcs_names:
|
|
145
|
+
test_module_content += f"""
|
|
146
|
+
|
|
147
|
+
def {test_func_name}() -> None:
|
|
148
|
+
\"\"\"Test function.\"\"\"
|
|
149
|
+
raise {NotImplementedError.__name__}
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
return test_module_content
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_test_classes_content(
|
|
156
|
+
module: ModuleType,
|
|
157
|
+
test_module: ModuleType,
|
|
158
|
+
test_module_content: str,
|
|
159
|
+
) -> str:
|
|
160
|
+
"""Generate test class content for a module.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
module: The source module containing classes to test
|
|
164
|
+
test_module: The test module to add class tests to
|
|
165
|
+
test_module_content: The current content of the test module
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
The updated test module content with class tests added
|
|
169
|
+
|
|
170
|
+
This function:
|
|
171
|
+
1. Identifies all classes in the source module
|
|
172
|
+
2. Determines which classes and methods don't have corresponding tests
|
|
173
|
+
3. Generates skeleton test classes and methods for untested classes and methods
|
|
174
|
+
4. Inserts the new test classes into the existing content
|
|
175
|
+
if the class already exists
|
|
176
|
+
|
|
177
|
+
Raises:
|
|
178
|
+
ValueError: If a test class declaration appears multiple
|
|
179
|
+
times in the test module
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
classes = get_all_cls_from_module(module)
|
|
183
|
+
test_classes = get_all_cls_from_module(test_module)
|
|
184
|
+
|
|
185
|
+
class_to_methods = {
|
|
186
|
+
c: get_all_methods_from_cls(c, exclude_parent_methods=True) for c in classes
|
|
187
|
+
}
|
|
188
|
+
test_class_to_methods = {
|
|
189
|
+
tc: get_all_methods_from_cls(tc, exclude_parent_methods=True)
|
|
190
|
+
for tc in test_classes
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
supposed_test_class_to_methods_names = {
|
|
194
|
+
make_test_obj_name(c): [make_test_obj_name(m) for m in ms]
|
|
195
|
+
for c, ms in class_to_methods.items()
|
|
196
|
+
}
|
|
197
|
+
test_class_to_methods_names = {
|
|
198
|
+
get_isolated_obj_name(tc): [get_isolated_obj_name(tm) for tm in tms]
|
|
199
|
+
for tc, tms in test_class_to_methods.items()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
untested_test_class_to_methods_names: dict[str, list[str]] = {}
|
|
203
|
+
for (
|
|
204
|
+
test_class_name,
|
|
205
|
+
supposed_test_methods_names,
|
|
206
|
+
) in supposed_test_class_to_methods_names.items():
|
|
207
|
+
test_methods_names = test_class_to_methods_names.get(test_class_name, [])
|
|
208
|
+
untested_methods_names = [
|
|
209
|
+
tmn for tmn in supposed_test_methods_names if tmn not in test_methods_names
|
|
210
|
+
]
|
|
211
|
+
if (
|
|
212
|
+
not supposed_test_methods_names
|
|
213
|
+
and test_class_name not in test_class_to_methods_names
|
|
214
|
+
):
|
|
215
|
+
untested_test_class_to_methods_names[test_class_name] = []
|
|
216
|
+
if untested_methods_names:
|
|
217
|
+
untested_test_class_to_methods_names[test_class_name] = (
|
|
218
|
+
untested_methods_names
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
for (
|
|
222
|
+
test_class_name,
|
|
223
|
+
untested_methods_names,
|
|
224
|
+
) in untested_test_class_to_methods_names.items():
|
|
225
|
+
test_class_declaration = f"""
|
|
226
|
+
class {test_class_name}:
|
|
227
|
+
\"\"\"Test class.\"\"\"
|
|
228
|
+
"""
|
|
229
|
+
test_class_content = test_class_declaration
|
|
230
|
+
for untested_method_name in untested_methods_names:
|
|
231
|
+
test_class_content += f"""
|
|
232
|
+
def {untested_method_name}(self) -> None:
|
|
233
|
+
\"\"\"Test method.\"\"\"
|
|
234
|
+
raise {NotImplementedError.__name__}
|
|
235
|
+
"""
|
|
236
|
+
parts = test_module_content.split(test_class_declaration)
|
|
237
|
+
expected_parts = 2
|
|
238
|
+
if len(parts) > expected_parts:
|
|
239
|
+
msg = f"Found {len(parts)} parts, expected 2"
|
|
240
|
+
raise ValueError(msg)
|
|
241
|
+
parts.insert(1, test_class_content)
|
|
242
|
+
test_module_content = "".join(parts)
|
|
243
|
+
|
|
244
|
+
return test_module_content
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Project initialization orchestration.
|
|
2
|
+
|
|
3
|
+
This module provides the main initialization flow for pyrig projects.
|
|
4
|
+
The `init()` function runs a series of setup steps to fully configure
|
|
5
|
+
a new project, including:
|
|
6
|
+
|
|
7
|
+
1. Writing priority config files (pyproject.toml with dev dependencies)
|
|
8
|
+
2. Installing dependencies with uv
|
|
9
|
+
3. Creating project structure (source and test directories)
|
|
10
|
+
4. Running pre-commit hooks for initial formatting
|
|
11
|
+
5. Running tests to verify setup
|
|
12
|
+
6. Re-installing to activate CLI entry points
|
|
13
|
+
|
|
14
|
+
The initialization process is idempotent and can be re-run safely.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
Run from command line:
|
|
18
|
+
$ uv run pyrig init
|
|
19
|
+
|
|
20
|
+
Or programmatically:
|
|
21
|
+
>>> from pyrig.src.project.init import init
|
|
22
|
+
>>> init()
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
from collections.abc import Callable
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
import pyrig
|
|
30
|
+
from pyrig.src.os.os import run_subprocess
|
|
31
|
+
from pyrig.src.project.mgt import PROJECT_MGT, get_pyrig_cli_cmd_args
|
|
32
|
+
from pyrig.src.string import make_name_from_obj
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
STANDARD_DEV_DEPS: list[str] = ["pyrig-dev"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def adding_dev_dependencies() -> None:
|
|
41
|
+
"""Install development dependencies.
|
|
42
|
+
|
|
43
|
+
This installs the dev dependencies listed in pyproject.toml.
|
|
44
|
+
"""
|
|
45
|
+
run_subprocess([PROJECT_MGT, "add", "--group", "dev", *STANDARD_DEV_DEPS])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def creating_priority_config_files() -> None:
|
|
49
|
+
"""Create priority config files.
|
|
50
|
+
|
|
51
|
+
This creates the priority config files that are required for
|
|
52
|
+
the other setup steps.
|
|
53
|
+
"""
|
|
54
|
+
from pyrig.dev.configs.base.base import ConfigFile # noqa: PLC0415
|
|
55
|
+
|
|
56
|
+
ConfigFile.init_priority_config_files()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def syncing_venv() -> None:
|
|
60
|
+
"""Sync the virtual environment.
|
|
61
|
+
|
|
62
|
+
This installs the dependencies listed in pyproject.toml.
|
|
63
|
+
"""
|
|
64
|
+
run_subprocess([PROJECT_MGT, "sync"])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def creating_project_root() -> None:
|
|
68
|
+
"""Execute the create-root CLI command via subprocess.
|
|
69
|
+
|
|
70
|
+
Invokes `uv run pyrig create-root` to generate all config files
|
|
71
|
+
and the project directory structure.
|
|
72
|
+
"""
|
|
73
|
+
from pyrig.dev.cli.subcommands import mkroot # noqa: PLC0415
|
|
74
|
+
|
|
75
|
+
run_subprocess(get_pyrig_cli_cmd_args(mkroot))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def creating_test_files() -> None:
|
|
79
|
+
"""Execute the create-tests CLI command via subprocess.
|
|
80
|
+
|
|
81
|
+
Invokes `uv run pyrig create-tests` to generate test skeleton
|
|
82
|
+
files that mirror the source code structure.
|
|
83
|
+
"""
|
|
84
|
+
from pyrig.dev.cli.subcommands import mktests # noqa: PLC0415
|
|
85
|
+
|
|
86
|
+
run_subprocess(get_pyrig_cli_cmd_args(mktests))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def running_pre_commit_hooks() -> None:
|
|
90
|
+
"""Run all pre-commit hooks.
|
|
91
|
+
|
|
92
|
+
This runs all pre-commit hooks to ensure the codebase is
|
|
93
|
+
in a clean, linted, and formatted state.
|
|
94
|
+
"""
|
|
95
|
+
from pyrig.dev.configs.git.pre_commit import ( # noqa: PLC0415
|
|
96
|
+
PreCommitConfigConfigFile,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
PreCommitConfigConfigFile.run_hooks(add_before_commit=True)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def running_tests() -> None:
|
|
103
|
+
"""Run the test suite.
|
|
104
|
+
|
|
105
|
+
This executes the test suite to verify that everything is
|
|
106
|
+
working correctly after initialization.
|
|
107
|
+
"""
|
|
108
|
+
from pyrig.dev.configs.testing.conftest import ConftestConfigFile # noqa: PLC0415
|
|
109
|
+
|
|
110
|
+
ConftestConfigFile.run_tests()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def committing_initial_changes() -> None:
|
|
114
|
+
"""Commit all initial changes.
|
|
115
|
+
|
|
116
|
+
This commits all changes made during initialization in a single commit.
|
|
117
|
+
"""
|
|
118
|
+
# changes were added by the run pre-commit hooks step
|
|
119
|
+
run_subprocess(
|
|
120
|
+
["git", "commit", "--no-verify", "-m", f"{pyrig.__name__}: Initial commit"]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
SETUP_STEPS: list[Callable[..., Any]] = [
|
|
125
|
+
adding_dev_dependencies,
|
|
126
|
+
syncing_venv,
|
|
127
|
+
creating_priority_config_files,
|
|
128
|
+
syncing_venv,
|
|
129
|
+
creating_project_root,
|
|
130
|
+
creating_test_files,
|
|
131
|
+
running_pre_commit_hooks,
|
|
132
|
+
running_tests,
|
|
133
|
+
committing_initial_changes,
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def init_project() -> None:
|
|
138
|
+
"""Initialize a pyrig project by running all setup steps.
|
|
139
|
+
|
|
140
|
+
Executes each step in `SETUP_STEPS` sequentially, logging progress.
|
|
141
|
+
This is the main entry point for the `pyrig init` command.
|
|
142
|
+
|
|
143
|
+
The steps include:
|
|
144
|
+
1. Write priority config files (pyproject.toml)
|
|
145
|
+
2. Install dependencies
|
|
146
|
+
3. Update dependencies to latest
|
|
147
|
+
4. Create project structure
|
|
148
|
+
5. Generate test skeletons
|
|
149
|
+
6. Run pre-commit hooks
|
|
150
|
+
7. Run tests
|
|
151
|
+
8. Re-install to activate CLI
|
|
152
|
+
"""
|
|
153
|
+
# for init set log level to info
|
|
154
|
+
logging.basicConfig(level=logging.INFO)
|
|
155
|
+
|
|
156
|
+
for step in SETUP_STEPS:
|
|
157
|
+
step_name = make_name_from_obj(step, join_on=" ")
|
|
158
|
+
logger.info(step_name)
|
|
159
|
+
step()
|
|
160
|
+
logger.info("Setup complete!")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""A func that creates __init__.py files for all packages and modules."""
|
|
2
|
+
|
|
3
|
+
from pyrig.dev.utils.packages import find_packages
|
|
4
|
+
from pyrig.src.modules.module import make_init_module, to_path
|
|
5
|
+
from pyrig.src.modules.package import DOCS_DIR_NAME
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_namespace_packages() -> list[str]:
|
|
9
|
+
"""Get all namespace packages."""
|
|
10
|
+
packages = find_packages(depth=None)
|
|
11
|
+
namespace_packages = find_packages(depth=None, include_namespace_packages=True)
|
|
12
|
+
namespace_packages = [
|
|
13
|
+
p for p in namespace_packages if not p.startswith(DOCS_DIR_NAME)
|
|
14
|
+
]
|
|
15
|
+
return list(set(namespace_packages) - set(packages))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def make_init_files() -> None:
|
|
19
|
+
"""Create __init__.py files for all packages and modules.
|
|
20
|
+
|
|
21
|
+
Will not overwrite existing files.
|
|
22
|
+
"""
|
|
23
|
+
any_namespace_packages = get_namespace_packages()
|
|
24
|
+
if any_namespace_packages:
|
|
25
|
+
# make init files for all namespace packages
|
|
26
|
+
for package in any_namespace_packages:
|
|
27
|
+
make_init_module(to_path(package, is_package=True))
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Repository protection and security configuration.
|
|
2
|
+
|
|
3
|
+
This module provides functions to configure secure repository settings and
|
|
4
|
+
branch protection rulesets on GitHub. It implements pyrig's opinionated
|
|
5
|
+
security defaults, including required reviews, status checks, and merge
|
|
6
|
+
restrictions.
|
|
7
|
+
|
|
8
|
+
The protection rules enforce:
|
|
9
|
+
- Required pull request reviews with code owner approval
|
|
10
|
+
- Required status checks (health check workflow must pass)
|
|
11
|
+
- Linear commit history (no merge commits)
|
|
12
|
+
- Signed commits
|
|
13
|
+
- No force pushes or deletions
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> from pyrig.src.git.github.repo.protect import protect_repository
|
|
17
|
+
>>> protect_repository() # Applies all protection rules
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from pyrig.dev.configs.pyproject import PyprojectConfigFile
|
|
23
|
+
from pyrig.dev.utils.git import (
|
|
24
|
+
DEFAULT_BRANCH,
|
|
25
|
+
DEFAULT_RULESET_NAME,
|
|
26
|
+
create_or_update_ruleset,
|
|
27
|
+
get_github_repo_token,
|
|
28
|
+
get_repo,
|
|
29
|
+
get_rules_payload,
|
|
30
|
+
)
|
|
31
|
+
from pyrig.src.git.git import (
|
|
32
|
+
get_repo_owner_and_name_from_git,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def protect_repository() -> None:
|
|
37
|
+
"""Apply all security protections to the repository.
|
|
38
|
+
|
|
39
|
+
Configures both repository-level settings and branch protection
|
|
40
|
+
rulesets. This is the main entry point for securing a repository.
|
|
41
|
+
"""
|
|
42
|
+
set_secure_repo_settings()
|
|
43
|
+
create_or_update_default_branch_ruleset()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def set_secure_repo_settings() -> None:
|
|
47
|
+
"""Configure repository-level settings for security and consistency.
|
|
48
|
+
|
|
49
|
+
Sets the following repository settings:
|
|
50
|
+
- Description from pyproject.toml
|
|
51
|
+
- Default branch to 'main'
|
|
52
|
+
- Delete branches on merge
|
|
53
|
+
- Allow update branch button
|
|
54
|
+
- Disable merge commits (squash and rebase only)
|
|
55
|
+
"""
|
|
56
|
+
owner, repo_name = get_repo_owner_and_name_from_git()
|
|
57
|
+
token = get_github_repo_token()
|
|
58
|
+
repo = get_repo(token, owner, repo_name)
|
|
59
|
+
|
|
60
|
+
toml_description = PyprojectConfigFile.get_project_description()
|
|
61
|
+
|
|
62
|
+
repo.edit(
|
|
63
|
+
name=repo_name,
|
|
64
|
+
description=toml_description,
|
|
65
|
+
default_branch=DEFAULT_BRANCH,
|
|
66
|
+
delete_branch_on_merge=True,
|
|
67
|
+
allow_update_branch=True,
|
|
68
|
+
allow_merge_commit=False,
|
|
69
|
+
allow_rebase_merge=True,
|
|
70
|
+
allow_squash_merge=True,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def create_or_update_default_branch_ruleset() -> None:
|
|
75
|
+
"""Create or update the default branch protection ruleset.
|
|
76
|
+
|
|
77
|
+
Applies pyrig's standard protection rules to the default branch (main).
|
|
78
|
+
If a ruleset with the same name already exists, it is updated.
|
|
79
|
+
"""
|
|
80
|
+
create_or_update_ruleset(
|
|
81
|
+
**get_default_ruleset_params(),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_default_ruleset_params() -> dict[str, Any]:
|
|
86
|
+
"""Build the parameter dictionary for the default branch ruleset.
|
|
87
|
+
|
|
88
|
+
Constructs the complete ruleset configuration including:
|
|
89
|
+
- Branch targeting (default branch only)
|
|
90
|
+
- Bypass permissions for repository admins
|
|
91
|
+
- All protection rules (reviews, status checks, etc.)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
A dictionary of parameters suitable for `create_or_update_ruleset()`.
|
|
95
|
+
"""
|
|
96
|
+
from pyrig.dev.configs.workflows.health_check import ( # noqa: PLC0415
|
|
97
|
+
HealthCheckWorkflow, # avoid circular import
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
owner, repo_name = get_repo_owner_and_name_from_git()
|
|
101
|
+
token = get_github_repo_token()
|
|
102
|
+
|
|
103
|
+
rules = get_rules_payload(
|
|
104
|
+
deletion={},
|
|
105
|
+
non_fast_forward={},
|
|
106
|
+
creation={},
|
|
107
|
+
update={},
|
|
108
|
+
pull_request={
|
|
109
|
+
"required_approving_review_count": 1,
|
|
110
|
+
"dismiss_stale_reviews_on_push": True,
|
|
111
|
+
"require_code_owner_review": True,
|
|
112
|
+
"require_last_push_approval": True,
|
|
113
|
+
"required_review_thread_resolution": True,
|
|
114
|
+
"allowed_merge_methods": ["squash", "rebase"],
|
|
115
|
+
},
|
|
116
|
+
required_linear_history={},
|
|
117
|
+
required_signatures={},
|
|
118
|
+
required_status_checks={
|
|
119
|
+
"strict_required_status_checks_policy": True,
|
|
120
|
+
"do_not_enforce_on_create": False,
|
|
121
|
+
"required_status_checks": [
|
|
122
|
+
{
|
|
123
|
+
"context": HealthCheckWorkflow.get_filename(),
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
"owner": owner,
|
|
131
|
+
"token": token,
|
|
132
|
+
"repo_name": repo_name,
|
|
133
|
+
"ruleset_name": DEFAULT_RULESET_NAME,
|
|
134
|
+
"enforcement": "active",
|
|
135
|
+
"bypass_actors": [
|
|
136
|
+
{
|
|
137
|
+
"actor_id": 5,
|
|
138
|
+
"actor_type": "RepositoryRole",
|
|
139
|
+
"bypass_mode": "always",
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
"target": "branch",
|
|
143
|
+
"conditions": {"ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}},
|
|
144
|
+
"rules": rules,
|
|
145
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Shared commands for the CLI.
|
|
2
|
+
|
|
3
|
+
This module provides shared CLI commands that can be used by multiple
|
|
4
|
+
packages in a multi-package architecture. These commands are automatically
|
|
5
|
+
discovered and added to the CLI by pyrig.
|
|
6
|
+
Example is version command that is available in all packages.
|
|
7
|
+
uv run my-awesome-project version will return my-awesome-project version 0.1.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from importlib.metadata import version as get_version
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
|
|
14
|
+
from pyrig.dev.utils.cli import get_project_name_from_argv
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def version() -> None:
|
|
18
|
+
"""Display the version information."""
|
|
19
|
+
project_name = get_project_name_from_argv()
|
|
20
|
+
typer.echo(f"{project_name} version {get_version(project_name)}")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Subcommands for the CLI.
|
|
2
|
+
|
|
3
|
+
They will be automatically imported and added to the CLI
|
|
4
|
+
IMPORTANT: All funcs in this file will be added as subcommands.
|
|
5
|
+
So best to define the logic elsewhere and just call it here in a wrapper.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def mkroot() -> None:
|
|
10
|
+
"""Creates the root of the project.
|
|
11
|
+
|
|
12
|
+
This inits all ConfigFiles and creates __init__.py files for the src
|
|
13
|
+
and tests package where they are missing. It does not overwrite any
|
|
14
|
+
existing files.
|
|
15
|
+
"""
|
|
16
|
+
from pyrig.dev.cli.commands.create_root import make_project_root # noqa: PLC0415
|
|
17
|
+
|
|
18
|
+
make_project_root()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def mktests() -> None:
|
|
22
|
+
"""Create all test files for the project.
|
|
23
|
+
|
|
24
|
+
This generates test skeletons for all functions and classes in the src
|
|
25
|
+
package. It does not overwrite any existing tests.
|
|
26
|
+
Tests are also automatically generated when missing by running pytest.
|
|
27
|
+
"""
|
|
28
|
+
from pyrig.dev.cli.commands.create_tests import make_test_skeletons # noqa: PLC0415
|
|
29
|
+
|
|
30
|
+
make_test_skeletons()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def mkinits() -> None:
|
|
34
|
+
"""Create all __init__.py files for the project.
|
|
35
|
+
|
|
36
|
+
This creates __init__.py files for all packages and modules
|
|
37
|
+
that are missing them. It does not overwrite any existing files.
|
|
38
|
+
"""
|
|
39
|
+
from pyrig.dev.cli.commands.make_inits import make_init_files # noqa: PLC0415
|
|
40
|
+
|
|
41
|
+
make_init_files()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def init() -> None:
|
|
45
|
+
"""Set up the project.
|
|
46
|
+
|
|
47
|
+
This is the setup command when you created the project from scratch.
|
|
48
|
+
It will init all config files, create the root, create tests, and run
|
|
49
|
+
all pre-commit hooks and tests.
|
|
50
|
+
"""
|
|
51
|
+
from pyrig.dev.cli.commands.init_project import init_project # noqa: PLC0415
|
|
52
|
+
|
|
53
|
+
init_project()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build() -> None:
|
|
57
|
+
"""Build all artifacts.
|
|
58
|
+
|
|
59
|
+
Invokes every subclass of Builder in the builder package.
|
|
60
|
+
"""
|
|
61
|
+
from pyrig.dev.cli.commands.build_artifacts import build_artifacts # noqa: PLC0415
|
|
62
|
+
|
|
63
|
+
build_artifacts()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def protect_repo() -> None:
|
|
67
|
+
"""Protect the repository.
|
|
68
|
+
|
|
69
|
+
This will set secure repo settings and add a branch protection rulesets.
|
|
70
|
+
"""
|
|
71
|
+
from pyrig.dev.cli.commands.protect_repo import protect_repository # noqa: PLC0415
|
|
72
|
+
|
|
73
|
+
protect_repository()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""__init__ module."""
|