py-wait-until 0.0.1__tar.gz
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.
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: py-wait-until
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Print a loading spinner while another process is running.
|
|
5
|
+
Author: billwallis
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
<span align="center">
|
|
10
|
+
|
|
11
|
+
[](https://www.python.org/downloads/)
|
|
12
|
+
[](https://github.com/astral-sh/uv)
|
|
13
|
+
[](https://github.com/billwallis/py-wait-until/actions/workflows/tests.yaml)
|
|
14
|
+
[](https://github.com/dbrgn/coverage-badge)
|
|
15
|
+
|
|
16
|
+
[](https://github.com/astral-sh/ruff)
|
|
17
|
+
[](https://results.pre-commit.ci/latest/github/billwallis/py-wait-until/main)
|
|
18
|
+
[](https://shields.io/badges/git-hub-last-commit)
|
|
19
|
+
|
|
20
|
+
</span>
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Wait Until
|
|
25
|
+
|
|
26
|
+
Print a loading spinner while another process is running.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
Simply prepend `py-wait-until` to any command:
|
|
31
|
+
|
|
32
|
+
```shell
|
|
33
|
+
py-wait-until python -c "import time; time.sleep(2)"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Contributing
|
|
37
|
+
|
|
38
|
+
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then install the dependencies:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uvx --from poethepoet poe install
|
|
42
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<span align="center">
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](https://github.com/astral-sh/uv)
|
|
5
|
+
[](https://github.com/billwallis/py-wait-until/actions/workflows/tests.yaml)
|
|
6
|
+
[](https://github.com/dbrgn/coverage-badge)
|
|
7
|
+
|
|
8
|
+
[](https://github.com/astral-sh/ruff)
|
|
9
|
+
[](https://results.pre-commit.ci/latest/github/billwallis/py-wait-until/main)
|
|
10
|
+
[](https://shields.io/badges/git-hub-last-commit)
|
|
11
|
+
|
|
12
|
+
</span>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Wait Until
|
|
17
|
+
|
|
18
|
+
Print a loading spinner while another process is running.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Simply prepend `py-wait-until` to any command:
|
|
23
|
+
|
|
24
|
+
```shell
|
|
25
|
+
py-wait-until python -c "import time; time.sleep(2)"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Contributing
|
|
29
|
+
|
|
30
|
+
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then install the dependencies:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uvx --from poethepoet poe install
|
|
34
|
+
```
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://json.schemastore.org/pyproject.json
|
|
2
|
+
|
|
3
|
+
[build-system]
|
|
4
|
+
requires = ["uv_build>=0.8.2,<1.0"]
|
|
5
|
+
build-backend = "uv_build"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "py-wait-until"
|
|
9
|
+
version = "0.0.1"
|
|
10
|
+
description = "Print a loading spinner while another process is running."
|
|
11
|
+
authors = [{name = "billwallis"}]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.13"
|
|
14
|
+
dependencies = []
|
|
15
|
+
|
|
16
|
+
[project.scripts]
|
|
17
|
+
py-wait-until = "py_wait_until.main:main"
|
|
18
|
+
|
|
19
|
+
[dependency-groups]
|
|
20
|
+
dev = [
|
|
21
|
+
"coverage-badge>=1.1.2",
|
|
22
|
+
"poethepoet>=0.37.0",
|
|
23
|
+
"pre-commit>=4.3.0",
|
|
24
|
+
]
|
|
25
|
+
test = [
|
|
26
|
+
"pytest>=8.4.2",
|
|
27
|
+
"pytest-cov>=7.0.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
[tool.uv]
|
|
32
|
+
required-version = ">=0.8,<1.0"
|
|
33
|
+
default-groups = "all"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
[tool.poe]
|
|
37
|
+
verbosity = -1
|
|
38
|
+
|
|
39
|
+
[tool.poe.tasks]
|
|
40
|
+
install.shell = """
|
|
41
|
+
uv sync --refresh
|
|
42
|
+
pre-commit install --install-hooks
|
|
43
|
+
"""
|
|
44
|
+
update.shell = """
|
|
45
|
+
uv sync --refresh --upgrade
|
|
46
|
+
pre-commit autoupdate
|
|
47
|
+
pre-commit install --install-hooks
|
|
48
|
+
"""
|
|
49
|
+
test = "uv run pytest -vvv"
|
|
50
|
+
|
|
51
|
+
[tool.poe.tasks.lint]
|
|
52
|
+
control.expr = "sys.platform"
|
|
53
|
+
|
|
54
|
+
[[tool.poe.tasks.lint.switch]]
|
|
55
|
+
case = "win32"
|
|
56
|
+
shell = """
|
|
57
|
+
echo '=== pre-commit checks ==='
|
|
58
|
+
SKIP=identity pre-commit run --all-files --hook-stage pre-commit
|
|
59
|
+
echo '=== pre-push checks ==='
|
|
60
|
+
pre-commit run --all-files --hook-stage pre-push
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
[[tool.poe.tasks.lint.switch]]
|
|
64
|
+
shell = """
|
|
65
|
+
echo '\\033[0;34mpre-commit checks\\033[0m'
|
|
66
|
+
SKIP=identity pre-commit run --all-files --hook-stage pre-commit
|
|
67
|
+
echo '\\033[0;34mpre-push checks\\033[0m'
|
|
68
|
+
pre-commit run --all-files --hook-stage pre-push
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
addopts = "--cov=src --cov-fail-under=80"
|
|
74
|
+
testpaths = ["tests"]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
[tool.ruff]
|
|
78
|
+
line-length = 80
|
|
79
|
+
indent-width = 4
|
|
80
|
+
target-version = "py313"
|
|
81
|
+
|
|
82
|
+
[tool.ruff.format]
|
|
83
|
+
quote-style = "double"
|
|
84
|
+
indent-style = "space"
|
|
85
|
+
skip-magic-trailing-comma = false
|
|
86
|
+
line-ending = "auto"
|
|
87
|
+
|
|
88
|
+
[tool.ruff.lint]
|
|
89
|
+
select = [
|
|
90
|
+
"ANN", # flake8-annotations
|
|
91
|
+
"B", # flake8-bugbear
|
|
92
|
+
"E", # pycodestyle-error
|
|
93
|
+
"F", # Pyflakes
|
|
94
|
+
"FURB", # refurb
|
|
95
|
+
"I", # isort
|
|
96
|
+
"N", # pep8-naming
|
|
97
|
+
"PL", # Pylint
|
|
98
|
+
"PERF", # Perflint
|
|
99
|
+
"RUF", # Ruff-specific
|
|
100
|
+
"S", # flake8-bandit
|
|
101
|
+
"UP", # pyupgrade
|
|
102
|
+
"W", # pycodestyle-warning
|
|
103
|
+
]
|
|
104
|
+
ignore = [
|
|
105
|
+
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed
|
|
106
|
+
"E501", # Line too long
|
|
107
|
+
]
|
|
108
|
+
fixable = ["ALL"]
|
|
109
|
+
unfixable = []
|
|
110
|
+
# Allow unused variables when underscore-prefixed
|
|
111
|
+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
112
|
+
|
|
113
|
+
# https://github.com/astral-sh/ruff/issues/4368
|
|
114
|
+
[tool.ruff.lint.extend-per-file-ignores]
|
|
115
|
+
"tests/**/*.py" = [
|
|
116
|
+
"ANN001", # Missing type annotation for function argument
|
|
117
|
+
"ANN201", # Missing return type annotation for public function
|
|
118
|
+
"S101", # Use of `assert` detected
|
|
119
|
+
"PLR2004", # Magic value used in comparison
|
|
120
|
+
"PLR0913", # Too many arguments in function definition
|
|
121
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
from collections.abc import Generator, Sequence
|
|
6
|
+
from typing import TextIO
|
|
7
|
+
|
|
8
|
+
WAIT_DURATION_SECONDS = 0.1
|
|
9
|
+
ANSI_CLEAR_LINE = "\033[2K"
|
|
10
|
+
ANSI_HIDE_CURSOR = "\033[?25l"
|
|
11
|
+
ANSI_SHOW_CURSOR = "\033[?25h"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _write_line(stream: TextIO, message: str) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Write and flush a line to the given stream.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
stream.write(message)
|
|
20
|
+
stream.flush()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _spinner() -> Generator[str]:
|
|
24
|
+
"""
|
|
25
|
+
Return an infinite generator that yields spinner characters.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
chars = ["|", "/", "-", "\\"]
|
|
29
|
+
index = -1
|
|
30
|
+
while True:
|
|
31
|
+
index += 1
|
|
32
|
+
yield chars[index % len(chars)]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _wait_for_process(cmd: list[str]) -> int:
|
|
36
|
+
"""
|
|
37
|
+
Execute a command and display a waiting message until it completes.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# Only show spinner if stdout is a TTY (interactive terminal)
|
|
41
|
+
if is_tty := sys.stdout.isatty():
|
|
42
|
+
_write_line(sys.stdout, ANSI_HIDE_CURSOR)
|
|
43
|
+
|
|
44
|
+
process = subprocess.Popen( # noqa: S603
|
|
45
|
+
cmd,
|
|
46
|
+
stdout=subprocess.PIPE,
|
|
47
|
+
stderr=subprocess.PIPE,
|
|
48
|
+
text=True,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
spin = _spinner()
|
|
52
|
+
while process.poll() is None:
|
|
53
|
+
if is_tty:
|
|
54
|
+
_write_line(
|
|
55
|
+
sys.stdout,
|
|
56
|
+
f"\rWaiting for process to finish... {next(spin)}",
|
|
57
|
+
)
|
|
58
|
+
time.sleep(WAIT_DURATION_SECONDS)
|
|
59
|
+
|
|
60
|
+
if is_tty:
|
|
61
|
+
_write_line(sys.stdout, f"\r{ANSI_CLEAR_LINE}")
|
|
62
|
+
_write_line(sys.stdout, ANSI_SHOW_CURSOR)
|
|
63
|
+
|
|
64
|
+
stdout, stderr = process.communicate()
|
|
65
|
+
if stdout:
|
|
66
|
+
_write_line(sys.stdout, stdout)
|
|
67
|
+
if stderr:
|
|
68
|
+
_write_line(sys.stderr, stderr)
|
|
69
|
+
|
|
70
|
+
return process.returncode
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
74
|
+
"""
|
|
75
|
+
Wait until a command completes.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
parser = argparse.ArgumentParser(
|
|
79
|
+
description="Wait until a command completes.",
|
|
80
|
+
)
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
"command",
|
|
83
|
+
nargs=argparse.REMAINDER,
|
|
84
|
+
help="Command and arguments to execute",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
args = parser.parse_args(argv)
|
|
88
|
+
if not args.command:
|
|
89
|
+
parser.print_help()
|
|
90
|
+
return 1
|
|
91
|
+
|
|
92
|
+
return _wait_for_process(args.command)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
raise SystemExit(main()) # pragma: no cover
|