Skip to content

Commit 829c430

Browse files
0.1.6.1 (#3)
* Bumps version to 0.1.5.1 Updates the package version to 0.1.5.1 in pyproject.toml and uv.lock. This increment addresses a minor release. * Implements immutability for value objects Ensures that value objects are immutable after initialization by preventing attribute reassignment and deletion. This enhances data integrity and prevents unintended modifications to value object state. * Bumps version to 0.1.6 Updates the package version to 0.1.6 in pyproject.toml and uv.lock. This version bump reflects changes and improvements. * Implements immutability for value objects Ensures that value objects are immutable after initialization by preventing attribute reassignment and deletion. This enhances data integrity and prevents unintended modifications to value object state. * Bumps version to 0.1.6 Updates the package version to 0.1.6 in pyproject.toml and uv.lock. This version bump reflects changes and improvements. * Refactors value object classes Simplifies value object implementations by removing unnecessary comments and focusing on core functionality. This improves code readability and maintainability. Specifically, removes redundant comments in `CompositeValueObject` and `CurrencyValueObject`. Also, refines equality checks and hashing in `CompositeValueObject` and removes redundant tests. Updates URL regex in `UrlValueObject` to fix a minor issue. * Adds immutability tests to value objects Adds tests to ensure that value objects and composite value objects are immutable after initialization. Also adds tests to verify correct behavior when `super().__init__` is not called in derived classes. * Bumps version to 0.1.6.1 Updates the package version to address a bug fix or minor enhancement. * Refactors value object classes Simplifies value object implementations by removing unnecessary comments and focusing on core functionality. This improves code readability and maintainability. Specifically, removes redundant comments in `CompositeValueObject` and `CurrencyValueObject`. Also, refines equality checks and hashing in `CompositeValueObject` and removes redundant tests. Updates URL regex in `UrlValueObject` to fix a minor issue. * Adds immutability tests to value objects Adds tests to ensure that value objects and composite value objects are immutable after initialization. Also adds tests to verify correct behavior when `super().__init__` is not called in derived classes. * Bumps version to 0.1.6.1 Updates the package version to address a bug fix or minor enhancement.
1 parent 111a8e3 commit 829c430

8 files changed

Lines changed: 41 additions & 15 deletions

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "ddd-value-objects"
3-
version = "0.1.6"
3+
version = "0.1.6.1"
44
description = "A collection of base classes for Domain-Driven Design (DDD) value objects and entities in Python"
55
readme = "README.md"
66
license = {text = "MIT"}

src/ddd_value_objects/composite_value_object.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
class CompositeValueObject(ABC, Generic[T]):
99
def __init__(self, value: T):
10-
# Convert dictionary to a more immutable form if possible,
11-
# but at least prevent reassignment of _value
1210
self._ensure_value_is_defined(value)
1311
object.__setattr__(self, '_value', dict(value))
1412
object.__setattr__(self, '_initialized', True)
@@ -25,7 +23,6 @@ def __delattr__(self, name: str) -> None:
2523

2624
@property
2725
def value(self) -> T:
28-
# Return a copy to prevent mutation of the internal dictionary
2926
return dict(self._value)
3027

3128
def equals(self, other: 'CompositeValueObject[T]') -> bool:
@@ -37,8 +34,6 @@ def __eq__(self, other: object) -> bool:
3734
return self.equals(other)
3835

3936
def __hash__(self) -> int:
40-
# Los diccionarios no son hasheables, así que convertimos a items ordenados si es posible
41-
# o usamos una representación inmutable para el hash.
4237
return hash((self.__class__, tuple(sorted(self._value.items()))))
4338

4439
def __str__(self) -> str:

src/ddd_value_objects/currency_value_object.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55

66
class CurrencyValueObject(StringValueObject):
7-
# Código ISO 4217: 3 letras mayúsculas
87
CURRENCY_REGEX = re.compile(r"^[A-Z]{3}$")
98

109
def __init__(self, value: str):

src/ddd_value_objects/url_value_object.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class UrlValueObject(StringValueObject):
88
URL_REGEX = re.compile(
99
r'^(?:http|ftp)s?://'
10-
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
10+
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'
1111
r'localhost|'
1212
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
1313
r'(?::\d+)?'

tests/test_composite_value_object.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ def test_composite_value_object_equality():
2626
assert vo1 != vo3
2727
assert vo1 != "not a vo"
2828

29-
# Test different classes
3029
class AnotherComposite(CompositeValueObject[dict]):
3130
pass
3231
vo4 = AnotherComposite({'a': 1})
@@ -36,9 +35,26 @@ class AnotherComposite(CompositeValueObject[dict]):
3635
def test_composite_value_object_hash():
3736
vo1 = MockCompositeValueObject({'a': 1, 'b': 2})
3837
vo2 = MockCompositeValueObject({'a': 1, 'b': 2})
39-
vo3 = MockCompositeValueObject({'b': 2, 'a': 1}) # Same content, different order
38+
vo3 = MockCompositeValueObject({'b': 2, 'a': 1})
4039
vo4 = MockCompositeValueObject({'a': 1})
4140

4241
assert hash(vo1) == hash(vo2)
4342
assert hash(vo1) == hash(vo3)
4443
assert hash(vo1) != hash(vo4)
44+
45+
def test_composite_value_object_immutability():
46+
vo = MockCompositeValueObject({'a': 1})
47+
with pytest.raises(TypeError, match="MockCompositeValueObject is immutable"):
48+
vo.a = 2
49+
with pytest.raises(TypeError, match="MockCompositeValueObject is immutable"):
50+
del vo._value
51+
52+
def test_composite_value_object_not_initialized():
53+
class UninitializedComposite(CompositeValueObject[dict]):
54+
def __init__(self, value: dict):
55+
pass
56+
57+
vo = UninitializedComposite({'a': 1})
58+
vo.any_attr = 100
59+
assert vo.any_attr == 100
60+
del vo.any_attr

tests/test_phone_number_value_object.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,5 @@ def test_phone_number_value_object_repr():
4646
assert repr(vo) == "PhoneNumberValueObject(value='+34600000000')"
4747

4848
def test_phone_number_value_object_clean_non_string():
49-
# This covers the case in _clean_number where value is not a string
50-
# Since _ensure_is_valid_phone expects a string for regex matching,
51-
# passing an int will eventually raise a TypeError.
5249
with pytest.raises(TypeError):
5350
PhoneNumberValueObject(123456789)

tests/test_value_object.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ def test_value_object_equality():
3030
assert vo1.equals(vo2)
3131
assert not vo1.equals(vo3)
3232

33-
# Test __eq__
3433
assert vo1 == vo2
3534
assert vo1 != vo3
3635
assert vo1 != "not a value object"
@@ -44,6 +43,26 @@ def test_value_object_hash():
4443
assert hash(vo1) == hash(vo2)
4544
assert hash(vo1) != hash(vo3)
4645

46+
def test_value_object_immutability():
47+
vo = MockValueObject(10)
48+
with pytest.raises(TypeError, match="MockValueObject is immutable"):
49+
vo.value = 20
50+
with pytest.raises(TypeError, match="MockValueObject is immutable"):
51+
del vo._value
52+
53+
def test_value_object_not_initialized():
54+
class UninitializedVO(ValueObject[int]):
55+
def __init__(self, value: int):
56+
# No llama a super().__init__(value)
57+
pass
58+
def equals(self, other: ValueObject) -> bool:
59+
return True
60+
61+
vo = UninitializedVO(10)
62+
vo.any_attr = 100
63+
assert vo.any_attr == 100
64+
del vo.any_attr
65+
4766
def test_invalid_argument_error_str_repr():
4867
err = InvalidArgumentError("Test message", {"param": "value"})
4968
assert str(err) == "Test message {'param': 'value'}"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)