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,436 @@
|
|
|
1
|
+
"""Configuration management for pyproject.toml.
|
|
2
|
+
|
|
3
|
+
This module provides the PyprojectConfigFile class for managing the
|
|
4
|
+
project's pyproject.toml file. It handles project metadata, dependencies,
|
|
5
|
+
tool configurations (ruff, mypy, pytest, bandit), and build settings.
|
|
6
|
+
|
|
7
|
+
The configuration enforces pyrig's opinionated defaults:
|
|
8
|
+
- All ruff rules enabled (with minimal exceptions)
|
|
9
|
+
- Strict mypy type checking
|
|
10
|
+
- Bandit security scanning
|
|
11
|
+
- uv as the build backend
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from functools import cache
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from subprocess import CompletedProcess # nosec: B404
|
|
18
|
+
from typing import Any, Literal
|
|
19
|
+
|
|
20
|
+
import requests
|
|
21
|
+
from packaging.version import Version
|
|
22
|
+
|
|
23
|
+
from pyrig.dev.cli.commands.init_project import STANDARD_DEV_DEPS
|
|
24
|
+
from pyrig.dev.configs.base.base import TomlConfigFile
|
|
25
|
+
from pyrig.dev.utils.resources import return_resource_content_on_fetch_error
|
|
26
|
+
from pyrig.dev.utils.versions import VersionConstraint, adjust_version_to_level
|
|
27
|
+
from pyrig.src.git.git import get_repo_owner_and_name_from_git
|
|
28
|
+
from pyrig.src.modules.package import (
|
|
29
|
+
get_pkg_name_from_cwd,
|
|
30
|
+
get_pkg_name_from_project_name,
|
|
31
|
+
get_project_name_from_cwd,
|
|
32
|
+
)
|
|
33
|
+
from pyrig.src.os.os import run_subprocess
|
|
34
|
+
from pyrig.src.testing.convention import (
|
|
35
|
+
COVERAGE_THRESHOLD,
|
|
36
|
+
TESTS_PACKAGE_NAME,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PyprojectConfigFile(TomlConfigFile):
|
|
41
|
+
"""Configuration file manager for pyproject.toml.
|
|
42
|
+
|
|
43
|
+
Manages the central project configuration including:
|
|
44
|
+
- Project metadata (name, description, dependencies)
|
|
45
|
+
- Build system configuration (uv)
|
|
46
|
+
- Tool configurations (ruff, mypy, pytest, bandit)
|
|
47
|
+
- CLI entry points
|
|
48
|
+
|
|
49
|
+
The class provides utilities for querying project information
|
|
50
|
+
and managing dependencies.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def dump(cls, config: dict[str, Any] | list[Any]) -> None:
|
|
55
|
+
"""Write configuration to pyproject.toml.
|
|
56
|
+
|
|
57
|
+
Normalizes dependencies before writing.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
config: The configuration dict to write.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
TypeError: If config is not a dict.
|
|
64
|
+
"""
|
|
65
|
+
if not isinstance(config, dict):
|
|
66
|
+
msg = f"Cannot dump {config} to pyproject.toml file."
|
|
67
|
+
raise TypeError(msg)
|
|
68
|
+
# remove the versions from the dependencies
|
|
69
|
+
cls.remove_wrong_dependencies(config)
|
|
70
|
+
super().dump(config)
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def get_parent_path(cls) -> Path:
|
|
74
|
+
"""Get the project root directory.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Path to the project root.
|
|
78
|
+
"""
|
|
79
|
+
return Path()
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def get_configs(cls) -> dict[str, Any]:
|
|
83
|
+
"""Get the expected pyproject.toml configuration.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Complete configuration dict with project metadata,
|
|
87
|
+
dependencies, build system, and tool configurations.
|
|
88
|
+
"""
|
|
89
|
+
from pyrig.dev.cli import ( # noqa: PLC0415
|
|
90
|
+
cli,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
repo_owner, _ = get_repo_owner_and_name_from_git(check_repo_url=False)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
"project": {
|
|
97
|
+
"name": get_project_name_from_cwd(),
|
|
98
|
+
"version": cls.get_project_version(),
|
|
99
|
+
"description": cls.get_project_description(),
|
|
100
|
+
"readme": "README.md",
|
|
101
|
+
"authors": [
|
|
102
|
+
{"name": repo_owner},
|
|
103
|
+
],
|
|
104
|
+
"license-files": ["LICENSE"],
|
|
105
|
+
"requires-python": cls.get_project_requires_python(),
|
|
106
|
+
"classifiers": [
|
|
107
|
+
*cls.make_python_version_classifiers(),
|
|
108
|
+
],
|
|
109
|
+
"scripts": {
|
|
110
|
+
cls.get_project_name(): f"{cli.__name__}:{cli.main.__name__}"
|
|
111
|
+
},
|
|
112
|
+
"dependencies": cls.make_dependency_versions(cls.get_dependencies()),
|
|
113
|
+
},
|
|
114
|
+
"dependency-groups": {
|
|
115
|
+
"dev": cls.make_dependency_versions(
|
|
116
|
+
cls.get_dev_dependencies(),
|
|
117
|
+
additional=cls.get_standard_dev_dependencies(),
|
|
118
|
+
)
|
|
119
|
+
},
|
|
120
|
+
"build-system": {
|
|
121
|
+
"requires": ["uv_build"],
|
|
122
|
+
"build-backend": "uv_build",
|
|
123
|
+
},
|
|
124
|
+
"tool": {
|
|
125
|
+
"uv": {
|
|
126
|
+
"build-backend": {
|
|
127
|
+
"module-name": get_pkg_name_from_cwd(),
|
|
128
|
+
"module-root": "",
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"ruff": {
|
|
132
|
+
"exclude": [".*", "**/migrations/*.py"],
|
|
133
|
+
"lint": {
|
|
134
|
+
"select": ["ALL"],
|
|
135
|
+
"ignore": ["D203", "D213", "COM812", "ANN401"],
|
|
136
|
+
"fixable": ["ALL"],
|
|
137
|
+
"per-file-ignores": {
|
|
138
|
+
f"**/{TESTS_PACKAGE_NAME}/**/*.py": ["S101"],
|
|
139
|
+
},
|
|
140
|
+
"pydocstyle": {"convention": "google"},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
"mypy": {
|
|
144
|
+
"strict": True,
|
|
145
|
+
"warn_unreachable": True,
|
|
146
|
+
"show_error_codes": True,
|
|
147
|
+
"files": ".",
|
|
148
|
+
},
|
|
149
|
+
"pytest": {
|
|
150
|
+
"ini_options": {
|
|
151
|
+
"testpaths": [TESTS_PACKAGE_NAME],
|
|
152
|
+
"addopts": f"--cov={cls.get_package_name()} --cov-report=term-missing --cov-fail-under={COVERAGE_THRESHOLD}", # noqa: E501
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"bandit": {
|
|
156
|
+
"exclude_dirs": [
|
|
157
|
+
".*",
|
|
158
|
+
],
|
|
159
|
+
"assert_used": {
|
|
160
|
+
"skips": [
|
|
161
|
+
f"*/{TESTS_PACKAGE_NAME}/*.py",
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def remove_wrong_dependencies(cls, config: dict[str, Any]) -> None:
|
|
170
|
+
"""Normalize dependencies by removing version specifiers.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
config: The configuration dict to modify in place.
|
|
174
|
+
"""
|
|
175
|
+
# removes the versions from the dependencies
|
|
176
|
+
config["project"]["dependencies"] = cls.make_dependency_versions(
|
|
177
|
+
config["project"]["dependencies"]
|
|
178
|
+
)
|
|
179
|
+
config["dependency-groups"]["dev"] = cls.make_dependency_versions(
|
|
180
|
+
config["dependency-groups"]["dev"]
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def get_project_description(cls) -> str:
|
|
185
|
+
"""Get the project description from pyproject.toml.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
The project description or empty string.
|
|
189
|
+
"""
|
|
190
|
+
return str(cls.load().get("project", {}).get("description", ""))
|
|
191
|
+
|
|
192
|
+
@classmethod
|
|
193
|
+
def get_project_version(cls) -> str:
|
|
194
|
+
"""Get the project version from pyproject.toml.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
The project version or empty string.
|
|
198
|
+
"""
|
|
199
|
+
return str(cls.load().get("project", {}).get("version", ""))
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def make_python_version_classifiers(cls) -> list[str]:
|
|
203
|
+
"""Make the Python version classifiers.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
List of Python version classifiers.
|
|
207
|
+
"""
|
|
208
|
+
versions = cls.get_supported_python_versions()
|
|
209
|
+
return [
|
|
210
|
+
f"Programming Language :: Python :: {v.major}.{v.minor}" for v in versions
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
@classmethod
|
|
214
|
+
def get_project_requires_python(cls, default: str = ">=3.12") -> str:
|
|
215
|
+
"""Get the project's requires-python from pyproject.toml.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
The project's requires-python or empty string.
|
|
219
|
+
"""
|
|
220
|
+
return str(cls.load().get("project", {}).get("requires-python", default))
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def make_dependency_versions(
|
|
224
|
+
cls,
|
|
225
|
+
dependencies: list[str],
|
|
226
|
+
additional: list[str] | None = None,
|
|
227
|
+
) -> list[str]:
|
|
228
|
+
"""Normalize and merge dependency lists.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
dependencies: Primary dependencies to process.
|
|
232
|
+
additional: Additional dependencies to merge.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Sorted, deduplicated list of normalized dependencies.
|
|
236
|
+
"""
|
|
237
|
+
if additional is None:
|
|
238
|
+
additional = []
|
|
239
|
+
# remove all versions from the dependencies to compare them
|
|
240
|
+
stripped_dependencies = {
|
|
241
|
+
cls.remove_version_from_dep(dep) for dep in dependencies
|
|
242
|
+
}
|
|
243
|
+
additional = [
|
|
244
|
+
dep
|
|
245
|
+
for dep in additional
|
|
246
|
+
if cls.remove_version_from_dep(dep) not in stripped_dependencies
|
|
247
|
+
]
|
|
248
|
+
dependencies.extend(additional)
|
|
249
|
+
return sorted(set(dependencies))
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def remove_version_from_dep(cls, dep: str) -> str:
|
|
253
|
+
"""Strip version specifier from a dependency string.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
dep: Dependency string like "requests>=2.0".
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Package name without version (e.g., "requests").
|
|
260
|
+
"""
|
|
261
|
+
return re.split(r"[^a-zA-Z0-9_.-]", dep)[0]
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def get_package_name(cls) -> str:
|
|
265
|
+
"""Get the Python package name (with underscores).
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
The package name derived from the project name.
|
|
269
|
+
"""
|
|
270
|
+
project_name = cls.get_project_name()
|
|
271
|
+
return get_pkg_name_from_project_name(project_name)
|
|
272
|
+
|
|
273
|
+
@classmethod
|
|
274
|
+
def get_project_name(cls) -> str:
|
|
275
|
+
"""Get the project name from pyproject.toml.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
The project name or empty string.
|
|
279
|
+
"""
|
|
280
|
+
return str(cls.load().get("project", {}).get("name", ""))
|
|
281
|
+
|
|
282
|
+
@classmethod
|
|
283
|
+
def get_all_dependencies(cls) -> list[str]:
|
|
284
|
+
"""Get all dependencies (runtime and dev).
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Combined list of all dependencies.
|
|
288
|
+
"""
|
|
289
|
+
all_deps = cls.get_dependencies()
|
|
290
|
+
all_deps.extend(cls.get_dev_dependencies())
|
|
291
|
+
return all_deps
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
def get_standard_dev_dependencies(cls) -> list[str]:
|
|
295
|
+
"""Get pyrig's standard development dependencies.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Sorted list of standard dev dependencies.
|
|
299
|
+
"""
|
|
300
|
+
# sort the dependencies
|
|
301
|
+
return sorted(STANDARD_DEV_DEPS)
|
|
302
|
+
|
|
303
|
+
@classmethod
|
|
304
|
+
def get_dev_dependencies(cls) -> list[str]:
|
|
305
|
+
"""Get development dependencies from pyproject.toml.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
List of dev dependencies.
|
|
309
|
+
"""
|
|
310
|
+
dev_deps: list[str] = cls.load().get("dependency-groups", {}).get("dev", [])
|
|
311
|
+
return dev_deps
|
|
312
|
+
|
|
313
|
+
@classmethod
|
|
314
|
+
def get_dependencies(cls) -> list[str]:
|
|
315
|
+
"""Get runtime dependencies from pyproject.toml.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
List of runtime dependencies.
|
|
319
|
+
"""
|
|
320
|
+
deps: list[str] = cls.load().get("project", {}).get("dependencies", [])
|
|
321
|
+
return deps
|
|
322
|
+
|
|
323
|
+
@classmethod
|
|
324
|
+
@return_resource_content_on_fetch_error(resource_name="LATEST_PYTHON_VERSION")
|
|
325
|
+
@cache
|
|
326
|
+
def fetch_latest_python_version(cls) -> str:
|
|
327
|
+
"""Fetch the latest stable Python version from endoflife.date.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
The latest stable Python version.
|
|
331
|
+
|
|
332
|
+
Raises:
|
|
333
|
+
requests.HTTPError: If the API request fails.
|
|
334
|
+
"""
|
|
335
|
+
url = "https://endoflife.date/api/python.json"
|
|
336
|
+
resp = requests.get(url, timeout=10)
|
|
337
|
+
resp.raise_for_status()
|
|
338
|
+
data: list[dict[str, str]] = resp.json()
|
|
339
|
+
# first element has metadata for latest stable
|
|
340
|
+
return data[0]["latest"]
|
|
341
|
+
|
|
342
|
+
@classmethod
|
|
343
|
+
def get_latest_python_version(
|
|
344
|
+
cls, level: Literal["major", "minor", "micro"] = "minor"
|
|
345
|
+
) -> Version:
|
|
346
|
+
"""Fetch the latest stable Python version from endoflife.date.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
The latest stable Python version as a string.
|
|
350
|
+
|
|
351
|
+
Raises:
|
|
352
|
+
requests.HTTPError: If the API request fails.
|
|
353
|
+
"""
|
|
354
|
+
latest_version = Version(cls.fetch_latest_python_version())
|
|
355
|
+
return adjust_version_to_level(latest_version, level)
|
|
356
|
+
|
|
357
|
+
@classmethod
|
|
358
|
+
def get_latest_possible_python_version(
|
|
359
|
+
cls, level: Literal["major", "minor", "micro"] = "micro"
|
|
360
|
+
) -> Version:
|
|
361
|
+
"""Get the latest Python version allowed by requires-python.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
level: Version precision (major, minor, or micro).
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
The latest allowed Python version.
|
|
368
|
+
"""
|
|
369
|
+
constraint = cls.load()["project"]["requires-python"]
|
|
370
|
+
version_constraint = VersionConstraint(constraint)
|
|
371
|
+
version = version_constraint.get_upper_inclusive()
|
|
372
|
+
if version is None:
|
|
373
|
+
version = cls.get_latest_python_version()
|
|
374
|
+
|
|
375
|
+
return adjust_version_to_level(version, level)
|
|
376
|
+
|
|
377
|
+
@classmethod
|
|
378
|
+
def get_first_supported_python_version(cls) -> Version:
|
|
379
|
+
"""Get the minimum supported Python version.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
The minimum Python version from requires-python.
|
|
383
|
+
|
|
384
|
+
Raises:
|
|
385
|
+
ValueError: If no lower bound is specified.
|
|
386
|
+
"""
|
|
387
|
+
constraint = cls.get_project_requires_python()
|
|
388
|
+
version_constraint = VersionConstraint(constraint)
|
|
389
|
+
lower = version_constraint.get_lower_inclusive()
|
|
390
|
+
if lower is None:
|
|
391
|
+
msg = "Need a lower bound for python version"
|
|
392
|
+
raise ValueError(msg)
|
|
393
|
+
return lower
|
|
394
|
+
|
|
395
|
+
@classmethod
|
|
396
|
+
def get_supported_python_versions(cls) -> list[Version]:
|
|
397
|
+
"""Get all supported Python minor versions.
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
List of supported Python versions (e.g., [3.10, 3.11, 3.12]).
|
|
401
|
+
"""
|
|
402
|
+
constraint = cls.get_project_requires_python()
|
|
403
|
+
version_constraint = VersionConstraint(constraint)
|
|
404
|
+
return version_constraint.get_version_range(
|
|
405
|
+
level="minor", upper_default=cls.get_latest_python_version()
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
@classmethod
|
|
409
|
+
def update_dependencies(cls, *, check: bool = True) -> CompletedProcess[bytes]:
|
|
410
|
+
"""Update dependencies to their latest versions.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
check: Whether to raise on non-zero exit code.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
The completed process result.
|
|
417
|
+
"""
|
|
418
|
+
from pyrig.src.project.mgt import PROJECT_MGT # noqa: PLC0415
|
|
419
|
+
|
|
420
|
+
upgrade_deps = run_subprocess([PROJECT_MGT, "lock", "--upgrade"], check=check)
|
|
421
|
+
_ = cls.install_dependencies(check=check)
|
|
422
|
+
return upgrade_deps
|
|
423
|
+
|
|
424
|
+
@classmethod
|
|
425
|
+
def install_dependencies(cls, *, check: bool = True) -> CompletedProcess[bytes]:
|
|
426
|
+
"""Install project dependencies using uv sync.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
check: Whether to raise on non-zero exit code.
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
The completed process result.
|
|
433
|
+
"""
|
|
434
|
+
from pyrig.src.project.mgt import PROJECT_MGT # noqa: PLC0415
|
|
435
|
+
|
|
436
|
+
return run_subprocess([PROJECT_MGT, "sync"], check=check)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Configuration for the builders package __init__.py.
|
|
2
|
+
|
|
3
|
+
This module provides the BuildersInitConfigFile class for creating
|
|
4
|
+
the dev/artifacts/builders directory structure with an __init__.py file.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
|
|
9
|
+
from pyrig.dev import builders
|
|
10
|
+
from pyrig.dev.configs.base.base import InitConfigFile
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BuildersInitConfigFile(InitConfigFile):
|
|
14
|
+
"""Configuration file manager for builders/__init__.py.
|
|
15
|
+
|
|
16
|
+
Creates the dev/artifacts/builders directory with an __init__.py
|
|
17
|
+
file that mirrors pyrig's builders package structure.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def get_src_module(cls) -> ModuleType:
|
|
22
|
+
"""Get the source module to mirror.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The pyrig.dev.builders module.
|
|
26
|
+
"""
|
|
27
|
+
return builders
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Configuration for the configs package __init__.py.
|
|
2
|
+
|
|
3
|
+
This module provides the ConfigsInitConfigFile class for creating
|
|
4
|
+
the dev/configs directory structure with an __init__.py file.
|
|
5
|
+
All ConfigFile subclasses in this package are automatically discovered.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from types import ModuleType
|
|
9
|
+
|
|
10
|
+
from pyrig.dev import configs
|
|
11
|
+
from pyrig.dev.configs.base.base import InitConfigFile
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConfigsInitConfigFile(InitConfigFile):
|
|
15
|
+
"""Configuration file manager for configs/__init__.py.
|
|
16
|
+
|
|
17
|
+
Creates the dev/configs directory with an __init__.py file
|
|
18
|
+
that mirrors pyrig's configs package structure.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def get_src_module(cls) -> ModuleType:
|
|
23
|
+
"""Get the source module to mirror.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The pyrig.dev.configs module.
|
|
27
|
+
"""
|
|
28
|
+
return configs
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Configuration for the .experiment.py scratch file.
|
|
2
|
+
|
|
3
|
+
This module provides the DotExperimentConfigFile class for creating
|
|
4
|
+
a .experiment.py file at the project root for local experimentation.
|
|
5
|
+
This file is automatically added to .gitignore.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from pyrig.dev.configs.base.base import PythonConfigFile
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DotExperimentConfigFile(PythonConfigFile):
|
|
14
|
+
"""Configuration file manager for .experiment.py.
|
|
15
|
+
|
|
16
|
+
Creates a scratch Python file at the project root for local
|
|
17
|
+
experimentation. This file is excluded from version control.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def get_filename(cls) -> str:
|
|
22
|
+
"""Get the experiment filename.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The string ".experiment".
|
|
26
|
+
"""
|
|
27
|
+
return ".experiment"
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def get_parent_path(cls) -> Path:
|
|
31
|
+
"""Get the project root directory.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Path to the project root.
|
|
35
|
+
"""
|
|
36
|
+
return Path()
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def get_content_str(cls) -> str:
|
|
40
|
+
"""Get the experiment file content.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
A minimal Python file with a docstring.
|
|
44
|
+
"""
|
|
45
|
+
return '''"""This file is for experimentation and is ignored by git."""
|
|
46
|
+
'''
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Configuration for the main.py entry point.
|
|
2
|
+
|
|
3
|
+
This module provides the MainConfigFile class for creating the
|
|
4
|
+
main.py file in the package's src directory. This file serves
|
|
5
|
+
as the CLI entry point.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from types import ModuleType
|
|
10
|
+
|
|
11
|
+
from pyrig import main
|
|
12
|
+
from pyrig.dev.configs.base.base import CopyModuleConfigFile
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MainConfigFile(CopyModuleConfigFile):
|
|
16
|
+
"""Configuration file manager for main.py.
|
|
17
|
+
|
|
18
|
+
Creates a main.py in pkg_name/src that serves as the CLI entry point.
|
|
19
|
+
Also cleans up any root-level main.py files.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
"""Initialize and clean up any root-level main.py."""
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.__class__.delete_root_main()
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def get_src_module(cls) -> ModuleType:
|
|
29
|
+
"""Get the source module to copy.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
The pyrig.main module.
|
|
33
|
+
"""
|
|
34
|
+
return main
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def is_correct(cls) -> bool:
|
|
38
|
+
"""Check if the main.py file is valid.
|
|
39
|
+
|
|
40
|
+
Allows modifications as long as the file contains a main function
|
|
41
|
+
and the standard __name__ == '__main__' guard.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
True if the file has required structure.
|
|
45
|
+
"""
|
|
46
|
+
return super().is_correct() or (
|
|
47
|
+
"def main" in cls.get_file_content()
|
|
48
|
+
and 'if __name__ == "__main__":' in cls.get_file_content()
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def delete_root_main(cls) -> None:
|
|
53
|
+
"""Delete any root-level main.py file.
|
|
54
|
+
|
|
55
|
+
Cleans up legacy main.py files that should be in src/.
|
|
56
|
+
"""
|
|
57
|
+
root_main_path = Path("main.py")
|
|
58
|
+
if root_main_path.exists():
|
|
59
|
+
root_main_path.unlink()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Configuration for the resources package __init__.py.
|
|
2
|
+
|
|
3
|
+
This module provides the ResourcesInitConfigFile class for creating
|
|
4
|
+
the dev/artifacts/resources directory structure with an __init__.py file.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
|
|
9
|
+
from pyrig import resources
|
|
10
|
+
from pyrig.dev.configs.base.base import InitConfigFile
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ResourcesInitConfigFile(InitConfigFile):
|
|
14
|
+
"""Configuration file manager for resources/__init__.py.
|
|
15
|
+
|
|
16
|
+
Creates the dev/artifacts/resources directory with an __init__.py
|
|
17
|
+
file that mirrors pyrig's resources package structure.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def get_src_module(cls) -> ModuleType:
|
|
22
|
+
"""Get the source module to mirror.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The pyrig.dev.artifacts.resources module.
|
|
26
|
+
"""
|
|
27
|
+
return resources
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Shared subcommands ConfigFile.
|
|
2
|
+
|
|
3
|
+
This module provides the SharedSubcommandsConfigFile class for creating
|
|
4
|
+
a shared_subcommands.py file where users can define custom CLI subcommands
|
|
5
|
+
that are available in all pyrig projects.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from types import ModuleType
|
|
9
|
+
|
|
10
|
+
from pyrig.dev.cli import shared_subcommands
|
|
11
|
+
from pyrig.dev.configs.base.base import CopyModuleOnlyDocstringConfigFile
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SharedSubcommandsConfigFile(CopyModuleOnlyDocstringConfigFile):
|
|
15
|
+
"""Configuration file manager for shared_subcommands.py.
|
|
16
|
+
|
|
17
|
+
Creates a shared_subcommands.py file with only the docstring from pyrig's
|
|
18
|
+
shared_subcommands module, allowing users to add custom CLI subcommands
|
|
19
|
+
that are available in all pyrig projects.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def get_src_module(cls) -> ModuleType:
|
|
24
|
+
"""Get the source module to copy docstring from.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The pyrig.dev.cli.shared_subcommands module.
|
|
28
|
+
"""
|
|
29
|
+
return shared_subcommands
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Configuration for the src package __init__.py.
|
|
2
|
+
|
|
3
|
+
This module provides the SrcInitConfigFile class for creating
|
|
4
|
+
the src directory structure with an __init__.py file.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
|
|
9
|
+
from pyrig import src
|
|
10
|
+
from pyrig.dev.configs.base.base import InitConfigFile
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SrcInitConfigFile(InitConfigFile):
|
|
14
|
+
"""Configuration file manager for src/__init__.py.
|
|
15
|
+
|
|
16
|
+
Creates the src directory with an __init__.py file that
|
|
17
|
+
mirrors pyrig's src package structure.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def get_src_module(cls) -> ModuleType:
|
|
22
|
+
"""Get the source module to mirror.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The pyrig.src module.
|
|
26
|
+
"""
|
|
27
|
+
return src
|