pyallel 0.3.4__tar.gz → 0.4.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.3.4 → pyallel-0.4.0}/PKG-INFO +2 -1
- {pyallel-0.3.4 → pyallel-0.4.0}/README.md +1 -0
- {pyallel-0.3.4 → pyallel-0.4.0}/pyproject.toml +1 -1
- {pyallel-0.3.4 → pyallel-0.4.0}/src/pyallel/main.py +6 -52
- pyallel-0.4.0/src/pyallel/process.py +63 -0
- {pyallel-0.3.4 → pyallel-0.4.0}/LICENSE +0 -0
- {pyallel-0.3.4 → pyallel-0.4.0}/src/pyallel/__init__.py +0 -0
- {pyallel-0.3.4 → pyallel-0.4.0}/src/pyallel/errors.py +0 -0
- {pyallel-0.3.4 → pyallel-0.4.0}/src/pyallel/parser.py +0 -0
- {pyallel-0.3.4 → pyallel-0.4.0}/src/pyallel/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyallel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -53,6 +53,7 @@ pyallel "black --color --check --diff ." "mypy ." "ruff check --no-fix ."
|
|
|
53
53
|
- [ ] Allow a single main command output to be streamed to stdout, while all other
|
|
54
54
|
commands will only get outputted after the main command has completed (such as running
|
|
55
55
|
`pytest` as the main command, whilst running `mypy`, `ruff` etc. as other commands)
|
|
56
|
+
- [x] Provide a way to set environment variables for each command to run with
|
|
56
57
|
- [ ] Allow list of files to be provided to supply as input arguments to each command
|
|
57
58
|
- [ ] Allow input to be piped into `pyallel` via stdin to supply as standard input to each
|
|
58
59
|
command
|
|
@@ -32,6 +32,7 @@ pyallel "black --color --check --diff ." "mypy ." "ruff check --no-fix ."
|
|
|
32
32
|
- [ ] Allow a single main command output to be streamed to stdout, while all other
|
|
33
33
|
commands will only get outputted after the main command has completed (such as running
|
|
34
34
|
`pytest` as the main command, whilst running `mypy`, `ruff` etc. as other commands)
|
|
35
|
+
- [x] Provide a way to set environment variables for each command to run with
|
|
35
36
|
- [ ] Allow list of files to be provided to supply as input arguments to each command
|
|
36
37
|
- [ ] Allow input to be piped into `pyallel` via stdin to supply as standard input to each
|
|
37
38
|
command
|
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
import io
|
|
3
2
|
|
|
4
|
-
import os
|
|
5
3
|
import sys
|
|
6
4
|
import time
|
|
7
|
-
import shutil
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
import shlex
|
|
10
5
|
import importlib.metadata
|
|
11
|
-
import subprocess
|
|
12
|
-
from typing import IO
|
|
13
6
|
|
|
14
7
|
from pyallel.errors import InvalidExecutableErrors, InvalidExecutableError
|
|
15
8
|
from pyallel.parser import Arguments, create_parser
|
|
9
|
+
from pyallel.process import Process
|
|
16
10
|
|
|
17
11
|
IN_TTY = sys.__stdin__.isatty()
|
|
18
12
|
|
|
@@ -42,57 +36,13 @@ TICK = "\u2713"
|
|
|
42
36
|
X = "\u2717"
|
|
43
37
|
|
|
44
38
|
|
|
45
|
-
@dataclass
|
|
46
|
-
class Process:
|
|
47
|
-
name: str
|
|
48
|
-
args: list[str]
|
|
49
|
-
start: float = 0.0
|
|
50
|
-
process: subprocess.Popen[bytes] | None = None
|
|
51
|
-
|
|
52
|
-
def run(self) -> None:
|
|
53
|
-
env = os.environ.copy()
|
|
54
|
-
# TODO: need to provide a way to supply environment variablesreturn
|
|
55
|
-
# for each provided command
|
|
56
|
-
env["MYPY_FORCE_COLOR"] = "1" if IN_TTY else "0"
|
|
57
|
-
self.start = time.perf_counter()
|
|
58
|
-
self.process = subprocess.Popen(
|
|
59
|
-
[self.name, *self.args],
|
|
60
|
-
stdout=subprocess.PIPE,
|
|
61
|
-
stderr=subprocess.STDOUT,
|
|
62
|
-
env=env,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
def poll(self) -> int | None:
|
|
66
|
-
if self.process:
|
|
67
|
-
return self.process.poll()
|
|
68
|
-
return None
|
|
69
|
-
|
|
70
|
-
def stdout(self) -> IO[bytes]:
|
|
71
|
-
if self.process and self.process.stdout:
|
|
72
|
-
return self.process.stdout
|
|
73
|
-
return io.BytesIO(b"")
|
|
74
|
-
|
|
75
|
-
def return_code(self) -> int | None:
|
|
76
|
-
if self.process:
|
|
77
|
-
return self.process.returncode
|
|
78
|
-
return None
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def parse_command(command: str) -> Process:
|
|
82
|
-
executable, *args = command.split(maxsplit=1)
|
|
83
|
-
if not shutil.which(executable):
|
|
84
|
-
raise InvalidExecutableError(executable)
|
|
85
|
-
args = shlex.split(" ".join(args))
|
|
86
|
-
return Process(name=executable, args=args)
|
|
87
|
-
|
|
88
|
-
|
|
89
39
|
def run_commands(commands: list[str]) -> list[Process]:
|
|
90
40
|
processes: list[Process] = []
|
|
91
41
|
errors: list[InvalidExecutableError] = []
|
|
92
42
|
|
|
93
43
|
for command in commands:
|
|
94
44
|
try:
|
|
95
|
-
processes.append(
|
|
45
|
+
processes.append(Process.from_command(command))
|
|
96
46
|
except InvalidExecutableError as e:
|
|
97
47
|
errors.append(e)
|
|
98
48
|
|
|
@@ -257,3 +207,7 @@ def run(*args: str) -> int:
|
|
|
257
207
|
|
|
258
208
|
def entry_point() -> None:
|
|
259
209
|
sys.exit(run(*sys.argv[1:]))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
entry_point()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import subprocess
|
|
5
|
+
import shlex
|
|
6
|
+
import io
|
|
7
|
+
import shutil
|
|
8
|
+
import os
|
|
9
|
+
from typing import IO
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pyallel.errors import InvalidExecutableError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Process:
|
|
17
|
+
name: str
|
|
18
|
+
args: list[str]
|
|
19
|
+
env: dict[str, str] = field(default_factory=dict)
|
|
20
|
+
start: float = 0.0
|
|
21
|
+
process: subprocess.Popen[bytes] | None = None
|
|
22
|
+
|
|
23
|
+
def run(self) -> None:
|
|
24
|
+
self.start = time.perf_counter()
|
|
25
|
+
self.process = subprocess.Popen(
|
|
26
|
+
[self.name, *self.args],
|
|
27
|
+
stdout=subprocess.PIPE,
|
|
28
|
+
stderr=subprocess.STDOUT,
|
|
29
|
+
env=self.env,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def poll(self) -> int | None:
|
|
33
|
+
if self.process:
|
|
34
|
+
return self.process.poll()
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
def stdout(self) -> IO[bytes]:
|
|
38
|
+
if self.process and self.process.stdout:
|
|
39
|
+
return self.process.stdout
|
|
40
|
+
return io.BytesIO(b"")
|
|
41
|
+
|
|
42
|
+
def return_code(self) -> int | None:
|
|
43
|
+
if self.process:
|
|
44
|
+
return self.process.returncode
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_command(cls, command: str) -> Process:
|
|
49
|
+
env = os.environ.copy()
|
|
50
|
+
args = command.split()
|
|
51
|
+
parsed_args: list[str] = []
|
|
52
|
+
for arg in args:
|
|
53
|
+
if "=" in arg:
|
|
54
|
+
name, value = arg.split("=")
|
|
55
|
+
env[name] = value
|
|
56
|
+
else:
|
|
57
|
+
parsed_args.append(arg)
|
|
58
|
+
|
|
59
|
+
if not shutil.which(parsed_args[0]):
|
|
60
|
+
raise InvalidExecutableError(parsed_args[0])
|
|
61
|
+
|
|
62
|
+
args = shlex.split(" ".join(parsed_args[1:]))
|
|
63
|
+
return Process(name=parsed_args[0], args=args, env=env)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|