sindripy 0.1.1__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 sindripy might be problematic. Click here for more details.

Files changed (34) hide show
  1. sindripy/__init__.py +11 -0
  2. sindripy/_compat.py +15 -0
  3. sindripy/mothers/__init__.py +24 -0
  4. sindripy/mothers/identifiers/__init__.py +0 -0
  5. sindripy/mothers/identifiers/string_uuid_primitives_mother.py +16 -0
  6. sindripy/mothers/object_mother.py +10 -0
  7. sindripy/mothers/primitives/__init__.py +0 -0
  8. sindripy/mothers/primitives/boolean_primitives_mother.py +20 -0
  9. sindripy/mothers/primitives/float_primitives_mother.py +30 -0
  10. sindripy/mothers/primitives/integer_primitives_mother.py +36 -0
  11. sindripy/mothers/primitives/list_primitives_mother.py +10 -0
  12. sindripy/mothers/primitives/string_primitives_mother.py +48 -0
  13. sindripy/py.typed +0 -0
  14. sindripy/value_objects/__init__.py +29 -0
  15. sindripy/value_objects/aggregate.py +312 -0
  16. sindripy/value_objects/decorators/__init__.py +0 -0
  17. sindripy/value_objects/decorators/validation.py +28 -0
  18. sindripy/value_objects/errors/__init__.py +0 -0
  19. sindripy/value_objects/errors/incorrect_value_type_error.py +12 -0
  20. sindripy/value_objects/errors/invalid_id_format_error.py +8 -0
  21. sindripy/value_objects/errors/required_value_error.py +8 -0
  22. sindripy/value_objects/errors/sindri_validation_error.py +10 -0
  23. sindripy/value_objects/identifiers/__init__.py +0 -0
  24. sindripy/value_objects/identifiers/string_uuid.py +55 -0
  25. sindripy/value_objects/primitives/__init__.py +0 -0
  26. sindripy/value_objects/primitives/boolean.py +44 -0
  27. sindripy/value_objects/primitives/float.py +44 -0
  28. sindripy/value_objects/primitives/integer.py +44 -0
  29. sindripy/value_objects/primitives/list.py +307 -0
  30. sindripy/value_objects/primitives/string.py +43 -0
  31. sindripy/value_objects/value_object.py +269 -0
  32. sindripy-0.1.1.dist-info/METADATA +144 -0
  33. sindripy-0.1.1.dist-info/RECORD +34 -0
  34. sindripy-0.1.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,55 @@
1
+ from uuid import UUID
2
+
3
+ from src.sindripy.value_objects.decorators.validation import validate
4
+ from src.sindripy.value_objects.errors.incorrect_value_type_error import IncorrectValueTypeError
5
+ from src.sindripy.value_objects.errors.invalid_id_format_error import InvalidIdFormatError
6
+ from src.sindripy.value_objects.errors.required_value_error import RequiredValueError
7
+ from src.sindripy.value_objects.value_object import ValueObject
8
+
9
+
10
+ class StringUuid(ValueObject[str]):
11
+ """
12
+ A value object that wraps UUID (Universally Unique Identifier) string values with validation.
13
+
14
+ This class provides a specialized implementation for creating value objects that
15
+ represent UUID values in the domain. It ensures that the wrapped value is a
16
+ valid UUID string format and not None.
17
+
18
+ The class includes built-in validation for:
19
+ - Required value (not None)
20
+ - Type checking (must be a string)
21
+ - UUID format validation (must be a valid UUID format)
22
+
23
+ Inherits all functionality from ValueObject including immutability,
24
+ equality comparison, string representation, and hashing.
25
+
26
+ Example:
27
+ ```python
28
+ class UserId(StringUuid):
29
+ @validate
30
+ def _validate_version(self, value: str) -> None:
31
+ parsed_uuid = UUID(value)
32
+ if parsed_uuid.version != 4:
33
+ raise ValueError("Only UUID version 4 allowed")
34
+
35
+ user_id = UserId("123e4567-e89b-12d3-a456-426614174000")
36
+ user_id.value # '123e4567-e89b-12d3-a456-426614174000'
37
+ ```
38
+ """
39
+
40
+ @validate
41
+ def _ensure_has_value(self, value: str) -> None:
42
+ if value is None:
43
+ raise RequiredValueError
44
+
45
+ @validate
46
+ def _ensure_value_is_string(self, value: str) -> None:
47
+ if not isinstance(value, str):
48
+ raise IncorrectValueTypeError(value, str)
49
+
50
+ @validate
51
+ def _ensure_value_has_valid_uuid_format(self, value: str) -> None:
52
+ try:
53
+ UUID(value)
54
+ except ValueError as error:
55
+ raise InvalidIdFormatError from error
File without changes
@@ -0,0 +1,44 @@
1
+ from src.sindripy.value_objects.decorators.validation import validate
2
+ from src.sindripy.value_objects.errors.incorrect_value_type_error import IncorrectValueTypeError
3
+ from src.sindripy.value_objects.errors.required_value_error import RequiredValueError
4
+ from src.sindripy.value_objects.value_object import ValueObject
5
+
6
+
7
+ class Boolean(ValueObject[bool]):
8
+ """
9
+ A value object that wraps boolean values with validation.
10
+
11
+ This class provides a base implementation for creating value objects that
12
+ represent boolean values in the domain. It ensures that the wrapped value
13
+ is a valid boolean and not None.
14
+
15
+ The class includes built-in validation for:
16
+ - Required value (not None)
17
+ - Type checking (must be a boolean)
18
+
19
+ Inherits all functionality from ValueObject including immutability,
20
+ equality comparison, string representation, and hashing.
21
+
22
+ Example:
23
+ ```python
24
+ class IsActive(Boolean):
25
+ @validate
26
+ def _validate_true_for_premium(self, value: bool) -> None:
27
+ # Custom business logic can be added here
28
+ pass
29
+
30
+ is_active = IsActive(True)
31
+ is_active.value # True
32
+ str(is_active) # 'True'
33
+ ```
34
+ """
35
+
36
+ @validate
37
+ def _ensure_has_value(self, value: bool) -> None:
38
+ if value is None:
39
+ raise RequiredValueError
40
+
41
+ @validate
42
+ def _ensure_value_is_boolean(self, value: bool) -> None:
43
+ if not isinstance(value, bool):
44
+ raise IncorrectValueTypeError(value, bool)
@@ -0,0 +1,44 @@
1
+ from src.sindripy.value_objects.decorators.validation import validate
2
+ from src.sindripy.value_objects.errors.incorrect_value_type_error import IncorrectValueTypeError
3
+ from src.sindripy.value_objects.errors.required_value_error import RequiredValueError
4
+ from src.sindripy.value_objects.value_object import ValueObject
5
+
6
+
7
+ class Float(ValueObject[float]):
8
+ """
9
+ A value object that wraps float values with validation.
10
+
11
+ This class provides a base implementation for creating value objects that
12
+ represent float values in the domain. It ensures that the wrapped value
13
+ is a valid float and not None. It accepts both positive and negative values.
14
+
15
+ The class includes built-in validation for:
16
+ - Required value (not None)
17
+ - Type checking (must be a float)
18
+
19
+ Inherits all functionality from ValueObject including immutability,
20
+ equality comparison, string representation, and hashing.
21
+
22
+ Example:
23
+ ```python
24
+ class Price(Float):
25
+ @validate
26
+ def _validate_positive(self, value: float) -> None:
27
+ if value < 0:
28
+ raise ValueError("Price cannot be negative")
29
+
30
+ price = Price(29.99)
31
+ price.value # 29.99
32
+ str(price) # '29.99'
33
+ ```
34
+ """
35
+
36
+ @validate
37
+ def _ensure_has_value(self, value: float) -> None:
38
+ if value is None:
39
+ raise RequiredValueError
40
+
41
+ @validate
42
+ def _ensure_value_is_float(self, value: float) -> None:
43
+ if not isinstance(value, float):
44
+ raise IncorrectValueTypeError(value, float)
@@ -0,0 +1,44 @@
1
+ from src.sindripy.value_objects.decorators.validation import validate
2
+ from src.sindripy.value_objects.errors.incorrect_value_type_error import IncorrectValueTypeError
3
+ from src.sindripy.value_objects.errors.required_value_error import RequiredValueError
4
+ from src.sindripy.value_objects.value_object import ValueObject
5
+
6
+
7
+ class Integer(ValueObject[int]):
8
+ """
9
+ A value object that wraps integer values with validation.
10
+
11
+ This class provides a base implementation for creating value objects that
12
+ represent integer values in the domain. It ensures that the wrapped value
13
+ is a valid integer and not None.
14
+
15
+ The class includes built-in validation for:
16
+ - Required value (not None)
17
+ - Type checking (must be an integer)
18
+
19
+ Inherits all functionality from ValueObject including immutability,
20
+ equality comparison, string representation, and hashing.
21
+
22
+ Example:
23
+ ```python
24
+ class Age(Integer):
25
+ @validate
26
+ def _validate_positive(self, value: int) -> None:
27
+ if value < 0:
28
+ raise ValueError("Age cannot be negative")
29
+
30
+ age = Age(25)
31
+ age.value # 25
32
+ str(age) # '25'
33
+ ```
34
+ """
35
+
36
+ @validate
37
+ def _ensure_has_value(self, value: int) -> None:
38
+ if value is None:
39
+ raise RequiredValueError
40
+
41
+ @validate
42
+ def _ensure_value_is_integer(self, value: int) -> None:
43
+ if not isinstance(value, int):
44
+ raise IncorrectValueTypeError(value, int)
@@ -0,0 +1,307 @@
1
+ from collections.abc import Iterator
2
+ from typing import Any, Generic, TypeVar, get_args, get_origin
3
+
4
+ from src.sindripy._compat import Self, override
5
+ from src.sindripy.value_objects.decorators.validation import validate
6
+ from src.sindripy.value_objects.errors.incorrect_value_type_error import IncorrectValueTypeError
7
+ from src.sindripy.value_objects.errors.required_value_error import RequiredValueError
8
+ from src.sindripy.value_objects.value_object import ValueObject
9
+
10
+ T = TypeVar("T")
11
+
12
+
13
+ class List(ValueObject[list[T]], Generic[T]):
14
+ @override
15
+ def __init_subclass__(cls, **kwargs: Any) -> None:
16
+ """
17
+ Initialize subclass with proper type parameter validation.
18
+
19
+ This method ensures that any subclass of List is properly parameterized
20
+ with a type argument and extracts the element type for validation purposes. It
21
+ is run automatically when a subclass is created, at definition time.
22
+ """
23
+ super().__init_subclass__(**kwargs)
24
+
25
+ cls._validate_class_has_type_parameters()
26
+ list_base = cls._find_parameterized_list_base()
27
+ element_type = cls._extract_element_type_from_base(list_base)
28
+ cls._validate_and_store_element_type(element_type)
29
+
30
+ @classmethod
31
+ def from_primitives(cls, value: list[Any]) -> Self:
32
+ elements = []
33
+
34
+ for primitive in value:
35
+ if cls._element_is_an_aggregate_instance():
36
+ elements.append(cls._element_type.from_primitives(primitive))
37
+ elif cls._element_is_a_value_object_instance():
38
+ elements.append(cls._element_type(primitive))
39
+ else:
40
+ elements.append(primitive)
41
+ return cls(elements)
42
+
43
+ @validate
44
+ def _ensure_has_value(self, value: list[T]) -> None:
45
+ if value is None:
46
+ raise RequiredValueError
47
+
48
+ @validate
49
+ def _ensure_is_list(self, value: list[T]) -> None:
50
+ if not isinstance(value, list):
51
+ raise IncorrectValueTypeError(value, type[Any])
52
+
53
+ @validate
54
+ def _ensure_list_elements_have_expected_type(self, value: list[T]) -> None:
55
+ cls = self.__class__
56
+
57
+ if not hasattr(cls, "_element_type"):
58
+ return
59
+
60
+ element_type = cls._element_type
61
+
62
+ if isinstance(element_type, TypeVar):
63
+ return
64
+
65
+ if not isinstance(element_type, type):
66
+ return
67
+
68
+ if cls._element_is_a_value_object_instance() or cls._element_is_a_primitive_type():
69
+ for item in value:
70
+ if not isinstance(item, element_type):
71
+ raise IncorrectValueTypeError(item, list)
72
+
73
+ def __contains__(self, item: Any) -> bool:
74
+ """
75
+ Check if an item is present in the list.
76
+
77
+ Args:
78
+ item: The item to check for membership in the list.
79
+
80
+ Returns:
81
+ True if the item is in the list, False otherwise.
82
+
83
+ Example:
84
+ ```python
85
+ numbers = IntList([1, 2, 3, 4, 5])
86
+ 3 in numbers # True
87
+ 6 in numbers # False
88
+ ```
89
+ """
90
+ return item in self._value
91
+
92
+ def __iter__(self) -> Iterator[T]:
93
+ """
94
+ Return an iterator over the list elements.
95
+
96
+ Returns:
97
+ An iterator that yields each element in the list.
98
+
99
+ Example:
100
+ ```python
101
+ numbers = IntList([1, 2, 3])
102
+ list(numbers) # [1, 2, 3]
103
+ for num in numbers:
104
+ print(num)
105
+ # 1
106
+ # 2
107
+ # 3
108
+ ```
109
+ """
110
+ return iter(self._value)
111
+
112
+ def __len__(self) -> int:
113
+ """
114
+ Return the number of elements in the list.
115
+
116
+ Returns:
117
+ The length of the wrapped list.
118
+
119
+ Example:
120
+ ```python
121
+ numbers = IntList([1, 2, 3, 4, 5])
122
+ len(numbers) # 5
123
+ ```
124
+ """
125
+ return len(self._value)
126
+
127
+ def __reversed__(self) -> Iterator[T]:
128
+ """
129
+ Return a reverse iterator over the list elements.
130
+
131
+ Returns:
132
+ A reverse iterator that yields elements from the end to the beginning.
133
+
134
+ Example:
135
+ ```python
136
+ numbers = IntList([1, 2, 3])
137
+ list(reversed(numbers)) # [3, 2, 1]
138
+ ```
139
+ """
140
+ return reversed(self._value)
141
+
142
+ @override
143
+ def __hash__(self) -> int:
144
+ """
145
+ Return the hash value of this value object.
146
+
147
+ Since lists are unhashable in Python, we convert the list to a tuple
148
+ for hashing purposes while maintaining the original list value.
149
+
150
+ Returns:
151
+ The hash value based on the tuple representation of the list.
152
+ """
153
+ return hash(tuple(self._value))
154
+
155
+ @override
156
+ def __eq__(self, other: Any) -> bool:
157
+ """
158
+ Check equality with another List value object.
159
+
160
+ Args:
161
+ other: The other object to compare against.
162
+
163
+ Returns:
164
+ True if both are List instances with the same element type and values, False otherwise.
165
+
166
+ Example:
167
+ ```python
168
+ list1 = IntList([1, 2, 3])
169
+ list2 = IntList([1, 2, 3])
170
+ list3 = IntList([4, 5, 6])
171
+ list1 == list2 # True
172
+ list1 == list3 # False
173
+ ```
174
+ """
175
+ if not isinstance(other, List):
176
+ return False
177
+
178
+ if self._element_type != other._element_type:
179
+ return False
180
+
181
+ return self._value == other._value
182
+
183
+ @override
184
+ def __repr__(self) -> str:
185
+ """
186
+ Return a string representation suitable for debugging.
187
+
188
+ Returns:
189
+ A string showing the class name and the wrapped list value.
190
+
191
+ Example:
192
+ ```python
193
+ numbers = IntList([1, 2, 3])
194
+ repr(numbers) # 'IntList(_value=[1, 2, 3])'
195
+ ```
196
+ """
197
+ return f"{self.__class__.__name__}(_value={self._value!r})"
198
+
199
+ @override
200
+ def __str__(self) -> str:
201
+ """
202
+ Return a human-readable string representation.
203
+
204
+ Returns:
205
+ The string representation of the wrapped list.
206
+
207
+ Example:
208
+ ```python
209
+ numbers = IntList([1, 2, 3])
210
+ str(numbers) # '[1, 2, 3]'
211
+ ```
212
+ """
213
+ return str(self._value)
214
+
215
+ @classmethod
216
+ def _validate_class_has_type_parameters(cls) -> None:
217
+ """
218
+ Validate that the class has type parameters defined.
219
+
220
+ Raises:
221
+ TypeError: If the class doesn't have __orig_bases__ or it's empty.
222
+ """
223
+ if not hasattr(cls, "__orig_bases__") or not getattr(cls, "__orig_bases__", None):
224
+ raise TypeError(f"Class {cls.__name__} must be parameterized with a type argument")
225
+
226
+ @classmethod
227
+ def _find_parameterized_list_base(cls) -> Any:
228
+ """
229
+ Find the parameterized List base class in the inheritance chain.
230
+
231
+ Returns:
232
+ The parameterized List base class.
233
+
234
+ Raises:
235
+ TypeError: If no parameterized List base is found.
236
+ """
237
+ orig_bases = getattr(cls, "__orig_bases__", ())
238
+ for base in orig_bases:
239
+ if get_origin(base) is List:
240
+ return base
241
+
242
+ raise TypeError(f"Class {cls.__name__} must inherit from List[T] with a type parameter")
243
+
244
+ @classmethod
245
+ def _extract_element_type_from_base(cls, list_base: Any) -> Any:
246
+ """
247
+ Extract the element type from the parameterized List base.
248
+
249
+ Args:
250
+ list_base: The parameterized List base class.
251
+
252
+ Returns:
253
+ The element type parameter.
254
+ """
255
+ element_type, *_ = get_args(list_base)
256
+ return element_type
257
+
258
+ @classmethod
259
+ def _validate_and_store_element_type(cls, element_type: Any) -> None:
260
+ """
261
+ Validate the element type and store it in the class.
262
+
263
+ This method handles different types of type parameters:
264
+ - TypeVar instances for generic types
265
+ - Concrete types like int, str, etc.
266
+ - Generic types like list[int], dict[str, int], etc.
267
+
268
+ Args:
269
+ element_type: The element type to validate and store.
270
+
271
+ Raises:
272
+ TypeError: If the element type is not a valid type parameter.
273
+ """
274
+ # Handle TypeVar cases: If the type is a generic type, store it directly
275
+ if isinstance(element_type, TypeVar):
276
+ cls._element_type = element_type
277
+ return
278
+
279
+ # Validate concrete types: Ensure the type parameter is actually a valid type
280
+ if isinstance(element_type, type):
281
+ cls._element_type = element_type
282
+ return
283
+
284
+ # Handle generic types like list[int], dict[str, int], etc.
285
+ if hasattr(element_type, "__origin__") or get_origin(element_type) is not None:
286
+ cls._element_type = element_type
287
+ return
288
+
289
+ raise TypeError(f"Type parameter must be a valid type, not a primitive value: {element_type}")
290
+
291
+ @classmethod
292
+ def _element_is_an_aggregate_instance(cls) -> bool:
293
+ return hasattr(cls._element_type, "from_primitives")
294
+
295
+ @classmethod
296
+ def _element_is_a_value_object_instance(cls) -> bool:
297
+ try:
298
+ return isinstance(cls._element_type, type) and issubclass(cls._element_type, ValueObject)
299
+ except TypeError:
300
+ return False
301
+
302
+ @classmethod
303
+ def _element_is_a_primitive_type(cls) -> bool:
304
+ try:
305
+ return isinstance(cls._element_type, type) and not issubclass(cls._element_type, ValueObject)
306
+ except TypeError:
307
+ return False
@@ -0,0 +1,43 @@
1
+ from src.sindripy.value_objects.decorators.validation import validate
2
+ from src.sindripy.value_objects.errors.incorrect_value_type_error import IncorrectValueTypeError
3
+ from src.sindripy.value_objects.errors.required_value_error import RequiredValueError
4
+ from src.sindripy.value_objects.value_object import ValueObject
5
+
6
+
7
+ class String(ValueObject[str]):
8
+ """
9
+ A value object that wraps string values with validation.
10
+
11
+ This class provides a base implementation for creating value objects that
12
+ represent string values in the domain. It ensures that the wrapped value
13
+ is a valid string and not None.
14
+
15
+ The class includes built-in validation for:
16
+ - Required value (not None)
17
+ - Type checking (must be a string)
18
+
19
+ Inherits all functionality from ValueObject including immutability,
20
+ equality comparison, string representation, and hashing.
21
+
22
+ Example:
23
+ ```python
24
+ class Email(String):
25
+ @validate
26
+ def _validate_email_format(self, value: str) -> None:
27
+ if "@" not in value:
28
+ raise ValueError("Invalid email format")
29
+
30
+ email = Email("user@example.com")
31
+ email.value # 'user@example.com'
32
+ ```
33
+ """
34
+
35
+ @validate
36
+ def _ensure_has_value(self, value: str) -> None:
37
+ if value is None:
38
+ raise RequiredValueError
39
+
40
+ @validate
41
+ def _ensure_is_string(self, value: str) -> None:
42
+ if not isinstance(value, str):
43
+ raise IncorrectValueTypeError(value, str)