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,
        )
Beispiel #3
0
    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
Beispiel #4
0
 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")