pyallel 1.3.7__tar.gz → 2.0.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: 1.3.7
3
+ Version: 2.0.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
@@ -47,42 +47,54 @@ Once installed, you can run `pyallel` to see usage information, like so:
47
47
  ```
48
48
  usage: pyallel [-h] [-t] [-n] [-V] [--colour {yes,no,auto}] [commands ...]
49
49
 
50
- Run and handle the output of multiple executables in pyallel (as in parallel)
50
+ run and handle the output of multiple executables in pyallel (as in parallel)
51
51
 
52
- positional arguments:
53
- commands list of quoted commands to run in parallel e.g "mypy ." "black ."
52
+ RUNNING COMMANDS
53
+ ================
54
+ to run multiple commands you must separate them using the command separator symbol (::)
55
+
56
+ pyallel mypy . :: black .
57
+
58
+ if you want to provide options to a command you need to use the double dash symbol (--) to indicate that
59
+ any options provided after this symbol should not be interpreted by pyallel
60
+
61
+ pyallel -n -- mypy -V :: black --version
54
62
 
55
- each command is executed inside a shell, so shell syntax is supported as
56
- if you were running the command directly in a shell, some examples are below
63
+ commands can also be grouped using the group separator symbol (:::)
57
64
 
58
- "MYPY_FORCE_COLOR=1 mypy ." <- provide environment variables
59
- "mypy | tee -a mypy.log" <- use pipes to redirect output
60
- "cat > test.log < other.log" <- use input and output redirection
61
- "mypy .; pytest ." <- run commands one at a time in sequence
62
- "echo \$SHELL" or "\$(echo mypy .)" <- expand variables and commands to evaluate (must be escaped)
63
- "pytest . && mypy . || echo failed!" <- use AND (&&) and OR (||) to run commands conditionally
65
+ pyallel echo boil kettle :: sleep 1 ::: echo make coffee
64
66
 
65
- PROCESS GROUPS
66
- --------------
67
- commands can be grouped using the group separator symbol (:::)
67
+ the above will print 'boil kettle' and sleep for 1 second first before printing 'make coffee'.
68
+ command groups are ran in the sequence you provide them, and if a command within a command group fails,
69
+ the rest of the command groups in the sequence are not run
68
70
 
69
- pyallel "echo boil kettle" "sleep 1" ::: "echo make coffee"
71
+ modifiers can also be set for commands to augment their behaviour using the command modifier symbol (::::)
70
72
 
71
- the above will print "boil kettle" and sleep for 1 second first before printing "make coffee"
73
+ lines (only used in interactive mode):
74
+ the lines modifier allows you to specify how many lines the command output can take up on the screen
72
75
 
73
- command groups are ran in the sequence you provide them, and if a command group fails
74
- (if a command fails inside the command group) the rest of the command groups in the sequence are not run
76
+ pyallel lines=90 :::: echo running long command... :: echo running other command...
75
77
 
76
- COMMAND MODIFIERS
77
- -----------------
78
- modifiers can be set for commands to augment their behaviour using the command modifier symbol (::)
78
+ 90 is expressed as a percentage value, which must be between 1 and 100 inclusive
79
79
 
80
- lines (only used in interactive mode):
81
- the lines modifier allows you to specify how many lines the command output can take up on the screen
80
+ SHELL SYNTAX
81
+ ============
82
+ each command is executed inside its own shell, this means shell syntax is supported.
83
+ it is important to note that certain shell syntax must be escaped using backslashes (\)
84
+ or wrapped in single quotes (''), otherwise it will be evaluated in your current
85
+ shell immediately instead of the shell that your command will run within.
82
86
 
83
- pyallel "lines=90 :: echo running long command..." "echo running other command..."
87
+ some examples of using shell syntax are below (single quotes are used only if required)
84
88
 
85
- 90 is expressed as a percentage value, which must be between 1 and 100 inclusive
89
+ pyallel MYPY_FORCE_COLOR=1 mypy . <- provide environment variables
90
+ pyallel 'mypy . | tee -a mypy.log' <- use pipes to redirect output
91
+ pyallel 'cat > test.log <<< hello!' <- use input and output redirection
92
+ pyallel 'mypy .; pytest .' <- run commands one at a time in sequence
93
+ pyallel 'echo $SHELL; $(echo mypy .)' <- expand variables and commands to evaluate
94
+ pyallel 'pytest . && mypy . || echo failed!' <- use AND (&&) and OR (||) to run commands conditionally
95
+
96
+ positional arguments:
97
+ commands list of commands and their arguments to run in parallel
86
98
 
87
99
  options:
88
100
  -h, --help show this help message and exit
@@ -96,13 +108,8 @@ options:
96
108
 
97
109
  Currently you can provide a variable number of `commands` to run to `pyallel`, like so:
98
110
 
99
- > [!IMPORTANT]
100
- > If you need to provide arguments to a command, you must surround the command and it's arguments in quotes!
101
-
102
111
  ```bash
103
- pyallel "MYPY_FORCE_COLOR=1 mypy ." \
104
- "black --check --diff ." \
105
- "pytest ."
112
+ pyallel MYPY_FORCE_COLOR=1 mypy . :: black --check --diff . :: pytest .
106
113
  ```
107
114
 
108
115
  # Build
@@ -23,42 +23,54 @@ Once installed, you can run `pyallel` to see usage information, like so:
23
23
  ```
24
24
  usage: pyallel [-h] [-t] [-n] [-V] [--colour {yes,no,auto}] [commands ...]
25
25
 
26
- Run and handle the output of multiple executables in pyallel (as in parallel)
26
+ run and handle the output of multiple executables in pyallel (as in parallel)
27
27
 
28
- positional arguments:
29
- commands list of quoted commands to run in parallel e.g "mypy ." "black ."
28
+ RUNNING COMMANDS
29
+ ================
30
+ to run multiple commands you must separate them using the command separator symbol (::)
31
+
32
+ pyallel mypy . :: black .
33
+
34
+ if you want to provide options to a command you need to use the double dash symbol (--) to indicate that
35
+ any options provided after this symbol should not be interpreted by pyallel
36
+
37
+ pyallel -n -- mypy -V :: black --version
30
38
 
31
- each command is executed inside a shell, so shell syntax is supported as
32
- if you were running the command directly in a shell, some examples are below
39
+ commands can also be grouped using the group separator symbol (:::)
33
40
 
34
- "MYPY_FORCE_COLOR=1 mypy ." <- provide environment variables
35
- "mypy | tee -a mypy.log" <- use pipes to redirect output
36
- "cat > test.log < other.log" <- use input and output redirection
37
- "mypy .; pytest ." <- run commands one at a time in sequence
38
- "echo \$SHELL" or "\$(echo mypy .)" <- expand variables and commands to evaluate (must be escaped)
39
- "pytest . && mypy . || echo failed!" <- use AND (&&) and OR (||) to run commands conditionally
41
+ pyallel echo boil kettle :: sleep 1 ::: echo make coffee
40
42
 
41
- PROCESS GROUPS
42
- --------------
43
- commands can be grouped using the group separator symbol (:::)
43
+ the above will print 'boil kettle' and sleep for 1 second first before printing 'make coffee'.
44
+ command groups are ran in the sequence you provide them, and if a command within a command group fails,
45
+ the rest of the command groups in the sequence are not run
44
46
 
45
- pyallel "echo boil kettle" "sleep 1" ::: "echo make coffee"
47
+ modifiers can also be set for commands to augment their behaviour using the command modifier symbol (::::)
46
48
 
47
- the above will print "boil kettle" and sleep for 1 second first before printing "make coffee"
49
+ lines (only used in interactive mode):
50
+ the lines modifier allows you to specify how many lines the command output can take up on the screen
48
51
 
49
- command groups are ran in the sequence you provide them, and if a command group fails
50
- (if a command fails inside the command group) the rest of the command groups in the sequence are not run
52
+ pyallel lines=90 :::: echo running long command... :: echo running other command...
51
53
 
52
- COMMAND MODIFIERS
53
- -----------------
54
- modifiers can be set for commands to augment their behaviour using the command modifier symbol (::)
54
+ 90 is expressed as a percentage value, which must be between 1 and 100 inclusive
55
55
 
56
- lines (only used in interactive mode):
57
- the lines modifier allows you to specify how many lines the command output can take up on the screen
56
+ SHELL SYNTAX
57
+ ============
58
+ each command is executed inside its own shell, this means shell syntax is supported.
59
+ it is important to note that certain shell syntax must be escaped using backslashes (\)
60
+ or wrapped in single quotes (''), otherwise it will be evaluated in your current
61
+ shell immediately instead of the shell that your command will run within.
58
62
 
59
- pyallel "lines=90 :: echo running long command..." "echo running other command..."
63
+ some examples of using shell syntax are below (single quotes are used only if required)
60
64
 
61
- 90 is expressed as a percentage value, which must be between 1 and 100 inclusive
65
+ pyallel MYPY_FORCE_COLOR=1 mypy . <- provide environment variables
66
+ pyallel 'mypy . | tee -a mypy.log' <- use pipes to redirect output
67
+ pyallel 'cat > test.log <<< hello!' <- use input and output redirection
68
+ pyallel 'mypy .; pytest .' <- run commands one at a time in sequence
69
+ pyallel 'echo $SHELL; $(echo mypy .)' <- expand variables and commands to evaluate
70
+ pyallel 'pytest . && mypy . || echo failed!' <- use AND (&&) and OR (||) to run commands conditionally
71
+
72
+ positional arguments:
73
+ commands list of commands and their arguments to run in parallel
62
74
 
63
75
  options:
64
76
  -h, --help show this help message and exit
@@ -72,13 +84,8 @@ options:
72
84
 
73
85
  Currently you can provide a variable number of `commands` to run to `pyallel`, like so:
74
86
 
75
- > [!IMPORTANT]
76
- > If you need to provide arguments to a command, you must surround the command and it's arguments in quotes!
77
-
78
87
  ```bash
79
- pyallel "MYPY_FORCE_COLOR=1 mypy ." \
80
- "black --check --diff ." \
81
- "pytest ."
88
+ pyallel MYPY_FORCE_COLOR=1 mypy . :: black --check --diff . :: pytest .
82
89
  ```
83
90
 
84
91
  # Build
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pyallel"
3
- version = "1.3.7"
3
+ version = "2.0.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"
@@ -0,0 +1,10 @@
1
+ class PyallelError(Exception):
2
+ """Base error for issues raised by pyallel"""
3
+
4
+
5
+ class InvalidLinesModifierError(PyallelError):
6
+ """Raised when the lines modifier is invalid"""
7
+
8
+
9
+ class NoCommandsForProcessGroupError(PyallelError):
10
+ """Raised when no commands have been provided for a process group"""
@@ -7,7 +7,7 @@ import traceback
7
7
 
8
8
  from pyallel import constants
9
9
  from pyallel.colours import Colours
10
- from pyallel.errors import InvalidLinesModifierError
10
+ from pyallel.errors import PyallelError
11
11
  from pyallel.parser import Arguments, create_parser
12
12
  from pyallel.printer import (
13
13
  InteractiveConsolePrinter,
@@ -40,7 +40,7 @@ def entry_point(*args: str) -> int:
40
40
 
41
41
  try:
42
42
  process_group_manager = ProcessGroupManager.from_args(*parsed_args.commands)
43
- except InvalidLinesModifierError as e:
43
+ except PyallelError as e:
44
44
  print(f"{colours.red_bold}Error: {str(e)}{colours.reset_colour}")
45
45
  return 1
46
46
 
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ from argparse import ArgumentParser, RawTextHelpFormatter
4
+ from typing import Literal
5
+
6
+
7
+ class Arguments:
8
+ colour: Literal["yes", "no", "auto"]
9
+ commands: list[str]
10
+ interactive: bool
11
+ timer: bool
12
+ version: bool
13
+
14
+ def __repr__(self) -> str:
15
+ msg = "Arguments:\n"
16
+ padding = len(sorted(self.__dict__.keys(), key=len, reverse=True)[0]) + 1
17
+ for field, value in self.__dict__.items():
18
+ msg += f" {field: <{padding}}: {value}\n"
19
+ return msg
20
+
21
+
22
+ DESCRIPTION = r"""run and handle the output of multiple executables in %(prog)s (as in parallel)
23
+
24
+ RUNNING COMMANDS
25
+ ================
26
+ to run multiple commands you must separate them using the command separator symbol (::)
27
+
28
+ %(prog)s mypy . :: black .
29
+
30
+ if you want to provide options to a command you need to use the double dash symbol (--) to indicate that
31
+ any options provided after this symbol should not be interpreted by %(prog)s
32
+
33
+ %(prog)s -n -- mypy -V :: black --version
34
+
35
+ commands can also be grouped using the group separator symbol (:::)
36
+
37
+ %(prog)s echo boil kettle :: sleep 1 ::: echo make coffee
38
+
39
+ the above will print 'boil kettle' and sleep for 1 second first before printing 'make coffee'.
40
+ command groups are ran in the sequence you provide them, and if a command within a command group fails,
41
+ the rest of the command groups in the sequence are not run
42
+
43
+
44
+ modifiers can also be set for commands to augment their behaviour using the command modifier symbol (::::)
45
+
46
+ lines (only used in interactive mode):
47
+ the lines modifier allows you to specify how many lines the command output can take up on the screen
48
+
49
+ %(prog)s lines=90 :::: echo running long command... :: echo running other command...
50
+
51
+ 90 is expressed as a percentage value, which must be between 1 and 100 inclusive
52
+
53
+ SHELL SYNTAX
54
+ ============
55
+ each command is executed inside its own shell, this means shell syntax is supported.
56
+ it is important to note that certain shell syntax must be escaped using backslashes (\)
57
+ or wrapped in single quotes (''), otherwise it will be evaluated in your current
58
+ shell immediately instead of the shell that your command will run within.
59
+
60
+ some examples of using shell syntax are below (single quotes are used only if required)
61
+
62
+ %(prog)s MYPY_FORCE_COLOR=1 mypy . <- provide environment variables
63
+ %(prog)s 'mypy . | tee -a mypy.log' <- use pipes to redirect output
64
+ %(prog)s 'cat > test.log <<< hello!' <- use input and output redirection
65
+ %(prog)s 'mypy .; pytest .' <- run commands one at a time in sequence
66
+ %(prog)s 'echo $SHELL; $(echo mypy .)' <- expand variables and commands to evaluate
67
+ %(prog)s 'pytest . && mypy . || echo failed!' <- use AND (&&) and OR (||) to run commands conditionally
68
+ """
69
+
70
+
71
+ def create_parser() -> ArgumentParser:
72
+ parser = ArgumentParser(
73
+ prog="pyallel",
74
+ description=DESCRIPTION,
75
+ formatter_class=RawTextHelpFormatter,
76
+ )
77
+ parser.add_argument(
78
+ "commands",
79
+ help="list of commands and their arguments to run in parallel",
80
+ nargs="*",
81
+ )
82
+ parser.add_argument(
83
+ "-t",
84
+ "--no-timer",
85
+ dest="timer",
86
+ help="don't time how long each command is taking",
87
+ action="store_false",
88
+ default=True,
89
+ )
90
+ parser.add_argument(
91
+ "-n",
92
+ "--non-interactive",
93
+ help="run in non-interactive mode",
94
+ action="store_false",
95
+ dest="interactive",
96
+ default=True,
97
+ )
98
+ parser.add_argument(
99
+ "-V",
100
+ "--version",
101
+ help="print version and exit",
102
+ action="store_true",
103
+ default=False,
104
+ )
105
+ parser.add_argument(
106
+ "--colour",
107
+ help='colour terminal output, defaults to "%(default)s"',
108
+ choices=("yes", "no", "auto"),
109
+ default="auto",
110
+ )
111
+
112
+ return parser
@@ -78,7 +78,7 @@ class Process:
78
78
 
79
79
  @classmethod
80
80
  def from_command(cls, id: int, command: str) -> Process:
81
- cmd = command.split(" :: ", maxsplit=1)
81
+ cmd = command.split(" :::: ", maxsplit=1)
82
82
  if len(cmd) == 1:
83
83
  return cls(id, cmd[0])
84
84
 
@@ -64,11 +64,22 @@ class ProcessGroup:
64
64
 
65
65
  @classmethod
66
66
  def from_commands(cls, id: int, process_id: int, *commands: str) -> ProcessGroup:
67
+ cmds: list[str] = []
67
68
  processes: list[Process] = []
68
69
 
69
70
  percentage_lines_sum = 0.0
70
71
  for i, command in enumerate(commands):
71
- process = Process.from_command(i + process_id, command)
72
+ if command != "::":
73
+ cmds.append(command)
74
+ continue
75
+
76
+ process = Process.from_command(i + process_id, " ".join(cmds))
77
+ percentage_lines_sum += process.percentage_lines
78
+ processes.append(process)
79
+ cmds.clear()
80
+
81
+ if cmds:
82
+ process = Process.from_command(i + process_id, " ".join(cmds))
72
83
  percentage_lines_sum += process.percentage_lines
73
84
  processes.append(process)
74
85
 
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import signal
4
4
  from typing import Any
5
5
 
6
+ from pyallel.errors import NoCommandsForProcessGroupError
6
7
  from pyallel.process import ProcessOutput
7
8
  from pyallel.process_group import ProcessGroupOutput, ProcessGroup
8
9
 
@@ -113,39 +114,31 @@ class ProcessGroupManager:
113
114
 
114
115
  @classmethod
115
116
  def from_args(cls, *args: str) -> ProcessGroupManager:
116
- last_separator_index = 0
117
117
  commands: list[str] = []
118
118
  process_groups: list[ProcessGroup] = []
119
119
  progress_group_id = 1
120
120
  process_id = 1
121
121
 
122
- for i, arg in enumerate(args):
123
- if arg == ":::":
124
- if i - 1 == 0:
125
- pg = ProcessGroup.from_commands(
126
- progress_group_id, process_id, args[0]
127
- )
128
- else:
129
- pg = ProcessGroup.from_commands(
130
- progress_group_id, process_id, *commands[last_separator_index:]
131
- )
132
-
133
- process_groups.append(pg)
134
- process_id += len(pg.processes)
135
- last_separator_index = i
136
- progress_group_id += 1
122
+ for arg in args:
123
+ if arg != ":::":
124
+ commands.append(arg)
137
125
  continue
138
126
 
139
- commands.append(arg)
127
+ if not commands:
128
+ raise NoCommandsForProcessGroupError(
129
+ f"no commands provided for process group {progress_group_id}, did you forgot to provide them before the ::: symbol?"
130
+ )
140
131
 
141
- if len(process_groups) > 1:
142
- last_separator_index -= 1
132
+ pg = ProcessGroup.from_commands(progress_group_id, process_id, *commands)
133
+ process_groups.append(pg)
134
+ process_id += len(pg.processes)
135
+ progress_group_id += 1
136
+ commands.clear()
143
137
 
144
- process_groups.append(
145
- ProcessGroup.from_commands(
146
- progress_group_id, process_id, *commands[last_separator_index:]
138
+ if commands:
139
+ process_groups.append(
140
+ ProcessGroup.from_commands(progress_group_id, process_id, *commands)
147
141
  )
148
- )
149
142
 
150
143
  process_group_manager = cls(process_groups=process_groups)
151
144
 
@@ -1,2 +0,0 @@
1
- class InvalidLinesModifierError(Exception):
2
- """Raised when the lines modifier is invalid"""
@@ -1,99 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from argparse import ArgumentParser, RawTextHelpFormatter
4
- from typing import Literal
5
-
6
-
7
- class Arguments:
8
- colour: Literal["yes", "no", "auto"]
9
- commands: list[str]
10
- interactive: bool
11
- timer: bool
12
- version: bool
13
-
14
- def __repr__(self) -> str:
15
- msg = "Arguments:\n"
16
- padding = len(sorted(self.__dict__.keys(), key=len, reverse=True)[0]) + 1
17
- for field, value in self.__dict__.items():
18
- msg += f" {field: <{padding}}: {value}\n"
19
- return msg
20
-
21
-
22
- COMMANDS_HELP = r"""list of quoted commands to run in parallel e.g "mypy ." "black ."
23
-
24
- each command is executed inside a shell, so shell syntax is supported as
25
- if you were running the command directly in a shell, some examples are below
26
-
27
- "MYPY_FORCE_COLOR=1 mypy ." <- provide environment variables
28
- "mypy | tee -a mypy.log" <- use pipes to redirect output
29
- "cat > test.log < other.log" <- use input and output redirection
30
- "mypy .; pytest ." <- run commands one at a time in sequence
31
- "echo \$SHELL" or "\$(echo mypy .)" <- expand variables and commands to evaluate (must be escaped)
32
- "pytest . && mypy . || echo failed!" <- use AND (&&) and OR (||) to run commands conditionally
33
-
34
- PROCESS GROUPS
35
- --------------
36
- commands can be grouped using the group separator symbol (:::)
37
-
38
- %(prog)s "echo boil kettle" "sleep 1" ::: "echo make coffee"
39
-
40
- the above will print "boil kettle" and sleep for 1 second first before printing "make coffee"
41
-
42
- command groups are ran in the sequence you provide them, and if a command group fails
43
- (if a command fails inside the command group) the rest of the command groups in the sequence are not run
44
-
45
- COMMAND MODIFIERS
46
- -----------------
47
- modifiers can be set for commands to augment their behaviour using the command modifier symbol (::)
48
-
49
- lines (only used in interactive mode):
50
- the lines modifier allows you to specify how many lines the command output can take up on the screen
51
-
52
- %(prog)s "lines=90 :: echo running long command..." "echo running other command..."
53
-
54
- 90 is expressed as a percentage value, which must be between 1 and 100 inclusive
55
- """
56
-
57
-
58
- def create_parser() -> ArgumentParser:
59
- parser = ArgumentParser(
60
- prog="pyallel",
61
- description="Run and handle the output of multiple executables in pyallel (as in parallel)",
62
- formatter_class=RawTextHelpFormatter,
63
- )
64
- parser.add_argument(
65
- "commands",
66
- help=COMMANDS_HELP,
67
- nargs="*",
68
- )
69
- parser.add_argument(
70
- "-t",
71
- "--no-timer",
72
- dest="timer",
73
- help="don't time how long each command is taking",
74
- action="store_false",
75
- default=True,
76
- )
77
- parser.add_argument(
78
- "-n",
79
- "--non-interactive",
80
- help="run in non-interactive mode",
81
- action="store_false",
82
- dest="interactive",
83
- default=True,
84
- )
85
- parser.add_argument(
86
- "-V",
87
- "--version",
88
- help="print version and exit",
89
- action="store_true",
90
- default=False,
91
- )
92
- parser.add_argument(
93
- "--colour",
94
- help='colour terminal output, defaults to "%(default)s"',
95
- choices=("yes", "no", "auto"),
96
- default="auto",
97
- )
98
-
99
- return parser
File without changes
File without changes
File without changes
File without changes
File without changes