libPyshell 0.4.0__tar.gz → 0.5.0__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
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: libPyshell
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Support for writing shell scripts in Python
5
5
  Home-page: https://github.com/skogsbaer/libPyshell
6
6
  Author: Stefan Wehr
@@ -8,6 +8,13 @@ Author-email: stefan.wehr@gmail.com
8
8
  Requires-Python: >=3.9
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
+ Dynamic: author
12
+ Dynamic: author-email
13
+ Dynamic: description
14
+ Dynamic: description-content-type
15
+ Dynamic: home-page
16
+ Dynamic: requires-python
17
+ Dynamic: summary
11
18
 
12
19
  # Pyshell
13
20
 
@@ -37,6 +44,13 @@ magicFiles = run(['grep', 'magic'] + files, captureStdout=splitLines, onError='i
37
44
 
38
45
  ## Changelog
39
46
 
47
+ * 0.5.0 (2025-03-11)
48
+ * support of timeouts for run
49
+
50
+ * 0.4.1 (2024-09-12)
51
+ * fix capture handling
52
+ * add failOnError option to rm commands
53
+
40
54
  * 0.4.0 (2024-03-20)
41
55
  * re-implement run in terms of subprocess.run. This fixes a bug that caused stdout to
42
56
  disappear.
@@ -26,6 +26,13 @@ magicFiles = run(['grep', 'magic'] + files, captureStdout=splitLines, onError='i
26
26
 
27
27
  ## Changelog
28
28
 
29
+ * 0.5.0 (2025-03-11)
30
+ * support of timeouts for run
31
+
32
+ * 0.4.1 (2024-09-12)
33
+ * fix capture handling
34
+ * add failOnError option to rm commands
35
+
29
36
  * 0.4.0 (2024-03-20)
30
37
  * re-implement run in terms of subprocess.run. This fixes a bug that caused stdout to
31
38
  disappear.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: libPyshell
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Support for writing shell scripts in Python
5
5
  Home-page: https://github.com/skogsbaer/libPyshell
6
6
  Author: Stefan Wehr
@@ -8,6 +8,13 @@ Author-email: stefan.wehr@gmail.com
8
8
  Requires-Python: >=3.9
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
+ Dynamic: author
12
+ Dynamic: author-email
13
+ Dynamic: description
14
+ Dynamic: description-content-type
15
+ Dynamic: home-page
16
+ Dynamic: requires-python
17
+ Dynamic: summary
11
18
 
12
19
  # Pyshell
13
20
 
@@ -37,6 +44,13 @@ magicFiles = run(['grep', 'magic'] + files, captureStdout=splitLines, onError='i
37
44
 
38
45
  ## Changelog
39
46
 
47
+ * 0.5.0 (2025-03-11)
48
+ * support of timeouts for run
49
+
50
+ * 0.4.1 (2024-09-12)
51
+ * fix capture handling
52
+ * add failOnError option to rm commands
53
+
40
54
  * 0.4.0 (2024-03-20)
41
55
  * re-implement run in terms of subprocess.run. This fixes a bug that caused stdout to
42
56
  disappear.
@@ -2,7 +2,7 @@
2
2
 
3
3
  from distutils.core import setup
4
4
 
5
- VERSION = '0.4.0'
5
+ VERSION = '0.5.0'
6
6
 
7
7
  with open("README.md", "r", encoding="utf-8") as fh:
8
8
  long_description = fh.read()
@@ -197,7 +197,7 @@ def _handleCapture(capture: CaptureType) -> Optional[_FILE]:
197
197
  return subprocess.PIPE
198
198
  elif callable(capture):
199
199
  return subprocess.PIPE
200
- elif capture is None:
200
+ elif capture == False:
201
201
  return None
202
202
  else:
203
203
  return capture
@@ -227,7 +227,8 @@ def run(cmd: Union[list[str], str],
227
227
  decodeErrors: str='replace',
228
228
  decodeErrorsStdout: Optional[str]=None,
229
229
  decodeErrorsStderr: Optional[str]=None,
230
- encodeErrorsStdin: Optional[str]=None
230
+ encodeErrorsStdin: Optional[str]=None,
231
+ timeout: Optional[int]=None
231
232
  ) -> RunResult:
232
233
  """Runs the given command.
233
234
 
@@ -258,7 +259,8 @@ def run(cmd: Union[list[str], str],
258
259
  * `decodeErrors`: how to handle decoding/encoding errors on stdout and stderr and stdin.
259
260
  * `decodeErrorsStdout` and `decodeErrorsStderr` and `encodeErrorsStdin`: overwrite the value
260
261
  of decodeErrors for stdout or stderr or stdin
261
-
262
+ * `timeout`: an optional timeout value in seconds. If a timeout occurs, the exit code will
263
+ be 124 (as for the unix timeout command)
262
264
  Returns:
263
265
  a `RunResult` value, given access to the captured stdout of the child process (if it was
264
266
  captured at all) and to the exit code of the child process.
@@ -315,11 +317,20 @@ def run(cmd: Union[list[str], str],
315
317
  if _PYSHELL_DEBUG:
316
318
  _debug(f'subprocess.run({cmd}, shell={shell}, input={input}, stdout={stdout}, ' \
317
319
  f'stderr={stderr}, cwd={cwd}, env={runEnv})')
318
- res = subprocess.run(cmd, shell=shell, input=input, stdout=stdout, stderr=stderr, cwd=cwd,
319
- env=runEnv)
320
- stdoutData = _massageOutput(res.stdout, encoding, decodeErrorsStdout or decodeErrors, captureStdout)
321
- stderrData = _massageOutput(res.stderr, encoding, decodeErrorsStderr or decodeErrors, captureStderr)
322
- exitcode = res.returncode
320
+ try:
321
+ res = subprocess.run(cmd, shell=shell, input=input, stdout=stdout, stderr=stderr, cwd=cwd,
322
+ env=runEnv, timeout=timeout)
323
+ out = res.stdout
324
+ err = res.stderr
325
+ exitcode = res.returncode
326
+ hadTimeout = False
327
+ except subprocess.TimeoutExpired as e:
328
+ out = e.stdout
329
+ err = e.stderr
330
+ exitcode = 124
331
+ hadTimeout = True
332
+ stdoutData = _massageOutput(out, encoding, decodeErrorsStdout or decodeErrors, captureStdout)
333
+ stderrData = _massageOutput(err, encoding, decodeErrorsStderr or decodeErrors, captureStderr)
323
334
  if onError == 'raise' and exitcode != 0:
324
335
  err = RunError(cmd, exitcode, stdoutData, stderrData)
325
336
  raise err
@@ -430,13 +441,15 @@ def removeFile(path: str):
430
441
 
431
442
  def cp(src: str, target: str):
432
443
  """
433
- Copy `src` to `target`.
444
+ Copy `src` to `target`. Behaves like the cp shell command.
434
445
 
435
446
  * If `src` is a file and `target` is a file: overwrites `target`.
436
447
  * If `src` is a file and `target` is a dirname: places the copy in directory `target`,
437
448
  with the basename of `src.
438
- * If `src` is a directory then `target` must also be a directory: copies
439
- the `src` directory (*not* its content) to `target`.
449
+ * If `src` is a directory and `target` exists, then `target` must also be a directory:
450
+ copies the `src` directory (*not* its content) to `target`.
451
+ * If `src` is a directory and `target` does not exist: copies the `src` directory
452
+ and names the copy `target`.
440
453
  """
441
454
  if isFile(src):
442
455
  if isDir(target):
@@ -511,22 +524,34 @@ class workingDir:
511
524
  cd(self.old_dir)
512
525
  return False # reraise expection
513
526
 
514
- def rm(path: str, force: bool=False):
527
+ def rm(path: str, force: bool=False, failOnError: bool=True):
515
528
  """
516
529
  Remove the file at `path`.
517
530
  """
518
531
  if force and not exists(path):
519
532
  return
520
- os.remove(path)
533
+ try:
534
+ os.remove(path)
535
+ except Exception as e:
536
+ if failOnError:
537
+ raise
538
+ else:
539
+ sys.stderr.write(str(e) + '\n')
521
540
 
522
- def rmdir(d: str, recursive: bool=False):
541
+ def rmdir(d: str, recursive: bool=False, failOnError: bool=True):
523
542
  """
524
543
  Remove directory `d`. Set `recursive=True` if the directory is not empty.
525
544
  """
526
- if recursive:
527
- shutil.rmtree(d)
528
- else:
529
- os.rmdir(d)
545
+ try:
546
+ if recursive:
547
+ shutil.rmtree(d)
548
+ else:
549
+ os.rmdir(d)
550
+ except Exception as e:
551
+ if failOnError:
552
+ raise
553
+ else:
554
+ sys.stderr.write(str(e) + '\n')
530
555
 
531
556
  # See https://stackoverflow.com/questions/9741351/how-to-find-exit-code-or-reason-when-atexit-callback-is-called-in-python
532
557
  class _ExitHooks(object):
@@ -581,7 +606,7 @@ def _registerAtExit(action: Any, mode: AtExitMode):
581
606
  def mkTempFile(suffix: str='', prefix: str='',
582
607
  dir:Optional[str]=None,
583
608
  deleteAtExit:AtExitMode=True):
584
- """Create a temporary file.
609
+ """Create a temporary file name.
585
610
 
586
611
  `deleteAtExit` controls if and how the file is deleted once the shell sript terminates.
587
612
  It has one of the following values.
@@ -596,7 +621,7 @@ def mkTempFile(suffix: str='', prefix: str='',
596
621
  if deleteAtExit:
597
622
  def action():
598
623
  if isFile(f):
599
- rm(f)
624
+ rm(f, failOnError=False)
600
625
  _registerAtExit(action, deleteAtExit)
601
626
  return f
602
627
 
@@ -610,7 +635,7 @@ def mkTempDir(suffix: str='', prefix: str='tmp',
610
635
  if deleteAtExit:
611
636
  def action():
612
637
  if isDir(d):
613
- rmdir(d, True)
638
+ rmdir(d, recursive=True, failOnError=False)
614
639
  _registerAtExit(action, deleteAtExit)
615
640
  return d
616
641
 
@@ -646,7 +671,7 @@ class tempDir:
646
671
  return False # reraise
647
672
  if self.delete:
648
673
  if isDir(self.dir_to_delete):
649
- rmdir(self.dir_to_delete, recursive=True)
674
+ rmdir(self.dir_to_delete, recursive=True, failOnError=False)
650
675
  return False # reraise expection
651
676
 
652
677
  def ls(d: str, *globs: str) -> list[str]:
File without changes
File without changes
File without changes