dycw-actions 0.3.2__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.
Files changed (83) hide show
  1. actions/__init__.py +1 -1
  2. actions/action_dicts/constants.py +8 -0
  3. actions/action_dicts/lib.py +186 -0
  4. actions/clean_dir/cli.py +21 -0
  5. actions/clean_dir/constants.py +7 -0
  6. actions/clean_dir/lib.py +59 -0
  7. actions/clean_dir/settings.py +18 -0
  8. actions/cli.py +95 -6
  9. actions/constants.py +10 -0
  10. actions/pre_commit/click.py +15 -0
  11. actions/pre_commit/conformalize_repo/__init__.py +1 -0
  12. actions/pre_commit/conformalize_repo/cli.py +64 -0
  13. actions/pre_commit/conformalize_repo/configs/gitignore +244 -0
  14. actions/pre_commit/conformalize_repo/constants.py +78 -0
  15. actions/pre_commit/conformalize_repo/lib.py +1293 -0
  16. actions/pre_commit/conformalize_repo/settings.py +119 -0
  17. actions/pre_commit/constants.py +8 -0
  18. actions/pre_commit/format_requirements/__init__.py +1 -0
  19. actions/pre_commit/format_requirements/cli.py +24 -0
  20. actions/pre_commit/format_requirements/constants.py +7 -0
  21. actions/pre_commit/format_requirements/lib.py +52 -0
  22. actions/pre_commit/replace_sequence_strs/__init__.py +1 -0
  23. actions/pre_commit/replace_sequence_strs/cli.py +24 -0
  24. actions/pre_commit/replace_sequence_strs/constants.py +7 -0
  25. actions/pre_commit/replace_sequence_strs/lib.py +73 -0
  26. actions/pre_commit/touch_empty_py/__init__.py +1 -0
  27. actions/pre_commit/touch_empty_py/cli.py +24 -0
  28. actions/pre_commit/touch_empty_py/constants.py +7 -0
  29. actions/pre_commit/touch_empty_py/lib.py +54 -0
  30. actions/pre_commit/touch_py_typed/__init__.py +1 -0
  31. actions/pre_commit/touch_py_typed/cli.py +24 -0
  32. actions/pre_commit/touch_py_typed/constants.py +7 -0
  33. actions/pre_commit/touch_py_typed/lib.py +64 -0
  34. actions/pre_commit/update_requirements/__init__.py +1 -0
  35. actions/pre_commit/update_requirements/classes.py +117 -0
  36. actions/pre_commit/update_requirements/cli.py +24 -0
  37. actions/pre_commit/update_requirements/constants.py +7 -0
  38. actions/pre_commit/update_requirements/lib.py +128 -0
  39. actions/pre_commit/utilities.py +386 -0
  40. actions/publish_package/__init__.py +1 -0
  41. actions/publish_package/cli.py +27 -0
  42. actions/publish_package/constants.py +7 -0
  43. actions/{publish → publish_package}/lib.py +18 -17
  44. actions/{publish → publish_package}/settings.py +7 -7
  45. actions/py.typed +0 -0
  46. actions/random_sleep/__init__.py +1 -0
  47. actions/random_sleep/cli.py +26 -0
  48. actions/random_sleep/constants.py +7 -0
  49. actions/{sleep → random_sleep}/lib.py +14 -13
  50. actions/{sleep → random_sleep}/settings.py +3 -3
  51. actions/run_hooks/__init__.py +1 -0
  52. actions/run_hooks/cli.py +21 -0
  53. actions/run_hooks/constants.py +7 -0
  54. actions/run_hooks/lib.py +97 -0
  55. actions/run_hooks/settings.py +24 -0
  56. actions/setup_cronjob/__init__.py +1 -0
  57. actions/setup_cronjob/cli.py +31 -0
  58. actions/setup_cronjob/configs/cron.tmpl +3 -0
  59. actions/setup_cronjob/configs/logrotate.tmpl +10 -0
  60. actions/setup_cronjob/constants.py +12 -0
  61. actions/setup_cronjob/lib.py +120 -0
  62. actions/setup_cronjob/settings.py +27 -0
  63. actions/tag_commit/__init__.py +1 -0
  64. actions/tag_commit/cli.py +27 -0
  65. actions/tag_commit/constants.py +7 -0
  66. actions/tag_commit/lib.py +63 -0
  67. actions/{tag → tag_commit}/settings.py +3 -3
  68. actions/types.py +14 -1
  69. actions/utilities.py +131 -17
  70. dycw_actions-0.7.1.dist-info/METADATA +22 -0
  71. dycw_actions-0.7.1.dist-info/RECORD +77 -0
  72. {dycw_actions-0.3.2.dist-info → dycw_actions-0.7.1.dist-info}/WHEEL +1 -1
  73. actions/publish/cli.py +0 -43
  74. actions/settings.py +0 -18
  75. actions/sleep/cli.py +0 -39
  76. actions/tag/cli.py +0 -43
  77. actions/tag/lib.py +0 -62
  78. dycw_actions-0.3.2.dist-info/METADATA +0 -14
  79. dycw_actions-0.3.2.dist-info/RECORD +0 -22
  80. /actions/{publish → action_dicts}/__init__.py +0 -0
  81. /actions/{sleep → clean_dir}/__init__.py +0 -0
  82. /actions/{tag → pre_commit}/__init__.py +0 -0
  83. {dycw_actions-0.3.2.dist-info → dycw_actions-0.7.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ from typed_settings import load_settings, option, settings
4
+
5
+ from actions.pre_commit.conformalize_repo.constants import RUN_VERSION_BUMP
6
+ from actions.utilities import LOADER
7
+
8
+
9
+ @settings
10
+ class Settings:
11
+ coverage: bool = option(default=False, help="Set up '.coveragerc.toml'")
12
+ description: str | None = option(default=None, help="Repo description")
13
+ envrc: bool = option(default=False, help="Set up '.envrc'")
14
+ envrc__uv: bool = option(default=False, help="Set up '.envrc' with uv")
15
+ envrc__uv__native_tls: bool = option(
16
+ default=False, help="Set up '.envrc' with uv native TLS"
17
+ )
18
+ github__pull_request__pre_commit: bool = option(
19
+ default=False, help="Set up 'pull-request.yaml' pre-commit"
20
+ )
21
+ github__pull_request__pre_commit__gitea: bool = option(
22
+ default=False, help="Set up 'pull-request.yaml' for Gitea"
23
+ )
24
+ github__pull_request__pyright: bool = option(
25
+ default=False, help="Set up 'pull-request.yaml' pyright"
26
+ )
27
+ github__pull_request__pytest__macos: bool = option(
28
+ default=False, help="Set up 'pull-request.yaml' pytest with MacOS"
29
+ )
30
+ github__pull_request__pytest__ubuntu: bool = option(
31
+ default=False, help="Set up 'pull-request.yaml' pytest with Ubuntu"
32
+ )
33
+ github__pull_request__pytest__windows: bool = option(
34
+ default=False, help="Set up 'pull-request.yaml' pytest with Windows"
35
+ )
36
+ github__pull_request__ruff: bool = option(
37
+ default=False, help="Set up 'pull-request.yaml' ruff"
38
+ )
39
+ github__push__publish: bool = option(
40
+ default=False, help="Set up 'push.yaml' publishing"
41
+ )
42
+ github__push__tag: bool = option(default=False, help="Set up 'push.yaml' tagging")
43
+ github__push__tag__major: bool = option(
44
+ default=False, help="Set up 'push.yaml' with the 'major' tag"
45
+ )
46
+ github__push__tag__major_minor: bool = option(
47
+ default=False, help="Set up 'push.yaml' with the 'major.minor' tag"
48
+ )
49
+ github__push__tag__latest: bool = option(
50
+ default=False, help="Set up 'push.yaml' tagging"
51
+ )
52
+ gitignore: bool = option(default=False, help="Set up '.gitignore'")
53
+ package_name: str | None = option(default=None, help="Package name")
54
+ pre_commit__dockerfmt: bool = option(
55
+ default=False, help="Set up '.pre-commit-config.yaml' dockerfmt"
56
+ )
57
+ pre_commit__prettier: bool = option(
58
+ default=False, help="Set up '.pre-commit-config.yaml' prettier"
59
+ )
60
+ pre_commit__python: bool = option(
61
+ default=False, help="Set up '.pre-commit-config.yaml' python"
62
+ )
63
+ pre_commit__ruff: bool = option(
64
+ default=False, help="Set up '.pre-commit-config.yaml' ruff"
65
+ )
66
+ pre_commit__shell: bool = option(
67
+ default=False, help="Set up '.pre-commit-config.yaml' shell"
68
+ )
69
+ pre_commit__taplo: bool = option(
70
+ default=False, help="Set up '.pre-commit-config.yaml' taplo"
71
+ )
72
+ pre_commit__uv: bool = option(
73
+ default=False, help="Set up '.pre-commit-config.yaml' uv"
74
+ )
75
+ pre_commit__uv__script: str | None = option(
76
+ default=None, help="Set up '.pre-commit-config.yaml' uv lock script"
77
+ )
78
+ pyproject: bool = option(default=False, help="Set up 'pyproject.toml'")
79
+ pyproject__project__optional_dependencies__scripts: bool = option(
80
+ default=False,
81
+ help="Set up 'pyproject.toml' [project.optional-dependencies.scripts]",
82
+ )
83
+ pyproject__tool__uv__indexes: list[tuple[str, str]] = option(
84
+ factory=list, help="Set up 'pyproject.toml' [[uv.tool.index]]"
85
+ )
86
+ pyright: bool = option(default=False, help="Set up 'pyrightconfig.json'")
87
+ pytest: bool = option(default=False, help="Set up 'pytest.toml'")
88
+ pytest__asyncio: bool = option(default=False, help="Set up 'pytest.toml' asyncio_*")
89
+ pytest__ignore_warnings: bool = option(
90
+ default=False, help="Set up 'pytest.toml' filterwarnings"
91
+ )
92
+ pytest__timeout: int | None = option(
93
+ default=None, help="Set up 'pytest.toml' timeout"
94
+ )
95
+ python_package_name: str | None = option(
96
+ default=None, help="Python package name override"
97
+ )
98
+ python_version: str = option(default="3.14", help="Python version")
99
+ readme: bool = option(default=False, help="Set up 'README.md'")
100
+ repo_name: str | None = option(default=None, help="Repo name")
101
+ ruff: bool = option(default=False, help="Set up 'ruff.toml'")
102
+ run_version_bump: bool = option(default=RUN_VERSION_BUMP, help="Run version bump")
103
+ script: str | None = option(
104
+ default=None, help="Set up a script instead of a package"
105
+ )
106
+
107
+ @property
108
+ def python_package_name_use(self) -> str | None:
109
+ if self.python_package_name is not None:
110
+ return self.python_package_name
111
+ if self.package_name is not None:
112
+ return self.package_name.replace("-", "_")
113
+ return None
114
+
115
+
116
+ SETTINGS = load_settings(Settings, [LOADER])
117
+
118
+
119
+ __all__ = ["SETTINGS", "Settings"]
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from actions.constants import PATH_ACTIONS
4
+
5
+ PATH_PRE_COMMIT = PATH_ACTIONS / "pre_commit"
6
+
7
+
8
+ __all__ = ["PATH_PRE_COMMIT"]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from utilities.logging import basic_config
6
+ from utilities.os import is_pytest
7
+
8
+ from actions.logging import LOGGER
9
+ from actions.pre_commit.click import path_argument
10
+ from actions.pre_commit.format_requirements.lib import format_requirements
11
+
12
+ if TYPE_CHECKING:
13
+ from pathlib import Path
14
+
15
+
16
+ @path_argument
17
+ def format_requirements_sub_cmd(*, paths: tuple[Path, ...]) -> None:
18
+ if is_pytest():
19
+ return
20
+ basic_config(obj=LOGGER)
21
+ format_requirements(*paths)
22
+
23
+
24
+ __all__ = ["format_requirements_sub_cmd"]
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ FORMAT_REQUIREMENTS_DOCSTRING = "Format a set of requirements"
4
+ FORMAT_REQUIREMENTS_SUB_CMD = "format-requirements"
5
+
6
+
7
+ __all__ = ["FORMAT_REQUIREMENTS_DOCSTRING", "FORMAT_REQUIREMENTS_SUB_CMD"]
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from typing import TYPE_CHECKING
5
+
6
+ from utilities.text import repr_str, strip_and_dedent
7
+
8
+ from actions import __version__
9
+ from actions.logging import LOGGER
10
+ from actions.pre_commit.utilities import get_pyproject_dependencies, yield_toml_doc
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import MutableSet
14
+ from pathlib import Path
15
+
16
+ from utilities.packaging import Requirement
17
+ from utilities.types import PathLike
18
+
19
+
20
+ def format_requirements(*paths: PathLike) -> None:
21
+ LOGGER.info(
22
+ strip_and_dedent("""
23
+ Running '%s' (version %s) with settings:
24
+ - paths = %s
25
+ """),
26
+ format_requirements.__name__,
27
+ __version__,
28
+ paths,
29
+ )
30
+ modifications: set[Path] = set()
31
+ for path in paths:
32
+ _format_path(path, modifications=modifications)
33
+ if len(modifications) >= 1:
34
+ LOGGER.info(
35
+ "Exiting due to modifications: %s",
36
+ ", ".join(map(repr_str, sorted(modifications))),
37
+ )
38
+ sys.exit(1)
39
+
40
+
41
+ def _format_path(
42
+ path: PathLike, /, *, modifications: MutableSet[Path] | None = None
43
+ ) -> None:
44
+ with yield_toml_doc(path, modifications=modifications) as doc:
45
+ get_pyproject_dependencies(doc).apply(_format_req)
46
+
47
+
48
+ def _format_req(requirement: Requirement, /) -> Requirement:
49
+ return requirement
50
+
51
+
52
+ __all__ = ["format_requirements"]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from utilities.logging import basic_config
6
+ from utilities.os import is_pytest
7
+
8
+ from actions.logging import LOGGER
9
+ from actions.pre_commit.click import path_argument
10
+ from actions.pre_commit.replace_sequence_strs.lib import replace_sequence_strs
11
+
12
+ if TYPE_CHECKING:
13
+ from pathlib import Path
14
+
15
+
16
+ @path_argument
17
+ def replace_sequence_strs_sub_cmd(*, paths: tuple[Path, ...]) -> None:
18
+ if is_pytest():
19
+ return
20
+ basic_config(obj=LOGGER)
21
+ replace_sequence_strs(*paths)
22
+
23
+
24
+ __all__ = ["replace_sequence_strs_sub_cmd"]
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ REPLACE_SEQUENCE_STRS_DOCSTRING = "Replace 'Sequence[str]' with 'list[str]'"
4
+ REPLACE_SEQUENCE_STRS_SUB_CMD = "replace-sequence-strs"
5
+
6
+
7
+ __all__ = ["REPLACE_SEQUENCE_STRS_DOCSTRING", "REPLACE_SEQUENCE_STRS_SUB_CMD"]
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from typing import TYPE_CHECKING, override
5
+
6
+ from libcst import CSTTransformer, Name, Subscript
7
+ from libcst.matchers import Index as MIndex
8
+ from libcst.matchers import Name as MName
9
+ from libcst.matchers import Subscript as MSubscript
10
+ from libcst.matchers import SubscriptElement as MSubscriptElement
11
+ from libcst.matchers import matches
12
+ from libcst.metadata import MetadataWrapper
13
+ from utilities.text import repr_str, strip_and_dedent
14
+
15
+ from actions import __version__
16
+ from actions.logging import LOGGER
17
+ from actions.pre_commit.utilities import yield_python_file
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import MutableSet
21
+ from pathlib import Path
22
+
23
+ from utilities.types import PathLike
24
+
25
+
26
+ def replace_sequence_strs(*paths: PathLike) -> None:
27
+ LOGGER.info(
28
+ strip_and_dedent("""
29
+ Running '%s' (version %s) with settings:
30
+ - paths = %s
31
+ """),
32
+ replace_sequence_strs.__name__,
33
+ __version__,
34
+ paths,
35
+ )
36
+ modifications: set[Path] = set()
37
+ for path in paths:
38
+ _format_path(path, modifications=modifications)
39
+ if len(modifications) >= 1:
40
+ LOGGER.info(
41
+ "Exiting due to modifications: %s",
42
+ ", ".join(map(repr_str, sorted(modifications))),
43
+ )
44
+ sys.exit(1)
45
+
46
+
47
+ def _format_path(
48
+ path: PathLike, /, *, modifications: MutableSet[Path] | None = None
49
+ ) -> None:
50
+ with yield_python_file(path, modifications=modifications) as context:
51
+ context.output = MetadataWrapper(context.input).module.visit(
52
+ SequenceToListTransformer()
53
+ )
54
+
55
+
56
+ class SequenceToListTransformer(CSTTransformer):
57
+ @override
58
+ def leave_Subscript(
59
+ self, original_node: Subscript, updated_node: Subscript
60
+ ) -> Subscript:
61
+ _ = original_node
62
+ if matches(
63
+ updated_node,
64
+ MSubscript(
65
+ value=MName("Sequence"),
66
+ slice=[MSubscriptElement(slice=MIndex(value=MName("str")))],
67
+ ),
68
+ ):
69
+ return updated_node.with_changes(value=Name("list"))
70
+ return updated_node
71
+
72
+
73
+ __all__ = ["replace_sequence_strs"]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from utilities.logging import basic_config
6
+ from utilities.os import is_pytest
7
+
8
+ from actions.logging import LOGGER
9
+ from actions.pre_commit.click import path_argument
10
+ from actions.pre_commit.touch_empty_py.lib import touch_empty_py
11
+
12
+ if TYPE_CHECKING:
13
+ from pathlib import Path
14
+
15
+
16
+ @path_argument
17
+ def touch_empty_py_sub_cmd(*, paths: tuple[Path, ...]) -> None:
18
+ if is_pytest():
19
+ return
20
+ basic_config(obj=LOGGER)
21
+ touch_empty_py(*paths)
22
+
23
+
24
+ __all__ = ["touch_empty_py_sub_cmd"]
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ TOUCH_EMPTY_PY_DOCSTRING = "Touch empty '.py' files"
4
+ TOUCH_EMPTY_PY_SUB_CMD = "touch-empty-py"
5
+
6
+
7
+ __all__ = ["TOUCH_EMPTY_PY_DOCSTRING", "TOUCH_EMPTY_PY_SUB_CMD"]
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from typing import TYPE_CHECKING
5
+
6
+ from libcst import parse_statement
7
+ from utilities.text import repr_str, strip_and_dedent
8
+
9
+ from actions import __version__
10
+ from actions.logging import LOGGER
11
+ from actions.pre_commit.utilities import yield_python_file
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import MutableSet
15
+ from pathlib import Path
16
+
17
+ from utilities.types import PathLike
18
+
19
+
20
+ def touch_empty_py(*paths: PathLike) -> None:
21
+ LOGGER.info(
22
+ strip_and_dedent("""
23
+ Running '%s' (version %s) with settings:
24
+ - paths = %s
25
+ """),
26
+ touch_empty_py.__name__,
27
+ __version__,
28
+ paths,
29
+ )
30
+ modifications: set[Path] = set()
31
+ for path in paths:
32
+ _format_path(path, modifications=modifications)
33
+ if len(modifications) >= 1:
34
+ LOGGER.info(
35
+ "Exiting due to modifications: %s",
36
+ ", ".join(map(repr_str, sorted(modifications))),
37
+ )
38
+ sys.exit(1)
39
+
40
+
41
+ def _format_path(
42
+ path: PathLike, /, *, modifications: MutableSet[Path] | None = None
43
+ ) -> None:
44
+ with yield_python_file(path, modifications=modifications) as context:
45
+ if len(context.input.body) >= 1:
46
+ return
47
+ body = [
48
+ *context.input.body,
49
+ parse_statement("from __future__ import annotations"),
50
+ ]
51
+ context.output = context.input.with_changes(body=body)
52
+
53
+
54
+ __all__ = ["touch_empty_py"]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from utilities.logging import basic_config
6
+ from utilities.os import is_pytest
7
+
8
+ from actions.logging import LOGGER
9
+ from actions.pre_commit.click import path_argument
10
+ from actions.pre_commit.touch_py_typed.lib import touch_py_typed
11
+
12
+ if TYPE_CHECKING:
13
+ from pathlib import Path
14
+
15
+
16
+ @path_argument
17
+ def touch_py_typed_sub_cmd(*, paths: tuple[Path, ...]) -> None:
18
+ if is_pytest():
19
+ return
20
+ basic_config(obj=LOGGER)
21
+ touch_py_typed(*paths)
22
+
23
+
24
+ __all__ = ["touch_py_typed_sub_cmd"]
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ TOUCH_PY_TYPED_DOCSTRING = "Touch 'py.typed'"
4
+ TOUCH_PY_TYPED_SUB_CMD = "touch-py-typed"
5
+
6
+
7
+ __all__ = ["TOUCH_PY_TYPED_DOCSTRING", "TOUCH_PY_TYPED_SUB_CMD"]
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING
6
+
7
+ from utilities.iterables import one
8
+ from utilities.text import repr_str, strip_and_dedent
9
+
10
+ from actions import __version__
11
+ from actions.logging import LOGGER
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import MutableSet
15
+
16
+ from utilities.types import PathLike
17
+
18
+
19
+ def touch_py_typed(*paths: PathLike) -> None:
20
+ LOGGER.info(
21
+ strip_and_dedent("""
22
+ Running '%s' (version %s) with settings:
23
+ - paths = %s
24
+ """),
25
+ touch_py_typed.__name__,
26
+ __version__,
27
+ paths,
28
+ )
29
+ modifications: set[Path] = set()
30
+ for path in paths:
31
+ _format_path(path, modifications=modifications)
32
+ if len(modifications) >= 1:
33
+ LOGGER.info(
34
+ "Exiting due to modifications: %s",
35
+ ", ".join(map(repr_str, sorted(modifications))),
36
+ )
37
+ sys.exit(1)
38
+
39
+
40
+ def _format_path(
41
+ path: PathLike, /, *, modifications: MutableSet[Path] | None = None
42
+ ) -> None:
43
+ path = Path(path)
44
+ if not path.is_file():
45
+ msg = f"Expected a file; {str(path)!r} is not"
46
+ raise FileNotFoundError(msg)
47
+ if path.name != "pyproject.toml":
48
+ msg = f"Expected 'pyproject.toml'; got {str(path)!r}"
49
+ raise TypeError(msg)
50
+ src = path.parent / "src"
51
+ if not src.exists():
52
+ return
53
+ if not src.is_dir():
54
+ msg = f"Expected a directory; {str(src)!r} is not"
55
+ raise NotADirectoryError(msg)
56
+ non_tests = one(p for p in src.iterdir() if p.name != "tests")
57
+ py_typed = non_tests / "py.typed"
58
+ if not py_typed.exists():
59
+ py_typed.touch()
60
+ if modifications is not None:
61
+ modifications.add(py_typed)
62
+
63
+
64
+ __all__ = ["touch_py_typed"]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,117 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass, field, replace
5
+ from functools import total_ordering
6
+ from typing import Any, Self, override
7
+
8
+ from pydantic import BaseModel
9
+ from utilities.version import (
10
+ ParseVersionError,
11
+ _VersionEmptySuffixError,
12
+ _VersionNegativeMajorVersionError,
13
+ _VersionNegativeMinorVersionError,
14
+ _VersionZeroError,
15
+ )
16
+ from utilities.version import Version as Version3
17
+ from utilities.version import parse_version as parse_version3
18
+
19
+ type TwoSidedVersions = tuple[Version2or3 | None, Version1or2 | None]
20
+ type Version1or2 = int | Version2
21
+ type Version2or3 = Version2 | Version3
22
+ type VersionSet = dict[str, Version2or3]
23
+
24
+
25
+ class PipListOutdatedOutput(BaseModel):
26
+ name: str
27
+ version: str
28
+ latest_version: str
29
+ latest_filetype: str
30
+
31
+
32
+ ##
33
+
34
+
35
+ @dataclass(repr=False, frozen=True, slots=True)
36
+ @total_ordering
37
+ class Version2:
38
+ """A version identifier."""
39
+
40
+ major: int = 0
41
+ minor: int = 0
42
+ suffix: str | None = field(default=None, kw_only=True)
43
+
44
+ def __post_init__(self) -> None:
45
+ if (self.major == 0) and (self.minor == 0):
46
+ raise _VersionZeroError(major=self.major, minor=self.minor, patch=0)
47
+ if self.major < 0:
48
+ raise _VersionNegativeMajorVersionError(major=self.major)
49
+ if self.minor < 0:
50
+ raise _VersionNegativeMinorVersionError(minor=self.minor)
51
+ if (self.suffix is not None) and (len(self.suffix) == 0):
52
+ raise _VersionEmptySuffixError(suffix=self.suffix)
53
+
54
+ def __le__(self, other: Any, /) -> bool:
55
+ if not isinstance(other, type(self)):
56
+ return NotImplemented
57
+ self_as_tuple = (self.major, self.minor)
58
+ other_as_tuple = (other.major, other.minor)
59
+ return self_as_tuple <= other_as_tuple
60
+
61
+ @override
62
+ def __repr__(self) -> str:
63
+ version = f"{self.major}.{self.minor}"
64
+ if self.suffix is not None:
65
+ version = f"{version}-{self.suffix}"
66
+ return version
67
+
68
+ def bump_major(self) -> Self:
69
+ return type(self)(self.major + 1, 0)
70
+
71
+ def bump_minor(self) -> Self:
72
+ return type(self)(self.major, self.minor + 1)
73
+
74
+ def with_suffix(self, *, suffix: str | None = None) -> Self:
75
+ return replace(self, suffix=suffix)
76
+
77
+
78
+ def parse_version1_or_2(version: str, /) -> Version1or2:
79
+ try:
80
+ return parse_version2(version)
81
+ except ParseVersionError:
82
+ return int(version)
83
+
84
+
85
+ def parse_version2_or_3(version: str, /) -> Version2or3:
86
+ try:
87
+ return parse_version3(version)
88
+ except ParseVersionError:
89
+ return parse_version2(version)
90
+
91
+
92
+ def parse_version2(version: str, /) -> Version2:
93
+ try:
94
+ ((major, minor, suffix),) = _PARSE_VERSION2_PATTERN.findall(version)
95
+ except ValueError:
96
+ raise ParseVersionError(version=version) from None
97
+ return Version2(int(major), int(minor), suffix=None if suffix == "" else suffix)
98
+
99
+
100
+ _PARSE_VERSION2_PATTERN = re.compile(r"^(\d+)\.(\d+)(?:-(\w+))?")
101
+
102
+
103
+ _ = PipListOutdatedOutput.model_rebuild()
104
+
105
+
106
+ __all__ = [
107
+ "PipListOutdatedOutput",
108
+ "TwoSidedVersions",
109
+ "Version1or2",
110
+ "Version2",
111
+ "Version2or3",
112
+ "Version3",
113
+ "VersionSet",
114
+ "parse_version1_or_2",
115
+ "parse_version2",
116
+ "parse_version2_or_3",
117
+ ]
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from utilities.logging import basic_config
6
+ from utilities.os import is_pytest
7
+
8
+ from actions.logging import LOGGER
9
+ from actions.pre_commit.click import path_argument
10
+ from actions.pre_commit.update_requirements.lib import update_requirements
11
+
12
+ if TYPE_CHECKING:
13
+ from pathlib import Path
14
+
15
+
16
+ @path_argument
17
+ def update_requirements_sub_cmd(*, paths: tuple[Path, ...]) -> None:
18
+ if is_pytest():
19
+ return
20
+ basic_config(obj=LOGGER)
21
+ update_requirements(*paths)
22
+
23
+
24
+ __all__ = ["update_requirements_sub_cmd"]
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ UPDATE_REQUIREMENTS_DOCSTRING = "Update a set of requirements"
4
+ UPDATE_REQUIREMENTS_SUB_CMD = "update-requirements"
5
+
6
+
7
+ __all__ = ["UPDATE_REQUIREMENTS_DOCSTRING", "UPDATE_REQUIREMENTS_SUB_CMD"]