Skip to content

Commit 5412373

Browse files
Add support for explicitly indicating that a value should remain unchanged via UNCHANGED.
1 parent 756bf53 commit 5412373

File tree

6 files changed

+25
-8
lines changed

6 files changed

+25
-8
lines changed

.github/workflows/publish.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
- name: Set up Python
1414
uses: actions/setup-python@v1
1515
with:
16-
python-version: '3.7'
16+
python-version: '3.10'
1717
- name: Install dependencies
1818
run: |
1919
python -m pip install --upgrade pip

spec_classes/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
EMPTY,
66
MISSING,
77
SENTINEL,
8+
UNCHANGED,
89
Alias,
910
Attr,
1011
AttrProxy,
@@ -32,4 +33,5 @@
3233
"MISSING",
3334
"EMPTY",
3435
"SENTINEL",
36+
"UNCHANGED",
3537
]

spec_classes/types/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from .attr import Attr
33
from .attr_proxy import AttrProxy
44
from .keyed import KeyedList, KeyedSet
5-
from .missing import EMPTY, MISSING, SENTINEL
5+
from .missing import EMPTY, MISSING, SENTINEL, UNCHANGED
66
from .spec_property import classproperty, spec_property
77
from .validated import ValidatedType, bounded, validated
88

@@ -16,6 +16,7 @@
1616
"MISSING",
1717
"EMPTY",
1818
"SENTINEL",
19+
"UNCHANGED",
1920
"ValidatedType",
2021
"bounded",
2122
"spec_property",

spec_classes/types/missing.py

+7
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,10 @@ class SENTINEL(metaclass=_MissingType):
3434
"""
3535
A generic sentinel that can be used to check fallthrough conditions.
3636
"""
37+
38+
39+
class UNCHANGED(metaclass=_MissingType):
40+
"""
41+
Can be passed in by user to indicate that whatever value is currently set
42+
should remain unchanged.
43+
"""

spec_classes/utils/mutation.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from lazy_object_proxy import Proxy
1111

1212
from spec_classes.errors import FrozenInstanceError
13-
from spec_classes.types import MISSING, Attr
13+
from spec_classes.types import EMPTY, MISSING, UNCHANGED, Attr
1414

1515
from .type_checking import check_type, type_label
1616

@@ -94,7 +94,7 @@ def mutate_attr(
9494
instance. If `inplace` is `False`, copy the instance before assigning
9595
the new attribute value.
9696
"""
97-
if value is MISSING:
97+
if value in (MISSING, EMPTY, UNCHANGED):
9898
return obj
9999

100100
metadata = getattr(obj, "__spec_class__", None)
@@ -217,14 +217,17 @@ def mutate_value(
217217
Returns:
218218
The mutated object.
219219
"""
220+
if new_value is UNCHANGED:
221+
return old_value.__wrapped__ if isinstance(old_value, Proxy) else old_value
222+
220223
mutate_safe = inplace
221224
used_attrs = set()
222225

223226
# If `new_value` is not `MISSING`, use it; otherwise use `old_value` if not
224227
# `replace`; otherwise use MISSING.
225-
if new_value is not MISSING:
228+
if new_value not in (MISSING, EMPTY, UNCHANGED):
226229
value = new_value
227-
elif not replace:
230+
elif new_value is UNCHANGED or not replace:
228231
value = old_value
229232
prepare = None # Old values have already been prepared, so we suppress further preparation.
230233
else:
@@ -275,7 +278,7 @@ def mutate_value(
275278
value = constructor()
276279

277280
# If there are any left-over attributes to apply to our value, we do so here.
278-
if value is not None and value is not MISSING and attrs:
281+
if value not in (None, MISSING) and attrs:
279282
if not mutate_safe:
280283
value = protect_via_deepcopy(value)
281284
mutate_safe = True

tests/methods/test_scalar.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import pytest
55

6-
from spec_classes import MISSING
6+
from spec_classes import MISSING, UNCHANGED
77

88

99
class TestScalarAttribute:
@@ -21,6 +21,10 @@ def test_with(self, spec_cls):
2121
assert spec.with_scalar(4, _inplace=True) is spec
2222
assert spec.scalar == 4
2323

24+
assert spec.with_scalar(UNCHANGED) is spec
25+
assert spec.with_scalar(UNCHANGED, _inplace=True) is spec
26+
assert spec.scalar == 4
27+
2428
def test_transform(self, spec_cls):
2529
spec = spec_cls(scalar=3)
2630
assert set(

0 commit comments

Comments
 (0)