multiCMD 1.27__tar.gz → 1.29__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.4
2
2
  Name: multiCMD
3
- Version: 1.27
3
+ Version: 1.29
4
4
  Summary: Run commands simultaneously
5
5
  Home-page: https://github.com/yufei-pan/multiCMD
6
6
  Author: Yufei Pan
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiCMD
3
- Version: 1.27
3
+ Version: 1.29
4
4
  Summary: Run commands simultaneously
5
5
  Home-page: https://github.com/yufei-pan/multiCMD
6
6
  Author: Yufei Pan
@@ -18,7 +18,7 @@ import re
18
18
  import itertools
19
19
  import signal
20
20
 
21
- version = '1.27'
21
+ version = '1.29'
22
22
  __version__ = version
23
23
 
24
24
  __running_threads = []
@@ -40,6 +40,191 @@ 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=...,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
+ if semaphore is ...:
58
+ semaphore = threading.Semaphore(max_threads)
59
+ self.semaphore = semaphore
60
+ self.runningThreads = []
61
+ self.tasks = []
62
+ self.timeout = timeout
63
+ self.quiet = quiet
64
+ self.dry_run = dry_run
65
+ self.parse = parse
66
+ self.__lastNotJoined = 0
67
+
68
+ def __iter__(self):
69
+ return iter(self.tasks)
70
+
71
+ def __repr__(self):
72
+ 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})'
73
+
74
+ def __str__(self):
75
+ return str(self.tasks)
76
+
77
+ def __len__(self):
78
+ return len(self.tasks)
79
+
80
+ def __bool__(self):
81
+ return bool(self.tasks)
82
+
83
+ def run_commands(self, commands, timeout=...,max_threads=...,quiet=...,dry_run=...,parse = ...,sem = ...):
84
+ '''
85
+ Run multiple commands in parallel asynchronously
86
+ @params:
87
+ commands: A list of commands to run ( list[str] | list[list[str]] )
88
+ timeout: The timeout for each command to override the object default
89
+ max_threads: The maximum number of threads to use to override the object default
90
+ quiet: Whether to suppress output to override the object default
91
+ dry_run: Whether to simulate running the commands to override the object default
92
+ parse: Whether to parse ranged input to override the object default
93
+ sem: The semaphore to use for threading to override the object default
94
+ @returns:
95
+ list: The Task Object of the commands ( list[Task] )
96
+ '''
97
+ if timeout is ...:
98
+ timeout = self.timeout
99
+ if max_threads is ...:
100
+ max_threads = self.max_threads
101
+ if quiet is ...:
102
+ quiet = self.quiet
103
+ if dry_run is ...:
104
+ dry_run = self.dry_run
105
+ if parse is ...:
106
+ parse = self.parse
107
+ if sem is ...:
108
+ sem = self.semaphore
109
+ if len(self.runningThreads) > 130000:
110
+ self.wait(timeout=0)
111
+ if len(self.runningThreads) > 130000:
112
+ print('The amount of running threads approching cpython limit of 130704. Waiting until some available.')
113
+ while len(self.runningThreads) > 120000:
114
+ self.wait(timeout=1)
115
+ elif len(self.runningThreads) + self.__lastNotJoined > 1000:
116
+ self.wait(timeout=0)
117
+ self.__lastNotJoined = len(self.runningThreads)
118
+ taskObjects: list[Task] = run_commands(commands,timeout=timeout,max_threads=max_threads,quiet=quiet,dry_run=dry_run,with_stdErr=False,
119
+ return_code_only=False,return_object=True, parse = parse, wait_for_return = False, sem = sem)
120
+ self.tasks.extend(taskObjects)
121
+ self.runningThreads.extend([task.thread for task in taskObjects])
122
+ return taskObjects
123
+
124
+ def run_command(self, command, timeout=...,max_threads=...,quiet=...,dry_run=...,parse = ...,sem = ...):
125
+ '''
126
+ Run a command in parallel asynchronously
127
+ @params:
128
+ command: The command to run ( str | list[str] )
129
+ timeout: The timeout for each command to override the object default
130
+ max_threads: The maximum number of threads to use to override the object default
131
+ quiet: Whether to suppress output to override the object default
132
+ dry_run: Whether to simulate running the commands to override the object default
133
+ parse: Whether to parse ranged input to override the object default
134
+ sem: The semaphore to use for threading to override the object default
135
+ @returns:
136
+ Task: The Task Object of the command
137
+ '''
138
+ return self.run_commands([command],timeout=timeout,max_threads=max_threads,quiet=quiet,dry_run=dry_run,parse=parse,sem=sem)[0]
139
+
140
+ def wait(self, timeout=..., threads = ...):
141
+ '''
142
+ Wait for the threads to finish
143
+ @params:
144
+ timeout: The timeout for each command to override the object default
145
+ threads: The threads to join, default to all running threads managed by this object
146
+ @returns:
147
+ list[threading.Thread]: The list of running threads that are still running
148
+ '''
149
+ if threads is ...:
150
+ threads = self.runningThreads
151
+ if timeout is ...:
152
+ timeout = self.timeout
153
+ for thread in threads:
154
+ if timeout >= 0:
155
+ thread.join(timeout=timeout)
156
+ else:
157
+ thread.join()
158
+ self.runningThreads = [thread for thread in self.runningThreads if thread.is_alive()]
159
+ return self.runningThreads
160
+
161
+ def stop(self,timeout=...):
162
+ '''
163
+ Stop all running threads. This signals all threads to stop and joins them
164
+ @params:
165
+ None
166
+ @returns:
167
+ list[Task]: The list of tasks that are managed by this object
168
+ '''
169
+ for task in self.tasks:
170
+ task.stop = True
171
+ self.wait(timeout)
172
+ return self.tasks
173
+
174
+ def cleanup(self,timeout=...):
175
+ '''
176
+ Cleanup the tasks and threads. This calls stop() and clears the tasks and threads
177
+ @params:
178
+ None
179
+ @returns:
180
+ list[Task]: The list of tasks that are managed by this object
181
+ '''
182
+ self.stop(timeout)
183
+ self.tasks = []
184
+ self.runningThreads = []
185
+ return self.tasks
186
+
187
+ def join(self, timeout=..., threads = ..., print_error=True):
188
+ '''
189
+ Wait for the threads to finish and print error if there is any
190
+ @params:
191
+ timeout: The timeout for each command to override the object default
192
+ threads: The threads to wait for, default to all running threads managed by this object
193
+ @returns:
194
+ list[Task]: The list of tasks that are managed by this object
195
+ '''
196
+ self.wait(timeout=timeout, threads=threads)
197
+ for task in self.tasks:
198
+ if task.returncode != 0 and print_error:
199
+ print(f'Command: {task.command} failed with return code: {task.returncode}')
200
+ print('Stdout:')
201
+ print('\n '.join(task.stdout))
202
+ print('Stderr:')
203
+ print('\n '.join(task.stderr))
204
+ return self.tasks
205
+
206
+ def get_results(self, with_stdErr=False):
207
+ '''
208
+ Get the results of the tasks
209
+ @params:
210
+ with_stdErr: Whether to append the standard error output to the standard output
211
+ @returns:
212
+ list[list[str]]: The output of the tasks ( list[list[str]] )
213
+ '''
214
+ if with_stdErr:
215
+ return [task.stdout + task.stderr for task in self.tasks]
216
+ else:
217
+ return [task.stdout for task in self.tasks]
218
+
219
+ def get_return_codes(self):
220
+ '''
221
+ Get the return codes of the tasks
222
+ @params:
223
+ None
224
+ @returns:
225
+ list[int]: The return codes of the tasks ( list[int] )
226
+ '''
227
+ return [task.returncode for task in self.tasks]
43
228
 
44
229
  def _expand_ranges(inStr):
45
230
  '''
File without changes
File without changes
File without changes