stouputils 1.15.0__py3-none-any.whl → 1.16.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 CHANGED
@@ -1,18 +1,7 @@
1
- """ A collection of utility modules designed to simplify and enhance the development process.
2
-
3
- This package provides various tools and utilities for common development tasks including:
4
-
5
- Key Features:
6
- - Continuous delivery utilities (GitHub, PyPI)
7
- - Display and logging utilities (print)
8
- - File and I/O management (io)
9
- - Decorators for common patterns
10
- - Context managers
11
- - Archive and backup tools
12
- - Parallel processing helpers
13
- - Collection utilities
14
- - Doctests utilities
1
+ """ Stouputils is a collection of utility modules designed to simplify and enhance the development process.
2
+ It includes a range of tools for tasks such as execution of doctests, display utilities, decorators, as well as context managers.
15
3
 
4
+ Check the documentation for more details: https://stoupy51.github.io/stouputils/
16
5
  """
17
6
  # Version (handle case where the package is not installed)
18
7
  from importlib.metadata import PackageNotFoundError
@@ -31,6 +20,7 @@ from .image import *
31
20
  from .io import *
32
21
  from .parallel import *
33
22
  from .print import *
23
+ from .typing import *
34
24
  from .version_pkg import *
35
25
 
36
26
  try:
stouputils/__init__.pyi CHANGED
@@ -10,6 +10,7 @@ from .image import *
10
10
  from .io import *
11
11
  from .parallel import *
12
12
  from .print import *
13
+ from .typing import *
13
14
  from .version_pkg import *
14
15
 
15
16
  __version__: str
stouputils/__main__.py CHANGED
@@ -7,12 +7,7 @@ import sys
7
7
 
8
8
  import argcomplete
9
9
 
10
- from .all_doctests import launch_tests
11
- from .archive import archive_cli
12
- from .backup import backup_cli
13
10
  from .decorators import handle_error
14
- from .print import CYAN, GREEN, RESET
15
- from .version_pkg import show_version_cli
16
11
 
17
12
  # Argument Parser Setup for Auto-Completion
18
13
  parser = argparse.ArgumentParser(prog="stouputils", add_help=False)
@@ -29,22 +24,28 @@ def main() -> None:
29
24
 
30
25
  # Print the version of stouputils and its dependencies
31
26
  if second_arg in ("--version", "-v", "version", "show_version"):
27
+ from .version_pkg import show_version_cli
32
28
  return show_version_cli()
33
29
 
34
30
  # Handle "all_doctests" command
35
31
  if second_arg == "all_doctests":
36
- if launch_tests("." if len(sys.argv) == 2 else sys.argv[2]) > 0:
32
+ root_dir: str = "." if len(sys.argv) == 2 else sys.argv[2]
33
+ pattern: str = sys.argv[3] if len(sys.argv) >= 4 else "*"
34
+ from .all_doctests import launch_tests
35
+ if launch_tests(root_dir, pattern=pattern) > 0:
37
36
  sys.exit(1)
38
37
  return
39
38
 
40
39
  # Handle "archive" command
41
40
  if second_arg == "archive":
42
41
  sys.argv.pop(1) # Remove "archive" from argv so archive_cli gets clean arguments
42
+ from .archive import archive_cli
43
43
  return archive_cli()
44
44
 
45
45
  # Handle "backup" command
46
46
  if second_arg == "backup":
47
47
  sys.argv.pop(1) # Remove "backup" from argv so backup_cli gets clean arguments
48
+ from .backup import backup_cli
48
49
  return backup_cli()
49
50
 
50
51
  # Handle "build" command
@@ -64,6 +65,7 @@ def main() -> None:
64
65
  pkg_version = "unknown"
65
66
 
66
67
  # Print help with nice formatting
68
+ from .print import CYAN, GREEN, RESET
67
69
  separator: str = "─" * 60
68
70
  print(f"""
69
71
  {CYAN}{separator}{RESET}
@@ -73,7 +75,7 @@ def main() -> None:
73
75
 
74
76
  {CYAN}Available commands:{RESET}
75
77
  {GREEN}--version, -v{RESET} [pkg] [-t <depth>] Show version information (optionally for a specific package)
76
- {GREEN}all_doctests{RESET} [dir] Run all doctests in the specified directory
78
+ {GREEN}all_doctests{RESET} [dir] [pattern] Run all doctests in the specified directory (optionally filter by pattern)
77
79
  {GREEN}archive{RESET} --help Archive utilities (make, repair)
78
80
  {GREEN}backup{RESET} --help Backup utilities (delta, consolidate, limit)
79
81
  {GREEN}build{RESET} [--no_stubs] [<minor|major>] Build and publish package to PyPI using 'uv' tool (complete routine)
@@ -9,29 +9,26 @@ This module is used to run all the doctests for all the modules in a given direc
9
9
  """
10
10
 
11
11
  # Imports
12
- import importlib
13
- import os
14
- import pkgutil
15
- import sys
16
- from types import ModuleType
17
12
  from typing import TYPE_CHECKING
18
13
 
19
14
  from . import decorators
20
15
  from .decorators import measure_time
21
16
  from .io import clean_path, relative_path
22
- from .print import error, info, progress, warning
17
+ from .print import error, info, warning
23
18
 
24
19
  if TYPE_CHECKING:
25
20
  from doctest import TestResults
21
+ from types import ModuleType
26
22
 
27
23
 
28
24
  # Main program
29
- def launch_tests(root_dir: str, strict: bool = True) -> int:
25
+ def launch_tests(root_dir: str, strict: bool = True, pattern: str = "*") -> int:
30
26
  """ Main function to launch tests for all modules in the given directory.
31
27
 
32
28
  Args:
33
29
  root_dir (str): Root directory to search for modules
34
30
  strict (bool): Modify the force_raise_exception variable to True in the decorators module
31
+ pattern (str): Pattern to filter module names (fnmatch style, e.g., '*typ*', 'io', etc.)
35
32
 
36
33
  Returns:
37
34
  int: The number of failed tests
@@ -62,11 +59,14 @@ def launch_tests(root_dir: str, strict: bool = True) -> int:
62
59
  strict = old_value
63
60
 
64
61
  # Get the path of the directory to check modules from
62
+ import os
65
63
  working_dir: str = clean_path(os.getcwd())
66
64
  root_dir = clean_path(os.path.abspath(root_dir))
67
65
  dir_to_check: str = os.path.dirname(root_dir) if working_dir != root_dir else root_dir
68
66
 
69
67
  # Get all modules from folder
68
+ import pkgutil
69
+ import sys
70
70
  sys.path.insert(0, dir_to_check)
71
71
  modules_file_paths: list[str] = []
72
72
  for directory_path, _, _ in os.walk(root_dir):
@@ -94,10 +94,21 @@ def launch_tests(root_dir: str, strict: bool = True) -> int:
94
94
  if not modules_file_paths:
95
95
  raise ValueError(f"No modules found in '{relative_path(root_dir)}'")
96
96
 
97
+ # Filter modules based on pattern
98
+ if pattern != "*":
99
+ import fnmatch
100
+ modules_file_paths = [
101
+ path for path in modules_file_paths
102
+ if fnmatch.fnmatch(path.split(".")[-1], pattern)
103
+ ]
104
+ if not modules_file_paths:
105
+ raise ValueError(f"No modules matching pattern '{pattern}' found in '{relative_path(root_dir)}'")
106
+
97
107
  # Find longest module path for alignment
98
108
  max_length: int = max(len(path) for path in modules_file_paths)
99
109
 
100
110
  # Dynamically import all modules from iacob package recursively using pkgutil and importlib
111
+ import importlib
101
112
  modules: list[ModuleType] = []
102
113
  separators: list[str] = []
103
114
  for module_path in modules_file_paths:
@@ -143,7 +154,7 @@ def launch_tests(root_dir: str, strict: bool = True) -> int:
143
154
  return total_failed
144
155
 
145
156
 
146
- def test_module_with_progress(module: ModuleType, separator: str) -> "TestResults":
157
+ def test_module_with_progress(module: ModuleType, separator: str) -> TestResults:
147
158
  """ Test a module with testmod and measure the time taken with progress printing.
148
159
 
149
160
  Args:
@@ -152,7 +163,7 @@ def test_module_with_progress(module: ModuleType, separator: str) -> "TestResult
152
163
  Returns:
153
164
  TestResults: The results of the tests
154
165
  """
155
- from doctest import TestResults, testmod
166
+ from doctest import testmod
156
167
  @measure_time(message=f"Testing module '{module.__name__}' {separator}took")
157
168
  def internal() -> TestResults:
158
169
  return testmod(m=module)
@@ -1,16 +1,17 @@
1
1
  from . import decorators as decorators
2
2
  from .decorators import measure_time as measure_time
3
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
4
+ from .print import error as error, info as info, warning as warning
5
5
  from doctest import TestResults as TestResults
6
6
  from types import ModuleType
7
7
 
8
- def launch_tests(root_dir: str, strict: bool = True) -> int:
8
+ def launch_tests(root_dir: str, strict: bool = True, pattern: str = '*') -> int:
9
9
  ''' Main function to launch tests for all modules in the given directory.
10
10
 
11
11
  \tArgs:
12
12
  \t\troot_dir\t\t\t\t(str):\t\t\tRoot directory to search for modules
13
13
  \t\tstrict\t\t\t\t\t(bool):\t\t\tModify the force_raise_exception variable to True in the decorators module
14
+ \t\tpattern\t\t\t\t\t(str):\t\t\tPattern to filter module names (fnmatch style, e.g., \'*typ*\', \'io\', etc.)
14
15
 
15
16
  \tReturns:
16
17
  \t\tint: The number of failed tests
stouputils/collections.py CHANGED
@@ -30,7 +30,7 @@ T = TypeVar("T")
30
30
 
31
31
  # Functions
32
32
  def unique_list[T](list_to_clean: Iterable[T], method: Literal["id", "hash", "str"] = "str") -> list[T]:
33
- """ Remove duplicates from the list while keeping the order using ids (default) or hash or str
33
+ """ Remove duplicates from the list while keeping the order using ids, hash, or str
34
34
 
35
35
  Args:
36
36
  list_to_clean (Iterable[T]): The list to clean
@@ -7,7 +7,7 @@ from typing import Any, Literal, TypeVar
7
7
  T = TypeVar('T')
8
8
 
9
9
  def unique_list[T](list_to_clean: Iterable[T], method: Literal['id', 'hash', 'str'] = 'str') -> list[T]:
10
- ''' Remove duplicates from the list while keeping the order using ids (default) or hash or str
10
+ ''' Remove duplicates from the list while keeping the order using ids, hash, or str
11
11
 
12
12
  \tArgs:
13
13
  \t\tlist_to_clean\t(Iterable[T]):\t\t\t\t\tThe list to clean
@@ -10,6 +10,7 @@
10
10
 
11
11
  # Imports
12
12
  import os
13
+ import subprocess
13
14
  import sys
14
15
  from collections.abc import Callable
15
16
  from typing import Any
@@ -22,17 +23,17 @@ def update_pip_and_required_packages() -> int:
22
23
  """ Update pip and required packages.
23
24
 
24
25
  Returns:
25
- int: Return code of the os.system call.
26
+ int: Return code of the subprocess.run call.
26
27
  """
27
- return os.system(f"{sys.executable} -m pip install --upgrade pip setuptools build twine pkginfo packaging")
28
+ return subprocess.run(f"{sys.executable} -m pip install --upgrade pip setuptools build twine pkginfo packaging", shell=True).returncode
28
29
 
29
30
  def build_package() -> int:
30
31
  """ Build the package.
31
32
 
32
33
  Returns:
33
- int: Return code of the os.system call.
34
+ int: Return code of the subprocess.run call.
34
35
  """
35
- return os.system(f"{sys.executable} -m build")
36
+ return subprocess.run(f"{sys.executable} -m build", shell=True).returncode
36
37
 
37
38
  def upload_package(repository: str, filepath: str) -> int:
38
39
  """ Upload the package to PyPI.
@@ -42,9 +43,9 @@ def upload_package(repository: str, filepath: str) -> int:
42
43
  filepath (str): Path to the file to upload.
43
44
 
44
45
  Returns:
45
- int: Return code of the os.system call.
46
+ int: Return code of the subprocess.run call.
46
47
  """
47
- return os.system(f"{sys.executable} -m twine upload --verbose -r {repository} {filepath}")
48
+ return subprocess.run(f"{sys.executable} -m twine upload --verbose -r {repository} {filepath}", shell=True).returncode
48
49
 
49
50
  @handle_error(message="Error while doing the pypi full routine", error_log=LogLevels.ERROR_TRACEBACK)
50
51
  def pypi_full_routine(
@@ -115,16 +116,16 @@ def pypi_full_routine_using_uv() -> None:
115
116
  # Increment version in pyproject.toml
116
117
  if "--no-bump" not in sys.argv and "--no_bump" not in sys.argv:
117
118
  increment: str = "patch" if sys.argv[-1] not in ("minor", "major") else sys.argv[-1]
118
- if os.system(f"uv version --bump {increment} --frozen") != 0:
119
+ if subprocess.run(f"uv version --bump {increment} --frozen", shell=True).returncode != 0:
119
120
  raise Exception("Error while incrementing version using 'uv version'")
120
121
 
121
122
  # Build the package using 'uv build'
122
123
  import shutil
123
124
  shutil.rmtree("dist", ignore_errors=True)
124
- if os.system(f"{sys.executable} -m uv build") != 0:
125
+ if subprocess.run(f"{sys.executable} -m uv build", shell=True).returncode != 0:
125
126
  raise Exception("Error while building the package using 'uv build'")
126
127
 
127
128
  # Upload the most recent file to PyPI using 'uv publish'
128
- if os.system(f"{sys.executable} -m uv publish") != 0:
129
+ if subprocess.run(f"{sys.executable} -m uv publish", shell=True).returncode != 0:
129
130
  raise Exception("Error while publishing the package using 'uv publish'")
130
131
 
@@ -6,13 +6,13 @@ def update_pip_and_required_packages() -> int:
6
6
  """ Update pip and required packages.
7
7
 
8
8
  \tReturns:
9
- \t\tint: Return code of the os.system call.
9
+ \t\tint: Return code of the subprocess.run call.
10
10
  \t"""
11
11
  def build_package() -> int:
12
12
  """ Build the package.
13
13
 
14
14
  \tReturns:
15
- \t\tint: Return code of the os.system call.
15
+ \t\tint: Return code of the subprocess.run call.
16
16
  \t"""
17
17
  def upload_package(repository: str, filepath: str) -> int:
18
18
  """ Upload the package to PyPI.
@@ -22,7 +22,7 @@ def upload_package(repository: str, filepath: str) -> int:
22
22
  \t\tfilepath (str): Path to the file to upload.
23
23
 
24
24
  \tReturns:
25
- \t\tint: Return code of the os.system call.
25
+ \t\tint: Return code of the subprocess.run call.
26
26
  \t"""
27
27
  def pypi_full_routine(repository: str, dist_directory: str, last_files: int = 1, endswith: str = '.tar.gz', update_all_function: Callable[[], int] = ..., build_package_function: Callable[[], int] = ..., upload_package_function: Callable[[str, str], int] = ...) -> None:
28
28
  ''' Upload the most recent file(s) to PyPI after updating pip and required packages and building the package.
@@ -24,7 +24,7 @@ def generate_stubs(
24
24
  package_name (str): Name of the package to generate stubs for.
25
25
  extra_args (str): Extra arguments to pass to stubgen. Defaults to "--include-docstrings --include-private".
26
26
  Returns:
27
- int: Return code of the os.system call.
27
+ int: 0 if successful, non-zero otherwise.
28
28
  """
29
29
  try:
30
30
  from mypy.stubgen import main as stubgen_main
@@ -10,7 +10,7 @@ def generate_stubs(package_name: str, extra_args: str = '--include-docstrings --
10
10
  \t\tpackage_name (str): Name of the package to generate stubs for.
11
11
  \t\textra_args (str): Extra arguments to pass to stubgen. Defaults to "--include-docstrings --include-private".
12
12
  \tReturns:
13
- \t\tint: Return code of the os.system call.
13
+ \t\tint: 0 if successful, non-zero otherwise.
14
14
  \t'''
15
15
  def clean_stubs_directory(output_directory: str, package_name: str) -> None:
16
16
  """ Clean the stubs directory by deleting all .pyi files.
@@ -2,11 +2,12 @@
2
2
  # Imports
3
3
  import argparse
4
4
  import os
5
+ import subprocess
5
6
  import sys
6
7
 
7
8
  from ...decorators import handle_error, measure_time
8
- from ...print import info
9
9
  from ...parallel import multithreading
10
+ from ...print import info
10
11
  from ..config.get import DataScienceConfig
11
12
  from ..dataset import LOWER_GS
12
13
  from ..models.all import ALL_MODELS, CLASS_MAP
@@ -122,7 +123,7 @@ def exhaustive_process(
122
123
  info(f"Executing command: '{cmd}'")
123
124
  sys.stdout.flush()
124
125
  sys.stderr.flush()
125
- os.system(cmd)
126
+ subprocess.run(cmd, shell=True)
126
127
  multithreading(
127
128
  runner,
128
129
  commands,
stouputils/image.py CHANGED
@@ -28,7 +28,7 @@ PIL_Image_or_NDArray = TypeVar("PIL_Image_or_NDArray", bound="Image.Image | NDAr
28
28
  def image_resize[PIL_Image_or_NDArray](
29
29
  image: PIL_Image_or_NDArray,
30
30
  max_result_size: int,
31
- resampling: "Image.Resampling | None" = None,
31
+ resampling: Image.Resampling | None = None,
32
32
  min_or_max: Callable[[int, int], int] = max,
33
33
  return_type: type[PIL_Image_or_NDArray] | str = "same",
34
34
  keep_aspect_ratio: bool = True,
@@ -37,7 +37,7 @@ def image_resize[PIL_Image_or_NDArray](
37
37
  Scales the image so that its largest dimension equals max_result_size.
38
38
 
39
39
  Args:
40
- image (Image.Image | np.ndarray): The image to resize.
40
+ image (Image.Image | NDArray): The image to resize.
41
41
  max_result_size (int): Maximum size for the largest dimension.
42
42
  resampling (Image.Resampling | None): PIL resampling filter to use (default: Image.Resampling.LANCZOS).
43
43
  min_or_max (Callable): Function to use to get the minimum or maximum of the two ratios.
@@ -84,7 +84,7 @@ def image_resize[PIL_Image_or_NDArray](
84
84
 
85
85
  # Convert numpy array to PIL Image if needed
86
86
  if not original_was_pil:
87
- image = Image.fromarray(image)
87
+ image = Image.fromarray(image) # type: ignore
88
88
 
89
89
  if keep_aspect_ratio:
90
90
 
@@ -123,8 +123,8 @@ def image_resize[PIL_Image_or_NDArray](
123
123
 
124
124
  def auto_crop[PIL_Image_or_NDArray](
125
125
  image: PIL_Image_or_NDArray,
126
- mask: "NDArray[np.bool_] | None" = None,
127
- threshold: int | float | Callable[["NDArray[np.number]"], int | float] | None = None,
126
+ mask: NDArray[np.bool_] | None = None,
127
+ threshold: int | float | Callable[[NDArray[np.number]], int | float] | None = None,
128
128
  return_type: type[PIL_Image_or_NDArray] | str = "same",
129
129
  contiguous: bool = True,
130
130
  ) -> Any:
@@ -263,7 +263,7 @@ def auto_crop[PIL_Image_or_NDArray](
263
263
 
264
264
  def numpy_to_gif(
265
265
  path: str,
266
- array: "NDArray[np.integer | np.floating | np.bool_]",
266
+ array: NDArray[np.integer | np.floating | np.bool_],
267
267
  duration: int = 100,
268
268
  loop: int = 0,
269
269
  mkdir: bool = True,
@@ -344,7 +344,7 @@ def numpy_to_gif(
344
344
 
345
345
  def numpy_to_obj(
346
346
  path: str,
347
- array: "NDArray[np.integer | np.floating | np.bool_]",
347
+ array: NDArray[np.integer | np.floating | np.bool_],
348
348
  threshold: float = 0.5,
349
349
  step_size: int = 1,
350
350
  pad_array: bool = True,
@@ -372,7 +372,6 @@ def numpy_to_obj(
372
372
  """
373
373
  # Imports
374
374
  import numpy as np
375
- from numpy.typing import NDArray
376
375
  from skimage import measure
377
376
 
378
377
  # Assertions
stouputils/image.pyi CHANGED
@@ -13,7 +13,7 @@ def image_resize[PIL_Image_or_NDArray](image: PIL_Image_or_NDArray, max_result_s
13
13
  \tScales the image so that its largest dimension equals max_result_size.
14
14
 
15
15
  \tArgs:
16
- \t\timage (Image.Image | np.ndarray): The image to resize.
16
+ \t\timage (Image.Image | NDArray): The image to resize.
17
17
  \t\tmax_result_size (int): Maximum size for the largest dimension.
18
18
  \t\tresampling (Image.Resampling | None): PIL resampling filter to use (default: Image.Resampling.LANCZOS).
19
19
  \t\tmin_or_max (Callable): Function to use to get the minimum or maximum of the two ratios.
stouputils/parallel.py CHANGED
@@ -7,6 +7,16 @@ This module provides utility functions for parallel processing, such as:
7
7
 
8
8
  I highly encourage you to read the function docstrings to understand when to use each method.
9
9
 
10
+ Priority (nice) mapping for multiprocessing():
11
+
12
+ - Unix-style values from -20 (highest priority) to 19 (lowest priority)
13
+ - Windows automatic mapping:
14
+ * -20 to -10: HIGH_PRIORITY_CLASS
15
+ * -9 to -1: ABOVE_NORMAL_PRIORITY_CLASS
16
+ * 0: NORMAL_PRIORITY_CLASS
17
+ * 1 to 9: BELOW_NORMAL_PRIORITY_CLASS
18
+ * 10 to 19: IDLE_PRIORITY_CLASS
19
+
10
20
  .. image:: https://raw.githubusercontent.com/Stoupy51/stouputils/refs/heads/main/assets/parallel_module.gif
11
21
  :alt: stouputils parallel examples
12
22
  """
@@ -42,6 +52,7 @@ def multiprocessing[T, R](
42
52
  desc: str = "",
43
53
  max_workers: int | float = CPU_COUNT,
44
54
  delay_first_calls: float = 0,
55
+ nice: int | None = None,
45
56
  color: str = MAGENTA,
46
57
  bar_format: str = BAR_FORMAT,
47
58
  ascii: bool = False,
@@ -69,6 +80,11 @@ def multiprocessing[T, R](
69
80
  delay_first_calls (float): Apply i*delay_first_calls seconds delay to the first "max_workers" calls.
70
81
  For instance, the first process will be delayed by 0 seconds, the second by 1 second, etc.
71
82
  (Defaults to 0): This can be useful to avoid functions being called in the same second.
83
+ nice (int | None): Adjust the priority of worker processes (Defaults to None).
84
+ Use Unix-style values: -20 (highest priority) to 19 (lowest priority).
85
+ Positive values reduce priority, negative values increase it.
86
+ Automatically converted to appropriate priority class on Windows.
87
+ If None, no priority adjustment is made.
72
88
  color (str): Color of the progress bar (Defaults to MAGENTA)
73
89
  bar_format (str): Format of the progress bar (Defaults to BAR_FORMAT)
74
90
  ascii (bool): Whether to use ASCII or Unicode characters for the progress bar
@@ -140,13 +156,21 @@ def multiprocessing[T, R](
140
156
  # Do multiprocessing only if there is more than 1 argument and more than 1 CPU
141
157
  if max_workers > 1 and len(args) > 1:
142
158
  def process() -> list[Any]:
159
+ # Wrap function with nice if specified
160
+ if nice is not None:
161
+ wrapped_args = [(nice, func, arg) for arg in args]
162
+ wrapped_func = _nice_wrapper
163
+ else:
164
+ wrapped_args = args
165
+ wrapped_func = func
166
+
143
167
  if verbose:
144
168
  return list(process_map(
145
- func, args, max_workers=max_workers, chunksize=chunksize, desc=desc, bar_format=bar_format, ascii=ascii, **tqdm_kwargs
169
+ wrapped_func, wrapped_args, max_workers=max_workers, chunksize=chunksize, desc=desc, bar_format=bar_format, ascii=ascii, **tqdm_kwargs
146
170
  )) # type: ignore
147
171
  else:
148
172
  with Pool(max_workers) as pool:
149
- return list(pool.map(func, args, chunksize=chunksize)) # type: ignore
173
+ return list(pool.map(wrapped_func, wrapped_args, chunksize=chunksize)) # type: ignore
150
174
  try:
151
175
  return process()
152
176
  except RuntimeError as e:
@@ -382,6 +406,53 @@ def run_in_subprocess[R](
382
406
  raise RuntimeError("Subprocess did not return any result") from e
383
407
 
384
408
 
409
+ # "Private" function to wrap function execution with nice priority (must be at module level for pickling)
410
+ def _nice_wrapper[T, R](args: tuple[int, Callable[[T], R], T]) -> R:
411
+ """ Wrapper that applies nice priority then executes the function.
412
+
413
+ Args:
414
+ args (tuple): Tuple containing (nice_value, func, arg)
415
+
416
+ Returns:
417
+ R: Result of the function execution
418
+ """
419
+ nice_value, func, arg = args
420
+ _set_process_priority(nice_value)
421
+ return func(arg)
422
+
423
+ # "Private" function to set process priority (must be at module level for pickling on Windows)
424
+ def _set_process_priority(nice_value: int) -> None:
425
+ """ Set the priority of the current process.
426
+
427
+ Args:
428
+ nice_value (int): Unix-style priority value (-20 to 19)
429
+ """
430
+ try:
431
+ import sys
432
+ if sys.platform == "win32":
433
+ # Map Unix nice values to Windows priority classes
434
+ # -20 to -10: HIGH, -9 to -1: ABOVE_NORMAL, 0: NORMAL, 1-9: BELOW_NORMAL, 10-19: IDLE
435
+ import ctypes
436
+ # Windows priority class constants
437
+ if nice_value <= -10:
438
+ priority = 0x00000080 # HIGH_PRIORITY_CLASS
439
+ elif nice_value < 0:
440
+ priority = 0x00008000 # ABOVE_NORMAL_PRIORITY_CLASS
441
+ elif nice_value == 0:
442
+ priority = 0x00000020 # NORMAL_PRIORITY_CLASS
443
+ elif nice_value < 10:
444
+ priority = 0x00004000 # BELOW_NORMAL_PRIORITY_CLASS
445
+ else:
446
+ priority = 0x00000040 # IDLE_PRIORITY_CLASS
447
+ kernel32 = ctypes.windll.kernel32
448
+ handle = kernel32.GetCurrentProcess()
449
+ kernel32.SetPriorityClass(handle, priority)
450
+ else:
451
+ # Unix-like systems
452
+ os.nice(nice_value)
453
+ except Exception:
454
+ pass # Silently ignore if we can't set priority
455
+
385
456
  # "Private" function for subprocess wrapper (must be at module level for pickling on Windows)
386
457
  def _subprocess_wrapper[R](
387
458
  result_queue: Any,
stouputils/parallel.pyi CHANGED
@@ -10,7 +10,7 @@ CPU_COUNT: int
10
10
  T = TypeVar('T')
11
11
  R = TypeVar('R')
12
12
 
13
- def multiprocessing[T, R](func: Callable[..., R] | list[Callable[..., R]], args: Iterable[T], use_starmap: bool = False, chunksize: int = 1, desc: str = '', max_workers: int | float = ..., delay_first_calls: float = 0, color: str = ..., bar_format: str = ..., ascii: bool = False, smooth_tqdm: bool = True, **tqdm_kwargs: Any) -> list[R]:
13
+ def multiprocessing[T, R](func: Callable[..., R] | list[Callable[..., R]], args: Iterable[T], use_starmap: bool = False, chunksize: int = 1, desc: str = '', max_workers: int | float = ..., delay_first_calls: float = 0, nice: int | None = None, color: str = ..., bar_format: str = ..., ascii: bool = False, smooth_tqdm: bool = True, **tqdm_kwargs: Any) -> list[R]:
14
14
  ''' Method to execute a function in parallel using multiprocessing
15
15
 
16
16
  \t- For CPU-bound operations where the GIL (Global Interpreter Lock) is a bottleneck.
@@ -32,6 +32,11 @@ def multiprocessing[T, R](func: Callable[..., R] | list[Callable[..., R]], args:
32
32
  \t\tdelay_first_calls\t(float):\t\t\tApply i*delay_first_calls seconds delay to the first "max_workers" calls.
33
33
  \t\t\tFor instance, the first process will be delayed by 0 seconds, the second by 1 second, etc.
34
34
  \t\t\t(Defaults to 0): This can be useful to avoid functions being called in the same second.
35
+ \t\tnice\t\t\t\t(int | None):\t\tAdjust the priority of worker processes (Defaults to None).
36
+ \t\t\tUse Unix-style values: -20 (highest priority) to 19 (lowest priority).
37
+ \t\t\tPositive values reduce priority, negative values increase it.
38
+ \t\t\tAutomatically converted to appropriate priority class on Windows.
39
+ \t\t\tIf None, no priority adjustment is made.
35
40
  \t\tcolor\t\t\t\t(str):\t\t\t\tColor of the progress bar (Defaults to MAGENTA)
36
41
  \t\tbar_format\t\t\t(str):\t\t\t\tFormat of the progress bar (Defaults to BAR_FORMAT)
37
42
  \t\tascii\t\t\t\t(bool):\t\t\t\tWhether to use ASCII or Unicode characters for the progress bar
@@ -169,6 +174,21 @@ def run_in_subprocess[R](func: Callable[..., R], *args: Any, timeout: float | No
169
174
  \t\t\t> # With timeout to prevent hanging
170
175
  \t\t\t> run_in_subprocess(some_gpu_func, data, timeout=300.0)
171
176
  \t'''
177
+ def _nice_wrapper[T, R](args: tuple[int, Callable[[T], R], T]) -> R:
178
+ """ Wrapper that applies nice priority then executes the function.
179
+
180
+ \tArgs:
181
+ \t\targs (tuple): Tuple containing (nice_value, func, arg)
182
+
183
+ \tReturns:
184
+ \t\tR: Result of the function execution
185
+ \t"""
186
+ def _set_process_priority(nice_value: int) -> None:
187
+ """ Set the priority of the current process.
188
+
189
+ \tArgs:
190
+ \t\tnice_value (int): Unix-style priority value (-20 to 19)
191
+ \t"""
172
192
  def _subprocess_wrapper[R](result_queue: Any, func: Callable[..., R], args: tuple[Any, ...], kwargs: dict[str, Any]) -> None:
173
193
  """ Wrapper function to execute the target function and store the result in the queue.
174
194
 
stouputils/print.py CHANGED
@@ -36,7 +36,8 @@ T = TypeVar("T")
36
36
 
37
37
  # Enable colors on Windows 10 terminal if applicable
38
38
  if os.name == "nt":
39
- os.system("color")
39
+ import subprocess
40
+ subprocess.run("color", shell=True)
40
41
 
41
42
  # Print functions
42
43
  previous_args_kwards: tuple[Any, Any] = ((), {})
stouputils/typing.py ADDED
@@ -0,0 +1,71 @@
1
+ """
2
+ This module provides utilities for typing enhancements such as JSON type aliases:
3
+ - JsonDict
4
+ - JsonList
5
+ - JsonMap
6
+ - MutJsonMap
7
+ - IterAny
8
+ """
9
+
10
+ # Imports
11
+ from collections.abc import Iterable, Mapping, MutableMapping
12
+ from dataclasses import asdict, is_dataclass
13
+ from typing import Any, cast
14
+
15
+ # Typing aliases
16
+ JsonDict = dict[str, Any]
17
+ """ A type alias for JSON dictionaries """
18
+ JsonList = list[Any]
19
+ """ A type alias for JSON lists """
20
+ JsonMap = Mapping[str, Any]
21
+ """ A type alias for JSON mapping """
22
+ MutJsonMap = MutableMapping[str, Any]
23
+ """ A type alias for mutable JSON mapping """
24
+ IterAny = Iterable[Any]
25
+ """ A type alias for iterable of any type """
26
+
27
+
28
+ ## Utility functions
29
+ def convert_to_serializable(obj: Any) -> Any:
30
+ """ Recursively convert objects to JSON-serializable forms.
31
+
32
+ Objects with a `to_dict()` or `asdict()` method are converted to their dictionary representation.
33
+ Dictionaries and lists are recursively processed.
34
+
35
+ Can also be used to convert nested structures containing custom objects,
36
+ such as defaultdict, dataclasses, or other user-defined types.
37
+
38
+ Args:
39
+ obj (Any): The object to convert
40
+ Returns:
41
+ Any: The JSON-serializable version of the object
42
+ Examples:
43
+ >>> from typing import defaultdict
44
+ >>> my_dict = defaultdict(lambda: defaultdict(int))
45
+ >>> my_dict['a']['b'] += 6
46
+ >>> my_dict['c']['d'] = 4
47
+ >>> my_dict['a']
48
+ defaultdict(<class 'int'>, {'b': 6})
49
+ >>> my_dict['c']
50
+ defaultdict(<class 'int'>, {'d': 4})
51
+ >>> convert_to_serializable(my_dict)
52
+ {'a': {'b': 6}, 'c': {'d': 4}}
53
+
54
+ >>> from dataclasses import dataclass
55
+ >>> @dataclass
56
+ ... class Point:
57
+ ... x: int
58
+ ... y: int
59
+ >>> convert_to_serializable(Point(3, 4))
60
+ {'x': 3, 'y': 4}
61
+ """
62
+ if hasattr(obj, "to_dict"):
63
+ return obj.to_dict()
64
+ elif is_dataclass(obj):
65
+ return asdict(obj) # pyright: ignore[reportArgumentType]
66
+ elif isinstance(obj, dict | Mapping | MutableMapping):
67
+ return {k: convert_to_serializable(v) for k, v in cast(JsonDict, obj).items()}
68
+ elif isinstance(obj, Iterable) and not isinstance(obj, (str, bytes)):
69
+ return [convert_to_serializable(item) for item in cast(IterAny, obj)]
70
+ return obj
71
+
stouputils/typing.pyi ADDED
@@ -0,0 +1,42 @@
1
+ from collections.abc import Iterable, Mapping, MutableMapping
2
+ from typing import Any
3
+
4
+ JsonDict = dict[str, Any]
5
+ JsonList = list[Any]
6
+ JsonMap = Mapping[str, Any]
7
+ MutJsonMap = MutableMapping[str, Any]
8
+ IterAny = Iterable[Any]
9
+
10
+ def convert_to_serializable(obj: Any) -> Any:
11
+ """ Recursively convert objects to JSON-serializable forms.
12
+
13
+ \tObjects with a `to_dict()` or `asdict()` method are converted to their dictionary representation.
14
+ \tDictionaries and lists are recursively processed.
15
+
16
+ \tCan also be used to convert nested structures containing custom objects,
17
+ \tsuch as defaultdict, dataclasses, or other user-defined types.
18
+
19
+ \tArgs:
20
+ \t\tobj (Any): The object to convert
21
+ \tReturns:
22
+ \t\tAny: The JSON-serializable version of the object
23
+ \tExamples:
24
+ \t\t>>> from typing import defaultdict
25
+ \t\t>>> my_dict = defaultdict(lambda: defaultdict(int))
26
+ \t\t>>> my_dict['a']['b'] += 6
27
+ \t\t>>> my_dict['c']['d'] = 4
28
+ \t\t>>> my_dict['a']
29
+ \t\tdefaultdict(<class 'int'>, {'b': 6})
30
+ \t\t>>> my_dict['c']
31
+ \t\tdefaultdict(<class 'int'>, {'d': 4})
32
+ \t\t>>> convert_to_serializable(my_dict)
33
+ \t\t{'a': {'b': 6}, 'c': {'d': 4}}
34
+
35
+ \t\t>>> from dataclasses import dataclass
36
+ \t\t>>> @dataclass
37
+ \t\t... class Point:
38
+ \t\t... x: int
39
+ \t\t... y: int
40
+ \t\t>>> convert_to_serializable(Point(3, 4))
41
+ \t\t{'x': 3, 'y': 4}
42
+ \t"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stouputils
3
- Version: 1.15.0
3
+ Version: 1.16.0
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
  Keywords: utilities,tools,helpers,development,python
6
6
  Author: Stoupy51
@@ -94,6 +94,7 @@ Start now by installing the package: `pip install stouputils`.<br>
94
94
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.parallel.html">parallel.py</a> <span class="comment"># 🔀 Utility functions for parallel processing <span class="paren">(multiprocessing, multithreading, run_in_subprocess)</span></span>
95
95
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.image.html">image.py</a> <span class="comment"># 🖼️ Little utilities for image processing <span class="paren">(image_resize, auto_crop, numpy_to_gif, numpy_to_obj)</span></span>
96
96
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.collections.html">collections.py</a> <span class="comment"># 🧰 Utilities for collection manipulation <span class="paren">(unique_list, sort_dict_keys, upsert_in_dataframe, array_to_disk)</span></span>
97
+ ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.typing.html">typing.py</a> <span class="comment"># 📝 Utilities for typing enhancements <span class="paren">(IterAny, JsonDict, JsonList, ..., convert_to_serializable)</span></span>
97
98
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.all_doctests.html">all_doctests.py</a> <span class="comment"># ✅ Run all doctests for all modules in a given directory <span class="paren">(launch_tests, test_module_with_progress)</span></span>
98
99
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.backup.html">backup.py</a> <span class="comment"># 💾 Utilities for backup management <span class="paren">(delta backup, consolidate)</span></span>
99
100
  ├── <a href="https://stoupy51.github.io/stouputils/latest/modules/stouputils.archive.html">archive.py</a> <span class="comment"># 📦 Functions for creating and managing archives</span>
@@ -1,10 +1,10 @@
1
- stouputils/__init__.py,sha256=KMJoy8FCiiiXJ53QfgU3rz7AY7AJ_j5kP3j24JuznC0,1136
2
- stouputils/__init__.pyi,sha256=J8LeijIkWrTdGlevNR8dlGlgYg-Dh_MvGjp2EsPZ8UM,351
3
- stouputils/__main__.py,sha256=sJSncuTWua7jA9pTw4BUoDzNUUExZKeyATbmY4lhl0E,2764
1
+ stouputils/__init__.py,sha256=w3WHJy3nt28l6WAtlZz6WRiyHhKy9mw66L6SJJXikW8,996
2
+ stouputils/__init__.pyi,sha256=as5qRu9FfJdJwb_DheDTq6hA5y4UfIFgPwItNfPHMwU,374
3
+ stouputils/__main__.py,sha256=MA3jjc1yL8_0Z9oB55BMqrFq1ot_-e5rNqSxFQGsMzs,2910
4
4
  stouputils/_deprecated.py,sha256=Bcq6YjdM9Rk9Vq-WMhc_tuEbPORX6U8HAJ9Vh-VIWTA,1478
5
5
  stouputils/_deprecated.pyi,sha256=6-8YsftJd2fRAdBLsysc6jf-uA8V2wiqkiFAbdfWfJQ,664
6
- stouputils/all_doctests.py,sha256=1bGGUg80nvLBY3wrPkFrkcuQRjFTWmTpHZtai9X-vnY,5891
7
- stouputils/all_doctests.pyi,sha256=8JD8qn7neYuR0PolabWxX6id1dNEvQDrvOhMS2aYhTM,1907
6
+ stouputils/all_doctests.py,sha256=317v-B4kqf_8lkrw6ul1ZGBgZ75CYIqoDn7kG5k8qyc,6322
7
+ stouputils/all_doctests.pyi,sha256=R3FRKaQv3sTZbxLvvsChHZZKygVMhmL6pqrYYLqvZCg,2017
8
8
  stouputils/applications/__init__.py,sha256=dbjwZt8PZF043KoJSItqCpH32FtRxN5sgV-8Q2b1l10,457
9
9
  stouputils/applications/__init__.pyi,sha256=DTYq2Uqq1uLzCMkFByjRqdtREA-9SaQnp4QpgmCEPFg,56
10
10
  stouputils/applications/automatic_docs.py,sha256=_6XbCuVi2EiSdkiPZ7XHr5mUGh2ZORev8Vd0tJDb0ug,20561
@@ -21,20 +21,20 @@ stouputils/archive.py,sha256=uDrPFxbY_C8SwUZRH4FWnYSoJKkFWynCx751zP9AHaY,12144
21
21
  stouputils/archive.pyi,sha256=Z2BbQAiErRYntv53QC9uf_XPw3tx3Oy73wB0Bbil11c,3246
22
22
  stouputils/backup.py,sha256=AE5WKMLiyk0VkRUfhmNfO2EUeUbZY5GTFVIuI5z7axA,20947
23
23
  stouputils/backup.pyi,sha256=-SLVykkR5U8479T84zjNPVBNnV193s0zyWjathY2DDA,4923
24
- stouputils/collections.py,sha256=5u904s_osO03-drmqPXtFZUlcweDatEQmY-dLUrnNX0,8849
25
- stouputils/collections.pyi,sha256=mKIBV4K7mm-PTvtoYi_cVOAGjW7bo3iIASqosSXFUzE,3519
24
+ stouputils/collections.py,sha256=Zi2OmlPmUPx-isZZpF9xYWU52OtzNqvSHGYM5use0AA,8838
25
+ stouputils/collections.pyi,sha256=J6Sb-8oSVeD1K-j5zNBZo_cP5Bi5NDh2qN4n4QBJIZE,3508
26
26
  stouputils/continuous_delivery/__init__.py,sha256=JqPww29xZ-pp6OJDGhUj2dxyV9rgTTMUz0YDDVr9RaA,731
27
27
  stouputils/continuous_delivery/__init__.pyi,sha256=_Sz2D10n1CDEyY8qDFwXNKdr01HVxanY4qdq9aN19cc,117
28
28
  stouputils/continuous_delivery/cd_utils.py,sha256=fkaHk2V3j66uFAUsM2c_UddNhXW2KAQcrh7jVsH79pU,8594
29
29
  stouputils/continuous_delivery/cd_utils.pyi,sha256=nxTLQydVOSVIix88dRtBXjMrUPpI5ftiQYbLI_nMByQ,4848
30
30
  stouputils/continuous_delivery/github.py,sha256=Iva2XNm60Th78P_evnhCJHn0Q9-06udPlOZAxtZB5vw,19464
31
31
  stouputils/continuous_delivery/github.pyi,sha256=RHRsSroEsT0I1qeuq-Wg0JLdEEDttLrzgHZPVRtLZ0Q,6641
32
- stouputils/continuous_delivery/pypi.py,sha256=v7EVkF36mi1VtGiSRd-H4k9DQsdMmOnRYDgjdnCezOU,5322
33
- stouputils/continuous_delivery/pypi.pyi,sha256=fRAu8ocLNpEN6dhUTMuFxlmRgt3-LRjKPOJjFlUPrJ4,2463
32
+ stouputils/continuous_delivery/pypi.py,sha256=H19RwvJN6QS-qcqvjtIaSLfZTA398jXu-SgSkvkaj0g,5524
33
+ stouputils/continuous_delivery/pypi.pyi,sha256=3zFRz3L_gF_JuSe1J3RZAKTVsFMFiqEdCJbwHRYBj7g,2478
34
34
  stouputils/continuous_delivery/pyproject.py,sha256=olD3QqzLfCLnTBw8IkSKSLBPWyeMv6uS7A0yGdFuIvQ,4802
35
35
  stouputils/continuous_delivery/pyproject.pyi,sha256=bMWwqyG0Auo46dt-dWGePQ9yJ8rSrgb7mnJTfbiS3TQ,2053
36
- stouputils/continuous_delivery/stubs.py,sha256=xUAcP21Y03PLEr7X6LrIBMvPeLI8Rp-EyaTLxocA0C4,3512
37
- stouputils/continuous_delivery/stubs.pyi,sha256=sLZypdz1oGoymQIRPez50rnH8TQhvEIx6A7xUdGtnys,2390
36
+ stouputils/continuous_delivery/stubs.py,sha256=N0qPBORNYmGIhoY0h40JVvNIS9CS_ixHlxNWQN1bPRE,3514
37
+ stouputils/continuous_delivery/stubs.pyi,sha256=UrL2dowLC6hsiKe-gfWeVCudz7tKmt4rsiCL9gsBLv4,2392
38
38
  stouputils/ctx.py,sha256=KVVDmL3pAPX2WM_QzjsmctbG-YfjJ-4aWBSoI7eU_ws,15586
39
39
  stouputils/ctx.pyi,sha256=-7AJwD9bKzKBFsYlgyULPznstq3LvXRXe2r_2at72FI,9799
40
40
  stouputils/data_science/config/get.py,sha256=smdWcu5bBlY38WGtC3GzIF2el-gpvSlDMRNsypmr0JM,1773
@@ -105,14 +105,14 @@ stouputils/data_science/models/model_interface.py,sha256=om1hnEYHTILfLJRcoTDhR7R
105
105
  stouputils/data_science/models/sandbox.py,sha256=ZeuoXNHnVvMlm6umCgTl2Ss0zyQSlxFEV9xJb3ET1Qw,4269
106
106
  stouputils/data_science/range_tuple.py,sha256=5f5PQcwENZEMV0O6U5IpZ2_ylNMB_graDyv-wxrDUhk,6908
107
107
  stouputils/data_science/scripts/augment_dataset.py,sha256=zGcQ2uSn_DO570NIFEs2DUc_d5uvWxLfY-RavjdO3aU,3469
108
- stouputils/data_science/scripts/exhaustive_process.py,sha256=Ty2lHBZBweWxH6smpjoUEqpGz6JmMUO_oaNZO7d-gtQ,5483
108
+ stouputils/data_science/scripts/exhaustive_process.py,sha256=Dc5gceIlIiP8U0m1qt3zpJEZAwQ7617JBAYWitdhjJI,5519
109
109
  stouputils/data_science/scripts/preprocess_dataset.py,sha256=OLC2KjEtSMeyHHPpNOATfNDuq0lZ09utKhsuzBA4MN4,2929
110
110
  stouputils/data_science/scripts/routine.py,sha256=FkTLzmcdm_qUp69D-dPAKJm2RfXZZLtPgje6lEopu2I,7662
111
111
  stouputils/data_science/utils.py,sha256=HFXI2RQZ53RbBOn_4Act2bi0z4xQlTtsuR5Am80v9JU,11084
112
112
  stouputils/decorators.py,sha256=miZ8r2g8VhmQs2_knkKuUagdQabriZe7w0fCOEB69Nw,21838
113
113
  stouputils/decorators.pyi,sha256=vbPRsvox4dotqcln3StgE6iZ1cWCOeAn56M9zMpdw2U,10948
114
- stouputils/image.py,sha256=NtduEVzgbCuZhDRpDZHGTW7-wTs7MqoxUwSQcipvb08,16633
115
- stouputils/image.pyi,sha256=Dkf64KmXJTAEcbtYDHFZ1kqEHqOf2FgJ2Z2BlJgp4fU,8455
114
+ stouputils/image.py,sha256=uGBweT-60JEn4SK5nVAxqiVI4Ijh4YyG_d5uXjXgtCY,16603
115
+ stouputils/image.pyi,sha256=B7aypF1kHsEHHzwTnbcINLam32H4iJJBRxsegwt2n78,8455
116
116
  stouputils/installer/__init__.py,sha256=DBwI9w3xvw0NR_jDMxmURwPi1F79kPLe7EuNjmrxW_U,502
117
117
  stouputils/installer/__init__.pyi,sha256=ZB-8frAUOW-0pCEJL-e2AdbFodivv46v3EBYwEXCxRo,117
118
118
  stouputils/installer/common.py,sha256=UJr5u02h4LQZQdkmVOkJ3vvW_0-ROGgVMMh0PNoVS1A,2209
@@ -127,14 +127,16 @@ stouputils/installer/windows.py,sha256=r2AIuoyAmtMEuoCtQBH9GWQWI-JUT2J9zoH28j9ru
127
127
  stouputils/installer/windows.pyi,sha256=tHogIFhPVDQS0I10liLkAxnpaFFAvmFtEVMpPIae5LU,1616
128
128
  stouputils/io.py,sha256=XG2cReP8wzmoe0LyMtUqvEqixiHehPvXW23h5hBf_Pw,17202
129
129
  stouputils/io.pyi,sha256=TCBTVEWUkI3dO_jWI9oPMF9SbnT1yLzFChE551JPbSY,9076
130
- stouputils/parallel.py,sha256=_o96klxFYgDPyxCeqFp5qNOtJhhXHQYmFIfqbJYnxko,19061
131
- stouputils/parallel.pyi,sha256=ug9I-Ni2q9cwwByXERQuxW-UM3rqw3dCiurnJjOWUpI,11576
132
- stouputils/print.py,sha256=crcvgJO-NCbQ6-C3Prsxvsn8E9mPKI186t_xcQ6O2Uo,24527
130
+ stouputils/parallel.py,sha256=CJUhoB270QT6XXpg0fHOlJdwbvoFd6qFQhCAZDFZng4,21648
131
+ stouputils/parallel.pyi,sha256=BTAtl4TFr71LgV3nVBY-yjpxUxlt9m6O2q82tNFHlbE,12420
132
+ stouputils/print.py,sha256=q-qSkUGeyn-lBMU5GU36ccm_dTmWS2i_RMxMYUhncfY,24564
133
133
  stouputils/print.pyi,sha256=SRAAdObriW_LPcqvDGrCpjfGLrswRhIyJmCvC9_3OpM,10232
134
134
  stouputils/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
135
+ stouputils/typing.py,sha256=TwvxrvxhBRkyHkoOpfyXebN13M3xJb8MAjKXiNIWjew,2205
136
+ stouputils/typing.pyi,sha256=U2UmFZausMYpnsUQROQE2JOwHcjx2hKV0rJuOdR57Ew,1341
135
137
  stouputils/version_pkg.py,sha256=Jsp-s03L14DkiZ94vQgrlQmaxApfn9DC8M_nzT1SJLk,7014
136
138
  stouputils/version_pkg.pyi,sha256=QPvqp1U3QA-9C_CC1dT9Vahv1hXEhstbM7x5uzMZSsQ,755
137
- stouputils-1.15.0.dist-info/WHEEL,sha256=XjEbIc5-wIORjWaafhI6vBtlxDBp7S9KiujWF1EM7Ak,79
138
- stouputils-1.15.0.dist-info/entry_points.txt,sha256=tx0z9VOnE-sfkmbFbA93zaBMzV3XSsKEJa_BWIqUzxw,57
139
- stouputils-1.15.0.dist-info/METADATA,sha256=1FFyPDRUgvHlhXrDArmApMkI6nwy3rDZbqWWKWlPQ1Q,13615
140
- stouputils-1.15.0.dist-info/RECORD,,
139
+ stouputils-1.16.0.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
140
+ stouputils-1.16.0.dist-info/entry_points.txt,sha256=tx0z9VOnE-sfkmbFbA93zaBMzV3XSsKEJa_BWIqUzxw,57
141
+ stouputils-1.16.0.dist-info/METADATA,sha256=cSM0j3GOs_HHAfqbLFyDyRo8khriZLWyL__71W7JFZE,13890
142
+ stouputils-1.16.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.25
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any