dycw-actions 0.3.40__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dycw-actions might be problematic. Click here for more details.
- actions/__init__.py +3 -0
- actions/cli.py +27 -0
- actions/conformalize/__init__.py +1 -0
- actions/conformalize/defaults.py +8 -0
- actions/conformalize/dicts.py +189 -0
- actions/hooks/__init__.py +1 -0
- actions/hooks/cli.py +37 -0
- actions/hooks/lib.py +97 -0
- actions/hooks/settings.py +24 -0
- actions/logging.py +8 -0
- actions/publish/__init__.py +1 -0
- actions/publish/cli.py +43 -0
- actions/publish/lib.py +56 -0
- actions/publish/settings.py +31 -0
- actions/requirements/__init__.py +1 -0
- actions/requirements/cli.py +37 -0
- actions/requirements/lib.py +121 -0
- actions/sequence_strs/__init__.py +1 -0
- actions/sequence_strs/cli.py +37 -0
- actions/sequence_strs/lib.py +79 -0
- actions/settings.py +18 -0
- actions/sleep/__init__.py +1 -0
- actions/sleep/cli.py +39 -0
- actions/sleep/lib.py +67 -0
- actions/sleep/settings.py +19 -0
- actions/tag/__init__.py +1 -0
- actions/tag/cli.py +43 -0
- actions/tag/lib.py +63 -0
- actions/tag/settings.py +20 -0
- actions/types.py +11 -0
- actions/utilities.py +105 -0
- dycw_actions-0.3.40.dist-info/METADATA +18 -0
- dycw_actions-0.3.40.dist-info/RECORD +35 -0
- dycw_actions-0.3.40.dist-info/WHEEL +4 -0
- dycw_actions-0.3.40.dist-info/entry_points.txt +3 -0
actions/__init__.py
ADDED
actions/cli.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from click import group
|
|
4
|
+
from utilities.click import CONTEXT_SETTINGS
|
|
5
|
+
|
|
6
|
+
from actions.hooks.cli import hooks_sub_cmd
|
|
7
|
+
from actions.publish.cli import publish_sub_cmd
|
|
8
|
+
from actions.requirements.cli import requirements_sub_cmd
|
|
9
|
+
from actions.sequence_strs.cli import sequence_strs_sub_cmd
|
|
10
|
+
from actions.sleep.cli import sleep_sub_cmd
|
|
11
|
+
from actions.tag.cli import tag_sub_cmd
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@group(**CONTEXT_SETTINGS)
|
|
15
|
+
def _main() -> None: ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_ = _main.command(name="hooks", **CONTEXT_SETTINGS)(hooks_sub_cmd)
|
|
19
|
+
_ = _main.command(name="publish", **CONTEXT_SETTINGS)(publish_sub_cmd)
|
|
20
|
+
_ = _main.command(name="requirements", **CONTEXT_SETTINGS)(requirements_sub_cmd)
|
|
21
|
+
_ = _main.command(name="sequence-strs", **CONTEXT_SETTINGS)(sequence_strs_sub_cmd)
|
|
22
|
+
_ = _main.command(name="sleep", **CONTEXT_SETTINGS)(sleep_sub_cmd)
|
|
23
|
+
_ = _main.command(name="tag", **CONTEXT_SETTINGS)(tag_sub_cmd)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
_main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from actions.conformalize.defaults import GITHUB_TOKEN, PRERELEASE, RESOLUTION
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from actions.types import StrDict
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_action_pre_commit_dict(
|
|
12
|
+
*,
|
|
13
|
+
token_checkout: str = GITHUB_TOKEN,
|
|
14
|
+
submodules: str | None = None,
|
|
15
|
+
token_uv: str = GITHUB_TOKEN,
|
|
16
|
+
repos: Any | None = None,
|
|
17
|
+
hooks: Any | None = None,
|
|
18
|
+
sleep: int = 1,
|
|
19
|
+
) -> StrDict:
|
|
20
|
+
dict_: StrDict = {"token-checkout": token_checkout}
|
|
21
|
+
_add_item(dict_, "submodules", value=submodules)
|
|
22
|
+
dict_["token-uv"] = token_uv
|
|
23
|
+
_add_item(dict_, "repos", value=repos)
|
|
24
|
+
_add_item(dict_, "hooks", value=hooks)
|
|
25
|
+
dict_["sleep"] = sleep
|
|
26
|
+
return {
|
|
27
|
+
"name": "Run 'pre-commit'",
|
|
28
|
+
"uses": "dycw/action-pre-commit@latest",
|
|
29
|
+
"with": dict_,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def run_action_publish_dict(
|
|
34
|
+
*,
|
|
35
|
+
token_checkout: str = GITHUB_TOKEN,
|
|
36
|
+
token_uv: str = GITHUB_TOKEN,
|
|
37
|
+
username: str | None = None,
|
|
38
|
+
password: str | None = None,
|
|
39
|
+
publish_url: str | None = None,
|
|
40
|
+
trusted_publishing: bool = False,
|
|
41
|
+
native_tls: bool = False,
|
|
42
|
+
) -> StrDict:
|
|
43
|
+
dict_: StrDict = {"token-checkout": token_checkout, "token-uv": token_uv}
|
|
44
|
+
_add_item(dict_, "username", value=username)
|
|
45
|
+
_add_item(dict_, "password", value=password)
|
|
46
|
+
_add_item(dict_, "publish-url", value=publish_url)
|
|
47
|
+
_add_boolean(dict_, "trusted-publishing", value=trusted_publishing)
|
|
48
|
+
_add_native_tls(dict_, native_tls=native_tls)
|
|
49
|
+
return {
|
|
50
|
+
"name": "Build and publish package",
|
|
51
|
+
"uses": "dycw/action-publish@latest",
|
|
52
|
+
"with": dict_,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def run_action_pyright_dict(
|
|
57
|
+
*,
|
|
58
|
+
token_checkout: str = GITHUB_TOKEN,
|
|
59
|
+
token_uv: str = GITHUB_TOKEN,
|
|
60
|
+
python_version: str | None = None,
|
|
61
|
+
resolution: str = RESOLUTION,
|
|
62
|
+
prerelease: str = PRERELEASE,
|
|
63
|
+
native_tls: bool = False,
|
|
64
|
+
with_requirements: str | None = None,
|
|
65
|
+
) -> StrDict:
|
|
66
|
+
dict_: StrDict = {"token-checkout": token_checkout, "token-uv": token_uv}
|
|
67
|
+
_add_python_version(dict_, python_version=python_version)
|
|
68
|
+
dict_["resolution"] = resolution
|
|
69
|
+
dict_["prerelease"] = prerelease
|
|
70
|
+
_add_native_tls(dict_, native_tls=native_tls)
|
|
71
|
+
_add_with_requirements(dict_, with_requirements=with_requirements)
|
|
72
|
+
return {
|
|
73
|
+
"name": "Run 'pyright'",
|
|
74
|
+
"uses": "dycw/action-pyright@latest",
|
|
75
|
+
"with": dict_,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def run_action_pytest_dict(
|
|
80
|
+
*,
|
|
81
|
+
token_checkout: str = GITHUB_TOKEN,
|
|
82
|
+
token_uv: str = GITHUB_TOKEN,
|
|
83
|
+
python_version: str | None = None,
|
|
84
|
+
resolution: str = RESOLUTION,
|
|
85
|
+
prerelease: str = PRERELEASE,
|
|
86
|
+
native_tls: bool = False,
|
|
87
|
+
with_requirements: str | None = None,
|
|
88
|
+
) -> StrDict:
|
|
89
|
+
dict_: StrDict = {"token-checkout": token_checkout, "token-uv": token_uv}
|
|
90
|
+
_add_python_version(dict_, python_version=python_version)
|
|
91
|
+
dict_["resolution"] = resolution
|
|
92
|
+
dict_["prerelease"] = prerelease
|
|
93
|
+
_add_native_tls(dict_, native_tls=native_tls)
|
|
94
|
+
_add_with_requirements(dict_, with_requirements=with_requirements)
|
|
95
|
+
return {"name": "Run 'pytest'", "uses": "dycw/action-pytest@latest", "with": dict_}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def run_action_random_sleep_dict(
|
|
99
|
+
*,
|
|
100
|
+
token_checkout: str = GITHUB_TOKEN,
|
|
101
|
+
token_uv: str = GITHUB_TOKEN,
|
|
102
|
+
min: int = 0, # noqa: A002
|
|
103
|
+
max: int = 3600, # noqa: A002
|
|
104
|
+
step: int = 1,
|
|
105
|
+
log_freq: int = 1,
|
|
106
|
+
) -> StrDict:
|
|
107
|
+
dict_: StrDict = {
|
|
108
|
+
"token-checkout": token_checkout,
|
|
109
|
+
"token-uv": token_uv,
|
|
110
|
+
"min": min,
|
|
111
|
+
"max": max,
|
|
112
|
+
"step": step,
|
|
113
|
+
"log-freq": log_freq,
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
"name": "Tag latest commit",
|
|
117
|
+
"uses": "dycw/action-tag@latest",
|
|
118
|
+
"with": dict_,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def run_action_ruff_dict(
|
|
123
|
+
*, token_checkout: str = GITHUB_TOKEN, token_ruff: str = GITHUB_TOKEN
|
|
124
|
+
) -> StrDict:
|
|
125
|
+
dict_: StrDict = {"token-checkout": token_checkout, "token-ruff": token_ruff}
|
|
126
|
+
return {"name": "Run 'ruff'", "uses": "dycw/action-ruff@latest", "with": dict_}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def run_action_tag_dict(
|
|
130
|
+
*,
|
|
131
|
+
token_checkout: str = GITHUB_TOKEN,
|
|
132
|
+
token_uv: str = GITHUB_TOKEN,
|
|
133
|
+
user_name: str = "github-actions-bot",
|
|
134
|
+
user_email: str = "noreply@github.com",
|
|
135
|
+
major_minor: bool = False,
|
|
136
|
+
major: bool = False,
|
|
137
|
+
latest: bool = False,
|
|
138
|
+
) -> StrDict:
|
|
139
|
+
dict_: StrDict = {
|
|
140
|
+
"token-checkout": token_checkout,
|
|
141
|
+
"token-uv": token_uv,
|
|
142
|
+
"user-name": user_name,
|
|
143
|
+
"user-email": user_email,
|
|
144
|
+
}
|
|
145
|
+
_add_boolean(dict_, "major-minor", value=major_minor)
|
|
146
|
+
_add_boolean(dict_, "major", value=major)
|
|
147
|
+
_add_boolean(dict_, "latest", value=latest)
|
|
148
|
+
return {
|
|
149
|
+
"name": "Tag latest commit",
|
|
150
|
+
"uses": "dycw/action-tag@latest",
|
|
151
|
+
"with": dict_,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _add_boolean(dict_: StrDict, key: str, /, *, value: bool = False) -> None:
|
|
156
|
+
if value:
|
|
157
|
+
dict_[key] = value
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _add_item(dict_: StrDict, key: str, /, *, value: Any | None = None) -> None:
|
|
161
|
+
if value is not None:
|
|
162
|
+
dict_[key] = value
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _add_native_tls(dict_: StrDict, /, *, native_tls: bool = False) -> None:
|
|
166
|
+
_add_boolean(dict_, "native-tls", value=native_tls)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _add_python_version(
|
|
170
|
+
dict_: StrDict, /, *, python_version: str | None = None
|
|
171
|
+
) -> None:
|
|
172
|
+
_add_item(dict_, "python-version", value=python_version)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _add_with_requirements(
|
|
176
|
+
dict_: StrDict, /, *, with_requirements: str | None = None
|
|
177
|
+
) -> None:
|
|
178
|
+
_add_item(dict_, "with-requirements", value=with_requirements)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
__all__ = [
|
|
182
|
+
"run_action_pre_commit_dict",
|
|
183
|
+
"run_action_publish_dict",
|
|
184
|
+
"run_action_pyright_dict",
|
|
185
|
+
"run_action_pytest_dict",
|
|
186
|
+
"run_action_random_sleep_dict",
|
|
187
|
+
"run_action_ruff_dict",
|
|
188
|
+
"run_action_tag_dict",
|
|
189
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
actions/hooks/cli.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from rich.pretty import pretty_repr
|
|
4
|
+
from typed_settings import click_options
|
|
5
|
+
from utilities.logging import basic_config
|
|
6
|
+
from utilities.os import is_pytest
|
|
7
|
+
from utilities.text import strip_and_dedent
|
|
8
|
+
|
|
9
|
+
from actions import __version__
|
|
10
|
+
from actions.hooks.lib import run_hooks
|
|
11
|
+
from actions.hooks.settings import HooksSettings
|
|
12
|
+
from actions.logging import LOGGER
|
|
13
|
+
from actions.settings import CommonSettings
|
|
14
|
+
from actions.utilities import LOADER
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click_options(CommonSettings, [LOADER], show_envvars_in_help=True, argname="common")
|
|
18
|
+
@click_options(HooksSettings, [LOADER], show_envvars_in_help=True, argname="hooks")
|
|
19
|
+
def hooks_sub_cmd(*, common: CommonSettings, hooks: HooksSettings) -> None:
|
|
20
|
+
if is_pytest():
|
|
21
|
+
return
|
|
22
|
+
basic_config(obj=LOGGER)
|
|
23
|
+
LOGGER.info(
|
|
24
|
+
strip_and_dedent("""
|
|
25
|
+
Running '%s' (version %s) with settings:
|
|
26
|
+
%s
|
|
27
|
+
%s
|
|
28
|
+
"""),
|
|
29
|
+
run_hooks.__name__,
|
|
30
|
+
__version__,
|
|
31
|
+
pretty_repr(common),
|
|
32
|
+
pretty_repr(hooks),
|
|
33
|
+
)
|
|
34
|
+
run_hooks(repos=hooks.repos, hooks=hooks.hooks, sleep=hooks.sleep)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = ["hooks_sub_cmd"]
|
actions/hooks/lib.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from re import search
|
|
6
|
+
from subprocess import CalledProcessError
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from utilities.functions import ensure_class, ensure_str
|
|
10
|
+
from utilities.text import strip_and_dedent
|
|
11
|
+
from whenever import TimeDelta
|
|
12
|
+
from yaml import safe_load
|
|
13
|
+
|
|
14
|
+
from actions import __version__
|
|
15
|
+
from actions.hooks.settings import HOOKS_SETTINGS
|
|
16
|
+
from actions.logging import LOGGER
|
|
17
|
+
from actions.utilities import log_run
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from collections.abc import Iterator
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run_hooks(
|
|
24
|
+
*,
|
|
25
|
+
repos: list[str] | None = HOOKS_SETTINGS.repos,
|
|
26
|
+
hooks: list[str] | None = HOOKS_SETTINGS.hooks,
|
|
27
|
+
sleep: int = HOOKS_SETTINGS.sleep,
|
|
28
|
+
) -> None:
|
|
29
|
+
LOGGER.info(
|
|
30
|
+
strip_and_dedent("""
|
|
31
|
+
Running '%s' (version %s) with settings:
|
|
32
|
+
- repos = %s
|
|
33
|
+
- hooks = %s
|
|
34
|
+
- sleep = %d
|
|
35
|
+
"""),
|
|
36
|
+
run_hooks.__name__,
|
|
37
|
+
__version__,
|
|
38
|
+
repos,
|
|
39
|
+
hooks,
|
|
40
|
+
sleep,
|
|
41
|
+
)
|
|
42
|
+
results = {
|
|
43
|
+
hook: _run_hook(hook, sleep=sleep)
|
|
44
|
+
for hook in _yield_hooks(repos=repos, hooks=hooks)
|
|
45
|
+
}
|
|
46
|
+
failed = {hook: result for hook, result in results.items() if not result}
|
|
47
|
+
if len(failed) >= 1:
|
|
48
|
+
msg = f"Failed hook(s): {', '.join(failed)}"
|
|
49
|
+
raise RuntimeError(msg)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _yield_hooks(
|
|
53
|
+
*,
|
|
54
|
+
repos: list[str] | None = HOOKS_SETTINGS.repos,
|
|
55
|
+
hooks: list[str] | None = HOOKS_SETTINGS.hooks,
|
|
56
|
+
) -> Iterator[str]:
|
|
57
|
+
dict_ = safe_load(Path(".pre-commit-config.yaml").read_text())
|
|
58
|
+
repos_list = ensure_class(dict_["repos"], list)
|
|
59
|
+
results: set[str] = set()
|
|
60
|
+
for repo in (ensure_class(r, dict) for r in repos_list):
|
|
61
|
+
url = repo["repo"]
|
|
62
|
+
if (repos is not None) and any(search(repo_i, url) for repo_i in repos):
|
|
63
|
+
results.update(_yield_repo_hooks(repo))
|
|
64
|
+
elif hooks is not None:
|
|
65
|
+
for hook in _yield_repo_hooks(repo):
|
|
66
|
+
if any(search(hook_i, hook) for hook_i in hooks):
|
|
67
|
+
results.add(hook)
|
|
68
|
+
yield from sorted(results)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _yield_repo_hooks(repo: dict[str, Any], /) -> Iterator[str]:
|
|
72
|
+
hooks = ensure_class(repo["hooks"], list)
|
|
73
|
+
for hook in (ensure_class(r, dict) for r in hooks):
|
|
74
|
+
yield ensure_str(hook["id"])
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _run_hook(hook: str, /, *, sleep: int = HOOKS_SETTINGS.sleep) -> bool:
|
|
78
|
+
LOGGER.info("Running '%s'...", hook)
|
|
79
|
+
try:
|
|
80
|
+
log_run("pre-commit", "run", "--verbose", "--all-files", hook, print=True)
|
|
81
|
+
except CalledProcessError:
|
|
82
|
+
is_success = False
|
|
83
|
+
else:
|
|
84
|
+
is_success = True
|
|
85
|
+
delta = TimeDelta(seconds=sleep)
|
|
86
|
+
LOGGER.info(
|
|
87
|
+
"Hook '%s' %s; sleeping for %s...",
|
|
88
|
+
hook,
|
|
89
|
+
"succeeded" if is_success else "failed",
|
|
90
|
+
delta,
|
|
91
|
+
)
|
|
92
|
+
time.sleep(sleep)
|
|
93
|
+
LOGGER.info("Finished sleeping for %s", delta)
|
|
94
|
+
return is_success
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
__all__ = ["run_hooks"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typed_settings import load_settings, option, settings
|
|
4
|
+
|
|
5
|
+
from actions.utilities import LOADER, convert_list_strs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@settings
|
|
9
|
+
class HooksSettings:
|
|
10
|
+
repos: list[str] | None = option(
|
|
11
|
+
default=None,
|
|
12
|
+
converter=convert_list_strs,
|
|
13
|
+
help="The repos whose hooks are to be run",
|
|
14
|
+
)
|
|
15
|
+
hooks: list[str] | None = option(
|
|
16
|
+
default=None, converter=convert_list_strs, help="The hooks to be run"
|
|
17
|
+
)
|
|
18
|
+
sleep: int = option(default=1, help="Sleep in between runs")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
HOOKS_SETTINGS = load_settings(HooksSettings, [LOADER])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = ["HOOKS_SETTINGS", "HooksSettings"]
|
actions/logging.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
actions/publish/cli.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from rich.pretty import pretty_repr
|
|
4
|
+
from typed_settings import click_options
|
|
5
|
+
from utilities.logging import basic_config
|
|
6
|
+
from utilities.os import is_pytest
|
|
7
|
+
from utilities.text import strip_and_dedent
|
|
8
|
+
|
|
9
|
+
from actions import __version__
|
|
10
|
+
from actions.logging import LOGGER
|
|
11
|
+
from actions.publish.lib import publish_package
|
|
12
|
+
from actions.publish.settings import PublishSettings
|
|
13
|
+
from actions.settings import CommonSettings
|
|
14
|
+
from actions.utilities import LOADER
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click_options(CommonSettings, [LOADER], show_envvars_in_help=True, argname="common")
|
|
18
|
+
@click_options(PublishSettings, [LOADER], show_envvars_in_help=True, argname="publish")
|
|
19
|
+
def publish_sub_cmd(*, common: CommonSettings, publish: PublishSettings) -> None:
|
|
20
|
+
if is_pytest():
|
|
21
|
+
return
|
|
22
|
+
basic_config(obj=LOGGER)
|
|
23
|
+
LOGGER.info(
|
|
24
|
+
strip_and_dedent("""
|
|
25
|
+
Running '%s' (version %s) with settings:
|
|
26
|
+
%s
|
|
27
|
+
%s
|
|
28
|
+
"""),
|
|
29
|
+
publish_package.__name__,
|
|
30
|
+
__version__,
|
|
31
|
+
pretty_repr(common),
|
|
32
|
+
pretty_repr(publish),
|
|
33
|
+
)
|
|
34
|
+
publish_package(
|
|
35
|
+
username=publish.username,
|
|
36
|
+
password=publish.password,
|
|
37
|
+
publish_url=publish.publish_url,
|
|
38
|
+
trusted_publishing=publish.trusted_publishing,
|
|
39
|
+
native_tls=publish.native_tls,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = ["publish_sub_cmd"]
|
actions/publish/lib.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from utilities.tempfile import TemporaryDirectory
|
|
6
|
+
from utilities.text import strip_and_dedent
|
|
7
|
+
|
|
8
|
+
from actions import __version__
|
|
9
|
+
from actions.logging import LOGGER
|
|
10
|
+
from actions.publish.settings import PUBLISH_SETTINGS
|
|
11
|
+
from actions.utilities import log_run
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from typed_settings import Secret
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def publish_package(
|
|
18
|
+
*,
|
|
19
|
+
username: str | None = PUBLISH_SETTINGS.username,
|
|
20
|
+
password: Secret[str] | None = PUBLISH_SETTINGS.password,
|
|
21
|
+
publish_url: str | None = PUBLISH_SETTINGS.publish_url,
|
|
22
|
+
trusted_publishing: bool = PUBLISH_SETTINGS.trusted_publishing,
|
|
23
|
+
native_tls: bool = PUBLISH_SETTINGS.native_tls,
|
|
24
|
+
) -> None:
|
|
25
|
+
LOGGER.info(
|
|
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
|
+
"""),
|
|
34
|
+
publish_package.__name__,
|
|
35
|
+
__version__,
|
|
36
|
+
username,
|
|
37
|
+
password,
|
|
38
|
+
publish_url,
|
|
39
|
+
trusted_publishing,
|
|
40
|
+
native_tls,
|
|
41
|
+
)
|
|
42
|
+
with TemporaryDirectory() as temp:
|
|
43
|
+
log_run("uv", "build", "--out-dir", str(temp), "--wheel", "--clear")
|
|
44
|
+
log_run(
|
|
45
|
+
"uv",
|
|
46
|
+
"publish",
|
|
47
|
+
*([] if username is None else ["--username", username]),
|
|
48
|
+
*([] if password is None else ["--password", password]),
|
|
49
|
+
*([] if publish_url is None else ["--publish-url", publish_url]),
|
|
50
|
+
*(["--trusted-publishing", "always"] if trusted_publishing else []),
|
|
51
|
+
*(["--native-tls"] if native_tls else []),
|
|
52
|
+
f"{temp}/*",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = ["publish_package"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typed_settings import Secret, load_settings, option, secret, settings
|
|
4
|
+
|
|
5
|
+
from actions.utilities import LOADER, convert_secret_str, convert_str
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@settings
|
|
9
|
+
class PublishSettings:
|
|
10
|
+
username: str | None = option(
|
|
11
|
+
default=None, converter=convert_str, help="The username of the upload"
|
|
12
|
+
)
|
|
13
|
+
password: Secret[str] | None = secret(
|
|
14
|
+
default=None, converter=convert_secret_str, help="The password for the upload"
|
|
15
|
+
)
|
|
16
|
+
publish_url: str | None = option(
|
|
17
|
+
default=None, converter=convert_str, help="The URL of the upload endpoint"
|
|
18
|
+
)
|
|
19
|
+
trusted_publishing: bool = option(
|
|
20
|
+
default=False, help="Configure trusted publishing"
|
|
21
|
+
)
|
|
22
|
+
native_tls: bool = option(
|
|
23
|
+
default=False,
|
|
24
|
+
help="Whether to load TLS certificates from the platform's native certificate store",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
PUBLISH_SETTINGS = load_settings(PublishSettings, [LOADER])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = ["PUBLISH_SETTINGS", "PublishSettings"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from click import argument
|
|
7
|
+
from utilities.logging import basic_config
|
|
8
|
+
from utilities.os import is_pytest
|
|
9
|
+
from utilities.text import strip_and_dedent
|
|
10
|
+
|
|
11
|
+
from actions import __version__
|
|
12
|
+
from actions.logging import LOGGER
|
|
13
|
+
from actions.requirements.lib import format_requirements
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@argument(
|
|
17
|
+
"paths",
|
|
18
|
+
nargs=-1,
|
|
19
|
+
type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
|
|
20
|
+
)
|
|
21
|
+
def requirements_sub_cmd(*, paths: tuple[Path, ...]) -> None:
|
|
22
|
+
if is_pytest():
|
|
23
|
+
return
|
|
24
|
+
basic_config(obj=LOGGER)
|
|
25
|
+
LOGGER.info(
|
|
26
|
+
strip_and_dedent("""
|
|
27
|
+
Running '%s' (version %s) with settings:
|
|
28
|
+
- paths = %s
|
|
29
|
+
"""),
|
|
30
|
+
format_requirements.__name__,
|
|
31
|
+
__version__,
|
|
32
|
+
paths,
|
|
33
|
+
)
|
|
34
|
+
format_requirements(*paths)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = ["requirements_sub_cmd"]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING, Any, override
|
|
6
|
+
|
|
7
|
+
from packaging._tokenizer import ParserSyntaxError
|
|
8
|
+
from packaging.requirements import InvalidRequirement, Requirement, _parse_requirement
|
|
9
|
+
from packaging.specifiers import Specifier, SpecifierSet
|
|
10
|
+
from tomlkit import TOMLDocument, array, dumps, loads, string
|
|
11
|
+
from tomlkit.items import Array, Table
|
|
12
|
+
from utilities.text import repr_str, strip_and_dedent
|
|
13
|
+
|
|
14
|
+
from actions import __version__
|
|
15
|
+
from actions.logging import LOGGER
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Iterator
|
|
19
|
+
|
|
20
|
+
from utilities.types import PathLike
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_MODIFICATIONS: set[Path] = set()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def format_requirements(*paths: PathLike) -> None:
|
|
27
|
+
LOGGER.info(
|
|
28
|
+
strip_and_dedent("""
|
|
29
|
+
Running '%s' (version %s) with settings:
|
|
30
|
+
- paths = %s
|
|
31
|
+
"""),
|
|
32
|
+
format_requirements.__name__,
|
|
33
|
+
__version__,
|
|
34
|
+
paths,
|
|
35
|
+
)
|
|
36
|
+
for path in paths:
|
|
37
|
+
_format_path(path)
|
|
38
|
+
if len(_MODIFICATIONS) >= 1:
|
|
39
|
+
LOGGER.info(
|
|
40
|
+
"Exiting due to modifications: %s",
|
|
41
|
+
", ".join(map(repr_str, sorted(_MODIFICATIONS))),
|
|
42
|
+
)
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _format_path(path: PathLike, /) -> None:
|
|
47
|
+
path = Path(path)
|
|
48
|
+
current = loads(path.read_text())
|
|
49
|
+
expected = _get_formatted(path)
|
|
50
|
+
is_equal = current == expected # tomlkit cannot handle !=
|
|
51
|
+
if not is_equal:
|
|
52
|
+
_ = path.write_text(dumps(expected).rstrip("\n") + "\n")
|
|
53
|
+
_MODIFICATIONS.add(path)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _get_formatted(path: PathLike, /) -> TOMLDocument:
|
|
57
|
+
path = Path(path)
|
|
58
|
+
doc = loads(path.read_text())
|
|
59
|
+
if isinstance(dep_grps := doc.get("dependency-groups"), Table):
|
|
60
|
+
for key, value in dep_grps.items():
|
|
61
|
+
if isinstance(value, Array):
|
|
62
|
+
dep_grps[key] = _format_array(value)
|
|
63
|
+
if isinstance(project := doc["project"], Table):
|
|
64
|
+
if isinstance(deps := project["dependencies"], Array):
|
|
65
|
+
project["dependencies"] = _format_array(deps)
|
|
66
|
+
if isinstance(optional := project.get("optional-dependencies"), Table):
|
|
67
|
+
for key, value in optional.items():
|
|
68
|
+
if isinstance(value, Array):
|
|
69
|
+
optional[key] = _format_array(value)
|
|
70
|
+
return doc
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _format_array(dependencies: Array, /) -> Array:
|
|
74
|
+
new = array().multiline(multiline=True)
|
|
75
|
+
new.extend(map(_format_item, dependencies))
|
|
76
|
+
return new
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _format_item(item: Any, /) -> Any:
|
|
80
|
+
if not isinstance(item, str):
|
|
81
|
+
return item
|
|
82
|
+
return string(str(_CustomRequirement(item)))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class _CustomRequirement(Requirement):
|
|
86
|
+
@override
|
|
87
|
+
def __init__(self, requirement_string: str) -> None:
|
|
88
|
+
super().__init__(requirement_string)
|
|
89
|
+
try:
|
|
90
|
+
parsed = _parse_requirement(requirement_string)
|
|
91
|
+
except ParserSyntaxError as e:
|
|
92
|
+
raise InvalidRequirement(str(e)) from e
|
|
93
|
+
self.specifier = _CustomSpecifierSet(parsed.specifier)
|
|
94
|
+
|
|
95
|
+
@override
|
|
96
|
+
def _iter_parts(self, name: str) -> Iterator[str]:
|
|
97
|
+
yield name
|
|
98
|
+
if self.extras:
|
|
99
|
+
formatted_extras = ",".join(sorted(self.extras))
|
|
100
|
+
yield f"[{formatted_extras}]"
|
|
101
|
+
if self.specifier:
|
|
102
|
+
yield f" {self.specifier}"
|
|
103
|
+
if self.url:
|
|
104
|
+
yield f"@ {self.url}"
|
|
105
|
+
if self.marker:
|
|
106
|
+
yield " "
|
|
107
|
+
if self.marker:
|
|
108
|
+
yield f"; {self.marker}"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class _CustomSpecifierSet(SpecifierSet):
|
|
112
|
+
@override
|
|
113
|
+
def __str__(self) -> str:
|
|
114
|
+
specs = sorted(self._specs, key=self._key)
|
|
115
|
+
return ", ".join(map(str, specs))
|
|
116
|
+
|
|
117
|
+
def _key(self, spec: Specifier, /) -> int:
|
|
118
|
+
return [">=", "<"].index(spec.operator)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
__all__ = ["format_requirements"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from click import argument
|
|
7
|
+
from utilities.logging import basic_config
|
|
8
|
+
from utilities.os import is_pytest
|
|
9
|
+
from utilities.text import strip_and_dedent
|
|
10
|
+
|
|
11
|
+
from actions import __version__
|
|
12
|
+
from actions.logging import LOGGER
|
|
13
|
+
from actions.sequence_strs.lib import replace_sequence_strs
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@argument(
|
|
17
|
+
"paths",
|
|
18
|
+
nargs=-1,
|
|
19
|
+
type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
|
|
20
|
+
)
|
|
21
|
+
def sequence_strs_sub_cmd(*, paths: tuple[Path, ...]) -> None:
|
|
22
|
+
if is_pytest():
|
|
23
|
+
return
|
|
24
|
+
basic_config(obj=LOGGER)
|
|
25
|
+
LOGGER.info(
|
|
26
|
+
strip_and_dedent("""
|
|
27
|
+
Running '%s' (version %s) with settings:
|
|
28
|
+
- paths = %s
|
|
29
|
+
"""),
|
|
30
|
+
replace_sequence_strs.__name__,
|
|
31
|
+
__version__,
|
|
32
|
+
paths,
|
|
33
|
+
)
|
|
34
|
+
replace_sequence_strs(*paths)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = ["sequence_strs_sub_cmd"]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING, override
|
|
6
|
+
|
|
7
|
+
from libcst import CSTTransformer, Module, Name, Subscript, parse_module
|
|
8
|
+
from libcst.matchers import Index as MIndex
|
|
9
|
+
from libcst.matchers import Name as MName
|
|
10
|
+
from libcst.matchers import Subscript as MSubscript
|
|
11
|
+
from libcst.matchers import SubscriptElement as MSubscriptElement
|
|
12
|
+
from libcst.matchers import matches
|
|
13
|
+
from libcst.metadata import MetadataWrapper
|
|
14
|
+
from utilities.text import repr_str, strip_and_dedent
|
|
15
|
+
|
|
16
|
+
from actions import __version__
|
|
17
|
+
from actions.logging import LOGGER
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from utilities.types import PathLike
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_MODIFICATIONS: set[Path] = set()
|
|
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
|
+
for path in paths:
|
|
37
|
+
_format_path(path)
|
|
38
|
+
if len(_MODIFICATIONS) >= 1:
|
|
39
|
+
LOGGER.info(
|
|
40
|
+
"Exiting due to modifications: %s",
|
|
41
|
+
", ".join(map(repr_str, sorted(_MODIFICATIONS))),
|
|
42
|
+
)
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
|
|
45
|
+
|
|
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())
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class SequenceToListTransformer(CSTTransformer):
|
|
63
|
+
@override
|
|
64
|
+
def leave_Subscript(
|
|
65
|
+
self, original_node: Subscript, updated_node: Subscript
|
|
66
|
+
) -> Subscript:
|
|
67
|
+
_ = original_node
|
|
68
|
+
if matches(
|
|
69
|
+
updated_node,
|
|
70
|
+
MSubscript(
|
|
71
|
+
value=MName("Sequence"),
|
|
72
|
+
slice=[MSubscriptElement(slice=MIndex(value=MName("str")))],
|
|
73
|
+
),
|
|
74
|
+
):
|
|
75
|
+
return updated_node.with_changes(value=Name("list"))
|
|
76
|
+
return updated_node
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
__all__ = ["replace_sequence_strs"]
|
actions/settings.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typed_settings import Secret, load_settings, secret, settings
|
|
4
|
+
|
|
5
|
+
from actions.utilities import LOADER, convert_secret_str
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@settings
|
|
9
|
+
class CommonSettings:
|
|
10
|
+
token: Secret[str] | None = secret(
|
|
11
|
+
default=None, converter=convert_secret_str, help="GitHub token"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
COMMON_SETTINGS = load_settings(CommonSettings, [LOADER])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["COMMON_SETTINGS", "CommonSettings"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
actions/sleep/cli.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from rich.pretty import pretty_repr
|
|
4
|
+
from typed_settings import click_options
|
|
5
|
+
from utilities.logging import basic_config
|
|
6
|
+
from utilities.os import is_pytest
|
|
7
|
+
from utilities.text import strip_and_dedent
|
|
8
|
+
|
|
9
|
+
from actions import __version__
|
|
10
|
+
from actions.logging import LOGGER
|
|
11
|
+
from actions.settings import CommonSettings
|
|
12
|
+
from actions.sleep.lib import random_sleep
|
|
13
|
+
from actions.sleep.settings import SleepSettings
|
|
14
|
+
from actions.utilities import LOADER
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click_options(CommonSettings, [LOADER], show_envvars_in_help=True, argname="common")
|
|
18
|
+
@click_options(SleepSettings, [LOADER], show_envvars_in_help=True, argname="sleep")
|
|
19
|
+
def sleep_sub_cmd(*, common: CommonSettings, sleep: SleepSettings) -> None:
|
|
20
|
+
if is_pytest():
|
|
21
|
+
return
|
|
22
|
+
basic_config(obj=LOGGER)
|
|
23
|
+
LOGGER.info(
|
|
24
|
+
strip_and_dedent("""
|
|
25
|
+
Running '%s' (version %s) with settings:
|
|
26
|
+
%s
|
|
27
|
+
%s
|
|
28
|
+
"""),
|
|
29
|
+
random_sleep.__name__,
|
|
30
|
+
__version__,
|
|
31
|
+
pretty_repr(common),
|
|
32
|
+
pretty_repr(sleep),
|
|
33
|
+
)
|
|
34
|
+
random_sleep(
|
|
35
|
+
min_=sleep.min, max_=sleep.max, step=sleep.step, log_freq=sleep.log_freq
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = ["sleep_sub_cmd"]
|
actions/sleep/lib.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from math import ceil, floor
|
|
4
|
+
from random import choice
|
|
5
|
+
from time import sleep
|
|
6
|
+
|
|
7
|
+
from utilities.text import strip_and_dedent
|
|
8
|
+
from utilities.whenever import get_now
|
|
9
|
+
from whenever import TimeDelta, ZonedDateTime
|
|
10
|
+
|
|
11
|
+
from actions import __version__
|
|
12
|
+
from actions.logging import LOGGER
|
|
13
|
+
from actions.sleep.settings import SLEEP_SETTINGS
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def random_sleep(
|
|
17
|
+
*,
|
|
18
|
+
min_: int = SLEEP_SETTINGS.min,
|
|
19
|
+
max_: int = SLEEP_SETTINGS.max,
|
|
20
|
+
step: int = SLEEP_SETTINGS.step,
|
|
21
|
+
log_freq: int = SLEEP_SETTINGS.log_freq,
|
|
22
|
+
) -> None:
|
|
23
|
+
LOGGER.info(
|
|
24
|
+
strip_and_dedent("""
|
|
25
|
+
Running '%s' (version %s) with settings:
|
|
26
|
+
- min_ = %s
|
|
27
|
+
- max_ = %s
|
|
28
|
+
- step = %s
|
|
29
|
+
- log_freq = %s
|
|
30
|
+
"""),
|
|
31
|
+
random_sleep.__name__,
|
|
32
|
+
__version__,
|
|
33
|
+
min_,
|
|
34
|
+
max_,
|
|
35
|
+
step,
|
|
36
|
+
log_freq,
|
|
37
|
+
)
|
|
38
|
+
start = get_now()
|
|
39
|
+
delta = TimeDelta(seconds=choice(range(min_, max_, step)))
|
|
40
|
+
LOGGER.info("Sleeping for %s...", delta)
|
|
41
|
+
end = (start + delta).round(mode="ceil")
|
|
42
|
+
while (now := get_now()) < end:
|
|
43
|
+
_intermediate(start, now, end, log_freq=log_freq)
|
|
44
|
+
LOGGER.info("Finished sleeping for %s", delta)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _intermediate(
|
|
48
|
+
start: ZonedDateTime,
|
|
49
|
+
now: ZonedDateTime,
|
|
50
|
+
end: ZonedDateTime,
|
|
51
|
+
/,
|
|
52
|
+
*,
|
|
53
|
+
log_freq: int = SLEEP_SETTINGS.log_freq,
|
|
54
|
+
) -> None:
|
|
55
|
+
elapsed = TimeDelta(seconds=floor((now - start).in_seconds()))
|
|
56
|
+
remaining = TimeDelta(seconds=ceil((end - now).in_seconds()))
|
|
57
|
+
this_sleep = min(remaining, TimeDelta(seconds=log_freq))
|
|
58
|
+
LOGGER.info(
|
|
59
|
+
"Sleeping for %s... (elapsed = %s, remaining = %s)",
|
|
60
|
+
this_sleep,
|
|
61
|
+
elapsed,
|
|
62
|
+
remaining,
|
|
63
|
+
)
|
|
64
|
+
sleep(round(this_sleep.in_seconds()))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__all__ = ["random_sleep"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typed_settings import load_settings, option, settings
|
|
4
|
+
|
|
5
|
+
from actions.utilities import LOADER
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@settings
|
|
9
|
+
class SleepSettings:
|
|
10
|
+
min: int = option(default=0, help="Minimum duration, in seconds")
|
|
11
|
+
max: int = option(default=3600, help="Maximum duration, in seconds")
|
|
12
|
+
step: int = option(default=1, help="Step duration, in seconds")
|
|
13
|
+
log_freq: int = option(default=60, help="Log frequency, in seconds")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
SLEEP_SETTINGS = load_settings(SleepSettings, [LOADER])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ["SLEEP_SETTINGS", "SleepSettings"]
|
actions/tag/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
actions/tag/cli.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from rich.pretty import pretty_repr
|
|
4
|
+
from typed_settings import click_options
|
|
5
|
+
from utilities.logging import basic_config
|
|
6
|
+
from utilities.os import is_pytest
|
|
7
|
+
from utilities.text import strip_and_dedent
|
|
8
|
+
|
|
9
|
+
from actions import __version__
|
|
10
|
+
from actions.logging import LOGGER
|
|
11
|
+
from actions.settings import CommonSettings
|
|
12
|
+
from actions.tag.lib import tag_commit
|
|
13
|
+
from actions.tag.settings import TagSettings
|
|
14
|
+
from actions.utilities import LOADER
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click_options(CommonSettings, [LOADER], show_envvars_in_help=True, argname="common")
|
|
18
|
+
@click_options(TagSettings, [LOADER], show_envvars_in_help=True, argname="tag")
|
|
19
|
+
def tag_sub_cmd(*, common: CommonSettings, tag: TagSettings) -> None:
|
|
20
|
+
if is_pytest():
|
|
21
|
+
return
|
|
22
|
+
basic_config(obj=LOGGER)
|
|
23
|
+
LOGGER.info(
|
|
24
|
+
strip_and_dedent("""
|
|
25
|
+
Running '%s' (version %s) with settings:
|
|
26
|
+
%s
|
|
27
|
+
%s
|
|
28
|
+
"""),
|
|
29
|
+
tag_commit.__name__,
|
|
30
|
+
__version__,
|
|
31
|
+
pretty_repr(common),
|
|
32
|
+
pretty_repr(tag),
|
|
33
|
+
)
|
|
34
|
+
tag_commit(
|
|
35
|
+
user_name=tag.user_name,
|
|
36
|
+
user_email=tag.user_email,
|
|
37
|
+
major_minor=tag.major_minor,
|
|
38
|
+
major=tag.major,
|
|
39
|
+
latest=tag.latest,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = ["tag_sub_cmd"]
|
actions/tag/lib.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from subprocess import CalledProcessError
|
|
5
|
+
|
|
6
|
+
from utilities.text import strip_and_dedent
|
|
7
|
+
from utilities.version import parse_version
|
|
8
|
+
|
|
9
|
+
from actions import __version__
|
|
10
|
+
from actions.logging import LOGGER
|
|
11
|
+
from actions.tag.settings import TAG_SETTINGS
|
|
12
|
+
from actions.utilities import log_run
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def tag_commit(
|
|
16
|
+
*,
|
|
17
|
+
user_name: str = TAG_SETTINGS.user_name,
|
|
18
|
+
user_email: str = TAG_SETTINGS.user_email,
|
|
19
|
+
major_minor: bool = TAG_SETTINGS.major_minor,
|
|
20
|
+
major: bool = TAG_SETTINGS.major,
|
|
21
|
+
latest: bool = TAG_SETTINGS.latest,
|
|
22
|
+
) -> None:
|
|
23
|
+
LOGGER.info(
|
|
24
|
+
strip_and_dedent("""
|
|
25
|
+
Running '%s' (version %s) with settings:
|
|
26
|
+
- user_name = %s
|
|
27
|
+
- user_email = %s
|
|
28
|
+
- major_minor = %s
|
|
29
|
+
- major = %s
|
|
30
|
+
- latest = %s
|
|
31
|
+
"""),
|
|
32
|
+
tag_commit.__name__,
|
|
33
|
+
__version__,
|
|
34
|
+
user_name,
|
|
35
|
+
user_email,
|
|
36
|
+
major_minor,
|
|
37
|
+
major,
|
|
38
|
+
latest,
|
|
39
|
+
)
|
|
40
|
+
log_run("git", "config", "--global", "user.name", user_name)
|
|
41
|
+
log_run("git", "config", "--global", "user.email", user_email)
|
|
42
|
+
version = parse_version(
|
|
43
|
+
log_run("bump-my-version", "show", "current_version", return_=True)
|
|
44
|
+
)
|
|
45
|
+
_tag(str(version))
|
|
46
|
+
if major_minor:
|
|
47
|
+
_tag(f"{version.major}.{version.minor}")
|
|
48
|
+
if major:
|
|
49
|
+
_tag(str(version.major))
|
|
50
|
+
if latest:
|
|
51
|
+
_tag("latest")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _tag(version: str, /) -> None:
|
|
55
|
+
with suppress(CalledProcessError):
|
|
56
|
+
log_run("git", "tag", "--delete", version)
|
|
57
|
+
with suppress(CalledProcessError):
|
|
58
|
+
log_run("git", "push", "--delete", "origin", version)
|
|
59
|
+
log_run("git", "tag", "-a", version, "HEAD", "-m", version)
|
|
60
|
+
log_run("git", "push", "--tags", "--force", "--set-upstream", "origin")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
__all__ = ["tag_commit"]
|
actions/tag/settings.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typed_settings import load_settings, option, settings
|
|
4
|
+
|
|
5
|
+
from actions.utilities import LOADER
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@settings
|
|
9
|
+
class TagSettings:
|
|
10
|
+
user_name: str = option(default="github-actions-bot", help="'git' user name")
|
|
11
|
+
user_email: str = option(default="noreply@github.com", help="'git' user email")
|
|
12
|
+
major_minor: bool = option(default=False, help="Add the 'major.minor' tag")
|
|
13
|
+
major: bool = option(default=False, help="Add the 'major' tag")
|
|
14
|
+
latest: bool = option(default=False, help="Add the 'latest' tag")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
TAG_SETTINGS = load_settings(TagSettings, [LOADER])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["TAG_SETTINGS", "TagSettings"]
|
actions/types.py
ADDED
actions/utilities.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Literal, assert_never, overload
|
|
4
|
+
|
|
5
|
+
from typed_settings import EnvLoader, Secret
|
|
6
|
+
from utilities.subprocess import run
|
|
7
|
+
|
|
8
|
+
from actions.logging import LOGGER
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from utilities.types import StrStrMapping
|
|
12
|
+
|
|
13
|
+
from actions.types import SecretLike
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
LOADER = EnvLoader("")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def convert_list_strs(
|
|
20
|
+
x: str | list[str] | tuple[str, ...] | None, /
|
|
21
|
+
) -> list[str] | None:
|
|
22
|
+
match x:
|
|
23
|
+
case None:
|
|
24
|
+
return None
|
|
25
|
+
case list():
|
|
26
|
+
return x
|
|
27
|
+
case tuple():
|
|
28
|
+
return None if x == () else list(x)
|
|
29
|
+
case str():
|
|
30
|
+
return x.splitlines()
|
|
31
|
+
case never:
|
|
32
|
+
assert_never(never)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def convert_secret_str(x: SecretLike | None, /) -> Secret[str] | None:
|
|
36
|
+
empty = {None, ""}
|
|
37
|
+
match x:
|
|
38
|
+
case Secret():
|
|
39
|
+
return None if x.get_secret_value() in empty else x
|
|
40
|
+
case str():
|
|
41
|
+
return None if x in empty else Secret(x)
|
|
42
|
+
case None:
|
|
43
|
+
return None
|
|
44
|
+
case never:
|
|
45
|
+
assert_never(never)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def convert_str(x: str | None, /) -> str | None:
|
|
49
|
+
match x:
|
|
50
|
+
case str():
|
|
51
|
+
return None if x == "" else x
|
|
52
|
+
case None:
|
|
53
|
+
return None
|
|
54
|
+
case never:
|
|
55
|
+
assert_never(never)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@overload
|
|
59
|
+
def log_run(
|
|
60
|
+
cmd: SecretLike,
|
|
61
|
+
/,
|
|
62
|
+
*cmds: SecretLike,
|
|
63
|
+
env: StrStrMapping | None = None,
|
|
64
|
+
print: bool = False,
|
|
65
|
+
return_: Literal[True],
|
|
66
|
+
) -> str: ...
|
|
67
|
+
@overload
|
|
68
|
+
def log_run(
|
|
69
|
+
cmd: SecretLike,
|
|
70
|
+
/,
|
|
71
|
+
*cmds: SecretLike,
|
|
72
|
+
env: StrStrMapping | None = None,
|
|
73
|
+
print: bool = False,
|
|
74
|
+
return_: Literal[False] = False,
|
|
75
|
+
) -> None: ...
|
|
76
|
+
@overload
|
|
77
|
+
def log_run(
|
|
78
|
+
cmd: SecretLike,
|
|
79
|
+
/,
|
|
80
|
+
*cmds: SecretLike,
|
|
81
|
+
env: StrStrMapping | None = None,
|
|
82
|
+
print: bool = False,
|
|
83
|
+
return_: bool = False,
|
|
84
|
+
) -> str | None: ...
|
|
85
|
+
def log_run(
|
|
86
|
+
cmd: SecretLike,
|
|
87
|
+
/,
|
|
88
|
+
*cmds: SecretLike,
|
|
89
|
+
env: StrStrMapping | None = None,
|
|
90
|
+
print: bool = False, # noqa: A002
|
|
91
|
+
return_: bool = False,
|
|
92
|
+
) -> str | None:
|
|
93
|
+
all_cmds = [cmd, *cmds]
|
|
94
|
+
LOGGER.info("Running '%s'...", " ".join(map(str, all_cmds)))
|
|
95
|
+
unwrapped = [c if isinstance(c, str) else c.get_secret_value() for c in all_cmds]
|
|
96
|
+
return run(*unwrapped, env=env, print=print, return_=return_, logger=LOGGER)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
__all__ = [
|
|
100
|
+
"LOADER",
|
|
101
|
+
"convert_list_strs",
|
|
102
|
+
"convert_secret_str",
|
|
103
|
+
"convert_str",
|
|
104
|
+
"log_run",
|
|
105
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: dycw-actions
|
|
3
|
+
Version: 0.3.40
|
|
4
|
+
Summary: GitHub actions
|
|
5
|
+
Requires-Dist: click>=8.3.1,<9
|
|
6
|
+
Requires-Dist: dycw-utilities>=0.176.1,<1
|
|
7
|
+
Requires-Dist: libcst>=1.8.6,<2
|
|
8
|
+
Requires-Dist: packaging>=25.0,<26
|
|
9
|
+
Requires-Dist: pyyaml>=6.0.3,<7
|
|
10
|
+
Requires-Dist: rich>=14.2.0,<15
|
|
11
|
+
Requires-Dist: tomlkit>=0.13.3,<1
|
|
12
|
+
Requires-Dist: typed-settings[attrs,click]>=25.3.0,<26
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# `actions`
|
|
17
|
+
|
|
18
|
+
GitHub actions
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
actions/__init__.py,sha256=lw2DhpYl61GrPl9-Oi7xYfsHKXdE7tIgKWgLrQAcRQg,59
|
|
2
|
+
actions/cli.py,sha256=SnlIyfHLjC8uZ6yLhg4S15nqOf9TT81J74rZOqvyqiM,929
|
|
3
|
+
actions/conformalize/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
4
|
+
actions/conformalize/defaults.py,sha256=tYYLr3aRRRvnR6NSADOJMN-B6gtqg2A9gBuheEPyGy4,189
|
|
5
|
+
actions/conformalize/dicts.py,sha256=_l_3y-07bjDq7n8xQgsykrwZ6dEFFIzm0_P3PZMcaRo,5630
|
|
6
|
+
actions/hooks/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
7
|
+
actions/hooks/cli.py,sha256=xV-2TgrDUhkKYbLRVtCglYCu8uWmh0leiYnob66rS5k,1141
|
|
8
|
+
actions/hooks/lib.py,sha256=QNszNiQKzdao-mOWfmwFLR9KaGsUTv46oPKqu3oCt8E,2882
|
|
9
|
+
actions/hooks/settings.py,sha256=gWuBgU5vdN98LM_t5qzJ9cra64M0qA3ULpQ7hmXGNS4,633
|
|
10
|
+
actions/logging.py,sha256=rMTcQMGndUHTCaXXtyOHt_VXUMhQGRHoN7okpoX0y5I,120
|
|
11
|
+
actions/publish/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
12
|
+
actions/publish/cli.py,sha256=OEJSe5dYW06Mmf5JzAthUOFm8K5ZbkwAsUcP6L1kLa0,1334
|
|
13
|
+
actions/publish/lib.py,sha256=0zkNFWGqJcC-2v-3YXWdhO4VcQCX9BCo6uKIOvAFLUw,1785
|
|
14
|
+
actions/publish/settings.py,sha256=mVu2jjQQyFH9TDsh30dscSSB1LlxY493mxRiw31mgQo,972
|
|
15
|
+
actions/requirements/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
16
|
+
actions/requirements/cli.py,sha256=I4Nd14TYnoA2iWbreQyiGcm4NSpstLIRFkOHO2ymX0M,887
|
|
17
|
+
actions/requirements/lib.py,sha256=ovZAolTQQAAMVabBxyWNuY13ENp8-vRnRt1ymFurVhQ,3709
|
|
18
|
+
actions/sequence_strs/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
19
|
+
actions/sequence_strs/cli.py,sha256=YMC8jNXfHjASwkIca3rED8FeE-jgGv5ek04F7G2bIJA,896
|
|
20
|
+
actions/sequence_strs/lib.py,sha256=F1YFX9nrcMWnfvDtnx4jEXIHiX7LgB3vQGfjOQsjKXA,2260
|
|
21
|
+
actions/settings.py,sha256=D-bSBoDn183sIs2ejSHGUXps3HLHuUXYbnbJ3ZHWFg8,423
|
|
22
|
+
actions/sleep/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
23
|
+
actions/sleep/cli.py,sha256=8fw8cr8BzaymJ87appXwk8ntrxqTUtMVxGNchS4qHjo,1181
|
|
24
|
+
actions/sleep/lib.py,sha256=y_Sxjv_KD_pamHyQySF96YQ5ETA-wHHKOOJq6OD8ub4,1791
|
|
25
|
+
actions/sleep/settings.py,sha256=1hMd5mUsFb6gLajdAZ8bvFYjDhE1BWiPSKOYiHJEvqM,556
|
|
26
|
+
actions/tag/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
27
|
+
actions/tag/cli.py,sha256=itaa71bML6zb91jBDei0npLclIoh8ZKbvFWNzMNRR1g,1231
|
|
28
|
+
actions/tag/lib.py,sha256=3pyhj-b81NWVRGSzoeNkCk8fcrtCjcWss5bMr9xFEeU,1842
|
|
29
|
+
actions/tag/settings.py,sha256=s6QBI4bZFjk2mIPKIlyb3sLET5ZpXsTb-2bzjW9m5Ew,646
|
|
30
|
+
actions/types.py,sha256=azBTLx4DER-Sd1qGCmbJSFs3uzNe9w-7iyKwT2h6iOk,199
|
|
31
|
+
actions/utilities.py,sha256=o-OKP9EbeQ2yG4tTM1eHIRhtF928n7E6YjJQPlbmtIQ,2426
|
|
32
|
+
dycw_actions-0.3.40.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
|
|
33
|
+
dycw_actions-0.3.40.dist-info/entry_points.txt,sha256=2Uu7wAZOm0mmcsGBEsGB370HAWgVWECRFJ9rKgfC3-I,46
|
|
34
|
+
dycw_actions-0.3.40.dist-info/METADATA,sha256=6WAaMT5QZbVNsKM6cGNexpqJuDetmlAMc-FQUq4uhn4,467
|
|
35
|
+
dycw_actions-0.3.40.dist-info/RECORD,,
|