winipedia-utils 0.3.43__py3-none-any.whl → 0.4.18__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.

Potentially problematic release.


This version of winipedia-utils might be problematic. Click here for more details.

Files changed (33) hide show
  1. winipedia_utils/git/github/repo/__init__.py +1 -0
  2. winipedia_utils/git/github/repo/protect.py +104 -0
  3. winipedia_utils/git/github/repo/repo.py +205 -0
  4. winipedia_utils/git/github/workflows/base/__init__.py +1 -0
  5. winipedia_utils/git/{workflows → github/workflows}/base/base.py +118 -54
  6. winipedia_utils/git/github/workflows/health_check.py +57 -0
  7. winipedia_utils/git/{workflows → github/workflows}/publish.py +11 -8
  8. winipedia_utils/git/github/workflows/release.py +45 -0
  9. winipedia_utils/git/gitignore/config.py +49 -29
  10. winipedia_utils/git/gitignore/gitignore.py +1 -1
  11. winipedia_utils/git/pre_commit/config.py +18 -13
  12. winipedia_utils/git/pre_commit/hooks.py +22 -4
  13. winipedia_utils/git/pre_commit/run_hooks.py +2 -1
  14. winipedia_utils/iterating/iterate.py +3 -4
  15. winipedia_utils/modules/module.py +2 -0
  16. winipedia_utils/modules/package.py +2 -1
  17. winipedia_utils/projects/poetry/config.py +74 -36
  18. winipedia_utils/projects/project.py +2 -2
  19. winipedia_utils/setup.py +2 -0
  20. winipedia_utils/testing/config.py +83 -29
  21. winipedia_utils/testing/tests/base/fixtures/fixture.py +36 -0
  22. winipedia_utils/testing/tests/base/fixtures/scopes/module.py +6 -5
  23. winipedia_utils/testing/tests/base/fixtures/scopes/session.py +7 -8
  24. winipedia_utils/testing/tests/base/utils/utils.py +43 -2
  25. winipedia_utils/text/config.py +84 -37
  26. {winipedia_utils-0.3.43.dist-info → winipedia_utils-0.4.18.dist-info}/METADATA +23 -8
  27. {winipedia_utils-0.3.43.dist-info → winipedia_utils-0.4.18.dist-info}/RECORD +31 -27
  28. winipedia_utils/git/workflows/health_check.py +0 -51
  29. winipedia_utils/git/workflows/release.py +0 -33
  30. /winipedia_utils/git/{workflows/base → github}/__init__.py +0 -0
  31. /winipedia_utils/git/{workflows → github/workflows}/__init__.py +0 -0
  32. {winipedia_utils-0.3.43.dist-info → winipedia_utils-0.4.18.dist-info}/WHEEL +0 -0
  33. {winipedia_utils-0.3.43.dist-info → winipedia_utils-0.4.18.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1 @@
1
+ """__init__ module."""
@@ -0,0 +1,104 @@
1
+ """Script to protect the repo and branches of a repository."""
2
+
3
+ from typing import Any
4
+
5
+ from winipedia_utils.git.github.repo.repo import (
6
+ DEFAULT_BRANCH,
7
+ DEFAULT_RULESET_NAME,
8
+ create_or_update_ruleset,
9
+ get_repo,
10
+ get_rules_payload,
11
+ )
12
+ from winipedia_utils.git.github.workflows.health_check import HealthCheckWorkflow
13
+ from winipedia_utils.modules.package import get_src_package
14
+ from winipedia_utils.projects.poetry.config import PyprojectConfigFile
15
+ from winipedia_utils.testing.tests.base.utils.utils import get_github_repo_token
16
+
17
+
18
+ def protect_repository() -> None:
19
+ """Protect the repository."""
20
+ set_secure_repo_settings()
21
+ create_or_update_default_branch_ruleset()
22
+
23
+
24
+ def set_secure_repo_settings() -> None:
25
+ """Set standard settings for the repository."""
26
+ src_pkg_name = get_src_package().__name__
27
+ owner = PyprojectConfigFile.get_main_author_name()
28
+ token = get_github_repo_token()
29
+ repo = get_repo(token, owner, src_pkg_name)
30
+
31
+ toml_description = PyprojectConfigFile.load()["project"]["description"]
32
+
33
+ repo.edit(
34
+ name=src_pkg_name,
35
+ description=toml_description,
36
+ default_branch=DEFAULT_BRANCH,
37
+ delete_branch_on_merge=True,
38
+ allow_update_branch=True,
39
+ allow_merge_commit=False,
40
+ allow_rebase_merge=True,
41
+ allow_squash_merge=True,
42
+ )
43
+
44
+
45
+ def create_or_update_default_branch_ruleset() -> None:
46
+ """Add a branch protection rule to the repository."""
47
+ create_or_update_ruleset(
48
+ **get_default_ruleset_params(),
49
+ )
50
+
51
+
52
+ def get_default_ruleset_params() -> dict[str, Any]:
53
+ """Get the default ruleset parameters."""
54
+ src_pkg_name = get_src_package().__name__
55
+ token = get_github_repo_token()
56
+
57
+ rules = get_rules_payload(
58
+ deletion={},
59
+ non_fast_forward={},
60
+ creation={},
61
+ update={},
62
+ pull_request={
63
+ "required_approving_review_count": 1,
64
+ "dismiss_stale_reviews_on_push": True,
65
+ "require_code_owner_review": True,
66
+ "require_last_push_approval": True,
67
+ "required_review_thread_resolution": True,
68
+ "automatic_copilot_code_review_enabled": False,
69
+ "allowed_merge_methods": ["merge", "squash", "rebase"],
70
+ },
71
+ required_linear_history={},
72
+ required_signatures={},
73
+ required_status_checks={
74
+ "strict_required_status_checks_policy": True,
75
+ "do_not_enforce_on_create": False,
76
+ "required_status_checks": [
77
+ {
78
+ "context": HealthCheckWorkflow.get_workflow_name(),
79
+ }
80
+ ],
81
+ },
82
+ )
83
+
84
+ return {
85
+ "owner": PyprojectConfigFile.get_main_author_name(),
86
+ "token": token,
87
+ "repo_name": src_pkg_name,
88
+ "ruleset_name": DEFAULT_RULESET_NAME,
89
+ "enforcement": "active",
90
+ "bypass_actors": [
91
+ {
92
+ "actor_id": 5,
93
+ "actor_type": "RepositoryRole",
94
+ "bypass_mode": "always",
95
+ }
96
+ ],
97
+ "target": "branch",
98
+ "conditions": {"ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}},
99
+ "rules": rules,
100
+ }
101
+
102
+
103
+ if __name__ == "__main__":
104
+ protect_repository()
@@ -0,0 +1,205 @@
1
+ """Contains utilities for working with GitHub repositories."""
2
+
3
+ from typing import Any, Literal
4
+
5
+ from github import Github
6
+ from github.Auth import Token
7
+ from github.Repository import Repository
8
+
9
+ from winipedia_utils.logging.logger import get_logger
10
+
11
+ logger = get_logger(__name__)
12
+
13
+ DEFAULT_BRANCH = "main"
14
+
15
+ DEFAULT_RULESET_NAME = f"{DEFAULT_BRANCH} protection"
16
+
17
+
18
+ def get_rules_payload( # noqa: PLR0913
19
+ *,
20
+ creation: dict[str, Any] | None = None,
21
+ update: dict[str, Any] | None = None,
22
+ deletion: dict[str, Any] | None = None,
23
+ required_linear_history: dict[str, Any] | None = None,
24
+ merge_queue: dict[str, Any] | None = None,
25
+ required_deployments: dict[str, Any] | None = None,
26
+ required_signatures: dict[str, Any] | None = None,
27
+ pull_request: dict[str, Any] | None = None,
28
+ required_status_checks: dict[str, Any] | None = None,
29
+ non_fast_forward: dict[str, Any] | None = None,
30
+ commit_message_pattern: dict[str, Any] | None = None,
31
+ commit_author_email_pattern: dict[str, Any] | None = None,
32
+ committer_email_pattern: dict[str, Any] | None = None,
33
+ branch_name_pattern: dict[str, Any] | None = None,
34
+ tag_name_pattern: dict[str, Any] | None = None,
35
+ file_path_restriction: dict[str, Any] | None = None,
36
+ max_file_path_length: dict[str, Any] | None = None,
37
+ file_extension_restriction: dict[str, Any] | None = None,
38
+ max_file_size: dict[str, Any] | None = None,
39
+ workflows: dict[str, Any] | None = None,
40
+ code_scanning: dict[str, Any] | None = None,
41
+ copilot_code_review: dict[str, Any] | None = None,
42
+ ) -> list[dict[str, Any]]:
43
+ """Build a rules array for a GitHub ruleset.
44
+
45
+ Args:
46
+ creation: Only allow users with bypass permission to create matching
47
+ refs.
48
+ update: Only allow users with bypass permission to update matching
49
+ refs.
50
+ deletion: Only allow users with bypass permissions to delete matching
51
+ refs.
52
+ required_linear_history: Prevent merge commits from being pushed to
53
+ matching refs.
54
+ merge_queue: Merges must be performed via a merge queue.
55
+ required_deployments: Choose which environments must be successfully
56
+ deployed to before refs can be pushed.
57
+ required_signatures: Commits pushed to matching refs must have verified
58
+ signatures.
59
+ pull_request: Require all commits be made to a non-target branch and
60
+ submitted via a pull request.
61
+ required_status_checks: Choose which status checks must pass before the
62
+ ref is updated.
63
+ non_fast_forward: Prevent users with push access from force pushing to
64
+ refs.
65
+ commit_message_pattern: Parameters to be used for the
66
+ commit_message_pattern rule.
67
+ commit_author_email_pattern: Parameters to be used for the
68
+ commit_author_email_pattern rule.
69
+ committer_email_pattern: Parameters to be used for the
70
+ committer_email_pattern rule.
71
+ branch_name_pattern: Parameters to be used for the branch_name_pattern
72
+ rule.
73
+ tag_name_pattern: Parameters to be used for the tag_name_pattern rule.
74
+ file_path_restriction: Prevent commits that include changes in
75
+ specified file and folder paths.
76
+ max_file_path_length: Prevent commits that include file paths that
77
+ exceed the specified character limit.
78
+ file_extension_restriction: Prevent commits that include files with
79
+ specified file extensions.
80
+ max_file_size: Prevent commits with individual files that exceed the
81
+ specified limit.
82
+ workflows: Require all changes made to a targeted branch to pass the
83
+ specified workflows.
84
+ code_scanning: Choose which tools must provide code scanning results
85
+ before the reference is updated.
86
+ copilot_code_review: Request Copilot code review for new pull requests
87
+ automatically.
88
+
89
+ Returns:
90
+ A list of rule objects to be used in a GitHub ruleset.
91
+ """
92
+ rules: list[dict[str, Any]] = []
93
+
94
+ rule_map = {
95
+ "creation": creation,
96
+ "update": update,
97
+ "deletion": deletion,
98
+ "required_linear_history": required_linear_history,
99
+ "merge_queue": merge_queue,
100
+ "required_deployments": required_deployments,
101
+ "required_signatures": required_signatures,
102
+ "pull_request": pull_request,
103
+ "required_status_checks": required_status_checks,
104
+ "non_fast_forward": non_fast_forward,
105
+ "commit_message_pattern": commit_message_pattern,
106
+ "commit_author_email_pattern": commit_author_email_pattern,
107
+ "committer_email_pattern": committer_email_pattern,
108
+ "branch_name_pattern": branch_name_pattern,
109
+ "tag_name_pattern": tag_name_pattern,
110
+ "file_path_restriction": file_path_restriction,
111
+ "max_file_path_length": max_file_path_length,
112
+ "file_extension_restriction": file_extension_restriction,
113
+ "max_file_size": max_file_size,
114
+ "workflows": workflows,
115
+ "code_scanning": code_scanning,
116
+ "copilot_code_review": copilot_code_review,
117
+ }
118
+
119
+ for rule_type, rule_config in rule_map.items():
120
+ if rule_config is not None:
121
+ rule_obj: dict[str, Any] = {"type": rule_type}
122
+ if rule_config: # If there are parameters
123
+ rule_obj["parameters"] = rule_config
124
+ rules.append(rule_obj)
125
+
126
+ return rules
127
+
128
+
129
+ def create_or_update_ruleset( # noqa: PLR0913
130
+ token: str,
131
+ owner: str,
132
+ repo_name: str,
133
+ *,
134
+ ruleset_name: str,
135
+ enforcement: Literal["active", "disabled", "evaluate"] = "active",
136
+ target: Literal["branch", "tag", "push"] = "branch",
137
+ bypass_actors: list[dict[str, Any]] | None = None,
138
+ conditions: dict[
139
+ Literal["ref_name"], dict[Literal["include", "exclude"], list[str]]
140
+ ]
141
+ | None = None,
142
+ rules: list[dict[str, Any]] | None = None,
143
+ ) -> Any:
144
+ """Create a ruleset for the repository."""
145
+ repo = get_repo(token, owner, repo_name)
146
+ ruleset_id = ruleset_exists(
147
+ token=token, owner=owner, repo_name=repo_name, ruleset_name=ruleset_name
148
+ )
149
+ method = "PUT" if ruleset_id else "POST"
150
+ url = f"{repo.url}/rulesets"
151
+
152
+ if ruleset_id:
153
+ url += f"/{ruleset_id}"
154
+
155
+ payload: dict[str, Any] = {
156
+ "name": ruleset_name,
157
+ "enforcement": enforcement,
158
+ "target": target,
159
+ "conditions": conditions,
160
+ "rules": rules,
161
+ }
162
+ if bypass_actors:
163
+ payload["bypass_actors"] = bypass_actors
164
+
165
+ _headers, res = repo._requester.requestJsonAndCheck( # noqa: SLF001
166
+ method,
167
+ url,
168
+ headers={
169
+ "Accept": "application/vnd.github+json",
170
+ "X-GitHub-Api-Version": "2022-11-28",
171
+ },
172
+ input=payload,
173
+ )
174
+
175
+ return res
176
+
177
+
178
+ def get_all_rulesets(token: str, owner: str, repo_name: str) -> Any:
179
+ """Get all rulesets for the repository."""
180
+ repo = get_repo(token, owner, repo_name)
181
+ url = f"{repo.url}/rulesets"
182
+ method = "GET"
183
+ _headers, res = repo._requester.requestJsonAndCheck( # noqa: SLF001
184
+ method,
185
+ url,
186
+ headers={
187
+ "Accept": "application/vnd.github+json",
188
+ "X-GitHub-Api-Version": "2022-11-28",
189
+ },
190
+ )
191
+ return res
192
+
193
+
194
+ def get_repo(token: str, owner: str, repo_name: str) -> Repository:
195
+ """Get the repository."""
196
+ auth = Token(token)
197
+ github = Github(auth=auth)
198
+ return github.get_repo(f"{owner}/{repo_name}")
199
+
200
+
201
+ def ruleset_exists(token: str, owner: str, repo_name: str, ruleset_name: str) -> int:
202
+ """Check if the main protection ruleset exists."""
203
+ rulesets = get_all_rulesets(token, owner, repo_name)
204
+ main_ruleset = next((rs for rs in rulesets if rs["name"] == ruleset_name), None)
205
+ return main_ruleset["id"] if main_ruleset else 0
@@ -0,0 +1 @@
1
+ """__init__ module."""
@@ -4,6 +4,9 @@ from abc import abstractmethod
4
4
  from pathlib import Path
5
5
  from typing import Any
6
6
 
7
+ import winipedia_utils
8
+ from winipedia_utils.modules.module import make_obj_importpath
9
+ from winipedia_utils.modules.package import get_src_package
7
10
  from winipedia_utils.text.config import YamlConfigFile
8
11
  from winipedia_utils.text.string import split_on_uppercase
9
12
 
@@ -11,22 +14,36 @@ from winipedia_utils.text.string import split_on_uppercase
11
14
  class Workflow(YamlConfigFile):
12
15
  """Base class for workflows."""
13
16
 
17
+ @classmethod
14
18
  @abstractmethod
15
- def get_workflow_triggers(self) -> dict[str, Any]:
19
+ def get_workflow_triggers(cls) -> dict[str, Any]:
16
20
  """Get the workflow triggers."""
17
21
 
22
+ @classmethod
18
23
  @abstractmethod
19
- def get_permissions(self) -> dict[str, Any]:
24
+ def get_permissions(cls) -> dict[str, Any]:
20
25
  """Get the workflow permissions."""
21
26
 
27
+ @classmethod
22
28
  @abstractmethod
23
- def get_jobs(self) -> dict[str, Any]:
29
+ def get_jobs(cls) -> dict[str, Any]:
24
30
  """Get the workflow jobs."""
25
31
 
26
- def get_path(self) -> Path:
32
+ @classmethod
33
+ def get_parent_path(cls) -> Path:
27
34
  """Get the path to the config file."""
28
- file_name = self.get_standard_job_name() + ".yaml"
29
- return Path(".github/workflows") / file_name
35
+ return Path(".github/workflows")
36
+
37
+ @classmethod
38
+ def get_configs(cls) -> dict[str, Any]:
39
+ """Get the workflow config."""
40
+ return {
41
+ "name": cls.get_workflow_name(),
42
+ "on": cls.get_workflow_triggers(),
43
+ "permissions": cls.get_permissions(),
44
+ "run-name": cls.get_run_name(),
45
+ "jobs": cls.get_jobs(),
46
+ }
30
47
 
31
48
  @classmethod
32
49
  def get_standard_job(
@@ -39,7 +56,7 @@ class Workflow(YamlConfigFile):
39
56
  ) -> dict[str, Any]:
40
57
  """Get a standard job."""
41
58
  if name is None:
42
- name = cls.get_standard_job_name()
59
+ name = cls.get_filename()
43
60
 
44
61
  if steps is None:
45
62
  steps = []
@@ -57,38 +74,28 @@ class Workflow(YamlConfigFile):
57
74
  job[name]["if"] = if_condition
58
75
  return job
59
76
 
60
- @classmethod
61
- def get_standard_job_name(cls) -> str:
62
- """Get the standard job name."""
63
- return "_".join(
64
- split_on_uppercase(cls.__name__.removesuffix(Workflow.__name__))
65
- ).lower()
66
-
67
77
  @classmethod
68
78
  def get_workflow_name(cls) -> str:
69
79
  """Get the workflow name."""
70
80
  return " ".join(split_on_uppercase(cls.__name__))
71
81
 
72
- def get_run_name(self) -> str:
82
+ @classmethod
83
+ def get_run_name(cls) -> str:
73
84
  """Get the workflow run name."""
74
- return f"{self.get_workflow_name()}"
75
-
76
- def get_configs(self) -> dict[str, Any]:
77
- """Get the workflow config."""
78
- return {
79
- "name": self.get_workflow_name(),
80
- "on": self.get_workflow_triggers(),
81
- "permissions": self.get_permissions(),
82
- "run-name": self.get_run_name(),
83
- "jobs": self.get_jobs(),
84
- }
85
+ return f"{cls.get_workflow_name()}"
85
86
 
86
87
  @classmethod
87
- def get_checkout_step(cls, fetch_depth: int | None = None) -> dict[str, Any]:
88
+ def get_checkout_step(
89
+ cls,
90
+ fetch_depth: int | None = None,
91
+ *,
92
+ token: bool = False,
93
+ ) -> dict[str, Any]:
88
94
  """Get the checkout step.
89
95
 
90
96
  Args:
91
97
  fetch_depth: The fetch depth to use. If None, no fetch depth is specified.
98
+ token: Whether to use the repository token.
92
99
 
93
100
  Returns:
94
101
  The checkout step.
@@ -98,7 +105,10 @@ class Workflow(YamlConfigFile):
98
105
  "uses": "actions/checkout@main",
99
106
  }
100
107
  if fetch_depth is not None:
101
- step["with"] = {"fetch-depth": fetch_depth}
108
+ step.setdefault("with", {})["fetch-depth"] = fetch_depth
109
+
110
+ if token:
111
+ step.setdefault("with", {})["token"] = cls.get_repo_token()
102
112
  return step
103
113
 
104
114
  @classmethod
@@ -109,6 +119,7 @@ class Workflow(YamlConfigFile):
109
119
  fetch_depth: int | None = None,
110
120
  configure_pipy_token: bool = False,
111
121
  force_main_head: bool = False,
122
+ token: bool = False,
112
123
  ) -> list[dict[str, Any]]:
113
124
  """Get the poetry steps.
114
125
 
@@ -119,11 +130,12 @@ class Workflow(YamlConfigFile):
119
130
  force_main_head: Whether to exit if the running branch or current commit is not
120
131
  equal to the most recent commit on main. This is useful for workflows that
121
132
  should only run on main.
133
+ token: Whether to use the repository token.
122
134
 
123
135
  Returns:
124
136
  The poetry steps.
125
137
  """
126
- steps = [cls.get_checkout_step(fetch_depth)]
138
+ steps = [cls.get_checkout_step(fetch_depth, token=token)]
127
139
  if force_main_head:
128
140
  # exit with code 1 if the running branch is not main
129
141
  steps.append(
@@ -132,6 +144,7 @@ class Workflow(YamlConfigFile):
132
144
  "run": 'git fetch origin main --depth=1; main_sha=$(git rev-parse origin/main); if [ "$GITHUB_SHA" != "$main_sha" ]; then echo "Tag commit is not the latest commit on main."; exit 1; fi', # noqa: E501
133
145
  }
134
146
  )
147
+ steps.append(cls.get_setup_git_step())
135
148
  steps.append(
136
149
  {
137
150
  "name": "Setup Python",
@@ -145,13 +158,6 @@ class Workflow(YamlConfigFile):
145
158
  "run": "curl -sSL https://install.python-poetry.org | python3 -",
146
159
  }
147
160
  )
148
- steps.append(
149
- {
150
- "name": "Extract Version from pyproject.toml",
151
- "id": "version",
152
- "run": 'version=$(poetry version -s) && echo "Project version: $version" && echo "version=v$version" >> $GITHUB_OUTPUT', # noqa: E501
153
- },
154
- )
155
161
  if configure_pipy_token:
156
162
  steps.append(
157
163
  {
@@ -163,56 +169,99 @@ class Workflow(YamlConfigFile):
163
169
  steps.append({"name": "Install Dependencies", "run": "poetry install"})
164
170
  return steps
165
171
 
166
- @staticmethod
167
- def get_release_steps() -> list[dict[str, Any]]:
172
+ @classmethod
173
+ def get_release_steps(cls) -> list[dict[str, Any]]:
168
174
  """Get the release steps."""
169
175
  return [
170
176
  {
171
177
  "name": "Create and Push Tag",
172
- "run": f"git tag {Workflow.get_version()} && git push origin {Workflow.get_version()}", # noqa: E501
178
+ "run": f"git tag {cls.get_version()} && git push && git push origin {cls.get_version()}", # noqa: E501
173
179
  },
174
180
  {
175
181
  "name": "Build Changelog",
176
182
  "id": "build_changelog",
177
183
  "uses": "mikepenz/release-changelog-builder-action@develop",
178
- "with": {"token": "${{ secrets.GITHUB_TOKEN }}"},
184
+ "with": {"token": cls.get_github_token()},
179
185
  },
180
186
  {
181
187
  "name": "Create GitHub Release",
182
188
  "uses": "ncipollo/release-action@main",
183
189
  "with": {
184
- "tag": Workflow.get_version(),
185
- "name": Workflow.get_repo_and_version(),
190
+ "tag": cls.get_version(),
191
+ "name": cls.get_repo_and_version(),
186
192
  "body": "${{ steps.build_changelog.outputs.changelog }}",
187
193
  },
188
194
  },
189
195
  ]
190
196
 
191
- @staticmethod
192
- def get_publish_to_pypi_step() -> dict[str, Any]:
197
+ @classmethod
198
+ def get_extract_version_step(cls) -> dict[str, Any]:
199
+ """Get the extract version step."""
200
+ return {
201
+ "name": "Extract Version from pyproject.toml",
202
+ "id": "version",
203
+ "run": 'version=$(poetry version -s) && echo "Project version: $version" && echo "version=v$version" >> $GITHUB_OUTPUT', # noqa: E501
204
+ }
205
+
206
+ @classmethod
207
+ def get_publish_to_pypi_step(cls) -> dict[str, Any]:
193
208
  """Get the publish step."""
194
209
  return {"name": "Build and publish to PyPI", "run": "poetry publish --build"}
195
210
 
196
- @staticmethod
197
- def get_pre_commit_step() -> dict[str, Any]:
211
+ @classmethod
212
+ def get_pre_commit_step(cls) -> dict[str, Any]:
198
213
  """Get the pre-commit step.
199
214
 
200
215
  using pre commit in case other hooks are added later
201
216
  and bc it fails if files are changed,
202
217
  setup script shouldnt change files
203
218
  """
204
- return {
219
+ step: dict[str, Any] = {
205
220
  "name": "Run Hooks",
206
221
  "run": "poetry run pre-commit run --all-files --verbose",
207
222
  }
223
+ if get_src_package() == winipedia_utils:
224
+ step["env"] = {"REPO_TOKEN": cls.get_repo_token()}
225
+ return step
208
226
 
209
- @staticmethod
210
- def get_repository_name() -> str:
227
+ @classmethod
228
+ def get_setup_git_step(cls) -> dict[str, Any]:
229
+ """Get the setup git step."""
230
+ return {
231
+ "name": "Setup Git",
232
+ "run": 'git config --global user.email "github-actions[bot]@users.noreply.github.com" && git config --global user.name "github-actions[bot]"', # noqa: E501
233
+ }
234
+
235
+ @classmethod
236
+ def get_commit_step(cls) -> dict[str, Any]:
237
+ """Get the commit step."""
238
+ return {
239
+ "name": "Commit added changes",
240
+ "run": "git commit --no-verify -m '[skip ci] CI/CD: Committing possible added changes (e.g.: pyproject.toml and poetry.lock)'", # noqa: E501
241
+ }
242
+
243
+ @classmethod
244
+ def get_protect_repository_step(cls) -> dict[str, Any]:
245
+ """Get the protect repository step."""
246
+ from winipedia_utils.git.github.repo import ( # noqa: PLC0415
247
+ protect, # avoid circular import
248
+ )
249
+
250
+ return {
251
+ "name": "Protect Repository",
252
+ "run": f"poetry run python -m {make_obj_importpath(protect)}",
253
+ "env": {
254
+ "REPO_TOKEN": cls.get_repo_token(),
255
+ },
256
+ }
257
+
258
+ @classmethod
259
+ def get_repository_name(cls) -> str:
211
260
  """Get the repository name."""
212
261
  return "${{ github.event.repository.name }}"
213
262
 
214
- @staticmethod
215
- def get_ref_name() -> str:
263
+ @classmethod
264
+ def get_ref_name(cls) -> str:
216
265
  """Get the ref name."""
217
266
  return "${{ github.ref_name }}"
218
267
 
@@ -221,7 +270,22 @@ class Workflow(YamlConfigFile):
221
270
  """Get the version."""
222
271
  return "${{ steps.version.outputs.version }}"
223
272
 
224
- @staticmethod
225
- def get_repo_and_version() -> str:
273
+ @classmethod
274
+ def get_repo_and_version(cls) -> str:
226
275
  """Get the repository name and ref name."""
227
- return f"{Workflow.get_repository_name()} {Workflow.get_version()}"
276
+ return f"{cls.get_repository_name()} {cls.get_version()}"
277
+
278
+ @classmethod
279
+ def get_ownwer(cls) -> str:
280
+ """Get the repository owner."""
281
+ return "${{ github.repository_owner }}"
282
+
283
+ @classmethod
284
+ def get_github_token(cls) -> str:
285
+ """Get the GitHub token."""
286
+ return "${{ secrets.GITHUB_TOKEN }}"
287
+
288
+ @classmethod
289
+ def get_repo_token(cls) -> str:
290
+ """Get the repository token."""
291
+ return "${{ secrets.REPO_TOKEN }}"
@@ -0,0 +1,57 @@
1
+ """Contains the pull request workflow.
2
+
3
+ This workflow is used to run tests on pull requests.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from winipedia_utils.git.github.workflows.base.base import Workflow
9
+
10
+
11
+ class HealthCheckWorkflow(Workflow):
12
+ """Pull request workflow.
13
+
14
+ This workflow is triggered by a pull request.
15
+ It runs tests on the pull request.
16
+ """
17
+
18
+ @classmethod
19
+ def get_workflow_triggers(cls) -> dict[str, Any]:
20
+ """Get the workflow triggers."""
21
+ return {
22
+ "pull_request": {
23
+ "types": ["opened", "synchronize", "reopened"],
24
+ },
25
+ "schedule": [
26
+ {
27
+ # run every day at 6 am
28
+ "cron": "0 6 * * *",
29
+ },
30
+ ],
31
+ "workflow_dispatch": {},
32
+ }
33
+
34
+ @classmethod
35
+ def get_permissions(cls) -> dict[str, Any]:
36
+ """Get the workflow permissions."""
37
+ return {}
38
+
39
+ @classmethod
40
+ def get_jobs(cls) -> dict[str, Any]:
41
+ """Get the workflow jobs."""
42
+ return {
43
+ **cls.get_standard_job(
44
+ steps=[
45
+ *(
46
+ cls.get_poetry_setup_steps(
47
+ install_dependencies=True,
48
+ token=True,
49
+ )
50
+ ),
51
+ cls.get_protect_repository_step(),
52
+ cls.get_pre_commit_step(),
53
+ cls.get_commit_step(),
54
+ cls.get_extract_version_step(),
55
+ ],
56
+ ),
57
+ }