stouputils 1.16.3__py3-none-any.whl → 1.18.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/__init__.py +1 -0
- stouputils/__init__.pyi +1 -0
- stouputils/all_doctests.py +1 -1
- stouputils/collections.py +2 -5
- stouputils/collections.pyi +2 -4
- stouputils/continuous_delivery/stubs.py +1 -1
- stouputils/ctx.py +1 -3
- stouputils/ctx.pyi +1 -3
- stouputils/decorators.py +1 -1
- stouputils/image.py +8 -10
- stouputils/image.pyi +4 -6
- stouputils/io.py +22 -1
- stouputils/io.pyi +7 -1
- stouputils/lock/__init__.py +36 -0
- stouputils/lock/__init__.pyi +5 -0
- stouputils/lock/base.py +536 -0
- stouputils/lock/base.pyi +169 -0
- stouputils/lock/queue.py +377 -0
- stouputils/lock/queue.pyi +131 -0
- stouputils/lock/re_entrant.py +115 -0
- stouputils/lock/re_entrant.pyi +81 -0
- stouputils/lock/redis_fifo.py +299 -0
- stouputils/lock/redis_fifo.pyi +123 -0
- stouputils/lock/shared.py +30 -0
- stouputils/lock/shared.pyi +16 -0
- stouputils/parallel/__init__.py +29 -0
- stouputils/parallel/__init__.pyi +4 -0
- stouputils/parallel/capturer.py +133 -0
- stouputils/parallel/capturer.pyi +38 -0
- stouputils/parallel/common.py +134 -0
- stouputils/parallel/common.pyi +53 -0
- stouputils/parallel/multi.py +309 -0
- stouputils/{parallel.pyi → parallel/multi.pyi} +14 -112
- stouputils/parallel/subprocess.py +163 -0
- stouputils/parallel/subprocess.pyi +64 -0
- stouputils/print.py +2 -3
- stouputils/print.pyi +1 -2
- {stouputils-1.16.3.dist-info → stouputils-1.18.0.dist-info}/METADATA +4 -1
- {stouputils-1.16.3.dist-info → stouputils-1.18.0.dist-info}/RECORD +41 -21
- stouputils/parallel.py +0 -556
- {stouputils-1.16.3.dist-info → stouputils-1.18.0.dist-info}/WHEEL +0 -0
- {stouputils-1.16.3.dist-info → stouputils-1.18.0.dist-info}/entry_points.txt +0 -0
stouputils/__init__.py
CHANGED
stouputils/__init__.pyi
CHANGED
stouputils/all_doctests.py
CHANGED
|
@@ -99,7 +99,7 @@ def launch_tests(root_dir: str, strict: bool = True, pattern: str = "*") -> int:
|
|
|
99
99
|
import fnmatch
|
|
100
100
|
modules_file_paths = [
|
|
101
101
|
path for path in modules_file_paths
|
|
102
|
-
if fnmatch.fnmatch(path
|
|
102
|
+
if fnmatch.fnmatch(path, pattern)
|
|
103
103
|
]
|
|
104
104
|
if not modules_file_paths:
|
|
105
105
|
raise ValueError(f"No modules matching pattern '{pattern}' found in '{relative_path(root_dir)}'")
|
stouputils/collections.py
CHANGED
|
@@ -17,7 +17,7 @@ import os
|
|
|
17
17
|
import shutil
|
|
18
18
|
import tempfile
|
|
19
19
|
from collections.abc import Callable, Iterable
|
|
20
|
-
from typing import TYPE_CHECKING, Any, Literal
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
21
21
|
|
|
22
22
|
# Lazy imports for typing
|
|
23
23
|
if TYPE_CHECKING:
|
|
@@ -26,9 +26,6 @@ if TYPE_CHECKING:
|
|
|
26
26
|
import zarr # pyright: ignore[reportMissingTypeStubs]
|
|
27
27
|
from numpy.typing import NDArray
|
|
28
28
|
|
|
29
|
-
# Typing
|
|
30
|
-
T = TypeVar("T")
|
|
31
|
-
|
|
32
29
|
# Functions
|
|
33
30
|
def unique_list[T](list_to_clean: Iterable[T], method: Literal["id", "hash", "str"] = "str") -> list[T]:
|
|
34
31
|
""" Remove duplicates from the list while keeping the order using ids, hash, or str
|
|
@@ -79,7 +76,7 @@ def unique_list[T](list_to_clean: Iterable[T], method: Literal["id", "hash", "st
|
|
|
79
76
|
return result
|
|
80
77
|
|
|
81
78
|
|
|
82
|
-
def at_least_n(iterable: Iterable[T], predicate: Callable[[T], bool], n: int) -> bool:
|
|
79
|
+
def at_least_n[T](iterable: Iterable[T], predicate: Callable[[T], bool], n: int) -> bool:
|
|
83
80
|
""" Return True if at least n elements in iterable satisfy predicate.
|
|
84
81
|
It's like the built-in any() but for at least n matches.
|
|
85
82
|
|
stouputils/collections.pyi
CHANGED
|
@@ -2,9 +2,7 @@ import polars as pl
|
|
|
2
2
|
import zarr
|
|
3
3
|
from collections.abc import Callable as Callable, Iterable
|
|
4
4
|
from numpy.typing import NDArray as NDArray
|
|
5
|
-
from typing import Any, Literal
|
|
6
|
-
|
|
7
|
-
T = TypeVar('T')
|
|
5
|
+
from typing import Any, Literal
|
|
8
6
|
|
|
9
7
|
def unique_list[T](list_to_clean: Iterable[T], method: Literal['id', 'hash', 'str'] = 'str') -> list[T]:
|
|
10
8
|
''' Remove duplicates from the list while keeping the order using ids, hash, or str
|
|
@@ -31,7 +29,7 @@ def unique_list[T](list_to_clean: Iterable[T], method: Literal['id', 'hash', 'st
|
|
|
31
29
|
\t\t>>> unique_list([s1, s2, s1, s1, s3, s2, s3], method="str")
|
|
32
30
|
\t\t[{1, 2, 3}, {2, 3, 4}]
|
|
33
31
|
\t'''
|
|
34
|
-
def at_least_n(iterable: Iterable[T], predicate: Callable[[T], bool], n: int) -> bool:
|
|
32
|
+
def at_least_n[T](iterable: Iterable[T], predicate: Callable[[T], bool], n: int) -> bool:
|
|
35
33
|
""" Return True if at least n elements in iterable satisfy predicate.
|
|
36
34
|
\tIt's like the built-in any() but for at least n matches.
|
|
37
35
|
|
|
@@ -29,7 +29,7 @@ def generate_stubs(
|
|
|
29
29
|
try:
|
|
30
30
|
from mypy.stubgen import main as stubgen_main
|
|
31
31
|
except ImportError as e:
|
|
32
|
-
raise ImportError("mypy is required for
|
|
32
|
+
raise ImportError("mypy is required for generate_stubs function. Please install it via 'pip install mypy'.") from e
|
|
33
33
|
try:
|
|
34
34
|
stubgen_main(["-p", package_name, *extra_args.split()])
|
|
35
35
|
return 0
|
stouputils/ctx.py
CHANGED
|
@@ -20,13 +20,11 @@ import sys
|
|
|
20
20
|
import time
|
|
21
21
|
from collections.abc import Callable
|
|
22
22
|
from contextlib import AbstractAsyncContextManager, AbstractContextManager
|
|
23
|
-
from typing import IO, Any, TextIO
|
|
23
|
+
from typing import IO, Any, TextIO
|
|
24
24
|
|
|
25
25
|
from .io import super_open
|
|
26
26
|
from .print import TeeMultiOutput, debug
|
|
27
27
|
|
|
28
|
-
# Type variable for context managers
|
|
29
|
-
T = TypeVar("T")
|
|
30
28
|
|
|
31
29
|
# Abstract base class for context managers supporting both sync and async usage
|
|
32
30
|
class AbstractBothContextManager[T](AbstractContextManager[T], AbstractAsyncContextManager[T]):
|
stouputils/ctx.pyi
CHANGED
|
@@ -3,9 +3,7 @@ from .io import super_open as super_open
|
|
|
3
3
|
from .print import TeeMultiOutput as TeeMultiOutput, debug as debug
|
|
4
4
|
from collections.abc import Callable as Callable
|
|
5
5
|
from contextlib import AbstractAsyncContextManager, AbstractContextManager
|
|
6
|
-
from typing import Any, IO, TextIO
|
|
7
|
-
|
|
8
|
-
T = TypeVar('T')
|
|
6
|
+
from typing import Any, IO, TextIO
|
|
9
7
|
|
|
10
8
|
class AbstractBothContextManager[T](AbstractContextManager[T], AbstractAsyncContextManager[T], metaclass=abc.ABCMeta):
|
|
11
9
|
""" Abstract base class for context managers that support both synchronous and asynchronous usage. """
|
stouputils/decorators.py
CHANGED
|
@@ -211,7 +211,7 @@ def timeout(
|
|
|
211
211
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
212
212
|
# Check if we can use signal-based timeout (Unix only)
|
|
213
213
|
import os
|
|
214
|
-
use_signal: bool = os.name !=
|
|
214
|
+
use_signal: bool = os.name != "nt" # Not Windows
|
|
215
215
|
|
|
216
216
|
if use_signal:
|
|
217
217
|
try:
|
stouputils/image.py
CHANGED
|
@@ -12,7 +12,7 @@ See stouputils.data_science.data_processing for lots more image processing utili
|
|
|
12
12
|
# Imports
|
|
13
13
|
import os
|
|
14
14
|
from collections.abc import Callable
|
|
15
|
-
from typing import TYPE_CHECKING, Any,
|
|
15
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
16
16
|
|
|
17
17
|
from .io import super_open
|
|
18
18
|
from .print import debug, info
|
|
@@ -22,15 +22,13 @@ if TYPE_CHECKING:
|
|
|
22
22
|
from numpy.typing import NDArray
|
|
23
23
|
from PIL import Image
|
|
24
24
|
|
|
25
|
-
PIL_Image_or_NDArray = TypeVar("PIL_Image_or_NDArray", bound="Image.Image | NDArray[np.number]")
|
|
26
|
-
|
|
27
25
|
# Functions
|
|
28
|
-
def image_resize[
|
|
29
|
-
image:
|
|
26
|
+
def image_resize[T: "Image.Image | NDArray[np.number]"](
|
|
27
|
+
image: T,
|
|
30
28
|
max_result_size: int,
|
|
31
29
|
resampling: "Image.Resampling | None" = None,
|
|
32
30
|
min_or_max: Callable[[int, int], int] = max,
|
|
33
|
-
return_type: type[
|
|
31
|
+
return_type: type[T] | str = "same",
|
|
34
32
|
keep_aspect_ratio: bool = True,
|
|
35
33
|
) -> Any:
|
|
36
34
|
""" Resize an image while preserving its aspect ratio by default.
|
|
@@ -121,11 +119,11 @@ def image_resize[PIL_Image_or_NDArray](
|
|
|
121
119
|
return new_image
|
|
122
120
|
|
|
123
121
|
|
|
124
|
-
def auto_crop[
|
|
125
|
-
image:
|
|
122
|
+
def auto_crop[T: "Image.Image | NDArray[np.number]"](
|
|
123
|
+
image: T,
|
|
126
124
|
mask: "NDArray[np.bool_] | None" = None,
|
|
127
125
|
threshold: int | float | Callable[["NDArray[np.number]"], int | float] | None = None,
|
|
128
|
-
return_type: type[
|
|
126
|
+
return_type: type[T] | str = "same",
|
|
129
127
|
contiguous: bool = True,
|
|
130
128
|
) -> Any:
|
|
131
129
|
""" Automatically crop an image to remove zero or uniform regions.
|
|
@@ -401,7 +399,7 @@ def numpy_to_obj(
|
|
|
401
399
|
|
|
402
400
|
# Apply marching cubes algorithm to extract mesh
|
|
403
401
|
verts, faces, _, _ = cast(
|
|
404
|
-
tuple[NDArray[np.floating], NDArray[np.integer], NDArray[np.floating], NDArray[np.floating]],
|
|
402
|
+
"tuple[NDArray[np.floating], NDArray[np.integer], NDArray[np.floating], NDArray[np.floating]]",
|
|
405
403
|
measure.marching_cubes(volume, level=threshold, step_size=step_size, allow_degenerate=False) # type: ignore
|
|
406
404
|
)
|
|
407
405
|
|
stouputils/image.pyi
CHANGED
|
@@ -3,12 +3,10 @@ from .io import super_open as super_open
|
|
|
3
3
|
from .print import debug as debug, info as info
|
|
4
4
|
from PIL import Image
|
|
5
5
|
from collections.abc import Callable
|
|
6
|
-
from numpy.typing import NDArray
|
|
7
|
-
from typing import Any
|
|
6
|
+
from numpy.typing import NDArray as NDArray
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def image_resize[PIL_Image_or_NDArray](image: PIL_Image_or_NDArray, max_result_size: int, resampling: Image.Resampling | None = None, min_or_max: Callable[[int, int], int] = ..., return_type: type[PIL_Image_or_NDArray] | str = 'same', keep_aspect_ratio: bool = True) -> Any:
|
|
9
|
+
def image_resize[T: Image.Image | NDArray[np.number]](image: T, max_result_size: int, resampling: Image.Resampling | None = None, min_or_max: Callable[[int, int], int] = ..., return_type: type[T] | str = 'same', keep_aspect_ratio: bool = True) -> Any:
|
|
12
10
|
''' Resize an image while preserving its aspect ratio by default.
|
|
13
11
|
\tScales the image so that its largest dimension equals max_result_size.
|
|
14
12
|
|
|
@@ -47,7 +45,7 @@ def image_resize[PIL_Image_or_NDArray](image: PIL_Image_or_NDArray, max_result_s
|
|
|
47
45
|
\t\t>>> image_resize(pil_image, 50, resampling=Image.Resampling.NEAREST).size
|
|
48
46
|
\t\t(50, 25)
|
|
49
47
|
\t'''
|
|
50
|
-
def auto_crop[
|
|
48
|
+
def auto_crop[T: Image.Image | NDArray[np.number]](image: T, mask: NDArray[np.bool_] | None = None, threshold: int | float | Callable[[NDArray[np.number]], int | float] | None = None, return_type: type[T] | str = 'same', contiguous: bool = True) -> Any:
|
|
51
49
|
''' Automatically crop an image to remove zero or uniform regions.
|
|
52
50
|
|
|
53
51
|
\tThis function crops the image to keep only the region where pixels are non-zero
|
stouputils/io.py
CHANGED
|
@@ -11,6 +11,7 @@ This module provides utilities for file management.
|
|
|
11
11
|
- super_open: Open a file with the given mode, creating the directory if it doesn't exist (only if writing)
|
|
12
12
|
- replace_tilde: Replace the "~" by the user's home directory
|
|
13
13
|
- clean_path: Clean the path by replacing backslashes with forward slashes and simplifying the path
|
|
14
|
+
- safe_close: Safely close a file descriptor or file object after flushing, ignoring any exceptions
|
|
14
15
|
|
|
15
16
|
.. image:: https://raw.githubusercontent.com/Stoupy51/stouputils/refs/heads/main/assets/io_module.gif
|
|
16
17
|
:alt: stouputils io examples
|
|
@@ -338,7 +339,7 @@ def super_copy(src: str, dst: str, create_dir: bool = True, symlink: bool = Fals
|
|
|
338
339
|
src (str): The source path
|
|
339
340
|
dst (str): The destination path
|
|
340
341
|
create_dir (bool): Whether to create the directory if it doesn't exist (default: True)
|
|
341
|
-
symlink (bool): Whether to create a symlink instead of copying (Linux only
|
|
342
|
+
symlink (bool): Whether to create a symlink instead of copying (Linux only)
|
|
342
343
|
Returns:
|
|
343
344
|
str: The destination path
|
|
344
345
|
"""
|
|
@@ -491,3 +492,23 @@ def clean_path(file_path: str, trailing_slash: bool = True) -> str:
|
|
|
491
492
|
# Return the cleaned path
|
|
492
493
|
return file_path if file_path != "." else ""
|
|
493
494
|
|
|
495
|
+
def safe_close(file: IO[Any] | int | None) -> None:
|
|
496
|
+
""" Safely close a file object (or file descriptor) after flushing, ignoring any exceptions.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
file (IO[Any] | int | None): The file object or file descriptor to close
|
|
500
|
+
"""
|
|
501
|
+
if isinstance(file, int):
|
|
502
|
+
if file != -1:
|
|
503
|
+
for func in (os.fsync, os.close):
|
|
504
|
+
try:
|
|
505
|
+
func(file)
|
|
506
|
+
except Exception:
|
|
507
|
+
pass
|
|
508
|
+
elif file:
|
|
509
|
+
for func in (file.flush, file.close):
|
|
510
|
+
try:
|
|
511
|
+
func()
|
|
512
|
+
except Exception:
|
|
513
|
+
pass
|
|
514
|
+
|
stouputils/io.pyi
CHANGED
|
@@ -144,7 +144,7 @@ def super_copy(src: str, dst: str, create_dir: bool = True, symlink: bool = Fals
|
|
|
144
144
|
\t\tsrc (str): The source path
|
|
145
145
|
\t\tdst (str): The destination path
|
|
146
146
|
\t\tcreate_dir (bool): Whether to create the directory if it doesn't exist (default: True)
|
|
147
|
-
\t\tsymlink (bool): Whether to create a symlink instead of copying (Linux only
|
|
147
|
+
\t\tsymlink (bool): Whether to create a symlink instead of copying (Linux only)
|
|
148
148
|
\tReturns:
|
|
149
149
|
\t\tstr: The destination path
|
|
150
150
|
\t"""
|
|
@@ -211,3 +211,9 @@ def clean_path(file_path: str, trailing_slash: bool = True) -> str:
|
|
|
211
211
|
\t\t>>> clean_path("C:/folder1\\\\folder2")
|
|
212
212
|
\t\t\'C:/folder1/folder2\'
|
|
213
213
|
\t'''
|
|
214
|
+
def safe_close(file: IO[Any] | int | None) -> None:
|
|
215
|
+
""" Safely close a file object (or file descriptor) after flushing, ignoring any exceptions.
|
|
216
|
+
|
|
217
|
+
\tArgs:
|
|
218
|
+
\t\tfile (IO[Any] | int | None): The file object or file descriptor to close
|
|
219
|
+
\t"""
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
""" Inter-process locks implementing First-In-First-Out (FIFO).
|
|
2
|
+
|
|
3
|
+
Source:
|
|
4
|
+
- https://en.wikipedia.org/wiki/File_locking
|
|
5
|
+
- https://en.wikipedia.org/wiki/Starvation_%28computer_science%29
|
|
6
|
+
- https://en.wikipedia.org/wiki/FIFO_and_LIFO_accounting
|
|
7
|
+
|
|
8
|
+
Provides three classes:
|
|
9
|
+
|
|
10
|
+
- LockFifo: basic cross-process lock using filesystem (POSIX via fcntl, Windows via msvcrt).
|
|
11
|
+
- RLockFifo: reentrant per-(process,thread) lock built on top of LockFifo.
|
|
12
|
+
- RedisLockFifo: distributed lock using redis (optional dependency).
|
|
13
|
+
|
|
14
|
+
Usage
|
|
15
|
+
-----
|
|
16
|
+
>>> import stouputils as stp
|
|
17
|
+
>>> with stp.LockFifo("some_directory/my.lock", timeout=5):
|
|
18
|
+
... pass
|
|
19
|
+
|
|
20
|
+
>>> with stp.RLockFifo("some_directory/my_r.lock", timeout=5):
|
|
21
|
+
... pass
|
|
22
|
+
|
|
23
|
+
>>> def _redis_example():
|
|
24
|
+
... with stp.RedisLockFifo("my_redis_lock", timeout=5):
|
|
25
|
+
... pass
|
|
26
|
+
>>> import os
|
|
27
|
+
>>> if os.name != "nt":
|
|
28
|
+
... _redis_example()
|
|
29
|
+
"""
|
|
30
|
+
# Imports
|
|
31
|
+
from .base import *
|
|
32
|
+
from .queue import *
|
|
33
|
+
from .re_entrant import *
|
|
34
|
+
from .redis_fifo import *
|
|
35
|
+
from .shared import *
|
|
36
|
+
|