iripau 1.0.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 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
@@ -37,6 +37,7 @@ GLOBAL_PROMPTS = set()
37
37
 
38
38
 
39
39
  TeeStream = Union[io.IOBase, Callable[[], io.IOBase]]
40
+ TeeStreams = Iterable[TeeStream]
40
41
 
41
42
 
42
43
  class PipeFile(SpooledTemporaryFile):
@@ -123,27 +124,21 @@ class Popen(subprocess.Popen):
123
124
 
124
125
  def __init__(
125
126
  self, args, *, cwd=None, env=None, encoding=None, errors=None, text=None,
126
- stdout_tees: Iterable[TeeStream] = [], add_global_stdout_tees=True,
127
- stderr_tees: Iterable[TeeStream] = [], add_global_stderr_tees=True,
128
- prompt_tees: Iterable[TeeStream] = [], add_global_prompt_tees=True,
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,
129
130
  echo=None, alias=None, comment=None, **kwargs
130
131
  ):
131
132
  stdout = kwargs.get("stdout")
132
133
  stderr = kwargs.get("stderr")
133
134
 
134
- stdout_tees, stderr_tees, prompt_tees = self._get_tee_sets(
135
+ stdout_tees, stderr_tees, prompt_tees, err2out = self._get_tee_sets(
135
136
  stdout_tees, add_global_stdout_tees,
136
137
  stderr_tees, add_global_stderr_tees,
137
138
  prompt_tees, add_global_prompt_tees,
138
139
  echo, stdout, stderr
139
140
  )
140
141
 
141
- if stderr is STDOUT:
142
- err2out = True
143
- stderr_tees = set()
144
- else:
145
- err2out = False
146
-
147
142
  stdout_fds = {tee.fileno() for tee in stdout_tees}
148
143
  stderr_fds = {tee.fileno() for tee in stderr_tees}
149
144
  prompt_fds = {tee.fileno() for tee in prompt_tees}
@@ -175,7 +170,7 @@ class Popen(subprocess.Popen):
175
170
  self.stderr_process = stderr_process
176
171
 
177
172
  @staticmethod
178
- def _get_tee_files(tees: Iterable[TeeStream]):
173
+ def _get_tee_files(tees: TeeStreams):
179
174
  return set(callable(tee) and tee() or tee for tee in tees)
180
175
 
181
176
  @classmethod
@@ -217,12 +212,52 @@ class Popen(subprocess.Popen):
217
212
  if stderr_tees:
218
213
  stderr_tees.add(sys.stderr)
219
214
 
215
+ if stderr is STDOUT:
216
+ err2out = True
217
+ stderr_tees = set()
218
+ else:
219
+ err2out = False
220
+
220
221
  return (
221
222
  cls._get_tee_files(stdout_tees),
222
223
  cls._get_tee_files(stderr_tees),
223
- 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
224
241
  )
225
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
+
226
261
  def get_pids(self):
227
262
  """ Return the pid for all of the processes in the tree """
228
263
  output = run(
@@ -382,7 +417,7 @@ if subprocess.run(
382
417
  stderr=DEVNULL
383
418
  ).stdout.splitlines()[-2:]
384
419
 
385
- def stream_prompts(fds: Iterable[str], cmd, cwd=None, env=None, err2out=False, comment=None):
420
+ def stream_prompts(fds: Iterable[int], cmd, cwd=None, env=None, err2out=False, comment=None):
386
421
  """ Write shell prompt and command into file descriptors fds """
387
422
  fds = normalize_outerr_fds(fds)
388
423
  custom_env = {"CPS1": PS1, "CPS2": PS2}
@@ -392,7 +427,7 @@ if subprocess.run(
392
427
  "(\n"
393
428
  " IFS= read -r \"line\"\n"
394
429
  " echo \"${CPS1@P}${line}\"\n"
395
- " while IFS= read line; do\n"
430
+ " while IFS= read -r \"line\"; do\n"
396
431
  " echo \"${CPS2@P}${line}\"\n"
397
432
  " done\n"
398
433
  ") | " + quote(Tee.get_cmd(fds - {1}))
@@ -400,7 +435,7 @@ if subprocess.run(
400
435
  subprocess.run(
401
436
  ["bash", "-c", script],
402
437
  text=True,
403
- input=shellify(cmd, err2out, comment),
438
+ input=shellify(cmd, err2out, comment) + "\n",
404
439
  stdout=None if 1 in fds else DEVNULL,
405
440
  stderr=None if 2 in fds else DEVNULL,
406
441
  pass_fds=fds - {1, 2},
@@ -409,7 +444,7 @@ if subprocess.run(
409
444
  check=True
410
445
  )
411
446
  else: # Use hard-coded PS1 and PS2 strings
412
- def stream_prompts(fds: Iterable[str], cmd, cwd=None, env=None, err2out=False, comment=None):
447
+ def stream_prompts(fds: Iterable[int], cmd, cwd=None, env=None, err2out=False, comment=None):
413
448
  """ Write shell prompt and command into file descriptors fds """
414
449
  cmd = shellify(cmd, err2out, comment) + "\n"
415
450
  input = "$ " + "> ".join(cmd.splitlines(keepends=True))
@@ -449,7 +484,7 @@ def _output_context(kwargs, key, encoding, errors, text):
449
484
 
450
485
  def run(
451
486
  args, *, input=None, capture_output=False, timeout=None, check=False,
452
- encoding=None, errors=None, text=None, sigterm_timeout=10, **kwargs
487
+ encoding=None, errors=None, text=None, sigterm_timeout=10, comment=None, **kwargs
453
488
  ):
454
489
  """ A subprocess.run that instantiates this module's Popen """
455
490
  if input is not None:
@@ -463,7 +498,7 @@ def run(
463
498
  kwargs["stdout"] = FILE
464
499
  kwargs["stderr"] = FILE
465
500
 
466
- comment = f"timeout={timeout}" if timeout else None
501
+ comment = " ".join((comment or "", f"timeout={timeout}" if timeout else "")).strip()
467
502
  with (
468
503
  _output_context(kwargs, "stdout", encoding, errors, text) as stdout_file,
469
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: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Python utilities focused on command execution
5
5
  Author: Ricardo Quezada
6
6
  Maintainer: Ricardo Quezada
@@ -14,6 +14,7 @@ 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=71J2wmOyTHPL2q-Y-OptOTziC5ftf72INo_WK9EOTPw,16335
8
+ iripau/subprocess.py,sha256=mQDLrBgTOEJ_Ueb1nqIbRkrzMlnYWjfXS8kJsspMcsE,17684
8
9
  iripau/threading.py,sha256=q-a6OuDrz3hg_5u2Q0eMG3hzu_IgGNBfEw-cI_pyaDo,8955
9
- iripau-1.0.0.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
10
- iripau-1.0.0.dist-info/METADATA,sha256=qKygECoZsLVoLtgyCe8LfO4Z3uFqQEqMT__JbI1FkHM,787
11
- iripau-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- iripau-1.0.0.dist-info/top_level.txt,sha256=y2HMFLCoP2EP7kbRIqkyCVIf_YRZfA0cYYLNK28U9cY,7
13
- iripau-1.0.0.dist-info/RECORD,,
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