stouputils 1.18.1__py3-none-any.whl → 1.18.3__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/io.py CHANGED
@@ -492,7 +492,7 @@ def clean_path(file_path: str, trailing_slash: bool = True) -> str:
492
492
  # Return the cleaned path
493
493
  return file_path if file_path != "." else ""
494
494
 
495
- def safe_close(file: IO[Any] | int | None) -> None:
495
+ def safe_close(file: IO[Any] | int | Any | None) -> None:
496
496
  """ Safely close a file object (or file descriptor) after flushing, ignoring any exceptions.
497
497
 
498
498
  Args:
@@ -506,9 +506,9 @@ def safe_close(file: IO[Any] | int | None) -> None:
506
506
  except Exception:
507
507
  pass
508
508
  elif file:
509
- for func in (file.flush, file.close):
509
+ for func in ("flush", "close"):
510
510
  try:
511
- func()
511
+ getattr(file, func)()
512
512
  except Exception:
513
513
  pass
514
514
 
stouputils/io.pyi CHANGED
@@ -211,7 +211,7 @@ 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:
214
+ def safe_close(file: IO[Any] | int | Any | None) -> None:
215
215
  """ Safely close a file object (or file descriptor) after flushing, ignoring any exceptions.
216
216
 
217
217
  \tArgs:
@@ -65,8 +65,7 @@ class CaptureOutput:
65
65
 
66
66
  def parent_close_write(self) -> None:
67
67
  """ Close the parent's copy of the write end; the child's copy remains. """
68
- safe_close(self.write_fd)
69
- self.write_conn.close()
68
+ safe_close(self.write_conn)
70
69
  self.write_fd = -1 # Prevent accidental reuse
71
70
 
72
71
  def start_listener(self) -> None:
@@ -110,8 +109,7 @@ class CaptureOutput:
110
109
  if len(buffer) > self.chunk_size * 4:
111
110
  _handle_buffer()
112
111
  finally:
113
- safe_close(self.read_fd)
114
- self.read_conn.close()
112
+ safe_close(self.read_conn)
115
113
  self.read_fd = -1
116
114
  self._thread = None # Mark thread as stopped so callers don't block unnecessarily
117
115
 
@@ -123,8 +121,9 @@ class CaptureOutput:
123
121
  def join_listener(self, timeout: float | None = None) -> None:
124
122
  """ Wait for the listener thread to finish (until EOF). """
125
123
  if self._thread is None:
126
- safe_close(self.read_fd)
127
- return self.read_conn.close()
124
+ safe_close(self.read_conn)
125
+ self.read_fd = -1
126
+ return
128
127
  self._thread.join(timeout)
129
128
 
130
129
  # If thread finished, ensure read fd is closed and clear thread
@@ -4,9 +4,20 @@ import time
4
4
  from collections.abc import Callable
5
5
  from typing import Any
6
6
 
7
+ from ..typing import JsonDict
7
8
  from .capturer import CaptureOutput
8
9
 
9
10
 
11
+ class RemoteSubprocessError(RuntimeError):
12
+ """ Raised in the parent when the child raised an exception - contains the child's formatted traceback. """
13
+ def __init__(self, exc_type: str, exc_repr: str, traceback_str: str):
14
+ msg = f"Exception in subprocess ({exc_type}): {exc_repr}\n\nRemote traceback:\n{traceback_str}"
15
+ super().__init__(msg)
16
+ self.remote_type = exc_type
17
+ self.remote_repr = exc_repr
18
+ self.remote_traceback = traceback_str
19
+
20
+
10
21
  def run_in_subprocess[R](
11
22
  func: Callable[..., R],
12
23
  *args: Any,
@@ -37,7 +48,8 @@ def run_in_subprocess[R](
37
48
  R: The return value of the function.
38
49
 
39
50
  Raises:
40
- RuntimeError: If the subprocess exits with a non-zero exit code or times out.
51
+ RemoteSubprocessError: If the child raised an exception - contains the child's formatted traceback.
52
+ RuntimeError: If the subprocess exits with a non-zero exit code or did not return a result.
41
53
  TimeoutError: If the subprocess exceeds the specified timeout.
42
54
 
43
55
  Examples:
@@ -66,7 +78,7 @@ def run_in_subprocess[R](
66
78
  from multiprocessing import Queue
67
79
 
68
80
  # Create a queue to get the result from the subprocess (only if we need to wait)
69
- result_queue: Queue[R | Exception] | None = None if no_join else Queue()
81
+ result_queue: Queue[JsonDict] | None = None if no_join else Queue()
70
82
 
71
83
  # Optionally setup output capture pipe and listener
72
84
  capturer: CaptureOutput | None = None
@@ -105,23 +117,22 @@ def run_in_subprocess[R](
105
117
  process.join()
106
118
  raise TimeoutError(f"Subprocess exceeded timeout of {timeout} seconds and was terminated")
107
119
 
108
- # Check exit code
120
+ # Retrieve the payload if present
121
+ result_payload: JsonDict | None = result_queue.get_nowait() if not result_queue.empty() else None
122
+
123
+ # If the child sent a structured exception, raise it with the formatted traceback
124
+ if isinstance(result_payload, dict):
125
+ if result_payload.pop("ok", False) is False:
126
+ raise RemoteSubprocessError(**result_payload)
127
+ else:
128
+ return result_payload["result"]
129
+
130
+ # Raise an error according to the exit code presence
109
131
  if process.exitcode != 0:
110
- # Try to get any exception from the queue (non-blocking)
111
- if not result_queue.empty():
112
- result_or_exception = result_queue.get_nowait()
113
- if isinstance(result_or_exception, Exception):
114
- raise result_or_exception
115
132
  raise RuntimeError(f"Subprocess failed with exit code {process.exitcode}")
133
+ raise RuntimeError("Subprocess did not return any result")
116
134
 
117
- # Retrieve the result
118
- try:
119
- result_or_exception = result_queue.get_nowait()
120
- if isinstance(result_or_exception, Exception):
121
- raise result_or_exception
122
- return result_or_exception
123
- except Exception as e:
124
- raise RuntimeError("Subprocess did not return any result") from e
135
+ # Finally, ensure we drain/join the listener if capturing output
125
136
  finally:
126
137
  if capturer is not None:
127
138
  capturer.join_listener(timeout=5.0)
@@ -154,10 +165,21 @@ def _subprocess_wrapper[R](
154
165
  # Execute the target function and put the result in the queue
155
166
  result: R = func(*args, **kwargs)
156
167
  if result_queue is not None:
157
- result_queue.put(result)
168
+ result_queue.put({"ok": True, "result": result})
158
169
 
159
170
  # Handle cleanup and exceptions
160
171
  except Exception as e:
161
172
  if result_queue is not None:
162
- result_queue.put(e)
173
+ try:
174
+ import traceback
175
+ tb = traceback.format_exc()
176
+ result_queue.put({
177
+ "ok": False,
178
+ "exc_type": e.__class__.__name__,
179
+ "exc_repr": repr(e),
180
+ "traceback_str": tb,
181
+ })
182
+ except Exception:
183
+ # Nothing we can do if even this fails
184
+ pass
163
185
 
@@ -1,7 +1,16 @@
1
+ from ..typing import JsonDict as JsonDict
1
2
  from .capturer import CaptureOutput as CaptureOutput
3
+ from _typeshed import Incomplete
2
4
  from collections.abc import Callable as Callable
3
5
  from typing import Any
4
6
 
7
+ class RemoteSubprocessError(RuntimeError):
8
+ """ Raised in the parent when the child raised an exception - contains the child's formatted traceback. """
9
+ remote_type: Incomplete
10
+ remote_repr: Incomplete
11
+ remote_traceback: Incomplete
12
+ def __init__(self, exc_type: str, exc_repr: str, traceback_str: str) -> None: ...
13
+
5
14
  def run_in_subprocess[R](func: Callable[..., R], *args: Any, timeout: float | None = None, no_join: bool = False, capture_output: bool = False, **kwargs: Any) -> R:
6
15
  ''' Execute a function in a subprocess with positional and keyword arguments.
7
16
 
@@ -25,7 +34,8 @@ def run_in_subprocess[R](func: Callable[..., R], *args: Any, timeout: float | No
25
34
  \t\tR: The return value of the function.
26
35
 
27
36
  \tRaises:
28
- \t\tRuntimeError: If the subprocess exits with a non-zero exit code or times out.
37
+ \t\tRemoteSubprocessError: If the child raised an exception - contains the child\'s formatted traceback.
38
+ \t\tRuntimeError: If the subprocess exits with a non-zero exit code or did not return a result.
29
39
  \t\tTimeoutError: If the subprocess exceeds the specified timeout.
30
40
 
31
41
  \tExamples:
stouputils/print.py CHANGED
@@ -328,39 +328,39 @@ def info(
328
328
  else:
329
329
  print(message, *values, RESET, file=file, **print_kwargs)
330
330
 
331
- def debug(*values: Any, **print_kwargs: Any) -> None:
331
+ def debug(*values: Any, flush: bool = True, **print_kwargs: Any) -> None:
332
332
  """ Print a debug message looking like "[DEBUG HH:MM:SS] message" in cyan by default. """
333
333
  if "text" not in print_kwargs:
334
334
  print_kwargs["text"] = "DEBUG"
335
335
  if "color" not in print_kwargs:
336
336
  print_kwargs["color"] = CYAN
337
- info(*values, **print_kwargs)
337
+ info(*values, flush=flush, **print_kwargs)
338
338
 
339
- def alt_debug(*values: Any, **print_kwargs: Any) -> None:
339
+ def alt_debug(*values: Any, flush: bool = True, **print_kwargs: Any) -> None:
340
340
  """ Print a debug message looking like "[DEBUG HH:MM:SS] message" in blue by default. """
341
341
  if "text" not in print_kwargs:
342
342
  print_kwargs["text"] = "DEBUG"
343
343
  if "color" not in print_kwargs:
344
344
  print_kwargs["color"] = BLUE
345
- info(*values, **print_kwargs)
345
+ info(*values, flush=flush, **print_kwargs)
346
346
 
347
- def suggestion(*values: Any, **print_kwargs: Any) -> None:
347
+ def suggestion(*values: Any, flush: bool = True, **print_kwargs: Any) -> None:
348
348
  """ Print a suggestion message looking like "[SUGGESTION HH:MM:SS] message" in cyan by default. """
349
349
  if "text" not in print_kwargs:
350
350
  print_kwargs["text"] = "SUGGESTION"
351
351
  if "color" not in print_kwargs:
352
352
  print_kwargs["color"] = CYAN
353
- info(*values, **print_kwargs)
353
+ info(*values, flush=flush, **print_kwargs)
354
354
 
355
- def progress(*values: Any, **print_kwargs: Any) -> None:
355
+ def progress(*values: Any, flush: bool = True, **print_kwargs: Any) -> None:
356
356
  """ Print a progress message looking like "[PROGRESS HH:MM:SS] message" in magenta by default. """
357
357
  if "text" not in print_kwargs:
358
358
  print_kwargs["text"] = "PROGRESS"
359
359
  if "color" not in print_kwargs:
360
360
  print_kwargs["color"] = MAGENTA
361
- info(*values, **print_kwargs)
361
+ info(*values, flush=flush, **print_kwargs)
362
362
 
363
- def warning(*values: Any, **print_kwargs: Any) -> None:
363
+ def warning(*values: Any, flush: bool = True, **print_kwargs: Any) -> None:
364
364
  """ Print a warning message looking like "[WARNING HH:MM:SS] message" in yellow by default and in sys.stderr. """
365
365
  if "file" not in print_kwargs:
366
366
  print_kwargs["file"] = sys.stderr
@@ -368,9 +368,9 @@ def warning(*values: Any, **print_kwargs: Any) -> None:
368
368
  print_kwargs["text"] = "WARNING"
369
369
  if "color" not in print_kwargs:
370
370
  print_kwargs["color"] = YELLOW
371
- info(*values, **print_kwargs)
371
+ info(*values, flush=flush, **print_kwargs)
372
372
 
373
- def error(*values: Any, exit: bool = False, **print_kwargs: Any) -> None:
373
+ def error(*values: Any, exit: bool = False, flush: bool = True, **print_kwargs: Any) -> None:
374
374
  """ Print an error message (in sys.stderr and in red by default)
375
375
  and optionally ask the user to continue or stop the program.
376
376
 
@@ -390,7 +390,7 @@ def error(*values: Any, exit: bool = False, **print_kwargs: Any) -> None:
390
390
  print_kwargs["text"] = "ERROR"
391
391
  if "color" not in print_kwargs:
392
392
  print_kwargs["color"] = RED
393
- info(*values, **print_kwargs)
393
+ info(*values, flush=flush, **print_kwargs)
394
394
  if exit:
395
395
  try:
396
396
  print("Press enter to ignore error and continue, or 'CTRL+C' to stop the program... ", file=file)
@@ -402,6 +402,7 @@ def error(*values: Any, exit: bool = False, **print_kwargs: Any) -> None:
402
402
  def whatisit(
403
403
  *values: Any,
404
404
  print_function: Callable[..., None] = debug,
405
+ flush: bool = True,
405
406
  max_length: int = 250,
406
407
  color: str = CYAN,
407
408
  **print_kwargs: Any,
@@ -473,11 +474,11 @@ def whatisit(
473
474
 
474
475
  # Print the values
475
476
  if len(values) > 1:
476
- print_function("".join(f"\n {_internal(value)}" for value in values), **print_kwargs)
477
+ print_function("".join(f"\n {_internal(value)}" for value in values), flush=flush, **print_kwargs)
477
478
  elif len(values) == 1:
478
- print_function(_internal(values[0]), **print_kwargs)
479
+ print_function(_internal(values[0]), flush=flush, **print_kwargs)
479
480
 
480
- def breakpoint(*values: Any, print_function: Callable[..., None] = warning, **print_kwargs: Any) -> None:
481
+ def breakpoint(*values: Any, print_function: Callable[..., None] = warning, flush: bool = True, **print_kwargs: Any) -> None:
481
482
  """ Breakpoint function, pause the program and print the values.
482
483
 
483
484
  Args:
@@ -493,7 +494,7 @@ def breakpoint(*values: Any, print_function: Callable[..., None] = warning, **pr
493
494
  file = cast(TextIO, print_kwargs["file"][0])
494
495
  else:
495
496
  file = print_kwargs["file"]
496
- whatisit(*values, print_function=print_function, **print_kwargs)
497
+ whatisit(*values, print_function=print_function, flush=flush, **print_kwargs)
497
498
  try:
498
499
  input()
499
500
  except (KeyboardInterrupt, EOFError):
stouputils/print.pyi CHANGED
@@ -125,17 +125,17 @@ def info(*values: Any, color: str = ..., text: str = 'INFO ', prefix: str = '',
125
125
  \t\tuse_colored\t\t(bool):\t\t\t\t\tWhether to use the colored() function to format the message
126
126
  \t\tprint_kwargs\t(dict):\t\t\t\t\tKeyword arguments to pass to the print function
127
127
  \t'''
128
- def debug(*values: Any, **print_kwargs: Any) -> None:
128
+ def debug(*values: Any, flush: bool = True, **print_kwargs: Any) -> None:
129
129
  ''' Print a debug message looking like "[DEBUG HH:MM:SS] message" in cyan by default. '''
130
- def alt_debug(*values: Any, **print_kwargs: Any) -> None:
130
+ def alt_debug(*values: Any, flush: bool = True, **print_kwargs: Any) -> None:
131
131
  ''' Print a debug message looking like "[DEBUG HH:MM:SS] message" in blue by default. '''
132
- def suggestion(*values: Any, **print_kwargs: Any) -> None:
132
+ def suggestion(*values: Any, flush: bool = True, **print_kwargs: Any) -> None:
133
133
  ''' Print a suggestion message looking like "[SUGGESTION HH:MM:SS] message" in cyan by default. '''
134
- def progress(*values: Any, **print_kwargs: Any) -> None:
134
+ def progress(*values: Any, flush: bool = True, **print_kwargs: Any) -> None:
135
135
  ''' Print a progress message looking like "[PROGRESS HH:MM:SS] message" in magenta by default. '''
136
- def warning(*values: Any, **print_kwargs: Any) -> None:
136
+ def warning(*values: Any, flush: bool = True, **print_kwargs: Any) -> None:
137
137
  ''' Print a warning message looking like "[WARNING HH:MM:SS] message" in yellow by default and in sys.stderr. '''
138
- def error(*values: Any, exit: bool = False, **print_kwargs: Any) -> None:
138
+ def error(*values: Any, exit: bool = False, flush: bool = True, **print_kwargs: Any) -> None:
139
139
  """ Print an error message (in sys.stderr and in red by default)
140
140
  \tand optionally ask the user to continue or stop the program.
141
141
 
@@ -145,7 +145,7 @@ def error(*values: Any, exit: bool = False, **print_kwargs: Any) -> None:
145
145
  \t\t\tfalse to ignore the error automatically and continue
146
146
  \t\tprint_kwargs\t(dict):\t\tKeyword arguments to pass to the print function
147
147
  \t"""
148
- def whatisit(*values: Any, print_function: Callable[..., None] = ..., max_length: int = 250, color: str = ..., **print_kwargs: Any) -> None:
148
+ def whatisit(*values: Any, print_function: Callable[..., None] = ..., flush: bool = True, max_length: int = 250, color: str = ..., **print_kwargs: Any) -> None:
149
149
  ''' Print the type of each value and the value itself, with its id and length/shape.
150
150
 
151
151
  \tThe output format is: "type, <id id_number>:\t(length/shape) value"
@@ -157,7 +157,7 @@ def whatisit(*values: Any, print_function: Callable[..., None] = ..., max_length
157
157
  \t\tcolor\t\t\t(str):\t\tColor of the message (default: CYAN)
158
158
  \t\tprint_kwargs\t(dict):\t\tKeyword arguments to pass to the print function
159
159
  \t'''
160
- def breakpoint(*values: Any, print_function: Callable[..., None] = ..., **print_kwargs: Any) -> None:
160
+ def breakpoint(*values: Any, print_function: Callable[..., None] = ..., flush: bool = True, **print_kwargs: Any) -> None:
161
161
  """ Breakpoint function, pause the program and print the values.
162
162
 
163
163
  \tArgs:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stouputils
3
- Version: 1.18.1
3
+ Version: 1.18.3
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
@@ -125,8 +125,8 @@ stouputils/installer/main.py,sha256=8wrx_cnQo1dFGRf6x8vtxh6-96tQ-AzMyvJ0S64j0io,
125
125
  stouputils/installer/main.pyi,sha256=r3j4GoMBpU06MpOqjSwoDTiSMOmbA3WWUA87970b6KE,3134
126
126
  stouputils/installer/windows.py,sha256=r2AIuoyAmtMEuoCtQBH9GWQWI-JUT2J9zoH28j9ruOU,4880
127
127
  stouputils/installer/windows.pyi,sha256=tHogIFhPVDQS0I10liLkAxnpaFFAvmFtEVMpPIae5LU,1616
128
- stouputils/io.py,sha256=Q0Erzy7k8MTEw50s1O_CbuP2bh7oxt42tbcXcUeX97E,17782
129
- stouputils/io.pyi,sha256=zsId531UOFs-P5kc5cCl3W6oA0X47T_RD2HPDpElZX8,9308
128
+ stouputils/io.py,sha256=3-n6RGItDn7mdHSkzK4oSUMeL-3VJ1k2cvoSfzBH_ak,17797
129
+ stouputils/io.pyi,sha256=n2o28-4BfhlEH1f-vCLU8YlaqzG-4qgrZ3p8jEnJFeE,9314
130
130
  stouputils/lock/__init__.py,sha256=8EvKPwnd5oHAWP-2vs6ULUDCSNyUh-mw12nYvBqgVAc,1029
131
131
  stouputils/lock/__init__.pyi,sha256=qcTm6JcGXfwQB2dUCa2wFEInSwJF2pOrYnejSpvGd7k,120
132
132
  stouputils/lock/base.py,sha256=hjSXRzOLVTMNrxpf4QcmfCfdlSRHFT9e130Zz_cqxY8,20483
@@ -141,22 +141,22 @@ stouputils/lock/shared.py,sha256=G8Mcy7dXtNESyU7hSaeihNrCU4l98VhyQyO_vQYPJ7g,788
141
141
  stouputils/lock/shared.pyi,sha256=0CV6TpTaDEkcGA35Q-ijp8ckImZ32umlMA4U-8C_O-I,545
142
142
  stouputils/parallel/__init__.py,sha256=myD8KiVfPPKF26Xu8Clu0p-VaYDK74loMUjUkl6-9XU,1013
143
143
  stouputils/parallel/__init__.pyi,sha256=UtZKtl9i__OH0Edypap9oZUcTF1h91qfpItG1-x7TfE,97
144
- stouputils/parallel/capturer.py,sha256=lo7D1x2RGo9SHkr2sIrY6wL4V5wbsxngnmBMbn5-o_I,4177
144
+ stouputils/parallel/capturer.py,sha256=1ON8QuMrk9B0WS5lCIKtItzKRmlddrHsJAhMHYvKFyE,4127
145
145
  stouputils/parallel/capturer.pyi,sha256=DWa3biPFzrGJBmkaFhAWwhbX4gbKQAipBAOJm4_XBy8,1665
146
146
  stouputils/parallel/common.py,sha256=niDcAiEX3flX0ow91gXOB4umlOrR8PIYvpcKPClJHfM,4910
147
147
  stouputils/parallel/common.pyi,sha256=jbyftOYHKP2qaA8YC1f1f12-BDBkhfsQsnPdsR4oet8,2493
148
148
  stouputils/parallel/multi.py,sha256=tHJgcQJwsI6QeKEHoGJC4tsVK_6t1Fazkb06i1u-W_8,12610
149
149
  stouputils/parallel/multi.pyi,sha256=DWolZn1UoXxOfuw7LqEJcU8aQJsN-_DRhPGJlJCA5pQ,8021
150
- stouputils/parallel/subprocess.py,sha256=YD9mda-zMRpudlby4cLwLJxIY5BjPwn8K11eJ-3k-6E,5790
151
- stouputils/parallel/subprocess.pyi,sha256=9g5FDYfcnIikd9OOtDP1u_NPT2elk5YjUsQN9eIMSso,3145
152
- stouputils/print.py,sha256=PNyKvKheI7ior_-jQQ0Xu8ym7tSctheyHXTFykw2MKc,24552
153
- stouputils/print.pyi,sha256=0z-BFpOEZ48GBGcD08C4Be67cPKX4ZxSHqKyOLRS_M8,10205
150
+ stouputils/parallel/subprocess.py,sha256=LWbwwAmnz54dCz9TAcKNg1TOMCVSP0C-0GIXaS5nVx0,6728
151
+ stouputils/parallel/subprocess.pyi,sha256=gzRtpTslvoENLtSNk79fe3Xz8lV3IwuopT9uMHW9BTU,3680
152
+ stouputils/print.py,sha256=WITobaFWrnVfrE4YRwHTRzf6H6V0BhxSIoQ9LRIrRiQ,24831
153
+ stouputils/print.pyi,sha256=GOgbg7YomG-D9n2hcM0bUOaUU1PBjazWIn4vUSBYDDU,10365
154
154
  stouputils/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
155
155
  stouputils/typing.py,sha256=TwvxrvxhBRkyHkoOpfyXebN13M3xJb8MAjKXiNIWjew,2205
156
156
  stouputils/typing.pyi,sha256=U2UmFZausMYpnsUQROQE2JOwHcjx2hKV0rJuOdR57Ew,1341
157
157
  stouputils/version_pkg.py,sha256=Jsp-s03L14DkiZ94vQgrlQmaxApfn9DC8M_nzT1SJLk,7014
158
158
  stouputils/version_pkg.pyi,sha256=QPvqp1U3QA-9C_CC1dT9Vahv1hXEhstbM7x5uzMZSsQ,755
159
- stouputils-1.18.1.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
160
- stouputils-1.18.1.dist-info/entry_points.txt,sha256=tx0z9VOnE-sfkmbFbA93zaBMzV3XSsKEJa_BWIqUzxw,57
161
- stouputils-1.18.1.dist-info/METADATA,sha256=PWMcVUM25QN5JXf9cDGGl-xPQQc3evJlWk8QX6FliFQ,14011
162
- stouputils-1.18.1.dist-info/RECORD,,
159
+ stouputils-1.18.3.dist-info/WHEEL,sha256=e_m4S054HL0hyR3CpOk-b7Q7fDX6BuFkgL5OjAExXas,80
160
+ stouputils-1.18.3.dist-info/entry_points.txt,sha256=tx0z9VOnE-sfkmbFbA93zaBMzV3XSsKEJa_BWIqUzxw,57
161
+ stouputils-1.18.3.dist-info/METADATA,sha256=jA1lFVcNiNE1xCh5-QlKwvLG-VRVpK-BKKms71jisoI,14011
162
+ stouputils-1.18.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.26
2
+ Generator: uv 0.9.27
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any