stouputils 1.5.4__tar.gz → 1.6.1__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 (137) hide show
  1. {stouputils-1.5.4 → stouputils-1.6.1}/.gitignore +3 -0
  2. {stouputils-1.5.4 → stouputils-1.6.1}/PKG-INFO +2 -1
  3. {stouputils-1.5.4 → stouputils-1.6.1}/pyproject.toml +4 -2
  4. stouputils-1.6.1/stouputils/__init__.pyi +13 -0
  5. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/__main__.py +1 -1
  6. stouputils-1.6.1/stouputils/all_doctests.pyi +46 -0
  7. stouputils-1.6.1/stouputils/applications/__init__.pyi +2 -0
  8. stouputils-1.6.1/stouputils/applications/automatic_docs.pyi +101 -0
  9. stouputils-1.6.1/stouputils/applications/upscaler/__init__.pyi +3 -0
  10. stouputils-1.6.1/stouputils/applications/upscaler/config.pyi +18 -0
  11. stouputils-1.6.1/stouputils/applications/upscaler/image.pyi +109 -0
  12. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/applications/upscaler/video.py +2 -1
  13. stouputils-1.6.1/stouputils/applications/upscaler/video.pyi +60 -0
  14. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/archive.py +1 -1
  15. stouputils-1.6.1/stouputils/archive.pyi +67 -0
  16. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/backup.py +1 -1
  17. stouputils-1.6.1/stouputils/backup.pyi +109 -0
  18. stouputils-1.6.1/stouputils/collections.pyi +73 -0
  19. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/continuous_delivery/__init__.py +2 -0
  20. stouputils-1.6.1/stouputils/continuous_delivery/__init__.pyi +5 -0
  21. stouputils-1.6.1/stouputils/continuous_delivery/cd_utils.pyi +80 -0
  22. stouputils-1.6.1/stouputils/continuous_delivery/github.pyi +162 -0
  23. stouputils-1.6.1/stouputils/continuous_delivery/pypi.pyi +43 -0
  24. stouputils-1.6.1/stouputils/continuous_delivery/pyproject.pyi +47 -0
  25. stouputils-1.6.1/stouputils/continuous_delivery/stubs.py +83 -0
  26. stouputils-1.6.1/stouputils/continuous_delivery/stubs.pyi +39 -0
  27. stouputils-1.6.1/stouputils/ctx.pyi +143 -0
  28. stouputils-1.6.1/stouputils/decorators.pyi +206 -0
  29. stouputils-1.6.1/stouputils/image.pyi +44 -0
  30. stouputils-1.6.1/stouputils/installer/__init__.pyi +5 -0
  31. stouputils-1.6.1/stouputils/installer/common.pyi +39 -0
  32. stouputils-1.6.1/stouputils/installer/downloader.pyi +24 -0
  33. stouputils-1.6.1/stouputils/installer/linux.pyi +39 -0
  34. stouputils-1.6.1/stouputils/installer/main.pyi +57 -0
  35. stouputils-1.6.1/stouputils/installer/windows.pyi +31 -0
  36. stouputils-1.6.1/stouputils/io.pyi +141 -0
  37. stouputils-1.6.1/stouputils/parallel.pyi +144 -0
  38. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/print.py +1 -1
  39. stouputils-1.6.1/stouputils/print.pyi +146 -0
  40. {stouputils-1.5.4 → stouputils-1.6.1}/LICENSE +0 -0
  41. {stouputils-1.5.4 → stouputils-1.6.1}/README.md +0 -0
  42. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/__init__.py +0 -0
  43. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/all_doctests.py +0 -0
  44. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/applications/__init__.py +0 -0
  45. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/applications/automatic_docs.py +0 -0
  46. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/applications/upscaler/__init__.py +0 -0
  47. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/applications/upscaler/config.py +0 -0
  48. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/applications/upscaler/image.py +0 -0
  49. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/collections.py +0 -0
  50. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/continuous_delivery/cd_utils.py +0 -0
  51. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/continuous_delivery/github.py +0 -0
  52. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/continuous_delivery/pypi.py +0 -0
  53. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/continuous_delivery/pyproject.py +0 -0
  54. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/ctx.py +0 -0
  55. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/config/get.py +0 -0
  56. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/config/set.py +0 -0
  57. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/__init__.py +0 -0
  58. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/auto_contrast.py +0 -0
  59. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/axis_flip.py +0 -0
  60. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/bias_field_correction.py +0 -0
  61. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/binary_threshold.py +0 -0
  62. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/blur.py +0 -0
  63. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/brightness.py +0 -0
  64. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/canny.py +0 -0
  65. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/clahe.py +0 -0
  66. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/common.py +0 -0
  67. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/contrast.py +0 -0
  68. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/curvature_flow_filter.py +0 -0
  69. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/denoise.py +0 -0
  70. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/histogram_equalization.py +0 -0
  71. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/invert.py +0 -0
  72. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/laplacian.py +0 -0
  73. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/median_blur.py +0 -0
  74. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/noise.py +0 -0
  75. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/normalize.py +0 -0
  76. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/random_erase.py +0 -0
  77. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/resize.py +0 -0
  78. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/rotation.py +0 -0
  79. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/salt_pepper.py +0 -0
  80. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/sharpening.py +0 -0
  81. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/shearing.py +0 -0
  82. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/threshold.py +0 -0
  83. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/translation.py +0 -0
  84. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image/zoom.py +0 -0
  85. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image_augmentation.py +0 -0
  86. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/image_preprocess.py +0 -0
  87. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/prosthesis_detection.py +0 -0
  88. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/data_processing/technique.py +0 -0
  89. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/dataset/__init__.py +0 -0
  90. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/dataset/dataset.py +0 -0
  91. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/dataset/dataset_loader.py +0 -0
  92. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/dataset/grouping_strategy.py +0 -0
  93. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/dataset/image_loader.py +0 -0
  94. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/dataset/xy_tuple.py +0 -0
  95. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/metric_dictionnary.py +0 -0
  96. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/metric_utils.py +0 -0
  97. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/mlflow_utils.py +0 -0
  98. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/abstract_model.py +0 -0
  99. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/all.py +0 -0
  100. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/base_keras.py +0 -0
  101. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras/all.py +0 -0
  102. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras/convnext.py +0 -0
  103. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras/densenet.py +0 -0
  104. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras/efficientnet.py +0 -0
  105. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras/mobilenet.py +0 -0
  106. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras/resnet.py +0 -0
  107. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras/squeezenet.py +0 -0
  108. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras/vgg.py +0 -0
  109. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras/xception.py +0 -0
  110. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras_utils/callbacks/__init__.py +0 -0
  111. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +0 -0
  112. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +0 -0
  113. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +0 -0
  114. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +0 -0
  115. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +0 -0
  116. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras_utils/losses/__init__.py +0 -0
  117. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +0 -0
  118. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/keras_utils/visualizations.py +0 -0
  119. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/model_interface.py +0 -0
  120. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/models/sandbox.py +0 -0
  121. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/range_tuple.py +0 -0
  122. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/scripts/augment_dataset.py +0 -0
  123. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/scripts/exhaustive_process.py +0 -0
  124. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/scripts/preprocess_dataset.py +0 -0
  125. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/scripts/routine.py +0 -0
  126. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/data_science/utils.py +0 -0
  127. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/decorators.py +0 -0
  128. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/image.py +0 -0
  129. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/installer/__init__.py +0 -0
  130. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/installer/common.py +0 -0
  131. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/installer/downloader.py +0 -0
  132. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/installer/linux.py +0 -0
  133. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/installer/main.py +0 -0
  134. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/installer/windows.py +0 -0
  135. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/io.py +0 -0
  136. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/parallel.py +0 -0
  137. {stouputils-1.5.4 → stouputils-1.6.1}/stouputils/py.typed +0 -0
@@ -9,6 +9,9 @@ output.log
9
9
  logfile.txt
10
10
  .vscode
11
11
 
12
+ # Type stubs
13
+ *.pyi
14
+
12
15
  # Documentation
13
16
  docs/source/_static/
14
17
  docs/source/_templates/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stouputils
3
- Version: 1.5.4
3
+ Version: 1.6.1
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
@@ -10,6 +10,7 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: >=3.10
13
+ Requires-Dist: mypy>=1.18.2
13
14
  Requires-Dist: numpy
14
15
  Requires-Dist: opencv-python>=4.0.0
15
16
  Requires-Dist: orjson>=3.0.0
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
5
5
 
6
6
  [project]
7
7
  name = "stouputils"
8
- version = "1.5.4"
8
+ version = "1.6.1"
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"
@@ -14,7 +14,7 @@ classifiers = [
14
14
  "License :: OSI Approved :: MIT License",
15
15
  "Operating System :: OS Independent",
16
16
  ]
17
- dependencies = [ "tqdm>=4.0.0", "requests>=2.20.0", "pyyaml>=6.0.0", "toml>=0.10.0", "pillow>=10.0.0", "numpy", "opencv-python>=4.0.0", "orjson>=3.0.0", "pyfastcopy>=1.0.0", "python-box[all]>=7.0.0", "zarr>=2.18.3",]
17
+ dependencies = [ "tqdm>=4.0.0", "requests>=2.20.0", "pyyaml>=6.0.0", "toml>=0.10.0", "pillow>=10.0.0", "numpy", "opencv-python>=4.0.0", "orjson>=3.0.0", "pyfastcopy>=1.0.0", "python-box[all]>=7.0.0", "zarr>=2.18.3", "mypy>=1.18.2",]
18
18
  [[project.authors]]
19
19
  name = "Stoupy51"
20
20
  email = "stoupy51@gmail.com"
@@ -57,6 +57,7 @@ exclude = [
57
57
  ".cursor",
58
58
  "build",
59
59
  "dist",
60
+ "*.pyi",
60
61
  ]
61
62
  line-length = 200
62
63
 
@@ -84,4 +85,5 @@ ignore = [
84
85
 
85
86
  [tool.hatch.build]
86
87
  include = ["stouputils"]
88
+ ignore-vcs = true
87
89
 
@@ -0,0 +1,13 @@
1
+ from .all_doctests import *
2
+ from .archive import *
3
+ from .backup import *
4
+ from .collections import *
5
+ from .continuous_delivery import *
6
+ from .ctx import *
7
+ from .decorators import *
8
+ from .image import *
9
+ from .io import *
10
+ from .parallel import *
11
+ from .print import *
12
+
13
+ __version__: str
@@ -10,7 +10,7 @@ from .print import CYAN, GREEN, RESET, show_version
10
10
 
11
11
 
12
12
  @handle_error(message="Error while running 'stouputils'")
13
- def main():
13
+ def main() -> None:
14
14
  second_arg: str = sys.argv[1].lower() if len(sys.argv) >= 2 else ""
15
15
  if not second_arg:
16
16
  # Get version
@@ -0,0 +1,46 @@
1
+ from . import decorators as decorators
2
+ from .decorators import measure_time as measure_time
3
+ from .io import clean_path as clean_path, relative_path as relative_path
4
+ from .print import error as error, info as info, progress as progress, warning as warning
5
+ from doctest import TestResults as TestResults
6
+ from types import ModuleType
7
+
8
+ def launch_tests(root_dir: str, strict: bool = True) -> int:
9
+ ''' Main function to launch tests for all modules in the given directory.
10
+
11
+ \tArgs:
12
+ \t\troot_dir\t\t\t\t(str):\t\t\tRoot directory to search for modules
13
+ \t\tstrict\t\t\t\t\t(bool):\t\t\tModify the force_raise_exception variable to True in the decorators module
14
+
15
+ \tReturns:
16
+ \t\tint: The number of failed tests
17
+
18
+ \tExamples:
19
+ \t\t>>> launch_tests("unknown_dir")
20
+ \t\tTraceback (most recent call last):
21
+ \t\t\t...
22
+ \t\tValueError: No modules found in \'unknown_dir\'
23
+
24
+ \t.. code-block:: python
25
+
26
+ \t\t> if launch_tests("/path/to/source") > 0:
27
+ \t\t\tsys.exit(1)
28
+ \t\t[PROGRESS HH:MM:SS] Importing module \'module1\'\ttook 0.001s
29
+ \t\t[PROGRESS HH:MM:SS] Importing module \'module2\'\ttook 0.002s
30
+ \t\t[PROGRESS HH:MM:SS] Importing module \'module3\'\ttook 0.003s
31
+ \t\t[PROGRESS HH:MM:SS] Importing module \'module4\'\ttook 0.004s
32
+ \t\t[INFO HH:MM:SS] Testing 4 modules...
33
+ \t\t[PROGRESS HH:MM:SS] Testing module \'module1\'\ttook 0.005s
34
+ \t\t[PROGRESS HH:MM:SS] Testing module \'module2\'\ttook 0.006s
35
+ \t\t[PROGRESS HH:MM:SS] Testing module \'module3\'\ttook 0.007s
36
+ \t\t[PROGRESS HH:MM:SS] Testing module \'module4\'\ttook 0.008s
37
+ \t'''
38
+ def test_module_with_progress(module: ModuleType, separator: str) -> TestResults:
39
+ """ Test a module with testmod and measure the time taken with progress printing.
40
+
41
+ \tArgs:
42
+ \t\tmodule\t\t(ModuleType):\tModule to test
43
+ \t\tseparator\t(str):\t\t\tSeparator string for alignment in output
44
+ \tReturns:
45
+ \t\tTestResults: The results of the tests
46
+ \t"""
@@ -0,0 +1,2 @@
1
+ from .automatic_docs import *
2
+ from .upscaler import *
@@ -0,0 +1,101 @@
1
+ from ..continuous_delivery import version_to_float as version_to_float
2
+ from ..decorators import LogLevels as LogLevels, handle_error as handle_error, simple_cache as simple_cache
3
+ from ..io import clean_path as clean_path, super_json_dump as super_json_dump, super_open as super_open
4
+ from ..print import info as info
5
+ from collections.abc import Callable as Callable
6
+
7
+ REQUIREMENTS: list[str]
8
+
9
+ def check_dependencies(html_theme: str) -> None:
10
+ ''' Check for each requirement if it is installed.
11
+
12
+ \tArgs:
13
+ \t\thtml_theme (str): HTML theme to use for the documentation, to check if it is installed (e.g. "breeze", "pydata_sphinx_theme", "furo", etc.)
14
+ \t'''
15
+ def get_sphinx_conf_content(project: str, project_dir: str, author: str, current_version: str, copyright: str, html_logo: str, html_favicon: str, html_theme: str = 'breeze', github_user: str = '', github_repo: str = '', version_list: list[str] | None = None, skip_undocumented: bool = True) -> str:
16
+ """ Get the content of the Sphinx configuration file.
17
+
18
+ \tArgs:
19
+ \t\tproject (str): Name of the project
20
+ \t\tproject_dir (str): Path to the project directory
21
+ \t\tauthor (str): Author of the project
22
+ \t\tcurrent_version (str): Current version
23
+ \t\tcopyright (str): Copyright information
24
+ \t\thtml_logo (str): URL to the logo
25
+ \t\thtml_favicon (str): URL to the favicon
26
+ \t\tgithub_user (str): GitHub username
27
+ \t\tgithub_repo (str): GitHub repository name
28
+ \t\tversion_list (list[str] | None): List of versions. Defaults to None
29
+ \t\tskip_undocumented (bool): Whether to skip undocumented members. Defaults to True
30
+
31
+ \tReturns:
32
+ \t\tstr: Content of the Sphinx configuration file
33
+ \t"""
34
+ def get_versions_from_github(github_user: str, github_repo: str) -> list[str]:
35
+ """ Get list of versions from GitHub gh-pages branch.
36
+
37
+ \tArgs:
38
+ \t\tgithub_user (str): GitHub username
39
+ \t\tgithub_repo (str): GitHub repository name
40
+
41
+ \tReturns:
42
+ \t\tlist[str]: List of versions, with 'latest' as first element
43
+ \t"""
44
+ def markdown_to_rst(markdown_content: str) -> str:
45
+ """ Convert markdown content to RST format.
46
+
47
+ \tArgs:
48
+ \t\tmarkdown_content (str): Markdown content
49
+
50
+ \tReturns:
51
+ \t\tstr: RST content
52
+ \t"""
53
+ def generate_index_rst(readme_path: str, index_path: str, project: str, github_user: str, github_repo: str, get_versions_function: Callable[[str, str], list[str]] = ...) -> None:
54
+ """ Generate index.rst from README.md content.
55
+
56
+ \tArgs:
57
+ \t\treadme_path (str): Path to the README.md file
58
+ \t\tindex_path (str): Path where index.rst should be created
59
+ \t\tproject (str): Name of the project
60
+ \t\tgithub_user (str): GitHub username
61
+ \t\tgithub_repo (str): GitHub repository name
62
+ \t\tget_versions_function (Callable[[str, str], list[str]]): Function to get versions from GitHub
63
+ \t"""
64
+ def generate_documentation(source_dir: str, modules_dir: str, project_dir: str, build_dir: str) -> None:
65
+ """ Generate documentation using Sphinx.
66
+
67
+ \tArgs:
68
+ \t\tsource_dir (str): Source directory
69
+ \t\tmodules_dir (str): Modules directory
70
+ \t\tproject_dir (str): Project directory
71
+ \t\tbuild_dir (str): Build directory
72
+ \t"""
73
+ def generate_redirect_html(filepath: str) -> None:
74
+ """ Generate HTML content for redirect page.
75
+
76
+ \tArgs:
77
+ \t\tfilepath (str): Path to the file where the HTML content should be written
78
+ \t"""
79
+ def update_documentation(root_path: str, project: str, project_dir: str = '', author: str = 'Author', copyright: str = '2025, Author', html_logo: str = '', html_favicon: str = '', html_theme: str = 'breeze', github_user: str = '', github_repo: str = '', version: str | None = None, skip_undocumented: bool = True, get_versions_function: Callable[[str, str], list[str]] = ..., generate_index_function: Callable[..., None] = ..., generate_docs_function: Callable[..., None] = ..., generate_redirect_function: Callable[[str], None] = ..., get_conf_content_function: Callable[..., str] = ...) -> None:
80
+ ''' Update the Sphinx documentation.
81
+
82
+ \tArgs:
83
+ \t\troot_path (str): Root path of the project
84
+ \t\tproject (str): Name of the project
85
+ \t\tproject_dir (str): Path to the project directory (to be used with generate_docs_function)
86
+ \t\tauthor (str): Author of the project
87
+ \t\tcopyright (str): Copyright information
88
+ \t\thtml_logo (str): URL to the logo
89
+ \t\thtml_favicon (str): URL to the favicon
90
+ \t\thtml_theme (str): Theme to use for the documentation. Defaults to "breeze"
91
+ \t\tgithub_user (str): GitHub username
92
+ \t\tgithub_repo (str): GitHub repository name
93
+ \t\tversion (str | None): Version to build documentation for (e.g. "1.0.0", defaults to "latest")
94
+ \t\tskip_undocumented (bool): Whether to skip undocumented members. Defaults to True
95
+
96
+ \t\tget_versions_function (Callable[[str, str], list[str]]): Function to get versions from GitHub
97
+ \t\tgenerate_index_function (Callable[..., None]): Function to generate index.rst
98
+ \t\tgenerate_docs_function (Callable[..., None]): Function to generate documentation
99
+ \t\tgenerate_redirect_function (Callable[[str], None]): Function to create redirect file
100
+ \t\tget_conf_content_function (Callable[..., str]): Function to get Sphinx conf.py content
101
+ \t'''
@@ -0,0 +1,3 @@
1
+ from .config import *
2
+ from .image import *
3
+ from .video import *
@@ -0,0 +1,18 @@
1
+ WAIFU2X_NCNN_VULKAN_RELEASES: dict[str, str]
2
+ FFMPEG_RELEASES: dict[str, str]
3
+ YOUTUBE_BITRATE_RECOMMENDATIONS: dict[str, dict[str, dict[int, int]]]
4
+
5
+ class Config:
6
+ """ Configuration class for the upscaler. """
7
+ JPG_QUALITY: int
8
+ VIDEO_FINAL_BITRATE: int
9
+ FFMPEG_EXECUTABLE: str
10
+ FFMPEG_ARGS: tuple[str, ...]
11
+ FFPROBE_EXECUTABLE: str
12
+ FFMPEG_CHECK_HELP_TEXT: str
13
+ UPSCALER_EXECUTABLE: str
14
+ UPSCALER_ARGS: tuple[str, ...]
15
+ UPSCALER_EXECUTABLE_HELP_TEXT: str
16
+ SLIGHTLY_FASTER_MODE: bool
17
+ upscaler_executable_checked: bool
18
+ ffmpeg_executable_checked: bool
@@ -0,0 +1,109 @@
1
+ from ...installer import check_executable as check_executable
2
+ from ...io import clean_path as clean_path
3
+ from ...parallel import multithreading as multithreading
4
+ from ...print import colored_for_loop as colored_for_loop, debug as debug, info as info
5
+ from .config import Config as Config, WAIFU2X_NCNN_VULKAN_RELEASES as WAIFU2X_NCNN_VULKAN_RELEASES
6
+ from tempfile import TemporaryDirectory
7
+
8
+ def convert_frame(frame_path: str, delete_png: bool = True) -> None:
9
+ ''' Convert a PNG frame to JPG format to take less space.
10
+
11
+ \tArgs:
12
+ \t\tframe_path (str): Path to the PNG frame to convert.
13
+ \t\tdelete_png (bool): Whether to delete the original PNG file after conversion.
14
+
15
+ \tReturns:
16
+ \t\tNone: This function doesn\'t return anything.
17
+
18
+ \tExample:
19
+ \t\t.. code-block:: python
20
+
21
+ \t\t\t> convert_frame("input.png", delete_png=True)
22
+ \t\t\t> # input.png will be converted to input.jpg and the original file will be deleted
23
+
24
+ \t\t\t> convert_frame("input.png", delete_png=False)
25
+ \t\t\t> # input.png will be converted to input.jpg and the original file will be kept
26
+ \t'''
27
+ def get_all_files(folder: str, suffix: str | tuple[str, ...] = '') -> list[str]:
28
+ ''' Get all files paths in a folder, with a specific suffix if provided.
29
+
30
+ \tArgs:
31
+ \t\tfolder (str): Path to the folder containing the files.
32
+ \t\tsuffix (str | tuple[str, ...]): Suffix of the files to get (e.g. ".png", ".jpg", etc.).
33
+
34
+ \tReturns:
35
+ \t\tlist[str]: List of all files paths in the folder.
36
+
37
+ \tExample:
38
+ \t\t>>> files: list[str] = get_all_files("some_folder", ".png")
39
+ \t\t>>> len(files)
40
+ \t\t0
41
+ \t'''
42
+ def create_temp_dir_for_not_upscaled(input_path: str, output_path: str) -> TemporaryDirectory[str] | None:
43
+ """ Creates a temporary directory containing only images that haven't been upscaled yet.
44
+
45
+ Args:
46
+ input_path (str): Path to the folder containing input images.
47
+ output_path (str): Path to the folder where upscaled images are saved.
48
+
49
+ Returns:
50
+ TemporaryDirectory[str] | None: A temporary directory object if there are images to process,
51
+ None if all images are already upscaled.
52
+ """
53
+ def check_upscaler_executable() -> None: ...
54
+ def upscale(input_path: str, output_path: str, upscale_ratio: int) -> None:
55
+ ''' Upscale an input image (or a directory of images) with the upscaler executable.
56
+
57
+ \tArgs:
58
+ \t\tinput_path (str): Path to the image to upscale (or a directory).
59
+ \t\toutput_path (str): Path to the output image (or a directory).
60
+ \t\tupscale_ratio (int): Upscaling ratio.
61
+
62
+ \tExample:
63
+ \t\t.. code-block:: python
64
+
65
+ \t\t\t> upscale("folder", "folder", 2)
66
+ \t\t\tTraceback (most recent call last):
67
+ \t\t\t\t...
68
+ \t\t\tAssertionError: Input and output paths cannot be the same, got \'folder\'
69
+
70
+ \t\t\t> upscale("stouputils", "stouputils/output.jpg", 2)
71
+ \t\t\tTraceback (most recent call last):
72
+ \t\t\t\t...
73
+ \t\t\tAssertionError: If input is a directory, output must be a directory too, got \'stouputils/output.jpg\'
74
+
75
+
76
+ \t\t\t> upscale("input.jpg", "output.jpg", 2)
77
+ \t\t\t> # The input.jpg will be upscaled to output.jpg with a ratio of 2
78
+
79
+ \t\t\t> upscale("input_folder", "output_folder", 2)
80
+ \t\t\t> # The input_folder will be upscaled to output_folder with a ratio of 2
81
+ \t'''
82
+ def upscale_images(images: list[str], output_folder: str, upscale_ratio: int, desc: str = 'Upscaling images') -> None:
83
+ """ Upscale multiple images from a list.
84
+
85
+ \tArgs:
86
+ \t\timages (list[str]): List of paths to the images to upscale.
87
+ \t\toutput_folder (str): Path to the output folder where the upscaled images will be saved.
88
+ \t\tupscale_ratio (int): Upscaling ratio.
89
+ \t\tdesc (str): Description of the function execution displayed in the progress bar.
90
+ \t\t\tNo progress bar will be displayed if desc is empty.
91
+
92
+ \tReturns:
93
+ \t\tNone: This function doesn't return anything.
94
+ \t"""
95
+ def upscale_folder(input_folder: str, output_folder: str, upscale_ratio: int, slightly_faster_mode: bool = True, desc: str = 'Upscaling folder') -> None:
96
+ """ Upscale all images in a folder.
97
+
98
+ \tArgs:
99
+ \t\tinput_folder (str): Path to the input folder containing the images to upscale.
100
+ \t\toutput_folder (str): Path to the output folder where the upscaled images will be saved.
101
+ \t\tupscale_ratio (int): Upscaling ratio.
102
+ \t\tslightly_faster_mode (bool): Whether to use the slightly faster mode (no progress bar),
103
+ \t\t\tone call to the upscaler executable.
104
+ \t\tdesc (str): Description of the function execution displayed in the progress bar.
105
+ \t\t\tNo progress bar will be displayed if desc is empty.
106
+
107
+ \tReturns:
108
+ \t\tNone: This function doesn't return anything.
109
+ \t"""
@@ -191,7 +191,8 @@ def upscale_video(video_file: str, input_folder: str, progress_folder: str, outp
191
191
  else:
192
192
  error(
193
193
  "No upscaling ratio provided with --upscale flag. "
194
- "Please provide a ratio after the flag. (1/2/4/8/16/32)"
194
+ "Please provide a ratio after the flag. (1/2/4/8/16/32)",
195
+ exit=True
195
196
  )
196
197
  else:
197
198
  info("No upscaling ratio provided, please enter one (1/2/4/8/16/32, default=2):")
@@ -0,0 +1,60 @@
1
+ from ...installer import check_executable as check_executable
2
+ from ...io import clean_path as clean_path
3
+ from ...parallel import multithreading as multithreading
4
+ from ...print import colored_for_loop as colored_for_loop, debug as debug, error as error, info as info, warning as warning
5
+ from .config import Config as Config, FFMPEG_RELEASES as FFMPEG_RELEASES, YOUTUBE_BITRATE_RECOMMENDATIONS as YOUTUBE_BITRATE_RECOMMENDATIONS
6
+ from .image import convert_frame as convert_frame, get_all_files as get_all_files, upscale_folder as upscale_folder
7
+ from typing import Literal
8
+
9
+ def get_recommended_bitrate(resolution: tuple[int, int], frame_rate: int = 60, upload_type: Literal['SDR', 'HDR'] = 'SDR') -> int:
10
+ ''' Get the recommended bitrate (in kbps) for the output video based on the video resolution.
11
+
12
+ \tArgs:
13
+ \t\tresolution (tuple[int, int]): Video resolution (width, height).
14
+ \t\tframe_rate (int): Frame rate of the video, default is 60.
15
+ \t\tupload_type (Literal["SDR","HDR"]): Upload type from which the recommendation is made, default is "SDR".
16
+
17
+ \tReturns:
18
+ \t\tint: The recommended bitrate for the output video (in kbps)
19
+
20
+ \tSource: https://support.google.com/youtube/answer/1722171?hl=en#zippy=%2Cbitrate
21
+
22
+ \tExamples:
23
+ \t\t>>> # Valid examples
24
+ \t\t>>> get_recommended_bitrate((3840, 2160), 60, "SDR")
25
+ \t\t68000
26
+ \t\t>>> get_recommended_bitrate((1920, 1080), 60, "HDR")
27
+ \t\t15000
28
+ \t\t>>> get_recommended_bitrate((1920, 1080), 60, "SDR")
29
+ \t\t12000
30
+ \t\t>>> get_recommended_bitrate((1920, 1080), 30, "SDR")
31
+ \t\t8000
32
+
33
+ \t\t>>> # Invalid examples
34
+ \t\t>>> get_recommended_bitrate((1920, 1080), 60, "Ratio")
35
+ \t\tTraceback (most recent call last):
36
+ \t\t\t...
37
+ \t\tAssertionError: Invalid upload type: \'Ratio\'
38
+ \t\t>>> get_recommended_bitrate("1920x1080", 60, "SDR")
39
+ \t\tTraceback (most recent call last):
40
+ \t\t\t...
41
+ \t\tAssertionError: Invalid resolution: 1920x1080, must be a tuple of two integers
42
+ \t\t>>> get_recommended_bitrate((1920, 1080), -10, "SDR")
43
+ \t\tTraceback (most recent call last):
44
+ \t\t\t...
45
+ \t\tAssertionError: Invalid frame rate: -10, must be a positive integer
46
+ \t'''
47
+ def check_ffmpeg_executable() -> None: ...
48
+ def upscale_video(video_file: str, input_folder: str, progress_folder: str, output_folder: str) -> None:
49
+ """ Handles a video file. """
50
+ def video_upscaler_cli(input_folder: str, progress_folder: str, output_folder: str) -> None:
51
+ """ Upscales videos from an input folder and saves them to an output folder.
52
+
53
+ \tUses intermediate folders for extracted and upscaled frames within the progress folder.
54
+ \t**Handles resuming partially processed videos.**
55
+
56
+ \tArgs:
57
+ \t\tinput_folder (str): Path to the folder containing input videos.
58
+ \t\tprogress_folder (str): Path to the folder for storing intermediate files (frames).
59
+ \t\toutput_folder (str): Path to the folder where upscaled videos will be saved.
60
+ \t"""
@@ -248,7 +248,7 @@ def make_archive(
248
248
 
249
249
 
250
250
  # Main entry point for command line usage
251
- def archive_cli():
251
+ def archive_cli() -> None:
252
252
  """ Main entry point for command line usage.
253
253
 
254
254
  Examples:
@@ -0,0 +1,67 @@
1
+ from .decorators import LogLevels as LogLevels, handle_error as handle_error
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
4
+
5
+ def repair_zip_file(file_path: str, destination: str) -> bool:
6
+ ''' Try to repair a corrupted zip file by ignoring some of the errors
7
+
8
+ \tThis function manually parses the ZIP file structure to extract files
9
+ \teven when the ZIP file is corrupted. It reads the central directory
10
+ \tentries and attempts to decompress each file individually.
11
+
12
+ \tArgs:
13
+ \t\tfile_path\t\t(str):\tPath of the zip file to repair
14
+ \t\tdestination\t\t(str):\tDestination of the new file
15
+ \tReturns:
16
+ \t\tbool: Always returns True unless any strong error
17
+
18
+ \tExamples:
19
+
20
+ \t.. code-block:: python
21
+
22
+ \t\t> repair_zip_file("/path/to/source.zip", "/path/to/destination.zip")
23
+ \t'''
24
+ def make_archive(source: str, destinations: list[str] | str | None = None, override_time: None | tuple[int, int, int, int, int, int] = None, create_dir: bool = False, ignore_patterns: str | None = None) -> bool:
25
+ ''' Create a zip archive from a source directory with consistent file timestamps.
26
+ \t(Meaning deterministic zip file each time)
27
+
28
+ \tCreates a zip archive from the source directory and copies it to one or more destinations.
29
+ \tThe archive will have consistent file timestamps across runs if override_time is specified.
30
+ \tUses maximum compression level (9) with ZIP_DEFLATED algorithm.
31
+
32
+ \tArgs:
33
+ \t\tsource\t\t\t\t(str):\t\t\t\t\t\tThe source folder to archive
34
+ \t\tdestinations\t\t(list[str]|str):\t\t\tThe destination folder(s) or file(s) to copy the archive to
35
+ \t\toverride_time\t\t(None | tuple[int, ...]):\tThe constant time to use for the archive
36
+ \t\t\t(e.g. (2024, 1, 1, 0, 0, 0) for 2024-01-01 00:00:00)
37
+ \t\tcreate_dir\t\t\t(bool):\t\t\t\t\t\tWhether to create the destination directory if it doesn\'t exist
38
+ \t\tignore_patterns\t\t(str | None):\t\t\t\tGlob pattern(s) to ignore files. Can be a single pattern or comma-separated patterns (e.g. "*.pyc" or "*.pyc,__pycache__,*.log")
39
+ \tReturns:
40
+ \t\tbool: Always returns True unless any strong error
41
+ \tExamples:
42
+
43
+ \t.. code-block:: python
44
+
45
+ \t\t> make_archive("/path/to/source", "/path/to/destination.zip")
46
+ \t\t> make_archive("/path/to/source", ["/path/to/destination.zip", "/path/to/destination2.zip"])
47
+ \t\t> make_archive("src", "hello_from_year_2085.zip", override_time=(2085,1,1,0,0,0))
48
+ \t\t> make_archive("src", "output.zip", ignore_patterns="*.pyc")
49
+ \t\t> make_archive("src", "output.zip", ignore_patterns="__pycache__")
50
+ \t\t> make_archive("src", "output.zip", ignore_patterns="*.pyc,__pycache__,*.log")
51
+ \t'''
52
+ def archive_cli() -> None:
53
+ ''' Main entry point for command line usage.
54
+
55
+ \tExamples:
56
+
57
+ \t.. code-block:: bash
58
+
59
+ \t\t# Repair a corrupted zip file
60
+ \t\tpython -m stouputils.archive repair /path/to/corrupted.zip /path/to/repaired.zip
61
+
62
+ \t\t# Create a zip archive
63
+ \t\tpython -m stouputils.archive make /path/to/source /path/to/destination.zip
64
+
65
+ \t\t# Create a zip archive with ignore patterns
66
+ \t\tpython -m stouputils.archive make /path/to/source /path/to/destination.zip --ignore "*.pyc,__pycache__"
67
+ \t'''
@@ -33,7 +33,7 @@ LARGE_CHUNK_SIZE = 8388608 # 8MB chunks for large file operations
33
33
 
34
34
 
35
35
  # Main entry point for command line usage
36
- def backup_cli():
36
+ def backup_cli() -> None:
37
37
  """ Main entry point for command line usage.
38
38
 
39
39
  Examples:
@@ -0,0 +1,109 @@
1
+ import zipfile
2
+ from .decorators import handle_error as handle_error, measure_time as measure_time
3
+ from .io import clean_path as clean_path
4
+ from .print import CYAN as CYAN, GREEN as GREEN, RESET as RESET, colored_for_loop as colored_for_loop, info as info, warning as warning
5
+
6
+ CHUNK_SIZE: int
7
+ LARGE_CHUNK_SIZE: int
8
+
9
+ def backup_cli() -> None:
10
+ ''' Main entry point for command line usage.
11
+
12
+ \tExamples:
13
+
14
+ \t.. code-block:: bash
15
+
16
+ \t\t# Create a delta backup, excluding libraries and cache folders
17
+ \t\tpython -m stouputils.backup delta /path/to/source /path/to/backups -x "libraries/*" "cache/*"
18
+
19
+ \t\t# Consolidate backups into a single file
20
+ \t\tpython -m stouputils.backup consolidate /path/to/backups/latest.zip /path/to/consolidated.zip
21
+
22
+ \t\t# Limit the number of delta backups to 5
23
+ \t\tpython -m stouputils.backup limit 5 /path/to/backups
24
+ \t'''
25
+ def create_delta_backup(source_path: str, destination_folder: str, exclude_patterns: list[str] | None = None) -> None:
26
+ ''' Creates a ZIP delta backup, saving only modified or new files while tracking deleted files.
27
+
28
+ \tArgs:
29
+ \t\tsource_path (str): Path to the source file or directory to back up
30
+ \t\tdestination_folder (str): Path to the folder where the backup will be saved
31
+ \t\texclude_patterns (list[str] | None): List of glob patterns to exclude from backup
32
+ \tExamples:
33
+
34
+ \t.. code-block:: python
35
+
36
+ \t\t> create_delta_backup("/path/to/source", "/path/to/backups", exclude_patterns=["libraries/*", "cache/*"])
37
+ \t\t[INFO HH:MM:SS] Creating ZIP backup
38
+ \t\t[INFO HH:MM:SS] Backup created: \'/path/to/backups/backup_2025_02_18-10_00_00.zip\'
39
+ \t'''
40
+ def consolidate_backups(zip_path: str, destination_zip: str) -> None:
41
+ ''' Consolidates the files from the given backup and all previous ones into a new ZIP file,
42
+ \tensuring that the most recent version of each file is kept and deleted files are not restored.
43
+
44
+ \tArgs:
45
+ \t\tzip_path (str): Path to the latest backup ZIP file (If endswith "/latest.zip" or "/", the latest backup will be used)
46
+ \t\tdestination_zip (str): Path to the destination ZIP file where the consolidated backup will be saved
47
+ \tExamples:
48
+
49
+ \t.. code-block:: python
50
+
51
+ \t\t> consolidate_backups("/path/to/backups/latest.zip", "/path/to/consolidated.zip")
52
+ \t\t[INFO HH:MM:SS] Consolidating backups
53
+ \t\t[INFO HH:MM:SS] Consolidated backup created: \'/path/to/consolidated.zip\'
54
+ \t'''
55
+ def limit_backups(max_backups: int, backup_folder: str, keep_oldest: bool = True) -> None:
56
+ ''' Limits the number of delta backups by consolidating the oldest ones.
57
+
58
+ \tIf the number of backups exceeds max_backups, the oldest backups are consolidated
59
+ \tinto a single backup file, then deleted, until the count is within the limit.
60
+
61
+ \tArgs:
62
+ \t\tmax_backups (int): Maximum number of delta backups to keep
63
+ \t\tbackup_folder (str): Path to the folder containing backups
64
+ \t\tkeep_oldest (bool): If True, never delete the oldest backup (default: True)
65
+ \tExamples:
66
+
67
+ \t.. code-block:: python
68
+
69
+ \t\t> limit_backups(5, "/path/to/backups")
70
+ \t\t[INFO HH:MM:SS] Limiting backups
71
+ \t\t[INFO HH:MM:SS] Consolidated 3 oldest backups into \'/path/to/backups/consolidated_YYYY_MM_DD-HH_MM_SS.zip\'
72
+ \t\t[INFO HH:MM:SS] Deleted 3 old backups
73
+ \t'''
74
+ def get_file_hash(file_path: str) -> str | None:
75
+ """ Computes the SHA-256 hash of a file.
76
+
77
+ \tArgs:
78
+ \t\tfile_path (str): Path to the file
79
+ \tReturns:
80
+ \t\tstr | None: SHA-256 hash as a hexadecimal string or None if an error occurs
81
+ \t"""
82
+ def extract_hash_from_zipinfo(zip_info: zipfile.ZipInfo) -> str | None:
83
+ """ Extracts the stored hash from a ZipInfo object's comment.
84
+
85
+ \tArgs:
86
+ \t\tzip_info (zipfile.ZipInfo): The ZipInfo object representing a file in the ZIP
87
+ \tReturns:
88
+ \t\tstr | None: The stored hash if available, otherwise None
89
+ \t"""
90
+ def get_all_previous_backups(backup_folder: str, all_before: str | None = None) -> dict[str, dict[str, str]]:
91
+ ''' Retrieves all previous backups in a folder and maps each backup to a dictionary of file paths and their hashes.
92
+
93
+ \tArgs:
94
+ \t\tbackup_folder (str): The folder containing previous backup zip files
95
+ \t\tall_before (str | None): Path to the latest backup ZIP file
96
+ \t\t\t(If endswith "/latest.zip" or "/", the latest backup will be used)
97
+ \tReturns:
98
+ \t\tdict[str, dict[str, str]]: Dictionary mapping backup file paths to dictionaries of {file_path: file_hash}
99
+ \t'''
100
+ def is_file_in_any_previous_backup(file_path: str, file_hash: str, previous_backups: dict[str, dict[str, str]]) -> bool:
101
+ """ Checks if a file with the same hash exists in any previous backup.
102
+
103
+ \tArgs:
104
+ \t\tfile_path (str): The relative path of the file
105
+ \t\tfile_hash (str): The SHA-256 hash of the file
106
+ \t\tprevious_backups (dict[str, dict[str, str]]): Dictionary mapping backup zip paths to their stored file hashes
107
+ \tReturns:
108
+ \t\tbool: True if the file exists unchanged in any previous backup, False otherwise
109
+ \t"""