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.
- actions/__init__.py +1 -1
- actions/action_dicts/constants.py +8 -0
- actions/action_dicts/lib.py +186 -0
- actions/clean_dir/cli.py +21 -0
- actions/clean_dir/constants.py +7 -0
- actions/clean_dir/lib.py +59 -0
- actions/clean_dir/settings.py +18 -0
- actions/cli.py +95 -6
- actions/constants.py +10 -0
- actions/pre_commit/click.py +15 -0
- actions/pre_commit/conformalize_repo/__init__.py +1 -0
- actions/pre_commit/conformalize_repo/cli.py +64 -0
- actions/pre_commit/conformalize_repo/configs/gitignore +244 -0
- actions/pre_commit/conformalize_repo/constants.py +78 -0
- actions/pre_commit/conformalize_repo/lib.py +1293 -0
- actions/pre_commit/conformalize_repo/settings.py +119 -0
- actions/pre_commit/constants.py +8 -0
- actions/pre_commit/format_requirements/__init__.py +1 -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/pre_commit/replace_sequence_strs/lib.py +73 -0
- 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/__init__.py +1 -0
- actions/publish_package/cli.py +27 -0
- actions/publish_package/constants.py +7 -0
- actions/{publish → publish_package}/lib.py +18 -17
- actions/{publish → publish_package}/settings.py +7 -7
- actions/py.typed +0 -0
- actions/random_sleep/__init__.py +1 -0
- actions/random_sleep/cli.py +26 -0
- actions/random_sleep/constants.py +7 -0
- actions/{sleep → random_sleep}/lib.py +14 -13
- actions/{sleep → random_sleep}/settings.py +3 -3
- actions/run_hooks/__init__.py +1 -0
- actions/run_hooks/cli.py +21 -0
- actions/run_hooks/constants.py +7 -0
- actions/run_hooks/lib.py +97 -0
- actions/run_hooks/settings.py +24 -0
- actions/setup_cronjob/__init__.py +1 -0
- actions/setup_cronjob/cli.py +31 -0
- actions/setup_cronjob/configs/cron.tmpl +3 -0
- actions/setup_cronjob/configs/logrotate.tmpl +10 -0
- actions/setup_cronjob/constants.py +12 -0
- actions/setup_cronjob/lib.py +120 -0
- actions/setup_cronjob/settings.py +27 -0
- actions/tag_commit/__init__.py +1 -0
- actions/tag_commit/cli.py +27 -0
- actions/tag_commit/constants.py +7 -0
- actions/tag_commit/lib.py +63 -0
- actions/{tag → tag_commit}/settings.py +3 -3
- actions/types.py +14 -1
- actions/utilities.py +131 -17
- dycw_actions-0.7.1.dist-info/METADATA +22 -0
- dycw_actions-0.7.1.dist-info/RECORD +77 -0
- {dycw_actions-0.3.2.dist-info → dycw_actions-0.7.1.dist-info}/WHEEL +1 -1
- actions/publish/cli.py +0 -43
- actions/settings.py +0 -18
- actions/sleep/cli.py +0 -39
- actions/tag/cli.py +0 -43
- actions/tag/lib.py +0 -62
- dycw_actions-0.3.2.dist-info/METADATA +0 -14
- dycw_actions-0.3.2.dist-info/RECORD +0 -22
- /actions/{publish → action_dicts}/__init__.py +0 -0
- /actions/{sleep → clean_dir}/__init__.py +0 -0
- /actions/{tag → pre_commit}/__init__.py +0 -0
- {dycw_actions-0.3.2.dist-info → dycw_actions-0.7.1.dist-info}/entry_points.txt +0 -0
|
@@ -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"]
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Iterator, MutableSet
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Any, assert_never
|
|
9
|
+
|
|
10
|
+
import tomlkit
|
|
11
|
+
from libcst import Module, parse_module
|
|
12
|
+
from rich.pretty import pretty_repr
|
|
13
|
+
from tomlkit import TOMLDocument, aot, array, document, string, table
|
|
14
|
+
from tomlkit.items import AoT, Array, Table
|
|
15
|
+
from utilities.functions import ensure_class, ensure_str
|
|
16
|
+
from utilities.iterables import OneEmptyError, OneNonUniqueError, one
|
|
17
|
+
from utilities.packaging import Requirement
|
|
18
|
+
from utilities.types import PathLike
|
|
19
|
+
|
|
20
|
+
from actions.constants import YAML_INSTANCE
|
|
21
|
+
from actions.logging import LOGGER
|
|
22
|
+
from actions.types import StrDict
|
|
23
|
+
from actions.utilities import are_equal_modulo_new_line, write_text, yaml_dump
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from collections.abc import Callable, Iterable, Iterator, MutableSet
|
|
27
|
+
|
|
28
|
+
from utilities.types import PathLike
|
|
29
|
+
|
|
30
|
+
from actions.types import FuncRequirement, HasAppend, HasSetDefault, StrDict
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def ensure_aot_contains(array: AoT, /, *tables: Table) -> None:
|
|
34
|
+
for table_ in tables:
|
|
35
|
+
if table_ not in array:
|
|
36
|
+
array.append(table_)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def ensure_contains(array: HasAppend, /, *objs: Any) -> None:
|
|
40
|
+
if isinstance(array, AoT):
|
|
41
|
+
msg = f"Use {ensure_aot_contains.__name__!r} instead of {ensure_contains.__name__!r}"
|
|
42
|
+
raise TypeError(msg)
|
|
43
|
+
for obj in objs:
|
|
44
|
+
if obj not in array:
|
|
45
|
+
array.append(obj)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def ensure_contains_partial_dict(
|
|
49
|
+
container: HasAppend, partial: StrDict, /, *, extra: StrDict | None = None
|
|
50
|
+
) -> StrDict:
|
|
51
|
+
try:
|
|
52
|
+
return get_partial_dict(container, partial, skip_log=True)
|
|
53
|
+
except OneEmptyError:
|
|
54
|
+
dict_ = partial | ({} if extra is None else extra)
|
|
55
|
+
container.append(dict_)
|
|
56
|
+
return dict_
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def ensure_contains_partial_str(container: HasAppend, text: str, /) -> str:
|
|
60
|
+
try:
|
|
61
|
+
return get_partial_str(container, text, skip_log=True)
|
|
62
|
+
except OneEmptyError:
|
|
63
|
+
container.append(text)
|
|
64
|
+
return text
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def ensure_not_contains(array: Array, /, *objs: Any) -> None:
|
|
68
|
+
for obj in objs:
|
|
69
|
+
try:
|
|
70
|
+
index = next(i for i, o in enumerate(array) if o == obj)
|
|
71
|
+
except StopIteration:
|
|
72
|
+
pass
|
|
73
|
+
else:
|
|
74
|
+
del array[index]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_aot(container: HasSetDefault, key: str, /) -> AoT:
|
|
81
|
+
return ensure_class(container.setdefault(key, aot()), AoT)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_array(container: HasSetDefault, key: str, /) -> Array:
|
|
85
|
+
return ensure_class(container.setdefault(key, array()), Array)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_dict(container: HasSetDefault, key: str, /) -> StrDict:
|
|
89
|
+
return ensure_class(container.setdefault(key, {}), dict)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_list(container: HasSetDefault, key: str, /) -> list[Any]:
|
|
93
|
+
return ensure_class(container.setdefault(key, []), list)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_table(container: HasSetDefault, key: str, /) -> Table:
|
|
97
|
+
return ensure_class(container.setdefault(key, table()), Table)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_partial_dict(
|
|
104
|
+
iterable: Iterable[Any], dict_: StrDict, /, *, skip_log: bool = False
|
|
105
|
+
) -> StrDict:
|
|
106
|
+
try:
|
|
107
|
+
return one(i for i in iterable if is_partial_dict(dict_, i))
|
|
108
|
+
except OneEmptyError:
|
|
109
|
+
if not skip_log:
|
|
110
|
+
LOGGER.exception(
|
|
111
|
+
"Expected %s to contain %s (as a partial)",
|
|
112
|
+
pretty_repr(iterable),
|
|
113
|
+
pretty_repr(dict_),
|
|
114
|
+
)
|
|
115
|
+
raise
|
|
116
|
+
except OneNonUniqueError as error:
|
|
117
|
+
LOGGER.exception(
|
|
118
|
+
"Expected %s to contain %s uniquely (as a partial); got %s, %s and perhaps more",
|
|
119
|
+
pretty_repr(iterable),
|
|
120
|
+
pretty_repr(dict_),
|
|
121
|
+
pretty_repr(error.first),
|
|
122
|
+
pretty_repr(error.second),
|
|
123
|
+
)
|
|
124
|
+
raise
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def is_partial_dict(obj: Any, dict_: StrDict, /) -> bool:
|
|
128
|
+
if not isinstance(obj, dict):
|
|
129
|
+
return False
|
|
130
|
+
results: dict[str, bool] = {}
|
|
131
|
+
for key, obj_value in obj.items():
|
|
132
|
+
try:
|
|
133
|
+
dict_value = dict_[key]
|
|
134
|
+
except KeyError:
|
|
135
|
+
results[key] = False
|
|
136
|
+
else:
|
|
137
|
+
if isinstance(obj_value, dict) and isinstance(dict_value, dict):
|
|
138
|
+
results[key] = is_partial_dict(obj_value, dict_value)
|
|
139
|
+
else:
|
|
140
|
+
results[key] = obj_value == dict_value
|
|
141
|
+
return all(results.values())
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
##
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_partial_str(
|
|
148
|
+
iterable: Iterable[Any], text: str, /, *, skip_log: bool = False
|
|
149
|
+
) -> str:
|
|
150
|
+
try:
|
|
151
|
+
return one(i for i in iterable if is_partial_str(i, text))
|
|
152
|
+
except OneEmptyError:
|
|
153
|
+
if not skip_log:
|
|
154
|
+
LOGGER.exception(
|
|
155
|
+
"Expected %s to contain %s (as a partial)",
|
|
156
|
+
pretty_repr(iterable),
|
|
157
|
+
pretty_repr(text),
|
|
158
|
+
)
|
|
159
|
+
raise
|
|
160
|
+
except OneNonUniqueError as error:
|
|
161
|
+
LOGGER.exception(
|
|
162
|
+
"Expected %s to contain %s uniquely (as a partial); got %s, %s and perhaps more",
|
|
163
|
+
pretty_repr(iterable),
|
|
164
|
+
pretty_repr(text),
|
|
165
|
+
pretty_repr(error.first),
|
|
166
|
+
pretty_repr(error.second),
|
|
167
|
+
)
|
|
168
|
+
raise
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def is_partial_str(obj: Any, text: str, /) -> bool:
|
|
172
|
+
return isinstance(obj, str) and (text in obj)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
##
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_pyproject_dependencies(doc: TOMLDocument, /) -> PyProjectDependencies:
|
|
179
|
+
out = PyProjectDependencies()
|
|
180
|
+
if (project_key := "project") in doc:
|
|
181
|
+
project = get_table(doc, project_key)
|
|
182
|
+
if (dep_key := "dependencies") in project:
|
|
183
|
+
out.dependencies = get_array(project, dep_key)
|
|
184
|
+
if (opt_dep_key := "optional-dependencies") in project:
|
|
185
|
+
opt_dependencies = get_table(project, opt_dep_key)
|
|
186
|
+
out.opt_dependencies = {}
|
|
187
|
+
for key in opt_dependencies:
|
|
188
|
+
out.opt_dependencies[ensure_str(key)] = get_array(opt_dependencies, key)
|
|
189
|
+
if (dep_grps_key := "dependency-groups") in doc:
|
|
190
|
+
dep_grps = get_table(doc, dep_grps_key)
|
|
191
|
+
out.dep_groups = {}
|
|
192
|
+
for key in dep_grps:
|
|
193
|
+
out.dep_groups[ensure_str(key)] = get_array(dep_grps, key)
|
|
194
|
+
return out
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass(kw_only=True, slots=True)
|
|
198
|
+
class PyProjectDependencies:
|
|
199
|
+
dependencies: Array | None = None
|
|
200
|
+
opt_dependencies: dict[str, Array] | None = None
|
|
201
|
+
dep_groups: dict[str, Array] | None = None
|
|
202
|
+
|
|
203
|
+
def apply(self, func: FuncRequirement, /) -> None:
|
|
204
|
+
if (deps := self.dependencies) is not None:
|
|
205
|
+
self._apply_to_array(deps, func)
|
|
206
|
+
if (opt_depedencies := self.opt_dependencies) is not None:
|
|
207
|
+
for deps in opt_depedencies.values():
|
|
208
|
+
self._apply_to_array(deps, func)
|
|
209
|
+
if (dep_grps := self.dep_groups) is not None:
|
|
210
|
+
for deps in dep_grps.values():
|
|
211
|
+
self._apply_to_array(deps, func)
|
|
212
|
+
|
|
213
|
+
def _apply_to_array(self, array: Array, func: FuncRequirement, /) -> None:
|
|
214
|
+
strs = list(map(ensure_str, array))
|
|
215
|
+
reqs = list(map(Requirement, strs))
|
|
216
|
+
results = list(map(func, reqs))
|
|
217
|
+
new_strs = list(map(str, results))
|
|
218
|
+
strings = list(map(string, new_strs))
|
|
219
|
+
array.clear()
|
|
220
|
+
ensure_contains(array, *strings)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
##
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@contextmanager
|
|
227
|
+
def yield_json_dict(
|
|
228
|
+
path: PathLike, /, *, modifications: MutableSet[Path] | None = None
|
|
229
|
+
) -> Iterator[StrDict]:
|
|
230
|
+
with yield_mutable_write_context(
|
|
231
|
+
path, json.loads, dict, json.dumps, modifications=modifications
|
|
232
|
+
) as dict_:
|
|
233
|
+
yield dict_
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
##
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@contextmanager
|
|
240
|
+
def yield_python_file(
|
|
241
|
+
path: PathLike, /, *, modifications: MutableSet[Path] | None = None
|
|
242
|
+
) -> Iterator[WriteContext[Module]]:
|
|
243
|
+
with yield_immutable_write_context(
|
|
244
|
+
path,
|
|
245
|
+
parse_module,
|
|
246
|
+
lambda: Module(body=[]),
|
|
247
|
+
lambda module: module.code,
|
|
248
|
+
modifications=modifications,
|
|
249
|
+
) as context:
|
|
250
|
+
yield context
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
##
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@contextmanager
|
|
257
|
+
def yield_text_file(
|
|
258
|
+
path: PathLike, /, *, modifications: MutableSet[Path] | None = None
|
|
259
|
+
) -> Iterator[WriteContext[str]]:
|
|
260
|
+
with yield_immutable_write_context(
|
|
261
|
+
path, str, lambda: "", str, modifications=modifications
|
|
262
|
+
) as context:
|
|
263
|
+
yield context
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
##
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@contextmanager
|
|
270
|
+
def yield_toml_doc(
|
|
271
|
+
path: PathLike, /, *, modifications: MutableSet[Path] | None = None
|
|
272
|
+
) -> Iterator[TOMLDocument]:
|
|
273
|
+
with yield_mutable_write_context(
|
|
274
|
+
path, tomlkit.parse, document, tomlkit.dumps, modifications=modifications
|
|
275
|
+
) as doc:
|
|
276
|
+
yield doc
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
##
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@contextmanager
|
|
283
|
+
def yield_mutable_write_context[T](
|
|
284
|
+
path: PathLike,
|
|
285
|
+
loads: Callable[[str], T],
|
|
286
|
+
get_default: Callable[[], T],
|
|
287
|
+
dumps: Callable[[T], str],
|
|
288
|
+
/,
|
|
289
|
+
*,
|
|
290
|
+
modifications: MutableSet[Path] | None = None,
|
|
291
|
+
) -> Iterator[T]:
|
|
292
|
+
with yield_immutable_write_context(
|
|
293
|
+
path, loads, get_default, dumps, modifications=modifications
|
|
294
|
+
) as context:
|
|
295
|
+
yield context.output
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
##
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@dataclass(kw_only=True, slots=True)
|
|
302
|
+
class WriteContext[T]:
|
|
303
|
+
input: T
|
|
304
|
+
output: T
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@contextmanager
|
|
308
|
+
def yield_immutable_write_context[T](
|
|
309
|
+
path: PathLike,
|
|
310
|
+
loads: Callable[[str], T],
|
|
311
|
+
get_default: Callable[[], T],
|
|
312
|
+
dumps: Callable[[T], str],
|
|
313
|
+
/,
|
|
314
|
+
*,
|
|
315
|
+
modifications: MutableSet[Path] | None = None,
|
|
316
|
+
) -> Iterator[WriteContext[T]]:
|
|
317
|
+
try:
|
|
318
|
+
current = Path(path).read_text()
|
|
319
|
+
except FileNotFoundError:
|
|
320
|
+
current = None
|
|
321
|
+
input_ = get_default()
|
|
322
|
+
output = get_default()
|
|
323
|
+
else:
|
|
324
|
+
input_ = loads(current)
|
|
325
|
+
output = loads(current)
|
|
326
|
+
yield (context := WriteContext(input=input_, output=output))
|
|
327
|
+
if current is None:
|
|
328
|
+
write_text(path, dumps(context.output), modifications=modifications)
|
|
329
|
+
else:
|
|
330
|
+
match context.output, loads(current):
|
|
331
|
+
case Module() as output_module, Module() as current_module:
|
|
332
|
+
if not are_equal_modulo_new_line(
|
|
333
|
+
output_module.code, current_module.code
|
|
334
|
+
):
|
|
335
|
+
write_text(path, dumps(output_module), modifications=modifications)
|
|
336
|
+
case TOMLDocument() as output_doc, TOMLDocument() as current_doc:
|
|
337
|
+
if not (output_doc == current_doc): # noqa: SIM201
|
|
338
|
+
write_text(path, dumps(output_doc), modifications=modifications)
|
|
339
|
+
case str() as output_text, str() as current_text:
|
|
340
|
+
if not are_equal_modulo_new_line(output_text, current_text):
|
|
341
|
+
write_text(path, dumps(output_text), modifications=modifications)
|
|
342
|
+
case output_obj, current_obj:
|
|
343
|
+
if output_obj != current_obj:
|
|
344
|
+
write_text(path, dumps(output_obj), modifications=modifications)
|
|
345
|
+
case never:
|
|
346
|
+
assert_never(never)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
##
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@contextmanager
|
|
353
|
+
def yield_yaml_dict(
|
|
354
|
+
path: PathLike, /, *, modifications: MutableSet[Path] | None = None
|
|
355
|
+
) -> Iterator[StrDict]:
|
|
356
|
+
with yield_mutable_write_context(
|
|
357
|
+
path, YAML_INSTANCE.load, dict, yaml_dump, modifications=modifications
|
|
358
|
+
) as dict_:
|
|
359
|
+
yield dict_
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
__all__ = [
|
|
363
|
+
"PyProjectDependencies",
|
|
364
|
+
"ensure_aot_contains",
|
|
365
|
+
"ensure_contains",
|
|
366
|
+
"ensure_contains_partial_dict",
|
|
367
|
+
"ensure_contains_partial_str",
|
|
368
|
+
"ensure_not_contains",
|
|
369
|
+
"get_aot",
|
|
370
|
+
"get_array",
|
|
371
|
+
"get_dict",
|
|
372
|
+
"get_list",
|
|
373
|
+
"get_partial_dict",
|
|
374
|
+
"get_partial_str",
|
|
375
|
+
"get_pyproject_dependencies",
|
|
376
|
+
"get_table",
|
|
377
|
+
"is_partial_dict",
|
|
378
|
+
"is_partial_str",
|
|
379
|
+
"yield_immutable_write_context",
|
|
380
|
+
"yield_json_dict",
|
|
381
|
+
"yield_mutable_write_context",
|
|
382
|
+
"yield_python_file",
|
|
383
|
+
"yield_text_file",
|
|
384
|
+
"yield_toml_doc",
|
|
385
|
+
"yield_yaml_dict",
|
|
386
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typed_settings import click_options
|
|
4
|
+
from utilities.logging import basic_config
|
|
5
|
+
from utilities.os import is_pytest
|
|
6
|
+
|
|
7
|
+
from actions.logging import LOGGER
|
|
8
|
+
from actions.publish_package.lib import publish_package
|
|
9
|
+
from actions.publish_package.settings import Settings
|
|
10
|
+
from actions.utilities import LOADER
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click_options(Settings, [LOADER], show_envvars_in_help=True)
|
|
14
|
+
def publish_package_sub_cmd(settings: Settings, /) -> None:
|
|
15
|
+
if is_pytest():
|
|
16
|
+
return
|
|
17
|
+
basic_config(obj=LOGGER)
|
|
18
|
+
publish_package(
|
|
19
|
+
username=settings.username,
|
|
20
|
+
password=settings.password,
|
|
21
|
+
publish_url=settings.publish_url,
|
|
22
|
+
trusted_publishing=settings.trusted_publishing,
|
|
23
|
+
native_tls=settings.native_tls,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = ["publish_package_sub_cmd"]
|
|
@@ -3,11 +3,12 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from utilities.tempfile import TemporaryDirectory
|
|
6
|
+
from utilities.text import strip_and_dedent
|
|
6
7
|
|
|
7
8
|
from actions import __version__
|
|
8
9
|
from actions.logging import LOGGER
|
|
9
|
-
from actions.
|
|
10
|
-
from actions.utilities import
|
|
10
|
+
from actions.publish_package.settings import SETTINGS
|
|
11
|
+
from actions.utilities import logged_run
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
13
14
|
from typed_settings import Secret
|
|
@@ -15,21 +16,21 @@ if TYPE_CHECKING:
|
|
|
15
16
|
|
|
16
17
|
def publish_package(
|
|
17
18
|
*,
|
|
18
|
-
username: str | None =
|
|
19
|
-
password: Secret[str] | None =
|
|
20
|
-
publish_url: str | None =
|
|
21
|
-
trusted_publishing: bool =
|
|
22
|
-
native_tls: bool =
|
|
19
|
+
username: str | None = SETTINGS.username,
|
|
20
|
+
password: Secret[str] | None = SETTINGS.password,
|
|
21
|
+
publish_url: str | None = SETTINGS.publish_url,
|
|
22
|
+
trusted_publishing: bool = SETTINGS.trusted_publishing,
|
|
23
|
+
native_tls: bool = SETTINGS.native_tls,
|
|
23
24
|
) -> None:
|
|
24
25
|
LOGGER.info(
|
|
25
|
-
"""
|
|
26
|
-
Running %
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
strip_and_dedent("""
|
|
27
|
+
Running '%s' (version %s) with settings:
|
|
28
|
+
- username = %s
|
|
29
|
+
- password = %s
|
|
30
|
+
- publish_url = %s
|
|
31
|
+
- trusted_publishing = %s
|
|
32
|
+
- native_tls = %s
|
|
33
|
+
"""),
|
|
33
34
|
publish_package.__name__,
|
|
34
35
|
__version__,
|
|
35
36
|
username,
|
|
@@ -39,8 +40,8 @@ Running %r (version %s) with settings:
|
|
|
39
40
|
native_tls,
|
|
40
41
|
)
|
|
41
42
|
with TemporaryDirectory() as temp:
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
logged_run("uv", "build", "--out-dir", str(temp), "--wheel", "--clear")
|
|
44
|
+
logged_run(
|
|
44
45
|
"uv",
|
|
45
46
|
"publish",
|
|
46
47
|
*([] if username is None else ["--username", username]),
|
|
@@ -2,19 +2,19 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typed_settings import Secret, load_settings, option, secret, settings
|
|
4
4
|
|
|
5
|
-
from actions.utilities import LOADER,
|
|
5
|
+
from actions.utilities import LOADER, convert_secret_str, convert_str
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@settings
|
|
9
|
-
class
|
|
9
|
+
class Settings:
|
|
10
10
|
username: str | None = option(
|
|
11
|
-
default=None, converter=
|
|
11
|
+
default=None, converter=convert_str, help="The username of the upload"
|
|
12
12
|
)
|
|
13
13
|
password: Secret[str] | None = secret(
|
|
14
|
-
default=None, converter=
|
|
14
|
+
default=None, converter=convert_secret_str, help="The password for the upload"
|
|
15
15
|
)
|
|
16
16
|
publish_url: str | None = option(
|
|
17
|
-
default=None, converter=
|
|
17
|
+
default=None, converter=convert_str, help="The URL of the upload endpoint"
|
|
18
18
|
)
|
|
19
19
|
trusted_publishing: bool = option(
|
|
20
20
|
default=False, help="Configure trusted publishing"
|
|
@@ -25,7 +25,7 @@ class PublishSettings:
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
SETTINGS = load_settings(Settings, [LOADER])
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
__all__ = ["
|
|
31
|
+
__all__ = ["SETTINGS", "Settings"]
|
actions/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typed_settings import click_options
|
|
4
|
+
from utilities.logging import basic_config
|
|
5
|
+
from utilities.os import is_pytest
|
|
6
|
+
|
|
7
|
+
from actions.logging import LOGGER
|
|
8
|
+
from actions.random_sleep.lib import random_sleep
|
|
9
|
+
from actions.random_sleep.settings import Settings
|
|
10
|
+
from actions.utilities import LOADER
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click_options(Settings, [LOADER], show_envvars_in_help=True)
|
|
14
|
+
def random_sleep_sub_cmd(settings: Settings, /) -> None:
|
|
15
|
+
if is_pytest():
|
|
16
|
+
return
|
|
17
|
+
basic_config(obj=LOGGER)
|
|
18
|
+
random_sleep(
|
|
19
|
+
min_=settings.min,
|
|
20
|
+
max_=settings.max,
|
|
21
|
+
step=settings.step,
|
|
22
|
+
log_freq=settings.log_freq,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = ["random_sleep_sub_cmd"]
|