stouputils 1.9.1__tar.gz → 1.9.3__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 (139) hide show
  1. {stouputils-1.9.1 → stouputils-1.9.3}/PKG-INFO +2 -2
  2. {stouputils-1.9.1 → stouputils-1.9.3}/README.md +1 -1
  3. {stouputils-1.9.1 → stouputils-1.9.3}/pyproject.toml +1 -1
  4. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/ctx.py +49 -0
  5. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/ctx.pyi +29 -0
  6. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/decorators.py +146 -42
  7. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/decorators.pyi +52 -18
  8. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/parallel.py +41 -23
  9. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/parallel.pyi +14 -8
  10. {stouputils-1.9.1 → stouputils-1.9.3}/.gitignore +0 -0
  11. {stouputils-1.9.1 → stouputils-1.9.3}/LICENSE +0 -0
  12. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/__init__.py +0 -0
  13. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/__init__.pyi +0 -0
  14. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/__main__.py +0 -0
  15. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/_deprecated.py +0 -0
  16. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/_deprecated.pyi +0 -0
  17. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/all_doctests.py +0 -0
  18. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/all_doctests.pyi +0 -0
  19. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/__init__.py +0 -0
  20. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/__init__.pyi +0 -0
  21. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/automatic_docs.py +0 -0
  22. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/automatic_docs.pyi +0 -0
  23. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/upscaler/__init__.py +0 -0
  24. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/upscaler/__init__.pyi +0 -0
  25. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/upscaler/config.py +0 -0
  26. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/upscaler/config.pyi +0 -0
  27. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/upscaler/image.py +0 -0
  28. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/upscaler/image.pyi +0 -0
  29. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/upscaler/video.py +0 -0
  30. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/applications/upscaler/video.pyi +0 -0
  31. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/archive.py +0 -0
  32. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/archive.pyi +0 -0
  33. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/backup.py +0 -0
  34. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/backup.pyi +0 -0
  35. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/collections.py +0 -0
  36. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/collections.pyi +0 -0
  37. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/__init__.py +0 -0
  38. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/__init__.pyi +0 -0
  39. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/cd_utils.py +0 -0
  40. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/cd_utils.pyi +0 -0
  41. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/github.py +0 -0
  42. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/github.pyi +0 -0
  43. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/pypi.py +0 -0
  44. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/pypi.pyi +0 -0
  45. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/pyproject.py +0 -0
  46. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/pyproject.pyi +0 -0
  47. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/stubs.py +0 -0
  48. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/continuous_delivery/stubs.pyi +0 -0
  49. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/config/get.py +0 -0
  50. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/config/set.py +0 -0
  51. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/__init__.py +0 -0
  52. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/auto_contrast.py +0 -0
  53. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/axis_flip.py +0 -0
  54. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/bias_field_correction.py +0 -0
  55. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/binary_threshold.py +0 -0
  56. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/blur.py +0 -0
  57. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/brightness.py +0 -0
  58. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/canny.py +0 -0
  59. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/clahe.py +0 -0
  60. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/common.py +0 -0
  61. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/contrast.py +0 -0
  62. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/curvature_flow_filter.py +0 -0
  63. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/denoise.py +0 -0
  64. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/histogram_equalization.py +0 -0
  65. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/invert.py +0 -0
  66. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/laplacian.py +0 -0
  67. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/median_blur.py +0 -0
  68. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/noise.py +0 -0
  69. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/normalize.py +0 -0
  70. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/random_erase.py +0 -0
  71. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/resize.py +0 -0
  72. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/rotation.py +0 -0
  73. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/salt_pepper.py +0 -0
  74. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/sharpening.py +0 -0
  75. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/shearing.py +0 -0
  76. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/threshold.py +0 -0
  77. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/translation.py +0 -0
  78. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image/zoom.py +0 -0
  79. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image_augmentation.py +0 -0
  80. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/image_preprocess.py +0 -0
  81. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/prosthesis_detection.py +0 -0
  82. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/data_processing/technique.py +0 -0
  83. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/dataset/__init__.py +0 -0
  84. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/dataset/dataset.py +0 -0
  85. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/dataset/dataset_loader.py +0 -0
  86. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/dataset/grouping_strategy.py +0 -0
  87. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/dataset/image_loader.py +0 -0
  88. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/dataset/xy_tuple.py +0 -0
  89. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/metric_dictionnary.py +0 -0
  90. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/metric_utils.py +0 -0
  91. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/mlflow_utils.py +0 -0
  92. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/abstract_model.py +0 -0
  93. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/all.py +0 -0
  94. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/base_keras.py +0 -0
  95. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras/all.py +0 -0
  96. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras/convnext.py +0 -0
  97. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras/densenet.py +0 -0
  98. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras/efficientnet.py +0 -0
  99. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras/mobilenet.py +0 -0
  100. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras/resnet.py +0 -0
  101. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras/squeezenet.py +0 -0
  102. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras/vgg.py +0 -0
  103. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras/xception.py +0 -0
  104. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras_utils/callbacks/__init__.py +0 -0
  105. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +0 -0
  106. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +0 -0
  107. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +0 -0
  108. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +0 -0
  109. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +0 -0
  110. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras_utils/losses/__init__.py +0 -0
  111. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +0 -0
  112. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/keras_utils/visualizations.py +0 -0
  113. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/model_interface.py +0 -0
  114. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/models/sandbox.py +0 -0
  115. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/range_tuple.py +0 -0
  116. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/scripts/augment_dataset.py +0 -0
  117. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/scripts/exhaustive_process.py +0 -0
  118. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/scripts/preprocess_dataset.py +0 -0
  119. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/scripts/routine.py +0 -0
  120. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/data_science/utils.py +0 -0
  121. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/image.py +0 -0
  122. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/image.pyi +0 -0
  123. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/__init__.py +0 -0
  124. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/__init__.pyi +0 -0
  125. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/common.py +0 -0
  126. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/common.pyi +0 -0
  127. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/downloader.py +0 -0
  128. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/downloader.pyi +0 -0
  129. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/linux.py +0 -0
  130. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/linux.pyi +0 -0
  131. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/main.py +0 -0
  132. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/main.pyi +0 -0
  133. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/windows.py +0 -0
  134. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/installer/windows.pyi +0 -0
  135. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/io.py +0 -0
  136. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/io.pyi +0 -0
  137. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/print.py +0 -0
  138. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/print.pyi +0 -0
  139. {stouputils-1.9.1 → stouputils-1.9.3}/stouputils/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stouputils
3
- Version: 1.9.1
3
+ Version: 1.9.3
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
  Project-URL: Homepage, https://github.com/Stoupy51/stouputils
6
6
  Project-URL: Issues, https://github.com/Stoupy51/stouputils/issues
@@ -149,7 +149,7 @@ stouputils all_<TAB> # Completes to: all_doctests
149
149
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.backup.html">backup.py</a> <span class="comment"># 💾 Utilities for backup management (delta backup, consolidate)</span>
150
150
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.collections.html">collections.py</a> <span class="comment"># 🧰 Utilities for collection manipulation (unique_list, sort_dict_keys, upsert_in_dataframe, array_to_disk)</span>
151
151
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.ctx.html">ctx.py</a> <span class="comment"># 🔇 Context managers (Muffle, LogToFile, MeasureTime, DoNothing)</span>
152
- ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.decorators.html">decorators.py</a> <span class="comment"># 🎯 Decorators (measure_time, handle_error, simple_cache, retry, abstract, deprecated, silent)</span>
152
+ ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.decorators.html">decorators.py</a> <span class="comment"># 🎯 Decorators (measure_time, handle_error, timeout, retry, simple_cache, abstract, deprecated, silent)</span>
153
153
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.image.html">image.py</a> <span class="comment"># 🖼️ Little utilities for image processing (image_resize, auto_crop, numpy_to_gif, numpy_to_obj)</span>
154
154
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.io.html">io.py</a> <span class="comment"># 💾 Utilities for file management (super_json, super_csv, super_copy, super_open, clean_path)</span>
155
155
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.parallel.html">parallel.py</a> <span class="comment"># 🔀 Utility functions for parallel processing (multiprocessing, multithreading)</span>
@@ -107,7 +107,7 @@ stouputils all_<TAB> # Completes to: all_doctests
107
107
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.backup.html">backup.py</a> <span class="comment"># 💾 Utilities for backup management (delta backup, consolidate)</span>
108
108
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.collections.html">collections.py</a> <span class="comment"># 🧰 Utilities for collection manipulation (unique_list, sort_dict_keys, upsert_in_dataframe, array_to_disk)</span>
109
109
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.ctx.html">ctx.py</a> <span class="comment"># 🔇 Context managers (Muffle, LogToFile, MeasureTime, DoNothing)</span>
110
- ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.decorators.html">decorators.py</a> <span class="comment"># 🎯 Decorators (measure_time, handle_error, simple_cache, retry, abstract, deprecated, silent)</span>
110
+ ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.decorators.html">decorators.py</a> <span class="comment"># 🎯 Decorators (measure_time, handle_error, timeout, retry, simple_cache, abstract, deprecated, silent)</span>
111
111
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.image.html">image.py</a> <span class="comment"># 🖼️ Little utilities for image processing (image_resize, auto_crop, numpy_to_gif, numpy_to_obj)</span>
112
112
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.io.html">io.py</a> <span class="comment"># 💾 Utilities for file management (super_json, super_csv, super_copy, super_open, clean_path)</span>
113
113
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.parallel.html">parallel.py</a> <span class="comment"># 🔀 Utility functions for parallel processing (multiprocessing, multithreading)</span>
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
5
5
 
6
6
  [project]
7
7
  name = "stouputils"
8
- version = "1.9.1"
8
+ version = "1.9.3"
9
9
  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."
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.10"
@@ -5,6 +5,7 @@ This module provides context managers for temporarily silencing output.
5
5
  - MeasureTime: Context manager to measure execution time of a code block
6
6
  - Muffle: Context manager that temporarily silences output (alternative to stouputils.decorators.silent())
7
7
  - DoNothing: Context manager that does nothing (no-op)
8
+ - SetMPStartMethod: Context manager to temporarily set multiprocessing start method
8
9
 
9
10
  .. image:: https://raw.githubusercontent.com/Stoupy51/stouputils/refs/heads/main/assets/ctx_module.gif
10
11
  :alt: stouputils ctx examples
@@ -284,3 +285,51 @@ class DoNothing:
284
285
  """ Exit async context manager (does nothing) """
285
286
  pass
286
287
 
288
+ # Context manager to temporarily set multiprocessing start method
289
+ class SetMPStartMethod:
290
+ """ Context manager to temporarily set multiprocessing start method.
291
+
292
+ This context manager allows you to temporarily change the multiprocessing start method
293
+ and automatically restores the original method when exiting the context.
294
+
295
+ Args:
296
+ start_method (str): The start method to use: "spawn", "fork", or "forkserver"
297
+
298
+ Examples:
299
+ .. code-block:: python
300
+
301
+ > import multiprocessing as mp
302
+ > import stouputils as stp
303
+ > # Temporarily use spawn method
304
+ > with stp.SetMPStartMethod("spawn"):
305
+ > ... # Your multiprocessing code here
306
+ > ... pass
307
+
308
+ > # Original method is automatically restored
309
+ """
310
+ def __init__(self, start_method: str | None) -> None:
311
+ self.start_method: str | None = start_method
312
+ """ The start method to use """
313
+ self.old_method: str | None = None
314
+ """ The original start method to restore """
315
+
316
+ def __enter__(self) -> SetMPStartMethod:
317
+ """ Enter context manager which sets the start method """
318
+ if self.start_method is None:
319
+ return self
320
+ import multiprocessing as mp
321
+
322
+ self.old_method = mp.get_start_method(allow_none=True)
323
+ if self.old_method != self.start_method:
324
+ mp.set_start_method(self.start_method, force=True)
325
+ return self
326
+
327
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
328
+ """ Exit context manager which restores the original start method """
329
+ if self.start_method is None:
330
+ return
331
+ import multiprocessing as mp
332
+
333
+ if self.old_method != self.start_method:
334
+ mp.set_start_method(self.old_method, force=True)
335
+
@@ -141,3 +141,32 @@ class DoNothing:
141
141
  """ Enter async context manager (does nothing) """
142
142
  async def __aexit__(self, *excinfo: Any) -> None:
143
143
  """ Exit async context manager (does nothing) """
144
+
145
+ class SetMPStartMethod:
146
+ ''' Context manager to temporarily set multiprocessing start method.
147
+
148
+ \tThis context manager allows you to temporarily change the multiprocessing start method
149
+ \tand automatically restores the original method when exiting the context.
150
+
151
+ \tArgs:
152
+ \t\tstart_method (str): The start method to use: "spawn", "fork", or "forkserver"
153
+
154
+ \tExamples:
155
+ \t\t.. code-block:: python
156
+
157
+ \t\t\t> import multiprocessing as mp
158
+ \t\t\t> import stouputils as stp
159
+ \t\t\t> # Temporarily use spawn method
160
+ \t\t\t> with stp.SetMPStartMethod("spawn"):
161
+ \t\t\t> ... # Your multiprocessing code here
162
+ \t\t\t> ... pass
163
+
164
+ \t\t\t> # Original method is automatically restored
165
+ \t'''
166
+ start_method: str | None
167
+ old_method: str | None
168
+ def __init__(self, start_method: str | None) -> None: ...
169
+ def __enter__(self) -> SetMPStartMethod:
170
+ """ Enter context manager which sets the start method """
171
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
172
+ """ Exit context manager which restores the original start method """
@@ -3,8 +3,9 @@ This module provides decorators for various purposes:
3
3
 
4
4
  - measure_time(): Measure the execution time of a function and print it with the given print function
5
5
  - handle_error(): Handle an error with different log levels
6
- - simple_cache(): Easy cache function with parameter caching method
6
+ - timeout(): Raise an exception if the function runs longer than the specified timeout
7
7
  - retry(): Retry a function when specific exceptions are raised, with configurable delay and max attempts
8
+ - simple_cache(): Easy cache function with parameter caching method
8
9
  - abstract(): Mark a function as abstract, using LogLevels for error handling
9
10
  - deprecated(): Mark a function as deprecated, using LogLevels for warning handling
10
11
  - silent(): Make a function silent (disable stdout, and stderr if specified) (alternative to stouputils.ctx.Muffle)
@@ -167,67 +168,105 @@ def handle_error(
167
168
  return decorator
168
169
  return decorator(func)
169
170
 
170
- # Easy cache function with parameter caching method
171
- def simple_cache(
171
+ # Decorator that raises an exception if the function runs too long
172
+ def timeout(
172
173
  func: Callable[..., Any] | None = None,
173
174
  *,
174
- method: Literal["str", "pickle"] = "str"
175
+ seconds: float = 60.0,
176
+ message: str = ""
175
177
  ) -> Callable[..., Any]:
176
- """ Decorator that caches the result of a function based on its arguments.
178
+ """ Decorator that raises a TimeoutError if the function runs longer than the specified timeout.
177
179
 
178
- The str method is often faster than the pickle method (by a little) but not as accurate with complex objects.
180
+ Note: This decorator uses SIGALRM on Unix systems, which only works in the main thread.
181
+ On Windows or in non-main threads, it will fall back to a polling-based approach.
179
182
 
180
183
  Args:
181
- func (Callable[..., Any] | None): Function to cache
182
- method (Literal["str", "pickle"]): The method to use for caching.
184
+ func (Callable[..., Any] | None): Function to apply timeout to
185
+ seconds (float): Timeout duration in seconds (default: 60.0)
186
+ message (str): Custom timeout message (default: "Function '{func_name}' timed out after {seconds} seconds")
187
+
183
188
  Returns:
184
- Callable[..., Any]: A decorator that caches the result of a function.
189
+ Callable[..., Any]: Decorator that enforces timeout on the function
190
+
191
+ Raises:
192
+ TimeoutError: If the function execution exceeds the timeout duration
193
+
185
194
  Examples:
186
- >>> @simple_cache
187
- ... def test1(a: int, b: int) -> int:
188
- ... return a + b
195
+ >>> @timeout(seconds=2.0)
196
+ ... def slow_function():
197
+ ... time.sleep(5)
198
+ >>> slow_function() # Raises TimeoutError after 2 seconds
199
+ Traceback (most recent call last):
200
+ ...
201
+ TimeoutError: Function 'slow_function' timed out after 2.0 seconds
189
202
 
190
- >>> @simple_cache(method="str")
191
- ... def test2(a: int, b: int) -> int:
192
- ... return a + b
193
- >>> test2(1, 2)
194
- 3
195
- >>> test2(1, 2)
196
- 3
197
- >>> test2(3, 4)
198
- 7
203
+ >>> @timeout(seconds=1.0, message="Custom timeout message")
204
+ ... def another_slow_function():
205
+ ... time.sleep(3)
206
+ >>> another_slow_function() # Raises TimeoutError after 1 second
207
+ Traceback (most recent call last):
208
+ ...
209
+ TimeoutError: Custom timeout message
199
210
  """
200
211
  def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
201
- # Create the cache dict
202
- cache_dict: dict[bytes, Any] = {}
203
-
204
- # Create the wrapper
205
212
  @wraps(func)
206
213
  def wrapper(*args: tuple[Any, ...], **kwargs: dict[str, Any]) -> Any:
214
+ # Build timeout message
215
+ msg: str = message if message else f"Function '{_get_func_name(func)}' timed out after {seconds} seconds"
207
216
 
208
- # Get the hashed key
209
- if method == "str":
210
- hashed: bytes = str(args).encode() + str(kwargs).encode()
211
- elif method == "pickle":
212
- hashed: bytes = pickle_dumps((args, kwargs))
213
- else:
214
- raise ValueError("Invalid caching method. Supported methods are 'str' and 'pickle'.")
217
+ # Try to use signal-based timeout (Unix only, main thread only)
218
+ try:
219
+ import signal
220
+ def timeout_handler(signum: int, frame: Any) -> None:
221
+ raise TimeoutError(msg)
215
222
 
216
- # If the key is in the cache, return it
217
- if hashed in cache_dict:
218
- return cache_dict[hashed]
223
+ # Set the signal handler and alarm
224
+ old_handler = signal.signal(signal.SIGALRM, timeout_handler) # type: ignore
225
+ signal.setitimer(signal.ITIMER_REAL, seconds) # type: ignore
226
+
227
+ try:
228
+ result = func(*args, **kwargs)
229
+ finally:
230
+ # Cancel the alarm and restore the old handler
231
+ signal.setitimer(signal.ITIMER_REAL, 0) # type: ignore
232
+ signal.signal(signal.SIGALRM, old_handler) # type: ignore
219
233
 
220
- # Else, call the function and add the result to the cache
221
- else:
222
- result: Any = func(*args, **kwargs)
223
- cache_dict[hashed] = result
224
234
  return result
225
235
 
226
- # Return the wrapper
227
- wrapper.__name__ = _get_wrapper_name("stouputils.decorators.simple_cache", func)
236
+ except (ValueError, AttributeError) as e:
237
+ # SIGALRM not available (Windows) or not in main thread
238
+ # Fall back to polling-based timeout (less precise but portable)
239
+ import threading
240
+
241
+ result_container: list[Any] = []
242
+ exception_container: list[BaseException] = []
243
+
244
+ def target() -> None:
245
+ try:
246
+ result_container.append(func(*args, **kwargs))
247
+ except BaseException as e_2:
248
+ exception_container.append(e_2)
249
+
250
+ thread = threading.Thread(target=target, daemon=True)
251
+ thread.start()
252
+ thread.join(timeout=seconds)
253
+
254
+ if thread.is_alive():
255
+ # Thread is still running, timeout occurred
256
+ raise TimeoutError(msg) from e
257
+
258
+ # Check if an exception was raised in the thread
259
+ if exception_container:
260
+ raise exception_container[0] from e
261
+
262
+ # Return the result if available
263
+ if result_container:
264
+ return result_container[0]
265
+
266
+ wrapper.__name__ = _get_wrapper_name("stouputils.decorators.timeout", func)
228
267
  return wrapper
229
268
 
230
- # Handle both @simple_cache and @simple_cache(method=...)
269
+ # Handle both @timeout and @timeout(seconds=..., message=...)
231
270
  if func is None:
232
271
  return decorator
233
272
  return decorator(func)
@@ -307,6 +346,71 @@ def retry(
307
346
  return decorator
308
347
  return decorator(func)
309
348
 
349
+ # Easy cache function with parameter caching method
350
+ def simple_cache(
351
+ func: Callable[..., Any] | None = None,
352
+ *,
353
+ method: Literal["str", "pickle"] = "str"
354
+ ) -> Callable[..., Any]:
355
+ """ Decorator that caches the result of a function based on its arguments.
356
+
357
+ The str method is often faster than the pickle method (by a little) but not as accurate with complex objects.
358
+
359
+ Args:
360
+ func (Callable[..., Any] | None): Function to cache
361
+ method (Literal["str", "pickle"]): The method to use for caching.
362
+ Returns:
363
+ Callable[..., Any]: A decorator that caches the result of a function.
364
+ Examples:
365
+ >>> @simple_cache
366
+ ... def test1(a: int, b: int) -> int:
367
+ ... return a + b
368
+
369
+ >>> @simple_cache(method="str")
370
+ ... def test2(a: int, b: int) -> int:
371
+ ... return a + b
372
+ >>> test2(1, 2)
373
+ 3
374
+ >>> test2(1, 2)
375
+ 3
376
+ >>> test2(3, 4)
377
+ 7
378
+ """
379
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
380
+ # Create the cache dict
381
+ cache_dict: dict[bytes, Any] = {}
382
+
383
+ # Create the wrapper
384
+ @wraps(func)
385
+ def wrapper(*args: tuple[Any, ...], **kwargs: dict[str, Any]) -> Any:
386
+
387
+ # Get the hashed key
388
+ if method == "str":
389
+ hashed: bytes = str(args).encode() + str(kwargs).encode()
390
+ elif method == "pickle":
391
+ hashed: bytes = pickle_dumps((args, kwargs))
392
+ else:
393
+ raise ValueError("Invalid caching method. Supported methods are 'str' and 'pickle'.")
394
+
395
+ # If the key is in the cache, return it
396
+ if hashed in cache_dict:
397
+ return cache_dict[hashed]
398
+
399
+ # Else, call the function and add the result to the cache
400
+ else:
401
+ result: Any = func(*args, **kwargs)
402
+ cache_dict[hashed] = result
403
+ return result
404
+
405
+ # Return the wrapper
406
+ wrapper.__name__ = _get_wrapper_name("stouputils.decorators.simple_cache", func)
407
+ return wrapper
408
+
409
+ # Handle both @simple_cache and @simple_cache(method=...)
410
+ if func is None:
411
+ return decorator
412
+ return decorator(func)
413
+
310
414
  # Decorator that marks a function as abstract
311
415
  def abstract(
312
416
  func: Callable[..., Any] | None = None,
@@ -64,30 +64,39 @@ def handle_error(func: Callable[..., Any] | None = None, *, exceptions: tuple[ty
64
64
  \t\t... raise ValueError("Let\'s fail")
65
65
  \t\t>>> # test()\t# [WARNING HH:MM:SS] Error during test: (ValueError) Let\'s fail
66
66
  \t'''
67
- def simple_cache(func: Callable[..., Any] | None = None, *, method: Literal['str', 'pickle'] = 'str') -> Callable[..., Any]:
68
- ''' Decorator that caches the result of a function based on its arguments.
67
+ def timeout(func: Callable[..., Any] | None = None, *, seconds: float = 60.0, message: str = '') -> Callable[..., Any]:
68
+ ''' Decorator that raises a TimeoutError if the function runs longer than the specified timeout.
69
69
 
70
- \tThe str method is often faster than the pickle method (by a little) but not as accurate with complex objects.
70
+ \tNote: This decorator uses SIGALRM on Unix systems, which only works in the main thread.
71
+ \tOn Windows or in non-main threads, it will fall back to a polling-based approach.
71
72
 
72
73
  \tArgs:
73
- \t\tfunc (Callable[..., Any] | None): Function to cache
74
- \t\tmethod (Literal["str", "pickle"]): The method to use for caching.
74
+ \t\tfunc\t\t(Callable[..., Any] | None):\tFunction to apply timeout to
75
+ \t\tseconds\t\t(float):\t\t\t\t\t\tTimeout duration in seconds (default: 60.0)
76
+ \t\tmessage\t\t(str):\t\t\t\t\t\t\tCustom timeout message (default: "Function \'{func_name}\' timed out after {seconds} seconds")
77
+
75
78
  \tReturns:
76
- \t\tCallable[..., Any]: A decorator that caches the result of a function.
79
+ \t\tCallable[..., Any]: Decorator that enforces timeout on the function
80
+
81
+ \tRaises:
82
+ \t\tTimeoutError: If the function execution exceeds the timeout duration
83
+
77
84
  \tExamples:
78
- \t\t>>> @simple_cache
79
- \t\t... def test1(a: int, b: int) -> int:
80
- \t\t... return a + b
85
+ \t\t>>> @timeout(seconds=2.0)
86
+ \t\t... def slow_function():
87
+ \t\t... time.sleep(5)
88
+ \t\t>>> slow_function() # Raises TimeoutError after 2 seconds
89
+ \t\tTraceback (most recent call last):
90
+ \t\t\t...
91
+ \t\tTimeoutError: Function \'slow_function\' timed out after 2.0 seconds
81
92
 
82
- \t\t>>> @simple_cache(method="str")
83
- \t\t... def test2(a: int, b: int) -> int:
84
- \t\t... return a + b
85
- \t\t>>> test2(1, 2)
86
- \t\t3
87
- \t\t>>> test2(1, 2)
88
- \t\t3
89
- \t\t>>> test2(3, 4)
90
- \t\t7
93
+ \t\t>>> @timeout(seconds=1.0, message="Custom timeout message")
94
+ \t\t... def another_slow_function():
95
+ \t\t... time.sleep(3)
96
+ \t\t>>> another_slow_function() # Raises TimeoutError after 1 second
97
+ \t\tTraceback (most recent call last):
98
+ \t\t\t...
99
+ \t\tTimeoutError: Custom timeout message
91
100
  \t'''
92
101
  def retry(func: Callable[..., Any] | None = None, *, exceptions: tuple[type[BaseException], ...] | type[BaseException] = ..., max_attempts: int = 10, delay: float = 1.0, backoff: float = 1.0, message: str = '') -> Callable[..., Any]:
93
102
  ''' Decorator that retries a function when specific exceptions are raised.
@@ -118,6 +127,31 @@ def retry(func: Callable[..., Any] | None = None, *, exceptions: tuple[type[Base
118
127
  \t\t... def might_fail():
119
128
  \t\t... pass
120
129
  \t'''
130
+ def simple_cache(func: Callable[..., Any] | None = None, *, method: Literal['str', 'pickle'] = 'str') -> Callable[..., Any]:
131
+ ''' Decorator that caches the result of a function based on its arguments.
132
+
133
+ \tThe str method is often faster than the pickle method (by a little) but not as accurate with complex objects.
134
+
135
+ \tArgs:
136
+ \t\tfunc (Callable[..., Any] | None): Function to cache
137
+ \t\tmethod (Literal["str", "pickle"]): The method to use for caching.
138
+ \tReturns:
139
+ \t\tCallable[..., Any]: A decorator that caches the result of a function.
140
+ \tExamples:
141
+ \t\t>>> @simple_cache
142
+ \t\t... def test1(a: int, b: int) -> int:
143
+ \t\t... return a + b
144
+
145
+ \t\t>>> @simple_cache(method="str")
146
+ \t\t... def test2(a: int, b: int) -> int:
147
+ \t\t... return a + b
148
+ \t\t>>> test2(1, 2)
149
+ \t\t3
150
+ \t\t>>> test2(1, 2)
151
+ \t\t3
152
+ \t\t>>> test2(3, 4)
153
+ \t\t7
154
+ \t'''
121
155
  def abstract(func: Callable[..., Any] | None = None, *, error_log: LogLevels = ...) -> Callable[..., Any]:
122
156
  """ Decorator that marks a function as abstract.
123
157
 
@@ -17,7 +17,7 @@ import time
17
17
  from collections.abc import Callable
18
18
  from typing import Any, TypeVar, cast
19
19
 
20
- from .decorators import LogLevels, handle_error
20
+ from .ctx import SetMPStartMethod
21
21
  from .print import BAR_FORMAT, MAGENTA
22
22
 
23
23
 
@@ -34,9 +34,8 @@ T = TypeVar("T")
34
34
  R = TypeVar("R")
35
35
 
36
36
  # Functions
37
- @handle_error(error_log=LogLevels.ERROR_TRACEBACK)
38
37
  def multiprocessing(
39
- func: Callable[[T], R] | list[Callable[[T], R]],
38
+ func: Callable[..., R] | list[Callable[..., R]],
40
39
  args: list[T],
41
40
  use_starmap: bool = False,
42
41
  chunksize: int = 1,
@@ -131,14 +130,8 @@ def multiprocessing(
131
130
  if "SemLock created in a fork context is being shared with a process in a spawn context" in str(e):
132
131
 
133
132
  # Try with alternate start method
134
- old_method: str | None = mp.get_start_method(allow_none=True)
135
- new_method: str = "spawn" if old_method in (None, "fork") else "fork"
136
- mp.set_start_method(new_method, force=True)
137
-
138
- try:
133
+ with SetMPStartMethod("spawn" if mp.get_start_method() != "spawn" else "fork"):
139
134
  return process()
140
- finally:
141
- mp.set_start_method(old_method, force=True)
142
135
  else: # Re-raise if it's not the SemLock error
143
136
  raise
144
137
 
@@ -150,9 +143,8 @@ def multiprocessing(
150
143
  return [func(arg) for arg in args]
151
144
 
152
145
 
153
- @handle_error(error_log=LogLevels.ERROR_TRACEBACK)
154
146
  def multithreading(
155
- func: Callable[[T], R] | list[Callable[[T], R]],
147
+ func: Callable[..., R] | list[Callable[..., R]],
156
148
  args: list[T],
157
149
  use_starmap: bool = False,
158
150
  desc: str = "",
@@ -243,10 +235,10 @@ def multithreading(
243
235
  return [func(arg) for arg in args]
244
236
 
245
237
 
246
- @handle_error(error_log=LogLevels.ERROR_TRACEBACK)
247
238
  def run_in_subprocess(
248
239
  func: Callable[..., R],
249
240
  *args: Any,
241
+ timeout: float | None = None,
250
242
  **kwargs: Any
251
243
  ) -> R:
252
244
  """ Execute a function in a subprocess with positional and keyword arguments.
@@ -256,16 +248,19 @@ def run_in_subprocess(
256
248
  be created, run the function with the provided arguments, and return the result.
257
249
 
258
250
  Args:
259
- func (Callable): The function to execute in a subprocess.
251
+ func (Callable): The function to execute in a subprocess.
260
252
  (SHOULD BE A TOP-LEVEL FUNCTION TO BE PICKLABLE)
261
- *args (Any): Positional arguments to pass to the function.
262
- **kwargs (Any): Keyword arguments to pass to the function.
253
+ *args (Any): Positional arguments to pass to the function.
254
+ timeout (float | None): Maximum time in seconds to wait for the subprocess.
255
+ If None, wait indefinitely. If the subprocess exceeds this time, it will be terminated.
256
+ **kwargs (Any): Keyword arguments to pass to the function.
263
257
 
264
258
  Returns:
265
259
  R: The return value of the function.
266
260
 
267
261
  Raises:
268
- RuntimeError: If the subprocess exits with a non-zero exit code.
262
+ RuntimeError: If the subprocess exits with a non-zero exit code or times out.
263
+ TimeoutError: If the subprocess exceeds the specified timeout.
269
264
 
270
265
  Examples:
271
266
  .. code-block:: python
@@ -285,6 +280,9 @@ def run_in_subprocess(
285
280
  . return f"{greeting}, {name}!"
286
281
  > run_in_subprocess(greet, "World", greeting="Hi")
287
282
  'Hi, World!'
283
+
284
+ > # With timeout to prevent hanging
285
+ > run_in_subprocess(some_gpu_func, data, timeout=300.0)
288
286
  """
289
287
  import multiprocessing as mp
290
288
  from multiprocessing import Queue
@@ -298,20 +296,40 @@ def run_in_subprocess(
298
296
  args=(result_queue, func, args, kwargs)
299
297
  )
300
298
  process.start()
301
- process.join()
299
+
300
+ # Join with timeout to prevent indefinite hanging
301
+ process.join(timeout=timeout)
302
+
303
+ # Check if process is still alive (timed out)
304
+ if process.is_alive():
305
+ process.terminate()
306
+ time.sleep(0.5) # Give it a moment to terminate gracefully
307
+ if process.is_alive():
308
+ process.kill()
309
+ process.join()
310
+ raise TimeoutError(f"Subprocess exceeded timeout of {timeout} seconds and was terminated")
302
311
 
303
312
  # Check exit code
304
313
  if process.exitcode != 0:
305
- raise RuntimeError(f"Subprocess failed with exit code {process.exitcode}")
314
+ # Try to get any exception from the queue (non-blocking)
315
+ error_msg = f"Subprocess failed with exit code {process.exitcode}"
316
+ try:
317
+ if not result_queue.empty():
318
+ result_or_exception = result_queue.get_nowait()
319
+ if isinstance(result_or_exception, Exception):
320
+ raise result_or_exception
321
+ except Exception:
322
+ pass
323
+ raise RuntimeError(error_msg)
306
324
 
307
325
  # Retrieve the result
308
- if not result_queue.empty():
309
- result_or_exception = result_queue.get()
326
+ try:
327
+ result_or_exception = result_queue.get_nowait()
310
328
  if isinstance(result_or_exception, Exception):
311
329
  raise result_or_exception
312
330
  return result_or_exception
313
- else:
314
- raise RuntimeError("Subprocess did not return any result")
331
+ except Exception as e:
332
+ raise RuntimeError("Subprocess did not return any result") from e
315
333
 
316
334
 
317
335
  # "Private" function for subprocess wrapper (must be at module level for pickling on Windows)
@@ -1,4 +1,4 @@
1
- from .decorators import LogLevels as LogLevels, handle_error as handle_error
1
+ from .ctx import SetMPStartMethod as SetMPStartMethod
2
2
  from .print import BAR_FORMAT as BAR_FORMAT, MAGENTA as MAGENTA
3
3
  from collections.abc import Callable
4
4
  from typing import Any, TypeVar
@@ -10,7 +10,7 @@ CPU_COUNT: int
10
10
  T = TypeVar('T')
11
11
  R = TypeVar('R')
12
12
 
13
- def multiprocessing(func: Callable[[T], R] | list[Callable[[T], R]], args: list[T], use_starmap: bool = False, chunksize: int = 1, desc: str = '', max_workers: int = ..., delay_first_calls: float = 0, color: str = ..., bar_format: str = ..., ascii: bool = False) -> list[R]:
13
+ def multiprocessing(func: Callable[..., R] | list[Callable[..., R]], args: list[T], use_starmap: bool = False, chunksize: int = 1, desc: str = '', max_workers: int = ..., delay_first_calls: float = 0, color: str = ..., bar_format: str = ..., ascii: bool = False) -> list[R]:
14
14
  ''' Method to execute a function in parallel using multiprocessing
15
15
 
16
16
  \t- For CPU-bound operations where the GIL (Global Interpreter Lock) is a bottleneck.
@@ -64,7 +64,7 @@ def multiprocessing(func: Callable[[T], R] | list[Callable[[T], R]], args: list[
64
64
  \t\t\t. )
65
65
  \t\t\t[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
66
66
  \t'''
67
- def multithreading(func: Callable[[T], R] | list[Callable[[T], R]], args: list[T], use_starmap: bool = False, desc: str = '', max_workers: int = ..., delay_first_calls: float = 0, color: str = ..., bar_format: str = ..., ascii: bool = False) -> list[R]:
67
+ def multithreading(func: Callable[..., R] | list[Callable[..., R]], args: list[T], use_starmap: bool = False, desc: str = '', max_workers: int = ..., delay_first_calls: float = 0, color: str = ..., bar_format: str = ..., ascii: bool = False) -> list[R]:
68
68
  ''' Method to execute a function in parallel using multithreading, you should use it:
69
69
 
70
70
  \t- For I/O-bound operations where the GIL is not a bottleneck, such as network requests or disk operations.
@@ -116,7 +116,7 @@ def multithreading(func: Callable[[T], R] | list[Callable[[T], R]], args: list[T
116
116
  \t\t\t. )
117
117
  \t\t\t[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
118
118
  \t'''
119
- def run_in_subprocess(func: Callable[..., R], *args: Any, **kwargs: Any) -> R:
119
+ def run_in_subprocess(func: Callable[..., R], *args: Any, timeout: float | None = None, **kwargs: Any) -> R:
120
120
  ''' Execute a function in a subprocess with positional and keyword arguments.
121
121
 
122
122
  \tThis is useful when you need to run a function in isolation to avoid memory leaks,
@@ -124,16 +124,19 @@ def run_in_subprocess(func: Callable[..., R], *args: Any, **kwargs: Any) -> R:
124
124
  \tbe created, run the function with the provided arguments, and return the result.
125
125
 
126
126
  \tArgs:
127
- \t\tfunc (Callable): The function to execute in a subprocess.
127
+ \t\tfunc (Callable): The function to execute in a subprocess.
128
128
  \t\t\t(SHOULD BE A TOP-LEVEL FUNCTION TO BE PICKLABLE)
129
- \t\t*args (Any): Positional arguments to pass to the function.
130
- \t\t**kwargs (Any): Keyword arguments to pass to the function.
129
+ \t\t*args (Any): Positional arguments to pass to the function.
130
+ \t\ttimeout (float | None): Maximum time in seconds to wait for the subprocess.
131
+ \t\t\tIf None, wait indefinitely. If the subprocess exceeds this time, it will be terminated.
132
+ \t\t**kwargs (Any): Keyword arguments to pass to the function.
131
133
 
132
134
  \tReturns:
133
135
  \t\tR: The return value of the function.
134
136
 
135
137
  \tRaises:
136
- \t\tRuntimeError: If the subprocess exits with a non-zero exit code.
138
+ \t\tRuntimeError: If the subprocess exits with a non-zero exit code or times out.
139
+ \t\tTimeoutError: If the subprocess exceeds the specified timeout.
137
140
 
138
141
  \tExamples:
139
142
  \t\t.. code-block:: python
@@ -153,6 +156,9 @@ def run_in_subprocess(func: Callable[..., R], *args: Any, **kwargs: Any) -> R:
153
156
  \t\t\t. return f"{greeting}, {name}!"
154
157
  \t\t\t> run_in_subprocess(greet, "World", greeting="Hi")
155
158
  \t\t\t\'Hi, World!\'
159
+
160
+ \t\t\t> # With timeout to prevent hanging
161
+ \t\t\t> run_in_subprocess(some_gpu_func, data, timeout=300.0)
156
162
  \t'''
157
163
  def _subprocess_wrapper(result_queue: Any, func: Callable[..., R], args: tuple[Any, ...], kwargs: dict[str, Any]) -> None:
158
164
  """ Wrapper function to execute the target function and store the result in the queue.
File without changes
File without changes
File without changes
File without changes