winipedia-utils 0.4.56__py3-none-any.whl → 0.5.19__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.
- winipedia_utils/git/github/workflows/base/base.py +56 -20
- winipedia_utils/git/github/workflows/health_check.py +25 -3
- winipedia_utils/git/github/workflows/release.py +9 -3
- winipedia_utils/git/pre_commit/config.py +1 -1
- winipedia_utils/git/pre_commit/hooks.py +17 -19
- winipedia_utils/git/pre_commit/run_hooks.py +15 -3
- winipedia_utils/modules/class_.py +11 -4
- winipedia_utils/modules/function.py +9 -3
- winipedia_utils/modules/inspection.py +56 -0
- winipedia_utils/modules/module.py +2 -32
- winipedia_utils/projects/poetry/config.py +35 -16
- winipedia_utils/projects/poetry/poetry.py +122 -3
- winipedia_utils/testing/create_tests.py +1 -1
- winipedia_utils/text/config.py +3 -3
- {winipedia_utils-0.4.56.dist-info → winipedia_utils-0.5.19.dist-info}/METADATA +36 -4
- {winipedia_utils-0.4.56.dist-info → winipedia_utils-0.5.19.dist-info}/RECORD +18 -17
- {winipedia_utils-0.4.56.dist-info → winipedia_utils-0.5.19.dist-info}/WHEEL +0 -0
- {winipedia_utils-0.4.56.dist-info → winipedia_utils-0.5.19.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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": "
|
|
194
|
-
"
|
|
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":
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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.
|
|
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
|
-
|
|
12
|
+
get_poetry_run_module_args,
|
|
15
13
|
)
|
|
16
14
|
|
|
17
15
|
|
|
18
|
-
def patch_version() -> list[str
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
103
|
+
return get_poetry_run_module_args(create_tests)
|
|
106
104
|
|
|
107
105
|
|
|
108
|
-
def lint_code() -> list[str
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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)
|
|
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(
|
|
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
|
|
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
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Inspection utilities for introspecting Python objects.
|
|
2
|
+
|
|
3
|
+
This module provides utility functions for inspecting Python objects,
|
|
4
|
+
including checking if an object is a function or method, and unwrapping
|
|
5
|
+
methods to their underlying functions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import inspect
|
|
9
|
+
import sys
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from typing import Any, cast
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_obj_members(
|
|
15
|
+
obj: Any, *, include_annotate: bool = False
|
|
16
|
+
) -> list[tuple[str, Any]]:
|
|
17
|
+
"""Get all members of an object."""
|
|
18
|
+
members = [(member, value) for member, value in inspect.getmembers(obj)]
|
|
19
|
+
if not include_annotate:
|
|
20
|
+
members = [
|
|
21
|
+
(member, value)
|
|
22
|
+
for member, value in members
|
|
23
|
+
if member not in ("__annotate__", "__annotate_func__")
|
|
24
|
+
]
|
|
25
|
+
return members
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def inside_frozen_bundle() -> bool:
|
|
29
|
+
"""Return True if the code is running inside a frozen bundle."""
|
|
30
|
+
return getattr(sys, "frozen", False)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_def_line(obj: Any) -> int:
|
|
34
|
+
"""Return the line number where a method-like object is defined."""
|
|
35
|
+
if isinstance(obj, property):
|
|
36
|
+
obj = obj.fget
|
|
37
|
+
unwrapped = inspect.unwrap(obj)
|
|
38
|
+
if hasattr(unwrapped, "__code__"):
|
|
39
|
+
return int(unwrapped.__code__.co_firstlineno)
|
|
40
|
+
# getsourcelines does not work if in a pyinstaller bundle or something
|
|
41
|
+
if inside_frozen_bundle():
|
|
42
|
+
return 0
|
|
43
|
+
return inspect.getsourcelines(unwrapped)[1]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_unwrapped_obj(obj: Any) -> Any:
|
|
47
|
+
"""Return the unwrapped version of a method-like object."""
|
|
48
|
+
if isinstance(obj, property):
|
|
49
|
+
obj = obj.fget # get the getter function of the property
|
|
50
|
+
return inspect.unwrap(obj)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_qualname_of_obj(obj: Callable[..., Any] | type) -> str:
|
|
54
|
+
"""Return the name of a method-like object."""
|
|
55
|
+
unwrapped = get_unwrapped_obj(obj)
|
|
56
|
+
return cast("str", unwrapped.__qualname__)
|
|
@@ -17,7 +17,7 @@ from collections.abc import Callable, Sequence
|
|
|
17
17
|
from importlib import import_module
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from types import ModuleType
|
|
20
|
-
from typing import Any
|
|
20
|
+
from typing import Any
|
|
21
21
|
|
|
22
22
|
from winipedia_utils.logging.logger import get_logger
|
|
23
23
|
from winipedia_utils.modules.class_ import (
|
|
@@ -25,6 +25,7 @@ from winipedia_utils.modules.class_ import (
|
|
|
25
25
|
get_all_methods_from_cls,
|
|
26
26
|
)
|
|
27
27
|
from winipedia_utils.modules.function import get_all_functions_from_module
|
|
28
|
+
from winipedia_utils.modules.inspection import get_qualname_of_obj, get_unwrapped_obj
|
|
28
29
|
from winipedia_utils.modules.package import (
|
|
29
30
|
get_modules_and_packages_from_package,
|
|
30
31
|
make_dir_with_init_file,
|
|
@@ -329,24 +330,6 @@ def get_default_module_content() -> str:
|
|
|
329
330
|
return '''"""module."""'''
|
|
330
331
|
|
|
331
332
|
|
|
332
|
-
def inside_frozen_bundle() -> bool:
|
|
333
|
-
"""Return True if the code is running inside a frozen bundle."""
|
|
334
|
-
return getattr(sys, "frozen", False)
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def get_def_line(obj: Any) -> int:
|
|
338
|
-
"""Return the line number where a method-like object is defined."""
|
|
339
|
-
if isinstance(obj, property):
|
|
340
|
-
obj = obj.fget
|
|
341
|
-
unwrapped = inspect.unwrap(obj)
|
|
342
|
-
if hasattr(unwrapped, "__code__"):
|
|
343
|
-
return int(unwrapped.__code__.co_firstlineno)
|
|
344
|
-
# getsourcelines does not work if in a pyinstaller bundle or something
|
|
345
|
-
if inside_frozen_bundle():
|
|
346
|
-
return 0
|
|
347
|
-
return inspect.getsourcelines(unwrapped)[1]
|
|
348
|
-
|
|
349
|
-
|
|
350
333
|
def get_module_of_obj(obj: Any, default: ModuleType | None = None) -> ModuleType:
|
|
351
334
|
"""Return the module name where a method-like object is defined.
|
|
352
335
|
|
|
@@ -368,19 +351,6 @@ def get_module_of_obj(obj: Any, default: ModuleType | None = None) -> ModuleType
|
|
|
368
351
|
return module
|
|
369
352
|
|
|
370
353
|
|
|
371
|
-
def get_qualname_of_obj(obj: Callable[..., Any] | type) -> str:
|
|
372
|
-
"""Return the name of a method-like object."""
|
|
373
|
-
unwrapped = get_unwrapped_obj(obj)
|
|
374
|
-
return cast("str", unwrapped.__qualname__)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
def get_unwrapped_obj(obj: Any) -> Any:
|
|
378
|
-
"""Return the unwrapped version of a method-like object."""
|
|
379
|
-
if isinstance(obj, property):
|
|
380
|
-
obj = obj.fget # get the getter function of the property
|
|
381
|
-
return inspect.unwrap(obj)
|
|
382
|
-
|
|
383
|
-
|
|
384
354
|
def get_executing_module() -> ModuleType:
|
|
385
355
|
"""Get the module where execution has started.
|
|
386
356
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"""Config utilities for poetry and pyproject.toml."""
|
|
2
2
|
|
|
3
|
+
from functools import cache
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Any, cast
|
|
5
6
|
|
|
7
|
+
import requests
|
|
8
|
+
from packaging.version import Version
|
|
9
|
+
|
|
6
10
|
from winipedia_utils.modules.package import get_src_package, make_name_from_package
|
|
7
11
|
from winipedia_utils.projects.poetry.poetry import VersionConstraint
|
|
8
12
|
from winipedia_utils.testing.config import ExperimentConfigFile
|
|
@@ -135,25 +139,29 @@ class PyprojectConfigFile(TomlConfigFile):
|
|
|
135
139
|
return cls.get_main_author()["name"]
|
|
136
140
|
|
|
137
141
|
@classmethod
|
|
138
|
-
|
|
142
|
+
@cache
|
|
143
|
+
def fetch_latest_python_version(cls) -> Version:
|
|
144
|
+
"""Fetch the latest python version from python.org."""
|
|
145
|
+
url = "https://endoflife.date/api/python.json"
|
|
146
|
+
resp = requests.get(url, timeout=10)
|
|
147
|
+
resp.raise_for_status()
|
|
148
|
+
data = resp.json()
|
|
149
|
+
# first element has metadata for latest stable
|
|
150
|
+
latest_version = data[0]["latest"]
|
|
151
|
+
return Version(latest_version)
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def get_latest_possible_python_version(cls) -> Version:
|
|
139
155
|
"""Get the latest possible python version."""
|
|
140
156
|
constraint = cls.load()["project"]["requires-python"]
|
|
141
157
|
version_constraint = VersionConstraint(constraint)
|
|
142
|
-
|
|
143
|
-
if
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
# convert to inclusive
|
|
147
|
-
if upper.micro != 0:
|
|
148
|
-
micro = upper.micro - 1
|
|
149
|
-
return f"{upper.major}.{upper.minor}" + (f".{micro}" if micro != 0 else "")
|
|
150
|
-
if upper.minor != 0:
|
|
151
|
-
minor = upper.minor - 1
|
|
152
|
-
return f"{upper.major}" + (f".{minor}" if minor != 0 else "")
|
|
153
|
-
return f"{upper.major - 1}.x"
|
|
158
|
+
version = version_constraint.get_upper_inclusive()
|
|
159
|
+
if version is None:
|
|
160
|
+
version = cls.fetch_latest_python_version()
|
|
161
|
+
return version
|
|
154
162
|
|
|
155
163
|
@classmethod
|
|
156
|
-
def get_first_supported_python_version(cls) ->
|
|
164
|
+
def get_first_supported_python_version(cls) -> Version:
|
|
157
165
|
"""Get the first supported python version."""
|
|
158
166
|
constraint = cls.load()["project"]["requires-python"]
|
|
159
167
|
version_constraint = VersionConstraint(constraint)
|
|
@@ -161,7 +169,16 @@ class PyprojectConfigFile(TomlConfigFile):
|
|
|
161
169
|
if lower is None:
|
|
162
170
|
msg = "Need a lower bound for python version"
|
|
163
171
|
raise ValueError(msg)
|
|
164
|
-
return
|
|
172
|
+
return lower
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def get_supported_python_versions(cls) -> list[Version]:
|
|
176
|
+
"""Get all supported python versions."""
|
|
177
|
+
constraint = cls.load()["project"]["requires-python"]
|
|
178
|
+
version_constraint = VersionConstraint(constraint)
|
|
179
|
+
return version_constraint.get_version_range(
|
|
180
|
+
level="minor", upper_default=cls.fetch_latest_python_version()
|
|
181
|
+
)
|
|
165
182
|
|
|
166
183
|
|
|
167
184
|
class TypedConfigFile(ConfigFile):
|
|
@@ -223,7 +240,9 @@ class DotPythonVersionConfigFile(ConfigFile):
|
|
|
223
240
|
def get_configs(cls) -> dict[str, Any]:
|
|
224
241
|
"""Get the config."""
|
|
225
242
|
return {
|
|
226
|
-
cls.VERSION_KEY:
|
|
243
|
+
cls.VERSION_KEY: str(
|
|
244
|
+
PyprojectConfigFile.get_first_supported_python_version()
|
|
245
|
+
)
|
|
227
246
|
}
|
|
228
247
|
|
|
229
248
|
@classmethod
|
|
@@ -5,6 +5,7 @@ This module provides utility functions for working with Python projects
|
|
|
5
5
|
|
|
6
6
|
from collections.abc import Iterable
|
|
7
7
|
from types import ModuleType
|
|
8
|
+
from typing import Literal
|
|
8
9
|
|
|
9
10
|
from packaging.specifiers import SpecifierSet
|
|
10
11
|
from packaging.version import Version
|
|
@@ -35,6 +36,11 @@ def get_run_python_module_args(module: ModuleType) -> list[str]:
|
|
|
35
36
|
return [*RUN_PYTHON_MODULE_ARGS, make_obj_importpath(module)]
|
|
36
37
|
|
|
37
38
|
|
|
39
|
+
def get_poetry_run_module_args(module: ModuleType) -> list[str]:
|
|
40
|
+
"""Get the args to run a module."""
|
|
41
|
+
return [*POETRY_RUN_ARGS, *get_run_python_module_args(module)]
|
|
42
|
+
|
|
43
|
+
|
|
38
44
|
def get_python_module_script(module: ModuleType) -> str:
|
|
39
45
|
"""Get the script to run a module."""
|
|
40
46
|
return get_script_from_args(get_run_python_module_args(module))
|
|
@@ -42,7 +48,7 @@ def get_python_module_script(module: ModuleType) -> str:
|
|
|
42
48
|
|
|
43
49
|
def get_poetry_run_module_script(module: ModuleType) -> str:
|
|
44
50
|
"""Get the script to run a module."""
|
|
45
|
-
return get_script_from_args(
|
|
51
|
+
return get_script_from_args(get_poetry_run_module_args(module))
|
|
46
52
|
|
|
47
53
|
|
|
48
54
|
class VersionConstraint:
|
|
@@ -92,7 +98,9 @@ class VersionConstraint:
|
|
|
92
98
|
max(self.lowers_inclusive) if self.lowers_inclusive else None
|
|
93
99
|
)
|
|
94
100
|
|
|
95
|
-
def get_lower_inclusive(
|
|
101
|
+
def get_lower_inclusive(
|
|
102
|
+
self, default: str | Version | None = None
|
|
103
|
+
) -> Version | None:
|
|
96
104
|
"""Get the minimum version.
|
|
97
105
|
|
|
98
106
|
Is given inclusive. E.g. >=3.8, <3.12 -> 3.8
|
|
@@ -106,12 +114,15 @@ class VersionConstraint:
|
|
|
106
114
|
Returns:
|
|
107
115
|
The minimum version
|
|
108
116
|
"""
|
|
117
|
+
default = str(default) if default else None
|
|
109
118
|
if self.lower_inclusive is None:
|
|
110
119
|
return Version(default) if default else None
|
|
111
120
|
|
|
112
121
|
return self.lower_inclusive
|
|
113
122
|
|
|
114
|
-
def get_upper_exclusive(
|
|
123
|
+
def get_upper_exclusive(
|
|
124
|
+
self, default: str | Version | None = None
|
|
125
|
+
) -> Version | None:
|
|
115
126
|
"""Get the maximum version.
|
|
116
127
|
|
|
117
128
|
Is given exclusive. E.g. >=3.8, <3.12 -> 3.12
|
|
@@ -123,7 +134,115 @@ class VersionConstraint:
|
|
|
123
134
|
Returns:
|
|
124
135
|
The maximum version
|
|
125
136
|
"""
|
|
137
|
+
default = str(default) if default else None
|
|
126
138
|
if self.upper_exclusive is None:
|
|
127
139
|
return Version(default) if default else None
|
|
128
140
|
|
|
129
141
|
return self.upper_exclusive
|
|
142
|
+
|
|
143
|
+
def get_upper_inclusive(
|
|
144
|
+
self, default: str | Version | None = None
|
|
145
|
+
) -> Version | None:
|
|
146
|
+
"""Get the maximum version.
|
|
147
|
+
|
|
148
|
+
Is given inclusive. E.g. >=3.8, <3.12 -> 3.11
|
|
149
|
+
if >=3.8, <=3.12 -> 3.12
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
default: The default value to return if there is no maximum version
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The maximum version
|
|
156
|
+
"""
|
|
157
|
+
# increment the default by 1 micro to make it exclusive
|
|
158
|
+
if default:
|
|
159
|
+
default = Version(str(default))
|
|
160
|
+
default = Version(f"{default.major}.{default.minor}.{default.micro + 1}")
|
|
161
|
+
upper_exclusive = self.get_upper_exclusive(default)
|
|
162
|
+
if upper_exclusive is None:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
if upper_exclusive.micro != 0:
|
|
166
|
+
return Version(
|
|
167
|
+
f"{upper_exclusive.major}.{upper_exclusive.minor}.{upper_exclusive.micro - 1}" # noqa: E501
|
|
168
|
+
)
|
|
169
|
+
if upper_exclusive.minor != 0:
|
|
170
|
+
return Version(f"{upper_exclusive.major}.{upper_exclusive.minor - 1}")
|
|
171
|
+
return Version(f"{upper_exclusive.major - 1}")
|
|
172
|
+
|
|
173
|
+
def get_version_range(
|
|
174
|
+
self,
|
|
175
|
+
level: Literal["major", "minor", "micro"] = "major",
|
|
176
|
+
lower_default: str | Version | None = None,
|
|
177
|
+
upper_default: str | Version | None = None,
|
|
178
|
+
) -> list[Version]:
|
|
179
|
+
"""Get the version range.
|
|
180
|
+
|
|
181
|
+
returns a range of versions according to the level
|
|
182
|
+
|
|
183
|
+
E.g. >=3.8, <3.12; level=major -> 3
|
|
184
|
+
>=3.8, <4.12; level=major -> 3, 4
|
|
185
|
+
E.g. >=3.8, <=3.12; level=minor -> 3.8, 3.9, 3.10, 3.11, 3.12
|
|
186
|
+
E.g. >=3.8.1, <=4.12.1; level=micro -> 3.8.1, 3.8.2, ... 4.12.1
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
level: The level of the version to return
|
|
190
|
+
lower_default: The default lower bound if none is specified
|
|
191
|
+
upper_default: The default upper bound if none is specified
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
A list of versions
|
|
195
|
+
"""
|
|
196
|
+
lower = self.get_lower_inclusive(lower_default)
|
|
197
|
+
upper = self.get_upper_inclusive(upper_default)
|
|
198
|
+
|
|
199
|
+
if lower is None or upper is None:
|
|
200
|
+
msg = "No lower or upper bound. Please specify default values."
|
|
201
|
+
raise ValueError(msg)
|
|
202
|
+
|
|
203
|
+
major_level, minor_level, micro_level = range(3)
|
|
204
|
+
level_int = {"major": major_level, "minor": minor_level, "micro": micro_level}[
|
|
205
|
+
level
|
|
206
|
+
]
|
|
207
|
+
lower_as_list = [lower.major, lower.minor, lower.micro]
|
|
208
|
+
upper_as_list = [upper.major, upper.minor, upper.micro]
|
|
209
|
+
|
|
210
|
+
versions: list[list[int]] = []
|
|
211
|
+
for major in range(lower_as_list[major_level], upper_as_list[major_level] + 1):
|
|
212
|
+
version = [major]
|
|
213
|
+
|
|
214
|
+
minor_lower_og, minor_upper_og = (
|
|
215
|
+
lower_as_list[minor_level],
|
|
216
|
+
upper_as_list[minor_level],
|
|
217
|
+
)
|
|
218
|
+
diff = minor_upper_og - minor_lower_og
|
|
219
|
+
minor_lower = minor_lower_og if diff >= 0 else 0
|
|
220
|
+
minor_upper = minor_upper_og if diff >= 0 else minor_lower_og + abs(diff)
|
|
221
|
+
for minor in range(
|
|
222
|
+
minor_lower,
|
|
223
|
+
minor_upper + 1,
|
|
224
|
+
):
|
|
225
|
+
# pop the minor if one already exists
|
|
226
|
+
if len(version) > minor_level:
|
|
227
|
+
version.pop()
|
|
228
|
+
|
|
229
|
+
version.append(minor)
|
|
230
|
+
|
|
231
|
+
micro_lower_og, micro_upper_og = (
|
|
232
|
+
lower_as_list[micro_level],
|
|
233
|
+
upper_as_list[micro_level],
|
|
234
|
+
)
|
|
235
|
+
diff = micro_upper_og - micro_lower_og
|
|
236
|
+
micro_lower = micro_lower_og if diff >= 0 else 0
|
|
237
|
+
micro_upper = (
|
|
238
|
+
micro_upper_og if diff >= 0 else micro_lower_og + abs(diff)
|
|
239
|
+
)
|
|
240
|
+
for micro in range(
|
|
241
|
+
micro_lower,
|
|
242
|
+
micro_upper + 1,
|
|
243
|
+
):
|
|
244
|
+
version.append(micro)
|
|
245
|
+
versions.append(version[: level_int + 1])
|
|
246
|
+
version.pop()
|
|
247
|
+
version_versions = sorted({Version(".".join(map(str, v))) for v in versions})
|
|
248
|
+
return [v for v in version_versions if self.sset.contains(v)]
|
|
@@ -14,11 +14,11 @@ from winipedia_utils.modules.class_ import (
|
|
|
14
14
|
get_all_methods_from_cls,
|
|
15
15
|
)
|
|
16
16
|
from winipedia_utils.modules.function import get_all_functions_from_module
|
|
17
|
+
from winipedia_utils.modules.inspection import get_qualname_of_obj
|
|
17
18
|
from winipedia_utils.modules.module import (
|
|
18
19
|
create_module,
|
|
19
20
|
get_isolated_obj_name,
|
|
20
21
|
get_module_content_as_str,
|
|
21
|
-
get_qualname_of_obj,
|
|
22
22
|
to_path,
|
|
23
23
|
)
|
|
24
24
|
from winipedia_utils.modules.package import (
|
winipedia_utils/text/config.py
CHANGED
|
@@ -13,7 +13,7 @@ from winipedia_utils.iterating.iterate import nested_structure_is_subset
|
|
|
13
13
|
from winipedia_utils.modules.class_ import init_all_nonabstract_subclasses
|
|
14
14
|
from winipedia_utils.modules.package import DependencyGraph, get_src_package
|
|
15
15
|
from winipedia_utils.projects.poetry.poetry import (
|
|
16
|
-
|
|
16
|
+
get_poetry_run_module_script,
|
|
17
17
|
)
|
|
18
18
|
from winipedia_utils.text.string import split_on_uppercase
|
|
19
19
|
|
|
@@ -158,11 +158,11 @@ class ConfigFile(ABC):
|
|
|
158
158
|
init_all_nonabstract_subclasses(cls, load_package_before=pkg)
|
|
159
159
|
|
|
160
160
|
@staticmethod
|
|
161
|
-
def
|
|
161
|
+
def get_poetry_run_setup_script() -> str:
|
|
162
162
|
"""Get the poetry run setup script."""
|
|
163
163
|
from winipedia_utils import setup # noqa: PLC0415 # avoid circular import
|
|
164
164
|
|
|
165
|
-
return
|
|
165
|
+
return get_poetry_run_module_script(setup)
|
|
166
166
|
|
|
167
167
|
|
|
168
168
|
class YamlConfigFile(ConfigFile):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: winipedia-utils
|
|
3
|
-
Version: 0.
|
|
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
|
|
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
|
|
207
|
-
- Health check workflow must pass (the
|
|
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
|
|
@@ -16,17 +16,17 @@ winipedia_utils/git/github/repo/protect.py,sha256=nOVjb5GVinGIClp7k9_qqgKnAl_gk1
|
|
|
16
16
|
winipedia_utils/git/github/repo/repo.py,sha256=OqoOfqDhe_Iik71dNqi4h3fGrMno33hSjk0bpNg3eZk,7865
|
|
17
17
|
winipedia_utils/git/github/workflows/__init__.py,sha256=BPdntTwFEyBMJ6MyT7gddPHswvRdH9tsRtfK72VSV7Y,57
|
|
18
18
|
winipedia_utils/git/github/workflows/base/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
|
|
19
|
-
winipedia_utils/git/github/workflows/base/base.py,sha256=
|
|
20
|
-
winipedia_utils/git/github/workflows/health_check.py,sha256=
|
|
19
|
+
winipedia_utils/git/github/workflows/base/base.py,sha256=K8Lb19clzaw35YCXHFJ7BVKqeI8bkqDtA-4aSjxl9LM,12770
|
|
20
|
+
winipedia_utils/git/github/workflows/health_check.py,sha256=rSz3cV5xEgMIV-SaAjKBonucS_xqz1-t9FUDUbctRy8,2523
|
|
21
21
|
winipedia_utils/git/github/workflows/publish.py,sha256=TPbSp5QH2vVl55UdqE_kjf1HIkdubcgqWlkLjWFX5EA,1378
|
|
22
|
-
winipedia_utils/git/github/workflows/release.py,sha256=
|
|
22
|
+
winipedia_utils/git/github/workflows/release.py,sha256=GkObB3LcH9J-L9dWhK5N3aowggGFu3kvmEFKHmILBu4,1456
|
|
23
23
|
winipedia_utils/git/gitignore/__init__.py,sha256=k-2E26JaZPkF69UUOJkpQl8T_PudrC7EYCIOxwgIQVU,57
|
|
24
24
|
winipedia_utils/git/gitignore/config.py,sha256=Oi1gAf2mbR7vxMi0zsAFpCGzDaLNDd5S2vXEmA3eKg4,2595
|
|
25
25
|
winipedia_utils/git/gitignore/gitignore.py,sha256=uE2MdynWgQuTG-y2YLR0FU5_giSE7s_TqSVQ6vnNOf8,2419
|
|
26
26
|
winipedia_utils/git/pre_commit/__init__.py,sha256=gFLVGQRmS6abgy5MfPQy_GZiF1_hGxuXtcOHX95WL-A,58
|
|
27
|
-
winipedia_utils/git/pre_commit/config.py,sha256=
|
|
28
|
-
winipedia_utils/git/pre_commit/hooks.py,sha256=
|
|
29
|
-
winipedia_utils/git/pre_commit/run_hooks.py,sha256=
|
|
27
|
+
winipedia_utils/git/pre_commit/config.py,sha256=6Qvb8hoSm7Dmt5KmHOGATuLGrQDWd2aRKtevWThePio,1834
|
|
28
|
+
winipedia_utils/git/pre_commit/hooks.py,sha256=d88F9Sgik3K6-lJpTio38_vx_ujL64BQ767xeBHHojY,4130
|
|
29
|
+
winipedia_utils/git/pre_commit/run_hooks.py,sha256=3D8LDkVZVBw6q4RM0KmqMiNc299rjIrtxoL0bLGP2MU,1817
|
|
30
30
|
winipedia_utils/iterating/__init__.py,sha256=rlF9hzxbowq5yOfcXvOKOQdB-EQmfrislQpf659Zeu4,53
|
|
31
31
|
winipedia_utils/iterating/iterate.py,sha256=k4U6qrnE4zij-GJhI5X0wM3pveSi8wtEsA1i8xQrfG0,3522
|
|
32
32
|
winipedia_utils/logging/__init__.py,sha256=AMt1LwA_E7hexYjMpGzUempoyDdAF-dowWvq59wC5aM,51
|
|
@@ -34,9 +34,10 @@ winipedia_utils/logging/ansi.py,sha256=7Z-FITaUn5B1ZE0OUVARjNzuMui1V36iu4YTjlD6Q
|
|
|
34
34
|
winipedia_utils/logging/config.py,sha256=sdC3GHMkJqlY-WwIsOMeTcy1bihthWV2LbvxLt9dhc4,2895
|
|
35
35
|
winipedia_utils/logging/logger.py,sha256=NluvfRpY4SfJi6URjfV52l3cxeUYFMeCAJULK_PQXpQ,701
|
|
36
36
|
winipedia_utils/modules/__init__.py,sha256=e3CFaC3FhK4ibknFOv1bqOZxA7XeVwmLqWX7oajUm78,51
|
|
37
|
-
winipedia_utils/modules/class_.py,sha256=
|
|
38
|
-
winipedia_utils/modules/function.py,sha256=
|
|
39
|
-
winipedia_utils/modules/
|
|
37
|
+
winipedia_utils/modules/class_.py,sha256=LhhDoXh664h1ILP2lfxlF6M95lFoFNJhsTozxCQ0Myo,5655
|
|
38
|
+
winipedia_utils/modules/function.py,sha256=CvMosbeL2wyAu5eT3vjAvArn9j7aUPh_X3fGoIzwWBk,3479
|
|
39
|
+
winipedia_utils/modules/inspection.py,sha256=XPSJfLN6WnNmbXn-6z9D01cmqmPS-HrTPy6-B6C34tw,1821
|
|
40
|
+
winipedia_utils/modules/module.py,sha256=spyg4yI46BLMyhCrSSXIUbY1fHFjcpmRCdKQRUJB7pQ,12792
|
|
40
41
|
winipedia_utils/modules/package.py,sha256=6Wb8Pu-SgRWGmKM2r1nhU-yftE7USSKWAuHQn2jRXlg,18058
|
|
41
42
|
winipedia_utils/oop/__init__.py,sha256=wGjsVwLbTVEQWOfDJvN9nlvC-3NmAi8Doc2xIrm6e78,47
|
|
42
43
|
winipedia_utils/oop/mixins/__init__.py,sha256=PDK-cJcdRUfDUCz36qQ5pmMW07G133WtN49OpmILGNI,54
|
|
@@ -46,8 +47,8 @@ winipedia_utils/os/__init__.py,sha256=cBRq8hWhaWvYeC3cSBYL6Y70kM9COQWHj8vVxxSadI
|
|
|
46
47
|
winipedia_utils/os/os.py,sha256=K_5FD1sC1h5aSdtqXAG0uq90sSweLYLkgkRPQS0Jfxg,1768
|
|
47
48
|
winipedia_utils/projects/__init__.py,sha256=_iYHzUcTPmutpsExPDcMF9OQDgnz-kTSuWens9iP9bI,52
|
|
48
49
|
winipedia_utils/projects/poetry/__init__.py,sha256=tbvV3wYd3H39hjjlKbF84Irj4hYgv1A7KWyXdCQzFro,59
|
|
49
|
-
winipedia_utils/projects/poetry/config.py,sha256=
|
|
50
|
-
winipedia_utils/projects/poetry/poetry.py,sha256=
|
|
50
|
+
winipedia_utils/projects/poetry/config.py,sha256=D42eowQfkBCurmb-CenujFXudtt5j1joZk0kM29wujA,8570
|
|
51
|
+
winipedia_utils/projects/poetry/poetry.py,sha256=e22_ltYXXF_y6UQbCwRX0NiQieyALkxDFfTLc2U9TSA,8478
|
|
51
52
|
winipedia_utils/projects/project.py,sha256=rirg4xCIOTI6w7cLWufAVHAix7FGicvaCd9OnZQP8dA,521
|
|
52
53
|
winipedia_utils/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
53
54
|
winipedia_utils/resources/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
|
|
@@ -69,7 +70,7 @@ winipedia_utils/testing/__init__.py,sha256=kXhB5xw02ec5xpcW_KV--9CBKdyCjnuR-NZzA
|
|
|
69
70
|
winipedia_utils/testing/assertions.py,sha256=VoksKQKEXppY9e8Bk8d_WMAjRX5mDE8c5blBwzK6nWo,1494
|
|
70
71
|
winipedia_utils/testing/config.py,sha256=Xtgu7Nsll6pIzHLUDsalzt-nPBNSNOs0JKCXWi5v36M,3508
|
|
71
72
|
winipedia_utils/testing/convention.py,sha256=7vurpqS--awhN_FLSOviSKENGuFyY9Ejr1NKRm0MPsg,4833
|
|
72
|
-
winipedia_utils/testing/create_tests.py,sha256=
|
|
73
|
+
winipedia_utils/testing/create_tests.py,sha256=sr_j-0faeGWoIMFTdi8mLpkhUOqsgLdMkQ8MCHgF5j4,9389
|
|
73
74
|
winipedia_utils/testing/fixtures.py,sha256=G8QIrZXndtud0uOk6PY3f8IIDoyX_ronogjeLAYGjrM,1033
|
|
74
75
|
winipedia_utils/testing/skip.py,sha256=WpVk1J4DBISDfR6CXOKCEjP4MHy52DaGwPkmdDuM6Y0,486
|
|
75
76
|
winipedia_utils/testing/tests/__init__.py,sha256=kL-1O6lAO5j4JPOqPdi3dHdbOQ_UXcgPFppj82HhrRU,57
|
|
@@ -86,9 +87,9 @@ winipedia_utils/testing/tests/base/utils/__init__.py,sha256=mC-8dCkp8xarqkQu2QQL
|
|
|
86
87
|
winipedia_utils/testing/tests/base/utils/utils.py,sha256=D7N-PW4N8853nJ2m4eYjO3jBMByYB9oh1GK4Hl5Tbwg,2598
|
|
87
88
|
winipedia_utils/testing/tests/conftest.py,sha256=BLgUJtLecOwuEsIyJ__0buqovd5AhiGvbMNk8CHgSQs,888
|
|
88
89
|
winipedia_utils/text/__init__.py,sha256=j2bwtK6kyeHI6SnoBjpRju0C1W2n2paXBDlNjNtaUxA,48
|
|
89
|
-
winipedia_utils/text/config.py,sha256=
|
|
90
|
+
winipedia_utils/text/config.py,sha256=jjKmn-tSbyaK6jGL0FxFHSREP6A6V1ZSX3RuIgvQ4io,7794
|
|
90
91
|
winipedia_utils/text/string.py,sha256=yXmwOab5hXyVQG1NwlWDpy2prj0U7Vb2F5HKLT2Y77Q,3382
|
|
91
|
-
winipedia_utils-0.
|
|
92
|
-
winipedia_utils-0.
|
|
93
|
-
winipedia_utils-0.
|
|
94
|
-
winipedia_utils-0.
|
|
92
|
+
winipedia_utils-0.5.19.dist-info/METADATA,sha256=idQGJhJSW-FtOVp7Jcph48nrILgsorcLdUy_k5wLz3Q,16129
|
|
93
|
+
winipedia_utils-0.5.19.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
94
|
+
winipedia_utils-0.5.19.dist-info/licenses/LICENSE,sha256=o316mE2gGzd__JT69p7S_zlOmKiHh8YjpImCCcWyTvM,1066
|
|
95
|
+
winipedia_utils-0.5.19.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|