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.
Files changed (102) hide show
  1. pyrig/__init__.py +1 -0
  2. pyrig/dev/__init__.py +6 -0
  3. pyrig/dev/builders/__init__.py +1 -0
  4. pyrig/dev/builders/base/__init__.py +5 -0
  5. pyrig/dev/builders/base/base.py +256 -0
  6. pyrig/dev/builders/pyinstaller.py +229 -0
  7. pyrig/dev/cli/__init__.py +5 -0
  8. pyrig/dev/cli/cli.py +95 -0
  9. pyrig/dev/cli/commands/__init__.py +1 -0
  10. pyrig/dev/cli/commands/build_artifacts.py +16 -0
  11. pyrig/dev/cli/commands/create_root.py +25 -0
  12. pyrig/dev/cli/commands/create_tests.py +244 -0
  13. pyrig/dev/cli/commands/init_project.py +160 -0
  14. pyrig/dev/cli/commands/make_inits.py +27 -0
  15. pyrig/dev/cli/commands/protect_repo.py +145 -0
  16. pyrig/dev/cli/shared_subcommands.py +20 -0
  17. pyrig/dev/cli/subcommands.py +73 -0
  18. pyrig/dev/configs/__init__.py +1 -0
  19. pyrig/dev/configs/base/__init__.py +5 -0
  20. pyrig/dev/configs/base/base.py +826 -0
  21. pyrig/dev/configs/containers/__init__.py +1 -0
  22. pyrig/dev/configs/containers/container_file.py +111 -0
  23. pyrig/dev/configs/dot_env.py +95 -0
  24. pyrig/dev/configs/dot_python_version.py +88 -0
  25. pyrig/dev/configs/git/__init__.py +5 -0
  26. pyrig/dev/configs/git/gitignore.py +181 -0
  27. pyrig/dev/configs/git/pre_commit.py +170 -0
  28. pyrig/dev/configs/licence.py +112 -0
  29. pyrig/dev/configs/markdown/__init__.py +1 -0
  30. pyrig/dev/configs/markdown/docs/__init__.py +1 -0
  31. pyrig/dev/configs/markdown/docs/index.py +38 -0
  32. pyrig/dev/configs/markdown/readme.py +132 -0
  33. pyrig/dev/configs/py_typed.py +28 -0
  34. pyrig/dev/configs/pyproject.py +436 -0
  35. pyrig/dev/configs/python/__init__.py +5 -0
  36. pyrig/dev/configs/python/builders_init.py +27 -0
  37. pyrig/dev/configs/python/configs_init.py +28 -0
  38. pyrig/dev/configs/python/dot_experiment.py +46 -0
  39. pyrig/dev/configs/python/main.py +59 -0
  40. pyrig/dev/configs/python/resources_init.py +27 -0
  41. pyrig/dev/configs/python/shared_subcommands.py +29 -0
  42. pyrig/dev/configs/python/src_init.py +27 -0
  43. pyrig/dev/configs/python/subcommands.py +27 -0
  44. pyrig/dev/configs/testing/__init__.py +5 -0
  45. pyrig/dev/configs/testing/conftest.py +64 -0
  46. pyrig/dev/configs/testing/fixtures_init.py +27 -0
  47. pyrig/dev/configs/testing/main_test.py +74 -0
  48. pyrig/dev/configs/testing/zero_test.py +43 -0
  49. pyrig/dev/configs/workflows/__init__.py +5 -0
  50. pyrig/dev/configs/workflows/base/__init__.py +5 -0
  51. pyrig/dev/configs/workflows/base/base.py +1662 -0
  52. pyrig/dev/configs/workflows/build.py +106 -0
  53. pyrig/dev/configs/workflows/health_check.py +133 -0
  54. pyrig/dev/configs/workflows/publish.py +68 -0
  55. pyrig/dev/configs/workflows/release.py +90 -0
  56. pyrig/dev/tests/__init__.py +5 -0
  57. pyrig/dev/tests/conftest.py +40 -0
  58. pyrig/dev/tests/fixtures/__init__.py +1 -0
  59. pyrig/dev/tests/fixtures/assertions.py +147 -0
  60. pyrig/dev/tests/fixtures/autouse/__init__.py +5 -0
  61. pyrig/dev/tests/fixtures/autouse/class_.py +42 -0
  62. pyrig/dev/tests/fixtures/autouse/module.py +40 -0
  63. pyrig/dev/tests/fixtures/autouse/session.py +589 -0
  64. pyrig/dev/tests/fixtures/factories.py +118 -0
  65. pyrig/dev/utils/__init__.py +1 -0
  66. pyrig/dev/utils/cli.py +17 -0
  67. pyrig/dev/utils/git.py +312 -0
  68. pyrig/dev/utils/packages.py +93 -0
  69. pyrig/dev/utils/resources.py +77 -0
  70. pyrig/dev/utils/testing.py +66 -0
  71. pyrig/dev/utils/versions.py +268 -0
  72. pyrig/main.py +9 -0
  73. pyrig/py.typed +0 -0
  74. pyrig/resources/GITIGNORE +216 -0
  75. pyrig/resources/LATEST_PYTHON_VERSION +1 -0
  76. pyrig/resources/MIT_LICENSE_TEMPLATE +21 -0
  77. pyrig/resources/__init__.py +1 -0
  78. pyrig/src/__init__.py +1 -0
  79. pyrig/src/git/__init__.py +6 -0
  80. pyrig/src/git/git.py +146 -0
  81. pyrig/src/graph.py +255 -0
  82. pyrig/src/iterate.py +107 -0
  83. pyrig/src/modules/__init__.py +22 -0
  84. pyrig/src/modules/class_.py +369 -0
  85. pyrig/src/modules/function.py +189 -0
  86. pyrig/src/modules/inspection.py +148 -0
  87. pyrig/src/modules/module.py +658 -0
  88. pyrig/src/modules/package.py +452 -0
  89. pyrig/src/os/__init__.py +6 -0
  90. pyrig/src/os/os.py +121 -0
  91. pyrig/src/project/__init__.py +5 -0
  92. pyrig/src/project/mgt.py +83 -0
  93. pyrig/src/resource.py +58 -0
  94. pyrig/src/string.py +100 -0
  95. pyrig/src/testing/__init__.py +6 -0
  96. pyrig/src/testing/assertions.py +66 -0
  97. pyrig/src/testing/convention.py +203 -0
  98. pyrig-2.2.6.dist-info/METADATA +174 -0
  99. pyrig-2.2.6.dist-info/RECORD +102 -0
  100. pyrig-2.2.6.dist-info/WHEEL +4 -0
  101. pyrig-2.2.6.dist-info/entry_points.txt +3 -0
  102. pyrig-2.2.6.dist-info/licenses/LICENSE +21 -0
pyrig/src/resource.py ADDED
@@ -0,0 +1,58 @@
1
+ """Resource file access utilities for development and PyInstaller builds.
2
+
3
+ This module provides utilities for accessing static resource files (images,
4
+ configuration files, data files, etc.) in a way that works both during
5
+ development and when the application is bundled with PyInstaller.
6
+
7
+ When running from source, resources are accessed from the filesystem. When
8
+ running from a PyInstaller bundle, resources are extracted from the frozen
9
+ executable's temporary directory (MEIPASS). Using `importlib.resources`
10
+ handles both cases transparently.
11
+
12
+ Resources should be placed in `pkg/dev/artifacts/resources/` directories
13
+ and accessed via the `get_resource_path` function.
14
+
15
+ Example:
16
+ >>> import my_project.dev.artifacts.resources as resources
17
+ >>> from pyrig.src.resource import get_resource_path
18
+ >>> config_path = get_resource_path("config.json", resources)
19
+ >>> data = config_path.read_text()
20
+ """
21
+
22
+ from importlib.resources import as_file, files
23
+ from pathlib import Path
24
+ from types import ModuleType
25
+
26
+
27
+ def get_resource_path(name: str, package: ModuleType) -> Path:
28
+ """Get the filesystem path to a resource file.
29
+
30
+ Resolves the path to a resource file within a package, handling both
31
+ development (source) and production (PyInstaller bundle) environments.
32
+ Uses `importlib.resources` to ensure compatibility with frozen executables.
33
+
34
+ Args:
35
+ name: The filename of the resource including extension
36
+ (e.g., "icon.png", "config.json").
37
+ package: The package module containing the resource. This should be
38
+ the `resources` package itself, not a parent package.
39
+
40
+ Returns:
41
+ A Path object pointing to the resource file. In development, this
42
+ is the actual file path. In a PyInstaller bundle, this is a path
43
+ to the extracted file in the temporary directory.
44
+
45
+ Example:
46
+ >>> import my_app.dev.artifacts.resources as resources
47
+ >>> icon_path = get_resource_path("icon.png", resources)
48
+ >>> print(icon_path)
49
+ /path/to/my_app/dev/artifacts/resources/icon.png
50
+
51
+ Note:
52
+ The returned path is only valid within the context of the current
53
+ process. For PyInstaller bundles, the file is extracted to a
54
+ temporary directory that is cleaned up when the process exits.
55
+ """
56
+ resource_path = files(package) / name
57
+ with as_file(resource_path) as path:
58
+ return path
pyrig/src/string.py ADDED
@@ -0,0 +1,100 @@
1
+ """String manipulation and naming convention utilities.
2
+
3
+ This module provides utility functions for string transformations commonly needed
4
+ when working with Python naming conventions. It handles conversions between
5
+ different case styles (snake_case, PascalCase, kebab-case) and creates
6
+ human-readable names from Python objects.
7
+
8
+ These utilities are particularly useful for:
9
+ - Generating CLI command names from function names
10
+ - Creating display names for classes and modules
11
+ - Parsing and transforming identifiers
12
+
13
+ Example:
14
+ >>> from pyrig.src.string import split_on_uppercase, make_name_from_obj
15
+ >>> split_on_uppercase("MyClassName")
16
+ ['My', 'Class', 'Name']
17
+ >>> make_name_from_obj("my_function_name")
18
+ 'My-Function-Name'
19
+ """
20
+
21
+ import re
22
+ from collections.abc import Callable
23
+ from types import ModuleType
24
+ from typing import Any
25
+
26
+
27
+ def split_on_uppercase(string: str) -> list[str]:
28
+ """Split a string at uppercase letter boundaries.
29
+
30
+ Useful for parsing PascalCase or camelCase identifiers into their
31
+ component words. Empty strings between consecutive uppercase letters
32
+ are filtered out.
33
+
34
+ Args:
35
+ string: The string to split, typically in PascalCase or camelCase.
36
+
37
+ Returns:
38
+ A list of substrings, each starting with an uppercase letter
39
+ (except possibly the first if the original string started lowercase).
40
+
41
+ Example:
42
+ >>> split_on_uppercase("HelloWorld")
43
+ ['Hello', 'World']
44
+ >>> split_on_uppercase("XMLParser")
45
+ ['X', 'M', 'L', 'Parser']
46
+ >>> split_on_uppercase("lowercase")
47
+ ['lowercase']
48
+ """
49
+ return [s for s in re.split(r"(?=[A-Z])", string) if s]
50
+
51
+
52
+ def make_name_from_obj(
53
+ obj: ModuleType | Callable[..., Any] | type | str,
54
+ split_on: str = "_",
55
+ join_on: str = "-",
56
+ *,
57
+ capitalize: bool = True,
58
+ ) -> str:
59
+ """Create a human-readable name from a Python object or string.
60
+
61
+ Transforms Python identifiers (typically in snake_case) into formatted
62
+ display names. Commonly used to generate CLI command names, display
63
+ labels, or documentation titles from function/class/module names.
64
+
65
+ Args:
66
+ obj: The object to extract a name from. Can be a module, callable,
67
+ class, or string. For non-string objects, uses the last component
68
+ of `__name__` (e.g., "my_module" from "package.my_module").
69
+ split_on: Character(s) to split the name on. Defaults to underscore
70
+ for snake_case input.
71
+ join_on: Character(s) to join the parts with. Defaults to hyphen
72
+ for kebab-case output.
73
+ capitalize: Whether to capitalize each word. When True, produces
74
+ Title-Case output.
75
+
76
+ Returns:
77
+ A formatted string with parts split and rejoined according to the
78
+ specified separators.
79
+
80
+ Example:
81
+ >>> make_name_from_obj("my_function_name")
82
+ 'My-Function-Name'
83
+ >>> make_name_from_obj("my_function", join_on=" ", capitalize=True)
84
+ 'My Function'
85
+ >>> import os
86
+ >>> make_name_from_obj(os.path)
87
+ 'Path'
88
+ """
89
+ if not isinstance(obj, str):
90
+ name = getattr(obj, "__name__", "")
91
+ if not name:
92
+ msg = f"Cannot extract name from {obj}"
93
+ raise ValueError(msg)
94
+ obj_name: str = name.split(".")[-1]
95
+ else:
96
+ obj_name = obj
97
+ parts = obj_name.split(split_on)
98
+ if capitalize:
99
+ parts = [part.capitalize() for part in parts]
100
+ return join_on.join(parts)
@@ -0,0 +1,6 @@
1
+ """Testing utilities and test generation infrastructure.
2
+
3
+ This package provides utilities for test creation, validation, and
4
+ conventions. It includes automatic test skeleton generation, assertion
5
+ helpers, and utilities for mapping between source and test objects.
6
+ """
@@ -0,0 +1,66 @@
1
+ """Testing assertion utilities for enhanced test validation.
2
+
3
+ This module provides custom assertion functions that extend Python's built-in
4
+ assert statement with additional features like improved error messages and
5
+ specialized validation logic for common testing scenarios.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from pyrig.src.modules.function import is_abstractmethod
11
+
12
+
13
+ def assert_with_msg(expr: bool, msg: str) -> None: # noqa: FBT001
14
+ """Assert that an expression is true with a custom error message.
15
+
16
+ A thin wrapper around Python's built-in assert statement that makes it
17
+ easier to provide meaningful error messages when assertions fail.
18
+
19
+ Args:
20
+ expr: The expression to evaluate for truthiness
21
+ msg: The error message to display if the assertion fails
22
+
23
+ Raises:
24
+ AssertionError: If the expression evaluates to False
25
+
26
+ """
27
+ assert expr, msg # noqa: S101 # nosec: B101
28
+
29
+
30
+ def assert_with_info(expr: bool, expected: Any, actual: Any, msg: str = "") -> None: # noqa: FBT001
31
+ """Assert that an expression is true with a custom error message.
32
+
33
+ wraps around assert with msg and adds the expected and actual values to the message.
34
+
35
+ Args:
36
+ expr: The expression to evaluate for truthiness
37
+ expected: The expected value
38
+ actual: The actual value
39
+ msg: The error message to display if the assertion fails
40
+
41
+ Raises:
42
+ AssertionError: If the expression evaluates to False
43
+
44
+ """
45
+ msg = f"""
46
+ Expected: {expected}
47
+ Actual: {actual}
48
+ {msg}
49
+ """
50
+ assert_with_msg(expr, msg)
51
+
52
+
53
+ def assert_isabstrct_method(method: Any) -> None:
54
+ """Assert that a method is an abstract method.
55
+
56
+ Args:
57
+ method: The method to check
58
+
59
+ Raises:
60
+ AssertionError: If the method is not an abstract method
61
+
62
+ """
63
+ assert_with_msg(
64
+ is_abstractmethod(method),
65
+ f"Expected {method} to be abstract method",
66
+ )
@@ -0,0 +1,203 @@
1
+ """Test naming conventions and object mapping utilities.
2
+
3
+ This module defines pyrig's test naming conventions and provides bidirectional
4
+ mapping between source objects and their corresponding test objects. The
5
+ conventions follow pytest standards:
6
+
7
+ - Test functions: ``test_<function_name>``
8
+ - Test classes: ``Test<ClassName>``
9
+ - Test modules: ``test_<module_name>.py``
10
+ - Test package: ``tests/``
11
+
12
+ Key functions:
13
+ - ``make_test_obj_name``: Generate test name from source object
14
+ - ``make_test_obj_importpath_from_obj``: Generate test import path
15
+ - ``get_test_obj_from_obj``: Get test object for a source object
16
+ - ``get_obj_from_test_obj``: Get source object for a test object
17
+
18
+ Attributes:
19
+ TEST_FUNCTION_PREFIX: Prefix for test functions ("test_").
20
+ TEST_CLASS_PREFIX: Prefix for test classes ("Test").
21
+ TEST_MODULE_PREFIX: Prefix for test modules ("test_").
22
+ TESTS_PACKAGE_NAME: Name of the tests package ("tests").
23
+ """
24
+
25
+ from collections.abc import Callable, Iterable
26
+ from types import ModuleType
27
+ from typing import Any, overload
28
+
29
+ from pyrig.src.modules.module import (
30
+ get_isolated_obj_name,
31
+ import_obj_from_importpath,
32
+ make_obj_importpath,
33
+ )
34
+
35
+ COVERAGE_THRESHOLD = 90
36
+
37
+ TEST_FUNCTION_PREFIX = "test_"
38
+
39
+ TEST_CLASS_PREFIX = "Test"
40
+
41
+ TEST_MODULE_PREFIX = TEST_FUNCTION_PREFIX
42
+
43
+ TEST_PREFIXES = [TEST_FUNCTION_PREFIX, TEST_CLASS_PREFIX, TEST_MODULE_PREFIX]
44
+
45
+ TESTS_PACKAGE_NAME = "tests"
46
+
47
+
48
+ def get_right_test_prefix(obj: Callable[..., Any] | type | ModuleType) -> str:
49
+ """Get the appropriate test prefix for an object based on its type.
50
+
51
+ Args:
52
+ obj: The object to get the test prefix for (function, class, or module)
53
+
54
+ Returns:
55
+ The appropriate test prefix string for the given object type
56
+
57
+ """
58
+ if isinstance(obj, ModuleType):
59
+ return TEST_MODULE_PREFIX
60
+ if isinstance(obj, type):
61
+ return TEST_CLASS_PREFIX
62
+ return TEST_FUNCTION_PREFIX
63
+
64
+
65
+ def make_test_obj_name(obj: Callable[..., Any] | type | ModuleType) -> str:
66
+ """Create a test name for an object by adding the appropriate prefix.
67
+
68
+ Args:
69
+ obj: The object to create a test name for
70
+
71
+ Returns:
72
+ The test name with the appropriate prefix
73
+
74
+ """
75
+ prefix = get_right_test_prefix(obj)
76
+ name = get_isolated_obj_name(obj)
77
+ return prefix + name
78
+
79
+
80
+ def reverse_make_test_obj_name(test_name: str) -> str:
81
+ """Extract the original object name from a test name by removing the prefix.
82
+
83
+ Args:
84
+ test_name: The test name to extract the original name from
85
+
86
+ Returns:
87
+ The original object name without the test prefix
88
+
89
+ Raises:
90
+ ValueError: If the test name doesn't start with any of the expected prefixes
91
+
92
+ """
93
+ for prefix in TEST_PREFIXES:
94
+ if test_name.startswith(prefix):
95
+ return test_name.removeprefix(prefix)
96
+ msg = f"{test_name=} is expected to start with one of {TEST_PREFIXES=}"
97
+ raise ValueError(msg)
98
+
99
+
100
+ def make_test_obj_importpath_from_obj(
101
+ obj: Callable[..., Any] | type | ModuleType,
102
+ ) -> str:
103
+ """Create an import path for a test object based on the original object.
104
+
105
+ Args:
106
+ obj: The original object to create a test import path for
107
+
108
+ Returns:
109
+ The import path for the corresponding test object
110
+
111
+ """
112
+ parts = make_obj_importpath(obj).split(".")
113
+ test_name = make_test_obj_name(obj)
114
+ test_parts = [
115
+ (TEST_MODULE_PREFIX if part[0].islower() else TEST_CLASS_PREFIX) + part
116
+ for part in parts
117
+ ]
118
+ test_parts[-1] = test_name
119
+ test_parts.insert(0, TESTS_PACKAGE_NAME)
120
+ return ".".join(test_parts)
121
+
122
+
123
+ def make_obj_importpath_from_test_obj(
124
+ test_obj: Callable[..., Any] | type | ModuleType,
125
+ ) -> str:
126
+ """Create an import path for an original object based on its test object.
127
+
128
+ Args:
129
+ test_obj: The test object to create an original import path for
130
+
131
+ Returns:
132
+ The import path for the corresponding original object
133
+
134
+ """
135
+ test_parts = make_obj_importpath(test_obj).split(".")
136
+ test_parts = test_parts[1:]
137
+ parts = [reverse_make_test_obj_name(part) for part in test_parts]
138
+ return ".".join(parts)
139
+
140
+
141
+ @overload
142
+ def get_test_obj_from_obj(obj: type) -> type: ...
143
+
144
+
145
+ @overload
146
+ def get_test_obj_from_obj(obj: Callable[..., Any]) -> Callable[..., Any]: ...
147
+
148
+
149
+ @overload
150
+ def get_test_obj_from_obj(obj: ModuleType) -> ModuleType: ...
151
+
152
+
153
+ def get_test_obj_from_obj(
154
+ obj: Callable[..., Any] | type | ModuleType,
155
+ ) -> Callable[..., Any] | type | ModuleType:
156
+ """Get the test object corresponding to an original object.
157
+
158
+ Args:
159
+ obj: The original object to get the test object for
160
+
161
+ Returns:
162
+ The corresponding test object
163
+
164
+ """
165
+ test_obj_path = make_test_obj_importpath_from_obj(obj)
166
+ return import_obj_from_importpath(test_obj_path)
167
+
168
+
169
+ def get_obj_from_test_obj(
170
+ test_obj: Callable[..., Any] | type | ModuleType,
171
+ ) -> Callable[..., Any] | type | ModuleType:
172
+ """Get the original object corresponding to a test object.
173
+
174
+ Args:
175
+ test_obj: The test object to get the original object for
176
+
177
+ Returns:
178
+ The corresponding original object
179
+
180
+ """
181
+ obj_importpath = make_obj_importpath_from_test_obj(test_obj)
182
+ return import_obj_from_importpath(obj_importpath)
183
+
184
+
185
+ def make_summary_error_msg(
186
+ errors_locations: Iterable[str],
187
+ ) -> str:
188
+ """Create an error message summarizing multiple error locations.
189
+
190
+ Args:
191
+ errors_locations: Collection of error locations
192
+
193
+ Returns:
194
+ A formatted error message listing all error locations
195
+ """
196
+ msg = """
197
+ Found errors at:
198
+ """
199
+ for error_location in errors_locations:
200
+ msg += f"""
201
+ - {error_location}
202
+ """
203
+ return msg
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyrig
3
+ Version: 2.2.6
4
+ Summary: A Python toolkit that standardizes and automates project setup, configuration and development.
5
+ Author: Winipedia
6
+ License-File: LICENSE
7
+ Classifier: Programming Language :: Python :: 3.12
8
+ Classifier: Programming Language :: Python :: 3.13
9
+ Classifier: Programming Language :: Python :: 3.14
10
+ Requires-Dist: typer>=0.20.0
11
+ Requires-Python: >=3.12
12
+ Description-Content-Type: text/markdown
13
+
14
+ # pyrig
15
+
16
+ <!-- tooling -->
17
+ [![pyrig](https://img.shields.io/badge/built%20with-pyrig-3776AB?logo=buildkite&logoColor=black)](https://github.com/Winipedia/pyrig)
18
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
19
+ [![Container](https://img.shields.io/badge/Container-Podman-A23CD6?logo=podman&logoColor=grey&colorA=0D1F3F&colorB=A23CD6)](https://podman.io/)
20
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://pre-commit.com/)
21
+ <!-- code-quality -->
22
+ [![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
23
+ [![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)[![mypy](https://img.shields.io/badge/type%20checked-mypy-039dfc.svg)](https://mypy-lang.org/)
24
+ [![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
25
+ [![pytest](https://img.shields.io/badge/tested%20with-pytest-46a2f1.svg?logo=pytest)](https://pytest.org/)
26
+ [![codecov](https://codecov.io/gh/Winipedia/pyrig/branch/main/graph/badge.svg)](https://codecov.io/gh/Winipedia/pyrig)
27
+ <!-- package-info -->
28
+ [![PyPI](https://img.shields.io/pypi/v/pyrig?logo=pypi&logoColor=white)](https://pypi.org/project/pyrig/)
29
+ [![Python](https://img.shields.io/badge/python-3.12|3.13|3.14-blue.svg?logo=python&logoColor=white)](https://www.python.org/)
30
+ [![License](https://img.shields.io/github/license/Winipedia/pyrig)](https://github.com/Winipedia/pyrig/blob/main/LICENSE)
31
+ <!-- ci/cd -->
32
+ [![CI](https://img.shields.io/github/actions/workflow/status/Winipedia/pyrig/health_check.yaml?label=CI&logo=github)](https://github.com/Winipedia/pyrig/actions/workflows/health_check.yaml)
33
+ [![CD](https://img.shields.io/github/actions/workflow/status/Winipedia/pyrig/release.yaml?label=CD&logo=github)](https://github.com/Winipedia/pyrig/actions/workflows/release.yaml)
34
+
35
+ ---
36
+
37
+ > A Python toolkit that standardizes and automates project setup, configuration and development.
38
+
39
+ ---
40
+
41
+ ## What is pyrig?
42
+
43
+ **pyrig** is an opinionated Python project framework that enforces best practices and keeps your projects up-to-date automatically. Unlike traditional project templates, pyrig is a living system that manages your entire development lifecycle. Pyrig makes project development seemless and keeps you focused on your code. It allows even in bigger project to not lose the overview. It opinionated and best practices approach allows you to always know what belongs where and where to find things.
44
+
45
+ ### Key Features
46
+
47
+ - **Automated Setup** - Initialize production-ready projects in seconds with `pyrig init`
48
+ - **Living Configuration** - Configs stay synchronized automatically, no manual maintenance
49
+ - **Enforced Quality** - Strict linting, type checking, testing, and security scanning out of the box
50
+ - **Always Current** - Automatic dependency updates and latest tool versions via CI/CD
51
+ - **Multi-Package Support** - Build package ecosystems with cross-package discovery of ConfigFiles, Builders, fixtures, and CLI commands
52
+ - **Extensible Architecture** - Plugin system for custom ConfigFiles and Builders
53
+
54
+ ### Philosophy
55
+
56
+ pyrig is designed for **serious, long-term Python projects** where code quality and maintainability matter. It makes opinionated choices about tooling and enforces best practices, so you can focus on building features instead of configuring tools and wondering whta is the best way to do something.
57
+
58
+ ## Quick Start
59
+
60
+ ### Prerequisites
61
+
62
+ - **Git** with username matching your GitHub username
63
+ - **uv** package manager
64
+ - **Podman** (for containerization)
65
+
66
+ ### Installation
67
+
68
+ ```bash
69
+ # Create a new GitHub repository (don't initialize with README)
70
+ # Clone it locally
71
+ git clone https://github.com/YourUsername/your-project.git
72
+ cd your-project
73
+
74
+ # Initialize with uv
75
+ uv init
76
+
77
+ # Add pyrig
78
+ uv add pyrig
79
+
80
+ # Initialize your project (this does everything!)
81
+ uv run pyrig init
82
+ ```
83
+
84
+ That's it! You now have a fully configured project with:
85
+ - Automated testing with pytest (90% coverage requirement)
86
+ - Linting and formatting with ruff (ALL rules enabled)
87
+ - Type checking with ty and mypy (strict mode)
88
+ - Security scanning with bandit
89
+ - Pre-commit hooks for quality enforcement
90
+ - GitHub Actions CI/CD workflows
91
+ - Branch protection and repository security
92
+ - Containerfile for deployment
93
+ - Custom CLI commands via `dev/cli/subcommands.py` and `dev/cli/shared_subcommands.py`
94
+
95
+ ### Next Steps
96
+
97
+ After initialization, start coding in `<package>/src/` and run:
98
+
99
+ ```bash
100
+ uv run pyrig mktests # Generate test skeletons
101
+ uv run pytest # Run tests
102
+ git add .
103
+ git commit -m "Add feature" # Pre-commit hooks run automatically
104
+ git push
105
+ ```
106
+
107
+ ## Documentation
108
+
109
+ - **[Documentation Index](docs/index.md)** - Full documentation
110
+ - **[Getting Started Guide](docs/getting-started.md)** - Complete guide to creating your first pyrig project
111
+ - **[Multi-Package Architecture](docs/multi-package-architecture.md)** - Build package ecosystems with cross-package discovery
112
+ - **[Configuration Files Reference](docs/config-files/index.md)** - Detailed documentation for every config file
113
+
114
+ ## Technology Stack
115
+
116
+ pyrig uses cutting-edge Python tooling:
117
+
118
+ - **[uv](https://github.com/astral-sh/uv)** - Fast Python package manager
119
+ - **[ruff](https://github.com/astral-sh/ruff)** - Extremely fast linter and formatter
120
+ - **[ty](https://github.com/astral-sh/ty)** - Fast type checker
121
+ - **[mypy](https://mypy-lang.org/)** - Static type checker
122
+ - **[pytest](https://pytest.org/)** - Testing framework
123
+ - **[bandit](https://github.com/PyCQA/bandit)** - Security scanner
124
+ - **[pre-commit](https://pre-commit.com/)** - Git hook framework
125
+ - **[Podman](https://podman.io/)** - Daemonless container runtime
126
+ - **[GitHub Actions](https://github.com/features/actions)** - CI/CD platform
127
+
128
+ ## What Makes pyrig Different?
129
+
130
+ Unlike cookiecutter, copier, or other project templates:
131
+
132
+ - **Living** - Configs stay synchronized automatically, not just at creation time
133
+ - **Opinionated** - Best practices enforced, not suggested
134
+ - **Comprehensive** - Handles everything from init to deployment
135
+ - **Current** - Automatically updates to latest tools and standards
136
+ - **Extensible** - Plugin architecture for custom functionality
137
+
138
+ ## Project Structure
139
+
140
+ pyrig creates a clean, organized structure:
141
+
142
+ ```
143
+ your-project/
144
+ ├── <package>/
145
+ │ ├── dev/ # Development tools (not in production)
146
+ │ ├── src/ # Your application code
147
+ │ └── resources/ # Static files
148
+ ├── tests/ # Test suite (mirrors src/)
149
+ ├── docs/ # Documentation
150
+ ├── .github/ # CI/CD workflows
151
+ └── ... # Config files
152
+ ```
153
+
154
+ ## Requirements
155
+
156
+ - Git (any recent version)
157
+ - uv package manager
158
+ - GitHub account (for CI/CD and repository protection)
159
+ - Podman (optional, for containerization)
160
+
161
+ ## Contributing
162
+
163
+ Contributions are welcome! pyrig is built with pyrig itself, so you can explore the codebase to see how it works.
164
+
165
+ ## License
166
+
167
+ MIT License - see [LICENSE](LICENSE) for details.
168
+
169
+ ## Links
170
+
171
+ - **PyPI**: [pypi.org/project/pyrig](https://pypi.org/project/pyrig/)
172
+ - **Documentation**: [docs/](docs/)
173
+ - **Issues**: [github.com/Winipedia/pyrig/issues](https://github.com/Winipedia/pyrig/issues)
174
+ - **Source**: [github.com/Winipedia/pyrig](https://github.com/Winipedia/pyrig)