Skip to content

Commit

Permalink
Add support for explicitly indicating that a value should remain unch…
Browse files Browse the repository at this point in the history
…anged via `UNCHANGED`.
  • Loading branch information
matthewwardrop committed Dec 16, 2024
1 parent 756bf53 commit 5412373
Show file tree
Hide file tree
Showing 6 changed files with 25 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.7'
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
2 changes: 2 additions & 0 deletions spec_classes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
EMPTY,
MISSING,
SENTINEL,
UNCHANGED,
Alias,
Attr,
AttrProxy,
Expand Down Expand Up @@ -32,4 +33,5 @@
"MISSING",
"EMPTY",
"SENTINEL",
"UNCHANGED",
]
3 changes: 2 additions & 1 deletion spec_classes/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from .attr import Attr
from .attr_proxy import AttrProxy
from .keyed import KeyedList, KeyedSet
from .missing import EMPTY, MISSING, SENTINEL
from .missing import EMPTY, MISSING, SENTINEL, UNCHANGED
from .spec_property import classproperty, spec_property
from .validated import ValidatedType, bounded, validated

Expand All @@ -16,6 +16,7 @@
"MISSING",
"EMPTY",
"SENTINEL",
"UNCHANGED",
"ValidatedType",
"bounded",
"spec_property",
Expand Down
7 changes: 7 additions & 0 deletions spec_classes/types/missing.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,10 @@ class SENTINEL(metaclass=_MissingType):
"""
A generic sentinel that can be used to check fallthrough conditions.
"""


class UNCHANGED(metaclass=_MissingType):
"""
Can be passed in by user to indicate that whatever value is currently set
should remain unchanged.
"""
13 changes: 8 additions & 5 deletions spec_classes/utils/mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from lazy_object_proxy import Proxy

from spec_classes.errors import FrozenInstanceError
from spec_classes.types import MISSING, Attr
from spec_classes.types import EMPTY, MISSING, UNCHANGED, Attr

from .type_checking import check_type, type_label

Expand Down Expand Up @@ -94,7 +94,7 @@ def mutate_attr(
instance. If `inplace` is `False`, copy the instance before assigning
the new attribute value.
"""
if value is MISSING:
if value in (MISSING, EMPTY, UNCHANGED):
return obj

metadata = getattr(obj, "__spec_class__", None)
Expand Down Expand Up @@ -217,14 +217,17 @@ def mutate_value(
Returns:
The mutated object.
"""
if new_value is UNCHANGED:
return old_value.__wrapped__ if isinstance(old_value, Proxy) else old_value

mutate_safe = inplace
used_attrs = set()

# If `new_value` is not `MISSING`, use it; otherwise use `old_value` if not
# `replace`; otherwise use MISSING.
if new_value is not MISSING:
if new_value not in (MISSING, EMPTY, UNCHANGED):
value = new_value
elif not replace:
elif new_value is UNCHANGED or not replace:
value = old_value
prepare = None # Old values have already been prepared, so we suppress further preparation.
else:
Expand Down Expand Up @@ -275,7 +278,7 @@ def mutate_value(
value = constructor()

# If there are any left-over attributes to apply to our value, we do so here.
if value is not None and value is not MISSING and attrs:
if value not in (None, MISSING) and attrs:
if not mutate_safe:
value = protect_via_deepcopy(value)
mutate_safe = True
Expand Down
6 changes: 5 additions & 1 deletion tests/methods/test_scalar.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from spec_classes import MISSING
from spec_classes import MISSING, UNCHANGED


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

assert spec.with_scalar(UNCHANGED) is spec
assert spec.with_scalar(UNCHANGED, _inplace=True) is spec
assert spec.scalar == 4

def test_transform(self, spec_cls):
spec = spec_cls(scalar=3)
assert set(
Expand Down

0 comments on commit 5412373

Please sign in to comment.