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
pyrig/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""__init__ module."""
|
pyrig/dev/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Development-time infrastructure for pyrig projects.
|
|
2
|
+
|
|
3
|
+
This package contains development tools, configuration management, CLI,
|
|
4
|
+
artifact building, and testing infrastructure. These components are used
|
|
5
|
+
during development and CI/CD but are not required at runtime.
|
|
6
|
+
"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""__init__ module."""
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Abstract base classes for artifact builders.
|
|
2
|
+
|
|
3
|
+
This module provides the ``Builder`` and ``PyInstallerBuilder`` abstract
|
|
4
|
+
base classes for creating distributable artifacts. Subclass these to
|
|
5
|
+
define custom build processes for your project.
|
|
6
|
+
|
|
7
|
+
The builder system uses automatic discovery: all non-abstract Builder
|
|
8
|
+
subclasses across packages depending on pyrig are found and invoked
|
|
9
|
+
when running ``pyrig build``.
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
Create a custom builder by subclassing PyInstallerBuilder:
|
|
13
|
+
|
|
14
|
+
class MyAppBuilder(PyInstallerBuilder):
|
|
15
|
+
@classmethod
|
|
16
|
+
def get_additional_resource_pkgs(cls) -> list[ModuleType]:
|
|
17
|
+
return [my_resources_package]
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
ARTIFACTS_DIR_NAME: Default output directory for artifacts ("dist").
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import platform
|
|
24
|
+
import shutil
|
|
25
|
+
import tempfile
|
|
26
|
+
from abc import ABC, abstractmethod
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
import pyrig
|
|
30
|
+
from pyrig import main, resources
|
|
31
|
+
from pyrig.dev import builders
|
|
32
|
+
from pyrig.dev.configs.pyproject import PyprojectConfigFile
|
|
33
|
+
from pyrig.dev.utils.packages import get_src_package
|
|
34
|
+
from pyrig.src.modules.class_ import (
|
|
35
|
+
get_all_nonabst_subcls_from_mod_in_all_deps_depen_on_dep,
|
|
36
|
+
)
|
|
37
|
+
from pyrig.src.modules.module import (
|
|
38
|
+
to_path,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Builder(ABC):
|
|
43
|
+
"""Abstract base class for artifact builders.
|
|
44
|
+
|
|
45
|
+
Subclass this class and implement ``create_artifacts`` to define
|
|
46
|
+
a custom build process. The build is triggered automatically when
|
|
47
|
+
the class is instantiated.
|
|
48
|
+
|
|
49
|
+
Subclasses must implement:
|
|
50
|
+
- ``create_artifacts``: Create artifacts in the provided temp directory
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
ARTIFACTS_DIR_NAME: Output directory name for built artifacts.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
class MyBuilder(Builder):
|
|
57
|
+
@classmethod
|
|
58
|
+
def create_artifacts(cls, temp_artifacts_dir: Path) -> None:
|
|
59
|
+
# Create your artifacts here
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
MyBuilder()
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
ARTIFACTS_DIR_NAME = "dist"
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def create_artifacts(cls, temp_artifacts_dir: Path) -> None:
|
|
71
|
+
"""Create artifacts in the temporary directory.
|
|
72
|
+
|
|
73
|
+
Subclasses must implement this method to define the build process.
|
|
74
|
+
All artifacts should be written to ``temp_artifacts_dir``.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
temp_artifacts_dir: Temporary directory where artifacts should
|
|
78
|
+
be created. Contents will be moved to the final output
|
|
79
|
+
directory after the build completes.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self) -> None:
|
|
83
|
+
"""Initialize the builder and trigger the build process."""
|
|
84
|
+
self.__class__.build()
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def get_artifacts_dir(cls) -> Path:
|
|
88
|
+
"""Get the final output directory for artifacts.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Path to the artifacts directory (default: "dist").
|
|
92
|
+
"""
|
|
93
|
+
return Path(cls.ARTIFACTS_DIR_NAME)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def build(cls) -> None:
|
|
97
|
+
"""Execute the build process.
|
|
98
|
+
|
|
99
|
+
Creates a temporary directory, invokes ``create_artifacts``,
|
|
100
|
+
then moves and renames artifacts to the final output directory
|
|
101
|
+
with platform-specific suffixes.
|
|
102
|
+
"""
|
|
103
|
+
with tempfile.TemporaryDirectory() as temp_build_dir:
|
|
104
|
+
temp_dir_path = Path(temp_build_dir)
|
|
105
|
+
temp_artifacts_dir = cls.get_temp_artifacts_path(temp_dir_path)
|
|
106
|
+
cls.create_artifacts(temp_artifacts_dir)
|
|
107
|
+
artifacts = cls.get_temp_artifacts(temp_artifacts_dir)
|
|
108
|
+
cls.rename_artifacts(artifacts)
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def rename_artifacts(cls, artifacts: list[Path]) -> None:
|
|
112
|
+
"""Move artifacts to output directory with platform-specific names.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
artifacts: List of artifact paths to rename and move.
|
|
116
|
+
"""
|
|
117
|
+
artifacts_dir = cls.get_artifacts_dir()
|
|
118
|
+
artifacts_dir.mkdir(parents=True, exist_ok=True)
|
|
119
|
+
for artifact in artifacts:
|
|
120
|
+
# rename the files with -platform.system()
|
|
121
|
+
new_name = f"{artifact.stem}-{platform.system()}{artifact.suffix}"
|
|
122
|
+
new_path = artifacts_dir / new_name
|
|
123
|
+
shutil.move(str(artifact), str(new_path))
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def get_temp_artifacts(cls, temp_artifacts_dir: Path) -> list[Path]:
|
|
127
|
+
"""Get all artifacts from the temporary build directory.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
temp_artifacts_dir: Path to the temporary artifacts directory.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
List of paths to built artifacts.
|
|
134
|
+
|
|
135
|
+
Raises:
|
|
136
|
+
FileNotFoundError: If no artifacts were created.
|
|
137
|
+
"""
|
|
138
|
+
paths = list(temp_artifacts_dir.glob("*"))
|
|
139
|
+
if not paths:
|
|
140
|
+
msg = f"Expected {temp_artifacts_dir} to contain files"
|
|
141
|
+
raise FileNotFoundError(msg)
|
|
142
|
+
return paths
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def get_artifacts(cls) -> list[Path]:
|
|
146
|
+
"""Get all artifacts from the final output directory.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of paths to built artifacts in the output directory.
|
|
150
|
+
"""
|
|
151
|
+
return list(cls.get_artifacts_dir().glob("*"))
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def get_temp_artifacts_path(cls, temp_dir: Path) -> Path:
|
|
155
|
+
"""Create and return the temporary artifacts subdirectory.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
temp_dir: Parent temporary directory.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Path to the created artifacts subdirectory.
|
|
162
|
+
"""
|
|
163
|
+
path = temp_dir / cls.ARTIFACTS_DIR_NAME
|
|
164
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
return path
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def get_non_abstract_subclasses(cls) -> list[type["Builder"]]:
|
|
169
|
+
"""Discover all non-abstract Builder subclasses.
|
|
170
|
+
|
|
171
|
+
Searches all packages depending on pyrig for Builder subclasses.
|
|
172
|
+
Parent classes are discarded so only leaf implementations are returned.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
List of non-abstract Builder subclass types.
|
|
176
|
+
"""
|
|
177
|
+
return get_all_nonabst_subcls_from_mod_in_all_deps_depen_on_dep(
|
|
178
|
+
cls,
|
|
179
|
+
pyrig,
|
|
180
|
+
builders,
|
|
181
|
+
discard_parents=True,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def init_all_non_abstract_subclasses(cls) -> None:
|
|
186
|
+
"""Instantiate all discovered Builder subclasses to trigger builds."""
|
|
187
|
+
for builder_cls in cls.get_non_abstract_subclasses():
|
|
188
|
+
builder_cls()
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def get_app_name(cls) -> str:
|
|
192
|
+
"""Get the application name from pyproject.toml.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
The project name as defined in pyproject.toml.
|
|
196
|
+
"""
|
|
197
|
+
return PyprojectConfigFile.get_project_name()
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def get_root_path(cls) -> Path:
|
|
201
|
+
"""Get the project root directory path.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Path to the project root (parent of the source package).
|
|
205
|
+
"""
|
|
206
|
+
src_pkg = get_src_package()
|
|
207
|
+
return to_path(src_pkg, is_package=True).resolve().parent
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def get_main_path(cls) -> Path:
|
|
211
|
+
"""Get the absolute path to the main.py entry point.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Path to the main.py file in the source package.
|
|
215
|
+
"""
|
|
216
|
+
return cls.get_src_pkg_path() / cls.get_main_path_from_src_pkg()
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
def get_resources_path(cls) -> Path:
|
|
220
|
+
"""Get the absolute path to the resources directory.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Path to the dev/artifacts/resources directory.
|
|
224
|
+
"""
|
|
225
|
+
return cls.get_src_pkg_path() / cls.get_resources_path_from_src_pkg()
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def get_src_pkg_path(cls) -> Path:
|
|
229
|
+
"""Get the absolute path to the source package.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Path to the source package directory.
|
|
233
|
+
"""
|
|
234
|
+
return cls.get_root_path() / PyprojectConfigFile.get_package_name()
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def get_main_path_from_src_pkg(cls) -> Path:
|
|
238
|
+
"""Get the relative path to main.py from the source package.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Relative path from source package to main.py.
|
|
242
|
+
"""
|
|
243
|
+
return to_path(main, is_package=False).relative_to(
|
|
244
|
+
to_path(pyrig, is_package=True)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def get_resources_path_from_src_pkg(cls) -> Path:
|
|
249
|
+
"""Get the relative path to resources from the source package.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Relative path from source package to resources directory.
|
|
253
|
+
"""
|
|
254
|
+
return to_path(resources, is_package=True).relative_to(
|
|
255
|
+
to_path(pyrig, is_package=True)
|
|
256
|
+
)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""PyInstaller-based artifact builder.
|
|
2
|
+
|
|
3
|
+
This module provides the PyInstallerBuilder class for creating
|
|
4
|
+
standalone executables from pyrig projects using PyInstaller.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
from abc import abstractmethod
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from types import ModuleType
|
|
12
|
+
|
|
13
|
+
from PIL import Image
|
|
14
|
+
from PyInstaller.__main__ import run
|
|
15
|
+
from PyInstaller.utils.hooks import collect_data_files
|
|
16
|
+
|
|
17
|
+
import pyrig
|
|
18
|
+
from pyrig import resources
|
|
19
|
+
from pyrig.dev.builders.base.base import Builder
|
|
20
|
+
from pyrig.src.modules.module import get_same_modules_from_deps_depen_on_dep
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PyInstallerBuilder(Builder):
|
|
24
|
+
"""Abstract builder for creating PyInstaller executables.
|
|
25
|
+
|
|
26
|
+
Subclass this to create standalone executables from your project.
|
|
27
|
+
The builder handles icon conversion, resource bundling, and
|
|
28
|
+
platform-specific configuration.
|
|
29
|
+
|
|
30
|
+
Subclasses must implement:
|
|
31
|
+
- ``get_additional_resource_pkgs``: Return packages containing resources
|
|
32
|
+
|
|
33
|
+
The builder automatically includes:
|
|
34
|
+
- All resources from dev/artifacts/resources directories
|
|
35
|
+
- Resources from all packages depending on pyrig
|
|
36
|
+
- Platform-appropriate icon format (ico/icns/png)
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
class MyAppBuilder(PyInstallerBuilder):
|
|
40
|
+
@classmethod
|
|
41
|
+
def get_additional_resource_pkgs(cls) -> list[ModuleType]:
|
|
42
|
+
return [my_app.resources]
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def create_artifacts(cls, temp_artifacts_dir: Path) -> None:
|
|
47
|
+
"""Build a PyInstaller executable.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
temp_artifacts_dir: Directory where the executable will be created.
|
|
51
|
+
"""
|
|
52
|
+
options = cls.get_pyinstaller_options(temp_artifacts_dir)
|
|
53
|
+
run(options)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def get_additional_resource_pkgs(cls) -> list[ModuleType]:
|
|
58
|
+
"""Return packages containing additional resources to bundle.
|
|
59
|
+
|
|
60
|
+
Override this method to specify packages whose contents should
|
|
61
|
+
be included in the executable. All files in these packages will
|
|
62
|
+
be bundled and accessible at runtime.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
List of module objects representing resource packages.
|
|
66
|
+
|
|
67
|
+
Note:
|
|
68
|
+
The dev/artifacts/resources package and resources from all
|
|
69
|
+
pyrig-dependent packages are included automatically.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def get_default_additional_resource_pkgs(cls) -> list[ModuleType]:
|
|
74
|
+
"""Get resource packages from all pyrig-dependent packages.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of resources modules from packages depending on pyrig.
|
|
78
|
+
"""
|
|
79
|
+
return get_same_modules_from_deps_depen_on_dep(resources, pyrig)
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def get_all_resource_pkgs(cls) -> list[ModuleType]:
|
|
83
|
+
"""Get all resource packages to bundle.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Combined list of default and additional resource packages.
|
|
87
|
+
"""
|
|
88
|
+
return [
|
|
89
|
+
*cls.get_default_additional_resource_pkgs(),
|
|
90
|
+
*cls.get_additional_resource_pkgs(),
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def get_add_datas(cls) -> list[tuple[str, str]]:
|
|
95
|
+
"""Build the --add-data arguments for PyInstaller.
|
|
96
|
+
|
|
97
|
+
Uses PyInstaller's collect_data_files to automatically gather
|
|
98
|
+
all data files from resource packages.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
List of (source_path, destination_path) tuples.
|
|
102
|
+
"""
|
|
103
|
+
add_datas: list[tuple[str, str]] = []
|
|
104
|
+
resources_pkgs = cls.get_all_resource_pkgs()
|
|
105
|
+
for pkg in resources_pkgs:
|
|
106
|
+
# collect_data_files returns list of (source, dest) tuples
|
|
107
|
+
pkg_datas = collect_data_files(pkg.__name__, include_py_files=True)
|
|
108
|
+
add_datas.extend(pkg_datas)
|
|
109
|
+
return add_datas
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def get_pyinstaller_options(cls, temp_artifacts_dir: Path) -> list[str]:
|
|
113
|
+
"""Build the complete PyInstaller command-line options.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
temp_artifacts_dir: Directory for build output.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
List of command-line arguments for PyInstaller.
|
|
120
|
+
"""
|
|
121
|
+
temp_dir = temp_artifacts_dir.parent
|
|
122
|
+
|
|
123
|
+
options = [
|
|
124
|
+
str(cls.get_main_path()),
|
|
125
|
+
"--name",
|
|
126
|
+
cls.get_app_name(),
|
|
127
|
+
"--clean",
|
|
128
|
+
"--noconfirm",
|
|
129
|
+
"--onefile",
|
|
130
|
+
"--noconsole",
|
|
131
|
+
"--workpath",
|
|
132
|
+
str(cls.get_temp_workpath(temp_dir)),
|
|
133
|
+
"--specpath",
|
|
134
|
+
str(cls.get_temp_specpath(temp_dir)),
|
|
135
|
+
"--distpath",
|
|
136
|
+
str(cls.get_temp_distpath(temp_dir)),
|
|
137
|
+
"--icon",
|
|
138
|
+
str(cls.get_app_icon_path(temp_dir)),
|
|
139
|
+
]
|
|
140
|
+
for src, dest in cls.get_add_datas():
|
|
141
|
+
options.extend(["--add-data", f"{src}{os.pathsep}{dest}"])
|
|
142
|
+
return options
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def get_temp_distpath(cls, temp_dir: Path) -> Path:
|
|
146
|
+
"""Get the temporary distribution output path.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
temp_dir: Parent temporary directory.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Path to the dist subdirectory.
|
|
153
|
+
"""
|
|
154
|
+
return cls.get_temp_artifacts_path(temp_dir)
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def get_temp_workpath(cls, temp_dir: Path) -> Path:
|
|
158
|
+
"""Get the temporary work directory for PyInstaller.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
temp_dir: Parent temporary directory.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Path to the workpath subdirectory.
|
|
165
|
+
"""
|
|
166
|
+
path = temp_dir / "workpath"
|
|
167
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
168
|
+
return path
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def get_temp_specpath(cls, temp_dir: Path) -> Path:
|
|
172
|
+
"""Get the temporary spec file directory.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
temp_dir: Parent temporary directory.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Path to the specpath subdirectory.
|
|
179
|
+
"""
|
|
180
|
+
path = temp_dir / "specpath"
|
|
181
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
182
|
+
return path
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def get_app_icon_path(cls, temp_dir: Path) -> Path:
|
|
186
|
+
"""Get the platform-appropriate icon path.
|
|
187
|
+
|
|
188
|
+
Converts the PNG icon to the appropriate format for the
|
|
189
|
+
current platform (ico for Windows, icns for macOS).
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
temp_dir: Directory for the converted icon file.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Path to the converted icon file.
|
|
196
|
+
"""
|
|
197
|
+
if platform.system() == "Windows":
|
|
198
|
+
return cls.convert_png_to_format("ico", temp_dir)
|
|
199
|
+
if platform.system() == "Darwin":
|
|
200
|
+
return cls.convert_png_to_format("icns", temp_dir)
|
|
201
|
+
return cls.convert_png_to_format("png", temp_dir)
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def convert_png_to_format(cls, file_format: str, temp_dir_path: Path) -> Path:
|
|
205
|
+
"""Convert the application icon PNG to another format.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
file_format: Target format (ico, icns, or png).
|
|
209
|
+
temp_dir_path: Directory for the output file.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Path to the converted icon file.
|
|
213
|
+
"""
|
|
214
|
+
output_path = temp_dir_path / f"icon.{file_format}"
|
|
215
|
+
png_path = cls.get_app_icon_png_path()
|
|
216
|
+
img = Image.open(png_path)
|
|
217
|
+
img.save(output_path, format=file_format.upper())
|
|
218
|
+
return output_path
|
|
219
|
+
|
|
220
|
+
@classmethod
|
|
221
|
+
def get_app_icon_png_path(cls) -> Path:
|
|
222
|
+
"""Get the path to the application icon PNG.
|
|
223
|
+
|
|
224
|
+
Override this method to use a custom icon location.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Path to icon.png in the resources directory.
|
|
228
|
+
"""
|
|
229
|
+
return cls.get_resources_path() / "icon.png"
|
pyrig/dev/cli/cli.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""CLI entry point and command registration.
|
|
2
|
+
|
|
3
|
+
This module provides the main CLI entry point for pyrig projects. It uses
|
|
4
|
+
Typer to build a command-line interface and automatically discovers
|
|
5
|
+
subcommands from the project's subcommands module.
|
|
6
|
+
|
|
7
|
+
The CLI supports both pyrig's built-in commands and project-specific
|
|
8
|
+
commands defined in the consuming project's subcommands module.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
$ uv run pyrig init
|
|
12
|
+
$ uv run pyrig create-root
|
|
13
|
+
$ uv run pyrig build
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from importlib import import_module
|
|
17
|
+
|
|
18
|
+
import typer
|
|
19
|
+
|
|
20
|
+
import pyrig
|
|
21
|
+
from pyrig import main as pyrig_main
|
|
22
|
+
from pyrig.dev.cli import shared_subcommands, subcommands
|
|
23
|
+
from pyrig.dev.utils.cli import get_pkg_name_from_argv
|
|
24
|
+
from pyrig.src.modules.function import get_all_functions_from_module
|
|
25
|
+
from pyrig.src.modules.module import (
|
|
26
|
+
get_module_name_replacing_start_module,
|
|
27
|
+
get_same_modules_from_deps_depen_on_dep,
|
|
28
|
+
import_module_from_file,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
app = typer.Typer(no_args_is_help=True)
|
|
32
|
+
"""The main Typer application instance."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def add_subcommands() -> None:
|
|
36
|
+
"""Discover and register all CLI subcommands.
|
|
37
|
+
|
|
38
|
+
Dynamically loads the main module and subcommands module for the
|
|
39
|
+
current project, registering all public functions as CLI commands.
|
|
40
|
+
This enables projects depending on pyrig to define their own commands.
|
|
41
|
+
"""
|
|
42
|
+
# extract project name from sys.argv[0]
|
|
43
|
+
pkg_name = get_pkg_name_from_argv()
|
|
44
|
+
|
|
45
|
+
main_module_name = get_module_name_replacing_start_module(pyrig_main, pkg_name)
|
|
46
|
+
main_module = import_module_from_file(main_module_name)
|
|
47
|
+
app.command()(main_module.main)
|
|
48
|
+
|
|
49
|
+
# replace the first parent with pkg_name
|
|
50
|
+
subcommands_module_name = get_module_name_replacing_start_module(
|
|
51
|
+
subcommands, pkg_name
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
subcommands_module = import_module_from_file(subcommands_module_name)
|
|
55
|
+
|
|
56
|
+
sub_cmds = get_all_functions_from_module(subcommands_module)
|
|
57
|
+
|
|
58
|
+
for sub_cmd in sub_cmds:
|
|
59
|
+
app.command()(sub_cmd)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def add_shared_subcommands() -> None:
|
|
63
|
+
"""Discover and register all shared CLI subcommands.
|
|
64
|
+
|
|
65
|
+
This discovers all packages inheriting from pyrig and loads their
|
|
66
|
+
shared_subcommands modules, registering all public functions as CLI
|
|
67
|
+
commands. This enables cross-package commands that are available
|
|
68
|
+
in all pyrig projects. Example is pyrigs version command that is
|
|
69
|
+
available in all pyrig projects.
|
|
70
|
+
So you can do:
|
|
71
|
+
uv run pyrig version -> pyrig version 0.1.0
|
|
72
|
+
uv run my-awesome-project version -> my-awesome-project version 0.1.0
|
|
73
|
+
"""
|
|
74
|
+
package_name = get_pkg_name_from_argv()
|
|
75
|
+
package = import_module(package_name)
|
|
76
|
+
all_shared_subcommands_modules = get_same_modules_from_deps_depen_on_dep(
|
|
77
|
+
shared_subcommands,
|
|
78
|
+
pyrig,
|
|
79
|
+
until_pkg=package,
|
|
80
|
+
)
|
|
81
|
+
for shared_subcommands_module in all_shared_subcommands_modules:
|
|
82
|
+
sub_cmds = get_all_functions_from_module(shared_subcommands_module)
|
|
83
|
+
for sub_cmd in sub_cmds:
|
|
84
|
+
app.command()(sub_cmd)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main() -> None:
|
|
88
|
+
"""Entry point for the CLI.
|
|
89
|
+
|
|
90
|
+
Registers all subcommands and invokes the Typer application.
|
|
91
|
+
This function is called by the console script entry point.
|
|
92
|
+
"""
|
|
93
|
+
add_subcommands()
|
|
94
|
+
add_shared_subcommands()
|
|
95
|
+
app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""__init__ module."""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Artifact build orchestration.
|
|
2
|
+
|
|
3
|
+
This module provides the main entry point for building all project
|
|
4
|
+
artifacts. It discovers all Builder subclasses and invokes each one.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pyrig.dev.builders.base.base import Builder
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_artifacts() -> None:
|
|
11
|
+
"""Build all artifacts by invoking all registered Builder subclasses.
|
|
12
|
+
|
|
13
|
+
Discovers all non-abstract Builder subclasses across all packages
|
|
14
|
+
depending on pyrig and invokes each one to create its artifacts.
|
|
15
|
+
"""
|
|
16
|
+
Builder.init_all_non_abstract_subclasses()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Project structure creation utilities.
|
|
2
|
+
|
|
3
|
+
This module provides the `create_root` function which generates all
|
|
4
|
+
configuration files and directory structure for a pyrig project.
|
|
5
|
+
It delegates to the ConfigFile system to discover and initialize
|
|
6
|
+
all registered config file types.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pyrig.dev.configs.base.base import ConfigFile
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def make_project_root() -> None:
|
|
13
|
+
"""Create all configuration files and project structure.
|
|
14
|
+
|
|
15
|
+
Discovers all ConfigFile subclasses and initializes each one,
|
|
16
|
+
creating the complete project structure including:
|
|
17
|
+
- pyproject.toml
|
|
18
|
+
- GitHub workflows
|
|
19
|
+
- Pre-commit configuration
|
|
20
|
+
- Ruff/mypy configuration
|
|
21
|
+
- Source and test directory structure
|
|
22
|
+
|
|
23
|
+
This is the implementation for the `pyrig create-root` command.
|
|
24
|
+
"""
|
|
25
|
+
ConfigFile.init_config_files()
|