winipedia-utils 0.4.43__py3-none-any.whl → 0.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (120) hide show
  1. winipedia_utils/artifacts/build.py +27 -0
  2. winipedia_utils/dev/artifacts/build.py +62 -0
  3. winipedia_utils/{text → dev/configs/base}/config.py +16 -11
  4. winipedia_utils/{git/gitignore/config.py → dev/configs/gitignore.py} +2 -2
  5. winipedia_utils/{git/pre_commit/config.py → dev/configs/pre_commit.py} +6 -6
  6. winipedia_utils/{projects/poetry/config.py → dev/configs/pyproject.py} +120 -32
  7. winipedia_utils/{testing/config.py → dev/configs/testing.py} +7 -4
  8. winipedia_utils/dev/configs/workflows/base/base.py +907 -0
  9. winipedia_utils/dev/configs/workflows/health_check.py +69 -0
  10. winipedia_utils/dev/configs/workflows/publish.py +51 -0
  11. winipedia_utils/dev/configs/workflows/release.py +91 -0
  12. winipedia_utils/dev/git/github/repo/__init__.py +1 -0
  13. winipedia_utils/{git → dev/git}/github/repo/protect.py +5 -5
  14. winipedia_utils/dev/git/pre_commit/hooks.py +85 -0
  15. winipedia_utils/{git → dev/git}/pre_commit/run_hooks.py +23 -13
  16. winipedia_utils/dev/projects/poetry/dev_deps.py +21 -0
  17. winipedia_utils/dev/projects/poetry/poetry.py +248 -0
  18. winipedia_utils/{projects → dev/projects}/project.py +6 -7
  19. winipedia_utils/dev/testing/__init__.py +1 -0
  20. winipedia_utils/{testing → dev/testing}/convention.py +1 -1
  21. winipedia_utils/{testing → dev/testing}/create_tests.py +14 -14
  22. winipedia_utils/dev/testing/tests/__init__.py +1 -0
  23. winipedia_utils/dev/testing/tests/base/__init__.py +1 -0
  24. winipedia_utils/dev/testing/tests/base/fixtures/__init__.py +1 -0
  25. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/fixture.py +1 -1
  26. winipedia_utils/dev/testing/tests/base/fixtures/scopes/__init__.py +1 -0
  27. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/class_.py +2 -2
  28. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/module.py +2 -2
  29. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/session.py +10 -10
  30. winipedia_utils/dev/testing/tests/base/utils/__init__.py +1 -0
  31. winipedia_utils/dev/testing/tests/base/utils/utils.py +1 -0
  32. winipedia_utils/{testing → dev/testing}/tests/conftest.py +2 -2
  33. winipedia_utils/{testing/tests/base/utils → dev/testing}/utils.py +7 -24
  34. winipedia_utils/setup.py +9 -5
  35. winipedia_utils/utils/__init__.py +1 -0
  36. winipedia_utils/utils/data/dataframe/__init__.py +1 -0
  37. winipedia_utils/{data → utils/data}/dataframe/cleaning.py +1 -1
  38. winipedia_utils/utils/data/structures/__init__.py +1 -0
  39. winipedia_utils/{text → utils/data/structures/text}/string.py +36 -3
  40. winipedia_utils/utils/git/__init__.py +1 -0
  41. winipedia_utils/utils/git/github/__init__.py +1 -0
  42. winipedia_utils/{git → utils/git}/github/github.py +1 -1
  43. winipedia_utils/utils/git/github/repo/__init__.py +1 -0
  44. winipedia_utils/{git → utils/git}/github/repo/repo.py +1 -1
  45. winipedia_utils/{git → utils/git}/gitignore/gitignore.py +2 -2
  46. winipedia_utils/{concurrent → utils/iterating/concurrent}/concurrent.py +4 -4
  47. winipedia_utils/{concurrent → utils/iterating/concurrent}/multiprocessing.py +2 -2
  48. winipedia_utils/{concurrent → utils/iterating/concurrent}/multithreading.py +1 -1
  49. winipedia_utils/{logging → utils/logging}/logger.py +1 -1
  50. winipedia_utils/{modules → utils/modules}/class_.py +15 -8
  51. winipedia_utils/{modules → utils/modules}/function.py +10 -4
  52. winipedia_utils/utils/modules/inspection.py +56 -0
  53. winipedia_utils/{modules → utils/modules}/module.py +27 -32
  54. winipedia_utils/{modules → utils/modules}/package.py +100 -34
  55. winipedia_utils/{oop → utils/oop}/mixins/meta.py +4 -4
  56. winipedia_utils/{oop → utils/oop}/mixins/mixin.py +2 -2
  57. winipedia_utils/{os → utils/os}/os.py +2 -2
  58. winipedia_utils/utils/resources/__init__.py +1 -0
  59. winipedia_utils/utils/resources/svgs/__init__.py +1 -0
  60. winipedia_utils/{resources → utils/resources}/svgs/svg.py +1 -1
  61. winipedia_utils/utils/testing/__init__.py +1 -0
  62. winipedia_utils/{testing → utils/testing}/assertions.py +18 -0
  63. winipedia_utils/{testing → utils/testing}/skip.py +1 -1
  64. {winipedia_utils-0.4.43.dist-info → winipedia_utils-0.7.1.dist-info}/METADATA +71 -36
  65. winipedia_utils-0.7.1.dist-info/RECORD +109 -0
  66. winipedia_utils/git/github/workflows/base/base.py +0 -294
  67. winipedia_utils/git/github/workflows/health_check.py +0 -57
  68. winipedia_utils/git/github/workflows/publish.py +0 -49
  69. winipedia_utils/git/github/workflows/release.py +0 -45
  70. winipedia_utils/git/pre_commit/hooks.py +0 -147
  71. winipedia_utils/projects/poetry/poetry.py +0 -129
  72. winipedia_utils/testing/__init__.py +0 -1
  73. winipedia_utils/testing/tests/__init__.py +0 -1
  74. winipedia_utils/testing/tests/base/__init__.py +0 -1
  75. winipedia_utils/testing/tests/base/fixtures/__init__.py +0 -1
  76. winipedia_utils/testing/tests/base/fixtures/scopes/__init__.py +0 -1
  77. winipedia_utils/testing/tests/base/utils/__init__.py +0 -1
  78. winipedia_utils-0.4.43.dist-info/RECORD +0 -94
  79. /winipedia_utils/{data/dataframe → artifacts}/__init__.py +0 -0
  80. /winipedia_utils/{data/structures → dev}/__init__.py +0 -0
  81. /winipedia_utils/{git/github → dev/artifacts}/__init__.py +0 -0
  82. /winipedia_utils/{git/github/repo → dev/configs}/__init__.py +0 -0
  83. /winipedia_utils/{git/github/workflows → dev/configs}/base/__init__.py +0 -0
  84. /winipedia_utils/{git/github → dev/configs}/workflows/__init__.py +0 -0
  85. /winipedia_utils/{resources → dev/configs/workflows/base}/__init__.py +0 -0
  86. /winipedia_utils/{git → dev/git}/__init__.py +0 -0
  87. /winipedia_utils/{resources/svgs → dev/git/github}/__init__.py +0 -0
  88. /winipedia_utils/{git → dev/git}/pre_commit/__init__.py +0 -0
  89. /winipedia_utils/{projects → dev/projects}/__init__.py +0 -0
  90. /winipedia_utils/{projects → dev/projects}/poetry/__init__.py +0 -0
  91. /winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/function.py +0 -0
  92. /winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/package.py +0 -0
  93. /winipedia_utils/{data → utils/data}/__init__.py +0 -0
  94. /winipedia_utils/{data → utils/data}/structures/dicts.py +0 -0
  95. /winipedia_utils/{text → utils/data/structures/text}/__init__.py +0 -0
  96. /winipedia_utils/{git → utils/git}/gitignore/__init__.py +0 -0
  97. /winipedia_utils/{iterating → utils/iterating}/__init__.py +0 -0
  98. /winipedia_utils/{concurrent → utils/iterating/concurrent}/__init__.py +0 -0
  99. /winipedia_utils/{iterating → utils/iterating}/iterate.py +0 -0
  100. /winipedia_utils/{logging → utils/logging}/__init__.py +0 -0
  101. /winipedia_utils/{logging → utils/logging}/ansi.py +0 -0
  102. /winipedia_utils/{logging → utils/logging}/config.py +0 -0
  103. /winipedia_utils/{modules → utils/modules}/__init__.py +0 -0
  104. /winipedia_utils/{oop → utils/oop}/__init__.py +0 -0
  105. /winipedia_utils/{oop → utils/oop}/mixins/__init__.py +0 -0
  106. /winipedia_utils/{os → utils/os}/__init__.py +0 -0
  107. /winipedia_utils/{resources → utils/resources}/svgs/delete_garbage_can.svg +0 -0
  108. /winipedia_utils/{resources → utils/resources}/svgs/download_arrow.svg +0 -0
  109. /winipedia_utils/{resources → utils/resources}/svgs/exit_fullscreen_icon.svg +0 -0
  110. /winipedia_utils/{resources → utils/resources}/svgs/fullscreen_icon.svg +0 -0
  111. /winipedia_utils/{resources → utils/resources}/svgs/menu_icon.svg +0 -0
  112. /winipedia_utils/{resources → utils/resources}/svgs/pause_icon.svg +0 -0
  113. /winipedia_utils/{resources → utils/resources}/svgs/play_icon.svg +0 -0
  114. /winipedia_utils/{resources → utils/resources}/svgs/plus_icon.svg +0 -0
  115. /winipedia_utils/{security → utils/security}/__init__.py +0 -0
  116. /winipedia_utils/{security → utils/security}/cryptography.py +0 -0
  117. /winipedia_utils/{security → utils/security}/keyring.py +0 -0
  118. /winipedia_utils/{testing → utils/testing}/fixtures.py +0 -0
  119. {winipedia_utils-0.4.43.dist-info → winipedia_utils-0.7.1.dist-info}/WHEEL +0 -0
  120. {winipedia_utils-0.4.43.dist-info → winipedia_utils-0.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,69 @@
1
+ """Contains the pull request workflow.
2
+
3
+ This workflow is used to run tests on pull requests.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from winipedia_utils.dev.configs.workflows.base.base import Workflow
9
+
10
+
11
+ class HealthCheckWorkflow(Workflow):
12
+ """Pull request workflow.
13
+
14
+ This workflow is triggered by a pull request.
15
+ It runs tests on the pull request.
16
+ """
17
+
18
+ @classmethod
19
+ def get_workflow_triggers(cls) -> dict[str, Any]:
20
+ """Get the workflow triggers."""
21
+ triggers = super().get_workflow_triggers()
22
+ triggers.update(cls.on_pull_request())
23
+ triggers.update(cls.on_schedule(cron="0 6 * * *"))
24
+ return triggers
25
+
26
+ @classmethod
27
+ def get_jobs(cls) -> dict[str, Any]:
28
+ """Get the workflow jobs."""
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()
59
+ ),
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
+ ]
@@ -0,0 +1,51 @@
1
+ """Contains the publish workflow.
2
+
3
+ This workflow is used to publish the package to PyPI with poetry.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from winipedia_utils.dev.configs.workflows.base.base import Workflow
9
+ from winipedia_utils.dev.configs.workflows.release import ReleaseWorkflow
10
+
11
+
12
+ class PublishWorkflow(Workflow):
13
+ """Publish workflow.
14
+
15
+ This workflow is triggered by the release workflow.
16
+ It publishes the package to PyPI with poetry.
17
+ """
18
+
19
+ @classmethod
20
+ def get_workflow_triggers(cls) -> dict[str, Any]:
21
+ """Get the workflow triggers."""
22
+ triggers = super().get_workflow_triggers()
23
+ triggers.update(
24
+ cls.on_workflow_run(workflows=[ReleaseWorkflow.get_workflow_name()])
25
+ )
26
+ return triggers
27
+
28
+ @classmethod
29
+ def get_jobs(cls) -> dict[str, Any]:
30
+ """Get the workflow jobs."""
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(),
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
+ ]
@@ -0,0 +1,91 @@
1
+ """Contains the release workflow.
2
+
3
+ This workflow is used to create a release on GitHub.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from winipedia_utils.dev.configs.workflows.health_check import HealthCheckWorkflow
9
+
10
+
11
+ class ReleaseWorkflow(HealthCheckWorkflow):
12
+ """Release workflow.
13
+
14
+ This workflow is triggered by a push to the main branch.
15
+ It creates a tag for the release and builds a changelog.
16
+ With tag and changelog it creates a release on GitHub
17
+ """
18
+
19
+ @classmethod
20
+ def get_workflow_triggers(cls) -> dict[str, Any]:
21
+ """Get the workflow triggers."""
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
26
+
27
+ @classmethod
28
+ def get_permissions(cls) -> dict[str, Any]:
29
+ """Get the workflow permissions."""
30
+ permissions = super().get_permissions()
31
+ permissions["contents"] = "write"
32
+ return permissions
33
+
34
+ @classmethod
35
+ def get_jobs(cls) -> dict[str, Any]:
36
+ """Get the workflow jobs."""
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_add_version_patch(),
83
+ cls.step_run_pre_commit_hooks(),
84
+ cls.step_commit_added_changes(),
85
+ cls.step_push_commits(),
86
+ cls.step_create_and_push_tag(),
87
+ cls.step_extract_version(),
88
+ cls.step_download_artifacts(),
89
+ cls.step_build_changelog(),
90
+ cls.step_create_release(),
91
+ ]
@@ -0,0 +1 @@
1
+ """__init__ module."""
@@ -2,17 +2,17 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
- from winipedia_utils.git.github.github import get_github_repo_token
6
- from winipedia_utils.git.github.repo.repo import (
5
+ from winipedia_utils.dev.configs.pyproject import PyprojectConfigFile
6
+ from winipedia_utils.dev.configs.workflows.health_check import HealthCheckWorkflow
7
+ from winipedia_utils.utils.git.github.github import get_github_repo_token
8
+ from winipedia_utils.utils.git.github.repo.repo import (
7
9
  DEFAULT_BRANCH,
8
10
  DEFAULT_RULESET_NAME,
9
11
  create_or_update_ruleset,
10
12
  get_repo,
11
13
  get_rules_payload,
12
14
  )
13
- from winipedia_utils.git.github.workflows.health_check import HealthCheckWorkflow
14
- from winipedia_utils.modules.package import get_src_package
15
- from winipedia_utils.projects.poetry.config import PyprojectConfigFile
15
+ from winipedia_utils.utils.modules.package import get_src_package
16
16
 
17
17
 
18
18
  def protect_repository() -> None:
@@ -0,0 +1,85 @@
1
+ """module contains functions that return the input for subprocess.run().
2
+
3
+ Each function is named after the hook it represents. The docstring of each function
4
+ describes the hook it represents. The function returns a list of strings that
5
+ represent the command to run. The first string is the command, and the following
6
+ strings are the arguments to the command. These funcs will be called by
7
+ run_hooks.py, which will pass the returned list to subprocess.run().
8
+ """
9
+
10
+ from winipedia_utils.dev.projects.poetry.poetry import (
11
+ POETRY_ARG,
12
+ get_poetry_run_module_args,
13
+ )
14
+
15
+
16
+ def add_updates_to_git() -> list[str]:
17
+ """Add the updated dependencies to git.
18
+
19
+ This function returns the input for subprocess.run() to add the updated
20
+ dependencies to git, so that the hook does not fail bc the file was changed.
21
+ """
22
+ return ["git", "add", "pyproject.toml", "poetry.lock"]
23
+
24
+
25
+ def check_package_manager_configs() -> list[str]:
26
+ """Check that poetry.lock and pyproject.toml is up to date.
27
+
28
+ This function returns the input for subprocess.run() to check that poetry.lock
29
+ is up to date.
30
+ """
31
+ return [POETRY_ARG, "check", "--strict"]
32
+
33
+
34
+ def create_missing_tests() -> list[str]:
35
+ """Create all tests for the project.
36
+
37
+ This function returns the input for subprocess.run() to create all tests.
38
+ """
39
+ from winipedia_utils.dev.testing import ( # noqa: PLC0415 # avoid circular import
40
+ create_tests,
41
+ )
42
+
43
+ return get_poetry_run_module_args(create_tests)
44
+
45
+
46
+ def lint_code() -> list[str]:
47
+ """Check the code.
48
+
49
+ This function returns the input for subprocess.run() to lint the code.
50
+ It autofixes all errors that can be autofixed with --fix.
51
+ """
52
+ return ["ruff", "check", "--fix"]
53
+
54
+
55
+ def format_code() -> list[str]:
56
+ """Format the code.
57
+
58
+ This function calls ruff format to format the code.
59
+ """
60
+ return ["ruff", "format"]
61
+
62
+
63
+ def check_static_types() -> list[str]:
64
+ """Check the types.
65
+
66
+ This function returns the input for subprocess.run() to check the static types.
67
+ """
68
+ return ["mypy", "--exclude-gitignore"]
69
+
70
+
71
+ def check_security() -> list[str]:
72
+ """Check the security of the code.
73
+
74
+ This function returns the input for subprocess.run() to check the security of
75
+ the code.
76
+ """
77
+ return ["bandit", "-c", "pyproject.toml", "-r", "."]
78
+
79
+
80
+ def run_tests() -> list[str]:
81
+ """Run the tests.
82
+
83
+ This function returns the input for subprocess.run() to run all tests.
84
+ """
85
+ return ["pytest"]
@@ -6,11 +6,11 @@ and should not be modified manually.
6
6
 
7
7
  import sys
8
8
 
9
- from winipedia_utils.git.pre_commit import hooks
10
- from winipedia_utils.logging.ansi import GREEN, RED, RESET
11
- from winipedia_utils.logging.logger import get_logger
12
- from winipedia_utils.modules.function import get_all_functions_from_module
13
- from winipedia_utils.os.os import run_subprocess
9
+ from winipedia_utils.dev.git.pre_commit import hooks
10
+ from winipedia_utils.utils.logging.ansi import GREEN, RED, RESET
11
+ from winipedia_utils.utils.logging.logger import get_logger
12
+ from winipedia_utils.utils.modules.function import get_all_functions_from_module
13
+ from winipedia_utils.utils.os.os import run_subprocess
14
14
 
15
15
  logger = get_logger(__name__)
16
16
 
@@ -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
- passed_str = (f"{GREEN}PASSED" if passed else f"{RED}FAILED") + RESET
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
- passed_str += f"\n{result.stdout}"
35
- exit_code = 1
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
- passed_str,
53
+ status_str,
43
54
  )
44
-
45
- if exit_code != 0:
46
- sys.exit(exit_code)
55
+ if not passed:
56
+ sys.exit(1)
@@ -0,0 +1,21 @@
1
+ """Contains a dict with the dev dependencies.
2
+
3
+ For poetry when winipedia_utils is a dependency.
4
+ winipedia_utils will add these automatically to the pyproject.toml file.
5
+ winipedia utils PyprojectConfigFile will auto dump the config here so it can access it
6
+ when being a dependency in another project.
7
+ """
8
+
9
+ DEV_DEPENDENCIES: set[str] = {
10
+ "ruff",
11
+ "types-networkx",
12
+ "types-defusedxml",
13
+ "types-pyyaml",
14
+ "pytest",
15
+ "types-setuptools",
16
+ "pytest-mock",
17
+ "bandit",
18
+ "pre-commit",
19
+ "mypy",
20
+ "types-tqdm",
21
+ }
@@ -0,0 +1,248 @@
1
+ """Project utilities for introspection and manipulation.
2
+
3
+ This module provides utility functions for working with Python projects
4
+ """
5
+
6
+ from collections.abc import Iterable
7
+ from types import ModuleType
8
+ from typing import Literal
9
+
10
+ from packaging.specifiers import SpecifierSet
11
+ from packaging.version import Version
12
+
13
+ from winipedia_utils.utils.logging.logger import get_logger
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ POETRY_ARG = "poetry"
19
+
20
+ POETRY_RUN_ARGS = [POETRY_ARG, "run"]
21
+
22
+ RUN_PYTHON_MODULE_ARGS = ["python", "-m"]
23
+
24
+
25
+ def get_script_from_args(args: Iterable[str]) -> str:
26
+ """Get the script from args."""
27
+ return " ".join(args)
28
+
29
+
30
+ def get_run_python_module_args(module: ModuleType) -> list[str]:
31
+ """Get the args to run a module."""
32
+ from winipedia_utils.utils.modules.module import ( # noqa: PLC0415 # avoid circular import
33
+ make_obj_importpath,
34
+ )
35
+
36
+ return [*RUN_PYTHON_MODULE_ARGS, make_obj_importpath(module)]
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
+
44
+ def get_python_module_script(module: ModuleType) -> str:
45
+ """Get the script to run a module."""
46
+ return get_script_from_args(get_run_python_module_args(module))
47
+
48
+
49
+ def get_poetry_run_module_script(module: ModuleType) -> str:
50
+ """Get the script to run a module."""
51
+ return get_script_from_args(get_poetry_run_module_args(module))
52
+
53
+
54
+ class VersionConstraint:
55
+ """Version class."""
56
+
57
+ def __init__(self, constraint: str) -> None:
58
+ """Initialize the version."""
59
+ self.constraint = constraint
60
+ self.spec = self.constraint.strip().strip('"').strip("'")
61
+ self.sset = SpecifierSet(self.spec)
62
+
63
+ self.lowers_inclusive = [
64
+ Version(s.version) for s in self.sset if s.operator == ">="
65
+ ]
66
+ self.lowers_exclusive = [
67
+ Version(s.version) for s in self.sset if s.operator == ">"
68
+ ]
69
+ # increment the last number of exclusive, so
70
+ # >3.4.1 to >=3.4.2; <3.4.0 to <=3.4.1; 3.0.0 to <=3.0.1
71
+ self.lowers_exclusive_to_inclusive = [
72
+ Version(f"{v.major}.{v.minor}.{v.micro + 1}") for v in self.lowers_exclusive
73
+ ]
74
+ self.lowers_inclusive = (
75
+ self.lowers_inclusive + self.lowers_exclusive_to_inclusive
76
+ )
77
+
78
+ self.uppers_inclusive = [
79
+ Version(s.version) for s in self.sset if s.operator == "<="
80
+ ]
81
+ self.uppers_exclusive = [
82
+ Version(s.version) for s in self.sset if s.operator == "<"
83
+ ]
84
+
85
+ # increment the last number of inclusive, so
86
+ # <=3.4.1 to <3.4.2; >=3.4.0 to >3.4.1; 3.0.0 to >3.0.1
87
+ self.uppers_inclusive_to_exclusive = [
88
+ Version(f"{v.major}.{v.minor}.{v.micro + 1}") for v in self.uppers_inclusive
89
+ ]
90
+ self.uppers_exclusive = (
91
+ self.uppers_inclusive_to_exclusive + self.uppers_exclusive
92
+ )
93
+
94
+ self.upper_exclusive = (
95
+ min(self.uppers_exclusive) if self.uppers_exclusive else None
96
+ )
97
+ self.lower_inclusive = (
98
+ max(self.lowers_inclusive) if self.lowers_inclusive else None
99
+ )
100
+
101
+ def get_lower_inclusive(
102
+ self, default: str | Version | None = None
103
+ ) -> Version | None:
104
+ """Get the minimum version.
105
+
106
+ Is given inclusive. E.g. >=3.8, <3.12 -> 3.8
107
+ if >3.7, <3.12 -> 3.7.1
108
+
109
+ E.g. >=3.8, <3.12 -> 3.8
110
+
111
+ Args:
112
+ default: The default value to return if there is no minimum version
113
+
114
+ Returns:
115
+ The minimum version
116
+ """
117
+ default = str(default) if default else None
118
+ if self.lower_inclusive is None:
119
+ return Version(default) if default else None
120
+
121
+ return self.lower_inclusive
122
+
123
+ def get_upper_exclusive(
124
+ self, default: str | Version | None = None
125
+ ) -> Version | None:
126
+ """Get the maximum version.
127
+
128
+ Is given exclusive. E.g. >=3.8, <3.12 -> 3.12
129
+ if >=3.8, <=3.12 -> 3.12.1
130
+
131
+ Args:
132
+ default: The default value to return if there is no maximum version
133
+
134
+ Returns:
135
+ The maximum version
136
+ """
137
+ default = str(default) if default else None
138
+ if self.upper_exclusive is None:
139
+ return Version(default) if default else None
140
+
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)]
@@ -1,17 +1,16 @@
1
1
  """Utilities for working with Python projects."""
2
2
 
3
- from winipedia_utils.modules.module import create_module
4
- from winipedia_utils.projects.poetry.config import (
5
- PyprojectConfigFile, # avoid circular import
6
- )
7
- from winipedia_utils.text.config import (
3
+ from winipedia_utils.dev.configs.base.config import (
8
4
  ConfigFile, # avoid circular import
9
5
  )
6
+ from winipedia_utils.dev.configs.pyproject import (
7
+ PyprojectConfigFile, # avoid circular import
8
+ )
9
+ from winipedia_utils.utils.modules.module import create_module
10
10
 
11
11
 
12
12
  def create_project_root() -> None:
13
13
  """Create the project root."""
14
- ConfigFile.init_config_files()
15
-
16
14
  src_package_name = PyprojectConfigFile.get_package_name()
17
15
  create_module(src_package_name, is_package=True)
16
+ ConfigFile.init_config_files()
@@ -0,0 +1 @@
1
+ """__init__ module."""