dycw-actions 0.6.4__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.
- actions/__init__.py +1 -1
- actions/action_dicts/lib.py +8 -8
- actions/clean_dir/cli.py +0 -12
- actions/clean_dir/constants.py +7 -0
- actions/cli.py +81 -30
- actions/pre_commit/click.py +15 -0
- actions/{conformalize_repo → pre_commit/conformalize_repo}/cli.py +2 -14
- actions/{conformalize_repo → pre_commit/conformalize_repo}/constants.py +8 -2
- actions/{conformalize_repo → pre_commit/conformalize_repo}/lib.py +79 -308
- actions/{conformalize_repo → pre_commit/conformalize_repo}/settings.py +1 -1
- actions/pre_commit/constants.py +8 -0
- actions/pre_commit/format_requirements/cli.py +24 -0
- actions/pre_commit/format_requirements/constants.py +7 -0
- actions/pre_commit/format_requirements/lib.py +52 -0
- actions/pre_commit/replace_sequence_strs/__init__.py +1 -0
- actions/pre_commit/replace_sequence_strs/cli.py +24 -0
- actions/pre_commit/replace_sequence_strs/constants.py +7 -0
- actions/{replace_sequence_strs → pre_commit/replace_sequence_strs}/lib.py +16 -22
- actions/pre_commit/touch_empty_py/__init__.py +1 -0
- actions/pre_commit/touch_empty_py/cli.py +24 -0
- actions/pre_commit/touch_empty_py/constants.py +7 -0
- actions/pre_commit/touch_empty_py/lib.py +54 -0
- actions/pre_commit/touch_py_typed/__init__.py +1 -0
- actions/pre_commit/touch_py_typed/cli.py +24 -0
- actions/pre_commit/touch_py_typed/constants.py +7 -0
- actions/pre_commit/touch_py_typed/lib.py +64 -0
- actions/pre_commit/update_requirements/__init__.py +1 -0
- actions/pre_commit/update_requirements/classes.py +117 -0
- actions/pre_commit/update_requirements/cli.py +24 -0
- actions/pre_commit/update_requirements/constants.py +7 -0
- actions/pre_commit/update_requirements/lib.py +128 -0
- actions/pre_commit/utilities.py +386 -0
- actions/publish_package/cli.py +7 -19
- actions/publish_package/constants.py +7 -0
- actions/publish_package/lib.py +3 -3
- actions/py.typed +0 -0
- actions/random_sleep/cli.py +6 -15
- actions/random_sleep/constants.py +7 -0
- actions/run_hooks/cli.py +3 -15
- actions/run_hooks/constants.py +7 -0
- actions/run_hooks/lib.py +2 -2
- actions/setup_cronjob/cli.py +0 -12
- actions/setup_cronjob/constants.py +5 -1
- actions/tag_commit/cli.py +7 -19
- actions/tag_commit/constants.py +7 -0
- actions/tag_commit/lib.py +8 -8
- actions/types.py +4 -1
- actions/utilities.py +68 -14
- {dycw_actions-0.6.4.dist-info → dycw_actions-0.7.1.dist-info}/METADATA +3 -2
- dycw_actions-0.7.1.dist-info/RECORD +77 -0
- actions/format_requirements/cli.py +0 -37
- actions/format_requirements/lib.py +0 -121
- actions/publish_package/doc.py +0 -6
- actions/random_sleep/doc.py +0 -6
- actions/replace_sequence_strs/cli.py +0 -37
- actions/run_hooks/doc.py +0 -6
- actions/tag_commit/doc.py +0 -6
- dycw_actions-0.6.4.dist-info/RECORD +0 -56
- /actions/{conformalize_repo → pre_commit}/__init__.py +0 -0
- /actions/{format_requirements → pre_commit/conformalize_repo}/__init__.py +0 -0
- /actions/{conformalize_repo → pre_commit/conformalize_repo}/configs/gitignore +0 -0
- /actions/{replace_sequence_strs → pre_commit/format_requirements}/__init__.py +0 -0
- {dycw_actions-0.6.4.dist-info → dycw_actions-0.7.1.dist-info}/WHEEL +0 -0
- {dycw_actions-0.6.4.dist-info → dycw_actions-0.7.1.dist-info}/entry_points.txt +0 -0
|
@@ -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"]
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
|
-
from pathlib import Path
|
|
5
4
|
from typing import TYPE_CHECKING, override
|
|
6
5
|
|
|
7
|
-
from libcst import CSTTransformer,
|
|
6
|
+
from libcst import CSTTransformer, Name, Subscript
|
|
8
7
|
from libcst.matchers import Index as MIndex
|
|
9
8
|
from libcst.matchers import Name as MName
|
|
10
9
|
from libcst.matchers import Subscript as MSubscript
|
|
@@ -15,12 +14,13 @@ from utilities.text import repr_str, strip_and_dedent
|
|
|
15
14
|
|
|
16
15
|
from actions import __version__
|
|
17
16
|
from actions.logging import LOGGER
|
|
17
|
+
from actions.pre_commit.utilities import yield_python_file
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
|
-
from
|
|
21
|
-
|
|
20
|
+
from collections.abc import MutableSet
|
|
21
|
+
from pathlib import Path
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
from utilities.types import PathLike
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def replace_sequence_strs(*paths: PathLike) -> None:
|
|
@@ -33,30 +33,24 @@ def replace_sequence_strs(*paths: PathLike) -> None:
|
|
|
33
33
|
__version__,
|
|
34
34
|
paths,
|
|
35
35
|
)
|
|
36
|
+
modifications: set[Path] = set()
|
|
36
37
|
for path in paths:
|
|
37
|
-
_format_path(path)
|
|
38
|
-
if len(
|
|
38
|
+
_format_path(path, modifications=modifications)
|
|
39
|
+
if len(modifications) >= 1:
|
|
39
40
|
LOGGER.info(
|
|
40
41
|
"Exiting due to modifications: %s",
|
|
41
|
-
", ".join(map(repr_str, sorted(
|
|
42
|
+
", ".join(map(repr_str, sorted(modifications))),
|
|
42
43
|
)
|
|
43
44
|
sys.exit(1)
|
|
44
45
|
|
|
45
46
|
|
|
46
|
-
def _format_path(
|
|
47
|
-
path =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _get_formatted(path: PathLike, /) -> Module:
|
|
56
|
-
path = Path(path)
|
|
57
|
-
existing = path.read_text()
|
|
58
|
-
wrapper = MetadataWrapper(parse_module(existing))
|
|
59
|
-
return wrapper.module.visit(SequenceToListTransformer())
|
|
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
|
+
)
|
|
60
54
|
|
|
61
55
|
|
|
62
56
|
class SequenceToListTransformer(CSTTransformer):
|
|
@@ -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,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,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,128 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from functools import partial
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from pydantic import TypeAdapter
|
|
8
|
+
from utilities.text import repr_str, strip_and_dedent
|
|
9
|
+
|
|
10
|
+
from actions import __version__
|
|
11
|
+
from actions.logging import LOGGER
|
|
12
|
+
from actions.pre_commit.update_requirements.classes import (
|
|
13
|
+
PipListOutdatedOutput,
|
|
14
|
+
Version1or2,
|
|
15
|
+
Version2,
|
|
16
|
+
Version3,
|
|
17
|
+
parse_version1_or_2,
|
|
18
|
+
parse_version2_or_3,
|
|
19
|
+
)
|
|
20
|
+
from actions.pre_commit.utilities import get_pyproject_dependencies, yield_toml_doc
|
|
21
|
+
from actions.utilities import logged_run
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from collections.abc import MutableSet
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
from utilities.packaging import Requirement
|
|
28
|
+
from utilities.types import PathLike
|
|
29
|
+
|
|
30
|
+
from actions.pre_commit.update_requirements.classes import Version2or3, VersionSet
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def update_requirements(*paths: PathLike) -> None:
|
|
34
|
+
LOGGER.info(
|
|
35
|
+
strip_and_dedent("""
|
|
36
|
+
Running '%s' (version %s) with settings:
|
|
37
|
+
- paths = %s
|
|
38
|
+
"""),
|
|
39
|
+
update_requirements.__name__,
|
|
40
|
+
__version__,
|
|
41
|
+
paths,
|
|
42
|
+
)
|
|
43
|
+
modifications: set[Path] = set()
|
|
44
|
+
for path in paths:
|
|
45
|
+
_format_path(path, modifications=modifications)
|
|
46
|
+
if len(modifications) >= 1:
|
|
47
|
+
LOGGER.info(
|
|
48
|
+
"Exiting due to modifications: %s",
|
|
49
|
+
", ".join(map(repr_str, sorted(modifications))),
|
|
50
|
+
)
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _format_path(
|
|
55
|
+
path: PathLike,
|
|
56
|
+
/,
|
|
57
|
+
*,
|
|
58
|
+
versions: VersionSet | None = None,
|
|
59
|
+
modifications: MutableSet[Path] | None = None,
|
|
60
|
+
) -> None:
|
|
61
|
+
versions_use = _get_versions() if versions is None else versions
|
|
62
|
+
with yield_toml_doc(path, modifications=modifications) as doc:
|
|
63
|
+
get_pyproject_dependencies(doc).apply(
|
|
64
|
+
partial(_format_req, versions=versions_use)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _get_versions() -> VersionSet:
|
|
69
|
+
json = logged_run(
|
|
70
|
+
"uv", "pip", "list", "--format", "json", "--outdated", "--strict", return_=True
|
|
71
|
+
)
|
|
72
|
+
packages = TypeAdapter(list[PipListOutdatedOutput]).validate_json(json)
|
|
73
|
+
return {p.name: parse_version2_or_3(p.latest_version) for p in packages}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _format_req(requirement: Requirement, /, *, versions: VersionSet) -> Requirement:
|
|
77
|
+
try:
|
|
78
|
+
lower = parse_version2_or_3(requirement[">="])
|
|
79
|
+
except KeyError:
|
|
80
|
+
lower = None
|
|
81
|
+
try:
|
|
82
|
+
upper = parse_version1_or_2(requirement["<"])
|
|
83
|
+
except KeyError:
|
|
84
|
+
upper = None
|
|
85
|
+
latest = versions.get(requirement.name)
|
|
86
|
+
new_lower: Version2or3 | None = None
|
|
87
|
+
new_upper: Version1or2 | None = None
|
|
88
|
+
match lower, upper, latest:
|
|
89
|
+
case None, None, None:
|
|
90
|
+
...
|
|
91
|
+
case None, None, Version2() | Version3():
|
|
92
|
+
new_lower = latest
|
|
93
|
+
new_upper = latest.bump_major().major
|
|
94
|
+
case Version2() | Version3(), None, None:
|
|
95
|
+
new_lower = lower
|
|
96
|
+
case (Version2(), None, Version2()) | (Version3(), None, Version3()):
|
|
97
|
+
new_lower = max(lower, latest)
|
|
98
|
+
case None, int() | Version2(), None:
|
|
99
|
+
new_upper = upper
|
|
100
|
+
case None, int(), Version2():
|
|
101
|
+
new_upper = max(upper, latest.bump_major().major)
|
|
102
|
+
case None, Version2(), Version3():
|
|
103
|
+
bumped = latest.bump_minor()
|
|
104
|
+
new_upper = max(upper, Version2(bumped.major, bumped.minor))
|
|
105
|
+
case (
|
|
106
|
+
(Version2(), int(), None)
|
|
107
|
+
| (Version3(), int(), None)
|
|
108
|
+
| (Version3(), Version2(), None)
|
|
109
|
+
):
|
|
110
|
+
new_lower = lower
|
|
111
|
+
new_upper = lower.bump_major().major
|
|
112
|
+
case (
|
|
113
|
+
(Version2(), int(), Version2())
|
|
114
|
+
| (Version3(), int(), Version3())
|
|
115
|
+
| (Version3(), Version2(), Version3())
|
|
116
|
+
):
|
|
117
|
+
new_lower = max(lower, latest)
|
|
118
|
+
new_upper = new_lower.bump_major().major
|
|
119
|
+
case never:
|
|
120
|
+
raise NotImplementedError(never)
|
|
121
|
+
if new_lower is not None:
|
|
122
|
+
requirement = requirement.replace(">=", str(new_lower))
|
|
123
|
+
if new_upper is not None:
|
|
124
|
+
requirement = requirement.replace("<", str(new_upper))
|
|
125
|
+
return requirement
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
__all__ = ["update_requirements"]
|