pyallel 0.3.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyallel
3
- Version: 0.3.3
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
@@ -63,6 +64,6 @@ pyallel "black --color --check --diff ." "mypy ." "ruff check --no-fix ."
63
64
  - [ ] Maybe allow command dependencies to be defined in a python file where commands are
64
65
  decorated with info that details it's dependencies?
65
66
  - [x] Add test suite
66
- - [ ] Improve error handling when parsing provided commands (check they are valid executables)
67
+ - [x] Improve error handling when parsing provided commands (check they are valid executables)
67
68
  - [ ] Add visual examples of `pyallel` in action
68
69
 
@@ -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
@@ -42,5 +43,5 @@ pyallel "black --color --check --diff ." "mypy ." "ruff check --no-fix ."
42
43
  - [ ] Maybe allow command dependencies to be defined in a python file where commands are
43
44
  decorated with info that details it's dependencies?
44
45
  - [x] Add test suite
45
- - [ ] Improve error handling when parsing provided commands (check they are valid executables)
46
+ - [x] Improve error handling when parsing provided commands (check they are valid executables)
46
47
  - [ ] Add visual examples of `pyallel` in action
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pyallel"
3
- version = "0.3.3"
3
+ version = "0.4.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"
@@ -1,16 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
4
3
  import sys
5
4
  import time
6
- import shutil
7
- from dataclasses import dataclass
8
- import shlex
9
5
  import importlib.metadata
10
- import subprocess
11
6
 
12
7
  from pyallel.errors import InvalidExecutableErrors, InvalidExecutableError
13
8
  from pyallel.parser import Arguments, create_parser
9
+ from pyallel.process import Process
14
10
 
15
11
  IN_TTY = sys.__stdin__.isatty()
16
12
 
@@ -40,46 +36,22 @@ TICK = "\u2713"
40
36
  X = "\u2717"
41
37
 
42
38
 
43
- @dataclass
44
- class Process:
45
- name: str
46
- args: list[str]
47
- start: float
48
- process: subprocess.Popen[bytes]
49
-
50
-
51
- def run_command(command: str) -> Process:
52
- executable, *args = command.split(maxsplit=1)
53
- if not shutil.which(executable):
54
- raise InvalidExecutableError(executable)
55
- args = shlex.split(" ".join(args))
56
- env = os.environ.copy()
57
- # TODO: need to provide a way to supply environment variables
58
- # for each provided command
59
- env["MYPY_FORCE_COLOR"] = "1" if IN_TTY else "0"
60
- start = time.perf_counter()
61
- process = subprocess.Popen(
62
- [executable, *args],
63
- stdout=subprocess.PIPE,
64
- stderr=subprocess.STDOUT,
65
- env=env,
66
- )
67
- return Process(name=executable, args=args, start=start, process=process)
68
-
69
-
70
39
  def run_commands(commands: list[str]) -> list[Process]:
71
40
  processes: list[Process] = []
72
41
  errors: list[InvalidExecutableError] = []
73
42
 
74
43
  for command in commands:
75
44
  try:
76
- processes.append(run_command(command))
45
+ processes.append(Process.from_command(command))
77
46
  except InvalidExecutableError as e:
78
47
  errors.append(e)
79
48
 
80
49
  if errors:
81
50
  raise InvalidExecutableErrors(*errors)
82
51
 
52
+ for process in processes:
53
+ process.run()
54
+
83
55
  return processes
84
56
 
85
57
 
@@ -135,10 +107,9 @@ def print_command_status(process: Process, passed: bool, debug: bool = False) ->
135
107
 
136
108
 
137
109
  def print_command_output(process: Process) -> None:
138
- if process.process.stdout:
139
- output = process.process.stdout.read()
140
- if output:
141
- print(f"{indent(output.decode())}")
110
+ output = process.stdout().read()
111
+ if output:
112
+ print(f"{indent(output.decode())}")
142
113
  print()
143
114
 
144
115
 
@@ -164,13 +135,13 @@ def main_loop(
164
135
  time.sleep(0.1)
165
136
 
166
137
  for process in processes:
167
- if process.name in completed_processes or process.process.poll() is None:
138
+ if process.name in completed_processes or process.poll() is None:
168
139
  continue
169
140
 
170
141
  print(f"{CLEAR_LINE}{CR}", end="")
171
142
  completed_processes.add(process.name)
172
143
 
173
- if process.process.returncode != 0:
144
+ if process.return_code() != 0:
174
145
  print_command_status(process, passed=False, debug=debug)
175
146
  print_command_output(process)
176
147
  passed = False
@@ -236,3 +207,7 @@ def run(*args: str) -> int:
236
207
 
237
208
  def entry_point() -> None:
238
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