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.
Files changed (37) hide show
  1. {sindripy-0.1.6 → sindripy-1.0.0}/.gitignore +4 -1
  2. {sindripy-0.1.6 → sindripy-1.0.0}/CHANGELOG.md +55 -0
  3. {sindripy-0.1.6 → sindripy-1.0.0}/PKG-INFO +32 -5
  4. {sindripy-0.1.6 → sindripy-1.0.0}/README.md +29 -4
  5. {sindripy-0.1.6 → sindripy-1.0.0}/pyproject.toml +25 -5
  6. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/__init__.py +1 -1
  7. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/identifiers/string_uuid.py +9 -9
  8. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/boolean.py +6 -6
  9. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/float.py +7 -7
  10. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/integer.py +7 -7
  11. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/list.py +8 -8
  12. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/string.py +7 -7
  13. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/value_object.py +61 -17
  14. {sindripy-0.1.6 → sindripy-1.0.0}/LICENSE +0 -0
  15. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/_compat.py +0 -0
  16. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/__init__.py +0 -0
  17. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/identifiers/__init__.py +0 -0
  18. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/identifiers/string_uuid_primitives_mother.py +0 -0
  19. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/object_mother.py +0 -0
  20. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/__init__.py +0 -0
  21. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/boolean_primitives_mother.py +0 -0
  22. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/float_primitives_mother.py +0 -0
  23. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/integer_primitives_mother.py +0 -0
  24. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/list_primitives_mother.py +0 -0
  25. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/mothers/primitives/string_primitives_mother.py +0 -0
  26. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/py.typed +0 -0
  27. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/__init__.py +0 -0
  28. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/aggregate.py +0 -0
  29. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/decorators/__init__.py +0 -0
  30. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/decorators/validation.py +0 -0
  31. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/__init__.py +0 -0
  32. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/incorrect_value_type_error.py +0 -0
  33. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/invalid_id_format_error.py +0 -0
  34. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/required_value_error.py +0 -0
  35. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/errors/sindri_validation_error.py +0 -0
  36. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/identifiers/__init__.py +0 -0
  37. {sindripy-0.1.6 → sindripy-1.0.0}/src/sindripy/value_objects/primitives/__init__.py +0 -0
@@ -161,4 +161,7 @@ dmypy.json
161
161
  cython_debug/
162
162
 
163
163
  # PyPI configuration file
164
- .pypirc
164
+ .pypirc
165
+
166
+ #IDEs and editors
167
+ .idea
@@ -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.1.6
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
- <div align="center">
49
- <h1>🛠️ Sindripy 🛠️</h1>
50
- <strong>Easy use and customizable implementation for Value Object and Object Mother patterns.</strong>
51
- </div>
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>&nbsp;&nbsp;•&nbsp;
@@ -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
- <div align="center">
2
- <h1>🛠️ Sindripy 🛠️</h1>
3
- <strong>Easy use and customizable implementation for Value Object and Object Mother patterns.</strong>
4
- </div>
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>&nbsp;&nbsp;•&nbsp;
@@ -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.1.6"
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 = false
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
  """
@@ -8,4 +8,4 @@ installed as a dependency.
8
8
  from sindripy import mothers, value_objects
9
9
 
10
10
  __all__ = ["mothers", "value_objects"]
11
- __version__ = "0.1.6"
11
+ __version__ = "1.0.0"
@@ -27,8 +27,8 @@ class StringUuid(ValueObject[str]):
27
27
  ```python
28
28
  class UserId(StringUuid):
29
29
  @validate
30
- def _validate_version(self, value: str) -> None:
31
- parsed_uuid = UUID(value)
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, value: str) -> None:
42
- if value is None:
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, value: str) -> None:
47
- if not isinstance(value, str):
48
- raise IncorrectValueTypeError(value, str)
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, value: str) -> None:
51
+ def _ensure_value_has_valid_uuid_format(self) -> None:
52
52
  try:
53
- UUID(value)
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, value: bool) -> None:
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, value: bool) -> None:
38
- if value is None:
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, value: bool) -> None:
43
- if not isinstance(value, bool):
44
- raise IncorrectValueTypeError(value, bool)
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, value: float) -> None:
27
- if value < 0:
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, value: float) -> None:
38
- if value is None:
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, value: float) -> None:
43
- if not isinstance(value, float):
44
- raise IncorrectValueTypeError(value, float)
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, value: int) -> None:
27
- if value < 0:
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, value: int) -> None:
38
- if value is None:
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, value: int) -> None:
43
- if not isinstance(value, int):
44
- raise IncorrectValueTypeError(value, int)
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, value: list[T]) -> None:
45
- if value is None:
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, value: list[T]) -> None:
50
- if not isinstance(value, list):
51
- raise IncorrectValueTypeError(value, type[Any])
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, value: list[T]) -> None:
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 value:
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, value: str) -> None:
27
- if "@" not in value:
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, value: str) -> None:
37
- if value is None:
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, value: str) -> None:
42
- if not isinstance(value, str):
43
- raise IncorrectValueTypeError(value, str)
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 before
45
- being stored. Once initialized, the value cannot be modified.
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 _validate(self, value: T) -> None:
66
+ def _call_validator_with_adapter(self, validator: Callable) -> None:
67
67
  """
68
- Validates the given value using all methods decorated with @validate.
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, value: str) -> None:
85
- if not value.strip():
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, value: str) -> None:
90
- if len(value) < 3:
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("ab") # Would raise ValueError for length
126
+ username._validate() # Would raise ValueError for length
95
127
  ```
96
128
  """
97
- validators: list[Callable[[T], None]] = []
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[[T], None]]] = []
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(value)
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.value == other.value
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