|
1 | | -import os |
2 | | -import time |
3 | | -import json |
4 | | -import inspect |
5 | | -import importlib |
6 | | - |
7 | 1 | import typer |
8 | 2 | import logging |
9 | | -from rich.progress import Progress, TextColumn, BarColumn, TimeRemainingColumn, TimeElapsedColumn |
10 | | -from typing import Union, Pattern, Required, Optional, Callable |
11 | 3 |
|
12 | 4 | from . import __version__ |
13 | | -from .utils import DebugOption, VerboseOption, QuietOption, verbose_args_processor, kwargs_processor |
| 5 | +from .utils import DebugOption, VerboseOption, QuietOption, verbose_args_processor |
14 | 6 | logger = logging.getLogger('base') |
15 | 7 |
|
16 | 8 |
|
@@ -123,168 +115,3 @@ def config(configuration: str): |
123 | 115 | [blue]Configure[/blue] the system. :wrench: |
124 | 116 | """ |
125 | 117 | typer.echo('Coming soon!') |
126 | | - |
127 | | - |
128 | | - |
129 | | -@cli_base.command(context_settings = {'allow_extra_args': True}, no_args_is_help = True) |
130 | | -def process( |
131 | | - ctx: typer.Context, |
132 | | - func: str = typer.Argument(help = '(path.to.file:function_name) OR (lambda x: x)'), |
133 | | - dataset: str = typer.Argument(help = '(path/to/file.txt) OR ([ i for i in range(2) ])'), |
134 | | - |
135 | | - args: list[str] = typer.Option([], '--args', '-a', help = 'Arguments passed to each thread'), |
136 | | - threads: int = typer.Option(8, '--threads', '-t', help = 'Maximum number of threads (will scale down based on dataset size)'), |
137 | | - |
138 | | - daemon: bool = typer.Option(False, '--daemon', '-d', help = 'Threads to run in daemon mode'), |
139 | | - output: str = typer.Option('./output.json', '--output', '-o', help = 'Output file location'), |
140 | | - fileout: bool = typer.Option(True, '--fileout', is_flag = True, help = 'Weather to write output to a file'), |
141 | | - stdout: bool = typer.Option(False, '--stdout', is_flag = True, help = 'Weather to print the output'), |
142 | | - |
143 | | - debug: bool = DebugOption, |
144 | | - verbose: bool = VerboseOption, |
145 | | - quiet: bool = QuietOption |
146 | | -): |
147 | | - """ |
148 | | - [bold]Utilise parallel processing on a dataset[/bold] |
149 | | - |
150 | | - \b\n |
151 | | - [bold]:glowing_star: Examples[/bold] |
152 | | - Kwargs can be parsed by adding overflow arguments in pairs |
153 | | - [green]$ thread process ... k1 v1 k2 v2[/green] |
154 | | - => kwargs = {k1: v1, k2: v2} |
155 | | -
|
156 | | - Single kwargs will be ignored |
157 | | - [green]$ thread process ... a1[/green] |
158 | | - => kwargs = {} |
159 | | - |
160 | | - """ |
161 | | - verbose_args_processor(debug, verbose, quiet) |
162 | | - kwargs = kwargs_processor(ctx) |
163 | | - logger.debug('Processed kwargs: %s' % kwargs) |
164 | | - |
165 | | - |
166 | | - # Verify output |
167 | | - if not fileout and not stdout: |
168 | | - raise typer.BadParameter('No output method specified') |
169 | | - |
170 | | - if fileout and not os.path.exists('/'.join(output.split('/')[:-1])): |
171 | | - raise typer.BadParameter('Output file directory does not exist') |
172 | | - |
173 | | - |
174 | | - |
175 | | - |
176 | | - # Loading function |
177 | | - f = None |
178 | | - try: |
179 | | - logger.info('Attempted to interpret function') |
180 | | - f = eval(func) # I know eval is bad practice, but I have yet to find a safer replacement |
181 | | - logger.debug(f'Evaluated function: %s' % f) |
182 | | - |
183 | | - if not inspect.isfunction(f): |
184 | | - logger.info('Invalid function') |
185 | | - except Exception: |
186 | | - logger.info('Failed to interpret function') |
187 | | - |
188 | | - if not f: |
189 | | - try: |
190 | | - logger.info('Attempting to fetch function file') |
191 | | - |
192 | | - fPath, fName = func.split(':') |
193 | | - f = importlib.import_module(fPath).__dict__[fName] |
194 | | - logger.debug(f'Evaluated function: %s' % f) |
195 | | - |
196 | | - if not inspect.isfunction(f): |
197 | | - logger.info('Not a function') |
198 | | - raise Exception('Not a function') |
199 | | - except Exception as e: |
200 | | - logger.warning('Failed to fetch function') |
201 | | - raise typer.BadParameter('Failed to fetch function') from e |
202 | | - |
203 | | - |
204 | | - |
205 | | - |
206 | | - # Loading dataset |
207 | | - ds: Union[list, tuple, set, None] = None |
208 | | - try: |
209 | | - logger.info('Attempting to interpret dataset') |
210 | | - ds = eval(dataset) |
211 | | - logger.debug(f'Evaluated dataset: %s' % (str(ds)[:125] + '...' if len(str(ds)) > 125 else ds)) |
212 | | - |
213 | | - if not isinstance(ds, (list, tuple, set)): |
214 | | - logger.info('Invalid dataset literal') |
215 | | - ds = None |
216 | | - |
217 | | - except Exception: |
218 | | - logger.info('Failed to interpret dataset') |
219 | | - |
220 | | - if not ds: |
221 | | - try: |
222 | | - logger.info('Attempting to fetch data file') |
223 | | - if not os.path.isfile(dataset): |
224 | | - logger.info('Invalid file path') |
225 | | - raise Exception('Invalid file path') |
226 | | - |
227 | | - with open(dataset, 'r') as a: |
228 | | - ds = [ i.endswith('\n') and i[:-2] for i in a.readlines() ] |
229 | | - except Exception as e: |
230 | | - logger.warning('Failed to read dataset') |
231 | | - raise typer.BadParameter('Failed to read dataset') from e |
232 | | - |
233 | | - logger.info('Interpreted dataset') |
234 | | - |
235 | | - |
236 | | - # Setup |
237 | | - logger.debug('Importing module') |
238 | | - from ..thread import ParallelProcessing |
239 | | - logger.info('Spawning threads... [Expected: {tcount} threads]'.format(tcount=min(len(ds), threads))) |
240 | | - |
241 | | - newProcess = ParallelProcessing( |
242 | | - function = f, |
243 | | - dataset = list(ds), |
244 | | - args = args, |
245 | | - kwargs = kwargs, |
246 | | - daemon = daemon, |
247 | | - max_threads = threads |
248 | | - ) |
249 | | - |
250 | | - logger.info('Created parallel process') |
251 | | - logger.info('Starting parallel process') |
252 | | - |
253 | | - start_t = time.perf_counter() |
254 | | - newProcess.start() |
255 | | - |
256 | | - typer.echo('Started parallel process') |
257 | | - typer.echo('Waiting for parallel process to complete, this may take a while...') |
258 | | - |
259 | | - with Progress( |
260 | | - TextColumn('[bold blue]{task.description}', justify = 'right'), |
261 | | - BarColumn(bar_width = None), |
262 | | - '[progress.percentage]{task.percentage:>3.1f}%', |
263 | | - '•', |
264 | | - TimeRemainingColumn(), |
265 | | - '•', |
266 | | - TimeElapsedColumn() |
267 | | - ) as progress: |
268 | | - percentage = 0 |
269 | | - job = progress.add_task('Working...', total = 100) |
270 | | - |
271 | | - while percentage < 100: |
272 | | - percentage = round(sum(i.progress for i in newProcess._threads) / (len(newProcess._threads) or 8), 2) * 100 |
273 | | - progress.update(job, completed = percentage) |
274 | | - time.sleep(0.1) |
275 | | - |
276 | | - result = newProcess.get_return_values() |
277 | | - |
278 | | - typer.echo(f'Completed in {(time.perf_counter() - start_t):.5f}s') |
279 | | - if fileout: |
280 | | - typer.echo(f'Writing file to {output}...') |
281 | | - try: |
282 | | - with open(output, 'w') as f: |
283 | | - json.dump(result, f, indent = 2) |
284 | | - typer.echo(f'Wrote to file') |
285 | | - except Exception as e: |
286 | | - logger.error('Failed to write to file') |
287 | | - logger.debug(str(e)) |
288 | | - |
289 | | - if stdout: |
290 | | - typer.echo(result) |
0 commit comments