pyrig 1.1.22__tar.gz → 2.0.30__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 (110) hide show
  1. pyrig-2.0.30/PKG-INFO +856 -0
  2. pyrig-2.0.30/README.md +837 -0
  3. pyrig-2.0.30/pyproject.toml +103 -0
  4. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/artifacts/builder/base/base.py +32 -8
  5. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/base/base.py +101 -16
  6. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/git/gitignore.py +8 -4
  7. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/git/pre_commit.py +8 -25
  8. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/pyproject.py +106 -124
  9. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/python/builder.py +2 -2
  10. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/python/configs.py +2 -2
  11. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/python/experiment.py +6 -1
  12. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/python/main.py +13 -0
  13. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/python/resources_init.py +2 -2
  14. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/python/src_init.py +2 -2
  15. pyrig-2.0.30/pyrig/dev/configs/python/subcommands.py +15 -0
  16. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/readme.py +6 -0
  17. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/testing/conftest.py +2 -1
  18. pyrig-2.0.30/pyrig/dev/configs/testing/fixtures/fixture.py +15 -0
  19. pyrig-2.0.30/pyrig/dev/configs/testing/fixtures/scopes/class_.py +15 -0
  20. pyrig-2.0.30/pyrig/dev/configs/testing/fixtures/scopes/function.py +15 -0
  21. pyrig-2.0.30/pyrig/dev/configs/testing/fixtures/scopes/module.py +15 -0
  22. pyrig-2.0.30/pyrig/dev/configs/testing/fixtures/scopes/package.py +15 -0
  23. pyrig-2.0.30/pyrig/dev/configs/testing/fixtures/scopes/session.py +15 -0
  24. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/testing/main_test.py +1 -1
  25. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/testing/zero_test.py +2 -2
  26. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/workflows/base/base.py +45 -57
  27. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/workflows/health_check.py +33 -1
  28. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/workflows/publish.py +3 -3
  29. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/workflows/release.py +15 -13
  30. pyrig-2.0.30/pyrig/dev/tests/conftest.py +33 -0
  31. {pyrig-1.1.22/pyrig/dev/tests/base → pyrig-2.0.30/pyrig/dev/tests}/fixtures/fixture.py +4 -3
  32. {pyrig-1.1.22/pyrig/dev/tests/base → pyrig-2.0.30/pyrig/dev/tests}/fixtures/scopes/class_.py +6 -5
  33. pyrig-2.0.30/pyrig/dev/tests/fixtures/scopes/function.py +8 -0
  34. {pyrig-1.1.22/pyrig/dev/tests/base → pyrig-2.0.30/pyrig/dev/tests}/fixtures/scopes/module.py +6 -5
  35. pyrig-2.0.30/pyrig/dev/tests/fixtures/scopes/package.py +8 -0
  36. pyrig-2.0.30/pyrig/dev/tests/fixtures/scopes/session.py +388 -0
  37. pyrig-1.1.22/pyrig/src/testing/fixtures.py → pyrig-2.0.30/pyrig/dev/tests/utils/decorators.py +15 -0
  38. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/main.py +1 -1
  39. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/git/github/github.py +18 -0
  40. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/git/github/repo/protect.py +9 -7
  41. pyrig-2.0.30/pyrig/src/graph.py +91 -0
  42. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/iterate.py +22 -1
  43. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/modules/class_.py +83 -31
  44. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/modules/module.py +27 -0
  45. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/modules/package.py +6 -6
  46. pyrig-2.0.30/pyrig/src/project/init.py +55 -0
  47. pyrig-1.1.22/pyrig/src/project/poetry/poetry.py → pyrig-2.0.30/pyrig/src/project/mgt.py +16 -16
  48. {pyrig-1.1.22/pyrig/src/project/poetry → pyrig-2.0.30/pyrig/src/project}/versions.py +11 -0
  49. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/testing/create_tests.py +0 -21
  50. pyrig-1.1.22/PKG-INFO +0 -989
  51. pyrig-1.1.22/README.md +0 -963
  52. pyrig-1.1.22/pyproject.toml +0 -80
  53. pyrig-1.1.22/pyrig/dev/configs/python/subcommands.py +0 -23
  54. pyrig-1.1.22/pyrig/dev/tests/base/fixtures/scopes/function.py +0 -7
  55. pyrig-1.1.22/pyrig/dev/tests/base/fixtures/scopes/package.py +0 -7
  56. pyrig-1.1.22/pyrig/dev/tests/base/fixtures/scopes/session.py +0 -271
  57. pyrig-1.1.22/pyrig/dev/tests/base/utils/utils.py +0 -1
  58. pyrig-1.1.22/pyrig/dev/tests/conftest.py +0 -39
  59. pyrig-1.1.22/pyrig/src/project/init.py +0 -37
  60. pyrig-1.1.22/pyrig/src/project/poetry/dev_deps.py +0 -23
  61. pyrig-1.1.22/pyrig/src/testing/skip.py +0 -19
  62. {pyrig-1.1.22 → pyrig-2.0.30}/LICENSE +0 -0
  63. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/__init__.py +0 -0
  64. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/__init__.py +0 -0
  65. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/artifacts/__init__.py +0 -0
  66. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/artifacts/build.py +0 -0
  67. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/artifacts/builder/__init__.py +0 -0
  68. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/artifacts/builder/base/__init__.py +0 -0
  69. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/artifacts/builder/builder.py +0 -0
  70. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/artifacts/resources/__init__.py +0 -0
  71. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/cli/__init__.py +0 -0
  72. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/cli/cli.py +0 -0
  73. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/cli/subcommands.py +0 -0
  74. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/__init__.py +0 -0
  75. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/base/__init__.py +0 -0
  76. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/configs.py +0 -0
  77. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/dot_env.py +0 -0
  78. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/dot_python_version.py +0 -0
  79. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/git/__init__.py +0 -0
  80. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/licence.py +0 -0
  81. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/py_typed.py +0 -0
  82. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/python/__init__.py +0 -0
  83. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/dev/configs/testing/__init__.py +0 -0
  84. {pyrig-1.1.22/pyrig/dev/configs/workflows → pyrig-2.0.30/pyrig/dev/configs/testing/fixtures}/__init__.py +0 -0
  85. {pyrig-1.1.22/pyrig/dev/configs/workflows/base → pyrig-2.0.30/pyrig/dev/configs/testing/fixtures/scopes}/__init__.py +0 -0
  86. {pyrig-1.1.22/pyrig/dev/tests → pyrig-2.0.30/pyrig/dev/configs/workflows}/__init__.py +0 -0
  87. {pyrig-1.1.22/pyrig/dev/tests → pyrig-2.0.30/pyrig/dev/configs/workflows}/base/__init__.py +0 -0
  88. {pyrig-1.1.22/pyrig/dev/tests/base/fixtures → pyrig-2.0.30/pyrig/dev/tests}/__init__.py +0 -0
  89. {pyrig-1.1.22/pyrig/dev/tests/base/fixtures/scopes → pyrig-2.0.30/pyrig/dev/tests/fixtures}/__init__.py +0 -0
  90. {pyrig-1.1.22/pyrig/dev/tests/base/utils → pyrig-2.0.30/pyrig/dev/tests/fixtures/scopes}/__init__.py +0 -0
  91. {pyrig-1.1.22/pyrig/src/git → pyrig-2.0.30/pyrig/dev/tests/utils}/__init__.py +0 -0
  92. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/py.typed +0 -0
  93. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/__init__.py +0 -0
  94. {pyrig-1.1.22/pyrig/src/git/github → pyrig-2.0.30/pyrig/src/git}/__init__.py +0 -0
  95. {pyrig-1.1.22/pyrig/src/git/github/repo → pyrig-2.0.30/pyrig/src/git/github}/__init__.py +0 -0
  96. {pyrig-1.1.22/pyrig/src/modules → pyrig-2.0.30/pyrig/src/git/github/repo}/__init__.py +0 -0
  97. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/git/github/repo/repo.py +0 -0
  98. {pyrig-1.1.22/pyrig/src/os → pyrig-2.0.30/pyrig/src/modules}/__init__.py +0 -0
  99. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/modules/function.py +0 -0
  100. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/modules/inspection.py +0 -0
  101. {pyrig-1.1.22/pyrig/src/project → pyrig-2.0.30/pyrig/src/os}/__init__.py +0 -0
  102. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/os/os.py +0 -0
  103. {pyrig-1.1.22/pyrig/src/project/poetry → pyrig-2.0.30/pyrig/src/project}/__init__.py +0 -0
  104. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/project/create_root.py +0 -0
  105. {pyrig-1.1.22/pyrig/dev/artifacts/resources → pyrig-2.0.30/pyrig/src}/resource.py +0 -0
  106. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/string.py +0 -0
  107. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/testing/__init__.py +0 -0
  108. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/testing/assertions.py +0 -0
  109. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/testing/convention.py +0 -0
  110. {pyrig-1.1.22 → pyrig-2.0.30}/pyrig/src/testing/utils.py +0 -0
pyrig-2.0.30/PKG-INFO ADDED
@@ -0,0 +1,856 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyrig
3
+ Version: 2.0.30
4
+ Summary: A dev kit that standardizes configurations and testing
5
+ Author: Winipedia
6
+ Author-email: Winipedia <win.steveker@gmx.de>
7
+ License-File: LICENSE
8
+ Requires-Dist: dotenv
9
+ Requires-Dist: packaging
10
+ Requires-Dist: pathspec
11
+ Requires-Dist: pillow
12
+ Requires-Dist: pygithub
13
+ Requires-Dist: pyyaml
14
+ Requires-Dist: setuptools
15
+ Requires-Dist: tomlkit
16
+ Requires-Dist: typer
17
+ Requires-Python: >=3.12
18
+ Description-Content-Type: text/markdown
19
+
20
+ # pyrig
21
+
22
+ **pyrig** is a Python development toolkit that helps you **rig up** your Python projects by standardizing project configurations and automating testing workflows. It eliminates boilerplate setup work by providing opinionated, best-practice configurations for linting, type checking, testing, and CI/CD—allowing you to focus on writing code instead of configuring tools.
23
+
24
+ Built for Python 3.12+ projects using uv and GitHub, pyrig automatically generates project structure, creates test skeletons that mirror your source code, and maintains configuration files for tools like ruff, mypy, pytest, and pre-commit hooks.
25
+
26
+ ---
27
+
28
+ ## Table of Contents
29
+
30
+ - [Features](#features)
31
+ - [Requirements](#requirements)
32
+ - [Installation](#installation)
33
+ - [Quick Start](#quick-start)
34
+ - [Initialization](#initialization)
35
+ - [Configuration Files](#configuration-files)
36
+ - [CLI Commands](#cli-commands)
37
+ - [Repository Protection](#repository-protection)
38
+ - [Testing](#testing)
39
+ - [Building Artifacts](#building-artifacts)
40
+ - [Examples](#examples)
41
+ - [Troubleshooting](#troubleshooting)
42
+ - [Contributing](#contributing)
43
+ - [License](#license)
44
+
45
+ ---
46
+
47
+ ## Features
48
+
49
+ - **Zero-Configuration Setup**: Opinionated, best-practice configurations for Python development tools
50
+ - **ConfigFile Machinery**: Automated system for discovering, creating, validating, and updating all configuration files
51
+ - **Automatic Test Generation**: Creates test skeletons that mirror your source code structure
52
+ - **Intelligent Fixture System**: Automatic discovery and loading of pytest fixtures across all packages
53
+ - **Strict Type Checking**: Enforces mypy strict mode with comprehensive type coverage
54
+ - **Code Quality Tools**: Pre-configured ruff (linting + formatting), mypy, bandit (security)
55
+ - **CI/CD Workflows**: GitHub Actions workflows for health checks, releases, and publishing
56
+ - **Repository Protection**: Automated GitHub branch protection and security settings
57
+ - **Dependency Management**: Automatic dependency updates with uv
58
+ - **Pre-commit Hooks**: Automated code quality checks before every commit with automatic installation
59
+ - **Artifact Building**: Extensible build system with PyInstaller support
60
+ - **Custom CLI**: Automatically generates CLI commands from your functions
61
+ - **Cross-Platform Testing**: Matrix testing across multiple OS and Python versions
62
+ - **Multi-Package Architecture**: Automatic discovery of configs, builders, fixtures, and resources across all packages depending on pyrig
63
+
64
+ ---
65
+
66
+ ## Requirements
67
+
68
+ - **Python**: 3.12 or higher
69
+ - **uv**: Package and dependency manager
70
+ - **Git**: Version control
71
+ - **GitHub**: For full CI/CD and repository protection features (optional but recommended)
72
+
73
+ ---
74
+
75
+ ## Installation
76
+
77
+ ### From PyPI
78
+
79
+ ```bash
80
+ pip install pyrig
81
+ # or
82
+ uv add pyrig
83
+ ```
84
+
85
+ **Note**: pyrig should be added as a regular dependency, not a dev dependency, because the CLI and utility functions require runtime availability. While pyrig manages dev dependencies for tools like ruff, mypy, and pytest, it keeps itself as a regular dependency to ensure full functionality in all environments.
86
+
87
+ ### From Source
88
+
89
+ ```bash
90
+ git clone https://github.com/winipedia/pyrig.git
91
+ cd pyrig
92
+ uv sync
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Quick Start
98
+
99
+ ```bash
100
+ # Create a new GitHub repository
101
+ # add REPO_TOKEN and PYPI_TOKEN to secrets as needed (more info below)
102
+ # Clone it locally
103
+ git clone https://github.com/your-username/your-project.git
104
+ cd your-project
105
+
106
+ # Initialize uv project
107
+ uv init --python 3.12 # 3.12 is the minimum supported version
108
+
109
+ # Add pyrig
110
+ uv add pyrig
111
+
112
+ # Initialize pyrig (creates all config files, tests, and runs setup)
113
+ uv run pyrig init
114
+
115
+ # Commit and push
116
+ git add .
117
+ git commit -m "chore: init project with pyrig"
118
+ git push
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Initialization
124
+
125
+ ### Prerequisites
126
+
127
+ 1. **Create a GitHub repository** for your project (e.g., `your-project`)
128
+
129
+ 2. **Configure GitHub Secrets**
130
+ - `REPO_TOKEN`: GitHub Fine-Grained Personal Access Token with permissions:
131
+ - `contents:read and write` (needed to commit after release)
132
+ - `administration:read and write` (needed to protect the repo)
133
+ - `PYPI_TOKEN`: PyPI token for your project (only needed if publishing to PyPI)
134
+
135
+ ### Setup Steps
136
+
137
+ 1. **Clone the repository**
138
+ ```bash
139
+ git clone https://github.com/your-username/your-project.git
140
+ cd your-project
141
+ ```
142
+
143
+ 2. **Install uv** (if not already installed)
144
+ ```bash
145
+ curl -LsSf https://astral.sh/uv/install.sh | sh
146
+ ```
147
+
148
+ 3. **Initialize uv project**
149
+ ```bash
150
+ uv init --python 3.12
151
+ ```
152
+
153
+ 4. **Add pyrig as a dependency**
154
+ ```bash
155
+ uv add pyrig
156
+ ```
157
+
158
+ 5. **Run pyrig initialization**
159
+ ```bash
160
+ uv run pyrig init
161
+ ```
162
+
163
+ **Note**: This will delete the root-level `main.py` created by `uv init` and replace it with a properly structured `your_project/main.py` inside the package.
164
+
165
+ 6. **Commit and push changes**
166
+ ```bash
167
+ git add .
168
+ git commit -m "chore: init project with pyrig"
169
+ git push
170
+ ```
171
+
172
+ ---
173
+
174
+ ## ConfigFile Machinery
175
+
176
+ The ConfigFile Machinery is pyrig's automated system for managing all configuration files. All configuration files are subclasses of `pyrig.dev.configs.base.base.ConfigFile` and are automatically discovered from `pkg/dev/configs/**` directories across all packages depending on pyrig.
177
+
178
+ **Key Features**:
179
+
180
+ - **Automatic Discovery**: Scans all `dev/configs/` directories across all packages depending on pyrig
181
+ - **Automatic Initialization**: Creates missing config files based on their class definitions
182
+ - **Automatic Validation**: Checks existing config files against their expected content
183
+ - **Automatic Updates**: Updates config files when their definitions change
184
+ - **Intelligent Subclass Discovery**: Only executes the most specific (leaf) implementations. If you subclass an existing config, only your custom subclass will be executed, preventing duplicates and ensuring your customizations take precedence.
185
+
186
+ **Note**: To prevent pyrig from managing a specific config file, make the file empty (the file must exist).
187
+
188
+ ### Extending Configuration Files
189
+
190
+ The ConfigFile Machinery uses a **subset validation algorithm** that allows you to extend configuration files with custom settings while maintaining pyrig's required settings.
191
+
192
+ **Subset Validation Rules**:
193
+
194
+ - **Dictionaries**: All required keys must exist, but you can add additional keys
195
+ - **Lists**: All required items must exist (order doesn't matter), but you can add additional items
196
+ - **Values**: Required values must match exactly (unless they are nested structures)
197
+
198
+ **Example**:
199
+
200
+ If pyrig requires:
201
+ ```toml
202
+ [tool.mypy]
203
+ strict = true
204
+ warn_redundant_casts = true
205
+ ```
206
+
207
+ You can extend it with:
208
+ ```toml
209
+ [tool.mypy]
210
+ strict = true
211
+ warn_redundant_casts = true
212
+ exclude = ["tests/fixtures/"] # Your custom setting
213
+ plugins = ["pydantic.mypy"] # Your custom setting
214
+ ```
215
+
216
+ **Recommended Approach**: Subclass existing configs (e.g., `PyprojectConfigFile`) in your own `dev/configs/python/pyproject.py` file. When you subclass an existing config, only your subclass executes, ensuring your customizations take full control.
217
+
218
+ ### ConfigFile Types
219
+
220
+ - **`ConfigFile`**: Base class for all config files
221
+ - **`CopyModuleConfigFile`**: Copies entire module content to a config file
222
+ - **`CopyModuleOnlyDocstringConfigFile`**: Copies only the docstring from a module
223
+ - **`PythonConfigFile`**: For Python source files
224
+ - **`YamlConfigFile`**: For YAML configuration files
225
+ - **`TomlConfigFile`**: For TOML configuration files
226
+
227
+ ### Configuration Files Managed by the Machinery
228
+
229
+ The ConfigFile Machinery automatically manages the following configuration files:
230
+
231
+ #### `pyproject.toml`
232
+
233
+ Stores project metadata and dependencies. Automatically adds essential dev dependencies (ruff, mypy, pytest, pre-commit) with strict settings.
234
+
235
+ - **Automatic Version Stripping**: By default, pyrig strips version constraints from all dependencies (e.g., `requests>=2.0` becomes `requests`). This ensures you always get the latest compatible versions when running `uv lock --upgrade`.
236
+ - **When Updates Happen**: Dependencies are upgraded during `pyrig init` and via the `assert_dependencies_are_up_to_date` autouse session fixture (runs `uv sync`, then `uv lock --upgrade`).
237
+ - **Disabling Version Stripping**: To keep specific version constraints, subclass `PyprojectConfigFile` and override `should_remove_version_from_dep()` to return `False`:
238
+ ```python
239
+ # your_project/dev/configs/pyproject.py
240
+ from pyrig.dev.configs.pyproject import PyprojectConfigFile as BasePyprojectConfigFile
241
+
242
+ class PyprojectConfigFile(BasePyprojectConfigFile):
243
+ @classmethod
244
+ def should_remove_version_from_dep(cls) -> bool:
245
+ return False
246
+ ```
247
+ - Enforces that GitHub repo name and cwd name are equal; hyphens in repo names are converted to underscores in package names
248
+
249
+ #### `pkg/py.typed`
250
+
251
+ Indicates that the package supports type checking.
252
+
253
+ #### `README.md`
254
+
255
+ Must start with `# <project_name>`. The rest of the content is up to you.
256
+
257
+ #### `LICENCE`
258
+
259
+ Empty file for you to add your own license.
260
+
261
+ #### `.experiment.py`
262
+
263
+ Empty file for experimentation. Git-ignored, not for production code.
264
+
265
+ #### `.python-version`
266
+
267
+ Set to the lowest supported Python version. Used by pyenv.
268
+
269
+ #### `.pre-commit-config.yaml`
270
+
271
+ Configured with the following hooks:
272
+
273
+ - `lint-code`: ruff check --fix
274
+ - `format-code`: ruff format
275
+ - `check-static-types`: mypy --exclude-gitignore
276
+ - `check-security`: bandit -c pyproject.toml -r .
277
+
278
+ Automatically installed during test session via autouse fixture.
279
+
280
+ **Note**: Heavy operations like `install-dependencies` and `create-root` run as autouse session fixtures during test execution, not as pre-commit hooks, to keep commits fast.
281
+
282
+ #### `.gitignore`
283
+
284
+ Pulls the latest [github/python.gitignore](https://github.com/github/gitignore/blob/main/Python.gitignore) and adds project-specific ignores.
285
+
286
+ #### `.env`
287
+
288
+ Empty file for environment variables. Git-ignored, used by python-dotenv.
289
+
290
+ #### `.github/workflows/health_check.yaml`
291
+
292
+ - **Triggers**: workflow_dispatch, pull_request, push to main, schedule (daily with staggered cron)
293
+ - **Purpose**: Matrix testing across different OS and Python versions
294
+ - **Staggered Cron Schedule**: To avoid test failures when multiple packages release on the same schedule, the cron time is staggered based on the package's position in the dependency chain. Packages with fewer dependencies on pyrig run earlier (starting at 1 AM UTC), with each additional dependency level adding 1 hour. This ensures that when dependencies release, dependent packages have time to pick up the new versions before their health checks run.
295
+
296
+ #### `.github/workflows/release.yaml`
297
+
298
+ - **Triggers**: workflow_dispatch, workflow_run (when health_check completes successfully on main)
299
+ - **Process**: Creates tag and changelog, creates GitHub release, builds and uploads artifacts, commits updates
300
+ - **Synchronization**: Keeps tags, package version, and PyPI in sync
301
+ - **Note**: Only runs after health check passes on main branch, so health check steps are not duplicated
302
+
303
+ #### `.github/workflows/publish.yaml`
304
+
305
+ - **Trigger**: Successful completion of release workflow
306
+ - **Purpose**: Publishes the package to PyPI
307
+ - **Note**: Empty the file to disable PyPI publishing
308
+
309
+ #### `pkg/main.py`
310
+
311
+ Main entry point for your application. Used by PyInstaller for building executables.
312
+
313
+ - **Usage**: `uv run your-pkg-name main` or `python -m your-pkg-name`
314
+
315
+ #### `pkg/src/`
316
+
317
+ Subfolder for organizing your source code, separating it from development infrastructure (`dev/`). Automatically created with an `__init__.py` file.
318
+
319
+ #### `pkg/dev/cli/subcommands.py`
320
+
321
+ Define custom CLI subcommands. Any function in this file is automatically added as a subcommand.
322
+
323
+ **Example**:
324
+ ```python
325
+ def hello(name: str = "World") -> None:
326
+ """Say hello to someone."""
327
+ print(f"Hello, {name}!")
328
+
329
+ # Run with: uv run your-pkg-name hello --name Alice
330
+ ```
331
+
332
+ #### `pkg/dev/configs/configs.py`
333
+
334
+ Define custom configuration files. Any subclass of `ConfigFile` is automatically discovered and initialized. Configs can be defined in any file in the `pkg/dev/configs` folder.
335
+
336
+ **Subclass Behavior**: If you subclass an existing `ConfigFile`, only your most specific subclass will be executed, preventing duplicates.
337
+
338
+ #### `pkg/dev/artifacts/builder/builder.py`
339
+
340
+ Define build scripts. Any subclass of `Builder` is automatically discovered and executed. Builders can be defined in any file in the `pkg/dev/artifacts/builder` folder.
341
+
342
+ **Subclass Behavior**: If you subclass an existing `Builder`, only your most specific subclass will be executed, preventing duplicate builds.
343
+
344
+ #### `pkg/dev/artifacts/resources/`
345
+
346
+ Directory for storing static resources (images, data files, etc.). Automatically included in PyInstaller builds.
347
+
348
+ - **Resource Access**: Use `get_resource_path()` to access resources at runtime
349
+ - **Automatic Discovery**: Resources from all packages depending on pyrig are automatically included
350
+
351
+ **Example**:
352
+ ```python
353
+ from pyrig.src.resource import get_resource_path
354
+ import your_project.dev.artifacts.resources as resources
355
+
356
+ config_path = get_resource_path("config.json", resources)
357
+ data = config_path.read_text()
358
+ ```
359
+
360
+ #### `pkg/dev/configs/python/resources_init.py`
361
+
362
+ Manages the `resources/__init__.py` file to ensure proper package initialization.
363
+
364
+ #### `pkg/dev/configs/testing/fixtures/`
365
+
366
+ Configuration files that manage the fixture system structure (fixture.py, scopes/session.py, scopes/package.py, scopes/module.py, scopes/class_.py, scopes/function.py).
367
+
368
+ #### `tests/conftest.py`
369
+
370
+ Automatically discovers and loads fixtures from all packages depending on pyrig. Fixtures are globally available across all tests without manual imports.
371
+
372
+ #### `tests/test_zero.py`
373
+
374
+ Empty test file to ensure pytest doesn't complain about missing tests during initial setup.
375
+
376
+ #### `tests/test_<pkg>/test_main.py`
377
+
378
+ Auto-generated test file that verifies the CLI entry point works correctly by running `uv run <pkg-name> --help`.
379
+
380
+ ---
381
+
382
+
383
+ ## CLI Commands
384
+
385
+ pyrig provides the following CLI commands:
386
+
387
+ ```bash
388
+ pyrig --help
389
+
390
+ Usage: pyrig [OPTIONS] COMMAND [ARGS]...
391
+
392
+ ╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────╮
393
+ │ --install-completion Install completion for the current shell. │
394
+ │ --show-completion Show completion for the current shell, to copy it or customize the │
395
+ │ installation. │
396
+ │ --help Show this message and exit. │
397
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯
398
+ ╭─ Commands ──────────────────────────────────────────────────────────────────────────────────────────╮
399
+ │ create-root Creates the root of the project. │
400
+ │ create-tests Create all test files for the project. │
401
+ │ init Set up the project. │
402
+ │ build Build all artifacts. │
403
+ │ protect-repo Protect the repository. │
404
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯
405
+ ```
406
+
407
+ ---
408
+
409
+ ## Repository Protection
410
+
411
+ pyrig automatically configures comprehensive GitHub repository protection measures to enforce code quality, security, and collaboration best practices. The protection is applied via the `pyrig protect-repo` command, which is automatically run in CI/CD workflows.
412
+
413
+ ### Repository Settings
414
+
415
+ The following repository-level settings are automatically configured:
416
+
417
+ | Setting | Configuration | Purpose |
418
+ |---------|--------------|---------|
419
+ | **Default Branch** | `main` | Standard default branch |
420
+ | **Delete Branch on Merge** | Enabled | Automatically deletes head branches after pull requests are merged |
421
+ | **Allow Update Branch** | Enabled | Allows updating pull request branches with the base branch |
422
+ | **Merge Commit** | Disabled | Prevents merge commits to maintain clean history |
423
+ | **Rebase Merge** | Enabled | Allows rebase and merge strategy |
424
+ | **Squash Merge** | Enabled | Allows squash and merge strategy |
425
+
426
+ ### Branch Protection Rules
427
+
428
+ A comprehensive ruleset named "main protection" is applied to the default branch with the following protections:
429
+
430
+ #### Branch Modification Protections
431
+
432
+ - **Deletion Protection**: Prevents deletion of the protected branch
433
+ - **Non-Fast-Forward Protection**: Prevents force pushes and history rewrites
434
+ - **Creation Protection**: Controls branch creation patterns
435
+ - **Update Protection**: Restricts direct updates to the branch
436
+
437
+ #### Pull Request Requirements
438
+
439
+ - **Required Approving Reviews**: At least 1 approval required before merging
440
+ - **Dismiss Stale Reviews**: Automatically dismisses approvals when new commits are pushed
441
+ - **Code Owner Review**: Requires review from code owners (if CODEOWNERS file exists)
442
+ - **Last Push Approval**: Requires approval from someone other than the last person to push
443
+ - **Review Thread Resolution**: All review comments must be resolved before merging
444
+ - **Allowed Merge Methods**: Only squash and rebase merges are permitted
445
+
446
+ #### Code Quality Requirements
447
+
448
+ - **Required Linear History**: Enforces a linear commit history
449
+ - **Required Commit Signatures**: Requires commits to be signed (GPG/SSH signatures)
450
+ - **Required Status Checks**: All CI/CD checks must pass before merging
451
+ - **Strict Status Checks**: Branch must be up-to-date with base branch before merging
452
+ - **Required Workflow**: The `health_check.yaml` workflow must pass successfully
453
+
454
+ #### Bypass Actors
455
+
456
+ - Repository owner can bypass all protection rules when necessary
457
+
458
+ ### Manual Application
459
+
460
+ The repository protection is automatically applied during CI/CD workflows, but you can also manually apply it:
461
+
462
+ ```bash
463
+ # Set the REPO_TOKEN environment variable with a GitHub Personal Access Token
464
+ export REPO_TOKEN=your_github_token # or add it to your .env file; pyrig picks it up automatically
465
+
466
+ # Run the protection command
467
+ uv run pyrig protect-repo
468
+ ```
469
+
470
+ **Required Token Permissions**:
471
+ - `contents:read and write`
472
+ - `administration:read and write`
473
+
474
+ ### CI/CD Integration
475
+
476
+ The `protect-repo` step is automatically included in both the `health_check.yaml` and `release.yaml` workflows, ensuring that protection rules are consistently applied and up-to-date with every CI/CD run.
477
+
478
+ ---
479
+
480
+
481
+ ## Testing
482
+
483
+ pyrig uses pytest as the test framework, which is automatically added as a dev dependency and configured in pyproject.toml.
484
+
485
+ ### Test Structure
486
+
487
+ pyrig generates test skeletons that mirror your source code structure:
488
+
489
+ - **Module Level**: Each source module has a corresponding test module
490
+ - **Class Level**: Each source class has a corresponding test class
491
+ - **Function Level**: Each source function/method has a corresponding test function
492
+
493
+ ### Automatic Test Generation
494
+
495
+ 1. **Manual**: Run `uv run pyrig create-tests`
496
+ 2. **Automatic**: An autouse session fixture creates missing tests when you run `pytest`
497
+
498
+ #### Fixture System
499
+
500
+ pyrig automatically discovers and loads fixtures from all packages depending on pyrig.
501
+
502
+ **Built-in Fixtures**:
503
+ - `config_file_factory` - Factory for creating test config file classes
504
+ - `builder_factory` - Factory for creating test builder classes
505
+
506
+ **Creating Custom Fixtures**:
507
+
508
+ Add a `pkg/dev/tests/fixtures/` directory:
509
+
510
+ ```
511
+ your_project/dev/tests/fixtures/
512
+ ├── __init__.py
513
+ ├── fixture.py # Custom fixtures
514
+ └── scopes/
515
+ ├── __init__.py
516
+ ├── session.py # Session-level autouse fixtures
517
+ └── ...
518
+ ```
519
+
520
+ **Notes**:
521
+ - All fixtures are automatically discovered - no manual imports needed
522
+ - Autouse fixtures must be decorated with `@pytest.fixture(autouse=True)` or `@autouse_session_fixture`
523
+ - Fixtures from all packages are available to all tests
524
+
525
+ ### Autouse Session Fixtures
526
+
527
+ Autouse session fixtures automatically enforce code quality and project conventions. These fixtures run once per test session before any tests execute.
528
+
529
+ #### `assert_root_is_correct`
530
+
531
+ Verifies and fixes all configuration files managed by the ConfigFile Machinery. If any config file is missing or incorrect, it automatically creates or corrects it. When running in GitHub Actions, it also ensures `.experiment.py` exists.
532
+
533
+ #### `assert_no_namespace_packages`
534
+
535
+ Ensures all packages have `__init__.py` files. If namespace packages are found (directories without `__init__.py`), they are automatically created. This prevents import issues and ensures proper package structure.
536
+
537
+ #### `assert_all_src_code_in_one_package`
538
+
539
+ Verifies that all source code is in a single package alongside the `tests` package. Also ensures the source package only contains the expected structure: `src/` and `dev/` subpackages, and a `main.py` module. This enforces a clean project structure.
540
+
541
+ #### `assert_src_package_correctly_named`
542
+
543
+ Checks that the source package name matches the project name defined in `pyproject.toml`. Hyphens in the project name are converted to underscores for the package name.
544
+
545
+ #### `assert_all_modules_tested`
546
+
547
+ Creates missing test modules, classes, and functions. For every module in the source package, it ensures a corresponding test module exists in the `tests` directory. Missing test skeletons are automatically generated.
548
+
549
+ #### `assert_no_unit_test_package_usage`
550
+
551
+ Prevents usage of the `unittest` package by scanning all Python files for `unittest` imports or usage. This enforces pytest as the sole testing framework for consistency.
552
+
553
+ #### `assert_dependencies_are_up_to_date`
554
+
555
+ Keeps dependencies current by running `uv self update` (when not in CI), `uv lock --upgrade`, and `uv sync`. This ensures you always have the latest compatible versions of all dependencies.
556
+
557
+ #### `assert_pre_commit_is_installed`
558
+
559
+ Ensures pre-commit hooks are installed by running `pre-commit install`. This guarantees that code quality checks run automatically before every commit.
560
+
561
+ #### `assert_src_runs_without_dev_deps`
562
+
563
+ Verifies that the source code runs correctly without dev dependencies installed. This fixture creates a temporary environment with only production dependencies, imports all source modules, and confirms they load successfully. This catches accidental imports of dev-only packages (like `pytest`) in production code.
564
+
565
+ ### Running Tests
566
+
567
+ ```bash
568
+ # Run all tests
569
+ uv run pytest
570
+
571
+ # Run specific test file
572
+ uv run pytest tests/test_your_project/test_calculator.py
573
+
574
+ # Run with coverage
575
+ uv run pytest --cov=your_project
576
+ ```
577
+
578
+ ### Disabling Tests
579
+
580
+ To disable tests for a specific module, empty the test file:
581
+ ```bash
582
+ echo "" > tests/test_your_project/test_src/test_calculator.py
583
+ ```
584
+
585
+ ### Testing Utilities
586
+
587
+ pyrig provides several testing utilities in `pyrig.src.testing`:
588
+
589
+ **Assertions** (`pyrig.src.testing.assertions`):
590
+ - `assert_with_msg(expr, msg)` - Assert with a custom error message
591
+ - `assert_with_info(expr, expected, actual, msg)` - Assert with expected/actual values in the message
592
+ - `assert_isabstrct_method(method)` - Assert that a method is abstract
593
+
594
+ **Skip Decorators** (`pyrig.dev.tests.utils.decorators`):
595
+ - `@skip_fixture_test` - Skip tests for fixtures (cannot be called directly)
596
+ - `@skip_in_github_actions` - Skip tests that cannot run in GitHub Actions
597
+
598
+ **Fixture Scope Decorators** (`pyrig.dev.tests.utils.decorators`):
599
+ - `@function_fixture`, `@class_fixture`, `@module_fixture`, `@package_fixture`, `@session_fixture`
600
+ - `@autouse_function_fixture`, `@autouse_class_fixture`, `@autouse_module_fixture`, `@autouse_package_fixture`, `@autouse_session_fixture`
601
+
602
+ **Example**:
603
+ ```python
604
+ from pyrig.src.testing.assertions import assert_with_msg
605
+ from pyrig.dev.tests.utils.decorators import autouse_session_fixture, skip_in_github_actions
606
+
607
+ @autouse_session_fixture
608
+ def my_fixture() -> str:
609
+ return "test data"
610
+
611
+ @skip_in_github_actions
612
+ def test_local_only() -> None:
613
+ assert_with_msg(True, "This should pass")
614
+ ```
615
+
616
+ ---
617
+
618
+ ## Building Artifacts
619
+
620
+ pyrig provides an extensible build system. All builders are subclasses of `Builder`. When you run `pyrig build`, the system automatically discovers all `Builder` subclasses. If you subclass an existing builder, only your most specific subclass executes.
621
+
622
+ ### Basic Builder
623
+
624
+ Create custom builders by subclassing `Builder` in `pkg/dev/artifacts/builder/builder.py`:
625
+
626
+ ```python
627
+ from pathlib import Path
628
+ from pyrig.dev.artifacts.builder.base.base import Builder
629
+
630
+ class MyBuilder(Builder):
631
+ """Custom builder for creating artifacts."""
632
+
633
+ @classmethod
634
+ def create_artifacts(cls, temp_artifacts_dir: Path) -> None:
635
+ """Build the project."""
636
+ # Create your artifacts in temp_artifacts_dir
637
+ artifact_path = temp_artifacts_dir / "my_artifact.txt"
638
+ artifact_path.write_text("Hello, World!")
639
+ ```
640
+
641
+ **Build artifacts**:
642
+ ```bash
643
+ uv run pyrig build
644
+ ```
645
+
646
+ Artifacts are placed in the `dist/` directory with platform-specific naming (e.g., `my_artifact-Linux.txt`, `my_artifact-Windows.txt`).
647
+
648
+ ### PyInstaller Builder
649
+
650
+ pyrig includes a `PyInstallerBuilder` class for creating standalone executables.
651
+
652
+ 1. **Implement your main function** in `your_project/main.py`
653
+ 2. **Create an icon.png file** at `your_project/dev/artifacts/resources/icon.png` (256x256 recommended)
654
+ 3. **Add resources** to `your_project/dev/artifacts/resources/` (optional)
655
+ 4. **Subclass PyInstallerBuilder** in `your_project/dev/artifacts/builder/builder.py`:
656
+ ```python
657
+ from types import ModuleType
658
+ from pyrig.dev.artifacts.builder.base.base import PyInstallerBuilder
659
+
660
+ class MyAppBuilder(PyInstallerBuilder):
661
+ """Build standalone executable with PyInstaller."""
662
+
663
+ @classmethod
664
+ def get_additional_resource_pkgs(cls) -> list[ModuleType]:
665
+ """Return additional resource packages to include in the build."""
666
+ return [] # Add your custom resource packages here
667
+ ```
668
+
669
+ 5. **Build**:
670
+ ```bash
671
+ uv run pyrig build
672
+ ```
673
+
674
+ The builder automatically:
675
+ - Creates a single executable file
676
+ - Converts icon.png to platform-specific format
677
+ - Auto-discovers and includes resources from all packages depending on pyrig
678
+ - Names output with platform suffix (e.g., `your-project-Windows.exe`)
679
+
680
+ ### Accessing Resources in Built Executables
681
+
682
+ Use `get_resource_path()` to access resources:
683
+
684
+ ```python
685
+ from pyrig.dev.artifacts.resources.resource import get_resource_path
686
+ import your_project.dev.artifacts.resources as resources
687
+
688
+ config_path = get_resource_path("config.json", resources)
689
+ data = json.loads(config_path.read_text())
690
+ ```
691
+
692
+ ### Multi-Package Architecture
693
+
694
+ pyrig supports multi-package architecture where multiple packages can depend on pyrig and automatically share configurations, builders, fixtures, and resources.
695
+
696
+ **Automatic Discovery**:
697
+
698
+ When you run pyrig commands or tests, it discovers components from all packages depending on pyrig:
699
+
700
+ 1. **ConfigFile Machinery**: All `ConfigFile` subclasses from all `dev/configs/` directories
701
+ 2. **Builders**: All `Builder` subclasses from all `dev/artifacts/builder/` directories
702
+ 3. **Fixtures**: All pytest fixtures from all `dev/tests/fixtures/` directories
703
+ 4. **Resources**: All files from all `dev/artifacts/resources/` directories
704
+
705
+ **Intelligent Subclass Discovery**:
706
+
707
+ - Only the most specific (leaf) subclasses are executed for configs and builders
708
+ - If you subclass a config or builder, only your subclass runs (not the parent)
709
+ - Fixtures use a different mechanism that loads all fixture modules without filtering
710
+
711
+ This enables:
712
+ - **Modular development**: Split your project into multiple packages with shared infrastructure
713
+ - **Reusable components**: Share configs, builders, fixtures, and resources across projects
714
+ - **Zero configuration**: Everything works automatically through dependency discovery
715
+
716
+
717
+
718
+ ---
719
+
720
+ ## Examples
721
+
722
+ ### Example 1: Complete Project Structure
723
+
724
+ After running `pyrig init`:
725
+
726
+ ```
727
+ your-project/
728
+ ├── .env, .experiment.py, .gitignore, .pre-commit-config.yaml, .python-version
729
+ ├── LICENSE, uv.lock, pyproject.toml, README.md
730
+ ├── .github/workflows/
731
+ │ ├── health_check.yaml, publish.yaml, release.yaml
732
+ ├── your_project/
733
+ │ ├── __init__.py, main.py, py.typed
734
+ │ ├── src/
735
+ │ └── dev/
736
+ │ ├── artifacts/builder/, artifacts/resources/
737
+ │ ├── cli/subcommands.py
738
+ │ ├── configs/configs.py
739
+ │ └── tests/fixtures/
740
+ └── tests/
741
+ ├── conftest.py, test_zero.py
742
+ └── test_your_project/
743
+ ```
744
+
745
+ ### Example 2: Adding a Custom Config File
746
+
747
+ ```python
748
+ from pathlib import Path
749
+ from pyrig.dev.configs.base.base import YamlConfigFile
750
+
751
+ class MyConfigFile(YamlConfigFile):
752
+ @classmethod
753
+ def get_filename(cls) -> str:
754
+ return "myconfig"
755
+
756
+ @classmethod
757
+ def get_parent_path(cls) -> Path:
758
+ return Path("config")
759
+
760
+ @classmethod
761
+ def get_configs(cls) -> dict[str, Any]:
762
+ return {"setting1": "value1", "setting2": "value2"}
763
+ ```
764
+
765
+ ### Example 3: Custom CLI Command
766
+
767
+ ```python
768
+ def deploy(environment: str = "staging") -> None:
769
+ """Deploy the application."""
770
+ print(f"Deploying to {environment}...")
771
+
772
+ # Run with: uv run your-project deploy --environment production
773
+ ```
774
+
775
+ ---
776
+
777
+ ## Troubleshooting
778
+
779
+ ### Common Issues
780
+
781
+ #### `uv run pyrig` command not found
782
+ ```bash
783
+ uv sync
784
+ ```
785
+
786
+ #### Pre-commit hooks failing
787
+ ```bash
788
+ uv run pre-commit install
789
+ uv run pre-commit run --all-files
790
+ ```
791
+
792
+ #### Tests not being generated automatically
793
+ ```bash
794
+ uv run pyrig create-tests
795
+ ```
796
+
797
+ #### GitHub Actions permission errors
798
+ Ensure `REPO_TOKEN` secret has: `contents:read and write`, `administration:read and write`
799
+
800
+ #### MyPy errors
801
+ Add type hints:
802
+ ```python
803
+ def add(a: int, b: int) -> int:
804
+ return a + b
805
+ ```
806
+
807
+ #### Dependency conflicts
808
+ ```bash
809
+ uv lock --upgrade && uv sync
810
+ ```
811
+
812
+ #### PyInstaller build fails
813
+ 1. Ensure `main.py` exists and has `main()` function implemented
814
+ 2. Ensure `icon.png` exists at `your_project/dev/artifacts/resources/icon.png`
815
+
816
+ #### Resources not found in built executable
817
+ Use `get_resource_path()`:
818
+ ```python
819
+ from pyrig.dev.artifacts.resources.resource import get_resource_path
820
+ import your_project.dev.artifacts.resources as resources
821
+ path = get_resource_path("config.json", resources)
822
+ ```
823
+
824
+ ---
825
+
826
+ ## Contributing
827
+
828
+ 1. **Report Issues**: [Open an issue](https://github.com/winipedia/pyrig/issues)
829
+ 2. **Suggest Features**: [Start a discussion](https://github.com/winipedia/pyrig/discussions)
830
+ 3. **Submit Pull Requests**: Fork, create feature branch, make changes, run tests, commit, push, open PR
831
+
832
+ ### Development Setup
833
+
834
+ ```bash
835
+ git clone https://github.com/winipedia/pyrig.git
836
+ cd pyrig
837
+ uv sync
838
+ uv run pytest
839
+ ```
840
+
841
+ ---
842
+
843
+ ## License
844
+
845
+ pyrig is licensed under the MIT License. See [LICENSE](LICENSE) for more information.
846
+
847
+ Copyright (c) 2025 Winipedia
848
+
849
+ ---
850
+
851
+ ## Links
852
+
853
+ - **Repository**: [github.com/winipedia/pyrig](https://github.com/winipedia/pyrig)
854
+ - **PyPI**: [pypi.org/project/pyrig](https://pypi.org/project/pyrig/)
855
+
856
+ ---