pyallel 0.2.1__tar.gz → 0.3.0__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.
- {pyallel-0.2.1 → pyallel-0.3.0}/PKG-INFO +5 -3
- {pyallel-0.2.1 → pyallel-0.3.0}/README.md +4 -2
- {pyallel-0.2.1 → pyallel-0.3.0}/pyproject.toml +9 -2
- {pyallel-0.2.1 → pyallel-0.3.0}/src/pyallel/main.py +61 -97
- pyallel-0.3.0/src/pyallel/parser.py +63 -0
- pyallel-0.3.0/src/pyallel/py.typed +0 -0
- {pyallel-0.2.1 → pyallel-0.3.0}/LICENSE +0 -0
- {pyallel-0.2.1 → pyallel-0.3.0}/src/pyallel/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyallel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Run and handle the output of multiple executables in pyallel (as in parallel)
|
|
5
5
|
Home-page: https://github.com/Danthewaann/pyallel
|
|
6
6
|
License: MIT
|
|
@@ -50,8 +50,10 @@ pyallel "black --color --check --diff ." "mypy ." "ruff check --no-fix ."
|
|
|
50
50
|
command
|
|
51
51
|
- [ ] Add custom config file for `pyallel` to read from as an alternative to providing
|
|
52
52
|
arguments via the command line
|
|
53
|
-
- [ ] Add support for providing config via a `
|
|
54
|
-
working directory
|
|
53
|
+
- [ ] Add support for providing config via a `[tool.pyallel]` section in a
|
|
54
|
+
`pyproject.toml` file in the current working directory
|
|
55
|
+
- [ ] Maybe allow command dependencies to be defined in a python file where commands are
|
|
56
|
+
decorated with info that details it's dependencies?
|
|
55
57
|
- [ ] Add test suite
|
|
56
58
|
- [ ] Improve error handling when parsing provided commands (check they are valid executables)
|
|
57
59
|
- [ ] Add visual examples of `pyallel` in action
|
|
@@ -32,8 +32,10 @@ pyallel "black --color --check --diff ." "mypy ." "ruff check --no-fix ."
|
|
|
32
32
|
command
|
|
33
33
|
- [ ] Add custom config file for `pyallel` to read from as an alternative to providing
|
|
34
34
|
arguments via the command line
|
|
35
|
-
- [ ] Add support for providing config via a `
|
|
36
|
-
working directory
|
|
35
|
+
- [ ] Add support for providing config via a `[tool.pyallel]` section in a
|
|
36
|
+
`pyproject.toml` file in the current working directory
|
|
37
|
+
- [ ] Maybe allow command dependencies to be defined in a python file where commands are
|
|
38
|
+
decorated with info that details it's dependencies?
|
|
37
39
|
- [ ] Add test suite
|
|
38
40
|
- [ ] Improve error handling when parsing provided commands (check they are valid executables)
|
|
39
41
|
- [ ] Add visual examples of `pyallel` in action
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pyallel"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Run and handle the output of multiple executables in pyallel (as in parallel)"
|
|
5
5
|
authors = ["Daniel Black <danielcrblack@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -16,15 +16,22 @@ classifiers = [
|
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
[tool.poetry.scripts]
|
|
19
|
-
pyallel = "pyallel.main:
|
|
19
|
+
pyallel = "pyallel.main:entry_point"
|
|
20
20
|
|
|
21
21
|
[tool.poetry.dependencies]
|
|
22
22
|
python = ">=3.11,<4.0"
|
|
23
23
|
|
|
24
|
+
[tool.poetry.group.dev.dependencies]
|
|
25
|
+
pytest = "^7.4.3"
|
|
26
|
+
mypy = "^1.7.0"
|
|
27
|
+
|
|
24
28
|
[tool.mypy]
|
|
25
29
|
strict = true
|
|
26
30
|
show_error_codes = true
|
|
27
31
|
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
addopts = "-vvv"
|
|
34
|
+
|
|
28
35
|
[build-system]
|
|
29
36
|
requires = ["poetry-core"]
|
|
30
37
|
build-backend = "poetry.core.masonry.api"
|
|
@@ -1,42 +1,41 @@
|
|
|
1
|
-
import argparse
|
|
2
1
|
import os
|
|
3
2
|
import sys
|
|
4
3
|
import time
|
|
5
|
-
from datetime import timedelta
|
|
6
4
|
from dataclasses import dataclass
|
|
5
|
+
import shlex
|
|
7
6
|
import importlib.metadata
|
|
8
7
|
import subprocess
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
from pyallel.parser import Arguments, create_parser
|
|
10
|
+
|
|
11
|
+
IN_TTY = sys.__stdin__.isatty()
|
|
12
|
+
|
|
13
|
+
if IN_TTY:
|
|
14
|
+
WHITE_BOLD = "\033[1m"
|
|
15
|
+
GREEN_BOLD = "\033[1;32m"
|
|
16
|
+
BLUE_BOLD = "\033[1;34m"
|
|
17
|
+
RED_BOLD = "\033[1;31m"
|
|
18
|
+
CLEAR_LINE = "\033[2K"
|
|
19
|
+
UP_LINE = "\033[1F"
|
|
20
|
+
NC = "\033[0m"
|
|
21
|
+
CR = "\r"
|
|
22
|
+
else:
|
|
23
|
+
WHITE_BOLD = ""
|
|
24
|
+
GREEN_BOLD = ""
|
|
25
|
+
BLUE_BOLD = ""
|
|
26
|
+
RED_BOLD = ""
|
|
27
|
+
CLEAR_LINE = ""
|
|
28
|
+
UP_LINE = ""
|
|
29
|
+
NC = ""
|
|
30
|
+
CR = ""
|
|
31
|
+
|
|
18
32
|
|
|
33
|
+
ICONS = ("/", "-", "\\", "|")
|
|
19
34
|
# Unicode character bytes to render different symbols in the terminal
|
|
20
35
|
TICK = "\u2713"
|
|
21
36
|
X = "\u2717"
|
|
22
37
|
|
|
23
38
|
|
|
24
|
-
class Arguments:
|
|
25
|
-
commands: list[str]
|
|
26
|
-
fail_fast: bool
|
|
27
|
-
interactive: bool
|
|
28
|
-
debug: bool
|
|
29
|
-
verbose: bool
|
|
30
|
-
version: bool
|
|
31
|
-
|
|
32
|
-
def __repr__(self) -> str:
|
|
33
|
-
msg = "Arguments:\n"
|
|
34
|
-
padding = len(sorted(self.__dict__.keys(), key=len, reverse=True)[0]) + 1
|
|
35
|
-
for field, value in self.__dict__.items():
|
|
36
|
-
msg += f" {field: <{padding}}: {value}\n"
|
|
37
|
-
return msg
|
|
38
|
-
|
|
39
|
-
|
|
40
39
|
@dataclass
|
|
41
40
|
class Process:
|
|
42
41
|
name: str
|
|
@@ -46,7 +45,8 @@ class Process:
|
|
|
46
45
|
|
|
47
46
|
|
|
48
47
|
def run_command(command: str) -> Process:
|
|
49
|
-
executable, *args = command.split()
|
|
48
|
+
executable, *args = command.split(maxsplit=1)
|
|
49
|
+
args = shlex.split(" ".join(args))
|
|
50
50
|
env = os.environ.copy()
|
|
51
51
|
start = time.perf_counter()
|
|
52
52
|
process = subprocess.Popen(
|
|
@@ -60,52 +60,6 @@ def run_command(command: str) -> Process:
|
|
|
60
60
|
return Process(name=executable, args=args, start=start, process=process)
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def create_parser() -> argparse.ArgumentParser:
|
|
64
|
-
parser = argparse.ArgumentParser(
|
|
65
|
-
prog="pyallel",
|
|
66
|
-
description="Run and handle the output of multiple executables in pyallel (as in parallel)",
|
|
67
|
-
)
|
|
68
|
-
parser.add_argument("commands", help="list of commands to run", nargs="*")
|
|
69
|
-
parser.add_argument(
|
|
70
|
-
"-f",
|
|
71
|
-
"--fail-fast",
|
|
72
|
-
help="exit immediately when a command fails",
|
|
73
|
-
action="store_true",
|
|
74
|
-
default=False,
|
|
75
|
-
)
|
|
76
|
-
parser.add_argument(
|
|
77
|
-
"-n",
|
|
78
|
-
"--non-interactive",
|
|
79
|
-
help="run in non-interactive mode",
|
|
80
|
-
action="store_false",
|
|
81
|
-
dest="interactive",
|
|
82
|
-
default=True,
|
|
83
|
-
)
|
|
84
|
-
parser.add_argument(
|
|
85
|
-
"-d",
|
|
86
|
-
"--debug",
|
|
87
|
-
help="output debug info for each command",
|
|
88
|
-
action="store_true",
|
|
89
|
-
default=False,
|
|
90
|
-
)
|
|
91
|
-
parser.add_argument(
|
|
92
|
-
"-V",
|
|
93
|
-
"--verbose",
|
|
94
|
-
help="run in verbose mode",
|
|
95
|
-
action="store_true",
|
|
96
|
-
default=False,
|
|
97
|
-
)
|
|
98
|
-
parser.add_argument(
|
|
99
|
-
"-v",
|
|
100
|
-
"--version",
|
|
101
|
-
help="print version and exit",
|
|
102
|
-
action="store_true",
|
|
103
|
-
default=False,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
return parser
|
|
107
|
-
|
|
108
|
-
|
|
109
63
|
def run_commands(commands: list[str]) -> list[Process]:
|
|
110
64
|
return [run_command(command) for command in commands]
|
|
111
65
|
|
|
@@ -127,13 +81,13 @@ def format_time_taken(time_taken: float) -> str:
|
|
|
127
81
|
elif 60 <= time_taken < 3600:
|
|
128
82
|
msg = f"{minutes}m"
|
|
129
83
|
if seconds:
|
|
130
|
-
msg += f" {seconds}s"
|
|
84
|
+
msg += f" {seconds}s"
|
|
131
85
|
elif time_taken >= 3600:
|
|
132
86
|
msg = f"{hour}h"
|
|
133
87
|
if minutes:
|
|
134
|
-
msg += f" {minutes}m"
|
|
88
|
+
msg += f" {minutes}m"
|
|
135
89
|
if seconds:
|
|
136
|
-
msg += f" {seconds}s"
|
|
90
|
+
msg += f" {seconds}s"
|
|
137
91
|
|
|
138
92
|
return msg
|
|
139
93
|
|
|
@@ -179,20 +133,22 @@ def main_loop(
|
|
|
179
133
|
completed_processes: set[str] = set()
|
|
180
134
|
passed = True
|
|
181
135
|
|
|
182
|
-
if not interactive:
|
|
136
|
+
if not interactive or not IN_TTY:
|
|
183
137
|
print(f"{WHITE_BOLD}Running commands...{NC}\n")
|
|
184
138
|
|
|
185
139
|
while True:
|
|
186
|
-
if interactive:
|
|
140
|
+
if interactive and IN_TTY:
|
|
187
141
|
for icon in ICONS:
|
|
188
|
-
print(
|
|
142
|
+
print(
|
|
143
|
+
f"{CLEAR_LINE}{CR}{WHITE_BOLD}Running commands{NC} {icon}", end=""
|
|
144
|
+
)
|
|
189
145
|
time.sleep(0.1)
|
|
190
146
|
|
|
191
147
|
for process in processes:
|
|
192
148
|
if process.name in completed_processes or process.process.poll() is None:
|
|
193
149
|
continue
|
|
194
150
|
|
|
195
|
-
print(f"
|
|
151
|
+
print(f"{CLEAR_LINE}{CR}", end="")
|
|
196
152
|
completed_processes.add(process.name)
|
|
197
153
|
|
|
198
154
|
if process.process.returncode != 0:
|
|
@@ -212,35 +168,43 @@ def main_loop(
|
|
|
212
168
|
return passed
|
|
213
169
|
|
|
214
170
|
|
|
215
|
-
def run() ->
|
|
171
|
+
def run(*args: str) -> int:
|
|
216
172
|
parser = create_parser()
|
|
217
|
-
|
|
218
|
-
|
|
173
|
+
parsed_args = parser.parse_args(args=args, namespace=Arguments())
|
|
174
|
+
|
|
175
|
+
if parsed_args.version:
|
|
219
176
|
my_version = importlib.metadata.version("pyallel")
|
|
220
177
|
print(my_version)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if not sys.stdin.isatty():
|
|
224
|
-
print(sys.stdin.read())
|
|
178
|
+
return 0
|
|
225
179
|
|
|
226
|
-
if
|
|
227
|
-
print(
|
|
180
|
+
if parsed_args.verbose:
|
|
181
|
+
print(parsed_args)
|
|
228
182
|
|
|
229
|
-
if not
|
|
183
|
+
if not parsed_args.commands:
|
|
230
184
|
parser.print_help()
|
|
231
|
-
|
|
185
|
+
return 2
|
|
232
186
|
|
|
233
|
-
|
|
187
|
+
start = time.perf_counter()
|
|
234
188
|
|
|
235
189
|
exit_code = 0
|
|
236
|
-
status = main_loop(
|
|
190
|
+
status = main_loop(
|
|
191
|
+
parsed_args.commands,
|
|
192
|
+
parsed_args.fail_fast,
|
|
193
|
+
parsed_args.interactive,
|
|
194
|
+
parsed_args.debug,
|
|
195
|
+
)
|
|
237
196
|
if not status:
|
|
238
197
|
print(f"{RED_BOLD}A command failed!{NC}")
|
|
239
198
|
exit_code = 1
|
|
240
199
|
else:
|
|
241
200
|
print(f"{GREEN_BOLD}Success!{NC}")
|
|
242
201
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
print(f"\nTime taken : {format_time_taken(
|
|
246
|
-
|
|
202
|
+
if parsed_args.debug:
|
|
203
|
+
elapsed = time.perf_counter() - start
|
|
204
|
+
print(f"\nTime taken : {format_time_taken(elapsed)}")
|
|
205
|
+
|
|
206
|
+
return exit_code
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def entry_point() -> None:
|
|
210
|
+
sys.exit(run(*sys.argv[1:]))
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from argparse import ArgumentParser
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Arguments:
|
|
5
|
+
commands: list[str]
|
|
6
|
+
fail_fast: bool
|
|
7
|
+
interactive: bool
|
|
8
|
+
debug: bool
|
|
9
|
+
verbose: bool
|
|
10
|
+
version: bool
|
|
11
|
+
|
|
12
|
+
def __repr__(self) -> str:
|
|
13
|
+
msg = "Arguments:\n"
|
|
14
|
+
padding = len(sorted(self.__dict__.keys(), key=len, reverse=True)[0]) + 1
|
|
15
|
+
for field, value in self.__dict__.items():
|
|
16
|
+
msg += f" {field: <{padding}}: {value}\n"
|
|
17
|
+
return msg
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def create_parser() -> ArgumentParser:
|
|
21
|
+
parser = ArgumentParser(
|
|
22
|
+
prog="pyallel",
|
|
23
|
+
description="Run and handle the output of multiple executables in pyallel (as in parallel)",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument("commands", help="list of commands to run", nargs="*")
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"-f",
|
|
28
|
+
"--fail-fast",
|
|
29
|
+
help="exit immediately when a command fails",
|
|
30
|
+
action="store_true",
|
|
31
|
+
default=False,
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"-n",
|
|
35
|
+
"--non-interactive",
|
|
36
|
+
help="run in non-interactive mode",
|
|
37
|
+
action="store_false",
|
|
38
|
+
dest="interactive",
|
|
39
|
+
default=True,
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"-d",
|
|
43
|
+
"--debug",
|
|
44
|
+
help="output debug info for each command",
|
|
45
|
+
action="store_true",
|
|
46
|
+
default=False,
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"-V",
|
|
50
|
+
"--verbose",
|
|
51
|
+
help="run in verbose mode",
|
|
52
|
+
action="store_true",
|
|
53
|
+
default=False,
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"-v",
|
|
57
|
+
"--version",
|
|
58
|
+
help="print version and exit",
|
|
59
|
+
action="store_true",
|
|
60
|
+
default=False,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return parser
|
|
File without changes
|
|
File without changes
|
|
File without changes
|