iripau 0.1.0__py3-none-any.whl → 1.1.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.
- iripau/requests.py +81 -0
- iripau/subprocess.py +55 -21
- {iripau-0.1.0.dist-info → iripau-1.1.0.dist-info}/METADATA +3 -2
- {iripau-0.1.0.dist-info → iripau-1.1.0.dist-info}/RECORD +7 -6
- {iripau-0.1.0.dist-info → iripau-1.1.0.dist-info}/WHEEL +0 -0
- {iripau-0.1.0.dist-info → iripau-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {iripau-0.1.0.dist-info → iripau-1.1.0.dist-info}/top_level.txt +0 -0
iripau/requests.py
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
"""
|
2
|
+
Utilities for the requests module
|
3
|
+
"""
|
4
|
+
|
5
|
+
import requests
|
6
|
+
|
7
|
+
from curlify import to_curl
|
8
|
+
|
9
|
+
from iripau.subprocess import TeeStreams, Popen
|
10
|
+
|
11
|
+
|
12
|
+
def curlify(
|
13
|
+
response, compressed=False, verify=True, pretty=False,
|
14
|
+
hide_output=False, headers_to_hide=[], headers_to_omit=[],
|
15
|
+
stdout_tees: TeeStreams = [], add_global_stdout_tees=True,
|
16
|
+
stderr_tees: TeeStreams = [], add_global_stderr_tees=True,
|
17
|
+
prompt_tees: TeeStreams = [], add_global_prompt_tees=True,
|
18
|
+
echo=None
|
19
|
+
):
|
20
|
+
""" Simulate the request was executed by a curl subprocess.
|
21
|
+
The command and output can be echoed and/or sent to files as described
|
22
|
+
in subprocess.run
|
23
|
+
"""
|
24
|
+
request = response.request
|
25
|
+
if headers_to_hide or headers_to_omit:
|
26
|
+
request = request.copy()
|
27
|
+
|
28
|
+
for header in headers_to_omit:
|
29
|
+
if header in request.headers:
|
30
|
+
del request.headers[header]
|
31
|
+
|
32
|
+
for header in headers_to_hide:
|
33
|
+
if header in request.headers:
|
34
|
+
request.headers[header] = "***"
|
35
|
+
|
36
|
+
stdout = hide_output and b"***" or response.content
|
37
|
+
if not stdout.endswith(b"\n"):
|
38
|
+
stdout += b"\n"
|
39
|
+
stderr = ""
|
40
|
+
|
41
|
+
Popen.simulate(
|
42
|
+
cmd=to_curl(request, compressed, verify, pretty),
|
43
|
+
stdout=stdout,
|
44
|
+
stderr=stderr,
|
45
|
+
comment=f"{response.status_code} - {response.reason}",
|
46
|
+
stdout_tees=stdout_tees,
|
47
|
+
stderr_tees=stderr_tees,
|
48
|
+
prompt_tees=prompt_tees,
|
49
|
+
add_global_stdout_tees=add_global_stdout_tees,
|
50
|
+
add_global_stderr_tees=add_global_stderr_tees,
|
51
|
+
add_global_prompt_tees=add_global_prompt_tees,
|
52
|
+
echo=echo
|
53
|
+
)
|
54
|
+
|
55
|
+
|
56
|
+
class Session(requests.Session):
|
57
|
+
""" A requests.Session that accepts curlify arguments in the request method """
|
58
|
+
|
59
|
+
def request(
|
60
|
+
self, *args, compressed=False, pretty=False,
|
61
|
+
hide_output=False, headers_to_hide=[], headers_to_omit=[],
|
62
|
+
stdout_tees: TeeStreams = [], add_global_stdout_tees=True,
|
63
|
+
stderr_tees: TeeStreams = [], add_global_stderr_tees=True,
|
64
|
+
prompt_tees: TeeStreams = [], add_global_prompt_tees=True,
|
65
|
+
echo=None, **kwargs
|
66
|
+
):
|
67
|
+
response = super().request(*args, **kwargs)
|
68
|
+
|
69
|
+
verify = kwargs.get("verify")
|
70
|
+
if verify is None:
|
71
|
+
verify = self.verify
|
72
|
+
|
73
|
+
curlify(
|
74
|
+
response, compressed, verify, pretty,
|
75
|
+
hide_output, headers_to_hide, headers_to_omit,
|
76
|
+
stdout_tees, add_global_stdout_tees,
|
77
|
+
stderr_tees, add_global_stderr_tees,
|
78
|
+
prompt_tees, add_global_prompt_tees,
|
79
|
+
echo
|
80
|
+
)
|
81
|
+
return response
|
iripau/subprocess.py
CHANGED
@@ -5,7 +5,6 @@ This module relies on the following system utilities being installed:
|
|
5
5
|
* bash
|
6
6
|
* kill
|
7
7
|
* pstree
|
8
|
-
* sudo
|
9
8
|
* tee
|
10
9
|
"""
|
11
10
|
|
@@ -38,6 +37,7 @@ GLOBAL_PROMPTS = set()
|
|
38
37
|
|
39
38
|
|
40
39
|
TeeStream = Union[io.IOBase, Callable[[], io.IOBase]]
|
40
|
+
TeeStreams = Iterable[TeeStream]
|
41
41
|
|
42
42
|
|
43
43
|
class PipeFile(SpooledTemporaryFile):
|
@@ -124,27 +124,21 @@ class Popen(subprocess.Popen):
|
|
124
124
|
|
125
125
|
def __init__(
|
126
126
|
self, args, *, cwd=None, env=None, encoding=None, errors=None, text=None,
|
127
|
-
stdout_tees:
|
128
|
-
stderr_tees:
|
129
|
-
prompt_tees:
|
127
|
+
stdout_tees: TeeStreams = [], add_global_stdout_tees=True,
|
128
|
+
stderr_tees: TeeStreams = [], add_global_stderr_tees=True,
|
129
|
+
prompt_tees: TeeStreams = [], add_global_prompt_tees=True,
|
130
130
|
echo=None, alias=None, comment=None, **kwargs
|
131
131
|
):
|
132
132
|
stdout = kwargs.get("stdout")
|
133
133
|
stderr = kwargs.get("stderr")
|
134
134
|
|
135
|
-
stdout_tees, stderr_tees, prompt_tees = self._get_tee_sets(
|
135
|
+
stdout_tees, stderr_tees, prompt_tees, err2out = self._get_tee_sets(
|
136
136
|
stdout_tees, add_global_stdout_tees,
|
137
137
|
stderr_tees, add_global_stderr_tees,
|
138
138
|
prompt_tees, add_global_prompt_tees,
|
139
139
|
echo, stdout, stderr
|
140
140
|
)
|
141
141
|
|
142
|
-
if stderr is STDOUT:
|
143
|
-
err2out = True
|
144
|
-
stderr_tees = set()
|
145
|
-
else:
|
146
|
-
err2out = False
|
147
|
-
|
148
142
|
stdout_fds = {tee.fileno() for tee in stdout_tees}
|
149
143
|
stderr_fds = {tee.fileno() for tee in stderr_tees}
|
150
144
|
prompt_fds = {tee.fileno() for tee in prompt_tees}
|
@@ -176,7 +170,7 @@ class Popen(subprocess.Popen):
|
|
176
170
|
self.stderr_process = stderr_process
|
177
171
|
|
178
172
|
@staticmethod
|
179
|
-
def _get_tee_files(tees:
|
173
|
+
def _get_tee_files(tees: TeeStreams):
|
180
174
|
return set(callable(tee) and tee() or tee for tee in tees)
|
181
175
|
|
182
176
|
@classmethod
|
@@ -218,12 +212,52 @@ class Popen(subprocess.Popen):
|
|
218
212
|
if stderr_tees:
|
219
213
|
stderr_tees.add(sys.stderr)
|
220
214
|
|
215
|
+
if stderr is STDOUT:
|
216
|
+
err2out = True
|
217
|
+
stderr_tees = set()
|
218
|
+
else:
|
219
|
+
err2out = False
|
220
|
+
|
221
221
|
return (
|
222
222
|
cls._get_tee_files(stdout_tees),
|
223
223
|
cls._get_tee_files(stderr_tees),
|
224
|
-
cls._get_tee_files(prompt_tees)
|
224
|
+
cls._get_tee_files(prompt_tees),
|
225
|
+
err2out
|
226
|
+
)
|
227
|
+
|
228
|
+
@classmethod
|
229
|
+
def simulate(
|
230
|
+
cls, cmd, stdout, stderr, encoding=None, errors=None, text=None, comment=None,
|
231
|
+
stdout_tees: TeeStreams = [], add_global_stdout_tees=True,
|
232
|
+
stderr_tees: TeeStreams = [], add_global_stderr_tees=True,
|
233
|
+
prompt_tees: TeeStreams = [], add_global_prompt_tees=True,
|
234
|
+
echo=None
|
235
|
+
):
|
236
|
+
stdout_tees, stderr_tees, prompt_tees, err2out = cls._get_tee_sets(
|
237
|
+
stdout_tees, add_global_stdout_tees,
|
238
|
+
stderr_tees, add_global_stderr_tees,
|
239
|
+
prompt_tees, add_global_prompt_tees,
|
240
|
+
echo, DEVNULL, DEVNULL
|
225
241
|
)
|
226
242
|
|
243
|
+
if not (stdout_tees or stderr_tees or prompt_tees):
|
244
|
+
return
|
245
|
+
|
246
|
+
stdout_fds = {tee.fileno() for tee in stdout_tees}
|
247
|
+
stderr_fds = {tee.fileno() for tee in stderr_tees}
|
248
|
+
prompt_fds = {tee.fileno() for tee in prompt_tees}
|
249
|
+
|
250
|
+
if prompt_fds:
|
251
|
+
stream_prompts(prompt_fds, cmd, None, None, err2out, comment)
|
252
|
+
|
253
|
+
if stdout_fds:
|
254
|
+
with Tee(PIPE, stdout_fds, DEVNULL, encoding, errors, text) as tee:
|
255
|
+
tee.communicate(stdout)
|
256
|
+
|
257
|
+
if stderr_fds:
|
258
|
+
with Tee(PIPE, stderr_fds, DEVNULL, encoding, errors, text) as tee:
|
259
|
+
tee.communicate(stderr)
|
260
|
+
|
227
261
|
def get_pids(self):
|
228
262
|
""" Return the pid for all of the processes in the tree """
|
229
263
|
output = run(
|
@@ -239,13 +273,13 @@ class Popen(subprocess.Popen):
|
|
239
273
|
|
240
274
|
def terminate_tree(self):
|
241
275
|
run(
|
242
|
-
["
|
276
|
+
["kill"] + self.get_pids(),
|
243
277
|
stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL,
|
244
278
|
)
|
245
279
|
|
246
280
|
def kill_tree(self):
|
247
281
|
run(
|
248
|
-
["
|
282
|
+
["kill", "-9"] + self.get_pids(),
|
249
283
|
stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL,
|
250
284
|
)
|
251
285
|
|
@@ -383,7 +417,7 @@ if subprocess.run(
|
|
383
417
|
stderr=DEVNULL
|
384
418
|
).stdout.splitlines()[-2:]
|
385
419
|
|
386
|
-
def stream_prompts(fds: Iterable[
|
420
|
+
def stream_prompts(fds: Iterable[int], cmd, cwd=None, env=None, err2out=False, comment=None):
|
387
421
|
""" Write shell prompt and command into file descriptors fds """
|
388
422
|
fds = normalize_outerr_fds(fds)
|
389
423
|
custom_env = {"CPS1": PS1, "CPS2": PS2}
|
@@ -393,7 +427,7 @@ if subprocess.run(
|
|
393
427
|
"(\n"
|
394
428
|
" IFS= read -r \"line\"\n"
|
395
429
|
" echo \"${CPS1@P}${line}\"\n"
|
396
|
-
" while IFS= read line; do\n"
|
430
|
+
" while IFS= read -r \"line\"; do\n"
|
397
431
|
" echo \"${CPS2@P}${line}\"\n"
|
398
432
|
" done\n"
|
399
433
|
") | " + quote(Tee.get_cmd(fds - {1}))
|
@@ -401,7 +435,7 @@ if subprocess.run(
|
|
401
435
|
subprocess.run(
|
402
436
|
["bash", "-c", script],
|
403
437
|
text=True,
|
404
|
-
input=shellify(cmd, err2out, comment),
|
438
|
+
input=shellify(cmd, err2out, comment) + "\n",
|
405
439
|
stdout=None if 1 in fds else DEVNULL,
|
406
440
|
stderr=None if 2 in fds else DEVNULL,
|
407
441
|
pass_fds=fds - {1, 2},
|
@@ -410,7 +444,7 @@ if subprocess.run(
|
|
410
444
|
check=True
|
411
445
|
)
|
412
446
|
else: # Use hard-coded PS1 and PS2 strings
|
413
|
-
def stream_prompts(fds: Iterable[
|
447
|
+
def stream_prompts(fds: Iterable[int], cmd, cwd=None, env=None, err2out=False, comment=None):
|
414
448
|
""" Write shell prompt and command into file descriptors fds """
|
415
449
|
cmd = shellify(cmd, err2out, comment) + "\n"
|
416
450
|
input = "$ " + "> ".join(cmd.splitlines(keepends=True))
|
@@ -450,7 +484,7 @@ def _output_context(kwargs, key, encoding, errors, text):
|
|
450
484
|
|
451
485
|
def run(
|
452
486
|
args, *, input=None, capture_output=False, timeout=None, check=False,
|
453
|
-
encoding=None, errors=None, text=None, sigterm_timeout=10, **kwargs
|
487
|
+
encoding=None, errors=None, text=None, sigterm_timeout=10, comment=None, **kwargs
|
454
488
|
):
|
455
489
|
""" A subprocess.run that instantiates this module's Popen """
|
456
490
|
if input is not None:
|
@@ -464,7 +498,7 @@ def run(
|
|
464
498
|
kwargs["stdout"] = FILE
|
465
499
|
kwargs["stderr"] = FILE
|
466
500
|
|
467
|
-
comment = f"timeout={timeout}" if timeout else
|
501
|
+
comment = " ".join((comment or "", f"timeout={timeout}" if timeout else "")).strip()
|
468
502
|
with (
|
469
503
|
_output_context(kwargs, "stdout", encoding, errors, text) as stdout_file,
|
470
504
|
_output_context(kwargs, "stderr", encoding, errors, text) as stderr_file,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: iripau
|
3
|
-
Version:
|
3
|
+
Version: 1.1.0
|
4
4
|
Summary: Python utilities focused on command execution
|
5
5
|
Author: Ricardo Quezada
|
6
6
|
Maintainer: Ricardo Quezada
|
@@ -9,11 +9,12 @@ Project-URL: Repository, https://github.com/ricardo-galo/iripau
|
|
9
9
|
Project-URL: Bug Tracker, https://github.com/ricardo-galo/iripau/issues
|
10
10
|
Project-URL: Changelog, https://github.com/ricardo-galo/iripau/blob/master/CHANGELOG.md
|
11
11
|
Keywords: subprocess,command,local,remote,ssh,Linux,real-time,log,logging,threading,utilities
|
12
|
-
Classifier: Development Status ::
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
13
13
|
Classifier: Programming Language :: Python
|
14
14
|
Requires-Python: >=3.7
|
15
15
|
Description-Content-Type: text/markdown
|
16
16
|
License-File: LICENSE
|
17
|
+
Requires-Dist: curlify
|
17
18
|
Requires-Dist: psutil
|
18
19
|
Dynamic: license-file
|
19
20
|
|
@@ -3,11 +3,12 @@ iripau/executable.py,sha256=W00UNJfMWwZ7cZkEvppwVWy8GaGfbr36eRx0BwaI6mA,3109
|
|
3
3
|
iripau/functools.py,sha256=Jyi0LdOx36Ih6s9TF4aFS3Xk-RW78YE2znB1PBWTAIo,2901
|
4
4
|
iripau/logging.py,sha256=dA-69y5MN8a_c95e_DOlULO0tc7t267okFi9iqJzrmg,2542
|
5
5
|
iripau/random.py,sha256=zC5GxmBdfVa6nAUZcv5gCLwBpZU3sA1TB71Cn311lHo,1023
|
6
|
+
iripau/requests.py,sha256=BJIlz2UzB7wNEHx0IZ-BF1UNfmRxXBhRaW86HBO0jII,2597
|
6
7
|
iripau/shutil.py,sha256=HwU4sUrStzAZjqbIMK7xm-NfjxMXHZx4lWAjwfBf4rw,2558
|
7
|
-
iripau/subprocess.py,sha256=
|
8
|
+
iripau/subprocess.py,sha256=mQDLrBgTOEJ_Ueb1nqIbRkrzMlnYWjfXS8kJsspMcsE,17684
|
8
9
|
iripau/threading.py,sha256=q-a6OuDrz3hg_5u2Q0eMG3hzu_IgGNBfEw-cI_pyaDo,8955
|
9
|
-
iripau-
|
10
|
-
iripau-
|
11
|
-
iripau-
|
12
|
-
iripau-
|
13
|
-
iripau-
|
10
|
+
iripau-1.1.0.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
11
|
+
iripau-1.1.0.dist-info/METADATA,sha256=U60idKE5l07kZz3VlMhNPWoHXZsco1DdkpKGGCgGqC0,810
|
12
|
+
iripau-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
13
|
+
iripau-1.1.0.dist-info/top_level.txt,sha256=y2HMFLCoP2EP7kbRIqkyCVIf_YRZfA0cYYLNK28U9cY,7
|
14
|
+
iripau-1.1.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|