winipedia-utils 0.3.43__tar.gz → 0.4.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of winipedia-utils might be problematic. Click here for more details.
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/PKG-INFO +23 -8
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/README.md +21 -7
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/pyproject.toml +2 -1
- winipedia_utils-0.4.12/winipedia_utils/git/github/repo/protect.py +104 -0
- winipedia_utils-0.4.12/winipedia_utils/git/github/repo/repo.py +205 -0
- {winipedia_utils-0.3.43/winipedia_utils/git → winipedia_utils-0.4.12/winipedia_utils/git/github}/workflows/base/base.py +95 -51
- winipedia_utils-0.4.12/winipedia_utils/git/github/workflows/health_check.py +55 -0
- {winipedia_utils-0.3.43/winipedia_utils/git → winipedia_utils-0.4.12/winipedia_utils/git/github}/workflows/publish.py +11 -8
- winipedia_utils-0.4.12/winipedia_utils/git/github/workflows/release.py +45 -0
- winipedia_utils-0.4.12/winipedia_utils/git/gitignore/config.py +74 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/git/gitignore/gitignore.py +1 -1
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/git/pre_commit/config.py +18 -13
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/git/pre_commit/hooks.py +22 -4
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/git/pre_commit/run_hooks.py +2 -1
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/iterating/iterate.py +3 -4
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/modules/module.py +2 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/modules/package.py +2 -1
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/projects/poetry/config.py +74 -36
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/projects/project.py +2 -2
- winipedia_utils-0.4.12/winipedia_utils/resources/__init__.py +1 -0
- winipedia_utils-0.4.12/winipedia_utils/resources/svgs/__init__.py +1 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/setup.py +2 -0
- winipedia_utils-0.4.12/winipedia_utils/testing/config.py +149 -0
- winipedia_utils-0.4.12/winipedia_utils/testing/tests/base/fixtures/fixture.py +42 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/base/fixtures/scopes/module.py +6 -5
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/base/fixtures/scopes/session.py +7 -8
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/base/utils/utils.py +43 -2
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/text/config.py +84 -37
- winipedia_utils-0.3.43/winipedia_utils/git/gitignore/config.py +0 -54
- winipedia_utils-0.3.43/winipedia_utils/git/workflows/health_check.py +0 -51
- winipedia_utils-0.3.43/winipedia_utils/git/workflows/release.py +0 -33
- winipedia_utils-0.3.43/winipedia_utils/testing/config.py +0 -95
- winipedia_utils-0.3.43/winipedia_utils/testing/tests/base/fixtures/fixture.py +0 -6
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/LICENSE +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/concurrent/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/concurrent/concurrent.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/concurrent/multiprocessing.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/concurrent/multithreading.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/data/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/data/dataframe/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/data/dataframe/cleaning.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/data/structures/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/data/structures/dicts.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/git/__init__.py +0 -0
- {winipedia_utils-0.3.43/winipedia_utils/git/workflows/base → winipedia_utils-0.4.12/winipedia_utils/git/github}/__init__.py +0 -0
- {winipedia_utils-0.3.43/winipedia_utils/resources → winipedia_utils-0.4.12/winipedia_utils/git/github/repo}/__init__.py +0 -0
- {winipedia_utils-0.3.43/winipedia_utils/git → winipedia_utils-0.4.12/winipedia_utils/git/github}/workflows/__init__.py +0 -0
- {winipedia_utils-0.3.43/winipedia_utils/resources/svgs → winipedia_utils-0.4.12/winipedia_utils/git/github/workflows/base}/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/git/gitignore/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/git/pre_commit/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/iterating/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/logging/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/logging/ansi.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/logging/config.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/logging/logger.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/modules/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/modules/class_.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/modules/function.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/oop/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/oop/mixins/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/oop/mixins/meta.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/oop/mixins/mixin.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/os/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/os/os.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/projects/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/projects/poetry/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/projects/poetry/poetry.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/py.typed +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/resources/svgs/delete_garbage_can.svg +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/resources/svgs/download_arrow.svg +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/resources/svgs/exit_fullscreen_icon.svg +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/resources/svgs/fullscreen_icon.svg +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/resources/svgs/menu_icon.svg +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/resources/svgs/pause_icon.svg +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/resources/svgs/play_icon.svg +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/resources/svgs/plus_icon.svg +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/resources/svgs/svg.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/security/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/security/cryptography.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/security/keyring.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/assertions.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/convention.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/create_tests.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/fixtures.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/skip.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/base/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/base/fixtures/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/base/fixtures/scopes/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/base/fixtures/scopes/class_.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/base/fixtures/scopes/function.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/base/fixtures/scopes/package.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/base/utils/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/testing/tests/conftest.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/text/__init__.py +0 -0
- {winipedia_utils-0.3.43 → winipedia_utils-0.4.12}/winipedia_utils/text/string.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: winipedia-utils
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.12
|
|
4
4
|
Summary: A package with many utility functions
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -16,6 +16,7 @@ Requires-Dist: defusedxml
|
|
|
16
16
|
Requires-Dist: keyring
|
|
17
17
|
Requires-Dist: pathspec
|
|
18
18
|
Requires-Dist: polars
|
|
19
|
+
Requires-Dist: pygithub
|
|
19
20
|
Requires-Dist: pyyaml
|
|
20
21
|
Requires-Dist: setuptools
|
|
21
22
|
Requires-Dist: tomlkit
|
|
@@ -62,13 +63,26 @@ A comprehensive Python utility package that enforces best practices, automates p
|
|
|
62
63
|
|
|
63
64
|
## Quick Start
|
|
64
65
|
|
|
65
|
-
###
|
|
66
|
+
### How to setup a new project
|
|
66
67
|
|
|
67
68
|
```bash
|
|
68
|
-
#
|
|
69
|
+
# 1: Create a new repository on GitHub
|
|
70
|
+
# The default branch must be called main
|
|
71
|
+
# add a PAT or Fine-Grained Access Token to your repo secrets called REPO_TOKEN that has write access to the repository (needed for branch protection in health_check.yaml - see winipedia_utils.git.github.repo.protect)
|
|
72
|
+
|
|
73
|
+
# 2: Clone the repository
|
|
74
|
+
git clone https://github.com/owner/repo.git
|
|
75
|
+
|
|
76
|
+
# 3: Create a new poetry project
|
|
77
|
+
poetry init # or poetry new
|
|
78
|
+
# 4: Poetry will ask you some stuff when you run poetry init.
|
|
79
|
+
# First author name must be equal to the GitHub repository owner (username).
|
|
80
|
+
# The repository name must be equal to the package/project name.
|
|
81
|
+
|
|
82
|
+
# 5: Add winipedia-utils to your project
|
|
69
83
|
poetry add winipedia-utils
|
|
70
84
|
|
|
71
|
-
# Run the automated setup
|
|
85
|
+
# 6: Run the automated setup
|
|
72
86
|
poetry run python -m winipedia_utils.setup
|
|
73
87
|
```
|
|
74
88
|
|
|
@@ -88,8 +102,9 @@ The setup creates the following configuration files:
|
|
|
88
102
|
- `.pre-commit-config.yaml` - Pre-commit hook configuration
|
|
89
103
|
- `.gitignore` - Git ignore rules (assumes you added one on GitHub before.)
|
|
90
104
|
- `pyproject.toml` - Project configuration with Poetry settings
|
|
91
|
-
- `.github/workflows/
|
|
92
|
-
- `.github/workflows/
|
|
105
|
+
- `.github/workflows/health_check.yaml` - Health check workflow (Runs on every push and pull request)
|
|
106
|
+
- `.github/workflows/release.yaml` - Release workflow (Creates a release on GitHub when the same actions as in health check pass and commits are pushed to main)
|
|
107
|
+
- `.github/workflows/publish.yaml` - Publishing workflow (Publishes to PyPI when a release is created by the release workflow, if you use this workflow, you need to add a PYPI_TOKEN (named PYPI_TOKEN) to your GitHub secrets that has write access to the package on PyPI.)
|
|
93
108
|
- `py.typed` - PEP 561 marker for type hints
|
|
94
109
|
- `experiment.py` - For experimentation (ignored by git)
|
|
95
110
|
- `test0.py` - Test file with one empyt test (so that initial tests pass)
|
|
@@ -106,8 +121,8 @@ Usually VSCode or other IDEs activates the venv automatically when opening the t
|
|
|
106
121
|
1. Patch version (poetry version patch)
|
|
107
122
|
2. Add version patch to git (git add pyproject.toml)
|
|
108
123
|
3. Update package manager (poetry self update)
|
|
109
|
-
4. Install packages (poetry install)
|
|
110
|
-
5. Update packages (poetry update)
|
|
124
|
+
4. Install packages (poetry install --with dev)
|
|
125
|
+
5. Update packages (poetry update --with dev (winipedia_utils forces all dependencies with * to be updated to latest compatible version))
|
|
111
126
|
6. Lock dependencies (poetry lock)
|
|
112
127
|
7. Check package manager configs (poetry check --strict)
|
|
113
128
|
8. Create tests (python -m winipedia_utils.testing.create_tests)
|
|
@@ -38,13 +38,26 @@ A comprehensive Python utility package that enforces best practices, automates p
|
|
|
38
38
|
|
|
39
39
|
## Quick Start
|
|
40
40
|
|
|
41
|
-
###
|
|
41
|
+
### How to setup a new project
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
#
|
|
44
|
+
# 1: Create a new repository on GitHub
|
|
45
|
+
# The default branch must be called main
|
|
46
|
+
# add a PAT or Fine-Grained Access Token to your repo secrets called REPO_TOKEN that has write access to the repository (needed for branch protection in health_check.yaml - see winipedia_utils.git.github.repo.protect)
|
|
47
|
+
|
|
48
|
+
# 2: Clone the repository
|
|
49
|
+
git clone https://github.com/owner/repo.git
|
|
50
|
+
|
|
51
|
+
# 3: Create a new poetry project
|
|
52
|
+
poetry init # or poetry new
|
|
53
|
+
# 4: Poetry will ask you some stuff when you run poetry init.
|
|
54
|
+
# First author name must be equal to the GitHub repository owner (username).
|
|
55
|
+
# The repository name must be equal to the package/project name.
|
|
56
|
+
|
|
57
|
+
# 5: Add winipedia-utils to your project
|
|
45
58
|
poetry add winipedia-utils
|
|
46
59
|
|
|
47
|
-
# Run the automated setup
|
|
60
|
+
# 6: Run the automated setup
|
|
48
61
|
poetry run python -m winipedia_utils.setup
|
|
49
62
|
```
|
|
50
63
|
|
|
@@ -64,8 +77,9 @@ The setup creates the following configuration files:
|
|
|
64
77
|
- `.pre-commit-config.yaml` - Pre-commit hook configuration
|
|
65
78
|
- `.gitignore` - Git ignore rules (assumes you added one on GitHub before.)
|
|
66
79
|
- `pyproject.toml` - Project configuration with Poetry settings
|
|
67
|
-
- `.github/workflows/
|
|
68
|
-
- `.github/workflows/
|
|
80
|
+
- `.github/workflows/health_check.yaml` - Health check workflow (Runs on every push and pull request)
|
|
81
|
+
- `.github/workflows/release.yaml` - Release workflow (Creates a release on GitHub when the same actions as in health check pass and commits are pushed to main)
|
|
82
|
+
- `.github/workflows/publish.yaml` - Publishing workflow (Publishes to PyPI when a release is created by the release workflow, if you use this workflow, you need to add a PYPI_TOKEN (named PYPI_TOKEN) to your GitHub secrets that has write access to the package on PyPI.)
|
|
69
83
|
- `py.typed` - PEP 561 marker for type hints
|
|
70
84
|
- `experiment.py` - For experimentation (ignored by git)
|
|
71
85
|
- `test0.py` - Test file with one empyt test (so that initial tests pass)
|
|
@@ -82,8 +96,8 @@ Usually VSCode or other IDEs activates the venv automatically when opening the t
|
|
|
82
96
|
1. Patch version (poetry version patch)
|
|
83
97
|
2. Add version patch to git (git add pyproject.toml)
|
|
84
98
|
3. Update package manager (poetry self update)
|
|
85
|
-
4. Install packages (poetry install)
|
|
86
|
-
5. Update packages (poetry update)
|
|
99
|
+
4. Install packages (poetry install --with dev)
|
|
100
|
+
5. Update packages (poetry update --with dev (winipedia_utils forces all dependencies with * to be updated to latest compatible version))
|
|
87
101
|
6. Lock dependencies (poetry lock)
|
|
88
102
|
7. Check package manager configs (poetry check --strict)
|
|
89
103
|
8. Create tests (python -m winipedia_utils.testing.create_tests)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Project section
|
|
2
2
|
[project]
|
|
3
3
|
name = "winipedia-utils"
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.4.12"
|
|
5
5
|
description = "A package with many utility functions"
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.12"
|
|
@@ -30,6 +30,7 @@ pyyaml = "*"
|
|
|
30
30
|
keyring = "*"
|
|
31
31
|
cryptography = "*"
|
|
32
32
|
polars = "*"
|
|
33
|
+
pygithub = "*"
|
|
33
34
|
|
|
34
35
|
[tool.poetry.group.dev.dependencies]
|
|
35
36
|
ruff = "*"
|
|
@@ -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
|