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
+ [![Python](https://img.shields.io/badge/Python-3.11+-blue.svg)](https://www.python.org/downloads/)
12
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
13
+ [![tests](https://github.com/billwallis/py-wait-until/actions/workflows/tests.yaml/badge.svg)](https://github.com/billwallis/py-wait-until/actions/workflows/tests.yaml)
14
+ [![coverage](https://raw.githubusercontent.com/billwallis/py-wait-until/refs/heads/main/coverage.svg)](https://github.com/dbrgn/coverage-badge)
15
+
16
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
17
+ [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/billwallis/py-wait-until/main.svg)](https://results.pre-commit.ci/latest/github/billwallis/py-wait-until/main)
18
+ [![GitHub last commit](https://img.shields.io/github/last-commit/billwallis/py-wait-until)](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
+ [![Python](https://img.shields.io/badge/Python-3.11+-blue.svg)](https://www.python.org/downloads/)
4
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
5
+ [![tests](https://github.com/billwallis/py-wait-until/actions/workflows/tests.yaml/badge.svg)](https://github.com/billwallis/py-wait-until/actions/workflows/tests.yaml)
6
+ [![coverage](https://raw.githubusercontent.com/billwallis/py-wait-until/refs/heads/main/coverage.svg)](https://github.com/dbrgn/coverage-badge)
7
+
8
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
9
+ [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/billwallis/py-wait-until/main.svg)](https://results.pre-commit.ci/latest/github/billwallis/py-wait-until/main)
10
+ [![GitHub last commit](https://img.shields.io/github/last-commit/billwallis/py-wait-until)](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