winipedia-utils 0.4.56__tar.gz → 0.5.19__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.
Files changed (96) hide show
  1. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/PKG-INFO +36 -4
  2. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/README.md +35 -3
  3. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/pyproject.toml +1 -1
  4. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/github/workflows/base/base.py +56 -20
  5. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/github/workflows/health_check.py +25 -3
  6. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/github/workflows/release.py +9 -3
  7. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/pre_commit/config.py +1 -1
  8. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/pre_commit/hooks.py +17 -19
  9. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/pre_commit/run_hooks.py +15 -3
  10. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/modules/class_.py +11 -4
  11. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/modules/function.py +9 -3
  12. winipedia_utils-0.5.19/winipedia_utils/modules/inspection.py +56 -0
  13. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/modules/module.py +2 -32
  14. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/projects/poetry/config.py +35 -16
  15. winipedia_utils-0.5.19/winipedia_utils/projects/poetry/poetry.py +248 -0
  16. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/create_tests.py +1 -1
  17. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/text/config.py +3 -3
  18. winipedia_utils-0.4.56/winipedia_utils/projects/poetry/poetry.py +0 -129
  19. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/LICENSE +0 -0
  20. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/__init__.py +0 -0
  21. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/concurrent/__init__.py +0 -0
  22. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/concurrent/concurrent.py +0 -0
  23. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/concurrent/multiprocessing.py +0 -0
  24. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/concurrent/multithreading.py +0 -0
  25. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/data/__init__.py +0 -0
  26. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/data/dataframe/__init__.py +0 -0
  27. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/data/dataframe/cleaning.py +0 -0
  28. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/data/structures/__init__.py +0 -0
  29. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/data/structures/dicts.py +0 -0
  30. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/__init__.py +0 -0
  31. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/github/__init__.py +0 -0
  32. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/github/github.py +0 -0
  33. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/github/repo/__init__.py +0 -0
  34. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/github/repo/protect.py +0 -0
  35. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/github/repo/repo.py +0 -0
  36. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/github/workflows/__init__.py +0 -0
  37. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/github/workflows/base/__init__.py +0 -0
  38. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/github/workflows/publish.py +0 -0
  39. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/gitignore/__init__.py +0 -0
  40. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/gitignore/config.py +0 -0
  41. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/gitignore/gitignore.py +0 -0
  42. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/git/pre_commit/__init__.py +0 -0
  43. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/iterating/__init__.py +0 -0
  44. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/iterating/iterate.py +0 -0
  45. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/logging/__init__.py +0 -0
  46. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/logging/ansi.py +0 -0
  47. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/logging/config.py +0 -0
  48. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/logging/logger.py +0 -0
  49. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/modules/__init__.py +0 -0
  50. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/modules/package.py +0 -0
  51. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/oop/__init__.py +0 -0
  52. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/oop/mixins/__init__.py +0 -0
  53. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/oop/mixins/meta.py +0 -0
  54. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/oop/mixins/mixin.py +0 -0
  55. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/os/__init__.py +0 -0
  56. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/os/os.py +0 -0
  57. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/projects/__init__.py +0 -0
  58. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/projects/poetry/__init__.py +0 -0
  59. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/projects/project.py +0 -0
  60. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/py.typed +0 -0
  61. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/resources/__init__.py +0 -0
  62. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/resources/svgs/__init__.py +0 -0
  63. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/resources/svgs/delete_garbage_can.svg +0 -0
  64. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/resources/svgs/download_arrow.svg +0 -0
  65. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/resources/svgs/exit_fullscreen_icon.svg +0 -0
  66. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/resources/svgs/fullscreen_icon.svg +0 -0
  67. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/resources/svgs/menu_icon.svg +0 -0
  68. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/resources/svgs/pause_icon.svg +0 -0
  69. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/resources/svgs/play_icon.svg +0 -0
  70. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/resources/svgs/plus_icon.svg +0 -0
  71. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/resources/svgs/svg.py +0 -0
  72. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/security/__init__.py +0 -0
  73. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/security/cryptography.py +0 -0
  74. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/security/keyring.py +0 -0
  75. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/setup.py +0 -0
  76. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/__init__.py +0 -0
  77. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/assertions.py +0 -0
  78. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/config.py +0 -0
  79. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/convention.py +0 -0
  80. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/fixtures.py +0 -0
  81. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/skip.py +0 -0
  82. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/__init__.py +0 -0
  83. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/base/__init__.py +0 -0
  84. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/base/fixtures/__init__.py +0 -0
  85. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/base/fixtures/fixture.py +0 -0
  86. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/base/fixtures/scopes/__init__.py +0 -0
  87. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/base/fixtures/scopes/class_.py +0 -0
  88. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/base/fixtures/scopes/function.py +0 -0
  89. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/base/fixtures/scopes/module.py +0 -0
  90. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/base/fixtures/scopes/package.py +0 -0
  91. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/base/fixtures/scopes/session.py +0 -0
  92. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/base/utils/__init__.py +0 -0
  93. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/base/utils/utils.py +0 -0
  94. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/testing/tests/conftest.py +0 -0
  95. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/winipedia_utils/text/__init__.py +0 -0
  96. {winipedia_utils-0.4.56 → winipedia_utils-0.5.19}/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.4.56
3
+ Version: 0.5.19
4
4
  Summary: A package with many utility functions
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -105,7 +105,7 @@ The setup creates the following configuration files:
105
105
  - `.pre-commit-config.yaml` - Pre-commit hook configuration
106
106
  - `.gitignore` - Git ignore rules (assumes you added one on GitHub before.)
107
107
  - `pyproject.toml` - Project configuration with Poetry settings
108
- - `.github/workflows/health_check.yaml` - Health check workflow (Runs on every push and pull request, all workfows run on the latest possible python version in pyproject.toml)
108
+ - `.github/workflows/health_check.yaml` - Health check workflow (Runs on every push and pull request using a matrix strategy to test across multiple operating systems and Python versions)
109
109
  - `.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)
110
110
  - `.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.)
111
111
  - `py.typed` - PEP 561 marker for type hints
@@ -114,6 +114,38 @@ The setup creates the following configuration files:
114
114
  - `conftest.py` - Pytest configuration file
115
115
  - `.python-version` - Python version file for pyenv (if you use pyenv, puts in the lowest supported python version in pyproject.toml opposed to the latest possible python version in workflows)
116
116
 
117
+ ### GitHub Workflows and Matrix Strategy
118
+
119
+ The project uses GitHub Actions workflows with a **matrix strategy** to ensure cross-platform compatibility:
120
+
121
+ #### Matrix Configuration
122
+
123
+ The health check and release workflows test your code across:
124
+ - **Operating Systems**: Ubuntu (latest), Windows (latest), macOS (latest)
125
+ - **Python Versions**: All versions specified in your `pyproject.toml` (e.g., 3.12, 3.13, 3.14)
126
+
127
+ This matrix strategy ensures your code works reliably across different environments before merging or releasing.
128
+
129
+ #### Workflow Structure
130
+
131
+ The health check workflow consists of two jobs:
132
+
133
+ 1. **Matrix Job** (`health_check_matrix`) - Runs all checks in parallel across the matrix of OS and Python versions:
134
+ - Checkout repository
135
+ - Setup Git, Python, and Poetry
136
+ - Add Poetry to PATH (Windows-specific step)
137
+ - Install dependencies
138
+ - Setup CI keyring
139
+ - Protect repository (applies branch protection rules)
140
+ - Run pre-commit hooks (linting, formatting, type checking, security, tests)
141
+
142
+ 2. **Aggregation Job** (`health_check`) - Aggregates matrix results into a single status check:
143
+ - Required for branch protection compatibility
144
+ - Only runs after all matrix jobs complete successfully
145
+ - Provides a single status check that can be marked as required in branch protection rules
146
+
147
+ The release workflow extends the health check workflow and adds a release job that runs after all health checks pass.
148
+
117
149
  ### Pre-commit Hook Workflow
118
150
 
119
151
  When you commit code using `git commit`, the following checks run automatically:
@@ -203,8 +235,8 @@ A ruleset named `main protection` is created for the `main` branch with the foll
203
235
  - Requires review thread resolution (all comments in reviews must be resolved before merge)
204
236
  - Allowed merge methods: `squash` and `rebase` (no merge commits, keeps history clean)
205
237
  - **Required Status Checks:**
206
- - Strict mode enabled (all status checks must pass on the latest commit, not older ones (sets the health check as required status check))
207
- - Health check workflow must pass (the CI/CD pipeline must complete successfully)
238
+ - Strict mode enabled (all status checks must pass on the latest commit, not older ones)
239
+ - Health check workflow must pass (the aggregated `health_check` job ensures all matrix combinations passed successfully)
208
240
  - **Bypass Actors** - Repository admins can bypass all rules (for emergency situations)
209
241
 
210
242
  ## Utilities
@@ -78,7 +78,7 @@ The setup creates the following configuration files:
78
78
  - `.pre-commit-config.yaml` - Pre-commit hook configuration
79
79
  - `.gitignore` - Git ignore rules (assumes you added one on GitHub before.)
80
80
  - `pyproject.toml` - Project configuration with Poetry settings
81
- - `.github/workflows/health_check.yaml` - Health check workflow (Runs on every push and pull request, all workfows run on the latest possible python version in pyproject.toml)
81
+ - `.github/workflows/health_check.yaml` - Health check workflow (Runs on every push and pull request using a matrix strategy to test across multiple operating systems and Python versions)
82
82
  - `.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)
83
83
  - `.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.)
84
84
  - `py.typed` - PEP 561 marker for type hints
@@ -87,6 +87,38 @@ The setup creates the following configuration files:
87
87
  - `conftest.py` - Pytest configuration file
88
88
  - `.python-version` - Python version file for pyenv (if you use pyenv, puts in the lowest supported python version in pyproject.toml opposed to the latest possible python version in workflows)
89
89
 
90
+ ### GitHub Workflows and Matrix Strategy
91
+
92
+ The project uses GitHub Actions workflows with a **matrix strategy** to ensure cross-platform compatibility:
93
+
94
+ #### Matrix Configuration
95
+
96
+ The health check and release workflows test your code across:
97
+ - **Operating Systems**: Ubuntu (latest), Windows (latest), macOS (latest)
98
+ - **Python Versions**: All versions specified in your `pyproject.toml` (e.g., 3.12, 3.13, 3.14)
99
+
100
+ This matrix strategy ensures your code works reliably across different environments before merging or releasing.
101
+
102
+ #### Workflow Structure
103
+
104
+ The health check workflow consists of two jobs:
105
+
106
+ 1. **Matrix Job** (`health_check_matrix`) - Runs all checks in parallel across the matrix of OS and Python versions:
107
+ - Checkout repository
108
+ - Setup Git, Python, and Poetry
109
+ - Add Poetry to PATH (Windows-specific step)
110
+ - Install dependencies
111
+ - Setup CI keyring
112
+ - Protect repository (applies branch protection rules)
113
+ - Run pre-commit hooks (linting, formatting, type checking, security, tests)
114
+
115
+ 2. **Aggregation Job** (`health_check`) - Aggregates matrix results into a single status check:
116
+ - Required for branch protection compatibility
117
+ - Only runs after all matrix jobs complete successfully
118
+ - Provides a single status check that can be marked as required in branch protection rules
119
+
120
+ The release workflow extends the health check workflow and adds a release job that runs after all health checks pass.
121
+
90
122
  ### Pre-commit Hook Workflow
91
123
 
92
124
  When you commit code using `git commit`, the following checks run automatically:
@@ -176,8 +208,8 @@ A ruleset named `main protection` is created for the `main` branch with the foll
176
208
  - Requires review thread resolution (all comments in reviews must be resolved before merge)
177
209
  - Allowed merge methods: `squash` and `rebase` (no merge commits, keeps history clean)
178
210
  - **Required Status Checks:**
179
- - Strict mode enabled (all status checks must pass on the latest commit, not older ones (sets the health check as required status check))
180
- - Health check workflow must pass (the CI/CD pipeline must complete successfully)
211
+ - Strict mode enabled (all status checks must pass on the latest commit, not older ones)
212
+ - Health check workflow must pass (the aggregated `health_check` job ensures all matrix combinations passed successfully)
181
213
  - **Bypass Actors** - Repository admins can bypass all rules (for emergency situations)
182
214
 
183
215
  ## Utilities
@@ -1,7 +1,7 @@
1
1
  # Project section
2
2
  [project]
3
3
  name = "winipedia-utils"
4
- version = "0.4.56"
4
+ version = "0.5.19"
5
5
  description = "A package with many utility functions"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.12"
@@ -74,36 +74,46 @@ class Workflow(YamlConfigFile):
74
74
  "on": cls.get_workflow_triggers(),
75
75
  "permissions": cls.get_permissions(),
76
76
  "run-name": cls.get_run_name(),
77
+ "defaults": {"run": {"shell": "bash"}},
77
78
  "jobs": cls.get_jobs(),
78
79
  }
79
80
 
80
81
  @classmethod
81
- def get_standard_job(
82
+ def get_standard_job( # noqa: PLR0913
82
83
  cls,
83
84
  name: str | None = None,
84
85
  runs_on: str = "ubuntu-latest",
86
+ strategy: dict[str, Any] | None = None,
85
87
  permissions: dict[str, Any] | None = None,
86
88
  if_condition: str | None = None,
87
89
  steps: list[dict[str, Any]] | None = None,
90
+ needs: list[str] | None = None,
88
91
  ) -> dict[str, Any]:
89
92
  """Get a standard job."""
93
+ job: dict[str, Any] = {}
90
94
  if name is None:
91
95
  name = cls.get_filename()
96
+ job[name] = {}
97
+ job_config = job[name]
92
98
 
93
- if steps is None:
94
- steps = []
95
-
96
- job: dict[str, Any] = {
97
- name: {
98
- "runs-on": runs_on,
99
- "steps": steps,
100
- }
101
- }
102
99
  if permissions is not None:
103
- job[name]["permissions"] = permissions
100
+ job_config["permissions"] = permissions
101
+
102
+ if strategy is not None:
103
+ job_config["strategy"] = strategy
104
+
105
+ job_config["runs-on"] = runs_on
106
+
107
+ if needs is not None:
108
+ job_config["needs"] = needs
104
109
 
105
110
  if if_condition is not None:
106
- job[name]["if"] = if_condition
111
+ job_config["if"] = if_condition
112
+
113
+ if steps is None:
114
+ steps = []
115
+ job_config["steps"] = steps
116
+
107
117
  return job
108
118
 
109
119
  @classmethod
@@ -151,8 +161,9 @@ class Workflow(YamlConfigFile):
151
161
  fetch_depth: int | None = None,
152
162
  configure_pipy_token: bool = False,
153
163
  force_main_head: bool = False,
154
- token: bool = False,
164
+ repo_token: bool = False,
155
165
  with_keyring: bool = False,
166
+ strategy_matrix: bool = False,
156
167
  ) -> list[dict[str, Any]]:
157
168
  """Get the poetry steps.
158
169
 
@@ -163,13 +174,15 @@ class Workflow(YamlConfigFile):
163
174
  force_main_head: Whether to exit if the running branch or current commit is not
164
175
  equal to the most recent commit on main. This is useful for workflows that
165
176
  should only run on main.
166
- token: Whether to use the repository token.
177
+ repo_token: Whether to use the repository token.
167
178
  with_keyring: Whether to setup the keyring.
179
+ strategy_matrix: Whether to use the strategy matrix python-version.
180
+ This is useful for jobs that use a matrix.
168
181
 
169
182
  Returns:
170
183
  The poetry steps.
171
184
  """
172
- steps = [cls.get_checkout_step(fetch_depth, token=token)]
185
+ steps = [cls.get_checkout_step(fetch_depth, token=repo_token)]
173
186
  if force_main_head:
174
187
  # exit with code 1 if the running branch is not main
175
188
  steps.append(
@@ -184,21 +197,33 @@ class Workflow(YamlConfigFile):
184
197
  "name": "Setup Python",
185
198
  "uses": "actions/setup-python@main",
186
199
  "with": {
187
- "python-version": PyprojectConfigFile.get_latest_possible_python_version() # noqa: E501
200
+ # get latest if strategy matrix python-version is not set
201
+ "python-version": "${{ matrix.python-version }}"
202
+ if strategy_matrix
203
+ else str(PyprojectConfigFile.get_latest_possible_python_version())
188
204
  },
189
205
  }
190
206
  )
191
207
  steps.append(
192
208
  {
193
- "name": "Install Poetry",
194
- "run": "curl -sSL https://install.python-poetry.org | python3 -",
209
+ "name": "Setup Poetry",
210
+ "uses": "snok/install-poetry@main",
195
211
  }
196
212
  )
197
213
 
214
+ if strategy_matrix:
215
+ steps.append(
216
+ {
217
+ # windows needs this step to find poetry
218
+ "name": "Add Poetry to PATH",
219
+ "run": "echo 'C:/Users/runneradmin/.local/bin' >> $GITHUB_PATH",
220
+ }
221
+ )
222
+
198
223
  if configure_pipy_token:
199
224
  steps.append(
200
225
  {
201
- "name": "Configure Poetry",
226
+ "name": "Configure Poetry with PyPI Token",
202
227
  "run": "poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }}",
203
228
  }
204
229
  )
@@ -215,6 +240,14 @@ class Workflow(YamlConfigFile):
215
240
  def get_release_steps(cls) -> list[dict[str, Any]]:
216
241
  """Get the release steps."""
217
242
  return [
243
+ *cls.get_poetry_setup_steps(
244
+ install_dependencies=True,
245
+ repo_token=True,
246
+ with_keyring=True,
247
+ ),
248
+ cls.get_pre_commit_step(),
249
+ cls.get_commit_step(),
250
+ cls.get_extract_version_step(),
218
251
  {
219
252
  "name": "Tag and Push",
220
253
  "run": f"git push && git tag {cls.get_version()} && git push origin {cls.get_version()}", # noqa: E501
@@ -260,6 +293,9 @@ class Workflow(YamlConfigFile):
260
293
  """
261
294
  step: dict[str, Any] = {
262
295
  "name": "Run Hooks",
296
+ # poetry run is necessary although the hook itself uses poetry run as well.
297
+ # not sure why, but on windows-latest the venv is not continued to the hooks
298
+ # and if you leave it here then pre-commit command is not found
263
299
  "run": "poetry run pre-commit run --all-files --verbose",
264
300
  }
265
301
  if get_src_package() == winipedia_utils:
@@ -287,7 +323,7 @@ class Workflow(YamlConfigFile):
287
323
  """Get the setup keyring step."""
288
324
  return {
289
325
  "name": "Setup CI keyring",
290
- "run": """poetry run pip install keyrings.alt && poetry run python -c "import keyring; from keyrings.alt.file import PlaintextKeyring; keyring.set_keyring(PlaintextKeyring()); keyring.set_password('video_vault','ci_user','ci-secret-token'); print('Keyring OK:', keyring.get_password('video_vault','ci_user'))" """, # noqa: E501
326
+ "run": 'poetry run pip install keyrings.alt && poetry run python -c "import keyring; from keyrings.alt.file import PlaintextKeyring; keyring.set_keyring(PlaintextKeyring());"', # noqa: E501
291
327
  }
292
328
 
293
329
  @classmethod
@@ -6,6 +6,7 @@ This workflow is used to run tests on pull requests.
6
6
  from typing import Any
7
7
 
8
8
  from winipedia_utils.git.github.workflows.base.base import Workflow
9
+ from winipedia_utils.projects.poetry.config import PyprojectConfigFile
9
10
 
10
11
 
11
12
  class HealthCheckWorkflow(Workflow):
@@ -39,20 +40,41 @@ class HealthCheckWorkflow(Workflow):
39
40
  @classmethod
40
41
  def get_jobs(cls) -> dict[str, Any]:
41
42
  """Get the workflow jobs."""
43
+ matrix_job_name = f"{cls.get_filename()}_matrix"
42
44
  return {
43
45
  **cls.get_standard_job(
46
+ name=matrix_job_name,
47
+ runs_on="${{ matrix.os }}",
48
+ strategy={
49
+ "matrix": {
50
+ "os": ["ubuntu-latest", "windows-latest", "macos-latest"],
51
+ "python-version": [
52
+ str(v)
53
+ for v in PyprojectConfigFile.get_supported_python_versions()
54
+ ],
55
+ },
56
+ "fail-fast": True,
57
+ },
44
58
  steps=[
45
59
  *(
46
60
  cls.get_poetry_setup_steps(
47
61
  install_dependencies=True,
48
- token=True,
62
+ repo_token=True,
49
63
  with_keyring=True,
64
+ strategy_matrix=True,
50
65
  )
51
66
  ),
52
67
  cls.get_protect_repository_step(),
53
68
  cls.get_pre_commit_step(),
54
- cls.get_commit_step(),
55
- cls.get_extract_version_step(),
69
+ ],
70
+ ),
71
+ **cls.get_standard_job(
72
+ needs=[matrix_job_name],
73
+ steps=[
74
+ {
75
+ "name": "Aggregate Matrix Results",
76
+ "run": "echo 'Aggregating matrix results into one job.'",
77
+ }
56
78
  ],
57
79
  ),
58
80
  }
@@ -40,6 +40,12 @@ class ReleaseWorkflow(HealthCheckWorkflow):
40
40
  @classmethod
41
41
  def get_jobs(cls) -> dict[str, Any]:
42
42
  """Get the workflow jobs."""
43
- steps = super().get_jobs()
44
- steps[cls.get_filename()]["steps"].extend(cls.get_release_steps())
45
- return steps
43
+ jobs = HealthCheckWorkflow.get_jobs()
44
+ release_job = cls.get_standard_job(
45
+ needs=[HealthCheckWorkflow.get_filename()],
46
+ steps=[
47
+ *cls.get_release_steps(),
48
+ ],
49
+ )
50
+ jobs.update(release_job)
51
+ return jobs
@@ -38,7 +38,7 @@ class PreCommitConfigConfigFile(YamlConfigFile):
38
38
  {
39
39
  "id": hook_name,
40
40
  "name": hook_name,
41
- "entry": cls.get_python_setup_script(),
41
+ "entry": cls.get_poetry_run_setup_script(),
42
42
  "language": "system",
43
43
  "always_run": True,
44
44
  "pass_filenames": False,
@@ -7,15 +7,13 @@ strings are the arguments to the command. These funcs will be called by
7
7
  run_hooks.py, which will pass the returned list to subprocess.run().
8
8
  """
9
9
 
10
- from pathlib import Path
11
-
12
10
  from winipedia_utils.projects.poetry.poetry import (
13
11
  POETRY_ARG,
14
- get_run_python_module_args,
12
+ get_poetry_run_module_args,
15
13
  )
16
14
 
17
15
 
18
- def patch_version() -> list[str | Path]:
16
+ def patch_version() -> list[str]:
19
17
  """Patch the version in pyproject.toml.
20
18
 
21
19
  This function returns the input for subprocess.run() to patch the version
@@ -24,7 +22,7 @@ def patch_version() -> list[str | Path]:
24
22
  return [POETRY_ARG, "version", "patch"]
25
23
 
26
24
 
27
- def add_version_patch_to_git() -> list[str | Path]:
25
+ def add_version_patch_to_git() -> list[str]:
28
26
  """Add the version patch to git.
29
27
 
30
28
  This function returns the input for subprocess.run() to add the version
@@ -33,7 +31,7 @@ def add_version_patch_to_git() -> list[str | Path]:
33
31
  return ["git", "add", "pyproject.toml"]
34
32
 
35
33
 
36
- def update_package_manager() -> list[str | Path]:
34
+ def update_package_manager() -> list[str]:
37
35
  """Update the package manager.
38
36
 
39
37
  This function returns the input for subprocess.run() to update the package
@@ -42,7 +40,7 @@ def update_package_manager() -> list[str | Path]:
42
40
  return [POETRY_ARG, "self", "update"]
43
41
 
44
42
 
45
- def install_dependencies_with_dev() -> list[str | Path]:
43
+ def install_dependencies_with_dev() -> list[str]:
46
44
  """Install all dependencies.
47
45
 
48
46
  This function returns the input for subprocess.run() to install all dependencies.
@@ -50,7 +48,7 @@ def install_dependencies_with_dev() -> list[str | Path]:
50
48
  return [POETRY_ARG, "install", "--with", "dev"]
51
49
 
52
50
 
53
- def update_dependencies_with_dev() -> list[str | Path]:
51
+ def update_dependencies_with_dev() -> list[str]:
54
52
  """Update all dependencies.
55
53
 
56
54
  This function returns the input for subprocess.run() to update all dependencies.
@@ -58,7 +56,7 @@ def update_dependencies_with_dev() -> list[str | Path]:
58
56
  return [POETRY_ARG, "update", "--with", "dev"]
59
57
 
60
58
 
61
- def add_updates_to_git() -> list[str | Path]:
59
+ def add_updates_to_git() -> list[str]:
62
60
  """Add the updated dependencies to git.
63
61
 
64
62
  This function returns the input for subprocess.run() to add the updated
@@ -67,7 +65,7 @@ def add_updates_to_git() -> list[str | Path]:
67
65
  return ["git", "add", "pyproject.toml"]
68
66
 
69
67
 
70
- def lock_dependencies() -> list[str | Path]:
68
+ def lock_dependencies() -> list[str]:
71
69
  """Lock the dependencies.
72
70
 
73
71
  This function returns the input for subprocess.run() to lock the dependencies.
@@ -75,7 +73,7 @@ def lock_dependencies() -> list[str | Path]:
75
73
  return [POETRY_ARG, "lock"]
76
74
 
77
75
 
78
- def add_lock_file_to_git() -> list[str | Path]:
76
+ def add_lock_file_to_git() -> list[str]:
79
77
  """Add the lock file to git.
80
78
 
81
79
  This function returns the input for subprocess.run() to add the lock file
@@ -84,7 +82,7 @@ def add_lock_file_to_git() -> list[str | Path]:
84
82
  return ["git", "add", "poetry.lock"]
85
83
 
86
84
 
87
- def check_package_manager_configs() -> list[str | Path]:
85
+ def check_package_manager_configs() -> list[str]:
88
86
  """Check that poetry.lock and pyproject.toml is up to date.
89
87
 
90
88
  This function returns the input for subprocess.run() to check that poetry.lock
@@ -93,7 +91,7 @@ def check_package_manager_configs() -> list[str | Path]:
93
91
  return [POETRY_ARG, "check", "--strict"]
94
92
 
95
93
 
96
- def create_missing_tests() -> list[str | Path]:
94
+ def create_missing_tests() -> list[str]:
97
95
  """Create all tests for the project.
98
96
 
99
97
  This function returns the input for subprocess.run() to create all tests.
@@ -102,10 +100,10 @@ def create_missing_tests() -> list[str | Path]:
102
100
  create_tests,
103
101
  )
104
102
 
105
- return [*get_run_python_module_args(create_tests)]
103
+ return get_poetry_run_module_args(create_tests)
106
104
 
107
105
 
108
- def lint_code() -> list[str | Path]:
106
+ def lint_code() -> list[str]:
109
107
  """Check the code.
110
108
 
111
109
  This function returns the input for subprocess.run() to lint the code.
@@ -114,7 +112,7 @@ def lint_code() -> list[str | Path]:
114
112
  return ["ruff", "check", "--fix"]
115
113
 
116
114
 
117
- def format_code() -> list[str | Path]:
115
+ def format_code() -> list[str]:
118
116
  """Format the code.
119
117
 
120
118
  This function calls ruff format to format the code.
@@ -122,7 +120,7 @@ def format_code() -> list[str | Path]:
122
120
  return ["ruff", "format"]
123
121
 
124
122
 
125
- def check_static_types() -> list[str | Path]:
123
+ def check_static_types() -> list[str]:
126
124
  """Check the types.
127
125
 
128
126
  This function returns the input for subprocess.run() to check the static types.
@@ -130,7 +128,7 @@ def check_static_types() -> list[str | Path]:
130
128
  return ["mypy", "--exclude-gitignore"]
131
129
 
132
130
 
133
- def check_security() -> list[str | Path]:
131
+ def check_security() -> list[str]:
134
132
  """Check the security of the code.
135
133
 
136
134
  This function returns the input for subprocess.run() to check the security of
@@ -139,7 +137,7 @@ def check_security() -> list[str | Path]:
139
137
  return ["bandit", "-c", "pyproject.toml", "-r", "."]
140
138
 
141
139
 
142
- def run_tests() -> list[str | Path]:
140
+ def run_tests() -> list[str]:
143
141
  """Run the tests.
144
142
 
145
143
  This function returns the input for subprocess.run() to run all tests.
@@ -28,10 +28,22 @@ def run_hooks() -> None:
28
28
  passed = result.returncode == 0
29
29
 
30
30
  log_method = logger.info
31
- passed_str = (f"{GREEN}PASSED" if passed else f"{RED}FAILED") + RESET
31
+ status_str = (f"{GREEN}PASSED" if passed else f"{RED}FAILED") + RESET
32
32
  if not passed:
33
33
  log_method = logger.error
34
- passed_str += f"\n{result.stdout}"
34
+ status_str += f"""
35
+ ---------------------------------------------------------------------------------------------
36
+ Stdout:
37
+
38
+ {result.stdout}
39
+
40
+ ---------------------------------------------------------------------------------------------
41
+ Stderr:
42
+
43
+ {result.stderr}
44
+
45
+ ---------------------------------------------------------------------------------------------
46
+ """
35
47
  exit_code = 1
36
48
  # make the dashes always the same lentgth by adjusting to len of hook name
37
49
  num_dashes = 50 - len(hook_func.__name__)
@@ -39,7 +51,7 @@ def run_hooks() -> None:
39
51
  "Hook %s -%s> %s",
40
52
  hook_func.__name__,
41
53
  "-" * num_dashes,
42
- passed_str,
54
+ status_str,
43
55
  )
44
56
 
45
57
  if exit_code != 0:
@@ -13,10 +13,14 @@ from types import ModuleType
13
13
  from typing import Any
14
14
 
15
15
  from winipedia_utils.modules.function import is_func
16
+ from winipedia_utils.modules.inspection import get_def_line, get_obj_members
16
17
 
17
18
 
18
19
  def get_all_methods_from_cls(
19
- class_: type, *, exclude_parent_methods: bool = False
20
+ class_: type,
21
+ *,
22
+ exclude_parent_methods: bool = False,
23
+ include_annotate: bool = False,
20
24
  ) -> list[Callable[..., Any]]:
21
25
  """Get all methods from a class.
22
26
 
@@ -27,17 +31,21 @@ def get_all_methods_from_cls(
27
31
  class_: The class to extract methods from
28
32
  exclude_parent_methods: If True, only include methods defined in this class,
29
33
  excluding those inherited from parent classes
34
+ include_annotate: If False, exclude __annotate__ method
35
+ introduced in Python 3.14, defaults to False
36
+
30
37
  Returns:
31
38
  A list of callable methods from the class
32
39
 
33
40
  """
34
41
  from winipedia_utils.modules.module import ( # noqa: PLC0415 # avoid circular import
35
- get_def_line,
36
42
  get_module_of_obj,
37
43
  )
38
44
 
39
45
  methods = [
40
- (method, name) for name, method in inspect.getmembers(class_) if is_func(method)
46
+ (method, name)
47
+ for name, method in get_obj_members(class_, include_annotate=include_annotate)
48
+ if is_func(method)
41
49
  ]
42
50
 
43
51
  if exclude_parent_methods:
@@ -67,7 +75,6 @@ def get_all_cls_from_module(module: ModuleType | str) -> list[type]:
67
75
 
68
76
  """
69
77
  from winipedia_utils.modules.module import ( # noqa: PLC0415 # avoid circular import
70
- get_def_line,
71
78
  get_module_of_obj,
72
79
  )
73
80
 
@@ -12,6 +12,8 @@ from importlib import import_module
12
12
  from types import ModuleType
13
13
  from typing import Any
14
14
 
15
+ from winipedia_utils.modules.inspection import get_def_line, get_obj_members
16
+
15
17
 
16
18
  def is_func_or_method(obj: Any) -> bool:
17
19
  """Return True if *obj* is a function or method.
@@ -57,7 +59,9 @@ def is_func(obj: Any) -> bool:
57
59
  return is_func_or_method(unwrapped)
58
60
 
59
61
 
60
- def get_all_functions_from_module(module: ModuleType | str) -> list[Callable[..., Any]]:
62
+ def get_all_functions_from_module(
63
+ module: ModuleType | str, *, include_annotate: bool = False
64
+ ) -> list[Callable[..., Any]]:
61
65
  """Get all functions defined in a module.
62
66
 
63
67
  Retrieves all function objects that are defined directly in the specified module,
@@ -66,13 +70,14 @@ def get_all_functions_from_module(module: ModuleType | str) -> list[Callable[...
66
70
 
67
71
  Args:
68
72
  module: The module to extract functions from
73
+ include_annotate: If False, exclude __annotate__ method
74
+ introduced in Python 3.14, defaults to False
69
75
 
70
76
  Returns:
71
77
  A list of callable functions defined in the module
72
78
 
73
79
  """
74
80
  from winipedia_utils.modules.module import ( # noqa: PLC0415 # avoid circular import
75
- get_def_line,
76
81
  get_module_of_obj,
77
82
  )
78
83
 
@@ -80,7 +85,8 @@ def get_all_functions_from_module(module: ModuleType | str) -> list[Callable[...
80
85
  module = import_module(module)
81
86
  funcs = [
82
87
  func
83
- for _name, func in inspect.getmembers(module, is_func)
88
+ for _name, func in get_obj_members(module, include_annotate=include_annotate)
89
+ if is_func(func)
84
90
  if get_module_of_obj(func).__name__ == module.__name__
85
91
  ]
86
92
  # sort by definition order