pyallel 1.3.3__tar.gz → 1.3.4__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.3
3
+ Version: 1.3.4
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
@@ -156,6 +156,7 @@ python -m venv .venv && \
156
156
  - [x] Add support to have commands depend on other commands (some commands must complete
157
157
  before a given command can start)
158
158
  - [x] Add support to state how many lines a command can use for it's output in interactive mode
159
+ - [x] Improve printing of output performance by only printing lines that have changed
159
160
  - [ ] Add a debug mode that logs debug information to a log file
160
161
  - [ ] Maybe add support to allow the user to provide stdin for commands that request it
161
162
  (such as a REPL)
@@ -132,6 +132,7 @@ python -m venv .venv && \
132
132
  - [x] Add support to have commands depend on other commands (some commands must complete
133
133
  before a given command can start)
134
134
  - [x] Add support to state how many lines a command can use for it's output in interactive mode
135
+ - [x] Improve printing of output performance by only printing lines that have changed
135
136
  - [ ] Add a debug mode that logs debug information to a log file
136
137
  - [ ] Maybe add support to allow the user to provide stdin for commands that request it
137
138
  (such as a REPL)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pyallel"
3
- version = "1.3.3"
3
+ version = "1.3.4"
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"
@@ -3,8 +3,11 @@ import shutil
3
3
  import sys
4
4
 
5
5
  IN_TTY = sys.stdout.isatty()
6
+
7
+ # From: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
6
8
  CLEAR_LINE = "\033[2K"
7
- UP_LINE = "\033[1F"
9
+ UP_LINE = "\033[1A\r"
10
+ DOWN_LINE = "\033[1B\r"
8
11
  ANSI_ESCAPE = re.compile(r"(\x9B|\x1B\[|\x1B\()[0-?]*[ -\/]*[@-~]")
9
12
 
10
13
  if IN_TTY:
@@ -19,7 +19,6 @@ def run_interactive(
19
19
  while True:
20
20
  process_group_manager.stream()
21
21
 
22
- printer.clear_printed_lines()
23
22
  output = process_group_manager.get_cur_process_group_output()
24
23
  printer.print_progress_group_output(
25
24
  output, process_group_manager._interrupt_count
@@ -27,7 +26,8 @@ def run_interactive(
27
26
 
28
27
  poll = process_group_manager.poll()
29
28
  if poll is not None:
30
- printer.clear_printed_lines()
29
+ printer.clear_last_printed_lines()
30
+ printer.reset()
31
31
  printer.print_progress_group_output(
32
32
  output, process_group_manager._interrupt_count, tail_output=False
33
33
  )
@@ -14,7 +14,8 @@ class Printer:
14
14
  self._timer = timer
15
15
  self._prefix = f"{self._colours.dim_on}=>{self._colours.dim_off} "
16
16
  self._icon = 0
17
- self._printed: list[tuple[bool, str, str]] = []
17
+ self._last_printed: list[tuple[bool, str, str]] = []
18
+ self._to_print: list[tuple[bool, str, str]] = []
18
19
 
19
20
  def write(
20
21
  self,
@@ -23,13 +24,15 @@ class Printer:
23
24
  end: str = "\n",
24
25
  flush: bool = False,
25
26
  truncate: bool = False,
27
+ columns: int | None = None,
26
28
  ) -> None:
27
29
  truncate_num = 0
28
30
  prefix = self._prefix if include_prefix else ""
31
+ columns = columns or constants.COLUMNS()
29
32
  if prefix:
30
33
  truncate_num = 6
31
34
  if truncate:
32
- columns = constants.COLUMNS() - truncate_num
35
+ columns = columns - truncate_num
33
36
  if get_num_lines(line, columns) > 1:
34
37
  line = truncate_line(line, columns)
35
38
  print(f"{prefix}{line}", end=end, flush=flush)
@@ -80,7 +83,7 @@ class Printer:
80
83
  )
81
84
  line_parts = (False, status, "\n")
82
85
  out.append(line_parts)
83
- self._printed.append(line_parts)
86
+ self._to_print.append(line_parts)
84
87
 
85
88
  if include_output:
86
89
  lines = output.data.splitlines(keepends=True)
@@ -101,7 +104,7 @@ class Printer:
101
104
  line = line[:-1]
102
105
 
103
106
  try:
104
- prev_line = self._printed[-1]
107
+ prev_line = self._to_print[-1]
105
108
  except IndexError:
106
109
  pass
107
110
  else:
@@ -110,7 +113,7 @@ class Printer:
110
113
 
111
114
  line_parts = (prefix, line, end)
112
115
  out.append(line_parts)
113
- self._printed.append(line_parts)
116
+ self._to_print.append(line_parts)
114
117
 
115
118
  return out
116
119
 
@@ -177,8 +180,8 @@ class Printer:
177
180
  self.generate_process_output(out, tail_output, append_newlines=True)
178
181
 
179
182
  if interrupt_count == 1:
180
- self._printed.append((False, "", "\n"))
181
- self._printed.append(
183
+ self._to_print.append((False, "", "\n"))
184
+ self._to_print.append(
182
185
  (
183
186
  False,
184
187
  f"{self._colours.yellow_bold}Interrupt!{self._colours.reset_colour}",
@@ -186,8 +189,8 @@ class Printer:
186
189
  )
187
190
  )
188
191
  elif interrupt_count == 2:
189
- self._printed.append((False, "", "\n"))
190
- self._printed.append(
192
+ self._to_print.append((False, "", "\n"))
193
+ self._to_print.append(
191
194
  (
192
195
  False,
193
196
  f"{self._colours.red_bold}Abort!{self._colours.reset_colour}",
@@ -199,7 +202,7 @@ class Printer:
199
202
  if self._icon == len(constants.ICONS):
200
203
  self._icon = 0
201
204
 
202
- return self._printed
205
+ return self._to_print
203
206
 
204
207
  def print_process_output(
205
208
  self,
@@ -221,7 +224,7 @@ class Printer:
221
224
  self.write(line, include_prefix, end)
222
225
 
223
226
  # Force a flush otherwise lines that don't end in a newline character will not get printed as they are read
224
- self.write("", include_prefix=False, end="", flush=True)
227
+ print("", end="", flush=True)
225
228
 
226
229
  def print_progress_group_output(
227
230
  self,
@@ -229,24 +232,61 @@ class Printer:
229
232
  interrupt_count: int = 0,
230
233
  tail_output: bool = True,
231
234
  ) -> None:
232
- for include_prefix, line, end in self.generate_process_group_output(
233
- output, interrupt_count, tail_output
234
- ):
235
- self.write(line, include_prefix, end, truncate=tail_output)
235
+ columns = constants.COLUMNS()
236
+ self.generate_process_group_output(output, interrupt_count, tail_output)
236
237
 
237
- def clear_printed_lines(self) -> None:
238
- # Clear all the lines that were just printed
239
- for _, _, end in self._printed:
240
- if end == "\n":
238
+ num_last_printed_lines = len(self._last_printed)
239
+
240
+ # If we don't have any last printed lines or we don't want to tail the output,
241
+ # we just print all the new lines
242
+ if not num_last_printed_lines or not tail_output:
243
+ for include_prefix, line, end in self._to_print:
241
244
  self.write(
242
- f"{constants.CLEAR_LINE}{constants.UP_LINE}{constants.CLEAR_LINE}",
243
- end="",
245
+ line, include_prefix, end, truncate=tail_output, columns=columns
244
246
  )
247
+ else:
248
+ # Compare the number of last lines and new lines and only update what has changed.
249
+ #
250
+ # Move the cursor up the amount the lines that were last printed so we can start
251
+ # comparing the last printed lines with the new lines that were generated
252
+ print(f"\033[{num_last_printed_lines}A", end="")
253
+ for i, line_parts in enumerate(self._to_print):
254
+ # If this is a completely new line, just print it
255
+ if i >= num_last_printed_lines:
256
+ include_prefix, line, end = line_parts
257
+ self.write(
258
+ line, include_prefix, end, truncate=tail_output, columns=columns
259
+ )
260
+ # If the current line is not the same as it's newly generated version, we update the line
261
+ elif line_parts[1] != self._last_printed[i][1]:
262
+ include_prefix, line, end = line_parts
263
+ # Clear the current line
264
+ print(f"{constants.CLEAR_LINE}\r", end="")
265
+ # Write the new line, this will move the cursor to the next line automatically
266
+ self.write(
267
+ line, include_prefix, end, truncate=tail_output, columns=columns
268
+ )
269
+ else:
270
+ # Move on to the next line as this one doesn't need to be updated
271
+ print(constants.DOWN_LINE, end="")
245
272
 
246
- self.reset()
273
+ # Force a flush to return the cursor to the bottom immediately
274
+ print("", end="", flush=True)
275
+
276
+ self._last_printed = self._to_print.copy()
277
+ self._to_print.clear()
278
+
279
+ def clear_last_printed_lines(self) -> None:
280
+ # Clear all the lines that were just printed
281
+ print(
282
+ f"{constants.CLEAR_LINE}{constants.UP_LINE}{constants.CLEAR_LINE}"
283
+ * len(self._last_printed),
284
+ end="",
285
+ )
247
286
 
248
287
  def reset(self) -> None:
249
- self._printed.clear()
288
+ self._last_printed.clear()
289
+ self._to_print.clear()
250
290
 
251
291
 
252
292
  def set_process_lines(
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes