sindripy 0.1.6__tar.gz → 1.0.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.
- {sindripy-0.1.6 → sindripy-1.0.0}/.gitignore +4 -1
- {sindripy-0.1.6 → sindripy-1.0.0}/CHANGELOG.md +55 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/PKG-INFO +32 -5
- {sindripy-0.1.6 → sindripy-1.0.0}/README.md +29 -4
- {sindripy-0.1.6 → sindripy-1.0.0}/pyproject.toml +25 -5
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/__init__.py +1 -1
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/identifiers/string_uuid.py +9 -9
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/boolean.py +6 -6
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/float.py +7 -7
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/integer.py +7 -7
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/list.py +8 -8
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/string.py +7 -7
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/value_object.py +61 -17
- {sindripy-0.1.6 → sindripy-1.0.0}/LICENSE +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/_compat.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/__init__.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/identifiers/__init__.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/identifiers/string_uuid_primitives_mother.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/object_mother.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/__init__.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/boolean_primitives_mother.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/float_primitives_mother.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/integer_primitives_mother.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/list_primitives_mother.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/string_primitives_mother.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/py.typed +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/__init__.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/aggregate.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/decorators/__init__.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/decorators/validation.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/__init__.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/incorrect_value_type_error.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/invalid_id_format_error.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/required_value_error.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/sindri_validation_error.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/identifiers/__init__.py +0 -0
- {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/__init__.py +0 -0
|
@@ -2,6 +2,61 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.0.0 (2026-02-03)
|
|
6
|
+
|
|
7
|
+
### Build System
|
|
8
|
+
|
|
9
|
+
- Update dependencies to avoid vulnerabilities
|
|
10
|
+
([`d863cce`](https://github.com/dimanu-py/sindri/commit/d863cce206199d53a745aec3e5be54d559c1b671))
|
|
11
|
+
|
|
12
|
+
- Update sindripy version to 0.1.7 in uv.lock file
|
|
13
|
+
([`d67bb2f`](https://github.com/dimanu-py/sindri/commit/d67bb2f15babf745650d12a43c17e6bc98d178ba))
|
|
14
|
+
|
|
15
|
+
- **pyproject**: Add 'docs' to include groups
|
|
16
|
+
([`bd8c7b7`](https://github.com/dimanu-py/sindri/commit/bd8c7b7ad1c06e080277173ee4ad83eea8885488))
|
|
17
|
+
|
|
18
|
+
- **pyproject**: Add keywords for pypi seo discovery
|
|
19
|
+
([`d36bf42`](https://github.com/dimanu-py/sindri/commit/d36bf42087994950ffacc7db37d11523e4d18da8))
|
|
20
|
+
|
|
21
|
+
- **pyproject**: Remove pip install command from build_command
|
|
22
|
+
([`842e8d7`](https://github.com/dimanu-py/sindri/commit/842e8d7195cb4e8041fd66dc268cd15446c4dbaf))
|
|
23
|
+
|
|
24
|
+
- **pyproject**: Set major_on_zero to true for versioning to bump major version on breaking changes
|
|
25
|
+
([`38d1329`](https://github.com/dimanu-py/sindri/commit/38d1329f09e4164bcbbff79b69b0108262636021))
|
|
26
|
+
|
|
27
|
+
### Documentation
|
|
28
|
+
|
|
29
|
+
- Update README to enhance clarity and structure for Value Object and Object Mother patterns
|
|
30
|
+
([`ff8a3a5`](https://github.com/dimanu-py/sindri/commit/ff8a3a5a8591178eb793f6cc5eb557bfbad5ff80))
|
|
31
|
+
|
|
32
|
+
- Update validation documentation and add migration guide for validator signature changes
|
|
33
|
+
([`e38b62d`](https://github.com/dimanu-py/sindri/commit/e38b62df0ac4719601dad2a9d3ab2c461b8cfe5b))
|
|
34
|
+
|
|
35
|
+
### Features
|
|
36
|
+
|
|
37
|
+
- **value-objects**: Add adapter pattern to ensure backward compatibility between new and old
|
|
38
|
+
validators until old version gets deprecated
|
|
39
|
+
([`39c3bf8`](https://github.com/dimanu-py/sindri/commit/39c3bf80136a130f6795287498eb7ae71685c3f1))
|
|
40
|
+
|
|
41
|
+
- **value-objects**: Store first the value inside attribute and then perform validation instead of
|
|
42
|
+
first perform validation and then assign the attribute
|
|
43
|
+
([`86d24b2`](https://github.com/dimanu-py/sindri/commit/86d24b2b6d96043863e27fe79ef33f407817fcd0))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## v0.1.7 (2025-11-21)
|
|
47
|
+
|
|
48
|
+
### Bug Fixes
|
|
49
|
+
|
|
50
|
+
- **pyproject**: Put faker dependency as a prod dependency of sindripy to avoid errors when using
|
|
51
|
+
object mothers
|
|
52
|
+
([`c478887`](https://github.com/dimanu-py/sindri/commit/c47888754e1b1776c917cf5c81aec05d2b0be65d))
|
|
53
|
+
|
|
54
|
+
### Build System
|
|
55
|
+
|
|
56
|
+
- **pyproject**: Modify build_command to try and update uv.lock with new version released
|
|
57
|
+
([`41082cf`](https://github.com/dimanu-py/sindri/commit/41082cf3eb7e602f42a5cbf19ca8f156abdc5c43))
|
|
58
|
+
|
|
59
|
+
|
|
5
60
|
## v0.1.6 (2025-11-15)
|
|
6
61
|
|
|
7
62
|
### Build System
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sindripy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Value Object and Object Mother patterns implementation for Python
|
|
5
5
|
Project-URL: documentation, https://dimanu-py.github.io/sindri/home/
|
|
6
6
|
Project-URL: repository, https://github.com/dimanu-py/sindri/
|
|
@@ -28,6 +28,7 @@ License: MIT License
|
|
|
28
28
|
SOFTWARE.
|
|
29
29
|
|
|
30
30
|
License-File: LICENSE
|
|
31
|
+
Keywords: Object Mother,Value Object,bdd,clean architecture,ddd,design patterns,domain driven design,hexagonal architecture,object mother,object mothers,object-mother,object-mothers,testing,unit tests,value object,value objects,value-object,value-objects
|
|
31
32
|
Classifier: Framework :: FastAPI
|
|
32
33
|
Classifier: Intended Audience :: Developers
|
|
33
34
|
Classifier: Operating System :: OS Independent
|
|
@@ -43,12 +44,14 @@ Classifier: Topic :: Software Development :: Testing :: BDD
|
|
|
43
44
|
Classifier: Topic :: Software Development :: Testing :: Unit
|
|
44
45
|
Classifier: Typing :: Typed
|
|
45
46
|
Requires-Python: <3.14,>=3.10
|
|
47
|
+
Requires-Dist: faker
|
|
46
48
|
Description-Content-Type: text/markdown
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
# Sindripy
|
|
51
|
+
|
|
52
|
+
### Value Object and Object Mother patterns for Python and Domain Driven Design applications
|
|
53
|
+
|
|
54
|
+
Easy use and customizable implementation for Value Object and Object Mother patterns.
|
|
52
55
|
|
|
53
56
|
<p align="center">
|
|
54
57
|
<a href="https://dimanu-py.github.io/sindri/getting_started/">Getting Started</a> •
|
|
@@ -134,6 +137,30 @@ random_age = IntegerPrimitivesMother.any()
|
|
|
134
137
|
random_name = StringPrimitivesMother.any()
|
|
135
138
|
```
|
|
136
139
|
|
|
140
|
+
# Migrating to v1.0.0
|
|
141
|
+
|
|
142
|
+
This release includes a breaking change to the validator API used by value objects:
|
|
143
|
+
|
|
144
|
+
- Validators no longer accept the incoming value as a parameter. Instead, the value is assigned to `self._value` before validators run, and validator methods have the signature `def _validate_xxx(self) -> None:`.
|
|
145
|
+
- This simplifies validator signatures and reduces repetition across validation methods.
|
|
146
|
+
|
|
147
|
+
Why this is a breaking change
|
|
148
|
+
|
|
149
|
+
- Existing validator methods that still declare a `value` parameter will receive the wrong signature and raise a `TypeError` when the framework calls them. Custom code that expects the old parameter (for example, custom exception constructors or external utilities) may also need small updates.
|
|
150
|
+
|
|
151
|
+
How to migrate
|
|
152
|
+
|
|
153
|
+
- Follow the step-by-step migration guide which shows the exact edits and examples: [Validator Signature Migration Guide](docs/value_objects/migration_guide.md).
|
|
154
|
+
- In short: find all methods decorated with `@validate`, remove the `value` parameter, replace uses of `value` with `self._value`, and update any error messages or custom exceptions that referenced the old parameter.
|
|
155
|
+
|
|
156
|
+
A quick checklist:
|
|
157
|
+
|
|
158
|
+
- [ ] Update `@validate` methods to `def _... (self) -> None`
|
|
159
|
+
- [ ] Replace `value` references with `self._value`
|
|
160
|
+
- [ ] Update error messages and custom exception usages
|
|
161
|
+
- [ ] Run your tests
|
|
162
|
+
|
|
163
|
+
|
|
137
164
|
<div style="background-color: #1e2d3d; border: 1px solid #00d9ff; border-radius: 8px; padding: 16px; margin: 16px 0; display: flex; align-items: flex-start; gap: 12px;">
|
|
138
165
|
<div style="font-size: 20px; color: #00d9ff; flex-shrink: 0;">ℹ️</div>
|
|
139
166
|
<div>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
# Sindripy
|
|
2
|
+
|
|
3
|
+
### Value Object and Object Mother patterns for Python and Domain Driven Design applications
|
|
4
|
+
|
|
5
|
+
Easy use and customizable implementation for Value Object and Object Mother patterns.
|
|
5
6
|
|
|
6
7
|
<p align="center">
|
|
7
8
|
<a href="https://dimanu-py.github.io/sindri/getting_started/">Getting Started</a> •
|
|
@@ -87,6 +88,30 @@ random_age = IntegerPrimitivesMother.any()
|
|
|
87
88
|
random_name = StringPrimitivesMother.any()
|
|
88
89
|
```
|
|
89
90
|
|
|
91
|
+
# Migrating to v1.0.0
|
|
92
|
+
|
|
93
|
+
This release includes a breaking change to the validator API used by value objects:
|
|
94
|
+
|
|
95
|
+
- Validators no longer accept the incoming value as a parameter. Instead, the value is assigned to `self._value` before validators run, and validator methods have the signature `def _validate_xxx(self) -> None:`.
|
|
96
|
+
- This simplifies validator signatures and reduces repetition across validation methods.
|
|
97
|
+
|
|
98
|
+
Why this is a breaking change
|
|
99
|
+
|
|
100
|
+
- Existing validator methods that still declare a `value` parameter will receive the wrong signature and raise a `TypeError` when the framework calls them. Custom code that expects the old parameter (for example, custom exception constructors or external utilities) may also need small updates.
|
|
101
|
+
|
|
102
|
+
How to migrate
|
|
103
|
+
|
|
104
|
+
- Follow the step-by-step migration guide which shows the exact edits and examples: [Validator Signature Migration Guide](docs/value_objects/migration_guide.md).
|
|
105
|
+
- In short: find all methods decorated with `@validate`, remove the `value` parameter, replace uses of `value` with `self._value`, and update any error messages or custom exceptions that referenced the old parameter.
|
|
106
|
+
|
|
107
|
+
A quick checklist:
|
|
108
|
+
|
|
109
|
+
- [ ] Update `@validate` methods to `def _... (self) -> None`
|
|
110
|
+
- [ ] Replace `value` references with `self._value`
|
|
111
|
+
- [ ] Update error messages and custom exception usages
|
|
112
|
+
- [ ] Run your tests
|
|
113
|
+
|
|
114
|
+
|
|
90
115
|
<div style="background-color: #1e2d3d; border: 1px solid #00d9ff; border-radius: 8px; padding: 16px; margin: 16px 0; display: flex; align-items: flex-start; gap: 12px;">
|
|
91
116
|
<div style="font-size: 20px; color: #00d9ff; flex-shrink: 0;">ℹ️</div>
|
|
92
117
|
<div>
|
|
@@ -1,13 +1,34 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sindripy"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "1.0.0"
|
|
4
4
|
description = "Value Object and Object Mother patterns implementation for Python"
|
|
5
5
|
authors = [{name = "dimanu-py", email = "dimanu.py@gmail.com"}]
|
|
6
6
|
dependencies = [
|
|
7
|
+
"faker",
|
|
7
8
|
]
|
|
8
9
|
requires-python = ">=3.10, <3.14"
|
|
9
10
|
readme = "README.md"
|
|
10
11
|
license = { file = "LICENSE" }
|
|
12
|
+
keywords = [
|
|
13
|
+
"value object",
|
|
14
|
+
"value-object",
|
|
15
|
+
"value objects",
|
|
16
|
+
"value-objects",
|
|
17
|
+
"Value Object",
|
|
18
|
+
"object mother",
|
|
19
|
+
"object-mother",
|
|
20
|
+
"object mothers",
|
|
21
|
+
"object-mothers",
|
|
22
|
+
"Object Mother",
|
|
23
|
+
"domain driven design",
|
|
24
|
+
"ddd",
|
|
25
|
+
"clean architecture",
|
|
26
|
+
"hexagonal architecture",
|
|
27
|
+
"design patterns",
|
|
28
|
+
"testing",
|
|
29
|
+
"bdd",
|
|
30
|
+
"unit tests",
|
|
31
|
+
]
|
|
11
32
|
classifiers = [
|
|
12
33
|
"Typing :: Typed",
|
|
13
34
|
"Programming Language :: Python",
|
|
@@ -22,6 +43,7 @@ classifiers = [
|
|
|
22
43
|
"Operating System :: OS Independent",
|
|
23
44
|
"Topic :: Software Development :: Testing :: Unit",
|
|
24
45
|
"Topic :: Software Development :: Testing :: BDD",
|
|
46
|
+
"Topic :: Software Development :: Libraries",
|
|
25
47
|
"Framework :: FastAPI"
|
|
26
48
|
]
|
|
27
49
|
|
|
@@ -34,6 +56,7 @@ dev = [
|
|
|
34
56
|
{include-group = "lint"},
|
|
35
57
|
{include-group = "test"},
|
|
36
58
|
{include-group = "release"},
|
|
59
|
+
{include-group = "docs"},
|
|
37
60
|
]
|
|
38
61
|
|
|
39
62
|
release = [
|
|
@@ -47,7 +70,6 @@ lint = [
|
|
|
47
70
|
]
|
|
48
71
|
test = [
|
|
49
72
|
"expects>=0.9.0",
|
|
50
|
-
"faker",
|
|
51
73
|
"pytest",
|
|
52
74
|
"pytest-asyncio",
|
|
53
75
|
"pytest-sugar>=1.1.1",
|
|
@@ -98,19 +120,17 @@ markers = [
|
|
|
98
120
|
]
|
|
99
121
|
testpaths = ["test"]
|
|
100
122
|
asyncio_default_fixture_loop_scope = "function"
|
|
101
|
-
asyncio_mode = "auto"
|
|
102
123
|
|
|
103
124
|
[tool.semantic_release]
|
|
104
125
|
version_toml = ["pyproject.toml:project.version"]
|
|
105
126
|
version_variables = ["src/sindripy/__init__.py:__version__"]
|
|
106
127
|
commit_message = "bump: new version {version} created"
|
|
107
128
|
commit_parser = "conventional"
|
|
108
|
-
major_on_zero =
|
|
129
|
+
major_on_zero = true
|
|
109
130
|
allow_zero_version = true
|
|
110
131
|
no_git_verify = false
|
|
111
132
|
tag_format = "{version}"
|
|
112
133
|
build_command = """
|
|
113
|
-
pip install -e '.[build]'
|
|
114
134
|
uv lock --upgrade-package sindripy
|
|
115
135
|
git add uv.lock
|
|
116
136
|
"""
|
|
@@ -27,8 +27,8 @@ class StringUuid(ValueObject[str]):
|
|
|
27
27
|
```python
|
|
28
28
|
class UserId(StringUuid):
|
|
29
29
|
@validate
|
|
30
|
-
def _validate_version(self
|
|
31
|
-
parsed_uuid = UUID(
|
|
30
|
+
def _validate_version(self) -> None:
|
|
31
|
+
parsed_uuid = UUID(self._value)
|
|
32
32
|
if parsed_uuid.version != 4:
|
|
33
33
|
raise ValueError("Only UUID version 4 allowed")
|
|
34
34
|
|
|
@@ -38,18 +38,18 @@ class StringUuid(ValueObject[str]):
|
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
40
|
@validate
|
|
41
|
-
def _ensure_has_value(self
|
|
42
|
-
if
|
|
41
|
+
def _ensure_has_value(self) -> None:
|
|
42
|
+
if self._value is None:
|
|
43
43
|
raise RequiredValueError
|
|
44
44
|
|
|
45
45
|
@validate
|
|
46
|
-
def _ensure_value_is_string(self
|
|
47
|
-
if not isinstance(
|
|
48
|
-
raise IncorrectValueTypeError(
|
|
46
|
+
def _ensure_value_is_string(self) -> None:
|
|
47
|
+
if not isinstance(self._value, str):
|
|
48
|
+
raise IncorrectValueTypeError(self._value, str)
|
|
49
49
|
|
|
50
50
|
@validate
|
|
51
|
-
def _ensure_value_has_valid_uuid_format(self
|
|
51
|
+
def _ensure_value_has_valid_uuid_format(self) -> None:
|
|
52
52
|
try:
|
|
53
|
-
UUID(
|
|
53
|
+
UUID(self._value)
|
|
54
54
|
except ValueError as error:
|
|
55
55
|
raise InvalidIdFormatError from error
|
|
@@ -23,7 +23,7 @@ class Boolean(ValueObject[bool]):
|
|
|
23
23
|
```python
|
|
24
24
|
class IsActive(Boolean):
|
|
25
25
|
@validate
|
|
26
|
-
def _validate_true_for_premium(self
|
|
26
|
+
def _validate_true_for_premium(self) -> None:
|
|
27
27
|
# Custom business logic can be added here
|
|
28
28
|
pass
|
|
29
29
|
|
|
@@ -34,11 +34,11 @@ class Boolean(ValueObject[bool]):
|
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
36
|
@validate
|
|
37
|
-
def _ensure_has_value(self
|
|
38
|
-
if
|
|
37
|
+
def _ensure_has_value(self) -> None:
|
|
38
|
+
if self._value is None:
|
|
39
39
|
raise RequiredValueError
|
|
40
40
|
|
|
41
41
|
@validate
|
|
42
|
-
def _ensure_value_is_boolean(self
|
|
43
|
-
if not isinstance(
|
|
44
|
-
raise IncorrectValueTypeError(
|
|
42
|
+
def _ensure_value_is_boolean(self) -> None:
|
|
43
|
+
if not isinstance(self._value, bool):
|
|
44
|
+
raise IncorrectValueTypeError(self._value, bool)
|
|
@@ -23,8 +23,8 @@ class Float(ValueObject[float]):
|
|
|
23
23
|
```python
|
|
24
24
|
class Price(Float):
|
|
25
25
|
@validate
|
|
26
|
-
def _validate_positive(self
|
|
27
|
-
if
|
|
26
|
+
def _validate_positive(self) -> None:
|
|
27
|
+
if self._value < 0:
|
|
28
28
|
raise ValueError("Price cannot be negative")
|
|
29
29
|
|
|
30
30
|
price = Price(29.99)
|
|
@@ -34,11 +34,11 @@ class Float(ValueObject[float]):
|
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
36
|
@validate
|
|
37
|
-
def _ensure_has_value(self
|
|
38
|
-
if
|
|
37
|
+
def _ensure_has_value(self) -> None:
|
|
38
|
+
if self._value is None:
|
|
39
39
|
raise RequiredValueError
|
|
40
40
|
|
|
41
41
|
@validate
|
|
42
|
-
def _ensure_value_is_float(self
|
|
43
|
-
if not isinstance(
|
|
44
|
-
raise IncorrectValueTypeError(
|
|
42
|
+
def _ensure_value_is_float(self) -> None:
|
|
43
|
+
if not isinstance(self._value, float):
|
|
44
|
+
raise IncorrectValueTypeError(self._value, float)
|
|
@@ -23,8 +23,8 @@ class Integer(ValueObject[int]):
|
|
|
23
23
|
```python
|
|
24
24
|
class Age(Integer):
|
|
25
25
|
@validate
|
|
26
|
-
def _validate_positive(self
|
|
27
|
-
if
|
|
26
|
+
def _validate_positive(self) -> None:
|
|
27
|
+
if self._value < 0:
|
|
28
28
|
raise ValueError("Age cannot be negative")
|
|
29
29
|
|
|
30
30
|
age = Age(25)
|
|
@@ -34,11 +34,11 @@ class Integer(ValueObject[int]):
|
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
36
|
@validate
|
|
37
|
-
def _ensure_has_value(self
|
|
38
|
-
if
|
|
37
|
+
def _ensure_has_value(self) -> None:
|
|
38
|
+
if self._value is None:
|
|
39
39
|
raise RequiredValueError
|
|
40
40
|
|
|
41
41
|
@validate
|
|
42
|
-
def _ensure_value_is_integer(self
|
|
43
|
-
if not isinstance(
|
|
44
|
-
raise IncorrectValueTypeError(
|
|
42
|
+
def _ensure_value_is_integer(self) -> None:
|
|
43
|
+
if not isinstance(self._value, int):
|
|
44
|
+
raise IncorrectValueTypeError(self._value, int)
|
|
@@ -14,7 +14,7 @@ class List(ValueObject[list[T]], Generic[T]):
|
|
|
14
14
|
@override
|
|
15
15
|
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
16
16
|
"""
|
|
17
|
-
Initialize subclass with proper type parameter validation.
|
|
17
|
+
Initialize a subclass with proper type parameter validation.
|
|
18
18
|
|
|
19
19
|
This method ensures that any subclass of List is properly parameterized
|
|
20
20
|
with a type argument and extracts the element type for validation purposes. It
|
|
@@ -41,17 +41,17 @@ class List(ValueObject[list[T]], Generic[T]):
|
|
|
41
41
|
return cls(elements)
|
|
42
42
|
|
|
43
43
|
@validate
|
|
44
|
-
def _ensure_has_value(self
|
|
45
|
-
if
|
|
44
|
+
def _ensure_has_value(self) -> None:
|
|
45
|
+
if self._value is None:
|
|
46
46
|
raise RequiredValueError
|
|
47
47
|
|
|
48
48
|
@validate
|
|
49
|
-
def _ensure_is_list(self
|
|
50
|
-
if not isinstance(
|
|
51
|
-
raise IncorrectValueTypeError(
|
|
49
|
+
def _ensure_is_list(self) -> None:
|
|
50
|
+
if not isinstance(self._value, list):
|
|
51
|
+
raise IncorrectValueTypeError(self._value, type[Any])
|
|
52
52
|
|
|
53
53
|
@validate
|
|
54
|
-
def _ensure_list_elements_have_expected_type(self
|
|
54
|
+
def _ensure_list_elements_have_expected_type(self) -> None:
|
|
55
55
|
cls = self.__class__
|
|
56
56
|
|
|
57
57
|
if not hasattr(cls, "_element_type"):
|
|
@@ -66,7 +66,7 @@ class List(ValueObject[list[T]], Generic[T]):
|
|
|
66
66
|
return
|
|
67
67
|
|
|
68
68
|
if cls._element_is_a_value_object_instance() or cls._element_is_a_primitive_type():
|
|
69
|
-
for item in
|
|
69
|
+
for item in self._value:
|
|
70
70
|
if not isinstance(item, element_type):
|
|
71
71
|
raise IncorrectValueTypeError(item, list)
|
|
72
72
|
|
|
@@ -23,8 +23,8 @@ class String(ValueObject[str]):
|
|
|
23
23
|
```python
|
|
24
24
|
class Email(String):
|
|
25
25
|
@validate
|
|
26
|
-
def _validate_email_format(self
|
|
27
|
-
if "@" not in
|
|
26
|
+
def _validate_email_format(self) -> None:
|
|
27
|
+
if "@" not in self._value:
|
|
28
28
|
raise ValueError("Invalid email format")
|
|
29
29
|
|
|
30
30
|
email = Email("user@example.com")
|
|
@@ -33,11 +33,11 @@ class String(ValueObject[str]):
|
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
35
|
@validate
|
|
36
|
-
def _ensure_has_value(self
|
|
37
|
-
if
|
|
36
|
+
def _ensure_has_value(self) -> None:
|
|
37
|
+
if self._value is None:
|
|
38
38
|
raise RequiredValueError
|
|
39
39
|
|
|
40
40
|
@validate
|
|
41
|
-
def _ensure_is_string(self
|
|
42
|
-
if not isinstance(
|
|
43
|
-
raise IncorrectValueTypeError(
|
|
41
|
+
def _ensure_is_string(self) -> None:
|
|
42
|
+
if not isinstance(self._value, str):
|
|
43
|
+
raise IncorrectValueTypeError(self._value, str)
|
|
@@ -41,8 +41,8 @@ class ValueObject(ABC, Generic[T]):
|
|
|
41
41
|
"""
|
|
42
42
|
Initialize the value object with the given value.
|
|
43
43
|
|
|
44
|
-
The value is validated using all methods decorated with @validate
|
|
45
|
-
|
|
44
|
+
The value is validated using all methods decorated with @validate once it has
|
|
45
|
+
been stored. Once initialized and validated, the value cannot be modified.
|
|
46
46
|
|
|
47
47
|
Args:
|
|
48
48
|
value: The value to be wrapped by this value object.
|
|
@@ -60,20 +60,52 @@ class ValueObject(ABC, Generic[T]):
|
|
|
60
60
|
repr(email) # String(_value='Hello World')
|
|
61
61
|
```
|
|
62
62
|
"""
|
|
63
|
-
self._validate(value)
|
|
64
63
|
object.__setattr__(self, "_value", value)
|
|
64
|
+
self._validate()
|
|
65
65
|
|
|
66
|
-
def
|
|
66
|
+
def _call_validator_with_adapter(self, validator: Callable) -> None:
|
|
67
67
|
"""
|
|
68
|
-
|
|
68
|
+
Call validator method with backward compatibility support.
|
|
69
|
+
|
|
70
|
+
Supports both old signature (method(self, value)) and new signature (method(self)).
|
|
71
|
+
Detects the validator signature and calls it appropriately.
|
|
72
|
+
"""
|
|
73
|
+
import inspect
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
# Get the signature of the validator method
|
|
77
|
+
sig = inspect.signature(validator)
|
|
78
|
+
params = list(sig.parameters.keys())
|
|
79
|
+
|
|
80
|
+
# Check if it expects a value parameter (old signature)
|
|
81
|
+
if len(params) == 2 and params[1] != "self":
|
|
82
|
+
# Old signature: method(self, value) - emit deprecation warning
|
|
83
|
+
_warn_deprecation(validator.__name__)
|
|
84
|
+
validator(self._value) # type: ignore[arg-type]
|
|
85
|
+
else:
|
|
86
|
+
# New signature: method(self)
|
|
87
|
+
validator()
|
|
88
|
+
except (ValueError, TypeError):
|
|
89
|
+
# If signature inspection fails, try new signature first, then old
|
|
90
|
+
try:
|
|
91
|
+
validator()
|
|
92
|
+
except Exception:
|
|
93
|
+
# Try old signature as fallback
|
|
94
|
+
_warn_deprecation(validator.__name__)
|
|
95
|
+
try:
|
|
96
|
+
validator(self._value) # type: ignore[arg-type]
|
|
97
|
+
except Exception:
|
|
98
|
+
# If both fail, re-raise the original error
|
|
99
|
+
raise
|
|
100
|
+
|
|
101
|
+
def _validate(self) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Validates the stored value using all methods decorated with @validate.
|
|
69
104
|
|
|
70
105
|
This method collects all validator methods from the class hierarchy (in reverse MRO order)
|
|
71
106
|
and executes them in the order specified by their _order attribute. All validators
|
|
72
107
|
must pass for the value to be considered valid.
|
|
73
108
|
|
|
74
|
-
Args:
|
|
75
|
-
value: The value to validate.
|
|
76
|
-
|
|
77
109
|
Raises:
|
|
78
110
|
Various validation errors if any validator fails.
|
|
79
111
|
|
|
@@ -81,25 +113,25 @@ class ValueObject(ABC, Generic[T]):
|
|
|
81
113
|
```python
|
|
82
114
|
class Username(ValueObject[str]):
|
|
83
115
|
@validate(order=1)
|
|
84
|
-
def _validate_not_empty(self
|
|
85
|
-
if not
|
|
116
|
+
def _validate_not_empty(self) -> None:
|
|
117
|
+
if not self._value.strip():
|
|
86
118
|
raise ValueError("Username cannot be empty")
|
|
87
119
|
|
|
88
120
|
@validate(order=2)
|
|
89
|
-
def _validate_length(self
|
|
90
|
-
if len(
|
|
121
|
+
def _validate_length(self) -> None:
|
|
122
|
+
if len(self._value) < 3:
|
|
91
123
|
raise ValueError("Username must be at least 3 characters")
|
|
92
124
|
|
|
93
125
|
username = Username("john") # Both validators pass
|
|
94
|
-
username._validate(
|
|
126
|
+
username._validate() # Would raise ValueError for length
|
|
95
127
|
```
|
|
96
128
|
"""
|
|
97
|
-
validators: list[Callable[[
|
|
129
|
+
validators: list[Callable[[], None]] = []
|
|
98
130
|
for cls in reversed(self.__class__.__mro__):
|
|
99
131
|
if cls is object:
|
|
100
132
|
continue
|
|
101
133
|
|
|
102
|
-
methods: list[tuple[int, Callable[[
|
|
134
|
+
methods: list[tuple[int, Callable[[], None]]] = []
|
|
103
135
|
for name, member in cls.__dict__.items():
|
|
104
136
|
if getattr(member, "_is_validator", False):
|
|
105
137
|
validators.append(getattr(self, name))
|
|
@@ -110,7 +142,7 @@ class ValueObject(ABC, Generic[T]):
|
|
|
110
142
|
validators.append(method)
|
|
111
143
|
|
|
112
144
|
for validator in validators:
|
|
113
|
-
validator
|
|
145
|
+
self._call_validator_with_adapter(validator)
|
|
114
146
|
|
|
115
147
|
@property
|
|
116
148
|
def value(self) -> T:
|
|
@@ -161,7 +193,7 @@ class ValueObject(ABC, Generic[T]):
|
|
|
161
193
|
if not isinstance(other, self.__class__):
|
|
162
194
|
return False
|
|
163
195
|
|
|
164
|
-
return self.
|
|
196
|
+
return self._value == other._value
|
|
165
197
|
|
|
166
198
|
@override
|
|
167
199
|
def __repr__(self) -> str:
|
|
@@ -267,3 +299,15 @@ class ValueObject(ABC, Generic[T]):
|
|
|
267
299
|
raise AttributeError("Cannot modify the value of a ValueObject")
|
|
268
300
|
|
|
269
301
|
raise AttributeError(f"Class {self.__class__.__name__} object has no attribute '{name}'")
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _warn_deprecation(validator_name: str) -> None:
|
|
305
|
+
import warnings
|
|
306
|
+
|
|
307
|
+
warnings.warn(
|
|
308
|
+
f"Validator '{validator_name}' may use deprecated signature. "
|
|
309
|
+
f"Update to '{validator_name}(self)' instead of '{validator_name}(self, value)'. "
|
|
310
|
+
"See migration guide for details: https://dimanu-py.github.io/sindri/value_objects/mibration_guide/",
|
|
311
|
+
DeprecationWarning,
|
|
312
|
+
stacklevel=4,
|
|
313
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/identifiers/string_uuid_primitives_mother.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/boolean_primitives_mother.py
RENAMED
|
File without changes
|
{sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/float_primitives_mother.py
RENAMED
|
File without changes
|
{sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/integer_primitives_mother.py
RENAMED
|
File without changes
|
|
File without changes
|
{sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/string_primitives_mother.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/incorrect_value_type_error.py
RENAMED
|
File without changes
|
{sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/invalid_id_format_error.py
RENAMED
|
File without changes
|
|
File without changes
|
{sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/sindri_validation_error.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|