dycw-utilities 0.166.30__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.
- dycw_utilities-0.185.8.dist-info/METADATA +33 -0
- dycw_utilities-0.185.8.dist-info/RECORD +90 -0
- {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +1 -1
- {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +1 -0
- utilities/__init__.py +1 -1
- utilities/altair.py +17 -10
- utilities/asyncio.py +50 -72
- utilities/atools.py +9 -11
- utilities/cachetools.py +16 -11
- utilities/click.py +76 -19
- utilities/concurrent.py +1 -1
- utilities/constants.py +492 -0
- utilities/contextlib.py +23 -30
- utilities/contextvars.py +1 -23
- utilities/core.py +2581 -0
- utilities/dataclasses.py +16 -119
- utilities/docker.py +387 -0
- utilities/enum.py +1 -1
- utilities/errors.py +2 -16
- utilities/fastapi.py +5 -5
- utilities/fpdf2.py +2 -1
- utilities/functions.py +34 -265
- utilities/http.py +2 -3
- utilities/hypothesis.py +84 -29
- utilities/importlib.py +17 -1
- utilities/iterables.py +39 -575
- utilities/jinja2.py +145 -0
- utilities/jupyter.py +5 -3
- utilities/libcst.py +1 -1
- utilities/lightweight_charts.py +4 -6
- utilities/logging.py +24 -24
- utilities/math.py +1 -36
- utilities/more_itertools.py +4 -6
- utilities/numpy.py +2 -1
- utilities/operator.py +2 -2
- utilities/orjson.py +42 -43
- utilities/os.py +4 -147
- utilities/packaging.py +129 -0
- utilities/parse.py +35 -15
- utilities/pathlib.py +3 -120
- utilities/platform.py +8 -90
- utilities/polars.py +38 -32
- utilities/postgres.py +37 -33
- utilities/pottery.py +20 -18
- utilities/pqdm.py +3 -4
- utilities/psutil.py +2 -3
- utilities/pydantic.py +25 -0
- utilities/pydantic_settings.py +87 -16
- utilities/pydantic_settings_sops.py +16 -3
- utilities/pyinstrument.py +4 -4
- utilities/pytest.py +96 -125
- utilities/pytest_plugins/pytest_regressions.py +2 -2
- utilities/pytest_regressions.py +32 -11
- utilities/random.py +2 -8
- utilities/redis.py +98 -94
- utilities/reprlib.py +11 -118
- utilities/shellingham.py +66 -0
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +13 -12
- utilities/sqlalchemy.py +57 -30
- utilities/sqlalchemy_polars.py +16 -25
- utilities/subprocess.py +2590 -0
- utilities/tabulate.py +32 -0
- utilities/testbook.py +8 -8
- utilities/text.py +24 -99
- utilities/throttle.py +159 -0
- utilities/time.py +18 -0
- utilities/timer.py +31 -14
- utilities/traceback.py +16 -23
- utilities/types.py +42 -2
- utilities/typing.py +26 -14
- utilities/uuid.py +1 -1
- utilities/version.py +202 -45
- utilities/whenever.py +53 -150
- dycw_utilities-0.166.30.dist-info/METADATA +0 -41
- dycw_utilities-0.166.30.dist-info/RECORD +0 -98
- dycw_utilities-0.166.30.dist-info/licenses/LICENSE +0 -21
- utilities/aeventkit.py +0 -388
- utilities/atomicwrites.py +0 -182
- utilities/cryptography.py +0 -41
- utilities/getpass.py +0 -8
- utilities/git.py +0 -19
- utilities/gzip.py +0 -31
- utilities/json.py +0 -70
- utilities/pickle.py +0 -25
- utilities/re.py +0 -156
- utilities/sentinel.py +0 -73
- utilities/socket.py +0 -8
- utilities/string.py +0 -20
- utilities/tempfile.py +0 -77
- utilities/typed_settings.py +0 -152
- utilities/tzdata.py +0 -11
- utilities/tzlocal.py +0 -28
- utilities/warnings.py +0 -65
- utilities/zipfile.py +0 -25
- utilities/zoneinfo.py +0 -133
utilities/functions.py
CHANGED
|
@@ -1,43 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from
|
|
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
|
|
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.
|
|
22
|
-
from utilities.
|
|
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 {
|
|
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
|
|
489
|
-
"""
|
|
490
|
-
return
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
|
547
|
-
"""
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
##
|
|
@@ -693,7 +494,7 @@ def second[U](pair: tuple[Any, U], /) -> U:
|
|
|
693
494
|
|
|
694
495
|
def skip_if_optimize[**P](func: Callable[P, None], /) -> Callable[P, None]:
|
|
695
496
|
"""Skip a function if we are in the optimized mode."""
|
|
696
|
-
if __debug__:
|
|
497
|
+
if __debug__: # pragma: no cover
|
|
697
498
|
return func
|
|
698
499
|
|
|
699
500
|
@wraps(func)
|
|
@@ -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"{
|
|
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
|
-
"
|
|
788
|
-
"
|
|
789
|
-
"
|
|
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
|
-
@
|
|
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.
|
|
55
|
-
|
|
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,37 +78,42 @@ from utilities.math import (
|
|
|
73
78
|
MIN_UINT16,
|
|
74
79
|
MIN_UINT32,
|
|
75
80
|
MIN_UINT64,
|
|
76
|
-
|
|
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.
|
|
79
|
-
from utilities.
|
|
80
|
-
from utilities.
|
|
81
|
-
from utilities.
|
|
82
|
-
from utilities.tempfile import TEMP_DIR, TemporaryDirectory
|
|
83
|
-
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
|
|
84
103
|
from utilities.whenever import (
|
|
85
|
-
DATE_DELTA_MAX,
|
|
86
|
-
DATE_DELTA_MIN,
|
|
87
104
|
DATE_DELTA_PARSABLE_MAX,
|
|
88
105
|
DATE_DELTA_PARSABLE_MIN,
|
|
89
|
-
DATE_TIME_DELTA_MAX,
|
|
90
|
-
DATE_TIME_DELTA_MIN,
|
|
91
106
|
DATE_TIME_DELTA_PARSABLE_MAX,
|
|
92
107
|
DATE_TIME_DELTA_PARSABLE_MIN,
|
|
93
108
|
DATE_TWO_DIGIT_YEAR_MAX,
|
|
94
109
|
DATE_TWO_DIGIT_YEAR_MIN,
|
|
95
|
-
DAY,
|
|
96
|
-
TIME_DELTA_MAX,
|
|
97
|
-
TIME_DELTA_MIN,
|
|
98
110
|
DatePeriod,
|
|
99
111
|
TimePeriod,
|
|
100
112
|
ZonedDateTimePeriod,
|
|
101
|
-
get_now,
|
|
102
113
|
to_date_time_delta,
|
|
103
114
|
to_days,
|
|
104
115
|
to_nanoseconds,
|
|
105
116
|
)
|
|
106
|
-
from utilities.zoneinfo import UTC, to_zone_info
|
|
107
117
|
|
|
108
118
|
if TYPE_CHECKING:
|
|
109
119
|
from collections.abc import Collection, Hashable, Iterable, Iterator
|
|
@@ -553,7 +563,7 @@ def floats_extra(
|
|
|
553
563
|
@composite
|
|
554
564
|
def git_repos(draw: DrawFn, /) -> Path:
|
|
555
565
|
path = draw(temp_paths())
|
|
556
|
-
with
|
|
566
|
+
with yield_temp_cwd(path):
|
|
557
567
|
_ = check_call(["git", "init", "-b", "master"])
|
|
558
568
|
_ = check_call(["git", "config", "user.name", "User"])
|
|
559
569
|
_ = check_call(["git", "config", "user.email", "a@z.com"])
|
|
@@ -864,6 +874,38 @@ def _path_parts(draw: DrawFn, /) -> str:
|
|
|
864
874
|
##
|
|
865
875
|
|
|
866
876
|
|
|
877
|
+
@composite
|
|
878
|
+
def permissions(
|
|
879
|
+
draw: DrawFn,
|
|
880
|
+
/,
|
|
881
|
+
*,
|
|
882
|
+
user_read: MaybeSearchStrategy[bool | None] = None,
|
|
883
|
+
user_write: MaybeSearchStrategy[bool | None] = None,
|
|
884
|
+
user_execute: MaybeSearchStrategy[bool | None] = None,
|
|
885
|
+
group_read: MaybeSearchStrategy[bool | None] = None,
|
|
886
|
+
group_write: MaybeSearchStrategy[bool | None] = None,
|
|
887
|
+
group_execute: MaybeSearchStrategy[bool | None] = None,
|
|
888
|
+
others_read: MaybeSearchStrategy[bool | None] = None,
|
|
889
|
+
others_write: MaybeSearchStrategy[bool | None] = None,
|
|
890
|
+
others_execute: MaybeSearchStrategy[bool | None] = None,
|
|
891
|
+
) -> Permissions:
|
|
892
|
+
"""Strategy for generating `Permissions`."""
|
|
893
|
+
return Permissions(
|
|
894
|
+
user_read=draw2(draw, user_read, booleans()),
|
|
895
|
+
user_write=draw2(draw, user_write, booleans()),
|
|
896
|
+
user_execute=draw2(draw, user_execute, booleans()),
|
|
897
|
+
group_read=draw2(draw, group_read, booleans()),
|
|
898
|
+
group_write=draw2(draw, group_write, booleans()),
|
|
899
|
+
group_execute=draw2(draw, group_execute, booleans()),
|
|
900
|
+
others_read=draw2(draw, others_read, booleans()),
|
|
901
|
+
others_write=draw2(draw, others_write, booleans()),
|
|
902
|
+
others_execute=draw2(draw, others_execute, booleans()),
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
##
|
|
907
|
+
|
|
908
|
+
|
|
867
909
|
@composite
|
|
868
910
|
def plain_date_times(
|
|
869
911
|
draw: DrawFn,
|
|
@@ -1007,7 +1049,7 @@ def setup_hypothesis_profiles(
|
|
|
1007
1049
|
assert_never(never)
|
|
1008
1050
|
|
|
1009
1051
|
phases = {Phase.explicit, Phase.reuse, Phase.generate, Phase.target}
|
|
1010
|
-
if "HYPOTHESIS_NO_SHRINK" not in environ:
|
|
1052
|
+
if "HYPOTHESIS_NO_SHRINK" not in environ: # pragma: no cover
|
|
1011
1053
|
phases.add(Phase.shrink)
|
|
1012
1054
|
for profile in Profile:
|
|
1013
1055
|
try:
|
|
@@ -1024,7 +1066,7 @@ def setup_hypothesis_profiles(
|
|
|
1024
1066
|
suppress_health_check=suppress_health_check,
|
|
1025
1067
|
verbosity=profile.verbosity,
|
|
1026
1068
|
)
|
|
1027
|
-
profile =
|
|
1069
|
+
profile = get_env("HYPOTHESIS_PROFILE", default=Profile.default.name)
|
|
1028
1070
|
settings.load_profile(profile)
|
|
1029
1071
|
|
|
1030
1072
|
|
|
@@ -1128,9 +1170,7 @@ def temp_dirs(draw: DrawFn, /) -> TemporaryDirectory:
|
|
|
1128
1170
|
"""Search strategy for temporary directories."""
|
|
1129
1171
|
_TEMP_DIR_HYPOTHESIS.mkdir(exist_ok=True)
|
|
1130
1172
|
uuid = draw(uuids())
|
|
1131
|
-
return TemporaryDirectory(
|
|
1132
|
-
prefix=f"{uuid}__", dir=_TEMP_DIR_HYPOTHESIS, ignore_cleanup_errors=IS_WINDOWS
|
|
1133
|
-
)
|
|
1173
|
+
return TemporaryDirectory(prefix=f"{uuid}__", dir=_TEMP_DIR_HYPOTHESIS)
|
|
1134
1174
|
|
|
1135
1175
|
|
|
1136
1176
|
##
|
|
@@ -1453,12 +1493,25 @@ def urls(
|
|
|
1453
1493
|
|
|
1454
1494
|
|
|
1455
1495
|
@composite
|
|
1456
|
-
def
|
|
1457
|
-
|
|
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."""
|
|
1458
1511
|
major, minor, patch = draw(triples(integers(min_value=0)))
|
|
1459
1512
|
_ = assume((major >= 1) or (minor >= 1) or (patch >= 1))
|
|
1460
1513
|
suffix_use = draw(text_ascii(min_size=1)) if draw2(draw, suffix) else None
|
|
1461
|
-
return
|
|
1514
|
+
return Version3(major=major, minor=minor, patch=patch, suffix=suffix_use)
|
|
1462
1515
|
|
|
1463
1516
|
|
|
1464
1517
|
##
|
|
@@ -1570,7 +1623,7 @@ def zoned_date_times(
|
|
|
1570
1623
|
with (
|
|
1571
1624
|
assume_does_not_raise(RepeatedTime),
|
|
1572
1625
|
assume_does_not_raise(SkippedTime),
|
|
1573
|
-
assume_does_not_raise(ValueError, match=r"Resulting
|
|
1626
|
+
assume_does_not_raise(ValueError, match=r"Resulting time is out of range"),
|
|
1574
1627
|
):
|
|
1575
1628
|
zoned = plain.assume_tz(time_zone_.key, disambiguate="raise")
|
|
1576
1629
|
with assume_does_not_raise(OverflowError, match=r"date value out of range"):
|
|
@@ -1613,6 +1666,7 @@ __all__ = [
|
|
|
1613
1666
|
"numbers",
|
|
1614
1667
|
"pairs",
|
|
1615
1668
|
"paths",
|
|
1669
|
+
"permissions",
|
|
1616
1670
|
"plain_date_times",
|
|
1617
1671
|
"py_datetimes",
|
|
1618
1672
|
"quadruples",
|
|
@@ -1639,7 +1693,8 @@ __all__ = [
|
|
|
1639
1693
|
"uint32s",
|
|
1640
1694
|
"uint64s",
|
|
1641
1695
|
"urls",
|
|
1642
|
-
"
|
|
1696
|
+
"version2s",
|
|
1697
|
+
"version3s",
|
|
1643
1698
|
"year_months",
|
|
1644
1699
|
"zone_infos",
|
|
1645
1700
|
"zoned_date_time_periods",
|
utilities/importlib.py
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import importlib.resources
|
|
3
4
|
from importlib import import_module
|
|
4
5
|
from importlib.util import find_spec
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from utilities.errors import ImpossibleCaseError
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from importlib.resources import Anchor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def files(*, anchor: Anchor | None = None) -> Path:
|
|
16
|
+
"""Get the path for an anchor."""
|
|
17
|
+
path = importlib.resources.files(anchor)
|
|
18
|
+
if isinstance(path, Path):
|
|
19
|
+
return path
|
|
20
|
+
raise ImpossibleCaseError(case=[f"{path=}"]) # pragma: no cover
|
|
5
21
|
|
|
6
22
|
|
|
7
23
|
def is_valid_import(module: str, /, *, name: str | None = None) -> bool:
|
|
@@ -15,4 +31,4 @@ def is_valid_import(module: str, /, *, name: str | None = None) -> bool:
|
|
|
15
31
|
return hasattr(mod, name)
|
|
16
32
|
|
|
17
33
|
|
|
18
|
-
__all__ = ["is_valid_import"]
|
|
34
|
+
__all__ = ["files", "is_valid_import"]
|