dycw-utilities 0.175.17__py3-none-any.whl → 0.185.8__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.
Files changed (94) hide show
  1. dycw_utilities-0.185.8.dist-info/METADATA +33 -0
  2. dycw_utilities-0.185.8.dist-info/RECORD +90 -0
  3. {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +2 -2
  4. utilities/__init__.py +1 -1
  5. utilities/altair.py +8 -6
  6. utilities/asyncio.py +40 -56
  7. utilities/atools.py +9 -11
  8. utilities/cachetools.py +8 -6
  9. utilities/click.py +4 -3
  10. utilities/concurrent.py +1 -1
  11. utilities/constants.py +492 -0
  12. utilities/contextlib.py +23 -30
  13. utilities/contextvars.py +1 -23
  14. utilities/core.py +2581 -0
  15. utilities/dataclasses.py +16 -119
  16. utilities/docker.py +139 -45
  17. utilities/enum.py +1 -1
  18. utilities/errors.py +2 -16
  19. utilities/fastapi.py +5 -5
  20. utilities/fpdf2.py +2 -1
  21. utilities/functions.py +33 -264
  22. utilities/http.py +2 -3
  23. utilities/hypothesis.py +48 -25
  24. utilities/iterables.py +39 -575
  25. utilities/jinja2.py +3 -6
  26. utilities/jupyter.py +5 -3
  27. utilities/libcst.py +1 -1
  28. utilities/lightweight_charts.py +4 -6
  29. utilities/logging.py +17 -15
  30. utilities/math.py +1 -36
  31. utilities/more_itertools.py +4 -6
  32. utilities/numpy.py +2 -1
  33. utilities/operator.py +2 -2
  34. utilities/orjson.py +24 -25
  35. utilities/os.py +4 -185
  36. utilities/packaging.py +129 -0
  37. utilities/parse.py +33 -13
  38. utilities/pathlib.py +2 -136
  39. utilities/platform.py +8 -90
  40. utilities/polars.py +34 -31
  41. utilities/postgres.py +9 -4
  42. utilities/pottery.py +20 -18
  43. utilities/pqdm.py +3 -4
  44. utilities/psutil.py +2 -3
  45. utilities/pydantic.py +18 -4
  46. utilities/pydantic_settings.py +7 -9
  47. utilities/pydantic_settings_sops.py +3 -3
  48. utilities/pyinstrument.py +4 -4
  49. utilities/pytest.py +49 -108
  50. utilities/pytest_plugins/pytest_regressions.py +2 -2
  51. utilities/pytest_regressions.py +8 -6
  52. utilities/random.py +2 -8
  53. utilities/redis.py +98 -94
  54. utilities/reprlib.py +11 -118
  55. utilities/shellingham.py +66 -0
  56. utilities/slack_sdk.py +13 -12
  57. utilities/sqlalchemy.py +42 -30
  58. utilities/sqlalchemy_polars.py +16 -25
  59. utilities/subprocess.py +1166 -148
  60. utilities/tabulate.py +32 -0
  61. utilities/testbook.py +8 -8
  62. utilities/text.py +24 -115
  63. utilities/throttle.py +159 -0
  64. utilities/time.py +18 -0
  65. utilities/timer.py +29 -12
  66. utilities/traceback.py +15 -22
  67. utilities/types.py +38 -3
  68. utilities/typing.py +18 -12
  69. utilities/uuid.py +1 -1
  70. utilities/version.py +202 -45
  71. utilities/whenever.py +22 -150
  72. dycw_utilities-0.175.17.dist-info/METADATA +0 -34
  73. dycw_utilities-0.175.17.dist-info/RECORD +0 -103
  74. utilities/atomicwrites.py +0 -182
  75. utilities/cryptography.py +0 -41
  76. utilities/getpass.py +0 -8
  77. utilities/git.py +0 -19
  78. utilities/grp.py +0 -28
  79. utilities/gzip.py +0 -31
  80. utilities/json.py +0 -70
  81. utilities/permissions.py +0 -298
  82. utilities/pickle.py +0 -25
  83. utilities/pwd.py +0 -28
  84. utilities/re.py +0 -156
  85. utilities/sentinel.py +0 -73
  86. utilities/socket.py +0 -8
  87. utilities/string.py +0 -20
  88. utilities/tempfile.py +0 -136
  89. utilities/tzdata.py +0 -11
  90. utilities/tzlocal.py +0 -28
  91. utilities/warnings.py +0 -65
  92. utilities/zipfile.py +0 -25
  93. utilities/zoneinfo.py +0 -133
  94. {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +0 -0
utilities/functions.py CHANGED
@@ -1,43 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Callable, Iterable, Iterator
4
- from dataclasses import asdict, dataclass
5
- from functools import _lru_cache_wrapper, cached_property, partial, reduce, wraps
3
+ from dataclasses import dataclass
4
+ from functools import wraps
6
5
  from inspect import getattr_static
7
6
  from pathlib import Path
8
- from re import findall
9
- from types import (
10
- BuiltinFunctionType,
11
- FunctionType,
12
- MethodDescriptorType,
13
- MethodType,
14
- MethodWrapperType,
15
- WrapperDescriptorType,
16
- )
17
- from typing import TYPE_CHECKING, Any, Literal, TypeGuard, cast, overload, override
7
+ from typing import TYPE_CHECKING, Any, Literal, assert_never, overload, override
18
8
 
19
9
  from whenever import Date, PlainDateTime, Time, TimeDelta, ZonedDateTime
20
10
 
21
- from utilities.reprlib import get_repr, get_repr_and_class
22
- from utilities.sentinel import Sentinel, is_sentinel, sentinel
23
- from utilities.types import Dataclass, Number, SupportsRichComparison, TypeLike
11
+ from utilities.constants import SECOND
12
+ from utilities.core import get_class_name, repr_, repr_str
24
13
 
25
14
  if TYPE_CHECKING:
26
- from collections.abc import Container
15
+ from collections.abc import Callable, Container, Iterable, Iterator
27
16
 
28
-
29
- def apply_decorators[F1: Callable, F2: Callable](
30
- func: F1, /, *decorators: Callable[[F2], F2]
31
- ) -> F1:
32
- """Apply a set of decorators to a function."""
33
- return reduce(_apply_decorators_one, decorators, func)
34
-
35
-
36
- def _apply_decorators_one[F: Callable](acc: F, el: Callable[[Any], Any], /) -> F:
37
- return el(acc)
38
-
39
-
40
- ##
17
+ from utilities.types import Duration, Number, TypeLike
41
18
 
42
19
 
43
20
  @overload
@@ -274,7 +251,7 @@ class EnsureMemberError(Exception):
274
251
  @override
275
252
  def __str__(self) -> str:
276
253
  return _make_error_msg(
277
- self.obj, f"a member of {get_repr(self.container)}", nullable=self.nullable
254
+ self.obj, f"a member of {repr_(self.container)}", nullable=self.nullable
278
255
  )
279
256
 
280
257
 
@@ -485,207 +462,31 @@ class EnsureZonedDateTimeError(Exception):
485
462
  ##
486
463
 
487
464
 
488
- def first[T](pair: tuple[T, Any], /) -> T:
489
- """Get the first element in a pair."""
490
- return pair[0]
491
-
492
-
493
- ##
494
-
495
-
496
- @overload
497
- def get_class[T](obj: type[T], /) -> type[T]: ...
498
- @overload
499
- def get_class[T](obj: T, /) -> type[T]: ...
500
- def get_class[T](obj: T | type[T], /) -> type[T]:
501
- """Get the class of an object, unless it is already a class."""
502
- return obj if isinstance(obj, type) else type(obj)
503
-
504
-
505
- ##
506
-
507
-
508
- def get_class_name(obj: Any, /, *, qual: bool = False) -> str:
509
- """Get the name of the class of an object, unless it is already a class."""
510
- cls = get_class(obj)
511
- return f"{cls.__module__}.{cls.__qualname__}" if qual else cls.__name__
465
+ def in_milli_seconds(duration: Duration, /) -> float:
466
+ """Convert a duration to milli-seconds."""
467
+ return 1e3 * in_seconds(duration)
512
468
 
513
469
 
514
- ##
515
-
516
-
517
- def get_func_name(obj: Callable[..., Any], /) -> str:
518
- """Get the name of a callable."""
519
- if isinstance(obj, BuiltinFunctionType):
520
- return obj.__name__
521
- if isinstance(obj, FunctionType):
522
- name = obj.__name__
523
- pattern = r"^.+\.([A-Z]\w+\." + name + ")$"
524
- try:
525
- (full_name,) = findall(pattern, obj.__qualname__)
526
- except ValueError:
527
- return name
528
- return full_name
529
- if isinstance(obj, MethodType):
530
- return f"{get_class_name(obj.__self__)}.{obj.__name__}"
531
- if isinstance(
532
- obj,
533
- MethodType | MethodDescriptorType | MethodWrapperType | WrapperDescriptorType,
534
- ):
535
- return obj.__qualname__
536
- if isinstance(obj, _lru_cache_wrapper):
537
- return cast("Any", obj).__name__
538
- if isinstance(obj, partial):
539
- return get_func_name(obj.func)
540
- return get_class_name(obj)
541
-
542
-
543
- ##
470
+ def in_seconds(duration: Duration, /) -> float:
471
+ """Convert a duration to seconds."""
472
+ match duration:
473
+ case int() | float():
474
+ return duration
475
+ case TimeDelta():
476
+ return duration.in_seconds()
477
+ case never:
478
+ assert_never(never)
544
479
 
545
480
 
546
- def get_func_qualname(obj: Callable[..., Any], /) -> str:
547
- """Get the qualified name of a callable."""
548
- if isinstance(
549
- obj, BuiltinFunctionType | FunctionType | MethodType | _lru_cache_wrapper
550
- ):
551
- return f"{obj.__module__}.{obj.__qualname__}"
552
- if isinstance(
553
- obj, MethodDescriptorType | MethodWrapperType | WrapperDescriptorType
554
- ):
555
- return f"{obj.__objclass__.__module__}.{obj.__qualname__}"
556
- if isinstance(obj, partial):
557
- return get_func_qualname(obj.func)
558
- return f"{obj.__module__}.{get_class_name(obj)}"
559
-
560
-
561
- ##
562
-
563
-
564
- def identity[T](obj: T, /) -> T:
565
- """Return the object itself."""
566
- return obj
567
-
568
-
569
- ##
570
-
571
-
572
- def is_none(obj: Any, /) -> TypeGuard[None]:
573
- """Check if an object is `None`."""
574
- return obj is None
575
-
576
-
577
- ##
578
-
579
-
580
- def is_not_none(obj: Any, /) -> bool:
581
- """Check if an object is not `None`."""
582
- return obj is not None
583
-
584
-
585
- ##
586
-
587
-
588
- def map_object[T](
589
- func: Callable[[Any], Any], obj: T, /, *, before: Callable[[Any], Any] | None = None
590
- ) -> T:
591
- """Map a function over an object, across a variety of structures."""
592
- if before is not None:
593
- obj = before(obj)
594
- match obj:
595
- case dict():
596
- return type(obj)({
597
- k: map_object(func, v, before=before) for k, v in obj.items()
598
- })
599
- case frozenset() | list() | set() | tuple():
600
- return type(obj)(map_object(func, i, before=before) for i in obj)
601
- case Dataclass():
602
- return map_object(func, asdict(obj), before=before)
603
- case _:
604
- return func(obj)
605
-
606
-
607
- ##
608
-
609
-
610
- @overload
611
- def min_nullable[T: SupportsRichComparison](
612
- iterable: Iterable[T | None], /, *, default: Sentinel = ...
613
- ) -> T: ...
614
- @overload
615
- def min_nullable[T: SupportsRichComparison, U](
616
- iterable: Iterable[T | None], /, *, default: U = ...
617
- ) -> T | U: ...
618
- def min_nullable[T: SupportsRichComparison, U](
619
- iterable: Iterable[T | None], /, *, default: U | Sentinel = sentinel
620
- ) -> T | U:
621
- """Compute the minimum of a set of values; ignoring nulls."""
622
- values = (i for i in iterable if i is not None)
623
- if is_sentinel(default):
624
- try:
625
- return min(values)
626
- except ValueError:
627
- raise MinNullableError(values=values) from None
628
- return min(values, default=default)
629
-
630
-
631
- @dataclass(kw_only=True, slots=True)
632
- class MinNullableError[T: SupportsRichComparison](Exception):
633
- values: Iterable[T]
634
-
635
- @override
636
- def __str__(self) -> str:
637
- return "Minimum of an all-None iterable is undefined"
638
-
639
-
640
- @overload
641
- def max_nullable[T: SupportsRichComparison](
642
- iterable: Iterable[T | None], /, *, default: Sentinel = ...
643
- ) -> T: ...
644
- @overload
645
- def max_nullable[T: SupportsRichComparison, U](
646
- iterable: Iterable[T | None], /, *, default: U = ...
647
- ) -> T | U: ...
648
- def max_nullable[T: SupportsRichComparison, U](
649
- iterable: Iterable[T | None], /, *, default: U | Sentinel = sentinel
650
- ) -> T | U:
651
- """Compute the maximum of a set of values; ignoring nulls."""
652
- values = (i for i in iterable if i is not None)
653
- if is_sentinel(default):
654
- try:
655
- return max(values)
656
- except ValueError:
657
- raise MaxNullableError(values=values) from None
658
- return max(values, default=default)
659
-
660
-
661
- @dataclass(kw_only=True, slots=True)
662
- class MaxNullableError[TSupportsRichComparison](Exception):
663
- values: Iterable[TSupportsRichComparison]
664
-
665
- @override
666
- def __str__(self) -> str:
667
- return "Maximum of an all-None iterable is undefined"
668
-
669
-
670
- ##
671
-
672
-
673
- def not_func[**P](func: Callable[P, bool], /) -> Callable[P, bool]:
674
- """Lift a boolean-valued function to return its conjugation."""
675
-
676
- @wraps(func)
677
- def wrapped(*args: P.args, **kwargs: P.kwargs) -> bool:
678
- return not func(*args, **kwargs)
679
-
680
- return wrapped
681
-
682
-
683
- ##
684
-
685
-
686
- def second[U](pair: tuple[Any, U], /) -> U:
687
- """Get the second element in a pair."""
688
- return pair[1]
481
+ def in_timedelta(duration: Duration, /) -> TimeDelta:
482
+ """Convert a duration to a timedelta."""
483
+ match duration:
484
+ case int() | float():
485
+ return duration * SECOND
486
+ case TimeDelta():
487
+ return duration
488
+ case never:
489
+ assert_never(never)
689
490
 
690
491
 
691
492
  ##
@@ -726,25 +527,8 @@ def yield_object_attributes(
726
527
  ##
727
528
 
728
529
 
729
- def yield_object_properties(
730
- obj: Any, /, *, skip: Iterable[str] | None = None
731
- ) -> Iterator[tuple[str, Any]]:
732
- """Yield all the object properties."""
733
- yield from yield_object_attributes(obj, skip=skip, static_type=property)
734
-
735
-
736
- def yield_object_cached_properties(
737
- obj: Any, /, *, skip: Iterable[str] | None = None
738
- ) -> Iterator[tuple[str, Any]]:
739
- """Yield all the object cached properties."""
740
- yield from yield_object_attributes(obj, skip=skip, static_type=cached_property)
741
-
742
-
743
- ##
744
-
745
-
746
530
  def _make_error_msg(obj: Any, desc: str, /, *, nullable: bool = False) -> str:
747
- msg = f"{get_repr_and_class(obj)} must be {desc}"
531
+ msg = f"Object {repr_str(obj)} of type {get_class_name(obj)!r} must be {desc}"
748
532
  if nullable:
749
533
  msg += " or None"
750
534
  return msg
@@ -766,9 +550,6 @@ __all__ = [
766
550
  "EnsureTimeDeltaError",
767
551
  "EnsureTimeError",
768
552
  "EnsureZonedDateTimeError",
769
- "MaxNullableError",
770
- "MinNullableError",
771
- "apply_decorators",
772
553
  "ensure_bool",
773
554
  "ensure_bytes",
774
555
  "ensure_class",
@@ -784,21 +565,9 @@ __all__ = [
784
565
  "ensure_time",
785
566
  "ensure_time_delta",
786
567
  "ensure_zoned_date_time",
787
- "first",
788
- "get_class",
789
- "get_class_name",
790
- "get_func_name",
791
- "get_func_qualname",
792
- "identity",
793
- "is_none",
794
- "is_not_none",
795
- "map_object",
796
- "max_nullable",
797
- "min_nullable",
798
- "not_func",
799
- "second",
568
+ "in_milli_seconds",
569
+ "in_seconds",
570
+ "in_timedelta",
800
571
  "skip_if_optimize",
801
572
  "yield_object_attributes",
802
- "yield_object_cached_properties",
803
- "yield_object_properties",
804
573
  ]
utilities/http.py CHANGED
@@ -1,11 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from contextlib import contextmanager
3
4
  from http.client import HTTPSConnection
4
5
  from ipaddress import IPv4Address
5
6
  from typing import TYPE_CHECKING
6
7
 
7
- from utilities.contextlib import enhanced_context_manager
8
-
9
8
  if TYPE_CHECKING:
10
9
  from collections.abc import Iterator
11
10
 
@@ -22,7 +21,7 @@ def get_public_ip(*, timeout: float | None = None) -> IPv4Address:
22
21
  ##
23
22
 
24
23
 
25
- @enhanced_context_manager
24
+ @contextmanager
26
25
  def yield_connection(
27
26
  host: str, /, *, timeout: float | None = None
28
27
  ) -> Iterator[HTTPSConnection]:
utilities/hypothesis.py CHANGED
@@ -51,8 +51,13 @@ from whenever import (
51
51
  ZonedDateTime,
52
52
  )
53
53
 
54
- from utilities.functions import ensure_int, ensure_str, max_nullable, min_nullable
55
- from utilities.math import (
54
+ from utilities.constants import (
55
+ DATE_DELTA_MAX,
56
+ DATE_DELTA_MIN,
57
+ DATE_TIME_DELTA_MAX,
58
+ DATE_TIME_DELTA_MIN,
59
+ DAY,
60
+ IS_LINUX,
56
61
  MAX_FLOAT32,
57
62
  MAX_FLOAT64,
58
63
  MAX_INT8,
@@ -73,38 +78,42 @@ from utilities.math import (
73
78
  MIN_UINT16,
74
79
  MIN_UINT32,
75
80
  MIN_UINT64,
76
- is_zero,
81
+ TEMP_DIR,
82
+ TIME_DELTA_MAX,
83
+ TIME_DELTA_MIN,
84
+ UTC,
85
+ Sentinel,
86
+ sentinel,
87
+ )
88
+ from utilities.core import (
89
+ Permissions,
90
+ TemporaryDirectory,
91
+ get_env,
92
+ get_now,
93
+ is_sentinel,
94
+ max_nullable,
95
+ min_nullable,
96
+ to_zone_info,
97
+ yield_temp_cwd,
77
98
  )
78
- from utilities.os import get_env_var
79
- from utilities.pathlib import module_path, temp_cwd
80
- from utilities.permissions import Permissions
81
- from utilities.platform import IS_LINUX
82
- from utilities.sentinel import Sentinel, is_sentinel, sentinel
83
- from utilities.tempfile import TEMP_DIR, TemporaryDirectory
84
- from utilities.version import Version
99
+ from utilities.functions import ensure_int, ensure_str
100
+ from utilities.math import is_zero
101
+ from utilities.pathlib import module_path
102
+ from utilities.version import Version2, Version3
85
103
  from utilities.whenever import (
86
- DATE_DELTA_MAX,
87
- DATE_DELTA_MIN,
88
104
  DATE_DELTA_PARSABLE_MAX,
89
105
  DATE_DELTA_PARSABLE_MIN,
90
- DATE_TIME_DELTA_MAX,
91
- DATE_TIME_DELTA_MIN,
92
106
  DATE_TIME_DELTA_PARSABLE_MAX,
93
107
  DATE_TIME_DELTA_PARSABLE_MIN,
94
108
  DATE_TWO_DIGIT_YEAR_MAX,
95
109
  DATE_TWO_DIGIT_YEAR_MIN,
96
- DAY,
97
- TIME_DELTA_MAX,
98
- TIME_DELTA_MIN,
99
110
  DatePeriod,
100
111
  TimePeriod,
101
112
  ZonedDateTimePeriod,
102
- get_now,
103
113
  to_date_time_delta,
104
114
  to_days,
105
115
  to_nanoseconds,
106
116
  )
107
- from utilities.zoneinfo import UTC, to_zone_info
108
117
 
109
118
  if TYPE_CHECKING:
110
119
  from collections.abc import Collection, Hashable, Iterable, Iterator
@@ -554,7 +563,7 @@ def floats_extra(
554
563
  @composite
555
564
  def git_repos(draw: DrawFn, /) -> Path:
556
565
  path = draw(temp_paths())
557
- with temp_cwd(path):
566
+ with yield_temp_cwd(path):
558
567
  _ = check_call(["git", "init", "-b", "master"])
559
568
  _ = check_call(["git", "config", "user.name", "User"])
560
569
  _ = check_call(["git", "config", "user.email", "a@z.com"])
@@ -1057,7 +1066,7 @@ def setup_hypothesis_profiles(
1057
1066
  suppress_health_check=suppress_health_check,
1058
1067
  verbosity=profile.verbosity,
1059
1068
  )
1060
- profile = get_env_var("HYPOTHESIS_PROFILE", default=Profile.default.name)
1069
+ profile = get_env("HYPOTHESIS_PROFILE", default=Profile.default.name)
1061
1070
  settings.load_profile(profile)
1062
1071
 
1063
1072
 
@@ -1484,12 +1493,25 @@ def urls(
1484
1493
 
1485
1494
 
1486
1495
  @composite
1487
- def versions(draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False) -> Version:
1488
- """Strategy for generating versions."""
1496
+ def version2s(
1497
+ draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False
1498
+ ) -> Version2:
1499
+ """Strategy for generating Version2 objects."""
1500
+ major, minor = draw(pairs(integers(min_value=0)))
1501
+ _ = assume((major >= 1) or (minor >= 1))
1502
+ suffix_use = draw(text_ascii(min_size=1)) if draw2(draw, suffix) else None
1503
+ return Version2(major=major, minor=minor, suffix=suffix_use)
1504
+
1505
+
1506
+ @composite
1507
+ def version3s(
1508
+ draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False
1509
+ ) -> Version3:
1510
+ """Strategy for generating Version3 objects."""
1489
1511
  major, minor, patch = draw(triples(integers(min_value=0)))
1490
1512
  _ = assume((major >= 1) or (minor >= 1) or (patch >= 1))
1491
1513
  suffix_use = draw(text_ascii(min_size=1)) if draw2(draw, suffix) else None
1492
- return Version(major=major, minor=minor, patch=patch, suffix=suffix_use)
1514
+ return Version3(major=major, minor=minor, patch=patch, suffix=suffix_use)
1493
1515
 
1494
1516
 
1495
1517
  ##
@@ -1671,7 +1693,8 @@ __all__ = [
1671
1693
  "uint32s",
1672
1694
  "uint64s",
1673
1695
  "urls",
1674
- "versions",
1696
+ "version2s",
1697
+ "version3s",
1675
1698
  "year_months",
1676
1699
  "zone_infos",
1677
1700
  "zoned_date_time_periods",