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.
Files changed (64) hide show
  1. actions/__init__.py +1 -1
  2. actions/action_dicts/lib.py +8 -8
  3. actions/clean_dir/cli.py +0 -12
  4. actions/clean_dir/constants.py +7 -0
  5. actions/cli.py +81 -30
  6. actions/pre_commit/click.py +15 -0
  7. actions/{conformalize_repo → pre_commit/conformalize_repo}/cli.py +2 -14
  8. actions/{conformalize_repo → pre_commit/conformalize_repo}/constants.py +8 -2
  9. actions/{conformalize_repo → pre_commit/conformalize_repo}/lib.py +79 -308
  10. actions/{conformalize_repo → pre_commit/conformalize_repo}/settings.py +1 -1
  11. actions/pre_commit/constants.py +8 -0
  12. actions/pre_commit/format_requirements/cli.py +24 -0
  13. actions/pre_commit/format_requirements/constants.py +7 -0
  14. actions/pre_commit/format_requirements/lib.py +52 -0
  15. actions/pre_commit/replace_sequence_strs/__init__.py +1 -0
  16. actions/pre_commit/replace_sequence_strs/cli.py +24 -0
  17. actions/pre_commit/replace_sequence_strs/constants.py +7 -0
  18. actions/{replace_sequence_strs → pre_commit/replace_sequence_strs}/lib.py +16 -22
  19. actions/pre_commit/touch_empty_py/__init__.py +1 -0
  20. actions/pre_commit/touch_empty_py/cli.py +24 -0
  21. actions/pre_commit/touch_empty_py/constants.py +7 -0
  22. actions/pre_commit/touch_empty_py/lib.py +54 -0
  23. actions/pre_commit/touch_py_typed/__init__.py +1 -0
  24. actions/pre_commit/touch_py_typed/cli.py +24 -0
  25. actions/pre_commit/touch_py_typed/constants.py +7 -0
  26. actions/pre_commit/touch_py_typed/lib.py +64 -0
  27. actions/pre_commit/update_requirements/__init__.py +1 -0
  28. actions/pre_commit/update_requirements/classes.py +117 -0
  29. actions/pre_commit/update_requirements/cli.py +24 -0
  30. actions/pre_commit/update_requirements/constants.py +7 -0
  31. actions/pre_commit/update_requirements/lib.py +128 -0
  32. actions/pre_commit/utilities.py +386 -0
  33. actions/publish_package/cli.py +7 -19
  34. actions/publish_package/constants.py +7 -0
  35. actions/publish_package/lib.py +3 -3
  36. actions/py.typed +0 -0
  37. actions/random_sleep/cli.py +6 -15
  38. actions/random_sleep/constants.py +7 -0
  39. actions/run_hooks/cli.py +3 -15
  40. actions/run_hooks/constants.py +7 -0
  41. actions/run_hooks/lib.py +2 -2
  42. actions/setup_cronjob/cli.py +0 -12
  43. actions/setup_cronjob/constants.py +5 -1
  44. actions/tag_commit/cli.py +7 -19
  45. actions/tag_commit/constants.py +7 -0
  46. actions/tag_commit/lib.py +8 -8
  47. actions/types.py +4 -1
  48. actions/utilities.py +68 -14
  49. {dycw_actions-0.6.4.dist-info → dycw_actions-0.7.1.dist-info}/METADATA +3 -2
  50. dycw_actions-0.7.1.dist-info/RECORD +77 -0
  51. actions/format_requirements/cli.py +0 -37
  52. actions/format_requirements/lib.py +0 -121
  53. actions/publish_package/doc.py +0 -6
  54. actions/random_sleep/doc.py +0 -6
  55. actions/replace_sequence_strs/cli.py +0 -37
  56. actions/run_hooks/doc.py +0 -6
  57. actions/tag_commit/doc.py +0 -6
  58. dycw_actions-0.6.4.dist-info/RECORD +0 -56
  59. /actions/{conformalize_repo → pre_commit}/__init__.py +0 -0
  60. /actions/{format_requirements → pre_commit/conformalize_repo}/__init__.py +0 -0
  61. /actions/{conformalize_repo → pre_commit/conformalize_repo}/configs/gitignore +0 -0
  62. /actions/{replace_sequence_strs → pre_commit/format_requirements}/__init__.py +0 -0
  63. {dycw_actions-0.6.4.dist-info → dycw_actions-0.7.1.dist-info}/WHEEL +0 -0
  64. {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"]
@@ -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"]
@@ -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, Module, Name, Subscript, parse_module
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 utilities.types import PathLike
21
-
20
+ from collections.abc import MutableSet
21
+ from pathlib import Path
22
22
 
23
- _MODIFICATIONS: set[Path] = set()
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(_MODIFICATIONS) >= 1:
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(_MODIFICATIONS))),
42
+ ", ".join(map(repr_str, sorted(modifications))),
42
43
  )
43
44
  sys.exit(1)
44
45
 
45
46
 
46
- def _format_path(path: PathLike, /) -> None:
47
- path = Path(path)
48
- current = parse_module(path.read_text())
49
- expected = _get_formatted(path)
50
- if current.code != expected.code:
51
- _ = path.write_text(expected.code.rstrip("\n") + "\n")
52
- _MODIFICATIONS.add(path)
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,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"]
@@ -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"]