stouputils 1.4.0__tar.gz → 1.4.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.
- {stouputils-1.4.0 → stouputils-1.4.1}/.gitignore +1 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/PKG-INFO +1 -1
- {stouputils-1.4.0 → stouputils-1.4.1}/pyproject.toml +4 -1
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/__init__.py +5 -5
- stouputils-1.4.1/stouputils/__main__.py +35 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/all_doctests.py +23 -12
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/applications/upscaler/image.py +2 -2
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/applications/upscaler/video.py +2 -2
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/archive.py +129 -110
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/backup.py +120 -119
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/collections.py +2 -3
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/ctx.py +87 -77
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/base_keras.py +2 -3
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/decorators.py +109 -109
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/image.py +7 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/io.py +144 -141
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/parallel.py +68 -85
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/print.py +119 -29
- {stouputils-1.4.0 → stouputils-1.4.1}/LICENSE +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/README.md +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/applications/__init__.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/applications/automatic_docs.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/applications/upscaler/__init__.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/applications/upscaler/config.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/continuous_delivery/__init__.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/continuous_delivery/cd_utils.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/continuous_delivery/github.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/continuous_delivery/pypi.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/continuous_delivery/pyproject.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/config/get.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/config/set.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/__init__.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/auto_contrast.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/axis_flip.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/bias_field_correction.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/binary_threshold.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/blur.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/brightness.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/canny.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/clahe.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/common.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/contrast.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/curvature_flow_filter.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/denoise.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/histogram_equalization.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/invert.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/laplacian.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/median_blur.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/noise.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/normalize.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/random_erase.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/resize.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/rotation.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/salt_pepper.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/sharpening.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/shearing.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/threshold.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/translation.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image/zoom.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image_augmentation.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/image_preprocess.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/prosthesis_detection.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/data_processing/technique.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/dataset/__init__.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/dataset/dataset.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/dataset/dataset_loader.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/dataset/grouping_strategy.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/dataset/image_loader.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/dataset/xy_tuple.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/metric_dictionnary.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/metric_utils.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/mlflow_utils.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/abstract_model.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/all.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras/all.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras/convnext.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras/densenet.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras/efficientnet.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras/mobilenet.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras/resnet.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras/squeezenet.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras/vgg.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras/xception.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras_utils/callbacks/__init__.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras_utils/losses/__init__.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/keras_utils/visualizations.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/model_interface.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/models/sandbox.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/range_tuple.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/scripts/augment_dataset.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/scripts/exhaustive_process.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/scripts/preprocess_dataset.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/scripts/routine.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/data_science/utils.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/installer/__init__.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/installer/common.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/installer/downloader.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/installer/linux.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/installer/main.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/installer/windows.py +0 -0
- {stouputils-1.4.0 → stouputils-1.4.1}/stouputils/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stouputils
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.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
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "stouputils"
|
|
8
|
-
version = "1.4.
|
|
8
|
+
version = "1.4.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"
|
|
@@ -23,6 +23,9 @@ email = "stoupy51@gmail.com"
|
|
|
23
23
|
Homepage = "https://github.com/Stoupy51/stouputils"
|
|
24
24
|
Issues = "https://github.com/Stoupy51/stouputils/issues"
|
|
25
25
|
|
|
26
|
+
[project.scripts]
|
|
27
|
+
stouputils = "stouputils.__main__:main"
|
|
28
|
+
|
|
26
29
|
[tool.pyright]
|
|
27
30
|
typeCheckingMode = "strict"
|
|
28
31
|
|
|
@@ -16,6 +16,9 @@ Key Features:
|
|
|
16
16
|
"""
|
|
17
17
|
# ruff: noqa: F403
|
|
18
18
|
|
|
19
|
+
# Version (handle case where the package is not installed)
|
|
20
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
21
|
+
|
|
19
22
|
# Imports
|
|
20
23
|
from .all_doctests import *
|
|
21
24
|
from .archive import *
|
|
@@ -31,11 +34,8 @@ from .io import *
|
|
|
31
34
|
from .parallel import *
|
|
32
35
|
from .print import *
|
|
33
36
|
|
|
34
|
-
# Version (handle case where the package is not installed)
|
|
35
|
-
import importlib.metadata
|
|
36
|
-
|
|
37
37
|
try:
|
|
38
|
-
__version__: str =
|
|
39
|
-
except
|
|
38
|
+
__version__: str = version("stouputils")
|
|
39
|
+
except PackageNotFoundError:
|
|
40
40
|
__version__: str = "0.0.0-dev"
|
|
41
41
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# Imports
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from .all_doctests import launch_tests
|
|
7
|
+
from .decorators import handle_error
|
|
8
|
+
from .print import show_version
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@handle_error(message="Error while running 'stouputils'")
|
|
12
|
+
def main():
|
|
13
|
+
second_arg: str = sys.argv[1].lower() if len(sys.argv) >= 2 else ""
|
|
14
|
+
if not second_arg:
|
|
15
|
+
# TODO
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
# Print the version of stouputils and its dependencies
|
|
19
|
+
if second_arg in ("--version","-v"):
|
|
20
|
+
return show_version()
|
|
21
|
+
|
|
22
|
+
# Handle "all_doctests" command
|
|
23
|
+
if second_arg == "all_doctests":
|
|
24
|
+
if launch_tests("." if len(sys.argv) == 2 else sys.argv[2]) > 0:
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
# Check if the command is any package name
|
|
29
|
+
if second_arg in (): # type: ignore
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
main()
|
|
35
|
+
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module is used to run all the doctests for all the modules in a given directory.
|
|
3
3
|
|
|
4
|
+
- launch_tests: Main function to launch tests for all modules in the given directory.
|
|
5
|
+
- test_module_with_progress: Test a module with testmod and measure the time taken with progress printing.
|
|
6
|
+
|
|
4
7
|
.. image:: https://raw.githubusercontent.com/Stoupy51/stouputils/refs/heads/main/assets/all_doctests_module.gif
|
|
5
8
|
:alt: stouputils all_doctests examples
|
|
6
9
|
"""
|
|
@@ -14,24 +17,17 @@ from doctest import TestResults, testmod
|
|
|
14
17
|
from types import ModuleType
|
|
15
18
|
|
|
16
19
|
from . import decorators
|
|
17
|
-
from .decorators import
|
|
18
|
-
from .print import error, info, progress, warning
|
|
20
|
+
from .decorators import measure_time
|
|
19
21
|
from .io import clean_path, relative_path
|
|
22
|
+
from .print import error, info, progress, warning
|
|
20
23
|
|
|
21
24
|
|
|
22
|
-
def test_module_with_progress(module: ModuleType, separator: str) -> TestResults:
|
|
23
|
-
@measure_time(progress, message=f"Testing module '{module.__name__}' {separator}took")
|
|
24
|
-
def internal() -> TestResults:
|
|
25
|
-
return testmod(m=module)
|
|
26
|
-
return internal()
|
|
27
|
-
|
|
28
25
|
# Main program
|
|
29
|
-
def launch_tests(root_dir: str,
|
|
26
|
+
def launch_tests(root_dir: str, strict: bool = True) -> int:
|
|
30
27
|
""" Main function to launch tests for all modules in the given directory.
|
|
31
28
|
|
|
32
29
|
Args:
|
|
33
30
|
root_dir (str): Root directory to search for modules
|
|
34
|
-
importing_errors (LogLevels): Log level for the errors when importing modules
|
|
35
31
|
strict (bool): Modify the force_raise_exception variable to True in the decorators module
|
|
36
32
|
|
|
37
33
|
Returns:
|
|
@@ -61,7 +57,7 @@ def launch_tests(root_dir: str, importing_errors: LogLevels = LogLevels.WARNING_
|
|
|
61
57
|
old_value: bool = strict
|
|
62
58
|
decorators.force_raise_exception = True
|
|
63
59
|
strict = old_value
|
|
64
|
-
|
|
60
|
+
|
|
65
61
|
# Get the path of the directory to check modules from
|
|
66
62
|
working_dir: str = clean_path(os.getcwd())
|
|
67
63
|
root_dir = clean_path(os.path.abspath(root_dir))
|
|
@@ -108,7 +104,7 @@ def launch_tests(root_dir: str, importing_errors: LogLevels = LogLevels.WARNING_
|
|
|
108
104
|
def internal(a: str = module_path, b: str = separator) -> None:
|
|
109
105
|
modules.append(importlib.import_module(a))
|
|
110
106
|
separators.append(b)
|
|
111
|
-
|
|
107
|
+
|
|
112
108
|
try:
|
|
113
109
|
internal()
|
|
114
110
|
except Exception as e:
|
|
@@ -135,3 +131,18 @@ def launch_tests(root_dir: str, importing_errors: LogLevels = LogLevels.WARNING_
|
|
|
135
131
|
# Return the number of failed tests
|
|
136
132
|
return nb_failed_tests
|
|
137
133
|
|
|
134
|
+
|
|
135
|
+
def test_module_with_progress(module: ModuleType, separator: str) -> TestResults:
|
|
136
|
+
""" Test a module with testmod and measure the time taken with progress printing.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
module (ModuleType): Module to test
|
|
140
|
+
separator (str): Separator string for alignment in output
|
|
141
|
+
Returns:
|
|
142
|
+
TestResults: The results of the tests
|
|
143
|
+
"""
|
|
144
|
+
@measure_time(progress, message=f"Testing module '{module.__name__}' {separator}took")
|
|
145
|
+
def internal() -> TestResults:
|
|
146
|
+
return testmod(m=module)
|
|
147
|
+
return internal()
|
|
148
|
+
|
|
@@ -36,8 +36,8 @@ from PIL import Image
|
|
|
36
36
|
|
|
37
37
|
from ...installer import check_executable
|
|
38
38
|
from ...io import clean_path
|
|
39
|
-
from ...parallel import
|
|
40
|
-
from ...print import debug, info
|
|
39
|
+
from ...parallel import multithreading
|
|
40
|
+
from ...print import colored_for_loop, debug, info
|
|
41
41
|
from .config import WAIFU2X_NCNN_VULKAN_RELEASES, Config
|
|
42
42
|
|
|
43
43
|
|
|
@@ -46,8 +46,8 @@ from PIL import Image
|
|
|
46
46
|
|
|
47
47
|
from ...installer import check_executable
|
|
48
48
|
from ...io import clean_path
|
|
49
|
-
from ...parallel import
|
|
50
|
-
from ...print import debug, error, info, warning
|
|
49
|
+
from ...parallel import multithreading
|
|
50
|
+
from ...print import colored_for_loop, debug, error, info, warning
|
|
51
51
|
from .config import FFMPEG_RELEASES, YOUTUBE_BITRATE_RECOMMENDATIONS, Config
|
|
52
52
|
from .image import convert_frame, get_all_files, upscale_folder
|
|
53
53
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module provides functions for creating and managing archives.
|
|
3
3
|
|
|
4
|
-
- make_archive: Create a zip archive from a source directory with consistent file timestamps.
|
|
5
4
|
- repair_zip_file: Try to repair a corrupted zip file by ignoring some of the errors
|
|
5
|
+
- make_archive: Create a zip archive from a source directory with consistent file timestamps.
|
|
6
|
+
- archive_cli: Main entry point for command line usage
|
|
6
7
|
|
|
7
8
|
.. image:: https://raw.githubusercontent.com/Stoupy51/stouputils/refs/heads/main/assets/archive_module.gif
|
|
8
9
|
:alt: stouputils archive examples
|
|
@@ -16,107 +17,7 @@ from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
|
|
|
16
17
|
|
|
17
18
|
from .decorators import LogLevels, handle_error
|
|
18
19
|
from .io import clean_path, super_copy
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
# Function that makes an archive with consistency (same zip file each time)
|
|
22
|
-
@handle_error()
|
|
23
|
-
def make_archive(
|
|
24
|
-
source: str,
|
|
25
|
-
destinations: list[str] | str | None = None,
|
|
26
|
-
override_time: None | tuple[int, int, int, int, int, int] = None,
|
|
27
|
-
create_dir: bool = False,
|
|
28
|
-
ignore_patterns: str | None = None,
|
|
29
|
-
) -> bool:
|
|
30
|
-
""" Create a zip archive from a source directory with consistent file timestamps.
|
|
31
|
-
(Meaning deterministic zip file each time)
|
|
32
|
-
|
|
33
|
-
Creates a zip archive from the source directory and copies it to one or more destinations.
|
|
34
|
-
The archive will have consistent file timestamps across runs if override_time is specified.
|
|
35
|
-
Uses maximum compression level (9) with ZIP_DEFLATED algorithm.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
source (str): The source folder to archive
|
|
39
|
-
destinations (list[str]|str): The destination folder(s) or file(s) to copy the archive to
|
|
40
|
-
override_time (None | tuple[int, ...]): The constant time to use for the archive
|
|
41
|
-
(e.g. (2024, 1, 1, 0, 0, 0) for 2024-01-01 00:00:00)
|
|
42
|
-
create_dir (bool): Whether to create the destination directory if it doesn't exist
|
|
43
|
-
ignore_patterns (str | None): Glob pattern(s) to ignore files. Can be a single pattern or comma-separated patterns (e.g. "*.pyc" or "*.pyc,__pycache__,*.log")
|
|
44
|
-
Returns:
|
|
45
|
-
bool: Always returns True unless any strong error
|
|
46
|
-
Examples:
|
|
47
|
-
|
|
48
|
-
.. code-block:: python
|
|
49
|
-
|
|
50
|
-
> make_archive("/path/to/source", "/path/to/destination.zip")
|
|
51
|
-
> make_archive("/path/to/source", ["/path/to/destination.zip", "/path/to/destination2.zip"])
|
|
52
|
-
> make_archive("src", "hello_from_year_2085.zip", override_time=(2085,1,1,0,0,0))
|
|
53
|
-
> make_archive("src", "output.zip", ignore_patterns="*.pyc")
|
|
54
|
-
> make_archive("src", "output.zip", ignore_patterns="__pycache__")
|
|
55
|
-
> make_archive("src", "output.zip", ignore_patterns="*.pyc,__pycache__,*.log")
|
|
56
|
-
"""
|
|
57
|
-
# Fix copy_destinations type if needed
|
|
58
|
-
if destinations is None:
|
|
59
|
-
destinations = []
|
|
60
|
-
if destinations and isinstance(destinations, str):
|
|
61
|
-
destinations = [destinations]
|
|
62
|
-
if not destinations:
|
|
63
|
-
raise ValueError("destinations must be a list of at least one destination")
|
|
64
|
-
|
|
65
|
-
# Create directories if needed
|
|
66
|
-
if create_dir:
|
|
67
|
-
for dest_file in destinations:
|
|
68
|
-
dest_file = clean_path(dest_file)
|
|
69
|
-
parent_dir = os.path.dirname(dest_file)
|
|
70
|
-
if parent_dir and not os.path.exists(parent_dir):
|
|
71
|
-
os.makedirs(parent_dir, exist_ok=True)
|
|
72
|
-
|
|
73
|
-
# Create the archive
|
|
74
|
-
destination: str = clean_path(destinations[0])
|
|
75
|
-
destination = destination if ".zip" in destination else destination + ".zip"
|
|
76
|
-
|
|
77
|
-
# Parse ignore patterns (can be a single pattern or comma-separated patterns)
|
|
78
|
-
ignore_pattern_list: list[str] = []
|
|
79
|
-
if ignore_patterns:
|
|
80
|
-
ignore_pattern_list = [pattern.strip() for pattern in ignore_patterns.split(',')]
|
|
81
|
-
|
|
82
|
-
def should_ignore(path: str) -> bool:
|
|
83
|
-
"""Check if a file or directory path should be ignored based on patterns."""
|
|
84
|
-
if not ignore_pattern_list:
|
|
85
|
-
return False
|
|
86
|
-
for pattern in ignore_pattern_list:
|
|
87
|
-
if fnmatch.fnmatch(os.path.basename(path), pattern) or fnmatch.fnmatch(path, pattern):
|
|
88
|
-
return True
|
|
89
|
-
return False
|
|
90
|
-
|
|
91
|
-
with ZipFile(destination, "w", compression=ZIP_DEFLATED, compresslevel=9) as zip:
|
|
92
|
-
for root, dirs, files in os.walk(source):
|
|
93
|
-
# Filter out ignored directories in-place to prevent walking into them
|
|
94
|
-
dirs[:] = [d for d in dirs if not should_ignore(d)]
|
|
95
|
-
|
|
96
|
-
for file in files:
|
|
97
|
-
file_path: str = clean_path(os.path.join(root, file))
|
|
98
|
-
rel_path = os.path.relpath(file_path, source)
|
|
99
|
-
|
|
100
|
-
# Skip files that match any ignore pattern
|
|
101
|
-
if should_ignore(file) or should_ignore(rel_path):
|
|
102
|
-
continue
|
|
103
|
-
|
|
104
|
-
info: ZipInfo = ZipInfo(rel_path)
|
|
105
|
-
info.compress_type = ZIP_DEFLATED
|
|
106
|
-
if override_time:
|
|
107
|
-
info.date_time = override_time
|
|
108
|
-
with open(file_path, "rb") as f:
|
|
109
|
-
zip.writestr(info, f.read())
|
|
110
|
-
|
|
111
|
-
# Copy the archive to the destination(s)
|
|
112
|
-
for dest_file in destinations[1:]:
|
|
113
|
-
@handle_error(Exception, message=f"Unable to copy '{destination}' to '{dest_file}'", error_log=LogLevels.WARNING)
|
|
114
|
-
def internal(src: str, dest: str) -> None:
|
|
115
|
-
super_copy(src, dest, create_dir=create_dir)
|
|
116
|
-
internal(destination, clean_path(dest_file))
|
|
117
|
-
|
|
118
|
-
return True
|
|
119
|
-
|
|
20
|
+
from .print import debug, error, info
|
|
120
21
|
|
|
121
22
|
|
|
122
23
|
# Function that repair a corrupted zip file (ignoring some of the errors)
|
|
@@ -246,9 +147,123 @@ def repair_zip_file(file_path: str, destination: str) -> bool:
|
|
|
246
147
|
|
|
247
148
|
return True
|
|
248
149
|
|
|
150
|
+
# Function that makes an archive with consistency (same zip file each time)
|
|
151
|
+
@handle_error()
|
|
152
|
+
def make_archive(
|
|
153
|
+
source: str,
|
|
154
|
+
destinations: list[str] | str | None = None,
|
|
155
|
+
override_time: None | tuple[int, int, int, int, int, int] = None,
|
|
156
|
+
create_dir: bool = False,
|
|
157
|
+
ignore_patterns: str | None = None,
|
|
158
|
+
) -> bool:
|
|
159
|
+
""" Create a zip archive from a source directory with consistent file timestamps.
|
|
160
|
+
(Meaning deterministic zip file each time)
|
|
249
161
|
|
|
250
|
-
|
|
251
|
-
if
|
|
162
|
+
Creates a zip archive from the source directory and copies it to one or more destinations.
|
|
163
|
+
The archive will have consistent file timestamps across runs if override_time is specified.
|
|
164
|
+
Uses maximum compression level (9) with ZIP_DEFLATED algorithm.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
source (str): The source folder to archive
|
|
168
|
+
destinations (list[str]|str): The destination folder(s) or file(s) to copy the archive to
|
|
169
|
+
override_time (None | tuple[int, ...]): The constant time to use for the archive
|
|
170
|
+
(e.g. (2024, 1, 1, 0, 0, 0) for 2024-01-01 00:00:00)
|
|
171
|
+
create_dir (bool): Whether to create the destination directory if it doesn't exist
|
|
172
|
+
ignore_patterns (str | None): Glob pattern(s) to ignore files. Can be a single pattern or comma-separated patterns (e.g. "*.pyc" or "*.pyc,__pycache__,*.log")
|
|
173
|
+
Returns:
|
|
174
|
+
bool: Always returns True unless any strong error
|
|
175
|
+
Examples:
|
|
176
|
+
|
|
177
|
+
.. code-block:: python
|
|
178
|
+
|
|
179
|
+
> make_archive("/path/to/source", "/path/to/destination.zip")
|
|
180
|
+
> make_archive("/path/to/source", ["/path/to/destination.zip", "/path/to/destination2.zip"])
|
|
181
|
+
> make_archive("src", "hello_from_year_2085.zip", override_time=(2085,1,1,0,0,0))
|
|
182
|
+
> make_archive("src", "output.zip", ignore_patterns="*.pyc")
|
|
183
|
+
> make_archive("src", "output.zip", ignore_patterns="__pycache__")
|
|
184
|
+
> make_archive("src", "output.zip", ignore_patterns="*.pyc,__pycache__,*.log")
|
|
185
|
+
"""
|
|
186
|
+
# Fix copy_destinations type if needed
|
|
187
|
+
if destinations is None:
|
|
188
|
+
destinations = []
|
|
189
|
+
if destinations and isinstance(destinations, str):
|
|
190
|
+
destinations = [destinations]
|
|
191
|
+
if not destinations:
|
|
192
|
+
raise ValueError("destinations must be a list of at least one destination")
|
|
193
|
+
|
|
194
|
+
# Create directories if needed
|
|
195
|
+
if create_dir:
|
|
196
|
+
for dest_file in destinations:
|
|
197
|
+
dest_file = clean_path(dest_file)
|
|
198
|
+
parent_dir = os.path.dirname(dest_file)
|
|
199
|
+
if parent_dir and not os.path.exists(parent_dir):
|
|
200
|
+
os.makedirs(parent_dir, exist_ok=True)
|
|
201
|
+
|
|
202
|
+
# Create the archive
|
|
203
|
+
destination: str = clean_path(destinations[0])
|
|
204
|
+
destination = destination if ".zip" in destination else destination + ".zip"
|
|
205
|
+
|
|
206
|
+
# Parse ignore patterns (can be a single pattern or comma-separated patterns)
|
|
207
|
+
ignore_pattern_list: list[str] = []
|
|
208
|
+
if ignore_patterns:
|
|
209
|
+
ignore_pattern_list = [pattern.strip() for pattern in ignore_patterns.split(',')]
|
|
210
|
+
|
|
211
|
+
def should_ignore(path: str) -> bool:
|
|
212
|
+
"""Check if a file or directory path should be ignored based on patterns."""
|
|
213
|
+
if not ignore_pattern_list:
|
|
214
|
+
return False
|
|
215
|
+
for pattern in ignore_pattern_list:
|
|
216
|
+
if fnmatch.fnmatch(os.path.basename(path), pattern) or fnmatch.fnmatch(path, pattern):
|
|
217
|
+
return True
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
with ZipFile(destination, "w", compression=ZIP_DEFLATED, compresslevel=9) as zip:
|
|
221
|
+
for root, dirs, files in os.walk(source):
|
|
222
|
+
# Filter out ignored directories in-place to prevent walking into them
|
|
223
|
+
dirs[:] = [d for d in dirs if not should_ignore(d)]
|
|
224
|
+
|
|
225
|
+
for file in files:
|
|
226
|
+
file_path: str = clean_path(os.path.join(root, file))
|
|
227
|
+
rel_path = os.path.relpath(file_path, source)
|
|
228
|
+
|
|
229
|
+
# Skip files that match any ignore pattern
|
|
230
|
+
if should_ignore(file) or should_ignore(rel_path):
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
info: ZipInfo = ZipInfo(rel_path)
|
|
234
|
+
info.compress_type = ZIP_DEFLATED
|
|
235
|
+
if override_time:
|
|
236
|
+
info.date_time = override_time
|
|
237
|
+
with open(file_path, "rb") as f:
|
|
238
|
+
zip.writestr(info, f.read())
|
|
239
|
+
|
|
240
|
+
# Copy the archive to the destination(s)
|
|
241
|
+
for dest_file in destinations[1:]:
|
|
242
|
+
@handle_error(Exception, message=f"Unable to copy '{destination}' to '{dest_file}'", error_log=LogLevels.WARNING)
|
|
243
|
+
def internal(src: str, dest: str) -> None:
|
|
244
|
+
super_copy(src, dest, create_dir=create_dir)
|
|
245
|
+
internal(destination, clean_path(dest_file))
|
|
246
|
+
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# Main entry point for command line usage
|
|
251
|
+
def archive_cli():
|
|
252
|
+
""" Main entry point for command line usage.
|
|
253
|
+
|
|
254
|
+
Examples:
|
|
255
|
+
|
|
256
|
+
.. code-block:: bash
|
|
257
|
+
|
|
258
|
+
# Repair a corrupted zip file
|
|
259
|
+
python -m stouputils.archive repair /path/to/corrupted.zip /path/to/repaired.zip
|
|
260
|
+
|
|
261
|
+
# Create a zip archive
|
|
262
|
+
python -m stouputils.archive make /path/to/source /path/to/destination.zip
|
|
263
|
+
|
|
264
|
+
# Create a zip archive with ignore patterns
|
|
265
|
+
python -m stouputils.archive make /path/to/source /path/to/destination.zip --ignore "*.pyc,__pycache__"
|
|
266
|
+
"""
|
|
252
267
|
import argparse
|
|
253
268
|
import sys
|
|
254
269
|
|
|
@@ -278,16 +293,16 @@ if __name__ == "__main__":
|
|
|
278
293
|
base, ext = os.path.splitext(input_file)
|
|
279
294
|
output_file = f"{base}_repaired{ext}"
|
|
280
295
|
|
|
281
|
-
|
|
296
|
+
debug(f"Repairing '{input_file}' to '{output_file}'...")
|
|
282
297
|
try:
|
|
283
298
|
repair_zip_file(input_file, output_file)
|
|
284
|
-
|
|
299
|
+
info(f"Successfully repaired zip file: {output_file}")
|
|
285
300
|
except Exception as e:
|
|
286
|
-
|
|
301
|
+
error(f"Error repairing zip file: {e}", exit=False)
|
|
287
302
|
sys.exit(1)
|
|
288
303
|
|
|
289
304
|
elif args.command == "make":
|
|
290
|
-
|
|
305
|
+
debug(f"Creating archive from '{args.source}' to '{args.destination}'...")
|
|
291
306
|
try:
|
|
292
307
|
make_archive(
|
|
293
308
|
source=args.source,
|
|
@@ -295,9 +310,9 @@ if __name__ == "__main__":
|
|
|
295
310
|
create_dir=args.create_dir,
|
|
296
311
|
ignore_patterns=args.ignore
|
|
297
312
|
)
|
|
298
|
-
|
|
313
|
+
info(f"Successfully created archive: {args.destination}")
|
|
299
314
|
except Exception as e:
|
|
300
|
-
|
|
315
|
+
error(f"Error creating archive: {e}", exit=False)
|
|
301
316
|
sys.exit(1)
|
|
302
317
|
|
|
303
318
|
else:
|
|
@@ -305,3 +320,7 @@ if __name__ == "__main__":
|
|
|
305
320
|
sys.exit(1)
|
|
306
321
|
|
|
307
322
|
|
|
323
|
+
if __name__ == "__main__":
|
|
324
|
+
archive_cli()
|
|
325
|
+
|
|
326
|
+
|