stouputils 1.18.1__tar.gz → 1.18.2__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.
Files changed (161) hide show
  1. {stouputils-1.18.1 → stouputils-1.18.2}/PKG-INFO +1 -1
  2. {stouputils-1.18.1 → stouputils-1.18.2}/pyproject.toml +1 -1
  3. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/io.py +3 -3
  4. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/io.pyi +1 -1
  5. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/parallel/capturer.py +5 -6
  6. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/parallel/subprocess.py +40 -18
  7. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/parallel/subprocess.pyi +11 -1
  8. {stouputils-1.18.1 → stouputils-1.18.2}/README.md +0 -0
  9. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/__init__.py +0 -0
  10. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/__init__.pyi +0 -0
  11. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/__main__.py +0 -0
  12. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/_deprecated.py +0 -0
  13. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/_deprecated.pyi +0 -0
  14. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/all_doctests.py +0 -0
  15. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/all_doctests.pyi +0 -0
  16. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/__init__.py +0 -0
  17. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/__init__.pyi +0 -0
  18. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/automatic_docs.py +0 -0
  19. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/automatic_docs.pyi +0 -0
  20. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/upscaler/__init__.py +0 -0
  21. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/upscaler/__init__.pyi +0 -0
  22. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/upscaler/config.py +0 -0
  23. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/upscaler/config.pyi +0 -0
  24. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/upscaler/image.py +0 -0
  25. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/upscaler/image.pyi +0 -0
  26. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/upscaler/video.py +0 -0
  27. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/applications/upscaler/video.pyi +0 -0
  28. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/archive.py +0 -0
  29. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/archive.pyi +0 -0
  30. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/backup.py +0 -0
  31. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/backup.pyi +0 -0
  32. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/collections.py +0 -0
  33. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/collections.pyi +0 -0
  34. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/__init__.py +0 -0
  35. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/__init__.pyi +0 -0
  36. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/cd_utils.py +0 -0
  37. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/cd_utils.pyi +0 -0
  38. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/github.py +0 -0
  39. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/github.pyi +0 -0
  40. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/pypi.py +0 -0
  41. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/pypi.pyi +0 -0
  42. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/pyproject.py +0 -0
  43. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/pyproject.pyi +0 -0
  44. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/stubs.py +0 -0
  45. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/continuous_delivery/stubs.pyi +0 -0
  46. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/ctx.py +0 -0
  47. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/ctx.pyi +0 -0
  48. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/config/get.py +0 -0
  49. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/config/set.py +0 -0
  50. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/__init__.py +0 -0
  51. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/auto_contrast.py +0 -0
  52. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/axis_flip.py +0 -0
  53. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/bias_field_correction.py +0 -0
  54. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/binary_threshold.py +0 -0
  55. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/blur.py +0 -0
  56. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/brightness.py +0 -0
  57. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/canny.py +0 -0
  58. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/clahe.py +0 -0
  59. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/common.py +0 -0
  60. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/contrast.py +0 -0
  61. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/curvature_flow_filter.py +0 -0
  62. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/denoise.py +0 -0
  63. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/histogram_equalization.py +0 -0
  64. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/invert.py +0 -0
  65. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/laplacian.py +0 -0
  66. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/median_blur.py +0 -0
  67. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/noise.py +0 -0
  68. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/normalize.py +0 -0
  69. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/random_erase.py +0 -0
  70. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/resize.py +0 -0
  71. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/rotation.py +0 -0
  72. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/salt_pepper.py +0 -0
  73. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/sharpening.py +0 -0
  74. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/shearing.py +0 -0
  75. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/threshold.py +0 -0
  76. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/translation.py +0 -0
  77. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image/zoom.py +0 -0
  78. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image_augmentation.py +0 -0
  79. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/image_preprocess.py +0 -0
  80. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/prosthesis_detection.py +0 -0
  81. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/data_processing/technique.py +0 -0
  82. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/dataset/__init__.py +0 -0
  83. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/dataset/dataset.py +0 -0
  84. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/dataset/dataset_loader.py +0 -0
  85. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/dataset/grouping_strategy.py +0 -0
  86. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/dataset/image_loader.py +0 -0
  87. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/dataset/xy_tuple.py +0 -0
  88. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/metric_dictionnary.py +0 -0
  89. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/metric_utils.py +0 -0
  90. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/mlflow_utils.py +0 -0
  91. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/abstract_model.py +0 -0
  92. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/all.py +0 -0
  93. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/base_keras.py +0 -0
  94. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras/all.py +0 -0
  95. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras/convnext.py +0 -0
  96. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras/densenet.py +0 -0
  97. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras/efficientnet.py +0 -0
  98. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras/mobilenet.py +0 -0
  99. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras/resnet.py +0 -0
  100. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras/squeezenet.py +0 -0
  101. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras/vgg.py +0 -0
  102. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras/xception.py +0 -0
  103. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras_utils/callbacks/__init__.py +0 -0
  104. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +0 -0
  105. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +0 -0
  106. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +0 -0
  107. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +0 -0
  108. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +0 -0
  109. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras_utils/losses/__init__.py +0 -0
  110. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +0 -0
  111. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/keras_utils/visualizations.py +0 -0
  112. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/model_interface.py +0 -0
  113. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/models/sandbox.py +0 -0
  114. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/range_tuple.py +0 -0
  115. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/scripts/augment_dataset.py +0 -0
  116. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/scripts/exhaustive_process.py +0 -0
  117. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/scripts/preprocess_dataset.py +0 -0
  118. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/scripts/routine.py +0 -0
  119. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/data_science/utils.py +0 -0
  120. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/decorators.py +0 -0
  121. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/decorators.pyi +0 -0
  122. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/image.py +0 -0
  123. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/image.pyi +0 -0
  124. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/__init__.py +0 -0
  125. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/__init__.pyi +0 -0
  126. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/common.py +0 -0
  127. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/common.pyi +0 -0
  128. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/downloader.py +0 -0
  129. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/downloader.pyi +0 -0
  130. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/linux.py +0 -0
  131. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/linux.pyi +0 -0
  132. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/main.py +0 -0
  133. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/main.pyi +0 -0
  134. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/windows.py +0 -0
  135. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/installer/windows.pyi +0 -0
  136. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/__init__.py +0 -0
  137. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/__init__.pyi +0 -0
  138. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/base.py +0 -0
  139. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/base.pyi +0 -0
  140. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/queue.py +0 -0
  141. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/queue.pyi +0 -0
  142. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/re_entrant.py +0 -0
  143. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/re_entrant.pyi +0 -0
  144. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/redis_fifo.py +0 -0
  145. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/redis_fifo.pyi +0 -0
  146. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/shared.py +0 -0
  147. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/lock/shared.pyi +0 -0
  148. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/parallel/__init__.py +0 -0
  149. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/parallel/__init__.pyi +0 -0
  150. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/parallel/capturer.pyi +0 -0
  151. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/parallel/common.py +0 -0
  152. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/parallel/common.pyi +0 -0
  153. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/parallel/multi.py +0 -0
  154. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/parallel/multi.pyi +0 -0
  155. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/print.py +0 -0
  156. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/print.pyi +0 -0
  157. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/py.typed +0 -0
  158. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/typing.py +0 -0
  159. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/typing.pyi +0 -0
  160. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/version_pkg.py +0 -0
  161. {stouputils-1.18.1 → stouputils-1.18.2}/stouputils/version_pkg.pyi +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stouputils
3
- Version: 1.18.1
3
+ Version: 1.18.2
4
4
  Summary: Stouputils is a collection of utility modules designed to simplify and enhance the development process. It includes a range of tools for tasks such as execution of doctests, display utilities, decorators, as well as context managers, and many more.
5
5
  Keywords: utilities,tools,helpers,development,python
6
6
  Author: Stoupy51
@@ -8,7 +8,7 @@ module-root = ""
8
8
 
9
9
  [project]
10
10
  name = "stouputils"
11
- version = "1.18.1"
11
+ version = "1.18.2"
12
12
  description = "Stouputils is a collection of utility modules designed to simplify and enhance the development process. It includes a range of tools for tasks such as execution of doctests, display utilities, decorators, as well as context managers, and many more."
13
13
  readme = "README.md"
14
14
  keywords = ["utilities", "tools", "helpers", "development", "python"]
@@ -492,7 +492,7 @@ def clean_path(file_path: str, trailing_slash: bool = True) -> str:
492
492
  # Return the cleaned path
493
493
  return file_path if file_path != "." else ""
494
494
 
495
- def safe_close(file: IO[Any] | int | None) -> None:
495
+ def safe_close(file: IO[Any] | int | Any | None) -> None:
496
496
  """ Safely close a file object (or file descriptor) after flushing, ignoring any exceptions.
497
497
 
498
498
  Args:
@@ -506,9 +506,9 @@ def safe_close(file: IO[Any] | int | None) -> None:
506
506
  except Exception:
507
507
  pass
508
508
  elif file:
509
- for func in (file.flush, file.close):
509
+ for func in ("flush", "close"):
510
510
  try:
511
- func()
511
+ getattr(file, func)()
512
512
  except Exception:
513
513
  pass
514
514
 
@@ -211,7 +211,7 @@ def clean_path(file_path: str, trailing_slash: bool = True) -> str:
211
211
  \t\t>>> clean_path("C:/folder1\\\\folder2")
212
212
  \t\t\'C:/folder1/folder2\'
213
213
  \t'''
214
- def safe_close(file: IO[Any] | int | None) -> None:
214
+ def safe_close(file: IO[Any] | int | Any | None) -> None:
215
215
  """ Safely close a file object (or file descriptor) after flushing, ignoring any exceptions.
216
216
 
217
217
  \tArgs:
@@ -65,8 +65,7 @@ class CaptureOutput:
65
65
 
66
66
  def parent_close_write(self) -> None:
67
67
  """ Close the parent's copy of the write end; the child's copy remains. """
68
- safe_close(self.write_fd)
69
- self.write_conn.close()
68
+ safe_close(self.write_conn)
70
69
  self.write_fd = -1 # Prevent accidental reuse
71
70
 
72
71
  def start_listener(self) -> None:
@@ -110,8 +109,7 @@ class CaptureOutput:
110
109
  if len(buffer) > self.chunk_size * 4:
111
110
  _handle_buffer()
112
111
  finally:
113
- safe_close(self.read_fd)
114
- self.read_conn.close()
112
+ safe_close(self.read_conn)
115
113
  self.read_fd = -1
116
114
  self._thread = None # Mark thread as stopped so callers don't block unnecessarily
117
115
 
@@ -123,8 +121,9 @@ class CaptureOutput:
123
121
  def join_listener(self, timeout: float | None = None) -> None:
124
122
  """ Wait for the listener thread to finish (until EOF). """
125
123
  if self._thread is None:
126
- safe_close(self.read_fd)
127
- return self.read_conn.close()
124
+ safe_close(self.read_conn)
125
+ self.read_fd = -1
126
+ return
128
127
  self._thread.join(timeout)
129
128
 
130
129
  # If thread finished, ensure read fd is closed and clear thread
@@ -4,9 +4,20 @@ import time
4
4
  from collections.abc import Callable
5
5
  from typing import Any
6
6
 
7
+ from ..typing import JsonDict
7
8
  from .capturer import CaptureOutput
8
9
 
9
10
 
11
+ class RemoteSubprocessError(RuntimeError):
12
+ """ Raised in the parent when the child raised an exception - contains the child's formatted traceback. """
13
+ def __init__(self, exc_type: str, exc_repr: str, traceback_str: str):
14
+ msg = f"Exception in subprocess ({exc_type}): {exc_repr}\n\nRemote traceback:\n{traceback_str}"
15
+ super().__init__(msg)
16
+ self.remote_type = exc_type
17
+ self.remote_repr = exc_repr
18
+ self.remote_traceback = traceback_str
19
+
20
+
10
21
  def run_in_subprocess[R](
11
22
  func: Callable[..., R],
12
23
  *args: Any,
@@ -37,7 +48,8 @@ def run_in_subprocess[R](
37
48
  R: The return value of the function.
38
49
 
39
50
  Raises:
40
- RuntimeError: If the subprocess exits with a non-zero exit code or times out.
51
+ RemoteSubprocessError: If the child raised an exception - contains the child's formatted traceback.
52
+ RuntimeError: If the subprocess exits with a non-zero exit code or did not return a result.
41
53
  TimeoutError: If the subprocess exceeds the specified timeout.
42
54
 
43
55
  Examples:
@@ -66,7 +78,7 @@ def run_in_subprocess[R](
66
78
  from multiprocessing import Queue
67
79
 
68
80
  # Create a queue to get the result from the subprocess (only if we need to wait)
69
- result_queue: Queue[R | Exception] | None = None if no_join else Queue()
81
+ result_queue: Queue[JsonDict] | None = None if no_join else Queue()
70
82
 
71
83
  # Optionally setup output capture pipe and listener
72
84
  capturer: CaptureOutput | None = None
@@ -105,23 +117,22 @@ def run_in_subprocess[R](
105
117
  process.join()
106
118
  raise TimeoutError(f"Subprocess exceeded timeout of {timeout} seconds and was terminated")
107
119
 
108
- # Check exit code
120
+ # Retrieve the payload if present
121
+ result_payload: JsonDict | None = result_queue.get_nowait() if not result_queue.empty() else None
122
+
123
+ # If the child sent a structured exception, raise it with the formatted traceback
124
+ if isinstance(result_payload, dict):
125
+ if result_payload.pop("ok", False) is False:
126
+ raise RemoteSubprocessError(**result_payload)
127
+ else:
128
+ return result_payload["result"]
129
+
130
+ # Raise an error according to the exit code presence
109
131
  if process.exitcode != 0:
110
- # Try to get any exception from the queue (non-blocking)
111
- if not result_queue.empty():
112
- result_or_exception = result_queue.get_nowait()
113
- if isinstance(result_or_exception, Exception):
114
- raise result_or_exception
115
132
  raise RuntimeError(f"Subprocess failed with exit code {process.exitcode}")
133
+ raise RuntimeError("Subprocess did not return any result")
116
134
 
117
- # Retrieve the result
118
- try:
119
- result_or_exception = result_queue.get_nowait()
120
- if isinstance(result_or_exception, Exception):
121
- raise result_or_exception
122
- return result_or_exception
123
- except Exception as e:
124
- raise RuntimeError("Subprocess did not return any result") from e
135
+ # Finally, ensure we drain/join the listener if capturing output
125
136
  finally:
126
137
  if capturer is not None:
127
138
  capturer.join_listener(timeout=5.0)
@@ -154,10 +165,21 @@ def _subprocess_wrapper[R](
154
165
  # Execute the target function and put the result in the queue
155
166
  result: R = func(*args, **kwargs)
156
167
  if result_queue is not None:
157
- result_queue.put(result)
168
+ result_queue.put({"ok": True, "result": result})
158
169
 
159
170
  # Handle cleanup and exceptions
160
171
  except Exception as e:
161
172
  if result_queue is not None:
162
- result_queue.put(e)
173
+ try:
174
+ import traceback
175
+ tb = traceback.format_exc()
176
+ result_queue.put({
177
+ "ok": False,
178
+ "exc_type": e.__class__.__name__,
179
+ "exc_repr": repr(e),
180
+ "traceback_str": tb,
181
+ })
182
+ except Exception:
183
+ # Nothing we can do if even this fails
184
+ pass
163
185
 
@@ -1,7 +1,16 @@
1
+ from ..typing import JsonDict as JsonDict
1
2
  from .capturer import CaptureOutput as CaptureOutput
3
+ from _typeshed import Incomplete
2
4
  from collections.abc import Callable as Callable
3
5
  from typing import Any
4
6
 
7
+ class RemoteSubprocessError(RuntimeError):
8
+ """ Raised in the parent when the child raised an exception - contains the child's formatted traceback. """
9
+ remote_type: Incomplete
10
+ remote_repr: Incomplete
11
+ remote_traceback: Incomplete
12
+ def __init__(self, exc_type: str, exc_repr: str, traceback_str: str) -> None: ...
13
+
5
14
  def run_in_subprocess[R](func: Callable[..., R], *args: Any, timeout: float | None = None, no_join: bool = False, capture_output: bool = False, **kwargs: Any) -> R:
6
15
  ''' Execute a function in a subprocess with positional and keyword arguments.
7
16
 
@@ -25,7 +34,8 @@ def run_in_subprocess[R](func: Callable[..., R], *args: Any, timeout: float | No
25
34
  \t\tR: The return value of the function.
26
35
 
27
36
  \tRaises:
28
- \t\tRuntimeError: If the subprocess exits with a non-zero exit code or times out.
37
+ \t\tRemoteSubprocessError: If the child raised an exception - contains the child\'s formatted traceback.
38
+ \t\tRuntimeError: If the subprocess exits with a non-zero exit code or did not return a result.
29
39
  \t\tTimeoutError: If the subprocess exceeds the specified timeout.
30
40
 
31
41
  \tExamples:
File without changes