winipedia-utils 0.4.48__py3-none-any.whl → 0.6.12__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.
- winipedia_utils/artifacts/__init__.py +1 -0
- winipedia_utils/artifacts/build.py +78 -0
- winipedia_utils/git/github/workflows/base/base.py +794 -199
- winipedia_utils/git/github/workflows/health_check.py +44 -32
- winipedia_utils/git/github/workflows/publish.py +25 -23
- winipedia_utils/git/github/workflows/release.py +58 -13
- winipedia_utils/git/pre_commit/config.py +3 -3
- winipedia_utils/git/pre_commit/hooks.py +11 -73
- winipedia_utils/git/pre_commit/run_hooks.py +18 -8
- 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/modules/package.py +3 -29
- winipedia_utils/os/os.py +2 -2
- winipedia_utils/projects/poetry/config.py +114 -29
- winipedia_utils/projects/poetry/dev_deps.py +21 -0
- winipedia_utils/projects/poetry/poetry.py +122 -3
- winipedia_utils/projects/project.py +1 -2
- winipedia_utils/setup.py +5 -1
- winipedia_utils/testing/create_tests.py +1 -1
- winipedia_utils/text/config.py +3 -3
- winipedia_utils/text/string.py +33 -0
- {winipedia_utils-0.4.48.dist-info → winipedia_utils-0.6.12.dist-info}/METADATA +52 -19
- {winipedia_utils-0.4.48.dist-info → winipedia_utils-0.6.12.dist-info}/RECORD +27 -23
- {winipedia_utils-0.4.48.dist-info → winipedia_utils-0.6.12.dist-info}/WHEEL +0 -0
- {winipedia_utils-0.4.48.dist-info → winipedia_utils-0.6.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,40 +18,52 @@ class HealthCheckWorkflow(Workflow):
|
|
|
18
18
|
@classmethod
|
|
19
19
|
def get_workflow_triggers(cls) -> dict[str, Any]:
|
|
20
20
|
"""Get the workflow triggers."""
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"schedule": [
|
|
26
|
-
{
|
|
27
|
-
# run every day at 6 am
|
|
28
|
-
"cron": "0 6 * * *",
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
"workflow_dispatch": {},
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
@classmethod
|
|
35
|
-
def get_permissions(cls) -> dict[str, Any]:
|
|
36
|
-
"""Get the workflow permissions."""
|
|
37
|
-
return {}
|
|
21
|
+
triggers = super().get_workflow_triggers()
|
|
22
|
+
triggers.update(cls.on_pull_request())
|
|
23
|
+
triggers.update(cls.on_schedule(cron="0 6 * * *"))
|
|
24
|
+
return triggers
|
|
38
25
|
|
|
39
26
|
@classmethod
|
|
40
27
|
def get_jobs(cls) -> dict[str, Any]:
|
|
41
28
|
"""Get the workflow jobs."""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
29
|
+
jobs: dict[str, Any] = {}
|
|
30
|
+
jobs.update(cls.job_health_check_matrix())
|
|
31
|
+
jobs.update(cls.job_health_check())
|
|
32
|
+
return jobs
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def job_health_check_matrix(cls) -> dict[str, Any]:
|
|
36
|
+
"""Get the health check matrix job."""
|
|
37
|
+
return cls.get_job(
|
|
38
|
+
job_func=cls.job_health_check_matrix,
|
|
39
|
+
strategy=cls.strategy_matrix_os_and_python_version(),
|
|
40
|
+
runs_on=cls.insert_matrix_os(),
|
|
41
|
+
steps=cls.steps_health_check_matrix(),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def job_health_check(cls) -> dict[str, Any]:
|
|
46
|
+
"""Get the health check job."""
|
|
47
|
+
return cls.get_job(
|
|
48
|
+
job_func=cls.job_health_check,
|
|
49
|
+
needs=[cls.make_id_from_func(cls.job_health_check_matrix)],
|
|
50
|
+
steps=cls.steps_aggregate_matrix_results(),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def steps_health_check_matrix(cls) -> list[dict[str, Any]]:
|
|
55
|
+
"""Get the health check matrix steps."""
|
|
56
|
+
return [
|
|
57
|
+
*cls.steps_core_matrix_setup(
|
|
58
|
+
python_version=cls.insert_matrix_python_version()
|
|
56
59
|
),
|
|
57
|
-
|
|
60
|
+
cls.step_protect_repository(),
|
|
61
|
+
cls.step_run_pre_commit_hooks(),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def steps_aggregate_matrix_results(cls) -> list[dict[str, Any]]:
|
|
66
|
+
"""Get the aggregate matrix results step."""
|
|
67
|
+
return [
|
|
68
|
+
cls.step_aggregate_matrix_results(),
|
|
69
|
+
]
|
|
@@ -19,31 +19,33 @@ class PublishWorkflow(Workflow):
|
|
|
19
19
|
@classmethod
|
|
20
20
|
def get_workflow_triggers(cls) -> dict[str, Any]:
|
|
21
21
|
"""Get the workflow triggers."""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@classmethod
|
|
30
|
-
def get_permissions(cls) -> dict[str, Any]:
|
|
31
|
-
"""Get the workflow permissions."""
|
|
32
|
-
return {
|
|
33
|
-
"contents": "read",
|
|
34
|
-
}
|
|
22
|
+
triggers = super().get_workflow_triggers()
|
|
23
|
+
triggers.update(
|
|
24
|
+
cls.on_workflow_run(workflows=[ReleaseWorkflow.get_workflow_name()])
|
|
25
|
+
)
|
|
26
|
+
return triggers
|
|
35
27
|
|
|
36
28
|
@classmethod
|
|
37
29
|
def get_jobs(cls) -> dict[str, Any]:
|
|
38
30
|
"""Get the workflow jobs."""
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
jobs: dict[str, Any] = {}
|
|
32
|
+
jobs.update(cls.job_publish())
|
|
33
|
+
return jobs
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def job_publish(cls) -> dict[str, Any]:
|
|
37
|
+
"""Get the publish job."""
|
|
38
|
+
return cls.get_job(
|
|
39
|
+
job_func=cls.job_publish,
|
|
40
|
+
steps=cls.steps_publish(),
|
|
41
|
+
if_condition=cls.if_workflow_run_is_success(),
|
|
49
42
|
)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def steps_publish(cls) -> list[dict[str, Any]]:
|
|
46
|
+
"""Get the publish steps."""
|
|
47
|
+
return [
|
|
48
|
+
*cls.steps_core_setup(),
|
|
49
|
+
cls.step_add_pypi_token_to_poetry(),
|
|
50
|
+
cls.step_publish_to_pypi(),
|
|
51
|
+
]
|
|
@@ -19,16 +19,10 @@ class ReleaseWorkflow(HealthCheckWorkflow):
|
|
|
19
19
|
@classmethod
|
|
20
20
|
def get_workflow_triggers(cls) -> dict[str, Any]:
|
|
21
21
|
"""Get the workflow triggers."""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
# run every Tuesday at 6 am
|
|
28
|
-
"cron": "0 6 * * 2",
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
}
|
|
22
|
+
triggers = super().get_workflow_triggers()
|
|
23
|
+
triggers.update(cls.on_push())
|
|
24
|
+
triggers.update(cls.on_schedule(cron="0 6 * * 2"))
|
|
25
|
+
return triggers
|
|
32
26
|
|
|
33
27
|
@classmethod
|
|
34
28
|
def get_permissions(cls) -> dict[str, Any]:
|
|
@@ -40,6 +34,57 @@ class ReleaseWorkflow(HealthCheckWorkflow):
|
|
|
40
34
|
@classmethod
|
|
41
35
|
def get_jobs(cls) -> dict[str, Any]:
|
|
42
36
|
"""Get the workflow jobs."""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
jobs = super().get_jobs()
|
|
38
|
+
last_job_name = list(jobs.keys())[-1]
|
|
39
|
+
jobs.update(cls.job_build(needs=[last_job_name]))
|
|
40
|
+
jobs.update(cls.job_release())
|
|
41
|
+
return jobs
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def job_build(cls, needs: list[str] | None = None) -> dict[str, Any]:
|
|
45
|
+
"""Get the build job."""
|
|
46
|
+
return cls.get_job(
|
|
47
|
+
job_func=cls.job_build,
|
|
48
|
+
needs=needs,
|
|
49
|
+
strategy=cls.strategy_matrix_os(),
|
|
50
|
+
runs_on=cls.insert_matrix_os(),
|
|
51
|
+
steps=cls.steps_build(),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def job_release(cls) -> dict[str, Any]:
|
|
56
|
+
"""Get the release job."""
|
|
57
|
+
return cls.get_job(
|
|
58
|
+
job_func=cls.job_release,
|
|
59
|
+
needs=[cls.make_id_from_func(cls.job_build)],
|
|
60
|
+
steps=cls.steps_release(),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def steps_build(cls) -> list[dict[str, Any]]:
|
|
65
|
+
"""Get the build steps."""
|
|
66
|
+
if not cls.BUILD_SCRIPT_PATH.exists():
|
|
67
|
+
return [cls.step_no_build_script()]
|
|
68
|
+
return [
|
|
69
|
+
*cls.steps_core_matrix_setup(),
|
|
70
|
+
cls.step_build_artifacts(),
|
|
71
|
+
cls.step_upload_artifacts(),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def steps_release(cls) -> list[dict[str, Any]]:
|
|
76
|
+
"""Get the release steps."""
|
|
77
|
+
return [
|
|
78
|
+
*cls.steps_core_setup(repo_token=True),
|
|
79
|
+
cls.step_install_python_dependencies(),
|
|
80
|
+
cls.step_setup_keyring(),
|
|
81
|
+
cls.step_setup_git(),
|
|
82
|
+
cls.step_run_pre_commit_hooks(),
|
|
83
|
+
cls.step_commit_added_changes(),
|
|
84
|
+
cls.step_push_commits(),
|
|
85
|
+
cls.step_create_and_push_tag(),
|
|
86
|
+
cls.step_extract_version(),
|
|
87
|
+
cls.step_download_artifacts(),
|
|
88
|
+
cls.step_build_changelog(),
|
|
89
|
+
cls.step_create_release(),
|
|
90
|
+
]
|
|
@@ -5,9 +5,9 @@ from typing import Any
|
|
|
5
5
|
|
|
6
6
|
import winipedia_utils
|
|
7
7
|
from winipedia_utils.logging.logger import get_logger
|
|
8
|
-
from winipedia_utils.modules.package import make_name_from_package
|
|
9
8
|
from winipedia_utils.os.os import run_subprocess
|
|
10
9
|
from winipedia_utils.text.config import YamlConfigFile
|
|
10
|
+
from winipedia_utils.text.string import make_name_from_obj
|
|
11
11
|
|
|
12
12
|
logger = get_logger(__name__)
|
|
13
13
|
|
|
@@ -29,7 +29,7 @@ class PreCommitConfigConfigFile(YamlConfigFile):
|
|
|
29
29
|
@classmethod
|
|
30
30
|
def get_configs(cls) -> dict[str, Any]:
|
|
31
31
|
"""Get the config."""
|
|
32
|
-
hook_name =
|
|
32
|
+
hook_name = make_name_from_obj(winipedia_utils, capitalize=False)
|
|
33
33
|
return {
|
|
34
34
|
"repos": [
|
|
35
35
|
{
|
|
@@ -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,84 +7,22 @@ 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
|
|
19
|
-
"""Patch the version in pyproject.toml.
|
|
20
|
-
|
|
21
|
-
This function returns the input for subprocess.run() to patch the version
|
|
22
|
-
in pyproject.toml.
|
|
23
|
-
"""
|
|
24
|
-
return [POETRY_ARG, "version", "patch"]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def add_version_patch_to_git() -> list[str | Path]:
|
|
28
|
-
"""Add the version patch to git.
|
|
29
|
-
|
|
30
|
-
This function returns the input for subprocess.run() to add the version
|
|
31
|
-
patch to git, so that the hook does not fail bc the file was changed.
|
|
32
|
-
"""
|
|
33
|
-
return ["git", "add", "pyproject.toml"]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def update_package_manager() -> list[str | Path]:
|
|
37
|
-
"""Update the package manager.
|
|
38
|
-
|
|
39
|
-
This function returns the input for subprocess.run() to update the package
|
|
40
|
-
manager.
|
|
41
|
-
"""
|
|
42
|
-
return [POETRY_ARG, "self", "update"]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def install_dependencies_with_dev() -> list[str | Path]:
|
|
46
|
-
"""Install all dependencies.
|
|
47
|
-
|
|
48
|
-
This function returns the input for subprocess.run() to install all dependencies.
|
|
49
|
-
"""
|
|
50
|
-
return [POETRY_ARG, "install", "--with", "dev"]
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def update_dependencies_with_dev() -> list[str | Path]:
|
|
54
|
-
"""Update all dependencies.
|
|
55
|
-
|
|
56
|
-
This function returns the input for subprocess.run() to update all dependencies.
|
|
57
|
-
"""
|
|
58
|
-
return [POETRY_ARG, "update", "--with", "dev"]
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def add_updates_to_git() -> list[str | Path]:
|
|
16
|
+
def add_updates_to_git() -> list[str]:
|
|
62
17
|
"""Add the updated dependencies to git.
|
|
63
18
|
|
|
64
19
|
This function returns the input for subprocess.run() to add the updated
|
|
65
20
|
dependencies to git, so that the hook does not fail bc the file was changed.
|
|
66
21
|
"""
|
|
67
|
-
return ["git", "add", "pyproject.toml"]
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def lock_dependencies() -> list[str | Path]:
|
|
71
|
-
"""Lock the dependencies.
|
|
72
|
-
|
|
73
|
-
This function returns the input for subprocess.run() to lock the dependencies.
|
|
74
|
-
"""
|
|
75
|
-
return [POETRY_ARG, "lock"]
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def add_lock_file_to_git() -> list[str | Path]:
|
|
79
|
-
"""Add the lock file to git.
|
|
80
|
-
|
|
81
|
-
This function returns the input for subprocess.run() to add the lock file
|
|
82
|
-
to git, so that the hook does not fail bc the file was changed.
|
|
83
|
-
"""
|
|
84
|
-
return ["git", "add", "poetry.lock"]
|
|
22
|
+
return ["git", "add", "pyproject.toml", "poetry.lock"]
|
|
85
23
|
|
|
86
24
|
|
|
87
|
-
def check_package_manager_configs() -> list[str
|
|
25
|
+
def check_package_manager_configs() -> list[str]:
|
|
88
26
|
"""Check that poetry.lock and pyproject.toml is up to date.
|
|
89
27
|
|
|
90
28
|
This function returns the input for subprocess.run() to check that poetry.lock
|
|
@@ -93,7 +31,7 @@ def check_package_manager_configs() -> list[str | Path]:
|
|
|
93
31
|
return [POETRY_ARG, "check", "--strict"]
|
|
94
32
|
|
|
95
33
|
|
|
96
|
-
def create_missing_tests() -> list[str
|
|
34
|
+
def create_missing_tests() -> list[str]:
|
|
97
35
|
"""Create all tests for the project.
|
|
98
36
|
|
|
99
37
|
This function returns the input for subprocess.run() to create all tests.
|
|
@@ -102,10 +40,10 @@ def create_missing_tests() -> list[str | Path]:
|
|
|
102
40
|
create_tests,
|
|
103
41
|
)
|
|
104
42
|
|
|
105
|
-
return
|
|
43
|
+
return get_poetry_run_module_args(create_tests)
|
|
106
44
|
|
|
107
45
|
|
|
108
|
-
def lint_code() -> list[str
|
|
46
|
+
def lint_code() -> list[str]:
|
|
109
47
|
"""Check the code.
|
|
110
48
|
|
|
111
49
|
This function returns the input for subprocess.run() to lint the code.
|
|
@@ -114,7 +52,7 @@ def lint_code() -> list[str | Path]:
|
|
|
114
52
|
return ["ruff", "check", "--fix"]
|
|
115
53
|
|
|
116
54
|
|
|
117
|
-
def format_code() -> list[str
|
|
55
|
+
def format_code() -> list[str]:
|
|
118
56
|
"""Format the code.
|
|
119
57
|
|
|
120
58
|
This function calls ruff format to format the code.
|
|
@@ -122,7 +60,7 @@ def format_code() -> list[str | Path]:
|
|
|
122
60
|
return ["ruff", "format"]
|
|
123
61
|
|
|
124
62
|
|
|
125
|
-
def check_static_types() -> list[str
|
|
63
|
+
def check_static_types() -> list[str]:
|
|
126
64
|
"""Check the types.
|
|
127
65
|
|
|
128
66
|
This function returns the input for subprocess.run() to check the static types.
|
|
@@ -130,7 +68,7 @@ def check_static_types() -> list[str | Path]:
|
|
|
130
68
|
return ["mypy", "--exclude-gitignore"]
|
|
131
69
|
|
|
132
70
|
|
|
133
|
-
def check_security() -> list[str
|
|
71
|
+
def check_security() -> list[str]:
|
|
134
72
|
"""Check the security of the code.
|
|
135
73
|
|
|
136
74
|
This function returns the input for subprocess.run() to check the security of
|
|
@@ -139,7 +77,7 @@ def check_security() -> list[str | Path]:
|
|
|
139
77
|
return ["bandit", "-c", "pyproject.toml", "-r", "."]
|
|
140
78
|
|
|
141
79
|
|
|
142
|
-
def run_tests() -> list[str
|
|
80
|
+
def run_tests() -> list[str]:
|
|
143
81
|
"""Run the tests.
|
|
144
82
|
|
|
145
83
|
This function returns the input for subprocess.run() to run all tests.
|
|
@@ -19,7 +19,6 @@ def run_hooks() -> None:
|
|
|
19
19
|
"""Import all funcs defined in hooks.py and runs them."""
|
|
20
20
|
hook_funcs = get_all_functions_from_module(hooks)
|
|
21
21
|
|
|
22
|
-
exit_code = 0
|
|
23
22
|
for hook_func in hook_funcs:
|
|
24
23
|
subprocess_args = hook_func()
|
|
25
24
|
result = run_subprocess(
|
|
@@ -28,19 +27,30 @@ def run_hooks() -> None:
|
|
|
28
27
|
passed = result.returncode == 0
|
|
29
28
|
|
|
30
29
|
log_method = logger.info
|
|
31
|
-
|
|
30
|
+
status_str = (f"{GREEN}PASSED" if passed else f"{RED}FAILED") + RESET
|
|
32
31
|
if not passed:
|
|
33
32
|
log_method = logger.error
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
status_str += f"""
|
|
34
|
+
---------------------------------------------------------------------------------------------
|
|
35
|
+
Stdout:
|
|
36
|
+
|
|
37
|
+
{result.stdout}
|
|
38
|
+
|
|
39
|
+
---------------------------------------------------------------------------------------------
|
|
40
|
+
Stderr:
|
|
41
|
+
|
|
42
|
+
{result.stderr}
|
|
43
|
+
|
|
44
|
+
---------------------------------------------------------------------------------------------
|
|
45
|
+
"""
|
|
46
|
+
|
|
36
47
|
# make the dashes always the same lentgth by adjusting to len of hook name
|
|
37
48
|
num_dashes = 50 - len(hook_func.__name__)
|
|
38
49
|
log_method(
|
|
39
50
|
"Hook %s -%s> %s",
|
|
40
51
|
hook_func.__name__,
|
|
41
52
|
"-" * num_dashes,
|
|
42
|
-
|
|
53
|
+
status_str,
|
|
43
54
|
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
sys.exit(exit_code)
|
|
55
|
+
if not passed:
|
|
56
|
+
sys.exit(1)
|
|
@@ -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
|
|