multiCMD 1.26__py3-none-any.whl → 1.28__py3-none-any.whl

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.
multiCMD.py CHANGED
@@ -18,7 +18,7 @@ import re
18
18
  import itertools
19
19
  import signal
20
20
 
21
- version = '1.26'
21
+ version = '1.28'
22
22
  __version__ = version
23
23
 
24
24
  __running_threads = []
@@ -40,6 +40,179 @@ class Task:
40
40
  if self.thread is not None:
41
41
  return self.thread.is_alive()
42
42
  return False
43
+
44
+ class AsyncExecutor:
45
+ def __init__(self, max_threads=1,semaphore=None,timeout=0,quiet=True,dry_run=False,parse=False):
46
+ '''
47
+ AsyncExecutor class to run commands in parallel asynchronously
48
+ @params:
49
+ max_threads: The maximum number of threads to use ( int ) ( Note: if passing semaphore, this likely will be ignored )
50
+ semaphore: The semaphore to use for threading ( threading.Semaphore )
51
+ timeout: The timeout for each command ( int )
52
+ quiet: Whether to suppress output ( bool )
53
+ dry_run: Whether to simulate running the commands ( bool )
54
+ parse: Whether to parse ranged input ( bool )
55
+ '''
56
+ self.max_threads = max_threads
57
+ self.semaphore = semaphore
58
+ self.runningThreads = []
59
+ self.tasks = []
60
+ self.timeout = timeout
61
+ self.quiet = quiet
62
+ self.dry_run = dry_run
63
+ self.parse = parse
64
+
65
+ def __iter__(self):
66
+ return iter(self.tasks)
67
+
68
+ def __repr__(self):
69
+ return f'AsyncExecutor(max_threads={self.max_threads}, semaphore={self.semaphore}, runningThreads={self.runningThreads}, tasks={self.tasks}, timeout={self.timeout}, quiet={self.quiet}, dry_run={self.dry_run}, parse={self.parse})'
70
+
71
+ def __str__(self):
72
+ return str(self.tasks)
73
+
74
+ def __len__(self):
75
+ return len(self.tasks)
76
+
77
+ def __bool__(self):
78
+ return bool(self.tasks)
79
+
80
+ def run_commands(self, commands, timeout=...,max_threads=...,quiet=...,dry_run=...,parse = ...,sem = ...):
81
+ '''
82
+ Run multiple commands in parallel asynchronously
83
+ @params:
84
+ commands: A list of commands to run ( list[str] | list[list[str]] )
85
+ timeout: The timeout for each command to override the object default
86
+ max_threads: The maximum number of threads to use to override the object default
87
+ quiet: Whether to suppress output to override the object default
88
+ dry_run: Whether to simulate running the commands to override the object default
89
+ parse: Whether to parse ranged input to override the object default
90
+ sem: The semaphore to use for threading to override the object default
91
+ @returns:
92
+ list: The Task Object of the commands ( list[Task] )
93
+ '''
94
+ if timeout is ...:
95
+ timeout = self.timeout
96
+ if max_threads is ...:
97
+ max_threads = self.max_threads
98
+ if quiet is ...:
99
+ quiet = self.quiet
100
+ if dry_run is ...:
101
+ dry_run = self.dry_run
102
+ if parse is ...:
103
+ parse = self.parse
104
+ if sem is ...:
105
+ sem = self.semaphore
106
+ taskObjects: list[Task] = run_commands(commands,timeout=timeout,max_threads=max_threads,quiet=quiet,dry_run=dry_run,with_stdErr=False,
107
+ return_code_only=False,return_object=True, parse = parse, wait_for_return = False, sem = sem)
108
+ self.tasks.extend(taskObjects)
109
+ self.runningThreads.extend([task.thread for task in taskObjects])
110
+ return taskObjects
111
+
112
+ def run_command(self, command, timeout=...,max_threads=...,quiet=...,dry_run=...,parse = ...,sem = ...):
113
+ '''
114
+ Run a command in parallel asynchronously
115
+ @params:
116
+ command: The command to run ( str | list[str] )
117
+ timeout: The timeout for each command to override the object default
118
+ max_threads: The maximum number of threads to use to override the object default
119
+ quiet: Whether to suppress output to override the object default
120
+ dry_run: Whether to simulate running the commands to override the object default
121
+ parse: Whether to parse ranged input to override the object default
122
+ sem: The semaphore to use for threading to override the object default
123
+ @returns:
124
+ Task: The Task Object of the command
125
+ '''
126
+ return self.run_commands([command],timeout=timeout,max_threads=max_threads,quiet=quiet,dry_run=dry_run,parse=parse,sem=sem)[0]
127
+
128
+ def wait(self, timeout=..., threads = ...):
129
+ '''
130
+ Wait for the threads to finish
131
+ @params:
132
+ timeout: The timeout for each command to override the object default
133
+ threads: The threads to join, default to all running threads managed by this object
134
+ @returns:
135
+ list[threading.Thread]: The list of running threads that are still running
136
+ '''
137
+ if threads is ...:
138
+ threads = self.runningThreads
139
+ if timeout is ...:
140
+ timeout = self.timeout
141
+ for thread in threads:
142
+ if timeout > 0:
143
+ thread.join(timeout=timeout)
144
+ else:
145
+ thread.join()
146
+ self.runningThreads = [thread for thread in self.runningThreads if thread.is_alive()]
147
+ return self.runningThreads
148
+
149
+ def stop(self,timeout=...):
150
+ '''
151
+ Stop all running threads. This signals all threads to stop and joins them
152
+ @params:
153
+ None
154
+ @returns:
155
+ list[Task]: The list of tasks that are managed by this object
156
+ '''
157
+ for task in self.tasks:
158
+ task.stop = True
159
+ self.wait(timeout)
160
+ return self.tasks
161
+
162
+ def cleanup(self,timeout=...):
163
+ '''
164
+ Cleanup the tasks and threads. This calls stop() and clears the tasks and threads
165
+ @params:
166
+ None
167
+ @returns:
168
+ list[Task]: The list of tasks that are managed by this object
169
+ '''
170
+ self.stop(timeout)
171
+ self.tasks = []
172
+ self.runningThreads = []
173
+ return self.tasks
174
+
175
+ def join(self, timeout=..., threads = ..., print_error=True):
176
+ '''
177
+ Wait for the threads to finish and print error if there is any
178
+ @params:
179
+ timeout: The timeout for each command to override the object default
180
+ threads: The threads to wait for, default to all running threads managed by this object
181
+ @returns:
182
+ list[Task]: The list of tasks that are managed by this object
183
+ '''
184
+ self.wait(timeout=timeout, threads=threads)
185
+ for task in self.tasks:
186
+ if task.returncode != 0 and print_error:
187
+ print(f'Command: {task.command} failed with return code: {task.returncode}')
188
+ print('Stdout:')
189
+ print('\n '.join(task.stdout))
190
+ print('Stderr:')
191
+ print('\n '.join(task.stderr))
192
+ return self.tasks
193
+
194
+ def get_results(self, with_stdErr=False):
195
+ '''
196
+ Get the results of the tasks
197
+ @params:
198
+ with_stdErr: Whether to append the standard error output to the standard output
199
+ @returns:
200
+ list[list[str]]: The output of the tasks ( list[list[str]] )
201
+ '''
202
+ if with_stdErr:
203
+ return [task.stdout + task.stderr for task in self.tasks]
204
+ else:
205
+ return [task.stdout for task in self.tasks]
206
+
207
+ def get_return_codes(self):
208
+ '''
209
+ Get the return codes of the tasks
210
+ @params:
211
+ None
212
+ @returns:
213
+ list[int]: The return codes of the tasks ( list[int] )
214
+ '''
215
+ return [task.returncode for task in self.tasks]
43
216
 
44
217
  def _expand_ranges(inStr):
45
218
  '''
@@ -307,7 +480,7 @@ def ping(hosts,timeout=1,max_threads=0,quiet=True,dry_run=False,with_stdErr=Fals
307
480
 
308
481
 
309
482
  def run_command(command, timeout=0,max_threads=1,quiet=False,dry_run=False,with_stdErr=False,
310
- return_code_only=False,return_object=False,wait_for_return=True):
483
+ return_code_only=False,return_object=False,wait_for_return=True, sem = None):
311
484
  '''
312
485
  Run a command
313
486
 
@@ -321,13 +494,14 @@ def run_command(command, timeout=0,max_threads=1,quiet=False,dry_run=False,with_
321
494
  return_code_only: Whether to return only the return code
322
495
  return_object: Whether to return the Task object
323
496
  wait_for_return: Whether to wait for the return of the command
497
+ sem: The semaphore to use for threading
324
498
 
325
499
  @returns:
326
500
  None | int | list[str] | Task: The output of the command
327
501
  '''
328
502
  return run_commands(commands=[command], timeout=timeout, max_threads=max_threads, quiet=quiet,
329
503
  dry_run=dry_run, with_stdErr=with_stdErr, return_code_only=return_code_only,
330
- return_object=return_object,parse=False,wait_for_return=wait_for_return)[0]
504
+ return_object=return_object,parse=False,wait_for_return=wait_for_return,sem=sem)[0]
331
505
 
332
506
  def __format_command(command,expand = False):
333
507
  '''
@@ -366,7 +540,7 @@ def __format_command(command,expand = False):
366
540
  return __format_command(str(command),expand=expand)
367
541
 
368
542
  def run_commands(commands, timeout=0,max_threads=1,quiet=False,dry_run=False,with_stdErr=False,
369
- return_code_only=False,return_object=False, parse = False, wait_for_return = True):
543
+ return_code_only=False,return_object=False, parse = False, wait_for_return = True, sem : threading.Semaphore = None):
370
544
  '''
371
545
  Run multiple commands in parallel
372
546
 
@@ -381,6 +555,7 @@ def run_commands(commands, timeout=0,max_threads=1,quiet=False,dry_run=False,wit
381
555
  return_object: Whether to return the Task object
382
556
  parse: Whether to parse ranged input
383
557
  wait_for_return: Whether to wait for the return of the commands
558
+ sem: The semaphore to use for threading
384
559
 
385
560
  @returns:
386
561
  list: The output of the commands ( list[None] | list[int] | list[list[str]] | list[Task] )
@@ -395,7 +570,8 @@ def run_commands(commands, timeout=0,max_threads=1,quiet=False,dry_run=False,wit
395
570
  if max_threads < 1:
396
571
  max_threads = len(formatedCommands)
397
572
  if max_threads > 1 or not wait_for_return:
398
- sem = threading.Semaphore(max_threads) # Limit concurrent sessions
573
+ if not sem:
574
+ sem = threading.Semaphore(max_threads) # Limit concurrent sessions
399
575
  threads = [threading.Thread(target=__run_command, args=(task,sem,timeout,quiet,dry_run,...),daemon=True) for task in tasks]
400
576
  for thread,task in zip(threads,tasks):
401
577
  task.thread = thread
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: multiCMD
3
- Version: 1.26
3
+ Version: 1.28
4
4
  Summary: Run commands simultaneously
5
5
  Home-page: https://github.com/yufei-pan/multiCMD
6
6
  Author: Yufei Pan
@@ -0,0 +1,6 @@
1
+ multiCMD.py,sha256=fgT8HBpqVnoT8HZ9bHQwFGOM5ONuJIn88ey4ITYiRP8,25050
2
+ multicmd-1.28.dist-info/METADATA,sha256=7EK4qvJSK1DheNstmVJTOq1qK9r0oGhZrlcL0JWIAEE,5640
3
+ multicmd-1.28.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
4
+ multicmd-1.28.dist-info/entry_points.txt,sha256=nSLBkYrcUCQxt1w3LIJkvgOhpRYEC0xAPqNG7u4OYs8,89
5
+ multicmd-1.28.dist-info/top_level.txt,sha256=DSqgftD40G09F3qEjpHRCUNUsGUvGZZG69Sm3YEUiWI,9
6
+ multicmd-1.28.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,6 +0,0 @@
1
- multiCMD.py,sha256=NIUkbXTFdCTQJb6QMuAAhf6cJoiDwZ0vZ0H721ND85c,18750
2
- multicmd-1.26.dist-info/METADATA,sha256=Xy_tztr92dFb0cszDrbVGHeI4SOybpmhYTEP0IIqUJ0,5640
3
- multicmd-1.26.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
4
- multicmd-1.26.dist-info/entry_points.txt,sha256=nSLBkYrcUCQxt1w3LIJkvgOhpRYEC0xAPqNG7u4OYs8,89
5
- multicmd-1.26.dist-info/top_level.txt,sha256=DSqgftD40G09F3qEjpHRCUNUsGUvGZZG69Sm3YEUiWI,9
6
- multicmd-1.26.dist-info/RECORD,,