ddutils 0.2.4__tar.gz → 0.3.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
1
  Metadata-Version: 2.4
2
2
  Name: ddutils
3
- Version: 0.2.4
3
+ Version: 0.3.0
4
4
  Summary: Domain Driven Utils Library
5
5
  Keywords: python,ddutils
6
6
  Author: davyddd
@@ -337,28 +337,34 @@ delta = timedelta(seconds=5, milliseconds=500)
337
337
  convert_timedelta_to_milliseconds(delta) # 5500
338
338
  ```
339
339
 
340
- ### convert_to_builtin
340
+ ### convert_to_base_value
341
341
 
342
- Casts a value to its builtin base type (see [get_builtin_base_type](#get_builtin_base_type)):
343
- a `str` subclass becomes a plain `str`, an `Enum` member becomes its `.value` cast the same way.
344
- Raises `TypeError` for a value without a builtin base.
342
+ Casts a value to its base value type (see [get_base_value_type](#get_base_value_type)):
343
+ a `str` subclass becomes a plain `str`, a `UUID` subclass a plain `UUID`, a `datetime` subclass a plain `datetime`
344
+ with the same `tzinfo` and `fold`, an `Enum` member becomes its `.value` cast the same way, `None` stays `None`.
345
+ Raises `TypeError` for a value without a base type.
346
+
347
+ Normalize a value once, then dispatch on the result with plain `isinstance` checks: subclasses, `Enum` members
348
+ and third-party types such as a `pendulum.DateTime` all land on the stdlib type.
345
349
 
346
350
  ```python
347
351
  from enum import Enum
348
- from ddutils.convertors import convert_to_builtin
352
+ from uuid import UUID
353
+ from ddutils.convertors import convert_to_base_value
349
354
 
350
355
 
351
356
  class Kind(str, Enum):
352
357
  A = 'a'
353
358
 
354
359
 
355
- class UserId(int):
360
+ class ProfileId(UUID):
356
361
  pass
357
362
 
358
363
 
359
- convert_to_builtin(Kind.A) # 'a' (not 'Kind.A', which `str(Kind.A)` would give)
360
- convert_to_builtin(UserId(2)) # 2
361
- convert_to_builtin(object()) # TypeError
364
+ convert_to_base_value(None) # None
365
+ convert_to_base_value(Kind.A) # 'a' (not 'Kind.A', which `str(Kind.A)` would give)
366
+ convert_to_base_value(ProfileId('12345678-1234-5678-1234-567812345678')) # UUID('1234...'), exact type UUID
367
+ convert_to_base_value(object()) # TypeError
362
368
  ```
363
369
 
364
370
  ## Datetime Helpers
@@ -526,29 +532,53 @@ Returns an iterator of `ExceptionInfo` objects describing each raised exception.
526
532
 
527
533
  Runtime counterparts of the annotation helpers: they inspect values, not type hints.
528
534
 
529
- ### get_builtin_base_type
535
+ ### get_base_value_type
530
536
 
531
- Returns the builtin type a value should be treated as, looking past subclasses: `bool`, `int`, `float`, `str` or `bytes`
532
- (`bool` is checked before `int`). An `Enum` member is looked at through its `.value`.
533
- Raises `TypeError` for any other value. Useful for APIs that dispatch on the exact type (serializers, query builders).
537
+ Returns the base value type a value should be treated as, looking past subclasses. The candidates, in order,
538
+ are `BASE_VALUE_TYPES`: `NoneType`, `bool`, `int`, `float`, `str`, `bytes`, `bytearray`, `datetime`, `date`, `time`,
539
+ `timedelta`, `UUID`, `Decimal` (`bool` is checked before `int`, `datetime` before `date`). An `Enum` member is looked at
540
+ through its `.value`. Raises `TypeError` for any other value, a collection or a mapping included. Useful for APIs that dispatch on the exact type
541
+ (serializers, query builders).
534
542
 
535
543
  ```python
544
+ from datetime import datetime
536
545
  from enum import Enum
537
- from ddutils.value_helpers import get_builtin_base_type
546
+ from uuid import UUID
547
+ from ddutils.value_helpers import get_base_value_type
538
548
 
539
549
 
540
550
  class Kind(str, Enum):
541
551
  A = 'a'
542
552
 
543
553
 
544
- class UserId(int):
554
+ class ProfileId(UUID):
545
555
  pass
546
556
 
547
557
 
548
- get_builtin_base_type(UserId(2)) # <class 'int'>
549
- get_builtin_base_type(True) # <class 'bool'>
550
- get_builtin_base_type(Kind.A) # <class 'str'>
551
- get_builtin_base_type(object()) # TypeError
558
+ get_base_value_type(None) # <class 'NoneType'>
559
+ get_base_value_type(True) # <class 'bool'>
560
+ get_base_value_type(Kind.A) # <class 'str'>
561
+ get_base_value_type(ProfileId('12345678-1234-5678-1234-567812345678')) # <class 'uuid.UUID'>
562
+ get_base_value_type(datetime.now()) # <class 'datetime.datetime'>
563
+ get_base_value_type(object()) # TypeError
564
+ ```
565
+
566
+ ### unwrap_enum
567
+
568
+ Returns the `.value` of an `Enum` member and any other value as is. `str(Kind.A)` of a `class Kind(str, Enum)`
569
+ is `'Kind.A'`, so a member has to be unwrapped before it is formatted or cast.
570
+
571
+ ```python
572
+ from enum import Enum
573
+ from ddutils.value_helpers import unwrap_enum
574
+
575
+
576
+ class Kind(str, Enum):
577
+ A = 'a'
578
+
579
+
580
+ unwrap_enum(Kind.A) # 'a'
581
+ unwrap_enum('a') # 'a'
552
582
  ```
553
583
 
554
584
  ### is_complex_sequence_value
@@ -319,28 +319,34 @@ delta = timedelta(seconds=5, milliseconds=500)
319
319
  convert_timedelta_to_milliseconds(delta) # 5500
320
320
  ```
321
321
 
322
- ### convert_to_builtin
322
+ ### convert_to_base_value
323
323
 
324
- Casts a value to its builtin base type (see [get_builtin_base_type](#get_builtin_base_type)):
325
- a `str` subclass becomes a plain `str`, an `Enum` member becomes its `.value` cast the same way.
326
- Raises `TypeError` for a value without a builtin base.
324
+ Casts a value to its base value type (see [get_base_value_type](#get_base_value_type)):
325
+ a `str` subclass becomes a plain `str`, a `UUID` subclass a plain `UUID`, a `datetime` subclass a plain `datetime`
326
+ with the same `tzinfo` and `fold`, an `Enum` member becomes its `.value` cast the same way, `None` stays `None`.
327
+ Raises `TypeError` for a value without a base type.
328
+
329
+ Normalize a value once, then dispatch on the result with plain `isinstance` checks: subclasses, `Enum` members
330
+ and third-party types such as a `pendulum.DateTime` all land on the stdlib type.
327
331
 
328
332
  ```python
329
333
  from enum import Enum
330
- from ddutils.convertors import convert_to_builtin
334
+ from uuid import UUID
335
+ from ddutils.convertors import convert_to_base_value
331
336
 
332
337
 
333
338
  class Kind(str, Enum):
334
339
  A = 'a'
335
340
 
336
341
 
337
- class UserId(int):
342
+ class ProfileId(UUID):
338
343
  pass
339
344
 
340
345
 
341
- convert_to_builtin(Kind.A) # 'a' (not 'Kind.A', which `str(Kind.A)` would give)
342
- convert_to_builtin(UserId(2)) # 2
343
- convert_to_builtin(object()) # TypeError
346
+ convert_to_base_value(None) # None
347
+ convert_to_base_value(Kind.A) # 'a' (not 'Kind.A', which `str(Kind.A)` would give)
348
+ convert_to_base_value(ProfileId('12345678-1234-5678-1234-567812345678')) # UUID('1234...'), exact type UUID
349
+ convert_to_base_value(object()) # TypeError
344
350
  ```
345
351
 
346
352
  ## Datetime Helpers
@@ -508,29 +514,53 @@ Returns an iterator of `ExceptionInfo` objects describing each raised exception.
508
514
 
509
515
  Runtime counterparts of the annotation helpers: they inspect values, not type hints.
510
516
 
511
- ### get_builtin_base_type
517
+ ### get_base_value_type
512
518
 
513
- Returns the builtin type a value should be treated as, looking past subclasses: `bool`, `int`, `float`, `str` or `bytes`
514
- (`bool` is checked before `int`). An `Enum` member is looked at through its `.value`.
515
- Raises `TypeError` for any other value. Useful for APIs that dispatch on the exact type (serializers, query builders).
519
+ Returns the base value type a value should be treated as, looking past subclasses. The candidates, in order,
520
+ are `BASE_VALUE_TYPES`: `NoneType`, `bool`, `int`, `float`, `str`, `bytes`, `bytearray`, `datetime`, `date`, `time`,
521
+ `timedelta`, `UUID`, `Decimal` (`bool` is checked before `int`, `datetime` before `date`). An `Enum` member is looked at
522
+ through its `.value`. Raises `TypeError` for any other value, a collection or a mapping included. Useful for APIs that dispatch on the exact type
523
+ (serializers, query builders).
516
524
 
517
525
  ```python
526
+ from datetime import datetime
518
527
  from enum import Enum
519
- from ddutils.value_helpers import get_builtin_base_type
528
+ from uuid import UUID
529
+ from ddutils.value_helpers import get_base_value_type
520
530
 
521
531
 
522
532
  class Kind(str, Enum):
523
533
  A = 'a'
524
534
 
525
535
 
526
- class UserId(int):
536
+ class ProfileId(UUID):
527
537
  pass
528
538
 
529
539
 
530
- get_builtin_base_type(UserId(2)) # <class 'int'>
531
- get_builtin_base_type(True) # <class 'bool'>
532
- get_builtin_base_type(Kind.A) # <class 'str'>
533
- get_builtin_base_type(object()) # TypeError
540
+ get_base_value_type(None) # <class 'NoneType'>
541
+ get_base_value_type(True) # <class 'bool'>
542
+ get_base_value_type(Kind.A) # <class 'str'>
543
+ get_base_value_type(ProfileId('12345678-1234-5678-1234-567812345678')) # <class 'uuid.UUID'>
544
+ get_base_value_type(datetime.now()) # <class 'datetime.datetime'>
545
+ get_base_value_type(object()) # TypeError
546
+ ```
547
+
548
+ ### unwrap_enum
549
+
550
+ Returns the `.value` of an `Enum` member and any other value as is. `str(Kind.A)` of a `class Kind(str, Enum)`
551
+ is `'Kind.A'`, so a member has to be unwrapped before it is formatted or cast.
552
+
553
+ ```python
554
+ from enum import Enum
555
+ from ddutils.value_helpers import unwrap_enum
556
+
557
+
558
+ class Kind(str, Enum):
559
+ A = 'a'
560
+
561
+
562
+ unwrap_enum(Kind.A) # 'a'
563
+ unwrap_enum('a') # 'a'
534
564
  ```
535
565
 
536
566
  ### is_complex_sequence_value
@@ -10,6 +10,7 @@ from . import (
10
10
  safe_decorators,
11
11
  scoped_registry,
12
12
  sequence_helpers,
13
+ value_helpers,
13
14
  )
14
15
 
15
16
  __all__ = (
@@ -24,4 +25,5 @@ __all__ = (
24
25
  'safe_decorators',
25
26
  'scoped_registry',
26
27
  'sequence_helpers',
28
+ 'value_helpers',
27
29
  )
@@ -0,0 +1,86 @@
1
+ from collections.abc import Callable
2
+ from datetime import date, datetime, time, timedelta
3
+ from decimal import Decimal
4
+ from types import NoneType
5
+ from typing import Any, Protocol
6
+ from uuid import UUID
7
+
8
+ from ddutils.value_helpers import BASE_VALUE_TYPES, get_base_value_type, unwrap_enum
9
+
10
+
11
+ class ClassInstanceWithDict(Protocol):
12
+ __dict__: dict
13
+
14
+
15
+ def convert_camel_case_to_snake_case(string: str) -> str:
16
+ return ''.join(f'_{char.lower()}' if char.isupper() else char for char in string).lstrip('_')
17
+
18
+
19
+ def convert_to_repr(obj: ClassInstanceWithDict) -> str:
20
+ class_name = obj.__class__.__name__
21
+ if not hasattr(obj, '__dict__'):
22
+ raise TypeError(f'Instance of {class_name} class does not have a `__dict__` attribute.')
23
+
24
+ attrs = ', '.join(f'{key}={value!r}' for key, value in obj.__dict__.items() if not key.startswith('_'))
25
+ return f'{class_name}({attrs})'
26
+
27
+
28
+ def convert_timedelta_to_milliseconds(delta: timedelta) -> int:
29
+ return int(delta.total_seconds() * 1000)
30
+
31
+
32
+ def _convert_to_datetime(value: datetime) -> datetime:
33
+ return datetime(
34
+ value.year, value.month, value.day, value.hour, value.minute, value.second, value.microsecond,
35
+ value.tzinfo, fold=value.fold,
36
+ ) # fmt: skip
37
+
38
+
39
+ def _convert_to_date(value: date) -> date:
40
+ return date(value.year, value.month, value.day)
41
+
42
+
43
+ def _convert_to_time(value: time) -> time:
44
+ return time(value.hour, value.minute, value.second, value.microsecond, value.tzinfo, fold=value.fold)
45
+
46
+
47
+ def _convert_to_timedelta(value: timedelta) -> timedelta:
48
+ # not `total_seconds()`: it goes through a float and loses microseconds on large values
49
+ return timedelta(days=value.days, seconds=value.seconds, microseconds=value.microseconds)
50
+
51
+
52
+ def _convert_to_uuid(value: UUID) -> UUID:
53
+ return UUID(int=value.int)
54
+
55
+
56
+ # A cast for every type of `BASE_VALUE_TYPES`: the constructor where it accepts an instance of a subclass,
57
+ # a function building the value from its fields otherwise
58
+ BASE_VALUE_CONVERTORS: dict[type, Callable[[Any], Any]] = {
59
+ NoneType: lambda _: None,
60
+ bool: bool,
61
+ int: int,
62
+ float: float,
63
+ str: str,
64
+ bytes: bytes,
65
+ bytearray: bytearray,
66
+ datetime: _convert_to_datetime,
67
+ date: _convert_to_date,
68
+ time: _convert_to_time,
69
+ timedelta: _convert_to_timedelta,
70
+ UUID: _convert_to_uuid,
71
+ Decimal: Decimal,
72
+ }
73
+
74
+ if set(BASE_VALUE_CONVERTORS) != set(BASE_VALUE_TYPES):
75
+ raise RuntimeError('`BASE_VALUE_CONVERTORS` must define a convertor for exactly the types of `BASE_VALUE_TYPES`')
76
+
77
+
78
+ def convert_to_base_value(value: Any) -> Any:
79
+ """
80
+ Cast a value to its base value type (see `get_base_value_type`): a `str` subclass becomes a plain `str`,
81
+ a `UUID` subclass a plain `UUID`, a `datetime` subclass a plain `datetime` with the same `tzinfo` and `fold`,
82
+ an `Enum` member becomes its `.value` cast the same way, `None` stays `None`.
83
+ Raises `TypeError` for a value without a base type.
84
+ """
85
+ value = unwrap_enum(value)
86
+ return BASE_VALUE_CONVERTORS[get_base_value_type(value)](value)
@@ -0,0 +1,37 @@
1
+ from collections.abc import Sequence
2
+ from datetime import date, datetime, time, timedelta
3
+ from decimal import Decimal
4
+ from enum import Enum
5
+ from types import NoneType
6
+ from typing import Any
7
+ from uuid import UUID
8
+
9
+ from ddutils.annotation_helpers import NON_COMPLEX_SEQUENCE_TYPES
10
+
11
+ # The order matters, a value is matched against the first type it is an instance of:
12
+ # `bool` is a subclass of `int`, `datetime` is a subclass of `date`
13
+ BASE_VALUE_TYPES = (NoneType, bool, int, float, str, bytes, bytearray, datetime, date, time, timedelta, UUID, Decimal)
14
+
15
+
16
+ def unwrap_enum(value: Any) -> Any:
17
+ """Return the `.value` of an `Enum` member, any other value as is."""
18
+ return value.value if isinstance(value, Enum) else value
19
+
20
+
21
+ def get_base_value_type(value: Any) -> type:
22
+ """
23
+ Return the base value type a value should be treated as, looking past subclasses: one of `BASE_VALUE_TYPES`
24
+ (`NoneType`, `bool`, `int`, `float`, `str`, `bytes`, `bytearray`, `datetime`, `date`, `time`, `timedelta`, `UUID`,
25
+ `Decimal`). An `Enum` member is looked at through its `.value`. Raises `TypeError` for any other value.
26
+ """
27
+ value = unwrap_enum(value)
28
+ for base_type in BASE_VALUE_TYPES:
29
+ if isinstance(value, base_type):
30
+ return base_type
31
+
32
+ raise TypeError(f'Value of type `{type(value).__name__}` has no base value type')
33
+
34
+
35
+ def is_complex_sequence_value(value: Any) -> bool:
36
+ """Return whether a value is a sequence other than `str`, `bytes` or `bytearray`; subclasses are handled."""
37
+ return isinstance(value, Sequence) and not isinstance(value, NON_COMPLEX_SEQUENCE_TYPES)
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "ddutils"
3
3
  description = "Domain Driven Utils Library"
4
- version = "0.2.4"
4
+ version = "0.3.0"
5
5
  license = "MIT"
6
6
  license-files = ["LICENSE"]
7
7
  readme = "README.md"
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "ddutils"
3
3
  description = "Domain Driven Utils Library"
4
- version = "0.2.4"
4
+ version = "0.3.0"
5
5
  authors = [{ name = "davyddd" }]
6
6
  license = "MIT"
7
7
  license-files = ["LICENSE"]
@@ -1,34 +0,0 @@
1
- from datetime import timedelta
2
- from typing import Any, Protocol
3
-
4
- from ddutils.value_helpers import get_builtin_base_type, unwrap_enum
5
-
6
-
7
- class ClassInstanceWithDict(Protocol):
8
- __dict__: dict
9
-
10
-
11
- def convert_camel_case_to_snake_case(string: str) -> str:
12
- return ''.join(f'_{char.lower()}' if char.isupper() else char for char in string).lstrip('_')
13
-
14
-
15
- def convert_to_repr(obj: ClassInstanceWithDict) -> str:
16
- class_name = obj.__class__.__name__
17
- if not hasattr(obj, '__dict__'):
18
- raise TypeError(f'Instance of {class_name} class does not have a `__dict__` attribute.')
19
-
20
- attrs = ', '.join(f'{key}={value!r}' for key, value in obj.__dict__.items() if not key.startswith('_'))
21
- return f'{class_name}({attrs})'
22
-
23
-
24
- def convert_timedelta_to_milliseconds(delta: timedelta) -> int:
25
- return int(delta.total_seconds() * 1000)
26
-
27
-
28
- def convert_to_builtin(value: Any) -> bool | int | float | str | bytes:
29
- """
30
- Cast a value to its builtin base type (see `get_builtin_base_type`): a `str` subclass becomes a plain `str`,
31
- an `Enum` member becomes its `.value` cast the same way. Raises `TypeError` for a value without a builtin base.
32
- """
33
- value = unwrap_enum(value)
34
- return get_builtin_base_type(value)(value)
@@ -1,30 +0,0 @@
1
- from collections.abc import Sequence
2
- from enum import Enum
3
- from typing import Any
4
-
5
- from ddutils.annotation_helpers import NON_COMPLEX_SEQUENCE_TYPES
6
-
7
- # `bool` goes before `int` because `bool` is a subclass of `int`
8
- BUILTIN_BASE_TYPES = (bool, int, float, str, bytes)
9
-
10
-
11
- def unwrap_enum(value: Any) -> Any:
12
- return value.value if isinstance(value, Enum) else value
13
-
14
-
15
- def get_builtin_base_type(value: Any) -> type:
16
- """
17
- Return the builtin type a value should be treated as, looking past subclasses: `bool`, `int`, `float`, `str`
18
- or `bytes`. An `Enum` member is looked at through its `.value`. Raises `TypeError` for any other value.
19
- """
20
- value = unwrap_enum(value)
21
- for base_type in BUILTIN_BASE_TYPES:
22
- if isinstance(value, base_type):
23
- return base_type
24
-
25
- raise TypeError(f'Value of type `{type(value).__name__}` has no builtin base type')
26
-
27
-
28
- def is_complex_sequence_value(value: Any) -> bool:
29
- """Return whether a value is a sequence other than `str`, `bytes` or `bytearray`; subclasses are handled."""
30
- return isinstance(value, Sequence) and not isinstance(value, NON_COMPLEX_SEQUENCE_TYPES)
File without changes
File without changes