stouputils 1.9.2__tar.gz → 1.9.4__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.
- {stouputils-1.9.2 → stouputils-1.9.4}/PKG-INFO +2 -2
- {stouputils-1.9.2 → stouputils-1.9.4}/README.md +1 -1
- {stouputils-1.9.2 → stouputils-1.9.4}/pyproject.toml +1 -1
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/__main__.py +8 -1
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/archive.py +19 -1
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/archive.pyi +1 -1
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/backup.py +17 -15
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/collections.py +6 -5
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/collections.pyi +4 -3
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/ctx.py +49 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/ctx.pyi +29 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/decorators.py +146 -42
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/decorators.pyi +52 -18
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/parallel.py +6 -11
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/parallel.pyi +5 -4
- {stouputils-1.9.2 → stouputils-1.9.4}/.gitignore +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/LICENSE +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/__init__.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/__init__.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/_deprecated.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/_deprecated.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/all_doctests.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/all_doctests.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/__init__.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/__init__.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/automatic_docs.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/automatic_docs.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/upscaler/__init__.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/upscaler/__init__.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/upscaler/config.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/upscaler/config.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/upscaler/image.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/upscaler/image.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/upscaler/video.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/applications/upscaler/video.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/backup.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/__init__.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/__init__.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/cd_utils.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/cd_utils.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/github.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/github.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/pypi.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/pypi.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/pyproject.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/pyproject.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/stubs.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/continuous_delivery/stubs.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/config/get.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/config/set.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/__init__.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/auto_contrast.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/axis_flip.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/bias_field_correction.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/binary_threshold.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/blur.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/brightness.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/canny.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/clahe.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/common.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/contrast.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/curvature_flow_filter.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/denoise.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/histogram_equalization.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/invert.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/laplacian.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/median_blur.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/noise.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/normalize.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/random_erase.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/resize.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/rotation.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/salt_pepper.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/sharpening.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/shearing.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/threshold.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/translation.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image/zoom.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image_augmentation.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/image_preprocess.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/prosthesis_detection.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/data_processing/technique.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/dataset/__init__.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/dataset/dataset.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/dataset/dataset_loader.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/dataset/grouping_strategy.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/dataset/image_loader.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/dataset/xy_tuple.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/metric_dictionnary.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/metric_utils.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/mlflow_utils.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/abstract_model.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/all.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/base_keras.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras/all.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras/convnext.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras/densenet.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras/efficientnet.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras/mobilenet.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras/resnet.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras/squeezenet.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras/vgg.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras/xception.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras_utils/callbacks/__init__.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras_utils/losses/__init__.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/keras_utils/visualizations.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/model_interface.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/models/sandbox.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/range_tuple.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/scripts/augment_dataset.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/scripts/exhaustive_process.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/scripts/preprocess_dataset.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/scripts/routine.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/data_science/utils.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/image.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/image.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/__init__.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/__init__.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/common.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/common.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/downloader.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/downloader.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/linux.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/linux.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/main.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/main.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/windows.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/installer/windows.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/io.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/io.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/print.py +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/print.pyi +0 -0
- {stouputils-1.9.2 → stouputils-1.9.4}/stouputils/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stouputils
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.4
|
|
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,
|
|
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,
|
|
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.
|
|
8
|
+
version = "1.9.4"
|
|
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"
|
|
@@ -8,13 +8,14 @@ import sys
|
|
|
8
8
|
import argcomplete
|
|
9
9
|
|
|
10
10
|
from .all_doctests import launch_tests
|
|
11
|
+
from .archive import archive_cli
|
|
11
12
|
from .backup import backup_cli
|
|
12
13
|
from .decorators import handle_error
|
|
13
14
|
from .print import CYAN, GREEN, RESET, show_version
|
|
14
15
|
|
|
15
16
|
# Argument Parser Setup for Auto-Completion
|
|
16
17
|
parser = argparse.ArgumentParser(prog="stouputils", add_help=False)
|
|
17
|
-
parser.add_argument("command", nargs="?", choices=["--version", "-v", "all_doctests", "backup"])
|
|
18
|
+
parser.add_argument("command", nargs="?", choices=["--version", "-v", "all_doctests", "archive", "backup"])
|
|
18
19
|
parser.add_argument("args", nargs="*")
|
|
19
20
|
argcomplete.autocomplete(parser)
|
|
20
21
|
|
|
@@ -33,6 +34,11 @@ def main() -> None:
|
|
|
33
34
|
sys.exit(1)
|
|
34
35
|
return
|
|
35
36
|
|
|
37
|
+
# Handle "archive" command
|
|
38
|
+
if second_arg == "archive":
|
|
39
|
+
sys.argv.pop(1) # Remove "archive" from argv so archive_cli gets clean arguments
|
|
40
|
+
return archive_cli()
|
|
41
|
+
|
|
36
42
|
# Handle "backup" command
|
|
37
43
|
if second_arg == "backup":
|
|
38
44
|
sys.argv.pop(1) # Remove "backup" from argv so backup_cli gets clean arguments
|
|
@@ -58,6 +64,7 @@ def main() -> None:
|
|
|
58
64
|
print(f"\n{CYAN}Available commands:{RESET}")
|
|
59
65
|
print(f" {GREEN}--version, -v{RESET} Show version information")
|
|
60
66
|
print(f" {GREEN}all_doctests{RESET} [dir] Run all doctests in the specified directory")
|
|
67
|
+
print(f" {GREEN}archive{RESET} --help Archive utilities (make, repair)")
|
|
61
68
|
print(f" {GREEN}backup{RESET} --help Backup utilities (delta, consolidate, limit)")
|
|
62
69
|
print(f"{CYAN}{separator}{RESET}")
|
|
63
70
|
return
|
|
@@ -17,7 +17,7 @@ from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
|
|
|
17
17
|
|
|
18
18
|
from .decorators import LogLevels, handle_error
|
|
19
19
|
from .io import clean_path, super_copy
|
|
20
|
-
from .print import debug, error, info
|
|
20
|
+
from .print import CYAN, GREEN, RESET, debug, error, info
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
# Function that repair a corrupted zip file (ignoring some of the errors)
|
|
@@ -267,6 +267,24 @@ def archive_cli() -> None:
|
|
|
267
267
|
import argparse
|
|
268
268
|
import sys
|
|
269
269
|
|
|
270
|
+
# Check for help or no command
|
|
271
|
+
if len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] in ("--help", "-h", "help")):
|
|
272
|
+
separator: str = "─" * 60
|
|
273
|
+
print(f"{CYAN}{separator}{RESET}")
|
|
274
|
+
print(f"{CYAN}stouputils {GREEN}archive {CYAN}utilities{RESET}")
|
|
275
|
+
print(f"{CYAN}{separator}{RESET}")
|
|
276
|
+
print(f"\n{CYAN}Usage:{RESET} stouputils archive <command> [options]")
|
|
277
|
+
print(f"\n{CYAN}Available commands:{RESET}")
|
|
278
|
+
print(f" {GREEN}make{RESET} <source> <destination> [--ignore PATTERNS] [--create-dir]")
|
|
279
|
+
print(" Create a zip archive from source directory")
|
|
280
|
+
print(f" {CYAN}--ignore{RESET} Glob patterns to ignore (comma-separated)")
|
|
281
|
+
print(f" {CYAN}--create-dir{RESET} Create destination directory if needed")
|
|
282
|
+
print(f"\n {GREEN}repair{RESET} <input_file> [output_file]")
|
|
283
|
+
print(" Repair a corrupted zip file")
|
|
284
|
+
print(" If output_file is omitted, adds '_repaired' suffix")
|
|
285
|
+
print(f"{CYAN}{separator}{RESET}")
|
|
286
|
+
return
|
|
287
|
+
|
|
270
288
|
parser = argparse.ArgumentParser(description="Archive utilities")
|
|
271
289
|
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
272
290
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .decorators import LogLevels as LogLevels, handle_error as handle_error
|
|
2
2
|
from .io import clean_path as clean_path, super_copy as super_copy
|
|
3
|
-
from .print import debug as debug, error as error, info as info
|
|
3
|
+
from .print import CYAN as CYAN, GREEN as GREEN, RESET as RESET, debug as debug, error as error, info as info
|
|
4
4
|
|
|
5
5
|
def repair_zip_file(file_path: str, destination: str) -> bool:
|
|
6
6
|
''' Try to repair a corrupted zip file by ignoring some of the errors
|
|
@@ -50,6 +50,23 @@ def backup_cli() -> None:
|
|
|
50
50
|
python -m stouputils.backup limit 5 /path/to/backups
|
|
51
51
|
"""
|
|
52
52
|
import argparse
|
|
53
|
+
import sys
|
|
54
|
+
|
|
55
|
+
# Check for help or no command
|
|
56
|
+
if len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] in ("--help", "-h", "help")):
|
|
57
|
+
separator: str = "─" * 60
|
|
58
|
+
print(f"{CYAN}{separator}{RESET}")
|
|
59
|
+
print(f"{CYAN}Backup Utilities{RESET}")
|
|
60
|
+
print(f"{CYAN}{separator}{RESET}")
|
|
61
|
+
print(f"\n{CYAN}Usage:{RESET} stouputils backup <command> [options]")
|
|
62
|
+
print(f"\n{CYAN}Available commands:{RESET}")
|
|
63
|
+
print(f" {GREEN}delta{RESET} Create a new delta backup")
|
|
64
|
+
print(f" {GREEN}consolidate{RESET} Consolidate existing backups into one")
|
|
65
|
+
print(f" {GREEN}limit{RESET} Limit the number of delta backups")
|
|
66
|
+
print(f"\n{CYAN}For detailed help on a specific command:{RESET}")
|
|
67
|
+
print(" stouputils backup <command> --help")
|
|
68
|
+
print(f"{CYAN}{separator}{RESET}")
|
|
69
|
+
return
|
|
53
70
|
|
|
54
71
|
# Setup command line argument parser
|
|
55
72
|
parser: argparse.ArgumentParser = argparse.ArgumentParser(
|
|
@@ -82,21 +99,6 @@ def backup_cli() -> None:
|
|
|
82
99
|
# Parse arguments and execute appropriate command
|
|
83
100
|
args: argparse.Namespace = parser.parse_args()
|
|
84
101
|
|
|
85
|
-
# Show custom help if no command is provided
|
|
86
|
-
if not args.command:
|
|
87
|
-
separator: str = "─" * 60
|
|
88
|
-
print(f"{CYAN}{separator}{RESET}")
|
|
89
|
-
print(f"{CYAN}Backup Utilities{RESET}")
|
|
90
|
-
print(f"{CYAN}{separator}{RESET}")
|
|
91
|
-
print(f"\n{CYAN}Usage:{RESET} stouputils backup <command> [options]")
|
|
92
|
-
print(f"\n{CYAN}Available commands:{RESET}")
|
|
93
|
-
print(f" {GREEN}delta{RESET} Create a new delta backup")
|
|
94
|
-
print(f" {GREEN}consolidate{RESET} Consolidate existing backups into one")
|
|
95
|
-
print(f" {GREEN}limit{RESET} Limit the number of delta backups")
|
|
96
|
-
print(f"\n{CYAN}For detailed help on a specific command:{RESET}")
|
|
97
|
-
print(" stouputils backup <command> --help")
|
|
98
|
-
print(f"{CYAN}{separator}{RESET}")
|
|
99
|
-
return
|
|
100
102
|
|
|
101
103
|
if args.command == "delta":
|
|
102
104
|
create_delta_backup(args.source, args.destination, args.exclude)
|
|
@@ -15,6 +15,7 @@ import atexit
|
|
|
15
15
|
import os
|
|
16
16
|
import shutil
|
|
17
17
|
import tempfile
|
|
18
|
+
from collections.abc import Iterable
|
|
18
19
|
from typing import TYPE_CHECKING, Any, Literal, TypeVar
|
|
19
20
|
|
|
20
21
|
# Lazy imports for typing
|
|
@@ -28,14 +29,14 @@ if TYPE_CHECKING:
|
|
|
28
29
|
T = TypeVar("T")
|
|
29
30
|
|
|
30
31
|
# Functions
|
|
31
|
-
def unique_list(list_to_clean:
|
|
32
|
+
def unique_list(list_to_clean: Iterable[T], method: Literal["id", "hash", "str"] = "str") -> list[T]:
|
|
32
33
|
""" Remove duplicates from the list while keeping the order using ids (default) or hash or str
|
|
33
34
|
|
|
34
35
|
Args:
|
|
35
|
-
list_to_clean (
|
|
36
|
+
list_to_clean (Iterable[T]): The list to clean
|
|
36
37
|
method (Literal["id", "hash", "str"]): The method to use to identify duplicates
|
|
37
38
|
Returns:
|
|
38
|
-
list[
|
|
39
|
+
list[T]: The cleaned list
|
|
39
40
|
|
|
40
41
|
Examples:
|
|
41
42
|
>>> unique_list([1, 2, 3, 2, 1], method="id")
|
|
@@ -54,8 +55,8 @@ def unique_list(list_to_clean: list[Any], method: Literal["id", "hash", "str"] =
|
|
|
54
55
|
[{1, 2, 3}, {2, 3, 4}]
|
|
55
56
|
"""
|
|
56
57
|
# Initialize the seen ids set and the result list
|
|
57
|
-
seen: set[
|
|
58
|
-
result: list[
|
|
58
|
+
seen: set[int | str] = set()
|
|
59
|
+
result: list[T] = []
|
|
59
60
|
|
|
60
61
|
# Iterate over each item in the list
|
|
61
62
|
for item in list_to_clean:
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import polars as pl
|
|
2
2
|
import zarr
|
|
3
|
+
from collections.abc import Iterable
|
|
3
4
|
from numpy.typing import NDArray as NDArray
|
|
4
5
|
from typing import Any, Literal, TypeVar
|
|
5
6
|
|
|
6
7
|
T = TypeVar('T')
|
|
7
8
|
|
|
8
|
-
def unique_list(list_to_clean:
|
|
9
|
+
def unique_list(list_to_clean: Iterable[T], method: Literal['id', 'hash', 'str'] = 'str') -> list[T]:
|
|
9
10
|
''' Remove duplicates from the list while keeping the order using ids (default) or hash or str
|
|
10
11
|
|
|
11
12
|
\tArgs:
|
|
12
|
-
\t\tlist_to_clean\t(
|
|
13
|
+
\t\tlist_to_clean\t(Iterable[T]):\t\t\t\t\tThe list to clean
|
|
13
14
|
\t\tmethod\t\t\t(Literal["id", "hash", "str"]):\tThe method to use to identify duplicates
|
|
14
15
|
\tReturns:
|
|
15
|
-
\t\tlist[
|
|
16
|
+
\t\tlist[T]: The cleaned list
|
|
16
17
|
|
|
17
18
|
\tExamples:
|
|
18
19
|
\t\t>>> unique_list([1, 2, 3, 2, 1], method="id")
|
|
@@ -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
|
-
-
|
|
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
|
-
#
|
|
171
|
-
def
|
|
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
|
-
|
|
175
|
+
seconds: float = 60.0,
|
|
176
|
+
message: str = ""
|
|
175
177
|
) -> Callable[..., Any]:
|
|
176
|
-
""" Decorator that
|
|
178
|
+
""" Decorator that raises a TimeoutError if the function runs longer than the specified timeout.
|
|
177
179
|
|
|
178
|
-
|
|
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
|
|
182
|
-
|
|
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]:
|
|
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
|
-
>>> @
|
|
187
|
-
... def
|
|
188
|
-
...
|
|
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
|
-
>>> @
|
|
191
|
-
... def
|
|
192
|
-
...
|
|
193
|
-
>>>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
#
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
227
|
-
|
|
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 @
|
|
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
|
|
68
|
-
''' Decorator that
|
|
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
|
-
\
|
|
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
|
|
74
|
-
\t\
|
|
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]:
|
|
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>>> @
|
|
79
|
-
\t\t... def
|
|
80
|
-
\t\t...
|
|
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>>> @
|
|
83
|
-
\t\t... def
|
|
84
|
-
\t\t...
|
|
85
|
-
\t\t>>>
|
|
86
|
-
\t\
|
|
87
|
-
\t\t
|
|
88
|
-
\t\
|
|
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
|
|