def transform( self, _transform=None, *, _inplace: bool = False, _if: bool = True, **attr_transforms, ): if not _if: return self return mutate_value( old_value=self, transform=_transform, attr_transforms=attr_transforms, inplace=_inplace, )
def update( self, _new_value=MISSING, *, _inplace: bool = False, _if: bool = True, **attrs, ): if not _if: return self return mutate_value( old_value=self, new_value=_new_value, attrs=attrs, inplace=_inplace, )
def _mutate_collection( self, value_or_index: Any, extractor: Callable, inserter: Callable, *, require_pre_existent: bool = False, new_item: Any = MISSING, transform: Callable = None, attrs: Dict[str, Any] = None, attr_transforms: Dict[str, Callable] = None, replace: bool = False, ) -> Any: """ General strategy for mutation elements within a collection, which wraps `cls._get_updated_value`. Extraction and insertion are handled by the functions passed in as `extractor` and `inserter` functions respectively. Extractor functions must have a signature of: `(collection, value_or_index)`, and output the index and value of existing items in the collection. Inserter functions must have a signature of: `(collection, index, new_item)`, and insert the given item into the collection appropriately. `index` will be the same `index` as that output by the extractor, and is not otherwise interpreted. """ if self.collection is MISSING: self.collection = self._create_collection() index, old_item = extractor( value_or_index, raise_if_missing=require_pre_existent ) new_item = mutate_value( old_value=old_item, new_value=new_item, prepare=self.prepare_item, attrs=attrs, constructor=self.attr_spec.item_type, transform=transform, attr_transforms=attr_transforms, replace=replace, inplace=False, # Although we've already copied, index lookups may depend on the old value. ) inserter(index, new_item) return self
def transform_attr( attr_spec: Attr, self, _transform=None, *, _inplace: bool = False, _if: bool = True, **attr_transforms, ): if not _if: return self return WithAttrMethod.with_attr( attr_spec, self, _new_value=mutate_value( old_value=Proxy( lambda: getattr(self, attr_spec.name, MISSING)), transform=_transform, constructor=attr_spec.type, attr_transforms=attr_transforms, ), _inplace=_inplace, )
def test_mutate_value(): # Simple update assert mutate_value(MISSING, new_value="value") == "value" assert mutate_value("old_value", new_value="value") == "value" # Via constructor assert mutate_value(MISSING, constructor=str) == "" assert mutate_value(MISSING, constructor=list) == [] # Via proxy obj = object() assert mutate_value(Proxy(lambda: obj)) is obj # Via transform assert (mutate_value( "old_value", transform=lambda x: x + "_renewed") == "old_value_renewed") # Nested types class Object: attr = "default" assert (mutate_value(MISSING, constructor=Object, attrs={ "attr": "value" }).attr == "value") assert (mutate_value( MISSING, constructor=Object, attr_transforms={ "attr": lambda x: f"{x}-transformed!" }, ).attr == "default-transformed!") assert not hasattr( mutate_value(MISSING, constructor=Object, attr_transforms={"missing_attr": lambda x: x}), "missing_attr", ) assert (mutate_value(MISSING, constructor=Spec, attrs={ "key": "key", "scalar": 10 }).key == "key") assert (mutate_value(MISSING, constructor=Spec, attrs={ "key": "key", "scalar": 10 }).scalar == 10) assert (mutate_value(Spec(key="key", scalar=10), constructor=Spec, attrs={ "list_values": [20] }).key == "key") assert (mutate_value(Spec(key="key", scalar=10), constructor=Spec, attrs={ "list_values": [20] }).scalar == 10) assert mutate_value(Spec(key="key", scalar=10), constructor=Spec, attrs={ "list_values": [20] }).list_values == [20] assert (mutate_value(MISSING, constructor=Spec, attrs={ "extra_attr": "value" }).extra_attr == "value") assert (mutate_value(MISSING, constructor=Spec, attr_transforms={ "key": lambda x: "override" }).key == "override") assert (mutate_value(MISSING, constructor=Spec, attr_transforms={ "new_attr": lambda x: "value" }).new_attr == "value") assert mutate_value(Spec(key="my_key"), constructor=Spec, replace=True).key == "key" with pytest.raises( ValueError, match="Cannot use attrs on a missing value without a constructor." ): assert mutate_value(MISSING, attrs={"invalid_attr": "value"}) # Verify that construction is not recursive. assert not hasattr( mutate_value(MISSING, constructor=Spec, attrs={"scalar": MISSING}), "scalar")