stouputils 1.9.5__tar.gz → 1.9.7__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.5 → stouputils-1.9.7}/PKG-INFO +1 -1
  2. {stouputils-1.9.5 → stouputils-1.9.7}/pyproject.toml +1 -1
  3. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/ctx.py +94 -21
  4. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/ctx.pyi +50 -11
  5. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/parallel.py +22 -4
  6. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/parallel.pyi +8 -4
  7. {stouputils-1.9.5 → stouputils-1.9.7}/.gitignore +0 -0
  8. {stouputils-1.9.5 → stouputils-1.9.7}/LICENSE +0 -0
  9. {stouputils-1.9.5 → stouputils-1.9.7}/README.md +0 -0
  10. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/__init__.py +0 -0
  11. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/__init__.pyi +0 -0
  12. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/__main__.py +0 -0
  13. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/_deprecated.py +0 -0
  14. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/_deprecated.pyi +0 -0
  15. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/all_doctests.py +0 -0
  16. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/all_doctests.pyi +0 -0
  17. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/__init__.py +0 -0
  18. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/__init__.pyi +0 -0
  19. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/automatic_docs.py +0 -0
  20. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/automatic_docs.pyi +0 -0
  21. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/upscaler/__init__.py +0 -0
  22. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/upscaler/__init__.pyi +0 -0
  23. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/upscaler/config.py +0 -0
  24. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/upscaler/config.pyi +0 -0
  25. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/upscaler/image.py +0 -0
  26. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/upscaler/image.pyi +0 -0
  27. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/upscaler/video.py +0 -0
  28. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/applications/upscaler/video.pyi +0 -0
  29. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/archive.py +0 -0
  30. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/archive.pyi +0 -0
  31. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/backup.py +0 -0
  32. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/backup.pyi +0 -0
  33. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/collections.py +0 -0
  34. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/collections.pyi +0 -0
  35. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/__init__.py +0 -0
  36. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/__init__.pyi +0 -0
  37. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/cd_utils.py +0 -0
  38. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/cd_utils.pyi +0 -0
  39. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/github.py +0 -0
  40. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/github.pyi +0 -0
  41. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/pypi.py +0 -0
  42. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/pypi.pyi +0 -0
  43. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/pyproject.py +0 -0
  44. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/pyproject.pyi +0 -0
  45. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/stubs.py +0 -0
  46. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/continuous_delivery/stubs.pyi +0 -0
  47. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/config/get.py +0 -0
  48. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/config/set.py +0 -0
  49. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/__init__.py +0 -0
  50. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/auto_contrast.py +0 -0
  51. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/axis_flip.py +0 -0
  52. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/bias_field_correction.py +0 -0
  53. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/binary_threshold.py +0 -0
  54. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/blur.py +0 -0
  55. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/brightness.py +0 -0
  56. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/canny.py +0 -0
  57. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/clahe.py +0 -0
  58. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/common.py +0 -0
  59. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/contrast.py +0 -0
  60. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/curvature_flow_filter.py +0 -0
  61. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/denoise.py +0 -0
  62. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/histogram_equalization.py +0 -0
  63. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/invert.py +0 -0
  64. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/laplacian.py +0 -0
  65. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/median_blur.py +0 -0
  66. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/noise.py +0 -0
  67. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/normalize.py +0 -0
  68. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/random_erase.py +0 -0
  69. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/resize.py +0 -0
  70. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/rotation.py +0 -0
  71. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/salt_pepper.py +0 -0
  72. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/sharpening.py +0 -0
  73. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/shearing.py +0 -0
  74. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/threshold.py +0 -0
  75. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/translation.py +0 -0
  76. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image/zoom.py +0 -0
  77. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image_augmentation.py +0 -0
  78. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/image_preprocess.py +0 -0
  79. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/prosthesis_detection.py +0 -0
  80. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/data_processing/technique.py +0 -0
  81. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/dataset/__init__.py +0 -0
  82. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/dataset/dataset.py +0 -0
  83. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/dataset/dataset_loader.py +0 -0
  84. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/dataset/grouping_strategy.py +0 -0
  85. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/dataset/image_loader.py +0 -0
  86. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/dataset/xy_tuple.py +0 -0
  87. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/metric_dictionnary.py +0 -0
  88. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/metric_utils.py +0 -0
  89. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/mlflow_utils.py +0 -0
  90. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/abstract_model.py +0 -0
  91. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/all.py +0 -0
  92. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/base_keras.py +0 -0
  93. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras/all.py +0 -0
  94. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras/convnext.py +0 -0
  95. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras/densenet.py +0 -0
  96. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras/efficientnet.py +0 -0
  97. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras/mobilenet.py +0 -0
  98. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras/resnet.py +0 -0
  99. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras/squeezenet.py +0 -0
  100. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras/vgg.py +0 -0
  101. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras/xception.py +0 -0
  102. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras_utils/callbacks/__init__.py +0 -0
  103. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +0 -0
  104. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +0 -0
  105. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +0 -0
  106. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +0 -0
  107. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +0 -0
  108. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras_utils/losses/__init__.py +0 -0
  109. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +0 -0
  110. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/keras_utils/visualizations.py +0 -0
  111. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/model_interface.py +0 -0
  112. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/models/sandbox.py +0 -0
  113. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/range_tuple.py +0 -0
  114. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/scripts/augment_dataset.py +0 -0
  115. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/scripts/exhaustive_process.py +0 -0
  116. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/scripts/preprocess_dataset.py +0 -0
  117. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/scripts/routine.py +0 -0
  118. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/data_science/utils.py +0 -0
  119. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/decorators.py +0 -0
  120. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/decorators.pyi +0 -0
  121. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/image.py +0 -0
  122. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/image.pyi +0 -0
  123. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/__init__.py +0 -0
  124. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/__init__.pyi +0 -0
  125. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/common.py +0 -0
  126. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/common.pyi +0 -0
  127. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/downloader.py +0 -0
  128. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/downloader.pyi +0 -0
  129. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/linux.py +0 -0
  130. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/linux.pyi +0 -0
  131. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/main.py +0 -0
  132. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/main.pyi +0 -0
  133. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/windows.py +0 -0
  134. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/installer/windows.pyi +0 -0
  135. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/io.py +0 -0
  136. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/io.pyi +0 -0
  137. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/print.py +0 -0
  138. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/print.pyi +0 -0
  139. {stouputils-1.9.5 → stouputils-1.9.7}/stouputils/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stouputils
3
- Version: 1.9.5
3
+ Version: 1.9.7
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
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
5
5
 
6
6
  [project]
7
7
  name = "stouputils"
8
- version = "1.9.5"
8
+ version = "1.9.7"
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"
@@ -1,5 +1,6 @@
1
1
  """
2
- This module provides context managers for temporarily silencing output.
2
+ This module provides context managers for various utilities such as logging to a file,
3
+ measuring execution time, silencing output, and setting multiprocessing start methods.
3
4
 
4
5
  - LogToFile: Context manager to log to a file every print call (with LINE_UP handling)
5
6
  - MeasureTime: Context manager to measure execution time of a code block
@@ -18,14 +19,22 @@ import os
18
19
  import sys
19
20
  import time
20
21
  from collections.abc import Callable
21
- from typing import IO, Any, TextIO
22
+ from contextlib import AbstractAsyncContextManager, AbstractContextManager
23
+ from typing import IO, Any, TextIO, TypeVar
22
24
 
23
25
  from .io import super_open
24
26
  from .print import TeeMultiOutput, debug
25
27
 
28
+ # Type variable for context managers
29
+ T = TypeVar("T")
30
+
31
+ # Abstract base class for context managers supporting both sync and async usage
32
+ class AbstractBothContextManager(AbstractContextManager[T], AbstractAsyncContextManager[T]):
33
+ """ Abstract base class for context managers that support both synchronous and asynchronous usage. """
34
+ pass
26
35
 
27
36
  # Context manager to log to a file
28
- class LogToFile:
37
+ class LogToFile(AbstractBothContextManager["LogToFile"]):
29
38
  """ Context manager to log to a file.
30
39
 
31
40
  This context manager allows you to temporarily log output to a file while still printing normally.
@@ -38,6 +47,8 @@ class LogToFile:
38
47
  tee_stdout (bool): Whether to redirect stdout to the file (default: True)
39
48
  tee_stderr (bool): Whether to redirect stderr to the file (default: True)
40
49
  ignore_lineup (bool): Whether to ignore lines containing LINE_UP escape sequence in files (default: False)
50
+ restore_on_exit (bool): Whether to restore original stdout/stderr on exit (default: False)
51
+ This ctx uses TeeMultiOutput which handles closed files gracefully, so restoring is not mandatory.
41
52
 
42
53
  Examples:
43
54
  .. code-block:: python
@@ -46,6 +57,11 @@ class LogToFile:
46
57
  > with stp.LogToFile("output.log"):
47
58
  > stp.info("This will be logged to output.log and printed normally")
48
59
  > print("This will also be logged")
60
+
61
+ > with stp.LogToFile("output.log") as log_ctx:
62
+ > stp.warning("This will be logged to output.log and printed normally")
63
+ > log_ctx.change_file("new_file.log")
64
+ > print("This will be logged to new_file.log")
49
65
  """
50
66
  def __init__(
51
67
  self,
@@ -54,7 +70,8 @@ class LogToFile:
54
70
  encoding: str = "utf-8",
55
71
  tee_stdout: bool = True,
56
72
  tee_stderr: bool = True,
57
- ignore_lineup: bool = True
73
+ ignore_lineup: bool = True,
74
+ restore_on_exit: bool = False
58
75
  ) -> None:
59
76
  self.path: str = path
60
77
  """ Attribute remembering path to the log file """
@@ -68,19 +85,27 @@ class LogToFile:
68
85
  """ Whether to redirect stderr to the file """
69
86
  self.ignore_lineup: bool = ignore_lineup
70
87
  """ Whether to ignore lines containing LINE_UP escape sequence in files """
71
- self.file: IO[Any] = super_open(self.path, mode=self.mode, encoding=self.encoding)
88
+ self.restore_on_exit: bool = restore_on_exit
89
+ """ Whether to restore original stdout/stderr on exit.
90
+ This ctx uses TeeMultiOutput which handles closed files gracefully, so restoring is not mandatory. """
91
+ self.file: IO[Any]
72
92
  """ Attribute remembering opened file """
73
- self.original_stdout: TextIO = sys.stdout
93
+ self.original_stdout: TextIO
74
94
  """ Original stdout before redirection """
75
- self.original_stderr: TextIO = sys.stderr
95
+ self.original_stderr: TextIO
76
96
  """ Original stderr before redirection """
77
97
 
78
98
  def __enter__(self) -> LogToFile:
79
99
  """ Enter context manager which opens the log file and redirects stdout/stderr """
100
+ # Open file
101
+ self.file = super_open(self.path, mode=self.mode, encoding=self.encoding)
102
+
80
103
  # Redirect stdout and stderr if requested
81
104
  if self.tee_stdout:
105
+ self.original_stdout = sys.stdout
82
106
  sys.stdout = TeeMultiOutput(self.original_stdout, self.file, ignore_lineup=self.ignore_lineup)
83
107
  if self.tee_stderr:
108
+ self.original_stderr = sys.stderr
84
109
  sys.stderr = TeeMultiOutput(self.original_stderr, self.file, ignore_lineup=self.ignore_lineup)
85
110
 
86
111
  # Return self
@@ -88,16 +113,35 @@ class LogToFile:
88
113
 
89
114
  def __exit__(self, exc_type: type[BaseException]|None, exc_val: BaseException|None, exc_tb: Any|None) -> None:
90
115
  """ Exit context manager which closes the log file and restores stdout/stderr """
91
- # Restore original stdout and stderr
92
- if self.tee_stdout:
93
- sys.stdout = self.original_stdout
94
-
95
- if self.tee_stderr:
96
- sys.stderr = self.original_stderr
116
+ # Restore original stdout and stderr (if requested)
117
+ if self.restore_on_exit:
118
+ if self.tee_stdout:
119
+ sys.stdout = self.original_stdout
120
+ if self.tee_stderr:
121
+ sys.stderr = self.original_stderr
97
122
 
98
123
  # Close file
99
124
  self.file.close()
100
125
 
126
+ async def __aenter__(self) -> LogToFile:
127
+ """ Enter async context manager which opens the log file and redirects stdout/stderr """
128
+ return self.__enter__()
129
+
130
+ async def __aexit__(self, exc_type: type[BaseException]|None, exc_val: BaseException|None, exc_tb: Any|None) -> None:
131
+ """ Exit async context manager which closes the log file and restores stdout/stderr """
132
+ self.__exit__(exc_type, exc_val, exc_tb)
133
+
134
+ def change_file(self, new_path: str) -> None:
135
+ """ Change the log file to a new path.
136
+
137
+ Args:
138
+ new_path (str): New path to the log file
139
+ """
140
+ # Close current file, open new file and redirect outputs
141
+ self.file.close()
142
+ self.path = new_path
143
+ self.__enter__()
144
+
101
145
  @staticmethod
102
146
  def common(logs_folder: str, filepath: str, func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
103
147
  """ Common code used at the beginning of a program to launch main function
@@ -129,7 +173,7 @@ class LogToFile:
129
173
  return func(*args, **kwargs)
130
174
 
131
175
  # Context manager to measure execution time
132
- class MeasureTime:
176
+ class MeasureTime(AbstractBothContextManager["MeasureTime"]):
133
177
  """ Context manager to measure execution time.
134
178
 
135
179
  This context manager measures the execution time of the code block it wraps
@@ -202,9 +246,18 @@ class MeasureTime:
202
246
  hours: int = hours % 24
203
247
  self.print_func(f"{self.message}: {days}d {hours}h {minutes}m {seconds}s")
204
248
 
249
+ async def __aenter__(self) -> MeasureTime:
250
+ """ Enter async context manager, record start time """
251
+ return self.__enter__()
252
+
253
+ async def __aexit__(self, exc_type: type[BaseException]|None, exc_val: BaseException|None, exc_tb: Any|None) -> None:
254
+ """ Exit async context manager, calculate duration and print """
255
+ self.__exit__(exc_type, exc_val, exc_tb)
256
+
205
257
  # Context manager to temporarily silence output
206
- class Muffle:
258
+ class Muffle(AbstractBothContextManager["Muffle"]):
207
259
  """ Context manager that temporarily silences output.
260
+ (No thread-safety guaranteed)
208
261
 
209
262
  Alternative to stouputils.decorators.silent()
210
263
 
@@ -215,18 +268,20 @@ class Muffle:
215
268
  def __init__(self, mute_stderr: bool = False) -> None:
216
269
  self.mute_stderr: bool = mute_stderr
217
270
  """ Attribute remembering if stderr should be muted """
218
- self.original_stdout: TextIO = sys.stdout
271
+ self.original_stdout: IO[Any]
219
272
  """ Attribute remembering original stdout """
220
- self.original_stderr: TextIO = sys.stderr
273
+ self.original_stderr: IO[Any]
221
274
  """ Attribute remembering original stderr """
222
275
 
223
276
  def __enter__(self) -> Muffle:
224
277
  """ Enter context manager which redirects stdout and stderr to devnull """
225
278
  # Redirect stdout to devnull
279
+ self.original_stdout = sys.stdout
226
280
  sys.stdout = open(os.devnull, "w", encoding="utf-8")
227
281
 
228
282
  # Redirect stderr to devnull if needed
229
283
  if self.mute_stderr:
284
+ self.original_stderr = sys.stderr
230
285
  sys.stderr = open(os.devnull, "w", encoding="utf-8")
231
286
 
232
287
  # Return self
@@ -243,8 +298,16 @@ class Muffle:
243
298
  sys.stderr.close()
244
299
  sys.stderr = self.original_stderr
245
300
 
301
+ async def __aenter__(self) -> Muffle:
302
+ """ Enter async context manager which redirects stdout and stderr to devnull """
303
+ return self.__enter__()
304
+
305
+ async def __aexit__(self, exc_type: type[BaseException]|None, exc_val: BaseException|None, exc_tb: Any|None) -> None:
306
+ """ Exit async context manager which restores original stdout and stderr """
307
+ self.__exit__(exc_type, exc_val, exc_tb)
308
+
246
309
  # Context manager that does nothing
247
- class DoNothing:
310
+ class DoNothing(AbstractBothContextManager["DoNothing"]):
248
311
  """ Context manager that does nothing.
249
312
 
250
313
  This is a no-op context manager that can be used as a placeholder
@@ -269,7 +332,7 @@ class DoNothing:
269
332
  """ No initialization needed, this is a no-op context manager """
270
333
  pass
271
334
 
272
- def __enter__(self) -> Any:
335
+ def __enter__(self) -> DoNothing:
273
336
  """ Enter context manager (does nothing) """
274
337
  return self
275
338
 
@@ -277,16 +340,18 @@ class DoNothing:
277
340
  """ Exit context manager (does nothing) """
278
341
  pass
279
342
 
280
- async def __aenter__(self) -> Any:
343
+ async def __aenter__(self) -> DoNothing:
281
344
  """ Enter async context manager (does nothing) """
282
345
  return self
283
346
 
284
347
  async def __aexit__(self, *excinfo: Any) -> None:
285
348
  """ Exit async context manager (does nothing) """
286
349
  pass
350
+ NullContextManager = DoNothing
351
+ """ Alias for DoNothing context manager """
287
352
 
288
353
  # Context manager to temporarily set multiprocessing start method
289
- class SetMPStartMethod:
354
+ class SetMPStartMethod(AbstractBothContextManager["SetMPStartMethod"]):
290
355
  """ Context manager to temporarily set multiprocessing start method.
291
356
 
292
357
  This context manager allows you to temporarily change the multiprocessing start method
@@ -333,3 +398,11 @@ class SetMPStartMethod:
333
398
  if self.old_method != self.start_method:
334
399
  mp.set_start_method(self.old_method, force=True)
335
400
 
401
+ async def __aenter__(self) -> SetMPStartMethod:
402
+ """ Enter async context manager which sets the start method """
403
+ return self.__enter__()
404
+
405
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
406
+ """ Exit async context manager which restores the original start method """
407
+ self.__exit__(exc_type, exc_val, exc_tb)
408
+
@@ -1,9 +1,16 @@
1
+ import abc
1
2
  from .io import super_open as super_open
2
3
  from .print import TeeMultiOutput as TeeMultiOutput, debug as debug
3
4
  from collections.abc import Callable as Callable
4
- from typing import Any, IO, TextIO
5
+ from contextlib import AbstractAsyncContextManager, AbstractContextManager
6
+ from typing import Any, IO, TextIO, TypeVar
5
7
 
6
- class LogToFile:
8
+ T = TypeVar('T')
9
+
10
+ class AbstractBothContextManager(AbstractContextManager[T], AbstractAsyncContextManager[T], metaclass=abc.ABCMeta):
11
+ """ Abstract base class for context managers that support both synchronous and asynchronous usage. """
12
+
13
+ class LogToFile(AbstractBothContextManager['LogToFile']):
7
14
  ''' Context manager to log to a file.
8
15
 
9
16
  \tThis context manager allows you to temporarily log output to a file while still printing normally.
@@ -16,6 +23,8 @@ class LogToFile:
16
23
  \t\ttee_stdout (bool): Whether to redirect stdout to the file (default: True)
17
24
  \t\ttee_stderr (bool): Whether to redirect stderr to the file (default: True)
18
25
  \t\tignore_lineup (bool): Whether to ignore lines containing LINE_UP escape sequence in files (default: False)
26
+ \t\trestore_on_exit (bool): Whether to restore original stdout/stderr on exit (default: False)
27
+ \t\t\tThis ctx uses TeeMultiOutput which handles closed files gracefully, so restoring is not mandatory.
19
28
 
20
29
  \tExamples:
21
30
  \t\t.. code-block:: python
@@ -24,6 +33,11 @@ class LogToFile:
24
33
  \t\t\t> with stp.LogToFile("output.log"):
25
34
  \t\t\t> stp.info("This will be logged to output.log and printed normally")
26
35
  \t\t\t> print("This will also be logged")
36
+
37
+ \t\t\t> with stp.LogToFile("output.log") as log_ctx:
38
+ \t\t\t> stp.warning("This will be logged to output.log and printed normally")
39
+ \t\t\t> log_ctx.change_file("new_file.log")
40
+ \t\t\t> print("This will be logged to new_file.log")
27
41
  \t'''
28
42
  path: str
29
43
  mode: str
@@ -31,14 +45,25 @@ class LogToFile:
31
45
  tee_stdout: bool
32
46
  tee_stderr: bool
33
47
  ignore_lineup: bool
48
+ restore_on_exit: bool
34
49
  file: IO[Any]
35
50
  original_stdout: TextIO
36
51
  original_stderr: TextIO
37
- def __init__(self, path: str, mode: str = 'w', encoding: str = 'utf-8', tee_stdout: bool = True, tee_stderr: bool = True, ignore_lineup: bool = True) -> None: ...
52
+ def __init__(self, path: str, mode: str = 'w', encoding: str = 'utf-8', tee_stdout: bool = True, tee_stderr: bool = True, ignore_lineup: bool = True, restore_on_exit: bool = False) -> None: ...
38
53
  def __enter__(self) -> LogToFile:
39
54
  """ Enter context manager which opens the log file and redirects stdout/stderr """
40
55
  def __exit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
41
56
  """ Exit context manager which closes the log file and restores stdout/stderr """
57
+ async def __aenter__(self) -> LogToFile:
58
+ """ Enter async context manager which opens the log file and redirects stdout/stderr """
59
+ async def __aexit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
60
+ """ Exit async context manager which closes the log file and restores stdout/stderr """
61
+ def change_file(self, new_path: str) -> None:
62
+ """ Change the log file to a new path.
63
+
64
+ \t\tArgs:
65
+ \t\t\tnew_path (str): New path to the log file
66
+ \t\t"""
42
67
  @staticmethod
43
68
  def common(logs_folder: str, filepath: str, func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
44
69
  ''' Common code used at the beginning of a program to launch main function
@@ -57,7 +82,7 @@ class LogToFile:
57
82
  \t\t\t... LogToFile.common(f"{ROOT}/logs", __file__, main)
58
83
  \t\t'''
59
84
 
60
- class MeasureTime:
85
+ class MeasureTime(AbstractBothContextManager['MeasureTime']):
61
86
  ''' Context manager to measure execution time.
62
87
 
63
88
  \tThis context manager measures the execution time of the code block it wraps
@@ -91,9 +116,14 @@ class MeasureTime:
91
116
  """ Enter context manager, record start time """
92
117
  def __exit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
93
118
  """ Exit context manager, calculate duration and print """
119
+ async def __aenter__(self) -> MeasureTime:
120
+ """ Enter async context manager, record start time """
121
+ async def __aexit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
122
+ """ Exit async context manager, calculate duration and print """
94
123
 
95
- class Muffle:
124
+ class Muffle(AbstractBothContextManager['Muffle']):
96
125
  ''' Context manager that temporarily silences output.
126
+ \t(No thread-safety guaranteed)
97
127
 
98
128
  \tAlternative to stouputils.decorators.silent()
99
129
 
@@ -102,15 +132,19 @@ class Muffle:
102
132
  \t\t... print("This will not be printed")
103
133
  \t'''
104
134
  mute_stderr: bool
105
- original_stdout: TextIO
106
- original_stderr: TextIO
135
+ original_stdout: IO[Any]
136
+ original_stderr: IO[Any]
107
137
  def __init__(self, mute_stderr: bool = False) -> None: ...
108
138
  def __enter__(self) -> Muffle:
109
139
  """ Enter context manager which redirects stdout and stderr to devnull """
110
140
  def __exit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
111
141
  """ Exit context manager which restores original stdout and stderr """
142
+ async def __aenter__(self) -> Muffle:
143
+ """ Enter async context manager which redirects stdout and stderr to devnull """
144
+ async def __aexit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
145
+ """ Exit async context manager which restores original stdout and stderr """
112
146
 
113
- class DoNothing:
147
+ class DoNothing(AbstractBothContextManager['DoNothing']):
114
148
  ''' Context manager that does nothing.
115
149
 
116
150
  \tThis is a no-op context manager that can be used as a placeholder
@@ -133,16 +167,17 @@ class DoNothing:
133
167
  \t'''
134
168
  def __init__(self, *args: Any, **kwargs: Any) -> None:
135
169
  """ No initialization needed, this is a no-op context manager """
136
- def __enter__(self) -> Any:
170
+ def __enter__(self) -> DoNothing:
137
171
  """ Enter context manager (does nothing) """
138
172
  def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
139
173
  """ Exit context manager (does nothing) """
140
- async def __aenter__(self) -> Any:
174
+ async def __aenter__(self) -> DoNothing:
141
175
  """ Enter async context manager (does nothing) """
142
176
  async def __aexit__(self, *excinfo: Any) -> None:
143
177
  """ Exit async context manager (does nothing) """
178
+ NullContextManager = DoNothing
144
179
 
145
- class SetMPStartMethod:
180
+ class SetMPStartMethod(AbstractBothContextManager['SetMPStartMethod']):
146
181
  ''' Context manager to temporarily set multiprocessing start method.
147
182
 
148
183
  \tThis context manager allows you to temporarily change the multiprocessing start method
@@ -170,3 +205,7 @@ class SetMPStartMethod:
170
205
  """ Enter context manager which sets the start method """
171
206
  def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
172
207
  """ Exit context manager which restores the original start method """
208
+ async def __aenter__(self) -> SetMPStartMethod:
209
+ """ Enter async context manager which sets the start method """
210
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
211
+ """ Exit async context manager which restores the original start method """
@@ -40,7 +40,7 @@ def multiprocessing(
40
40
  use_starmap: bool = False,
41
41
  chunksize: int = 1,
42
42
  desc: str = "",
43
- max_workers: int = CPU_COUNT,
43
+ max_workers: int | float = CPU_COUNT,
44
44
  delay_first_calls: float = 0,
45
45
  color: str = MAGENTA,
46
46
  bar_format: str = BAR_FORMAT,
@@ -61,7 +61,9 @@ def multiprocessing(
61
61
  (Defaults to 1 for proper progress bar display)
62
62
  desc (str): Description displayed in the progress bar
63
63
  (if not provided no progress bar will be displayed)
64
- max_workers (int): Number of workers to use (Defaults to CPU_COUNT), -1 means CPU_COUNT
64
+ max_workers (int | float): Number of workers to use (Defaults to CPU_COUNT), -1 means CPU_COUNT.
65
+ If float between 0 and 1, it's treated as a percentage of CPU_COUNT.
66
+ If negative float between -1 and 0, it's treated as a percentage of len(args).
65
67
  delay_first_calls (float): Apply i*delay_first_calls seconds delay to the first "max_workers" calls.
66
68
  For instance, the first process will be delayed by 0 seconds, the second by 1 second, etc.
67
69
  (Defaults to 0): This can be useful to avoid functions being called in the same second.
@@ -109,6 +111,13 @@ def multiprocessing(
109
111
  # Handle parameters
110
112
  if max_workers == -1:
111
113
  max_workers = CPU_COUNT
114
+ if isinstance(max_workers, float):
115
+ if max_workers > 0:
116
+ assert max_workers <= 1, "max_workers as positive float must be between 0 and 1 (percentage of CPU_COUNT)"
117
+ max_workers = int(max_workers * CPU_COUNT)
118
+ else:
119
+ assert -1 <= max_workers < 0, "max_workers as negative float must be between -1 and 0 (percentage of len(args))"
120
+ max_workers = int(-max_workers * len(args))
112
121
  verbose: bool = desc != ""
113
122
  desc, func, args = _handle_parameters(func, args, use_starmap, delay_first_calls, max_workers, desc, color)
114
123
  if bar_format == BAR_FORMAT:
@@ -148,7 +157,7 @@ def multithreading(
148
157
  args: list[T],
149
158
  use_starmap: bool = False,
150
159
  desc: str = "",
151
- max_workers: int = CPU_COUNT,
160
+ max_workers: int | float = CPU_COUNT,
152
161
  delay_first_calls: float = 0,
153
162
  color: str = MAGENTA,
154
163
  bar_format: str = BAR_FORMAT,
@@ -167,7 +176,9 @@ def multithreading(
167
176
  True means the function will be called like func(\*args[i]) instead of func(args[i])
168
177
  desc (str): Description displayed in the progress bar
169
178
  (if not provided no progress bar will be displayed)
170
- max_workers (int): Number of workers to use (Defaults to CPU_COUNT), -1 means CPU_COUNT
179
+ max_workers (int | float): Number of workers to use (Defaults to CPU_COUNT), -1 means CPU_COUNT.
180
+ If float between 0 and 1, it's treated as a percentage of CPU_COUNT.
181
+ If negative float between -1 and 0, it's treated as a percentage of len(args).
171
182
  delay_first_calls (float): Apply i*delay_first_calls seconds delay to the first "max_workers" calls.
172
183
  For instance with value to 1, the first thread will be delayed by 0 seconds, the second by 1 second, etc.
173
184
  (Defaults to 0): This can be useful to avoid functions being called in the same second.
@@ -213,6 +224,13 @@ def multithreading(
213
224
  # Handle parameters
214
225
  if max_workers == -1:
215
226
  max_workers = CPU_COUNT
227
+ if isinstance(max_workers, float):
228
+ if max_workers > 0:
229
+ assert max_workers <= 1, "max_workers as positive float must be between 0 and 1 (percentage of CPU_COUNT)"
230
+ max_workers = int(max_workers * CPU_COUNT)
231
+ else:
232
+ assert -1 <= max_workers < 0, "max_workers as negative float must be between -1 and 0 (percentage of len(args))"
233
+ max_workers = int(-max_workers * len(args))
216
234
  verbose: bool = desc != ""
217
235
  desc, func, args = _handle_parameters(func, args, use_starmap, delay_first_calls, max_workers, desc, color)
218
236
  if bar_format == BAR_FORMAT:
@@ -10,7 +10,7 @@ CPU_COUNT: int
10
10
  T = TypeVar('T')
11
11
  R = TypeVar('R')
12
12
 
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]:
13
+ def multiprocessing(func: Callable[..., R] | list[Callable[..., R]], args: list[T], use_starmap: bool = False, chunksize: int = 1, desc: str = '', max_workers: int | float = ..., 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.
@@ -26,7 +26,9 @@ def multiprocessing(func: Callable[..., R] | list[Callable[..., R]], args: list[
26
26
  \t\t\t(Defaults to 1 for proper progress bar display)
27
27
  \t\tdesc\t\t\t\t(str):\t\t\t\tDescription displayed in the progress bar
28
28
  \t\t\t(if not provided no progress bar will be displayed)
29
- \t\tmax_workers\t\t\t(int):\t\t\t\tNumber of workers to use (Defaults to CPU_COUNT), -1 means CPU_COUNT
29
+ \t\tmax_workers\t\t\t(int | float):\t\tNumber of workers to use (Defaults to CPU_COUNT), -1 means CPU_COUNT.
30
+ \t\t\tIf float between 0 and 1, it\'s treated as a percentage of CPU_COUNT.
31
+ \t\t\tIf negative float between -1 and 0, it\'s treated as a percentage of len(args).
30
32
  \t\tdelay_first_calls\t(float):\t\t\tApply i*delay_first_calls seconds delay to the first "max_workers" calls.
31
33
  \t\t\tFor instance, the first process will be delayed by 0 seconds, the second by 1 second, etc.
32
34
  \t\t\t(Defaults to 0): This can be useful to avoid functions being called in the same second.
@@ -64,7 +66,7 @@ def multiprocessing(func: Callable[..., R] | list[Callable[..., R]], args: list[
64
66
  \t\t\t. )
65
67
  \t\t\t[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
66
68
  \t'''
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]:
69
+ def multithreading(func: Callable[..., R] | list[Callable[..., R]], args: list[T], use_starmap: bool = False, desc: str = '', max_workers: int | float = ..., delay_first_calls: float = 0, color: str = ..., bar_format: str = ..., ascii: bool = False) -> list[R]:
68
70
  ''' Method to execute a function in parallel using multithreading, you should use it:
69
71
 
70
72
  \t- For I/O-bound operations where the GIL is not a bottleneck, such as network requests or disk operations.
@@ -78,7 +80,9 @@ def multithreading(func: Callable[..., R] | list[Callable[..., R]], args: list[T
78
80
  \t\t\tTrue means the function will be called like func(\\*args[i]) instead of func(args[i])
79
81
  \t\tdesc\t\t\t\t(str):\t\t\t\tDescription displayed in the progress bar
80
82
  \t\t\t(if not provided no progress bar will be displayed)
81
- \t\tmax_workers\t\t\t(int):\t\t\t\tNumber of workers to use (Defaults to CPU_COUNT), -1 means CPU_COUNT
83
+ \t\tmax_workers\t\t\t(int | float):\t\tNumber of workers to use (Defaults to CPU_COUNT), -1 means CPU_COUNT.
84
+ \t\t\tIf float between 0 and 1, it\'s treated as a percentage of CPU_COUNT.
85
+ \t\t\tIf negative float between -1 and 0, it\'s treated as a percentage of len(args).
82
86
  \t\tdelay_first_calls\t(float):\t\t\tApply i*delay_first_calls seconds delay to the first "max_workers" calls.
83
87
  \t\t\tFor instance with value to 1, the first thread will be delayed by 0 seconds, the second by 1 second, etc.
84
88
  \t\t\t(Defaults to 0): This can be useful to avoid functions being called in the same second.
File without changes
File without changes
File without changes
File without changes
File without changes