pyrig 2.2.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyrig/__init__.py +1 -0
- pyrig/dev/__init__.py +6 -0
- pyrig/dev/builders/__init__.py +1 -0
- pyrig/dev/builders/base/__init__.py +5 -0
- pyrig/dev/builders/base/base.py +256 -0
- pyrig/dev/builders/pyinstaller.py +229 -0
- pyrig/dev/cli/__init__.py +5 -0
- pyrig/dev/cli/cli.py +95 -0
- pyrig/dev/cli/commands/__init__.py +1 -0
- pyrig/dev/cli/commands/build_artifacts.py +16 -0
- pyrig/dev/cli/commands/create_root.py +25 -0
- pyrig/dev/cli/commands/create_tests.py +244 -0
- pyrig/dev/cli/commands/init_project.py +160 -0
- pyrig/dev/cli/commands/make_inits.py +27 -0
- pyrig/dev/cli/commands/protect_repo.py +145 -0
- pyrig/dev/cli/shared_subcommands.py +20 -0
- pyrig/dev/cli/subcommands.py +73 -0
- pyrig/dev/configs/__init__.py +1 -0
- pyrig/dev/configs/base/__init__.py +5 -0
- pyrig/dev/configs/base/base.py +826 -0
- pyrig/dev/configs/containers/__init__.py +1 -0
- pyrig/dev/configs/containers/container_file.py +111 -0
- pyrig/dev/configs/dot_env.py +95 -0
- pyrig/dev/configs/dot_python_version.py +88 -0
- pyrig/dev/configs/git/__init__.py +5 -0
- pyrig/dev/configs/git/gitignore.py +181 -0
- pyrig/dev/configs/git/pre_commit.py +170 -0
- pyrig/dev/configs/licence.py +112 -0
- pyrig/dev/configs/markdown/__init__.py +1 -0
- pyrig/dev/configs/markdown/docs/__init__.py +1 -0
- pyrig/dev/configs/markdown/docs/index.py +38 -0
- pyrig/dev/configs/markdown/readme.py +132 -0
- pyrig/dev/configs/py_typed.py +28 -0
- pyrig/dev/configs/pyproject.py +436 -0
- pyrig/dev/configs/python/__init__.py +5 -0
- pyrig/dev/configs/python/builders_init.py +27 -0
- pyrig/dev/configs/python/configs_init.py +28 -0
- pyrig/dev/configs/python/dot_experiment.py +46 -0
- pyrig/dev/configs/python/main.py +59 -0
- pyrig/dev/configs/python/resources_init.py +27 -0
- pyrig/dev/configs/python/shared_subcommands.py +29 -0
- pyrig/dev/configs/python/src_init.py +27 -0
- pyrig/dev/configs/python/subcommands.py +27 -0
- pyrig/dev/configs/testing/__init__.py +5 -0
- pyrig/dev/configs/testing/conftest.py +64 -0
- pyrig/dev/configs/testing/fixtures_init.py +27 -0
- pyrig/dev/configs/testing/main_test.py +74 -0
- pyrig/dev/configs/testing/zero_test.py +43 -0
- pyrig/dev/configs/workflows/__init__.py +5 -0
- pyrig/dev/configs/workflows/base/__init__.py +5 -0
- pyrig/dev/configs/workflows/base/base.py +1662 -0
- pyrig/dev/configs/workflows/build.py +106 -0
- pyrig/dev/configs/workflows/health_check.py +133 -0
- pyrig/dev/configs/workflows/publish.py +68 -0
- pyrig/dev/configs/workflows/release.py +90 -0
- pyrig/dev/tests/__init__.py +5 -0
- pyrig/dev/tests/conftest.py +40 -0
- pyrig/dev/tests/fixtures/__init__.py +1 -0
- pyrig/dev/tests/fixtures/assertions.py +147 -0
- pyrig/dev/tests/fixtures/autouse/__init__.py +5 -0
- pyrig/dev/tests/fixtures/autouse/class_.py +42 -0
- pyrig/dev/tests/fixtures/autouse/module.py +40 -0
- pyrig/dev/tests/fixtures/autouse/session.py +589 -0
- pyrig/dev/tests/fixtures/factories.py +118 -0
- pyrig/dev/utils/__init__.py +1 -0
- pyrig/dev/utils/cli.py +17 -0
- pyrig/dev/utils/git.py +312 -0
- pyrig/dev/utils/packages.py +93 -0
- pyrig/dev/utils/resources.py +77 -0
- pyrig/dev/utils/testing.py +66 -0
- pyrig/dev/utils/versions.py +268 -0
- pyrig/main.py +9 -0
- pyrig/py.typed +0 -0
- pyrig/resources/GITIGNORE +216 -0
- pyrig/resources/LATEST_PYTHON_VERSION +1 -0
- pyrig/resources/MIT_LICENSE_TEMPLATE +21 -0
- pyrig/resources/__init__.py +1 -0
- pyrig/src/__init__.py +1 -0
- pyrig/src/git/__init__.py +6 -0
- pyrig/src/git/git.py +146 -0
- pyrig/src/graph.py +255 -0
- pyrig/src/iterate.py +107 -0
- pyrig/src/modules/__init__.py +22 -0
- pyrig/src/modules/class_.py +369 -0
- pyrig/src/modules/function.py +189 -0
- pyrig/src/modules/inspection.py +148 -0
- pyrig/src/modules/module.py +658 -0
- pyrig/src/modules/package.py +452 -0
- pyrig/src/os/__init__.py +6 -0
- pyrig/src/os/os.py +121 -0
- pyrig/src/project/__init__.py +5 -0
- pyrig/src/project/mgt.py +83 -0
- pyrig/src/resource.py +58 -0
- pyrig/src/string.py +100 -0
- pyrig/src/testing/__init__.py +6 -0
- pyrig/src/testing/assertions.py +66 -0
- pyrig/src/testing/convention.py +203 -0
- pyrig-2.2.6.dist-info/METADATA +174 -0
- pyrig-2.2.6.dist-info/RECORD +102 -0
- pyrig-2.2.6.dist-info/WHEEL +4 -0
- pyrig-2.2.6.dist-info/entry_points.txt +3 -0
- pyrig-2.2.6.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Version parsing and constraint utilities.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for working with Python version specifiers
|
|
4
|
+
and constraints. It wraps the `packaging` library to provide convenient
|
|
5
|
+
methods for extracting version bounds and generating version ranges.
|
|
6
|
+
|
|
7
|
+
The main class `VersionConstraint` parses PEP 440 version specifiers
|
|
8
|
+
(e.g., ">=3.8,<3.12") and provides methods to:
|
|
9
|
+
- Get inclusive/exclusive lower and upper bounds
|
|
10
|
+
- Generate lists of versions within a constraint
|
|
11
|
+
- Adjust version precision (major/minor/micro)
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> from pyrig.src.project.versions import VersionConstraint
|
|
15
|
+
>>> vc = VersionConstraint(">=3.8,<3.12")
|
|
16
|
+
>>> vc.get_lower_inclusive()
|
|
17
|
+
<Version('3.8')>
|
|
18
|
+
>>> vc.get_version_range(level="minor")
|
|
19
|
+
[<Version('3.8')>, <Version('3.9')>, <Version('3.10')>, <Version('3.11')>]
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from typing import Literal
|
|
23
|
+
|
|
24
|
+
from packaging.specifiers import SpecifierSet
|
|
25
|
+
from packaging.version import Version
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def adjust_version_to_level(
|
|
29
|
+
version: Version, level: Literal["major", "minor", "micro"]
|
|
30
|
+
) -> Version:
|
|
31
|
+
"""Truncate a version to the specified precision level.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
version: The version to adjust.
|
|
35
|
+
level: The precision level to truncate to.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
A new Version with components beyond the level removed.
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
>>> adjust_version_to_level(Version("3.11.5"), "minor")
|
|
42
|
+
<Version('3.11')>
|
|
43
|
+
"""
|
|
44
|
+
if level == "major":
|
|
45
|
+
return Version(f"{version.major}")
|
|
46
|
+
if level == "minor":
|
|
47
|
+
return Version(f"{version.major}.{version.minor}")
|
|
48
|
+
return version
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class VersionConstraint:
|
|
52
|
+
"""Parser and analyzer for PEP 440 version constraints.
|
|
53
|
+
|
|
54
|
+
Parses version specifier strings (e.g., ">=3.8,<3.12") and provides
|
|
55
|
+
methods to extract bounds and generate version ranges. Handles both
|
|
56
|
+
inclusive and exclusive bounds, converting between them as needed.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
constraint: The original constraint string.
|
|
60
|
+
spec: The cleaned specifier string (quotes stripped).
|
|
61
|
+
sset: The parsed SpecifierSet from the packaging library.
|
|
62
|
+
lower_inclusive: The effective lower bound (inclusive).
|
|
63
|
+
upper_exclusive: The effective upper bound (exclusive).
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
>>> vc = VersionConstraint(">=3.8,<3.12")
|
|
67
|
+
>>> vc.get_lower_inclusive()
|
|
68
|
+
<Version('3.8')>
|
|
69
|
+
>>> vc.get_upper_exclusive()
|
|
70
|
+
<Version('3.12')>
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, constraint: str) -> None:
|
|
74
|
+
"""Initialize a VersionConstraint from a specifier string.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
constraint: A PEP 440 version specifier string (e.g., ">=3.8,<3.12").
|
|
78
|
+
"""
|
|
79
|
+
self.constraint = constraint
|
|
80
|
+
self.spec = self.constraint.strip().strip('"').strip("'")
|
|
81
|
+
self.sset = SpecifierSet(self.spec)
|
|
82
|
+
|
|
83
|
+
self.lowers_inclusive = [
|
|
84
|
+
Version(s.version) for s in self.sset if s.operator == ">="
|
|
85
|
+
]
|
|
86
|
+
self.lowers_exclusive = [
|
|
87
|
+
Version(s.version) for s in self.sset if s.operator == ">"
|
|
88
|
+
]
|
|
89
|
+
# increment the last number of exclusive, so
|
|
90
|
+
# >3.4.1 to >=3.4.2; <3.4.0 to <=3.4.1; 3.0.0 to <=3.0.1
|
|
91
|
+
self.lowers_exclusive_to_inclusive = [
|
|
92
|
+
Version(f"{v.major}.{v.minor}.{v.micro + 1}") for v in self.lowers_exclusive
|
|
93
|
+
]
|
|
94
|
+
self.lowers_inclusive = (
|
|
95
|
+
self.lowers_inclusive + self.lowers_exclusive_to_inclusive
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
self.uppers_inclusive = [
|
|
99
|
+
Version(s.version) for s in self.sset if s.operator == "<="
|
|
100
|
+
]
|
|
101
|
+
self.uppers_exclusive = [
|
|
102
|
+
Version(s.version) for s in self.sset if s.operator == "<"
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# increment the last number of inclusive, so
|
|
106
|
+
# <=3.4.1 to <3.4.2; >=3.4.0 to >3.4.1; 3.0.0 to >3.0.1
|
|
107
|
+
self.uppers_inclusive_to_exclusive = [
|
|
108
|
+
Version(f"{v.major}.{v.minor}.{v.micro + 1}") for v in self.uppers_inclusive
|
|
109
|
+
]
|
|
110
|
+
self.uppers_exclusive = (
|
|
111
|
+
self.uppers_inclusive_to_exclusive + self.uppers_exclusive
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
self.upper_exclusive = (
|
|
115
|
+
min(self.uppers_exclusive) if self.uppers_exclusive else None
|
|
116
|
+
)
|
|
117
|
+
self.lower_inclusive = (
|
|
118
|
+
max(self.lowers_inclusive) if self.lowers_inclusive else None
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def get_lower_inclusive(
|
|
122
|
+
self, default: str | Version | None = None
|
|
123
|
+
) -> Version | None:
|
|
124
|
+
"""Get the minimum version.
|
|
125
|
+
|
|
126
|
+
Is given inclusive. E.g. >=3.8, <3.12 -> 3.8
|
|
127
|
+
if >3.7, <3.12 -> 3.7.1
|
|
128
|
+
|
|
129
|
+
E.g. >=3.8, <3.12 -> 3.8
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
default: The default value to return if there is no minimum version
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The minimum version
|
|
136
|
+
"""
|
|
137
|
+
default = str(default) if default else None
|
|
138
|
+
if self.lower_inclusive is None:
|
|
139
|
+
return Version(default) if default else None
|
|
140
|
+
|
|
141
|
+
return self.lower_inclusive
|
|
142
|
+
|
|
143
|
+
def get_upper_exclusive(
|
|
144
|
+
self, default: str | Version | None = None
|
|
145
|
+
) -> Version | None:
|
|
146
|
+
"""Get the maximum version.
|
|
147
|
+
|
|
148
|
+
Is given exclusive. E.g. >=3.8, <3.12 -> 3.12
|
|
149
|
+
if >=3.8, <=3.12 -> 3.12.1
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
default: The default value to return if there is no maximum version
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The maximum version
|
|
156
|
+
"""
|
|
157
|
+
default = str(default) if default else None
|
|
158
|
+
if self.upper_exclusive is None:
|
|
159
|
+
return Version(default) if default else None
|
|
160
|
+
|
|
161
|
+
return self.upper_exclusive
|
|
162
|
+
|
|
163
|
+
def get_upper_inclusive(
|
|
164
|
+
self, default: str | Version | None = None
|
|
165
|
+
) -> Version | None:
|
|
166
|
+
"""Get the maximum version.
|
|
167
|
+
|
|
168
|
+
Is given inclusive. E.g. >=3.8, <3.12 -> 3.11
|
|
169
|
+
if >=3.8, <=3.12 -> 3.12
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
default: The default value to return if there is no maximum version
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
The maximum version
|
|
176
|
+
"""
|
|
177
|
+
# increment the default by 1 micro to make it exclusive
|
|
178
|
+
if default:
|
|
179
|
+
default = Version(str(default))
|
|
180
|
+
default = Version(f"{default.major}.{default.minor}.{default.micro + 1}")
|
|
181
|
+
upper_exclusive = self.get_upper_exclusive(default)
|
|
182
|
+
if upper_exclusive is None:
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
if upper_exclusive.micro != 0:
|
|
186
|
+
return Version(
|
|
187
|
+
f"{upper_exclusive.major}.{upper_exclusive.minor}.{upper_exclusive.micro - 1}" # noqa: E501
|
|
188
|
+
)
|
|
189
|
+
if upper_exclusive.minor != 0:
|
|
190
|
+
return Version(f"{upper_exclusive.major}.{upper_exclusive.minor - 1}")
|
|
191
|
+
return Version(f"{upper_exclusive.major - 1}")
|
|
192
|
+
|
|
193
|
+
def get_version_range(
|
|
194
|
+
self,
|
|
195
|
+
level: Literal["major", "minor", "micro"] = "major",
|
|
196
|
+
lower_default: str | Version | None = None,
|
|
197
|
+
upper_default: str | Version | None = None,
|
|
198
|
+
) -> list[Version]:
|
|
199
|
+
"""Get the version range.
|
|
200
|
+
|
|
201
|
+
returns a range of versions according to the level
|
|
202
|
+
|
|
203
|
+
E.g. >=3.8, <3.12; level=major -> 3
|
|
204
|
+
>=3.8, <4.12; level=major -> 3, 4
|
|
205
|
+
E.g. >=3.8, <=3.12; level=minor -> 3.8, 3.9, 3.10, 3.11, 3.12
|
|
206
|
+
E.g. >=3.8.1, <=4.12.1; level=micro -> 3.8.1, 3.8.2, ... 4.12.1
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
level: The level of the version to return
|
|
210
|
+
lower_default: The default lower bound if none is specified
|
|
211
|
+
upper_default: The default upper bound if none is specified
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
A list of versions
|
|
215
|
+
"""
|
|
216
|
+
lower = self.get_lower_inclusive(lower_default)
|
|
217
|
+
upper = self.get_upper_inclusive(upper_default)
|
|
218
|
+
|
|
219
|
+
if lower is None or upper is None:
|
|
220
|
+
msg = "No lower or upper bound. Please specify default values."
|
|
221
|
+
raise ValueError(msg)
|
|
222
|
+
|
|
223
|
+
major_level, minor_level, micro_level = range(3)
|
|
224
|
+
level_int = {"major": major_level, "minor": minor_level, "micro": micro_level}[
|
|
225
|
+
level
|
|
226
|
+
]
|
|
227
|
+
lower_as_list = [lower.major, lower.minor, lower.micro]
|
|
228
|
+
upper_as_list = [upper.major, upper.minor, upper.micro]
|
|
229
|
+
|
|
230
|
+
versions: list[list[int]] = []
|
|
231
|
+
for major in range(lower_as_list[major_level], upper_as_list[major_level] + 1):
|
|
232
|
+
version = [major]
|
|
233
|
+
|
|
234
|
+
minor_lower_og, minor_upper_og = (
|
|
235
|
+
lower_as_list[minor_level],
|
|
236
|
+
upper_as_list[minor_level],
|
|
237
|
+
)
|
|
238
|
+
diff = minor_upper_og - minor_lower_og
|
|
239
|
+
minor_lower = minor_lower_og if diff >= 0 else 0
|
|
240
|
+
minor_upper = minor_upper_og if diff >= 0 else minor_lower_og + abs(diff)
|
|
241
|
+
for minor in range(
|
|
242
|
+
minor_lower,
|
|
243
|
+
minor_upper + 1,
|
|
244
|
+
):
|
|
245
|
+
# pop the minor if one already exists
|
|
246
|
+
if len(version) > minor_level:
|
|
247
|
+
version.pop()
|
|
248
|
+
|
|
249
|
+
version.append(minor)
|
|
250
|
+
|
|
251
|
+
micro_lower_og, micro_upper_og = (
|
|
252
|
+
lower_as_list[micro_level],
|
|
253
|
+
upper_as_list[micro_level],
|
|
254
|
+
)
|
|
255
|
+
diff = micro_upper_og - micro_lower_og
|
|
256
|
+
micro_lower = micro_lower_og if diff >= 0 else 0
|
|
257
|
+
micro_upper = (
|
|
258
|
+
micro_upper_og if diff >= 0 else micro_lower_og + abs(diff)
|
|
259
|
+
)
|
|
260
|
+
for micro in range(
|
|
261
|
+
micro_lower,
|
|
262
|
+
micro_upper + 1,
|
|
263
|
+
):
|
|
264
|
+
version.append(micro)
|
|
265
|
+
versions.append(version[: level_int + 1])
|
|
266
|
+
version.pop()
|
|
267
|
+
version_versions = sorted({Version(".".join(map(str, v))) for v in versions})
|
|
268
|
+
return [v for v in version_versions if self.sset.contains(v)]
|
pyrig/main.py
ADDED
pyrig/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
|
|
204
|
+
# Ruff stuff:
|
|
205
|
+
.ruff_cache/
|
|
206
|
+
|
|
207
|
+
# PyPI configuration file
|
|
208
|
+
.pypirc
|
|
209
|
+
|
|
210
|
+
# Marimo
|
|
211
|
+
marimo/_static/
|
|
212
|
+
marimo/_lsp/
|
|
213
|
+
__marimo__/
|
|
214
|
+
|
|
215
|
+
# Streamlit
|
|
216
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14.2
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [year] [fullname]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""__init__ module."""
|
pyrig/src/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""src package."""
|
pyrig/src/git/git.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""GitHub repository utilities for token management and URL parsing.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for working with GitHub repositories,
|
|
4
|
+
including authentication token retrieval, GitHub Actions environment
|
|
5
|
+
detection, and repository URL parsing.
|
|
6
|
+
|
|
7
|
+
The token retrieval supports both environment variables and .env files,
|
|
8
|
+
following a priority order that prefers environment variables for CI/CD
|
|
9
|
+
compatibility.
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
>>> from pyrig.src.git.github.github import get_repo_owner_and_name_from_git
|
|
13
|
+
>>> owner, repo = get_repo_owner_and_name_from_git()
|
|
14
|
+
>>> print(f"{owner}/{repo}")
|
|
15
|
+
myorg/myrepo
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from subprocess import CompletedProcess # nosec: B404
|
|
21
|
+
|
|
22
|
+
from pyrig.src.modules.package import get_project_name_from_cwd
|
|
23
|
+
from pyrig.src.os.os import run_subprocess
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def running_in_github_actions() -> bool:
|
|
27
|
+
"""Check if the code is running inside a GitHub Actions workflow.
|
|
28
|
+
|
|
29
|
+
GitHub Actions sets the `GITHUB_ACTIONS` environment variable to "true"
|
|
30
|
+
in all workflow runs. This function checks for that variable.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
True if running in GitHub Actions, False otherwise.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> if running_in_github_actions():
|
|
37
|
+
... print("Running in CI")
|
|
38
|
+
... else:
|
|
39
|
+
... print("Running locally")
|
|
40
|
+
"""
|
|
41
|
+
return os.getenv("GITHUB_ACTIONS", "false") == "true"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_repo_url_from_git(*, check: bool = True) -> str:
|
|
45
|
+
"""Get the remote origin URL from the local git repository.
|
|
46
|
+
|
|
47
|
+
Executes `git config --get remote.origin.url` to retrieve the URL
|
|
48
|
+
of the origin remote.
|
|
49
|
+
Url can be:
|
|
50
|
+
- https://github.com/owner/repo.git
|
|
51
|
+
- git@github.com:owner/repo.git
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
check: Whether to check succes in subprocess.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The remote origin URL (e.g., "https://github.com/owner/repo.git"
|
|
58
|
+
or "git@github.com:owner/repo.git").
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
subprocess.CalledProcessError: If not in a git repository or if
|
|
62
|
+
the origin remote is not configured.
|
|
63
|
+
"""
|
|
64
|
+
stdout: str = run_subprocess(
|
|
65
|
+
["git", "config", "--get", "remote.origin.url"], check=check
|
|
66
|
+
).stdout.decode("utf-8")
|
|
67
|
+
return stdout.strip()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_git_username() -> str:
|
|
71
|
+
"""Get the git username from the local git config.
|
|
72
|
+
|
|
73
|
+
Executes `git config --get user.name` to retrieve the username.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The git username.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
subprocess.CalledProcessError: If the username cannot be read.
|
|
80
|
+
"""
|
|
81
|
+
stdout: str = run_subprocess(["git", "config", "--get", "user.name"]).stdout.decode(
|
|
82
|
+
"utf-8"
|
|
83
|
+
)
|
|
84
|
+
return stdout.strip()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_repo_owner_and_name_from_git(*, check_repo_url: bool = True) -> tuple[str, str]:
|
|
88
|
+
"""Extract the GitHub owner and repository name from the git remote.
|
|
89
|
+
|
|
90
|
+
Parses the remote origin URL to extract the owner (organization or user)
|
|
91
|
+
and repository name. Handles both HTTPS and SSH URL formats.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
check_repo_url: Whether to check succes in subprocess.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
A tuple of (owner, repository_name).
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
subprocess.CalledProcessError: If the git remote cannot be read.
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
>>> owner, repo = get_repo_owner_and_name_from_git()
|
|
104
|
+
>>> print(f"{owner}/{repo}")
|
|
105
|
+
myorg/myrepo
|
|
106
|
+
"""
|
|
107
|
+
url = get_repo_url_from_git(check=check_repo_url)
|
|
108
|
+
if not url:
|
|
109
|
+
# we default to git username and repo name from cwd
|
|
110
|
+
owner = get_git_username()
|
|
111
|
+
repo = get_project_name_from_cwd()
|
|
112
|
+
return owner, repo
|
|
113
|
+
|
|
114
|
+
parts = url.removesuffix(".git").split("/")
|
|
115
|
+
# keep last two parts
|
|
116
|
+
owner, repo = parts[-2:]
|
|
117
|
+
if ":" in owner:
|
|
118
|
+
owner = owner.split(":")[-1]
|
|
119
|
+
return owner, repo
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_git_unstaged_changes() -> str:
|
|
123
|
+
"""Check if the git repository has uncommitted changes.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The output of git diff
|
|
127
|
+
"""
|
|
128
|
+
completed_process = run_subprocess(["git", "diff"])
|
|
129
|
+
unstaged_changes: str = completed_process.stdout.decode("utf-8")
|
|
130
|
+
return unstaged_changes
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def git_add_file(path: Path, *, check: bool = True) -> CompletedProcess[bytes]:
|
|
134
|
+
"""Add a file to the git index.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
path: Path to the file to add.
|
|
138
|
+
check: Whether to check succes in subprocess.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
The completed process result.
|
|
142
|
+
"""
|
|
143
|
+
# make path relative to cwd if it is absolute
|
|
144
|
+
if path.is_absolute():
|
|
145
|
+
path = path.relative_to(Path.cwd())
|
|
146
|
+
return run_subprocess(["git", "add", str(path)], check=check)
|