stouputils 1.12.2__py3-none-any.whl → 1.13.0__py3-none-any.whl
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/__main__.py +11 -6
- stouputils/continuous_delivery/pypi.py +39 -1
- stouputils/continuous_delivery/pypi.pyi +9 -0
- stouputils/ctx.py +408 -408
- stouputils/data_science/config/set.py +125 -125
- stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -31
- stouputils/data_science/utils.py +285 -285
- stouputils/installer/__init__.py +18 -18
- stouputils/installer/linux.py +144 -144
- stouputils/installer/main.py +223 -223
- stouputils/installer/windows.py +136 -136
- stouputils/py.typed +1 -1
- stouputils/stouputils/__init__.pyi +15 -0
- stouputils/stouputils/_deprecated.pyi +12 -0
- stouputils/stouputils/all_doctests.pyi +46 -0
- stouputils/stouputils/applications/__init__.pyi +2 -0
- stouputils/stouputils/applications/automatic_docs.pyi +106 -0
- stouputils/stouputils/applications/upscaler/__init__.pyi +3 -0
- stouputils/stouputils/applications/upscaler/config.pyi +18 -0
- stouputils/stouputils/applications/upscaler/image.pyi +109 -0
- stouputils/stouputils/applications/upscaler/video.pyi +60 -0
- stouputils/stouputils/archive.pyi +67 -0
- stouputils/stouputils/backup.pyi +109 -0
- stouputils/stouputils/collections.pyi +86 -0
- stouputils/stouputils/continuous_delivery/__init__.pyi +5 -0
- stouputils/stouputils/continuous_delivery/cd_utils.pyi +129 -0
- stouputils/stouputils/continuous_delivery/github.pyi +162 -0
- stouputils/stouputils/continuous_delivery/pypi.pyi +53 -0
- stouputils/stouputils/continuous_delivery/pyproject.pyi +67 -0
- stouputils/stouputils/continuous_delivery/stubs.pyi +39 -0
- stouputils/stouputils/ctx.pyi +211 -0
- stouputils/stouputils/decorators.pyi +242 -0
- stouputils/stouputils/image.pyi +172 -0
- stouputils/stouputils/installer/__init__.pyi +5 -0
- stouputils/stouputils/installer/common.pyi +39 -0
- stouputils/stouputils/installer/downloader.pyi +24 -0
- stouputils/stouputils/installer/linux.pyi +39 -0
- stouputils/stouputils/installer/main.pyi +57 -0
- stouputils/stouputils/installer/windows.pyi +31 -0
- stouputils/stouputils/io.pyi +213 -0
- stouputils/stouputils/parallel.pyi +211 -0
- stouputils/stouputils/print.pyi +136 -0
- stouputils/stouputils/version_pkg.pyi +15 -0
- {stouputils-1.12.2.dist-info → stouputils-1.13.0.dist-info}/METADATA +1 -1
- {stouputils-1.12.2.dist-info → stouputils-1.13.0.dist-info}/RECORD +47 -16
- {stouputils-1.12.2.dist-info → stouputils-1.13.0.dist-info}/WHEEL +0 -0
- {stouputils-1.12.2.dist-info → stouputils-1.13.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from ..io import super_open as super_open
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
def read_pyproject(pyproject_path: str) -> dict[str, Any]:
|
|
5
|
+
''' Read the pyproject.toml file.
|
|
6
|
+
|
|
7
|
+
\tArgs:
|
|
8
|
+
\t\tpyproject_path: Path to the pyproject.toml file.
|
|
9
|
+
\tReturns:
|
|
10
|
+
\t\tdict[str, Any]: The content of the pyproject.toml file.
|
|
11
|
+
\tExample:
|
|
12
|
+
\t\t>>> content = read_pyproject("pyproject.toml")
|
|
13
|
+
\t\t>>> "." in content["project"]["version"]
|
|
14
|
+
\t\tTrue
|
|
15
|
+
\t'''
|
|
16
|
+
def format_toml_lists(content: str) -> str:
|
|
17
|
+
''' Format TOML lists with indentation.
|
|
18
|
+
|
|
19
|
+
\tArgs:
|
|
20
|
+
\t\tcontent (str): The content of the pyproject.toml file.
|
|
21
|
+
\tReturns:
|
|
22
|
+
\t\tstr: The formatted content with properly indented lists.
|
|
23
|
+
\tExample:
|
|
24
|
+
\t\t>>> toml_content = \'\'\'[project]
|
|
25
|
+
\t\t... dependencies = [ "tqdm>=4.0.0", "requests>=2.20.0", "pyyaml>=6.0.0", ]\'\'\'
|
|
26
|
+
\t\t>>> format_toml_lists(toml_content).replace("\\t", " ") == \'\'\'[project]
|
|
27
|
+
\t\t... dependencies = [
|
|
28
|
+
\t\t... "tqdm>=4.0.0",
|
|
29
|
+
\t\t... "requests>=2.20.0",
|
|
30
|
+
\t\t... "pyyaml>=6.0.0",
|
|
31
|
+
\t\t... ]\'\'\'
|
|
32
|
+
\t\tTrue
|
|
33
|
+
\t'''
|
|
34
|
+
def write_pyproject(path: str, content: dict[str, Any]) -> None:
|
|
35
|
+
""" Write to the pyproject.toml file with properly indented lists.
|
|
36
|
+
|
|
37
|
+
\tArgs:
|
|
38
|
+
\t\tpath: Path to the pyproject.toml file.
|
|
39
|
+
\t\tcontent: Content to write to the pyproject.toml file.
|
|
40
|
+
\t"""
|
|
41
|
+
def increment_version_from_input(version: str) -> str:
|
|
42
|
+
''' Increment the version.
|
|
43
|
+
|
|
44
|
+
\tArgs:
|
|
45
|
+
\t\tversion: The version to increment. (ex: "0.1.0")
|
|
46
|
+
\tReturns:
|
|
47
|
+
\t\tstr: The incremented version. (ex: "0.1.1")
|
|
48
|
+
\tExample:
|
|
49
|
+
\t\t>>> increment_version_from_input("0.1.0")
|
|
50
|
+
\t\t\'0.1.1\'
|
|
51
|
+
\t\t>>> increment_version_from_input("1.2.9")
|
|
52
|
+
\t\t\'1.2.10\'
|
|
53
|
+
\t'''
|
|
54
|
+
def increment_version_from_pyproject(path: str) -> None:
|
|
55
|
+
""" Increment the version in the pyproject.toml file.
|
|
56
|
+
|
|
57
|
+
\tArgs:
|
|
58
|
+
\t\tpath: Path to the pyproject.toml file.
|
|
59
|
+
\t"""
|
|
60
|
+
def get_version_from_pyproject(path: str) -> str:
|
|
61
|
+
''' Get the version from the pyproject.toml file.
|
|
62
|
+
|
|
63
|
+
\tArgs:
|
|
64
|
+
\t\tpath: Path to the pyproject.toml file.
|
|
65
|
+
\tReturns:
|
|
66
|
+
\t\tstr: The version. (ex: "0.1.0")
|
|
67
|
+
\t'''
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from ..decorators import LogLevels as LogLevels, handle_error as handle_error
|
|
2
|
+
from collections.abc import Callable as Callable
|
|
3
|
+
|
|
4
|
+
def generate_stubs(package_name: str, extra_args: str = '--include-docstrings --include-private') -> int:
|
|
5
|
+
''' Generate stub files for a Python package using stubgen.
|
|
6
|
+
|
|
7
|
+
\tNote: stubgen generates stubs in the \'out\' directory by default in the current working directory.
|
|
8
|
+
|
|
9
|
+
\tArgs:
|
|
10
|
+
\t\tpackage_name (str): Name of the package to generate stubs for.
|
|
11
|
+
\t\textra_args (str): Extra arguments to pass to stubgen. Defaults to "--include-docstrings --include-private".
|
|
12
|
+
\tReturns:
|
|
13
|
+
\t\tint: Return code of the os.system call.
|
|
14
|
+
\t'''
|
|
15
|
+
def clean_stubs_directory(output_directory: str, package_name: str) -> None:
|
|
16
|
+
""" Clean the stubs directory by deleting all .pyi files.
|
|
17
|
+
|
|
18
|
+
\tArgs:
|
|
19
|
+
\t\toutput_directory (str): Directory to clean.
|
|
20
|
+
\t\tpackage_name (str): Package name subdirectory. Only cleans output_directory/package_name.
|
|
21
|
+
\t"""
|
|
22
|
+
def stubs_full_routine(package_name: str, output_directory: str = 'typings', extra_args: str = '--include-docstrings --include-private', clean_before: bool = False, generate_stubs_function: Callable[[str, str], int] = ..., clean_stubs_function: Callable[[str, str], None] = ...) -> None:
|
|
23
|
+
''' Generate stub files for a Python package using stubgen.
|
|
24
|
+
|
|
25
|
+
\tNote: stubgen generates stubs in the \'out\' directory by default in the current working directory.
|
|
26
|
+
|
|
27
|
+
\tArgs:
|
|
28
|
+
\t\tpackage_name (str): Name of the package to generate stubs for.
|
|
29
|
+
\t\toutput_directory (str): Directory to clean before generating stubs. Defaults to "typings".
|
|
30
|
+
\t\t\tThis parameter is used for cleaning the directory before stub generation.
|
|
31
|
+
\t\textra_args (str): Extra arguments to pass to stubgen. Defaults to "--include-docstrings --include-private".
|
|
32
|
+
\t\tclean_before (bool): Whether to clean the output directory before generating stubs. Defaults to False.
|
|
33
|
+
\t\tgenerate_stubs_function (Callable[[str, str], int]): Function to generate stubs.
|
|
34
|
+
\t\t\tDefaults to :func:`generate_stubs`.
|
|
35
|
+
\t\tclean_stubs_function (Callable[[str], None]): Function to clean the stubs directory.
|
|
36
|
+
\t\t\tDefaults to :func:`clean_stubs_directory`.
|
|
37
|
+
\tRaises:
|
|
38
|
+
\t\tException: If stub generation fails.
|
|
39
|
+
\t'''
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from .io import super_open as super_open
|
|
3
|
+
from .print import TeeMultiOutput as TeeMultiOutput, debug as debug
|
|
4
|
+
from collections.abc import Callable as Callable
|
|
5
|
+
from contextlib import AbstractAsyncContextManager, AbstractContextManager
|
|
6
|
+
from typing import Any, IO, TextIO, TypeVar
|
|
7
|
+
|
|
8
|
+
T = TypeVar('T')
|
|
9
|
+
|
|
10
|
+
class AbstractBothContextManager[T](AbstractContextManager[T], AbstractAsyncContextManager[T], metaclass=abc.ABCMeta):
|
|
11
|
+
""" Abstract base class for context managers that support both synchronous and asynchronous usage. """
|
|
12
|
+
|
|
13
|
+
class LogToFile(AbstractBothContextManager['LogToFile']):
|
|
14
|
+
''' Context manager to log to a file.
|
|
15
|
+
|
|
16
|
+
\tThis context manager allows you to temporarily log output to a file while still printing normally.
|
|
17
|
+
\tThe file will receive log messages without ANSI color codes.
|
|
18
|
+
|
|
19
|
+
\tArgs:
|
|
20
|
+
\t\tpath (str): Path to the log file
|
|
21
|
+
\t\tmode (str): Mode to open the file in (default: "w")
|
|
22
|
+
\t\tencoding (str): Encoding to use for the file (default: "utf-8")
|
|
23
|
+
\t\ttee_stdout (bool): Whether to redirect stdout to the file (default: True)
|
|
24
|
+
\t\ttee_stderr (bool): Whether to redirect stderr to the file (default: True)
|
|
25
|
+
\t\tignore_lineup (bool): Whether to ignore lines containing LINE_UP escape sequence in files (default: False)
|
|
26
|
+
\t\trestore_on_exit (bool): Whether to restore original stdout/stderr on exit (default: False)
|
|
27
|
+
\t\t\tThis ctx uses TeeMultiOutput which handles closed files gracefully, so restoring is not mandatory.
|
|
28
|
+
|
|
29
|
+
\tExamples:
|
|
30
|
+
\t\t.. code-block:: python
|
|
31
|
+
|
|
32
|
+
\t\t\t> import stouputils as stp
|
|
33
|
+
\t\t\t> with stp.LogToFile("output.log"):
|
|
34
|
+
\t\t\t> stp.info("This will be logged to output.log and printed normally")
|
|
35
|
+
\t\t\t> print("This will also be logged")
|
|
36
|
+
|
|
37
|
+
\t\t\t> with stp.LogToFile("output.log") as log_ctx:
|
|
38
|
+
\t\t\t> stp.warning("This will be logged to output.log and printed normally")
|
|
39
|
+
\t\t\t> log_ctx.change_file("new_file.log")
|
|
40
|
+
\t\t\t> print("This will be logged to new_file.log")
|
|
41
|
+
\t'''
|
|
42
|
+
path: str
|
|
43
|
+
mode: str
|
|
44
|
+
encoding: str
|
|
45
|
+
tee_stdout: bool
|
|
46
|
+
tee_stderr: bool
|
|
47
|
+
ignore_lineup: bool
|
|
48
|
+
restore_on_exit: bool
|
|
49
|
+
file: IO[Any]
|
|
50
|
+
original_stdout: TextIO
|
|
51
|
+
original_stderr: TextIO
|
|
52
|
+
def __init__(self, path: str, mode: str = 'w', encoding: str = 'utf-8', tee_stdout: bool = True, tee_stderr: bool = True, ignore_lineup: bool = True, restore_on_exit: bool = False) -> None: ...
|
|
53
|
+
def __enter__(self) -> LogToFile:
|
|
54
|
+
""" Enter context manager which opens the log file and redirects stdout/stderr """
|
|
55
|
+
def __exit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
|
|
56
|
+
""" Exit context manager which closes the log file and restores stdout/stderr """
|
|
57
|
+
async def __aenter__(self) -> LogToFile:
|
|
58
|
+
""" Enter async context manager which opens the log file and redirects stdout/stderr """
|
|
59
|
+
async def __aexit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
|
|
60
|
+
""" Exit async context manager which closes the log file and restores stdout/stderr """
|
|
61
|
+
def change_file(self, new_path: str) -> None:
|
|
62
|
+
""" Change the log file to a new path.
|
|
63
|
+
|
|
64
|
+
\t\tArgs:
|
|
65
|
+
\t\t\tnew_path (str): New path to the log file
|
|
66
|
+
\t\t"""
|
|
67
|
+
@staticmethod
|
|
68
|
+
def common(logs_folder: str, filepath: str, func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
|
|
69
|
+
''' Common code used at the beginning of a program to launch main function
|
|
70
|
+
|
|
71
|
+
\t\tArgs:
|
|
72
|
+
\t\t\tlogs_folder (str): Folder to store logs in
|
|
73
|
+
\t\t\tfilepath (str): Path to the main function
|
|
74
|
+
\t\t\tfunc (Callable[..., Any]): Main function to launch
|
|
75
|
+
\t\t\t*args (tuple[Any, ...]): Arguments to pass to the main function
|
|
76
|
+
\t\t\t**kwargs (dict[str, Any]): Keyword arguments to pass to the main function
|
|
77
|
+
\t\tReturns:
|
|
78
|
+
\t\t\tAny: Return value of the main function
|
|
79
|
+
|
|
80
|
+
\t\tExamples:
|
|
81
|
+
\t\t\t>>> if __name__ == "__main__":
|
|
82
|
+
\t\t\t... LogToFile.common(f"{ROOT}/logs", __file__, main)
|
|
83
|
+
\t\t'''
|
|
84
|
+
|
|
85
|
+
class MeasureTime(AbstractBothContextManager['MeasureTime']):
|
|
86
|
+
''' Context manager to measure execution time.
|
|
87
|
+
|
|
88
|
+
\tThis context manager measures the execution time of the code block it wraps
|
|
89
|
+
\tand prints the result using a specified print function.
|
|
90
|
+
|
|
91
|
+
\tArgs:
|
|
92
|
+
\t\tprint_func (Callable): Function to use to print the execution time (e.g. debug, info, warning, error, etc.).
|
|
93
|
+
\t\tmessage (str): Message to display with the execution time. Defaults to "Execution time".
|
|
94
|
+
\t\tperf_counter (bool): Whether to use time.perf_counter_ns or time.time_ns. Defaults to True.
|
|
95
|
+
|
|
96
|
+
\tExamples:
|
|
97
|
+
\t\t.. code-block:: python
|
|
98
|
+
|
|
99
|
+
\t\t\t> import time
|
|
100
|
+
\t\t\t> import stouputils as stp
|
|
101
|
+
\t\t\t> with stp.MeasureTime(stp.info, message="My operation"):
|
|
102
|
+
\t\t\t... time.sleep(0.5)
|
|
103
|
+
\t\t\t> # [INFO HH:MM:SS] My operation: 500.123ms (500123456ns)
|
|
104
|
+
|
|
105
|
+
\t\t\t> with stp.MeasureTime(): # Uses debug by default
|
|
106
|
+
\t\t\t... time.sleep(0.1)
|
|
107
|
+
\t\t\t> # [DEBUG HH:MM:SS] Execution time: 100.456ms (100456789ns)
|
|
108
|
+
\t'''
|
|
109
|
+
print_func: Callable[..., None]
|
|
110
|
+
message: str
|
|
111
|
+
perf_counter: bool
|
|
112
|
+
ns: Callable[[], int]
|
|
113
|
+
start_ns: int
|
|
114
|
+
def __init__(self, print_func: Callable[..., None] = ..., message: str = 'Execution time', perf_counter: bool = True) -> None: ...
|
|
115
|
+
def __enter__(self) -> MeasureTime:
|
|
116
|
+
""" Enter context manager, record start time """
|
|
117
|
+
def __exit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
|
|
118
|
+
""" Exit context manager, calculate duration and print """
|
|
119
|
+
async def __aenter__(self) -> MeasureTime:
|
|
120
|
+
""" Enter async context manager, record start time """
|
|
121
|
+
async def __aexit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
|
|
122
|
+
""" Exit async context manager, calculate duration and print """
|
|
123
|
+
|
|
124
|
+
class Muffle(AbstractBothContextManager['Muffle']):
|
|
125
|
+
''' Context manager that temporarily silences output.
|
|
126
|
+
\t(No thread-safety guaranteed)
|
|
127
|
+
|
|
128
|
+
\tAlternative to stouputils.decorators.silent()
|
|
129
|
+
|
|
130
|
+
\tExamples:
|
|
131
|
+
\t\t>>> with Muffle():
|
|
132
|
+
\t\t... print("This will not be printed")
|
|
133
|
+
\t'''
|
|
134
|
+
mute_stderr: bool
|
|
135
|
+
original_stdout: IO[Any]
|
|
136
|
+
original_stderr: IO[Any]
|
|
137
|
+
def __init__(self, mute_stderr: bool = False) -> None: ...
|
|
138
|
+
def __enter__(self) -> Muffle:
|
|
139
|
+
""" Enter context manager which redirects stdout and stderr to devnull """
|
|
140
|
+
def __exit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
|
|
141
|
+
""" Exit context manager which restores original stdout and stderr """
|
|
142
|
+
async def __aenter__(self) -> Muffle:
|
|
143
|
+
""" Enter async context manager which redirects stdout and stderr to devnull """
|
|
144
|
+
async def __aexit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
|
|
145
|
+
""" Exit async context manager which restores original stdout and stderr """
|
|
146
|
+
|
|
147
|
+
class DoNothing(AbstractBothContextManager['DoNothing']):
|
|
148
|
+
''' Context manager that does nothing.
|
|
149
|
+
|
|
150
|
+
\tThis is a no-op context manager that can be used as a placeholder
|
|
151
|
+
\tor for conditional context management.
|
|
152
|
+
|
|
153
|
+
\tDifferent from contextlib.nullcontext because it handles args and kwargs,
|
|
154
|
+
\talong with **async** context management.
|
|
155
|
+
|
|
156
|
+
\tExamples:
|
|
157
|
+
\t\t>>> with DoNothing():
|
|
158
|
+
\t\t... print("This will be printed normally")
|
|
159
|
+
\t\tThis will be printed normally
|
|
160
|
+
|
|
161
|
+
\t\t>>> # Conditional context management
|
|
162
|
+
\t\t>>> some_condition = True
|
|
163
|
+
\t\t>>> ctx = DoNothing() if some_condition else Muffle()
|
|
164
|
+
\t\t>>> with ctx:
|
|
165
|
+
\t\t... print("May or may not be printed depending on condition")
|
|
166
|
+
\t\tMay or may not be printed depending on condition
|
|
167
|
+
\t'''
|
|
168
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
169
|
+
""" No initialization needed, this is a no-op context manager """
|
|
170
|
+
def __enter__(self) -> DoNothing:
|
|
171
|
+
""" Enter context manager (does nothing) """
|
|
172
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
173
|
+
""" Exit context manager (does nothing) """
|
|
174
|
+
async def __aenter__(self) -> DoNothing:
|
|
175
|
+
""" Enter async context manager (does nothing) """
|
|
176
|
+
async def __aexit__(self, *excinfo: Any) -> None:
|
|
177
|
+
""" Exit async context manager (does nothing) """
|
|
178
|
+
NullContextManager = DoNothing
|
|
179
|
+
|
|
180
|
+
class SetMPStartMethod(AbstractBothContextManager['SetMPStartMethod']):
|
|
181
|
+
''' Context manager to temporarily set multiprocessing start method.
|
|
182
|
+
|
|
183
|
+
\tThis context manager allows you to temporarily change the multiprocessing start method
|
|
184
|
+
\tand automatically restores the original method when exiting the context.
|
|
185
|
+
|
|
186
|
+
\tArgs:
|
|
187
|
+
\t\tstart_method (str): The start method to use: "spawn", "fork", or "forkserver"
|
|
188
|
+
|
|
189
|
+
\tExamples:
|
|
190
|
+
\t\t.. code-block:: python
|
|
191
|
+
|
|
192
|
+
\t\t\t> import multiprocessing as mp
|
|
193
|
+
\t\t\t> import stouputils as stp
|
|
194
|
+
\t\t\t> # Temporarily use spawn method
|
|
195
|
+
\t\t\t> with stp.SetMPStartMethod("spawn"):
|
|
196
|
+
\t\t\t> ... # Your multiprocessing code here
|
|
197
|
+
\t\t\t> ... pass
|
|
198
|
+
|
|
199
|
+
\t\t\t> # Original method is automatically restored
|
|
200
|
+
\t'''
|
|
201
|
+
start_method: str | None
|
|
202
|
+
old_method: str | None
|
|
203
|
+
def __init__(self, start_method: str | None) -> None: ...
|
|
204
|
+
def __enter__(self) -> SetMPStartMethod:
|
|
205
|
+
""" Enter context manager which sets the start method """
|
|
206
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
207
|
+
""" Exit context manager which restores the original start method """
|
|
208
|
+
async def __aenter__(self) -> SetMPStartMethod:
|
|
209
|
+
""" Enter async context manager which sets the start method """
|
|
210
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
211
|
+
""" Exit async context manager which restores the original start method """
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
from .ctx import MeasureTime as MeasureTime, Muffle as Muffle
|
|
2
|
+
from .print import error as error, progress as progress, warning as warning
|
|
3
|
+
from collections.abc import Callable as Callable
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
def measure_time(func: Callable[..., Any] | None = None, *, printer: Callable[..., None] = ..., message: str = '', perf_counter: bool = True, is_generator: bool = False) -> Callable[..., Any]:
|
|
8
|
+
''' Decorator that will measure the execution time of a function and print it with the given print function
|
|
9
|
+
|
|
10
|
+
\tArgs:
|
|
11
|
+
\t\tfunc\t\t\t(Callable[..., Any] | None): Function to decorate
|
|
12
|
+
\t\tprinter\t\t\t(Callable):\tFunction to use to print the execution time (e.g. debug, info, warning, error, etc.)
|
|
13
|
+
\t\tmessage\t\t\t(str):\t\tMessage to display with the execution time (e.g. "Execution time of Something"),
|
|
14
|
+
\t\t\tdefaults to "Execution time of {func.__name__}"
|
|
15
|
+
\t\tperf_counter\t(bool):\t\tWhether to use time.perf_counter_ns or time.time_ns
|
|
16
|
+
\t\t\tdefaults to True (use time.perf_counter_ns)
|
|
17
|
+
\t\tis_generator\t(bool):\t\tWhether the function is a generator or not (default: False)
|
|
18
|
+
\t\t\tWhen True, the decorator will yield from the function instead of returning it.
|
|
19
|
+
|
|
20
|
+
\tReturns:
|
|
21
|
+
\t\tCallable: Decorator to measure the time of the function.
|
|
22
|
+
|
|
23
|
+
\tExamples:
|
|
24
|
+
\t\t.. code-block:: python
|
|
25
|
+
|
|
26
|
+
\t\t\t> @measure_time(printer=info)
|
|
27
|
+
\t\t\t> def test():
|
|
28
|
+
\t\t\t> pass
|
|
29
|
+
\t\t\t> test() # [INFO HH:MM:SS] Execution time of test: 0.000ms (400ns)
|
|
30
|
+
\t'''
|
|
31
|
+
|
|
32
|
+
class LogLevels(Enum):
|
|
33
|
+
""" Log level for the errors in the decorator handle_error() """
|
|
34
|
+
NONE = 0
|
|
35
|
+
WARNING = 1
|
|
36
|
+
WARNING_TRACEBACK = 2
|
|
37
|
+
ERROR_TRACEBACK = 3
|
|
38
|
+
RAISE_EXCEPTION = 4
|
|
39
|
+
|
|
40
|
+
force_raise_exception: bool
|
|
41
|
+
|
|
42
|
+
def handle_error(func: Callable[..., Any] | None = None, *, exceptions: tuple[type[BaseException], ...] | type[BaseException] = ..., message: str = '', error_log: LogLevels = ..., sleep_time: float = 0.0) -> Callable[..., Any]:
|
|
43
|
+
''' Decorator that handle an error with different log levels.
|
|
44
|
+
|
|
45
|
+
\tArgs:
|
|
46
|
+
\t\tfunc (Callable[..., Any] | None): \tFunction to decorate
|
|
47
|
+
\t\texceptions\t(tuple[type[BaseException]], ...):\tExceptions to handle
|
|
48
|
+
\t\tmessage\t\t(str):\t\t\t\t\t\t\t\tMessage to display with the error. (e.g. "Error during something")
|
|
49
|
+
\t\terror_log\t(LogLevels):\t\t\t\t\t\tLog level for the errors
|
|
50
|
+
\t\t\tLogLevels.NONE:\t\t\t\t\tNone
|
|
51
|
+
\t\t\tLogLevels.WARNING:\t\t\t\tShow as warning
|
|
52
|
+
\t\t\tLogLevels.WARNING_TRACEBACK:\tShow as warning with traceback
|
|
53
|
+
\t\t\tLogLevels.ERROR_TRACEBACK:\t\tShow as error with traceback
|
|
54
|
+
\t\t\tLogLevels.RAISE_EXCEPTION:\t\tRaise exception
|
|
55
|
+
\t\tsleep_time\t(float):\t\t\t\t\t\t\tTime to sleep after the error (e.g. 0.0 to not sleep, 1.0 to sleep for 1 second)
|
|
56
|
+
|
|
57
|
+
\tExamples:
|
|
58
|
+
\t\t>>> @handle_error
|
|
59
|
+
\t\t... def might_fail():
|
|
60
|
+
\t\t... raise ValueError("Let\'s fail")
|
|
61
|
+
|
|
62
|
+
\t\t>>> @handle_error(error_log=LogLevels.WARNING)
|
|
63
|
+
\t\t... def test():
|
|
64
|
+
\t\t... raise ValueError("Let\'s fail")
|
|
65
|
+
\t\t>>> # test()\t# [WARNING HH:MM:SS] Error during test: (ValueError) Let\'s fail
|
|
66
|
+
\t'''
|
|
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
|
+
|
|
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.
|
|
72
|
+
|
|
73
|
+
\tArgs:
|
|
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
|
+
|
|
78
|
+
\tReturns:
|
|
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
|
+
|
|
84
|
+
\tExamples:
|
|
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
|
|
92
|
+
|
|
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
|
|
100
|
+
\t'''
|
|
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]:
|
|
102
|
+
''' Decorator that retries a function when specific exceptions are raised.
|
|
103
|
+
|
|
104
|
+
\tArgs:
|
|
105
|
+
\t\tfunc\t\t\t(Callable[..., Any] | None):\t\tFunction to retry
|
|
106
|
+
\t\texceptions\t\t(tuple[type[BaseException], ...]):\tExceptions to catch and retry on
|
|
107
|
+
\t\tmax_attempts\t(int | None):\t\t\t\t\t\tMaximum number of attempts (None for infinite retries)
|
|
108
|
+
\t\tdelay\t\t\t(float):\t\t\t\t\t\t\tInitial delay in seconds between retries (default: 1.0)
|
|
109
|
+
\t\tbackoff\t\t\t(float):\t\t\t\t\t\t\tMultiplier for delay after each retry (default: 1.0 for constant delay)
|
|
110
|
+
\t\tmessage\t\t\t(str):\t\t\t\t\t\t\t\tCustom message to display before ", retrying" (default: "{ExceptionName} encountered while running {func_name}")
|
|
111
|
+
|
|
112
|
+
\tReturns:
|
|
113
|
+
\t\tCallable[..., Any]: Decorator that retries the function on specified exceptions
|
|
114
|
+
|
|
115
|
+
\tExamples:
|
|
116
|
+
\t\t>>> import os
|
|
117
|
+
\t\t>>> @retry(exceptions=PermissionError, max_attempts=3, delay=0.1)
|
|
118
|
+
\t\t... def write_file():
|
|
119
|
+
\t\t... with open("test.txt", "w") as f:
|
|
120
|
+
\t\t... f.write("test")
|
|
121
|
+
|
|
122
|
+
\t\t>>> @retry(exceptions=(OSError, IOError), delay=0.5, backoff=2.0)
|
|
123
|
+
\t\t... def network_call():
|
|
124
|
+
\t\t... pass
|
|
125
|
+
|
|
126
|
+
\t\t>>> @retry(max_attempts=5, delay=1.0)
|
|
127
|
+
\t\t... def might_fail():
|
|
128
|
+
\t\t... pass
|
|
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'''
|
|
155
|
+
def abstract(func: Callable[..., Any] | None = None, *, error_log: LogLevels = ...) -> Callable[..., Any]:
|
|
156
|
+
""" Decorator that marks a function as abstract.
|
|
157
|
+
|
|
158
|
+
\tContrary to the abstractmethod decorator from the abc module that raises a TypeError
|
|
159
|
+
\twhen you try to instantiate a class that has abstract methods, this decorator raises
|
|
160
|
+
\ta NotImplementedError ONLY when the decorated function is called, indicating that the function
|
|
161
|
+
\tmust be implemented by a subclass.
|
|
162
|
+
|
|
163
|
+
\tArgs:
|
|
164
|
+
\t\tfunc (Callable[..., Any] | None): The function to mark as abstract
|
|
165
|
+
\t\terror_log (LogLevels): Log level for the error handling
|
|
166
|
+
\t\t\tLogLevels.NONE: None
|
|
167
|
+
\t\t\tLogLevels.WARNING: Show as warning
|
|
168
|
+
\t\t\tLogLevels.WARNING_TRACEBACK: Show as warning with traceback
|
|
169
|
+
\t\t\tLogLevels.ERROR_TRACEBACK: Show as error with traceback
|
|
170
|
+
\t\t\tLogLevels.RAISE_EXCEPTION: Raise exception
|
|
171
|
+
|
|
172
|
+
\tReturns:
|
|
173
|
+
\t\tCallable[..., Any]: Decorator that raises NotImplementedError when called
|
|
174
|
+
|
|
175
|
+
\tExamples:
|
|
176
|
+
\t\t>>> class Base:
|
|
177
|
+
\t\t... @abstract
|
|
178
|
+
\t\t... def method(self):
|
|
179
|
+
\t\t... pass
|
|
180
|
+
\t\t>>> Base().method()
|
|
181
|
+
\t\tTraceback (most recent call last):
|
|
182
|
+
\t\t\t...
|
|
183
|
+
\t\tNotImplementedError: Function 'method' is abstract and must be implemented by a subclass
|
|
184
|
+
\t"""
|
|
185
|
+
def deprecated(func: Callable[..., Any] | None = None, *, message: str = '', version: str = '', error_log: LogLevels = ...) -> Callable[..., Any]:
|
|
186
|
+
''' Decorator that marks a function as deprecated.
|
|
187
|
+
|
|
188
|
+
\tArgs:
|
|
189
|
+
\t\tfunc (Callable[..., Any] | None): Function to mark as deprecated
|
|
190
|
+
\t\tmessage (str): Additional message to display with the deprecation warning
|
|
191
|
+
\t\tversion (str): Version since when the function is deprecated (e.g. "v1.2.0")
|
|
192
|
+
\t\terror_log (LogLevels): Log level for the deprecation warning
|
|
193
|
+
\t\t\tLogLevels.NONE: None
|
|
194
|
+
\t\t\tLogLevels.WARNING: Show as warning
|
|
195
|
+
\t\t\tLogLevels.WARNING_TRACEBACK: Show as warning with traceback
|
|
196
|
+
\t\t\tLogLevels.ERROR_TRACEBACK: Show as error with traceback
|
|
197
|
+
\t\t\tLogLevels.RAISE_EXCEPTION: Raise exception
|
|
198
|
+
\tReturns:
|
|
199
|
+
\t\tCallable[..., Any]: Decorator that marks a function as deprecated
|
|
200
|
+
|
|
201
|
+
\tExamples:
|
|
202
|
+
\t\t>>> @deprecated
|
|
203
|
+
\t\t... def old_function():
|
|
204
|
+
\t\t... pass
|
|
205
|
+
|
|
206
|
+
\t\t>>> @deprecated(message="Use \'new_function()\' instead", error_log=LogLevels.WARNING)
|
|
207
|
+
\t\t... def another_old_function():
|
|
208
|
+
\t\t... pass
|
|
209
|
+
\t'''
|
|
210
|
+
def silent(func: Callable[..., Any] | None = None, *, mute_stderr: bool = False) -> Callable[..., Any]:
|
|
211
|
+
''' Decorator that makes a function silent (disable stdout, and stderr if specified).
|
|
212
|
+
|
|
213
|
+
\tAlternative to stouputils.ctx.Muffle.
|
|
214
|
+
|
|
215
|
+
\tArgs:
|
|
216
|
+
\t\tfunc\t\t\t(Callable[..., Any] | None):\tFunction to make silent
|
|
217
|
+
\t\tmute_stderr\t\t(bool):\t\t\t\t\t\t\tWhether to mute stderr or not
|
|
218
|
+
|
|
219
|
+
\tExamples:
|
|
220
|
+
\t\t>>> @silent
|
|
221
|
+
\t\t... def test():
|
|
222
|
+
\t\t... print("Hello, world!")
|
|
223
|
+
\t\t>>> test()
|
|
224
|
+
|
|
225
|
+
\t\t>>> @silent(mute_stderr=True)
|
|
226
|
+
\t\t... def test2():
|
|
227
|
+
\t\t... print("Hello, world!")
|
|
228
|
+
\t\t>>> test2()
|
|
229
|
+
|
|
230
|
+
\t\t>>> silent(print)("Hello, world!")
|
|
231
|
+
\t'''
|
|
232
|
+
def _get_func_name(func: Callable[..., Any]) -> str:
|
|
233
|
+
''' Get the name of a function, returns "<unknown>" if the name cannot be retrieved. '''
|
|
234
|
+
def _get_wrapper_name(decorator_name: str, func: Callable[..., Any]) -> str:
|
|
235
|
+
''' Get a descriptive name for a wrapper function.
|
|
236
|
+
|
|
237
|
+
\tArgs:
|
|
238
|
+
\t\tdecorator_name\t(str):\t\t\t\t\tName of the decorator
|
|
239
|
+
\t\tfunc\t\t\t(Callable[..., Any]):\tFunction being decorated
|
|
240
|
+
\tReturns:
|
|
241
|
+
\t\tstr: Combined name for the wrapper function (e.g., "stouputils.decorators.handle_error@function_name")
|
|
242
|
+
\t'''
|