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,106 @@
|
|
|
1
|
+
"""GitHub Actions workflow for building artifacts.
|
|
2
|
+
|
|
3
|
+
This module provides the BuildWorkflow class for creating
|
|
4
|
+
a workflow that builds artifacts across OS matrix after
|
|
5
|
+
successful health checks on main branch.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from pyrig.dev.configs.workflows.base.base import Workflow
|
|
11
|
+
from pyrig.dev.configs.workflows.health_check import HealthCheckWorkflow
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BuildWorkflow(Workflow):
|
|
15
|
+
"""Workflow for building project artifacts.
|
|
16
|
+
|
|
17
|
+
Triggers after health check workflow completes on main branch.
|
|
18
|
+
Builds artifacts across OS matrix and uploads them for the
|
|
19
|
+
release workflow to use.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def get_workflow_triggers(cls) -> dict[str, Any]:
|
|
24
|
+
"""Get the workflow triggers.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Trigger for health check completion on main.
|
|
28
|
+
"""
|
|
29
|
+
triggers = super().get_workflow_triggers()
|
|
30
|
+
triggers.update(
|
|
31
|
+
cls.on_workflow_run(
|
|
32
|
+
workflows=[HealthCheckWorkflow.get_workflow_name()],
|
|
33
|
+
branches=["main"],
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
return triggers
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def get_jobs(cls) -> dict[str, Any]:
|
|
40
|
+
"""Get the workflow jobs.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Dict with build job.
|
|
44
|
+
"""
|
|
45
|
+
jobs: dict[str, Any] = {}
|
|
46
|
+
jobs.update(cls.job_build_artifacts())
|
|
47
|
+
jobs.update(cls.job_build_container_image())
|
|
48
|
+
return jobs
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def job_build_artifacts(cls) -> dict[str, Any]:
|
|
52
|
+
"""Get the build job that runs across OS matrix.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Job configuration for building artifacts.
|
|
56
|
+
"""
|
|
57
|
+
return cls.get_job(
|
|
58
|
+
job_func=cls.job_build_artifacts,
|
|
59
|
+
if_condition=cls.if_workflow_run_is_success(),
|
|
60
|
+
strategy=cls.strategy_matrix_os(),
|
|
61
|
+
runs_on=cls.insert_matrix_os(),
|
|
62
|
+
steps=cls.steps_build_artifacts(),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def job_build_container_image(cls) -> dict[str, Any]:
|
|
67
|
+
"""Get the build job that builds the container image.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Job configuration for building container image.
|
|
71
|
+
"""
|
|
72
|
+
return cls.get_job(
|
|
73
|
+
job_func=cls.job_build_container_image,
|
|
74
|
+
if_condition=cls.if_workflow_run_is_success(),
|
|
75
|
+
runs_on=cls.UBUNTU_LATEST,
|
|
76
|
+
steps=cls.steps_build_container_image(),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def steps_build_artifacts(cls) -> list[dict[str, Any]]:
|
|
81
|
+
"""Get the steps for building artifacts.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
List of build steps, or placeholder if no builders defined.
|
|
85
|
+
"""
|
|
86
|
+
return [
|
|
87
|
+
*cls.steps_core_matrix_setup(),
|
|
88
|
+
cls.step_build_artifacts(),
|
|
89
|
+
cls.step_upload_artifacts(),
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def steps_build_container_image(cls) -> list[dict[str, Any]]:
|
|
94
|
+
"""Get the steps for building the container image.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
List of build steps.
|
|
98
|
+
"""
|
|
99
|
+
return [
|
|
100
|
+
cls.step_checkout_repository(),
|
|
101
|
+
cls.step_install_container_engine(),
|
|
102
|
+
cls.step_build_container_image(),
|
|
103
|
+
cls.step_make_dist_folder(),
|
|
104
|
+
cls.step_save_container_image(),
|
|
105
|
+
cls.step_upload_artifacts(name="container-image"),
|
|
106
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""GitHub Actions workflow for health checks and CI.
|
|
2
|
+
|
|
3
|
+
This module provides the HealthCheckWorkflow class for creating
|
|
4
|
+
a workflow that runs on pull requests, pushes, and scheduled intervals
|
|
5
|
+
to verify code quality and run tests.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import UTC, datetime, timedelta
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import pyrig
|
|
12
|
+
from pyrig.dev.configs.workflows.base.base import Workflow
|
|
13
|
+
from pyrig.dev.utils.packages import get_src_package
|
|
14
|
+
from pyrig.src.modules.package import DependencyGraph
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HealthCheckWorkflow(Workflow):
|
|
18
|
+
"""Workflow for continuous integration health checks.
|
|
19
|
+
|
|
20
|
+
Triggers on pull requests, pushes to main, and scheduled intervals.
|
|
21
|
+
Runs linting, type checking, security scanning, and tests across
|
|
22
|
+
a matrix of OS and Python versions.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
BASE_CRON_HOUR = 0
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def get_workflow_triggers(cls) -> dict[str, Any]:
|
|
29
|
+
"""Get the workflow triggers.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Triggers for pull requests, pushes, and scheduled runs.
|
|
33
|
+
"""
|
|
34
|
+
triggers = super().get_workflow_triggers()
|
|
35
|
+
triggers.update(cls.on_pull_request())
|
|
36
|
+
triggers.update(cls.on_push())
|
|
37
|
+
triggers.update(cls.on_schedule(cron=cls.get_staggered_cron()))
|
|
38
|
+
return triggers
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def get_staggered_cron(cls) -> str:
|
|
42
|
+
"""Get a staggered cron schedule based on dependency depth.
|
|
43
|
+
|
|
44
|
+
Packages with more dependencies run later to avoid conflicts
|
|
45
|
+
when dependencies release right before dependent packages.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Cron expression with hour offset based on dependency depth.
|
|
49
|
+
"""
|
|
50
|
+
offset = cls.get_dependency_offset()
|
|
51
|
+
base_time = datetime.now(tz=UTC).replace(
|
|
52
|
+
hour=cls.BASE_CRON_HOUR, minute=0, second=0, microsecond=0
|
|
53
|
+
)
|
|
54
|
+
scheduled_time = base_time + timedelta(hours=offset)
|
|
55
|
+
return f"0 {scheduled_time.hour} * * *"
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def get_dependency_offset(cls) -> int:
|
|
59
|
+
"""Calculate hour offset based on dependency depth to pyrig.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Number of hours to offset from base cron hour.
|
|
63
|
+
"""
|
|
64
|
+
graph = DependencyGraph()
|
|
65
|
+
src_pkg = get_src_package()
|
|
66
|
+
return graph.shortest_path_length(src_pkg.__name__, pyrig.__name__)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def get_jobs(cls) -> dict[str, Any]:
|
|
70
|
+
"""Get the workflow jobs.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dict with matrix and aggregation jobs.
|
|
74
|
+
"""
|
|
75
|
+
jobs: dict[str, Any] = {}
|
|
76
|
+
jobs.update(cls.job_health_check_matrix())
|
|
77
|
+
jobs.update(cls.job_health_check())
|
|
78
|
+
return jobs
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def job_health_check_matrix(cls) -> dict[str, Any]:
|
|
82
|
+
"""Get the matrix job that runs across OS and Python versions.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Job configuration for matrix testing.
|
|
86
|
+
"""
|
|
87
|
+
return cls.get_job(
|
|
88
|
+
job_func=cls.job_health_check_matrix,
|
|
89
|
+
strategy=cls.strategy_matrix_os_and_python_version(),
|
|
90
|
+
runs_on=cls.insert_matrix_os(),
|
|
91
|
+
steps=cls.steps_health_check_matrix(),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def job_health_check(cls) -> dict[str, Any]:
|
|
96
|
+
"""Get the aggregation job that depends on matrix completion.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Job configuration for result aggregation.
|
|
100
|
+
"""
|
|
101
|
+
return cls.get_job(
|
|
102
|
+
job_func=cls.job_health_check,
|
|
103
|
+
needs=[cls.make_id_from_func(cls.job_health_check_matrix)],
|
|
104
|
+
steps=cls.steps_aggregate_matrix_results(),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def steps_health_check_matrix(cls) -> list[dict[str, Any]]:
|
|
109
|
+
"""Get the steps for the matrix health check job.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
List of steps for setup, linting, and testing.
|
|
113
|
+
"""
|
|
114
|
+
return [
|
|
115
|
+
*cls.steps_core_matrix_setup(
|
|
116
|
+
python_version=cls.insert_matrix_python_version(),
|
|
117
|
+
),
|
|
118
|
+
cls.step_protect_repository(),
|
|
119
|
+
cls.step_run_pre_commit_hooks(),
|
|
120
|
+
cls.step_run_tests(),
|
|
121
|
+
cls.step_upload_coverage_report(),
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def steps_aggregate_matrix_results(cls) -> list[dict[str, Any]]:
|
|
126
|
+
"""Get the steps for aggregating matrix results.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
List with the aggregation step.
|
|
130
|
+
"""
|
|
131
|
+
return [
|
|
132
|
+
cls.step_aggregate_matrix_results(),
|
|
133
|
+
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""GitHub Actions workflow for publishing to PyPI.
|
|
2
|
+
|
|
3
|
+
This module provides the PublishWorkflow class for creating
|
|
4
|
+
a workflow that publishes the package to PyPI after a successful release.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pyrig.dev.configs.workflows.base.base import Workflow
|
|
10
|
+
from pyrig.dev.configs.workflows.release import ReleaseWorkflow
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PublishWorkflow(Workflow):
|
|
14
|
+
"""Workflow for publishing packages to PyPI.
|
|
15
|
+
|
|
16
|
+
Triggers after the release workflow completes successfully.
|
|
17
|
+
Builds a wheel and publishes it to PyPI.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def get_workflow_triggers(cls) -> dict[str, Any]:
|
|
22
|
+
"""Get the workflow triggers.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Trigger for release workflow completion.
|
|
26
|
+
"""
|
|
27
|
+
triggers = super().get_workflow_triggers()
|
|
28
|
+
triggers.update(
|
|
29
|
+
cls.on_workflow_run(workflows=[ReleaseWorkflow.get_workflow_name()])
|
|
30
|
+
)
|
|
31
|
+
return triggers
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def get_jobs(cls) -> dict[str, Any]:
|
|
35
|
+
"""Get the workflow jobs.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dict with the publish job.
|
|
39
|
+
"""
|
|
40
|
+
jobs: dict[str, Any] = {}
|
|
41
|
+
jobs.update(cls.job_publish())
|
|
42
|
+
return jobs
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def job_publish(cls) -> dict[str, Any]:
|
|
46
|
+
"""Get the publish job configuration.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Job that builds and publishes to PyPI.
|
|
50
|
+
"""
|
|
51
|
+
return cls.get_job(
|
|
52
|
+
job_func=cls.job_publish,
|
|
53
|
+
steps=cls.steps_publish(),
|
|
54
|
+
if_condition=cls.if_workflow_run_is_success(),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def steps_publish(cls) -> list[dict[str, Any]]:
|
|
59
|
+
"""Get the steps for publishing.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
List of steps for setup, build, and publish.
|
|
63
|
+
"""
|
|
64
|
+
return [
|
|
65
|
+
*cls.steps_core_setup(),
|
|
66
|
+
cls.step_build_wheel(),
|
|
67
|
+
cls.step_publish_to_pypi(),
|
|
68
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""GitHub Actions workflow for creating releases.
|
|
2
|
+
|
|
3
|
+
This module provides the ReleaseWorkflow class for creating
|
|
4
|
+
a workflow that creates tags and publishes GitHub releases
|
|
5
|
+
after successful build workflow completion.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from pyrig.dev.configs.workflows.base.base import Workflow
|
|
11
|
+
from pyrig.dev.configs.workflows.build import BuildWorkflow
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ReleaseWorkflow(Workflow):
|
|
15
|
+
"""Workflow for creating GitHub releases.
|
|
16
|
+
|
|
17
|
+
Triggers after build workflow completes successfully.
|
|
18
|
+
Downloads artifacts from the build workflow, creates version tags,
|
|
19
|
+
generates changelogs, and publishes GitHub releases.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def get_workflow_triggers(cls) -> dict[str, Any]:
|
|
24
|
+
"""Get the workflow triggers.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Trigger for build workflow completion.
|
|
28
|
+
"""
|
|
29
|
+
triggers = super().get_workflow_triggers()
|
|
30
|
+
triggers.update(
|
|
31
|
+
cls.on_workflow_run(
|
|
32
|
+
workflows=[BuildWorkflow.get_workflow_name()],
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
return triggers
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def get_permissions(cls) -> dict[str, Any]:
|
|
39
|
+
"""Get the workflow permissions.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Permissions with write access for creating releases.
|
|
43
|
+
"""
|
|
44
|
+
permissions = super().get_permissions()
|
|
45
|
+
permissions["contents"] = "write"
|
|
46
|
+
permissions["actions"] = "read"
|
|
47
|
+
return permissions
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def get_jobs(cls) -> dict[str, Any]:
|
|
51
|
+
"""Get the workflow jobs.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dict with release job.
|
|
55
|
+
"""
|
|
56
|
+
jobs: dict[str, Any] = {}
|
|
57
|
+
jobs.update(cls.job_release())
|
|
58
|
+
return jobs
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def job_release(cls) -> dict[str, Any]:
|
|
62
|
+
"""Get the release job that creates the GitHub release.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Job configuration for creating releases.
|
|
66
|
+
"""
|
|
67
|
+
return cls.get_job(
|
|
68
|
+
job_func=cls.job_release,
|
|
69
|
+
if_condition=cls.if_workflow_run_is_success(),
|
|
70
|
+
steps=cls.steps_release(),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def steps_release(cls) -> list[dict[str, Any]]:
|
|
75
|
+
"""Get the steps for creating the release.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of steps for tagging, changelog, and release creation.
|
|
79
|
+
"""
|
|
80
|
+
return [
|
|
81
|
+
*cls.steps_core_installed_setup(repo_token=True),
|
|
82
|
+
cls.step_run_pre_commit_hooks(),
|
|
83
|
+
cls.step_commit_added_changes(),
|
|
84
|
+
cls.step_push_commits(),
|
|
85
|
+
cls.step_create_and_push_tag(),
|
|
86
|
+
cls.step_extract_version(),
|
|
87
|
+
cls.step_download_artifacts_from_workflow_run(),
|
|
88
|
+
cls.step_build_changelog(),
|
|
89
|
+
cls.step_create_release(),
|
|
90
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Pytest configuration for pyrig tests.
|
|
2
|
+
|
|
3
|
+
This module automatically discovers and registers pytest plugins from all
|
|
4
|
+
packages that depend on pyrig. It finds fixtures modules across the dependency
|
|
5
|
+
graph and adds them to pytest_plugins for automatic fixture availability.
|
|
6
|
+
|
|
7
|
+
The discovery process:
|
|
8
|
+
1. Finds all packages depending on pyrig
|
|
9
|
+
2. Locates their fixtures modules
|
|
10
|
+
3. Collects all Python files within those modules
|
|
11
|
+
4. Registers them as pytest plugins
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import pyrig
|
|
17
|
+
from pyrig.dev.tests import fixtures
|
|
18
|
+
from pyrig.src.modules.module import (
|
|
19
|
+
get_same_modules_from_deps_depen_on_dep,
|
|
20
|
+
to_module_name,
|
|
21
|
+
to_path,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# find the fixtures module in all packages that depend on pyrig
|
|
25
|
+
# and add all paths to pytest_plugins
|
|
26
|
+
fixtures_pkgs = get_same_modules_from_deps_depen_on_dep(fixtures, pyrig)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
pytest_plugin_paths: list[Path] = []
|
|
30
|
+
for pkg in fixtures_pkgs:
|
|
31
|
+
absolute_path = Path(pkg.__path__[0])
|
|
32
|
+
relative_path = to_path(pkg.__name__, is_package=True)
|
|
33
|
+
|
|
34
|
+
pkg_root = Path(absolute_path.as_posix().removesuffix(relative_path.as_posix()))
|
|
35
|
+
|
|
36
|
+
for path in absolute_path.rglob("*.py"):
|
|
37
|
+
rel_plugin_path = path.relative_to(pkg_root)
|
|
38
|
+
pytest_plugin_paths.append(rel_plugin_path)
|
|
39
|
+
|
|
40
|
+
pytest_plugins = [to_module_name(path) for path in pytest_plugin_paths]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""__init__ module."""
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Fixtures that assert some state or condition."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import runpy
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from importlib import import_module
|
|
8
|
+
from types import ModuleType
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
from pytest_mock import MockerFixture
|
|
13
|
+
|
|
14
|
+
from pyrig import main
|
|
15
|
+
from pyrig.dev.cli.commands.create_tests import make_test_skeletons
|
|
16
|
+
from pyrig.dev.configs.pyproject import PyprojectConfigFile
|
|
17
|
+
from pyrig.dev.configs.python.main import MainConfigFile
|
|
18
|
+
from pyrig.dev.utils.testing import session_fixture
|
|
19
|
+
from pyrig.src.modules.module import (
|
|
20
|
+
get_module_content_as_str,
|
|
21
|
+
get_module_name_replacing_start_module,
|
|
22
|
+
get_objs_from_obj,
|
|
23
|
+
make_obj_importpath,
|
|
24
|
+
)
|
|
25
|
+
from pyrig.src.os.os import run_subprocess
|
|
26
|
+
from pyrig.src.project.mgt import PROJECT_MGT_RUN_ARGS
|
|
27
|
+
from pyrig.src.testing.assertions import assert_with_msg
|
|
28
|
+
from pyrig.src.testing.convention import (
|
|
29
|
+
get_obj_from_test_obj,
|
|
30
|
+
make_summary_error_msg,
|
|
31
|
+
make_test_obj_importpath_from_obj,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@session_fixture
|
|
38
|
+
def assert_no_untested_objs() -> Callable[
|
|
39
|
+
[ModuleType | type | Callable[..., Any]], None
|
|
40
|
+
]:
|
|
41
|
+
"""Fixture that asserts that all objects of an object have corresponding tests.
|
|
42
|
+
|
|
43
|
+
This fixture provides a function that can be called to assert that all objects
|
|
44
|
+
(functions, classes, or methods) in a given module, class, or function have
|
|
45
|
+
corresponding test objects in the test module, class, or function.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def _assert_no_untested_objs(
|
|
50
|
+
test_obj: ModuleType | type | Callable[..., Any],
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Assert that all objects in the source have corresponding test objects.
|
|
53
|
+
|
|
54
|
+
This function verifies that every object (function, class, or method) in the
|
|
55
|
+
source module or class has a corresponding test object
|
|
56
|
+
in the test module or class.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
test_obj: The test object (module, class, or function) to check
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
AssertionError: If any object lacks a corresponding test object,
|
|
63
|
+
with a detailed error message listing the untested objects
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
test_objs = get_objs_from_obj(test_obj)
|
|
67
|
+
test_objs_paths = {make_obj_importpath(obj) for obj in test_objs}
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
obj = get_obj_from_test_obj(test_obj)
|
|
71
|
+
except ImportError:
|
|
72
|
+
if isinstance(test_obj, ModuleType):
|
|
73
|
+
# we skip if module not found bc that means it has custom tests
|
|
74
|
+
# and is not part of the mirrored structure
|
|
75
|
+
logger.warning("No source module found for %s, skipping", test_obj)
|
|
76
|
+
return
|
|
77
|
+
raise
|
|
78
|
+
objs = get_objs_from_obj(obj)
|
|
79
|
+
test_obj_path_to_obj = {
|
|
80
|
+
make_test_obj_importpath_from_obj(obj): obj for obj in objs
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
missing_test_obj_path_to_obj = {
|
|
84
|
+
test_path: obj
|
|
85
|
+
for test_path, obj in test_obj_path_to_obj.items()
|
|
86
|
+
if test_path not in test_objs_paths
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# get the modules of these obj
|
|
90
|
+
if missing_test_obj_path_to_obj:
|
|
91
|
+
make_test_skeletons()
|
|
92
|
+
|
|
93
|
+
msg = f"""Found missing tests. Tests skeletons were automatically created for:
|
|
94
|
+
{make_summary_error_msg(missing_test_obj_path_to_obj.keys())}
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
assert_with_msg(
|
|
98
|
+
not missing_test_obj_path_to_obj,
|
|
99
|
+
msg,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return _assert_no_untested_objs
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.fixture
|
|
106
|
+
def main_test_fixture(mocker: MockerFixture) -> None:
|
|
107
|
+
"""Fixture for testing main."""
|
|
108
|
+
project_name = PyprojectConfigFile.get_project_name()
|
|
109
|
+
src_package_name = PyprojectConfigFile.get_package_name()
|
|
110
|
+
|
|
111
|
+
cmds = [
|
|
112
|
+
[*PROJECT_MGT_RUN_ARGS, project_name, "--help"],
|
|
113
|
+
[*PROJECT_MGT_RUN_ARGS, project_name, main.main.__name__, "--help"],
|
|
114
|
+
]
|
|
115
|
+
success = False
|
|
116
|
+
for cmd in cmds:
|
|
117
|
+
completed_process = run_subprocess(cmd, check=False)
|
|
118
|
+
if completed_process.returncode == 0:
|
|
119
|
+
success = True
|
|
120
|
+
break
|
|
121
|
+
else:
|
|
122
|
+
cmd_strs = [" ".join(cmd) for cmd in cmds]
|
|
123
|
+
assert_with_msg(
|
|
124
|
+
success,
|
|
125
|
+
f"Expected {main.main.__name__} to be callable by one of {cmd_strs}",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
main_module_name = get_module_name_replacing_start_module(main, src_package_name)
|
|
129
|
+
main_module = import_module(main_module_name)
|
|
130
|
+
main_mock = mocker.patch.object(main_module, main.main.__name__)
|
|
131
|
+
main_module.main()
|
|
132
|
+
assert_with_msg(
|
|
133
|
+
main_mock.call_count == 1,
|
|
134
|
+
f"Expected main to be called, got {main_mock.call_count}",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# must run main module directly as __main__
|
|
138
|
+
# so that pytest-cov sees that it calls main
|
|
139
|
+
# remove module if already imported, so run_module reloads it
|
|
140
|
+
del sys.modules[main_module_name]
|
|
141
|
+
# run module as __main__, pytest-cov will see it
|
|
142
|
+
# run only if file content is the same as pyrig.main
|
|
143
|
+
main_module_content = get_module_content_as_str(main_module)
|
|
144
|
+
config_main_module_content = MainConfigFile.get_content_str()
|
|
145
|
+
|
|
146
|
+
if main_module_content == config_main_module_content:
|
|
147
|
+
runpy.run_module(main_module_name, run_name="__main__")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Class-level test fixtures and utilities.
|
|
2
|
+
|
|
3
|
+
These fixtures in this module are automatically applied to all test classes
|
|
4
|
+
through pytest's autouse mechanism. Pyrig automatically adds this module to
|
|
5
|
+
pytest_plugins in conftest.py. However you still have decorate the fixture
|
|
6
|
+
with @autouse_class_fixture from pyrig.src.testing.fixtures or with pytest's
|
|
7
|
+
autouse mechanism @pytest.fixture(scope="class", autouse=True).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from types import ModuleType
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from pyrig.dev.utils.testing import autouse_class_fixture
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@autouse_class_fixture
|
|
20
|
+
def assert_all_methods_tested(
|
|
21
|
+
request: pytest.FixtureRequest,
|
|
22
|
+
assert_no_untested_objs: Callable[[ModuleType | type | Callable[..., Any]], None],
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Verify that all methods in a class have corresponding tests.
|
|
25
|
+
|
|
26
|
+
This fixture runs automatically for each test class and checks that every
|
|
27
|
+
method defined in the corresponding source class has a test method defined
|
|
28
|
+
in the test class.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
request: The pytest fixture request object containing the current class
|
|
32
|
+
assert_no_untested_objs: The assert_no_untested_objs fixture asserts
|
|
33
|
+
that all objects have corresponding tests
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
AssertionError: If any method in the source class lacks a test
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
class_ = request.node.cls
|
|
40
|
+
if class_ is None:
|
|
41
|
+
return
|
|
42
|
+
assert_no_untested_objs(class_)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Module-level test fixtures and utilities.
|
|
2
|
+
|
|
3
|
+
These fixtures in this module are automatically applied to all test modules
|
|
4
|
+
through pytest's autouse mechanism. Pyrig automatically adds this module to
|
|
5
|
+
pytest_plugins in conftest.py. However you still have decorate the fixture
|
|
6
|
+
with @autouse_module_fixture from pyrig.src.testing.fixtures or with pytest's
|
|
7
|
+
autouse mechanism @pytest.fixture(scope="module", autouse=True).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from types import ModuleType
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from pyrig.dev.utils.testing import autouse_module_fixture
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@autouse_module_fixture
|
|
20
|
+
def assert_all_funcs_and_classes_tested(
|
|
21
|
+
request: pytest.FixtureRequest,
|
|
22
|
+
assert_no_untested_objs: Callable[[ModuleType | type | Callable[..., Any]], None],
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Verify that all functions and classes in a module have corresponding tests.
|
|
25
|
+
|
|
26
|
+
This fixture runs automatically for each test module and checks that every
|
|
27
|
+
function and class defined in the corresponding source module has a test
|
|
28
|
+
function or class defined in the test module.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
request: The pytest fixture request object containing the current module
|
|
32
|
+
assert_no_untested_objs: The assert_no_untested_objs fixture asserts
|
|
33
|
+
that all objects have corresponding tests
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
AssertionError: If any function or class in the source module lacks a test
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
module: ModuleType = request.module
|
|
40
|
+
assert_no_untested_objs(module)
|