datatypes 0.12.0__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.
- datatypes/__init__.py +153 -0
- datatypes/collections/__init__.py +22 -0
- datatypes/collections/container.py +150 -0
- datatypes/collections/mapping.py +614 -0
- datatypes/collections/sequence.py +469 -0
- datatypes/command.py +572 -0
- datatypes/compat.py +205 -0
- datatypes/config/__init__.py +9 -0
- datatypes/config/environ.py +302 -0
- datatypes/config/parser.py +255 -0
- datatypes/copy.py +151 -0
- datatypes/csv.py +322 -0
- datatypes/datetime.py +888 -0
- datatypes/decorators/__init__.py +23 -0
- datatypes/decorators/base.py +343 -0
- datatypes/decorators/descriptor.py +389 -0
- datatypes/decorators/misc.py +118 -0
- datatypes/email.py +351 -0
- datatypes/enum.py +191 -0
- datatypes/event.py +207 -0
- datatypes/filetypes/__init__.py +12 -0
- datatypes/filetypes/html.py +556 -0
- datatypes/filetypes/toml.py +260 -0
- datatypes/http.py +723 -0
- datatypes/logging.py +384 -0
- datatypes/number.py +187 -0
- datatypes/parse.py +246 -0
- datatypes/path.py +3637 -0
- datatypes/profile.py +232 -0
- datatypes/reflection.py +1498 -0
- datatypes/server.py +408 -0
- datatypes/service.py +212 -0
- datatypes/string.py +1328 -0
- datatypes/token/__init__.py +16 -0
- datatypes/token/abnf.py +1704 -0
- datatypes/token/base.py +475 -0
- datatypes/token/word.py +259 -0
- datatypes/url.py +841 -0
- datatypes/utils.py +142 -0
- datatypes-0.12.0.dist-info/LICENSE.txt +21 -0
- datatypes-0.12.0.dist-info/METADATA +52 -0
- datatypes-0.12.0.dist-info/RECORD +44 -0
- datatypes-0.12.0.dist-info/WHEEL +5 -0
- datatypes-0.12.0.dist-info/top_level.txt +1 -0
datatypes/command.py
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import unicode_literals, division, print_function, absolute_import
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from subprocess import (
|
|
6
|
+
CalledProcessError,
|
|
7
|
+
SubprocessError,
|
|
8
|
+
TimeoutExpired,
|
|
9
|
+
)
|
|
10
|
+
import sys
|
|
11
|
+
import re
|
|
12
|
+
import signal
|
|
13
|
+
import time
|
|
14
|
+
import logging
|
|
15
|
+
from collections import deque
|
|
16
|
+
import threading
|
|
17
|
+
|
|
18
|
+
from .compat import *
|
|
19
|
+
from .collections import Dict
|
|
20
|
+
from .utils import make_list
|
|
21
|
+
from .decorators import property as cachedproperty
|
|
22
|
+
from .string import String
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CaptureProcess(subprocess.Popen):
|
|
29
|
+
"""
|
|
30
|
+
https://docs.python.org/3/library/subprocess.html#subprocess.Popen
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, *args, **kwargs):
|
|
33
|
+
|
|
34
|
+
# our default is to capture both stdout and stderr. We change the default
|
|
35
|
+
# behavior a little bit in that None will be treated as /dev/null
|
|
36
|
+
#
|
|
37
|
+
# From the docs:
|
|
38
|
+
# If you wish to capture and combine both streams into one,
|
|
39
|
+
# use stdout=PIPE and stderr=STDOUT
|
|
40
|
+
#
|
|
41
|
+
# Valid values are PIPE, DEVNULL, an existing file descriptor (a
|
|
42
|
+
# positive integer), an existing file object with a valid file
|
|
43
|
+
# descriptor, and None. PIPE indicates that a new pipe to the child
|
|
44
|
+
# should be created. DEVNULL indicates that the special file os.devnull
|
|
45
|
+
# will be used.
|
|
46
|
+
#
|
|
47
|
+
# Additionally, stderr can be STDOUT, which indicates that the stderr
|
|
48
|
+
# data from the child process should be captured into the same file
|
|
49
|
+
# handle as for stdout.
|
|
50
|
+
kwargs.setdefault("stderr", subprocess.STDOUT)
|
|
51
|
+
kwargs.setdefault("stdout", subprocess.PIPE)
|
|
52
|
+
self.pipe_name = "stdout"
|
|
53
|
+
|
|
54
|
+
if kwargs["stderr"] and not kwargs["stdout"]:
|
|
55
|
+
kwargs["stderr"] = subprocess.PIPE
|
|
56
|
+
self.pipe_name = "stderr"
|
|
57
|
+
|
|
58
|
+
elif kwargs["stderr"] and kwargs["stdout"]:
|
|
59
|
+
kwargs["stderr"] = subprocess.STDOUT
|
|
60
|
+
|
|
61
|
+
if not kwargs["stderr"]:
|
|
62
|
+
kwargs["stderr"] = subprocess.DEVNULL
|
|
63
|
+
|
|
64
|
+
if not kwargs["stdout"]:
|
|
65
|
+
kwargs["stdout"] = subprocess.DEVNULL
|
|
66
|
+
|
|
67
|
+
# https://docs.python.org/3/library/collections.html#collections.deque
|
|
68
|
+
# Deques support thread-safe, memory efficient appends and pops
|
|
69
|
+
self.deque = deque(maxlen=kwargs.pop("bufsize", 1000))
|
|
70
|
+
self.returncodes = kwargs.pop("returncodes", [0])
|
|
71
|
+
self.returncode = None
|
|
72
|
+
super().__init__(*args, **kwargs)
|
|
73
|
+
|
|
74
|
+
def __iter__(self):
|
|
75
|
+
output = getattr(self, self.pipe_name)
|
|
76
|
+
try:
|
|
77
|
+
# another round of links
|
|
78
|
+
# http://stackoverflow.com/a/17413045/5006 (what I used)
|
|
79
|
+
# http://stackoverflow.com/questions/2715847/
|
|
80
|
+
#for line in iter(self.stdout.readline, b""):
|
|
81
|
+
for line in iter(output.readline, b""):
|
|
82
|
+
line = line.decode("utf-8")
|
|
83
|
+
self.deque.append(line)
|
|
84
|
+
yield line
|
|
85
|
+
|
|
86
|
+
finally:
|
|
87
|
+
output.close()
|
|
88
|
+
|
|
89
|
+
def is_running(self):
|
|
90
|
+
return self.poll() is None
|
|
91
|
+
|
|
92
|
+
def check_returncode(self, returncodes=None):
|
|
93
|
+
"""
|
|
94
|
+
https://docs.python.org/3/library/subprocess.html#subprocess.CompletedProcess.check_returncode
|
|
95
|
+
"""
|
|
96
|
+
if not returncodes:
|
|
97
|
+
returncodes = self.returncodes
|
|
98
|
+
returncodes = set(make_list(returncodes))
|
|
99
|
+
|
|
100
|
+
if self.returncode is not None:
|
|
101
|
+
if self.returncode not in returncodes:
|
|
102
|
+
raise CalledProcessError(
|
|
103
|
+
self.returncode,
|
|
104
|
+
self.args,
|
|
105
|
+
"".join(self.deque),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# raise CalledProcessError(
|
|
109
|
+
# "{} returned {}, expected one of {}".format(
|
|
110
|
+
# self.args,
|
|
111
|
+
# self.returncode,
|
|
112
|
+
# returncodes,
|
|
113
|
+
# ),
|
|
114
|
+
# self.args,
|
|
115
|
+
# )
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class SimpleCommand(object):
|
|
119
|
+
"""makes running a command from a non CLI environment easy peasy
|
|
120
|
+
|
|
121
|
+
This is handy when you need to test some CLI functionality of your python
|
|
122
|
+
modules.
|
|
123
|
+
|
|
124
|
+
https://docs.python.org/3/library/subprocess.html
|
|
125
|
+
|
|
126
|
+
Moved here from testdata.client.Command on 7-9-2023
|
|
127
|
+
|
|
128
|
+
:Example:
|
|
129
|
+
c = Command("echo")
|
|
130
|
+
r = c.run("hello")
|
|
131
|
+
print(r) # hello
|
|
132
|
+
"""
|
|
133
|
+
quiet = False
|
|
134
|
+
"""this is the default quiet setting for running a script, it can
|
|
135
|
+
be overriden in run()"""
|
|
136
|
+
|
|
137
|
+
process_class = CaptureProcess
|
|
138
|
+
"""The class that will be used in .create_process, needs to be a subclass
|
|
139
|
+
of Popen, honestly, it will have to be a subclass of CaptureProcess since
|
|
140
|
+
it relies on the deque property and check_returncode method
|
|
141
|
+
|
|
142
|
+
https://docs.python.org/3/library/subprocess.html#subprocess.Popen
|
|
143
|
+
"""
|
|
144
|
+
@cachedproperty(cached="_environ")
|
|
145
|
+
def environ(self):
|
|
146
|
+
environ = dict(os.environ)
|
|
147
|
+
pythonpath = self.cwd
|
|
148
|
+
if "PYTHONPATH" in environ:
|
|
149
|
+
environ["PYTHONPATH"] += os.pathsep + pythonpath
|
|
150
|
+
else:
|
|
151
|
+
environ["PYTHONPATH"] = pythonpath
|
|
152
|
+
|
|
153
|
+
if os.getcwd() not in environ["PYTHONPATH"]:
|
|
154
|
+
environ["PYTHONPATH"] += os.pathsep + os.getcwd()
|
|
155
|
+
|
|
156
|
+
return environ
|
|
157
|
+
|
|
158
|
+
def __init__(self, command, cwd="", environ=None, **kwargs):
|
|
159
|
+
self.cwd = os.path.realpath(cwd) if cwd else os.getcwd()
|
|
160
|
+
self.command = command
|
|
161
|
+
|
|
162
|
+
self.logger_prefix = kwargs.pop("logger_prefix", None)
|
|
163
|
+
self.logger = self.get_logger(**kwargs)
|
|
164
|
+
|
|
165
|
+
self.sudo = kwargs.pop("sudo", False)
|
|
166
|
+
|
|
167
|
+
if environ is not None:
|
|
168
|
+
self.environ = environ
|
|
169
|
+
|
|
170
|
+
self.kwargs = kwargs
|
|
171
|
+
|
|
172
|
+
def _flush(self, process, line):
|
|
173
|
+
"""flush the line to stdout"""
|
|
174
|
+
if not process.quiet:
|
|
175
|
+
if self.logger_prefix is None:
|
|
176
|
+
self.logger_prefix = "{:0>5}: ".format(process.pid)
|
|
177
|
+
self.logger.info("{}{}".format(self.logger_prefix, line.rstrip()))
|
|
178
|
+
|
|
179
|
+
def get_logger(self, **kwargs):
|
|
180
|
+
logger = logging.getLogger(f"{__name__}.Command")
|
|
181
|
+
if len(logger.handlers) == 0:
|
|
182
|
+
logger.setLevel(logging.INFO)
|
|
183
|
+
log_handler = logging.StreamHandler(stream=sys.stdout)
|
|
184
|
+
log_handler.setFormatter(logging.Formatter('%(message)s'))
|
|
185
|
+
logger.addHandler(log_handler)
|
|
186
|
+
logger.propagate = False
|
|
187
|
+
return logger
|
|
188
|
+
|
|
189
|
+
def create_cmd(self, command, arg_str):
|
|
190
|
+
if command:
|
|
191
|
+
if isinstance(command, basestring):
|
|
192
|
+
cmd = command
|
|
193
|
+
if arg_str:
|
|
194
|
+
if isinstance(arg_str, basestring):
|
|
195
|
+
cmd += " " + arg_str
|
|
196
|
+
else:
|
|
197
|
+
cmd += " ".join(arg_str)
|
|
198
|
+
|
|
199
|
+
else:
|
|
200
|
+
cmd = list(command)
|
|
201
|
+
if arg_str:
|
|
202
|
+
if isinstance(arg_str, basestring):
|
|
203
|
+
cmd.append(arg_str)
|
|
204
|
+
else:
|
|
205
|
+
cmd.extend(arg_str)
|
|
206
|
+
|
|
207
|
+
else:
|
|
208
|
+
cmd = arg_str
|
|
209
|
+
|
|
210
|
+
if self.sudo:
|
|
211
|
+
if isinstance(cmd, basestring):
|
|
212
|
+
if not cmd.startswith("sudo"):
|
|
213
|
+
cmd = "sudo " + cmd
|
|
214
|
+
|
|
215
|
+
else:
|
|
216
|
+
if not cmd[0] == "sudo":
|
|
217
|
+
cmd.insert(0, "sudo")
|
|
218
|
+
|
|
219
|
+
return cmd
|
|
220
|
+
|
|
221
|
+
def create_process(self, arg_str, **kwargs):
|
|
222
|
+
"""
|
|
223
|
+
https://docs.python.org/3/library/subprocess.html
|
|
224
|
+
"""
|
|
225
|
+
process = None
|
|
226
|
+
cmd = self.create_cmd(self.command, arg_str)
|
|
227
|
+
|
|
228
|
+
kwargs = Dict(self.kwargs, kwargs)
|
|
229
|
+
quiet = kwargs.pop("quiet", self.quiet)
|
|
230
|
+
|
|
231
|
+
retcode = kwargs.pops([
|
|
232
|
+
"returncodes",
|
|
233
|
+
"returncode",
|
|
234
|
+
"code",
|
|
235
|
+
"ret_code",
|
|
236
|
+
"retcode",
|
|
237
|
+
"expected_ret_code",
|
|
238
|
+
"expected_returncode",
|
|
239
|
+
"codes",
|
|
240
|
+
"ret_codes",
|
|
241
|
+
"retcodes",
|
|
242
|
+
"expected_ret_codes",
|
|
243
|
+
"expected_returncodes",
|
|
244
|
+
], 0)
|
|
245
|
+
kwargs["returncodes"] = retcode
|
|
246
|
+
|
|
247
|
+
environ = self.environ
|
|
248
|
+
environ.update(kwargs.pop("environ", {}))
|
|
249
|
+
environ.update(kwargs.pop("env", {}))
|
|
250
|
+
# any kwargs with all capital letters should be considered environment
|
|
251
|
+
# variables
|
|
252
|
+
for k in list(kwargs.keys()):
|
|
253
|
+
if k.isupper():
|
|
254
|
+
environ[k] = kwargs.pop(k)
|
|
255
|
+
|
|
256
|
+
# make sure each value is a string
|
|
257
|
+
for k in environ.keys():
|
|
258
|
+
if not isinstance(environ[k], basestring):
|
|
259
|
+
environ[k] = String(environ[k])
|
|
260
|
+
|
|
261
|
+
# we will not allow these to be overridden via kwargs
|
|
262
|
+
kwargs["shell"] = isinstance(cmd, basestring)
|
|
263
|
+
kwargs["cwd"] = self.cwd
|
|
264
|
+
kwargs["env"] = environ
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
process = self.process_class(
|
|
268
|
+
cmd,
|
|
269
|
+
**kwargs
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
except CalledProcessError as e:
|
|
273
|
+
process.returncode = e.returncode
|
|
274
|
+
|
|
275
|
+
finally:
|
|
276
|
+
if process:
|
|
277
|
+
process.quiet = quiet
|
|
278
|
+
|
|
279
|
+
return process
|
|
280
|
+
|
|
281
|
+
def run(self, arg_str="", **kwargs):
|
|
282
|
+
"""runs the passed in arguments
|
|
283
|
+
|
|
284
|
+
:param arg_str: string, the argument flags that will be passed to the command
|
|
285
|
+
:param **kwargs: These will be passed to subprocess or consumed
|
|
286
|
+
:returns: string, the string of the output and will have .returncode attribute
|
|
287
|
+
"""
|
|
288
|
+
process = self.create_process(arg_str, **kwargs)
|
|
289
|
+
|
|
290
|
+
# consume all the output from the process
|
|
291
|
+
for line in process:
|
|
292
|
+
self._flush(process, line)
|
|
293
|
+
|
|
294
|
+
process.wait()
|
|
295
|
+
process.check_returncode()
|
|
296
|
+
|
|
297
|
+
# we wrap the output in a String so we can set returncode
|
|
298
|
+
ret = String("".join(process.deque))
|
|
299
|
+
ret.returncode = process.returncode
|
|
300
|
+
return ret
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class Command(SimpleCommand):
|
|
304
|
+
"""Runs a command in a thread when you call .start and syncronously when
|
|
305
|
+
.run is called
|
|
306
|
+
|
|
307
|
+
??? - Could I switch to using ThreadPoolExecutor instead?
|
|
308
|
+
https://docs.python.org/3/library/concurrent.futures.html
|
|
309
|
+
|
|
310
|
+
:Example:
|
|
311
|
+
c = Command("<SOME COMMAND>")
|
|
312
|
+
c.start()
|
|
313
|
+
while r := c.wait(1):
|
|
314
|
+
if "<SOME SENTINAL STRING>" in r:
|
|
315
|
+
break
|
|
316
|
+
"""
|
|
317
|
+
process = None
|
|
318
|
+
"""Will hold the process that's started when .start() is called"""
|
|
319
|
+
|
|
320
|
+
thread_class = threading.Thread
|
|
321
|
+
"""the threading class to use when .start is called"""
|
|
322
|
+
|
|
323
|
+
def is_running(self):
|
|
324
|
+
"""True if the process is running"""
|
|
325
|
+
return (self.process.poll() is None) if self.process else False
|
|
326
|
+
|
|
327
|
+
def quit(self, timeout=1):
|
|
328
|
+
"""same as .terminate but uses signals"""
|
|
329
|
+
return self.finish("send_signal", timeout=timeout, args=(signal.SIGTERM,))
|
|
330
|
+
|
|
331
|
+
def kill(self, timeout=1):
|
|
332
|
+
"""kill -9 the script running asyncronously"""
|
|
333
|
+
return self.finish("kill", timeout=timeout)
|
|
334
|
+
|
|
335
|
+
def stop(self, timeout=1):
|
|
336
|
+
"""This is just here because I use .start/.stop a lot and so it's strange
|
|
337
|
+
not having a .stop method"""
|
|
338
|
+
self.kill()
|
|
339
|
+
|
|
340
|
+
def terminate(self, timeout=1):
|
|
341
|
+
"""terminate the script running asyncronously"""
|
|
342
|
+
return self.finish("terminate", timeout=timeout)
|
|
343
|
+
|
|
344
|
+
def murder(self, timeout=1):
|
|
345
|
+
"""use pkill to kill any process that matches self.process.args"""
|
|
346
|
+
process_cmd = self.process.args
|
|
347
|
+
cmd = self.create_cmd(["pkill", "-f", process_cmd], "")
|
|
348
|
+
logger.debug("Murdering {}".format(process_cmd))
|
|
349
|
+
subprocess.run(cmd, check=False)
|
|
350
|
+
return self.wait(timeout)
|
|
351
|
+
|
|
352
|
+
def finish(self, method_name, timeout=1, maxcount=5, args=None, kwargs=None):
|
|
353
|
+
"""Internal method that kills the async process, if it can't shut it down
|
|
354
|
+
soft it will shut it down hard
|
|
355
|
+
|
|
356
|
+
:param method_name: str, the method name that will first be attempted
|
|
357
|
+
:param timeout: int, how long to wait for each attempted method_name call
|
|
358
|
+
:param maxcount: int, how many times to attempt calling method_name
|
|
359
|
+
:param *args: passed to method_name call
|
|
360
|
+
:param **kwargs: passed to method_name call
|
|
361
|
+
:returns: str, the buffer
|
|
362
|
+
"""
|
|
363
|
+
ret = []
|
|
364
|
+
|
|
365
|
+
args = args or ()
|
|
366
|
+
kwargs = kwargs or {}
|
|
367
|
+
process = self.process
|
|
368
|
+
|
|
369
|
+
while self.is_running():
|
|
370
|
+
getattr(process, method_name)(*args, **kwargs)
|
|
371
|
+
ret.append(self.wait(timeout=timeout))
|
|
372
|
+
if process.returncode is None:
|
|
373
|
+
count += 1
|
|
374
|
+
if count >= maxcount:
|
|
375
|
+
ret.append(self.murder())
|
|
376
|
+
|
|
377
|
+
return "".join(ret)
|
|
378
|
+
|
|
379
|
+
def start(self, arg_str="", **kwargs):
|
|
380
|
+
"""Run the command asyncronously, see parent's .run"""
|
|
381
|
+
self.process = self.create_process(arg_str, **kwargs)
|
|
382
|
+
|
|
383
|
+
def target():
|
|
384
|
+
for line in self.process:
|
|
385
|
+
self._flush(self.process, line)
|
|
386
|
+
|
|
387
|
+
t = self.thread_class(target=target)
|
|
388
|
+
t.daemon = True
|
|
389
|
+
t.start()
|
|
390
|
+
self.async_thread = t
|
|
391
|
+
return self.process
|
|
392
|
+
|
|
393
|
+
def join(self, *args, **kwargs): return self.wait(*args, **kwargs)
|
|
394
|
+
def wait(self, timeout=None):
|
|
395
|
+
"""After .run is called you can call this to return current output from
|
|
396
|
+
when .run was called or when this method was last called
|
|
397
|
+
|
|
398
|
+
:param timeout: int, how long to wait
|
|
399
|
+
:returns: str, the output, the str also has a returncode value, which
|
|
400
|
+
will be None if the process isn't done yet
|
|
401
|
+
"""
|
|
402
|
+
try:
|
|
403
|
+
self.process.wait(timeout=timeout)
|
|
404
|
+
|
|
405
|
+
except subprocess.TimeoutExpired:
|
|
406
|
+
pass
|
|
407
|
+
|
|
408
|
+
retcode = self.process.returncode
|
|
409
|
+
if retcode is not None and retcode > 0:
|
|
410
|
+
self.process.check_returncode()
|
|
411
|
+
|
|
412
|
+
ret = "".join(self.process.deque)
|
|
413
|
+
self.process.deque.clear()
|
|
414
|
+
|
|
415
|
+
ret = String(ret)
|
|
416
|
+
ret.returncode = retcode
|
|
417
|
+
|
|
418
|
+
return ret
|
|
419
|
+
|
|
420
|
+
def wait_for(self, callback, timeout=0.1, maxcount=10):
|
|
421
|
+
"""Wait until callback returns True or until maxcount * timeout is hit
|
|
422
|
+
|
|
423
|
+
:param callback: callable|str|re.Pattern, if a callable, then pass the
|
|
424
|
+
program output to callable until it returns True. If a string then
|
|
425
|
+
check string in output. If regex pattern then run search on output
|
|
426
|
+
:param timeout: float, each incremental timeout before next check
|
|
427
|
+
:param maxcount: int, the maximum times to check callback before failing
|
|
428
|
+
and just returning output
|
|
429
|
+
:returns str, the output, same as .wait()
|
|
430
|
+
"""
|
|
431
|
+
if not callable(callback):
|
|
432
|
+
if isinstance(callback, str):
|
|
433
|
+
needle = callback
|
|
434
|
+
callback = lambda haystack: needle in haystack
|
|
435
|
+
|
|
436
|
+
elif isinstance(callback, re.Pattern):
|
|
437
|
+
regex = callback
|
|
438
|
+
callback = lambda haystack: regex.search(haystack)
|
|
439
|
+
|
|
440
|
+
else:
|
|
441
|
+
raise ValueError(f"Not sure what to do with {callback}")
|
|
442
|
+
|
|
443
|
+
haystack = String("")
|
|
444
|
+
haystack.returncode = None
|
|
445
|
+
|
|
446
|
+
count = 0
|
|
447
|
+
while count < maxcount:
|
|
448
|
+
count += 1
|
|
449
|
+
|
|
450
|
+
hay = self.wait(timeout=timeout)
|
|
451
|
+
haystack = String(haystack + hay)
|
|
452
|
+
haystack.returncode = hay.returncode
|
|
453
|
+
|
|
454
|
+
if callback(haystack):
|
|
455
|
+
break
|
|
456
|
+
|
|
457
|
+
if haystack.returncode is not None:
|
|
458
|
+
break
|
|
459
|
+
|
|
460
|
+
return haystack
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class ModuleCommand(Command):
|
|
464
|
+
"""This sets the client up so you can just pass the module name and have everything
|
|
465
|
+
just work
|
|
466
|
+
|
|
467
|
+
Moved here from testdata.client.Command on 7-12-2023
|
|
468
|
+
|
|
469
|
+
:Example:
|
|
470
|
+
#You can setup this command 2 ways
|
|
471
|
+
|
|
472
|
+
# 1 - pass in the module name
|
|
473
|
+
c = ModuleCommand("module_name")
|
|
474
|
+
|
|
475
|
+
# 2 - have the parent set it
|
|
476
|
+
class MyCommand(ModuleCommand):
|
|
477
|
+
name = "module_name"
|
|
478
|
+
c = MyCommand()
|
|
479
|
+
"""
|
|
480
|
+
cmd_prefix = "{} -m".format(sys.executable)
|
|
481
|
+
"""this is what will be used to invoke captain from the command line when run()
|
|
482
|
+
is called"""
|
|
483
|
+
|
|
484
|
+
name = ""
|
|
485
|
+
"""This is the module name you want to run"""
|
|
486
|
+
|
|
487
|
+
def __init__(self, name="", cwd="", environ=None, **kwargs):
|
|
488
|
+
if name:
|
|
489
|
+
self.name = name
|
|
490
|
+
|
|
491
|
+
if not self.name:
|
|
492
|
+
raise ValueError("No name specified")
|
|
493
|
+
|
|
494
|
+
kwargs.setdefault("command", None)
|
|
495
|
+
|
|
496
|
+
super().__init__(
|
|
497
|
+
cwd=cwd,
|
|
498
|
+
environ=environ,
|
|
499
|
+
**kwargs
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
def create_cmd(self, command, arg_str):
|
|
503
|
+
if isinstance(command, basestring):
|
|
504
|
+
ret = "{} {} {}".format(self.cmd_prefix, self.name, command)
|
|
505
|
+
|
|
506
|
+
else:
|
|
507
|
+
ret = re.split(r"\s+", self.cmd_prefix)
|
|
508
|
+
ret.append(self.name)
|
|
509
|
+
if command:
|
|
510
|
+
ret.extend(command)
|
|
511
|
+
|
|
512
|
+
return super().create_cmd(ret, arg_str)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
class FileCommand(ModuleCommand):
|
|
516
|
+
"""This will add the .py to a script so you don't have to
|
|
517
|
+
|
|
518
|
+
Moved here from testdata.client.Command on 7-12-2023
|
|
519
|
+
|
|
520
|
+
an example might be best to understand how the command gets put together
|
|
521
|
+
|
|
522
|
+
Let's say you wanted to run this path:
|
|
523
|
+
|
|
524
|
+
/foo/bar/program.py
|
|
525
|
+
|
|
526
|
+
You could configure this class like this:
|
|
527
|
+
|
|
528
|
+
class ProgamCommand(FileCommand):
|
|
529
|
+
script_prefix = "/foo/bar"
|
|
530
|
+
script_postfix = ".py"
|
|
531
|
+
name = "program"
|
|
532
|
+
|
|
533
|
+
Then when you need to run program.py, you only need to do:
|
|
534
|
+
|
|
535
|
+
p = ProgramCommand()
|
|
536
|
+
p.run() # runs "python /foo/bar/program.py"
|
|
537
|
+
|
|
538
|
+
But you also could run it:
|
|
539
|
+
|
|
540
|
+
p = FileCommand("/foo/bar/program.py")
|
|
541
|
+
|
|
542
|
+
or:
|
|
543
|
+
class ProgamCommand(FileCommand):
|
|
544
|
+
script_prefix = "/foo/bar"
|
|
545
|
+
|
|
546
|
+
p = ProgramCommand("program.py")
|
|
547
|
+
"""
|
|
548
|
+
cmd_prefix = sys.executable
|
|
549
|
+
"""If you have a space in the executable path that might be really bad"""
|
|
550
|
+
|
|
551
|
+
script_prefix = ""
|
|
552
|
+
"""this will be prepended to the passed in script on initialization"""
|
|
553
|
+
|
|
554
|
+
script_postfix = ""
|
|
555
|
+
"""this will be appended to the passed in script on initialization"""
|
|
556
|
+
|
|
557
|
+
def __init__(self, fileroot="", cwd="", environ=None, **kwargs):
|
|
558
|
+
if fileroot:
|
|
559
|
+
self.name = fileroot
|
|
560
|
+
|
|
561
|
+
if not self.name:
|
|
562
|
+
raise ValueError("no name found")
|
|
563
|
+
|
|
564
|
+
path = self.name
|
|
565
|
+
if self.script_prefix and not fileroot.startswith(self.script_prefix):
|
|
566
|
+
path = os.path.join(self.script_prefix.rstrip("/"), fileroot)
|
|
567
|
+
|
|
568
|
+
if self.script_postfix and not path.endswith(self.script_postfix):
|
|
569
|
+
path += self.script_postfix
|
|
570
|
+
|
|
571
|
+
super().__init__(path, cwd=cwd, environ=environ, **kwargs)
|
|
572
|
+
|