stdl 0.5.5__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: stdl
3
- Version: 0.5.5
3
+ Version: 0.6.0
4
4
  Summary: Extended Python Standard Library
5
5
  Author-email: Žiga Ivanšek <ziga.ivansek@gmail.com>
6
6
  Project-URL: Repository, https://github.com/zigai/stdl
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stdl"
3
- version = "0.5.5"
3
+ version = "0.6.0"
4
4
  description = "Extended Python Standard Library"
5
5
  authors = [{ name = "Žiga Ivanšek", email = "ziga.ivansek@gmail.com" }]
6
6
  license = { file = "LICENSE.txt" }
@@ -2,13 +2,13 @@ import inspect
2
2
  import json
3
3
  import sys
4
4
  import time
5
- import typing as T
6
5
  from functools import wraps
6
+ from typing import Callable
7
7
 
8
8
 
9
9
  def timer(
10
10
  r: int | None = 2,
11
- sink: T.Callable = sys.stdout.write,
11
+ sink: Callable = sys.stdout.write,
12
12
  show_args: bool = False,
13
13
  serialize: bool = False,
14
14
  sep=" | ",
@@ -24,7 +24,7 @@ def timer(
24
24
  sep (str, optional): Separator between the function/method name and the arguments. Defaults to " | ".
25
25
  """
26
26
 
27
- def decorator(func: T.Callable):
27
+ def decorator(func: Callable):
28
28
  @wraps(func)
29
29
  def wrapper(*args, **kwargs):
30
30
  time_start = time.perf_counter()
@@ -67,7 +67,7 @@ def timer(
67
67
 
68
68
  def retry(attempts: int, delay: float = 0):
69
69
  if attempts < 1:
70
- raise ValueError("attempts must be greater than 0")
70
+ raise ValueError("Attempts must be greater than 0")
71
71
 
72
72
  def decorator(func):
73
73
  @wraps(func)
@@ -1,8 +1,8 @@
1
1
  import random
2
2
  import time
3
- import typing as T
4
3
  from dataclasses import dataclass
5
4
  from datetime import date, datetime, timedelta, timezone
5
+ from typing import Generator
6
6
 
7
7
  from dateutil.parser import parse as parse_datetime_str
8
8
 
@@ -98,7 +98,8 @@ def datetime_fmt(
98
98
  ms: bool = False,
99
99
  utc: bool = True,
100
100
  ) -> str:
101
- """Format date and time
101
+ """
102
+ Format date and time
102
103
 
103
104
  Args:
104
105
  d (str | float | None | datetime, optional): Input datetime. Defaults to current date and time.
@@ -220,7 +221,7 @@ def date_fmt(
220
221
  return d.strftime(f"%{fmt[0]}{sep}%{fmt[1]}{sep}%{fmt[2]}")
221
222
 
222
223
 
223
- def date_range(start: date, end: date) -> T.Generator[date, None, None]:
224
+ def date_range(start: date, end: date) -> Generator[date, None, None]:
224
225
  """
225
226
  Returns a generator for dates between ``start`` and ``end``
226
227
 
@@ -261,6 +262,90 @@ def sleep(lo: float, hi: float | None = None) -> float:
261
262
  return t
262
263
 
263
264
 
265
+ def seconds_to_hms(time: int | float, ms: bool = False) -> str:
266
+ """
267
+ Converts time in seconds to a string in the format HH:MM:SS[.mmm]
268
+
269
+ Example:
270
+ >>> seconds_to_hms(123.456)
271
+ '00:02:03'
272
+ >>> seconds_to_hms(123.456, ms=True)
273
+ '00:02:03.456'
274
+ >>> seconds_to_hms(-123.456, ms=True)
275
+ '-00:02:03.456'
276
+ """
277
+ sign = "-" if time < 0 else ""
278
+ time = abs(time)
279
+
280
+ s = int(time % 60)
281
+ m = int((time // 60) % 60)
282
+ h = int(time // 3600)
283
+ time_str = f"{sign}{h:02d}:{m:02d}:{s:02d}"
284
+
285
+ if ms:
286
+ milis = int(round((time - int(time)) * 1000))
287
+ time_str += f".{milis:03d}"
288
+ return time_str
289
+
290
+
291
+ def hms_to_seconds(time: str, ms: bool = False) -> float | None:
292
+ """
293
+ Converts a string in the format HH:MM:SS[.mmm] or h:m:s or m:s to time in seconds.
294
+
295
+ Example:
296
+ >>> hms_to_seconds('00:02:03')
297
+ 123.0
298
+ >>> hms_to_seconds('00:02:03.456', ms=True)
299
+ 123.456
300
+ """
301
+ if time is None:
302
+ return None
303
+
304
+ negative = False
305
+ time = time.strip()
306
+ if time.startswith("-"):
307
+ negative = True
308
+ time = time[1:]
309
+
310
+ parts = time.split(":")
311
+ if len(parts) == 1:
312
+ return float(parts[0])
313
+ time_milis = 0.0
314
+
315
+ if ms:
316
+ if "." in parts[-1]:
317
+ dot_split = parts[-1].split(".", 1)
318
+ parts[-1], millis_str = dot_split
319
+ if not millis_str.isdigit():
320
+ raise ValueError(f"Invalid milliseconds in time: '{time}'")
321
+ millis = int(millis_str.ljust(3, "0"))
322
+ time_milis = millis / 1000
323
+
324
+ try:
325
+ if len(parts) == 2:
326
+ minute_part, second_part = parts
327
+ m = int(minute_part)
328
+ s = int(second_part)
329
+ if s >= 60:
330
+ raise ValueError(f"Seconds ({s}) must be < 60.")
331
+ total = m * 60 + s
332
+ elif len(parts) == 3:
333
+ hour_part, minute_part, second_part = parts
334
+ h = int(hour_part)
335
+ m = int(minute_part)
336
+ s = int(second_part)
337
+ if m >= 60 or s >= 60:
338
+ raise ValueError(f"Minutes ({m}) or seconds ({s}) must be < 60.")
339
+ total = h * 3600 + m * 60 + s
340
+ else:
341
+ raise ValueError("Invalid number of time segments.")
342
+ except ValueError as e:
343
+ raise ValueError(f"Invalid time format: '{time}'") from e
344
+
345
+ result = total + time_milis
346
+ return -result if negative else result
347
+
348
+
264
349
  __all__ = [
265
350
  "Timer",
266
351
  "parse_datetime_str",