dycw-utilities 0.148.4__py3-none-any.whl → 0.174.12__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.

Potentially problematic release.


This version of dycw-utilities might be problematic. Click here for more details.

Files changed (83) hide show
  1. dycw_utilities-0.174.12.dist-info/METADATA +41 -0
  2. dycw_utilities-0.174.12.dist-info/RECORD +104 -0
  3. dycw_utilities-0.174.12.dist-info/WHEEL +4 -0
  4. {dycw_utilities-0.148.4.dist-info → dycw_utilities-0.174.12.dist-info}/entry_points.txt +3 -0
  5. utilities/__init__.py +1 -1
  6. utilities/{eventkit.py → aeventkit.py} +12 -11
  7. utilities/altair.py +7 -6
  8. utilities/asyncio.py +113 -64
  9. utilities/atomicwrites.py +1 -1
  10. utilities/atools.py +64 -4
  11. utilities/cachetools.py +9 -6
  12. utilities/click.py +145 -49
  13. utilities/concurrent.py +1 -1
  14. utilities/contextlib.py +4 -2
  15. utilities/contextvars.py +20 -1
  16. utilities/cryptography.py +3 -3
  17. utilities/dataclasses.py +15 -28
  18. utilities/docker.py +292 -0
  19. utilities/enum.py +2 -2
  20. utilities/errors.py +1 -1
  21. utilities/fastapi.py +8 -3
  22. utilities/fpdf2.py +2 -2
  23. utilities/functions.py +20 -297
  24. utilities/git.py +19 -0
  25. utilities/grp.py +28 -0
  26. utilities/hypothesis.py +360 -78
  27. utilities/inflect.py +1 -1
  28. utilities/iterables.py +12 -58
  29. utilities/jinja2.py +148 -0
  30. utilities/json.py +1 -1
  31. utilities/libcst.py +7 -7
  32. utilities/logging.py +74 -85
  33. utilities/math.py +8 -4
  34. utilities/more_itertools.py +4 -6
  35. utilities/operator.py +1 -1
  36. utilities/orjson.py +86 -34
  37. utilities/os.py +49 -2
  38. utilities/parse.py +2 -2
  39. utilities/pathlib.py +66 -34
  40. utilities/permissions.py +297 -0
  41. utilities/platform.py +5 -5
  42. utilities/polars.py +932 -420
  43. utilities/polars_ols.py +1 -1
  44. utilities/postgres.py +299 -174
  45. utilities/pottery.py +8 -73
  46. utilities/pqdm.py +3 -3
  47. utilities/pwd.py +28 -0
  48. utilities/pydantic.py +11 -0
  49. utilities/pydantic_settings.py +240 -0
  50. utilities/pydantic_settings_sops.py +76 -0
  51. utilities/pyinstrument.py +5 -5
  52. utilities/pytest.py +155 -46
  53. utilities/pytest_plugins/pytest_randomly.py +1 -1
  54. utilities/pytest_plugins/pytest_regressions.py +7 -3
  55. utilities/pytest_regressions.py +2 -3
  56. utilities/random.py +11 -6
  57. utilities/re.py +1 -1
  58. utilities/redis.py +101 -64
  59. utilities/sentinel.py +10 -0
  60. utilities/shelve.py +4 -1
  61. utilities/shutil.py +25 -0
  62. utilities/slack_sdk.py +8 -3
  63. utilities/sqlalchemy.py +422 -352
  64. utilities/sqlalchemy_polars.py +28 -52
  65. utilities/string.py +1 -1
  66. utilities/subprocess.py +864 -0
  67. utilities/tempfile.py +62 -4
  68. utilities/testbook.py +50 -0
  69. utilities/text.py +165 -42
  70. utilities/timer.py +2 -2
  71. utilities/traceback.py +46 -36
  72. utilities/types.py +62 -23
  73. utilities/typing.py +479 -19
  74. utilities/uuid.py +42 -5
  75. utilities/version.py +27 -26
  76. utilities/whenever.py +661 -151
  77. utilities/zoneinfo.py +80 -22
  78. dycw_utilities-0.148.4.dist-info/METADATA +0 -41
  79. dycw_utilities-0.148.4.dist-info/RECORD +0 -95
  80. dycw_utilities-0.148.4.dist-info/WHEEL +0 -4
  81. dycw_utilities-0.148.4.dist-info/licenses/LICENSE +0 -21
  82. utilities/period.py +0 -237
  83. utilities/typed_settings.py +0 -144
utilities/functions.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Callable, Iterable, Iterator, Sequence
4
- from dataclasses import asdict, dataclass, is_dataclass
3
+ from collections.abc import Callable, Iterable, Iterator
4
+ from dataclasses import asdict, dataclass
5
5
  from functools import _lru_cache_wrapper, cached_property, partial, reduce, wraps
6
6
  from inspect import getattr_static
7
7
  from pathlib import Path
@@ -14,33 +14,16 @@ from types import (
14
14
  MethodWrapperType,
15
15
  WrapperDescriptorType,
16
16
  )
17
- from typing import (
18
- TYPE_CHECKING,
19
- Any,
20
- Literal,
21
- TypeGuard,
22
- assert_never,
23
- cast,
24
- overload,
25
- override,
26
- )
17
+ from typing import TYPE_CHECKING, Any, Literal, TypeGuard, cast, overload, override
27
18
 
28
19
  from whenever import Date, PlainDateTime, Time, TimeDelta, ZonedDateTime
29
20
 
30
21
  from utilities.reprlib import get_repr, get_repr_and_class
31
- from utilities.sentinel import Sentinel, sentinel
32
- from utilities.types import (
33
- Dataclass,
34
- MaybeCallableBool,
35
- Number,
36
- StrMapping,
37
- SupportsRichComparison,
38
- TupleOrStrMapping,
39
- TypeLike,
40
- )
22
+ from utilities.sentinel import Sentinel, is_sentinel, sentinel
23
+ from utilities.types import Dataclass, Number, SupportsRichComparison, TypeLike
41
24
 
42
25
  if TYPE_CHECKING:
43
- from collections.abc import Container, Hashable, Sized
26
+ from collections.abc import Container
44
27
 
45
28
 
46
29
  def apply_decorators[F1: Callable, F2: Callable](
@@ -240,25 +223,6 @@ class EnsureFloatError(Exception):
240
223
  ##
241
224
 
242
225
 
243
- def ensure_hashable(obj: Any, /) -> Hashable:
244
- """Ensure an object is hashable."""
245
- if is_hashable(obj):
246
- return obj
247
- raise EnsureHashableError(obj=obj)
248
-
249
-
250
- @dataclass(kw_only=True, slots=True)
251
- class EnsureHashableError(Exception):
252
- obj: Any
253
-
254
- @override
255
- def __str__(self) -> str:
256
- return _make_error_msg(self.obj, "hashable")
257
-
258
-
259
- ##
260
-
261
-
262
226
  @overload
263
227
  def ensure_int(obj: Any, /, *, nullable: bool) -> int | None: ...
264
228
  @overload
@@ -415,44 +379,6 @@ class EnsurePlainDateTimeError(Exception):
415
379
  ##
416
380
 
417
381
 
418
- def ensure_sized(obj: Any, /) -> Sized:
419
- """Ensure an object is sized."""
420
- if is_sized(obj):
421
- return obj
422
- raise EnsureSizedError(obj=obj)
423
-
424
-
425
- @dataclass(kw_only=True, slots=True)
426
- class EnsureSizedError(Exception):
427
- obj: Any
428
-
429
- @override
430
- def __str__(self) -> str:
431
- return _make_error_msg(self.obj, "sized")
432
-
433
-
434
- ##
435
-
436
-
437
- def ensure_sized_not_str(obj: Any, /) -> Sized:
438
- """Ensure an object is sized, but not a string."""
439
- if is_sized_not_str(obj):
440
- return obj
441
- raise EnsureSizedNotStrError(obj=obj)
442
-
443
-
444
- @dataclass(kw_only=True, slots=True)
445
- class EnsureSizedNotStrError(Exception):
446
- obj: Any
447
-
448
- @override
449
- def __str__(self) -> str:
450
- return _make_error_msg(self.obj, "sized and not a string")
451
-
452
-
453
- ##
454
-
455
-
456
382
  @overload
457
383
  def ensure_str(obj: Any, /, *, nullable: bool) -> str | None: ...
458
384
  @overload
@@ -643,67 +569,7 @@ def identity[T](obj: T, /) -> T:
643
569
  ##
644
570
 
645
571
 
646
- def is_dataclass_class(obj: Any, /) -> TypeGuard[type[Dataclass]]:
647
- """Check if an object is a dataclass."""
648
- return isinstance(obj, type) and is_dataclass(obj)
649
-
650
-
651
- ##
652
-
653
-
654
- def is_dataclass_instance(obj: Any, /) -> TypeGuard[Dataclass]:
655
- """Check if an object is an instance of a dataclass."""
656
- return (not isinstance(obj, type)) and is_dataclass(obj)
657
-
658
-
659
- ##
660
-
661
-
662
- def is_hashable(obj: Any, /) -> TypeGuard[Hashable]:
663
- """Check if an object is hashable."""
664
- try:
665
- _ = hash(obj)
666
- except TypeError:
667
- return False
668
- return True
669
-
670
-
671
- ##
672
-
673
-
674
- @overload
675
- def is_iterable_of[T](obj: Any, cls: type[T], /) -> TypeGuard[Iterable[T]]: ...
676
- @overload
677
- def is_iterable_of[T1](
678
- obj: Any, cls: tuple[type[T1]], /
679
- ) -> TypeGuard[Iterable[T1]]: ...
680
- @overload
681
- def is_iterable_of[T1, T2](
682
- obj: Any, cls: tuple[type[T1], type[T2]], /
683
- ) -> TypeGuard[Iterable[T1 | T2]]: ...
684
- @overload
685
- def is_iterable_of[T1, T2, T3](
686
- obj: Any, cls: tuple[type[T1], type[T2], type[T3]], /
687
- ) -> TypeGuard[Iterable[T1 | T2 | T3]]: ...
688
- @overload
689
- def is_iterable_of[T1, T2, T3, T4](
690
- obj: Any, cls: tuple[type[T1], type[T2], type[T3], type[T4]], /
691
- ) -> TypeGuard[Iterable[T1 | T2 | T3 | T4]]: ...
692
- @overload
693
- def is_iterable_of[T1, T2, T3, T4, T5](
694
- obj: Any, cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]], /
695
- ) -> TypeGuard[Iterable[T1 | T2 | T3 | T4 | T5]]: ...
696
- @overload
697
- def is_iterable_of[T](obj: Any, cls: TypeLike[T], /) -> TypeGuard[Iterable[T]]: ...
698
- def is_iterable_of[T](obj: Any, cls: TypeLike[T], /) -> TypeGuard[Iterable[T]]:
699
- """Check if an object is a iterable of tuple or string mappings."""
700
- return isinstance(obj, Iterable) and all(map(make_isinstance(cls), obj))
701
-
702
-
703
- ##
704
-
705
-
706
- def is_none(obj: Any, /) -> bool:
572
+ def is_none(obj: Any, /) -> TypeGuard[None]:
707
573
  """Check if an object is `None`."""
708
574
  return obj is None
709
575
 
@@ -719,126 +585,6 @@ def is_not_none(obj: Any, /) -> bool:
719
585
  ##
720
586
 
721
587
 
722
- @overload
723
- def is_sequence_of[T](obj: Any, cls: type[T], /) -> TypeGuard[Sequence[T]]: ...
724
- @overload
725
- def is_sequence_of[T1](
726
- obj: Any, cls: tuple[type[T1]], /
727
- ) -> TypeGuard[Sequence[T1]]: ...
728
- @overload
729
- def is_sequence_of[T1, T2](
730
- obj: Any, cls: tuple[type[T1], type[T2]], /
731
- ) -> TypeGuard[Sequence[T1 | T2]]: ...
732
- @overload
733
- def is_sequence_of[T1, T2, T3](
734
- obj: Any, cls: tuple[type[T1], type[T2], type[T3]], /
735
- ) -> TypeGuard[Sequence[T1 | T2 | T3]]: ...
736
- @overload
737
- def is_sequence_of[T1, T2, T3, T4](
738
- obj: Any, cls: tuple[type[T1], type[T2], type[T3], type[T4]], /
739
- ) -> TypeGuard[Sequence[T1 | T2 | T3 | T4]]: ...
740
- @overload
741
- def is_sequence_of[T1, T2, T3, T4, T5](
742
- obj: Any, cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]], /
743
- ) -> TypeGuard[Sequence[T1 | T2 | T3 | T4 | T5]]: ...
744
- @overload
745
- def is_sequence_of[T](obj: Any, cls: TypeLike[T], /) -> TypeGuard[Sequence[T]]: ...
746
- def is_sequence_of[T](obj: Any, cls: TypeLike[T], /) -> TypeGuard[Sequence[T]]:
747
- """Check if an object is a sequence of tuple or string mappings."""
748
- return isinstance(obj, Sequence) and is_iterable_of(obj, cls)
749
-
750
-
751
- ##
752
-
753
-
754
- def is_sequence_of_tuple_or_str_mapping(
755
- obj: Any, /
756
- ) -> TypeGuard[Sequence[TupleOrStrMapping]]:
757
- """Check if an object is a sequence of tuple or string mappings."""
758
- return isinstance(obj, Sequence) and all(map(is_tuple_or_str_mapping, obj))
759
-
760
-
761
- ##
762
-
763
-
764
- def is_sized(obj: Any, /) -> TypeGuard[Sized]:
765
- """Check if an object is sized."""
766
- try:
767
- _ = len(obj)
768
- except TypeError:
769
- return False
770
- return True
771
-
772
-
773
- ##
774
-
775
-
776
- def is_sized_not_str(obj: Any, /) -> TypeGuard[Sized]:
777
- """Check if an object is sized, but not a string."""
778
- return is_sized(obj) and not isinstance(obj, str)
779
-
780
-
781
- ##
782
-
783
-
784
- def is_string_mapping(obj: Any, /) -> TypeGuard[StrMapping]:
785
- """Check if an object is a string mapping."""
786
- return isinstance(obj, dict) and is_iterable_of(obj, str)
787
-
788
-
789
- ##
790
-
791
-
792
- def is_tuple(obj: Any, /) -> TypeGuard[tuple[Any, ...]]:
793
- """Check if an object is a tuple or string mapping."""
794
- return make_isinstance(tuple)(obj)
795
-
796
-
797
- ##
798
-
799
-
800
- def is_tuple_or_str_mapping(obj: Any, /) -> TypeGuard[TupleOrStrMapping]:
801
- """Check if an object is a tuple or string mapping."""
802
- return is_tuple(obj) or is_string_mapping(obj)
803
-
804
-
805
- ##
806
-
807
-
808
- @overload
809
- def make_isinstance[T](cls: type[T], /) -> Callable[[Any], TypeGuard[T]]: ...
810
- @overload
811
- def make_isinstance[T1](cls: tuple[type[T1]], /) -> Callable[[Any], TypeGuard[T1]]: ...
812
- @overload
813
- def make_isinstance[T1, T2](
814
- cls: tuple[type[T1], type[T2]], /
815
- ) -> Callable[[Any], TypeGuard[T1 | T2]]: ...
816
- @overload
817
- def make_isinstance[T1, T2, T3](
818
- cls: tuple[type[T1], type[T2], type[T3]], /
819
- ) -> Callable[[Any], TypeGuard[T1 | T2 | T3]]: ...
820
- @overload
821
- def make_isinstance[T1, T2, T3, T4](
822
- cls: tuple[type[T1], type[T2], type[T3], type[T4]], /
823
- ) -> Callable[[Any], TypeGuard[T1 | T2 | T3 | T4]]: ...
824
- @overload
825
- def make_isinstance[T1, T2, T3, T4, T5](
826
- cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]], /
827
- ) -> Callable[[Any], TypeGuard[T1 | T2 | T3 | T4 | T5]]: ...
828
- @overload
829
- def make_isinstance[T](cls: TypeLike[T], /) -> Callable[[Any], TypeGuard[T]]: ...
830
- def make_isinstance[T](cls: TypeLike[T], /) -> Callable[[Any], TypeGuard[T]]:
831
- """Make a curried `isinstance` function."""
832
- return partial(_make_instance_core, cls=cls)
833
-
834
-
835
- def _make_instance_core[T](obj: Any, /, *, cls: TypeLike[T]) -> TypeGuard[T]:
836
- return isinstance(obj, cls)
837
-
838
-
839
- ##
840
-
841
-
842
588
  def map_object[T](
843
589
  func: Callable[[Any], Any], obj: T, /, *, before: Callable[[Any], Any] | None = None
844
590
  ) -> T:
@@ -874,7 +620,7 @@ def min_nullable[T: SupportsRichComparison, U](
874
620
  ) -> T | U:
875
621
  """Compute the minimum of a set of values; ignoring nulls."""
876
622
  values = (i for i in iterable if i is not None)
877
- if isinstance(default, Sentinel):
623
+ if is_sentinel(default):
878
624
  try:
879
625
  return min(values)
880
626
  except ValueError:
@@ -904,7 +650,7 @@ def max_nullable[T: SupportsRichComparison, U](
904
650
  ) -> T | U:
905
651
  """Compute the maximum of a set of values; ignoring nulls."""
906
652
  values = (i for i in iterable if i is not None)
907
- if isinstance(default, Sentinel):
653
+ if is_sentinel(default):
908
654
  try:
909
655
  return max(values)
910
656
  except ValueError:
@@ -945,23 +691,16 @@ def second[U](pair: tuple[Any, U], /) -> U:
945
691
  ##
946
692
 
947
693
 
948
- @overload
949
- def to_bool(*, bool_: MaybeCallableBool) -> bool: ...
950
- @overload
951
- def to_bool(*, bool_: None) -> None: ...
952
- @overload
953
- def to_bool(*, bool_: Sentinel) -> Sentinel: ...
954
- def to_bool(
955
- *, bool_: MaybeCallableBool | None | Sentinel = sentinel
956
- ) -> bool | None | Sentinel:
957
- """Get the bool."""
958
- match bool_:
959
- case bool() | None | Sentinel():
960
- return bool_
961
- case Callable() as func:
962
- return to_bool(bool_=func())
963
- case _ as never:
964
- assert_never(never)
694
+ def skip_if_optimize[**P](func: Callable[P, None], /) -> Callable[P, None]:
695
+ """Skip a function if we are in the optimized mode."""
696
+ if __debug__:
697
+ return func
698
+
699
+ @wraps(func)
700
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> None:
701
+ _ = (args, kwargs)
702
+
703
+ return wrapped
965
704
 
966
705
 
967
706
  ##
@@ -1017,15 +756,12 @@ __all__ = [
1017
756
  "EnsureClassError",
1018
757
  "EnsureDateError",
1019
758
  "EnsureFloatError",
1020
- "EnsureHashableError",
1021
759
  "EnsureIntError",
1022
760
  "EnsureMemberError",
1023
761
  "EnsureNotNoneError",
1024
762
  "EnsureNumberError",
1025
763
  "EnsurePathError",
1026
764
  "EnsurePlainDateTimeError",
1027
- "EnsureSizedError",
1028
- "EnsureSizedNotStrError",
1029
765
  "EnsureStrError",
1030
766
  "EnsureTimeDeltaError",
1031
767
  "EnsureTimeError",
@@ -1038,15 +774,12 @@ __all__ = [
1038
774
  "ensure_class",
1039
775
  "ensure_date",
1040
776
  "ensure_float",
1041
- "ensure_hashable",
1042
777
  "ensure_int",
1043
778
  "ensure_member",
1044
779
  "ensure_not_none",
1045
780
  "ensure_number",
1046
781
  "ensure_path",
1047
782
  "ensure_plain_date_time",
1048
- "ensure_sized",
1049
- "ensure_sized_not_str",
1050
783
  "ensure_str",
1051
784
  "ensure_time",
1052
785
  "ensure_time_delta",
@@ -1057,24 +790,14 @@ __all__ = [
1057
790
  "get_func_name",
1058
791
  "get_func_qualname",
1059
792
  "identity",
1060
- "is_dataclass_class",
1061
- "is_dataclass_instance",
1062
- "is_hashable",
1063
- "is_iterable_of",
1064
793
  "is_none",
1065
794
  "is_not_none",
1066
- "is_sequence_of_tuple_or_str_mapping",
1067
- "is_sized",
1068
- "is_sized_not_str",
1069
- "is_string_mapping",
1070
- "is_tuple",
1071
- "is_tuple_or_str_mapping",
1072
- "make_isinstance",
1073
795
  "map_object",
1074
796
  "max_nullable",
1075
797
  "min_nullable",
1076
798
  "not_func",
1077
799
  "second",
800
+ "skip_if_optimize",
1078
801
  "yield_object_attributes",
1079
802
  "yield_object_cached_properties",
1080
803
  "yield_object_properties",
utilities/git.py ADDED
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING
5
+
6
+ from git import Repo
7
+
8
+ from utilities.pathlib import to_path
9
+
10
+ if TYPE_CHECKING:
11
+ from utilities.types import MaybeCallablePathLike
12
+
13
+
14
+ def get_repo(path: MaybeCallablePathLike = Path.cwd, /) -> Repo:
15
+ """Get the repo object."""
16
+ return Repo(to_path(path), search_parent_directories=True)
17
+
18
+
19
+ __all__ = ["get_repo"]
utilities/grp.py ADDED
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import assert_never
4
+
5
+ from utilities.os import EFFECTIVE_GROUP_ID
6
+ from utilities.platform import SYSTEM
7
+
8
+
9
+ def get_gid_name(gid: int, /) -> str | None:
10
+ """Get the name of a group."""
11
+ match SYSTEM:
12
+ case "windows": # skipif-not-windows
13
+ return None
14
+ case "mac" | "linux":
15
+ from grp import getgrgid
16
+
17
+ return getgrgid(gid).gr_name
18
+ case never:
19
+ assert_never(never)
20
+
21
+
22
+ ROOT_GROUP_NAME = get_gid_name(0)
23
+ EFFECTIVE_GROUP_NAME = (
24
+ None if EFFECTIVE_GROUP_ID is None else get_gid_name(EFFECTIVE_GROUP_ID)
25
+ )
26
+
27
+
28
+ __all__ = ["EFFECTIVE_GROUP_NAME", "ROOT_GROUP_NAME", "get_gid_name"]