pulumi/pulumi · error · TypeError

Can't set key values on non-dict variables.

Error message

Can't set key values on non-dict variables.

What it means

Pulumi's `_utils.py` provides a descriptor-based lazy property wrapper whose `__setitem__` delegates to the wrapped getter, mutates the returned mapping, and writes it back via the setter. If the getter returns anything that is not a `dict`, key assignment is impossible and this TypeError is raised. It protects against silently overwriting non-dict values.

Source

Thrown at sdk/python/lib/pulumi/_utils.py:228

    def fdel(self, *_):
        return self._data.reset()

    def __set__(self, _, v: typing.Any):
        return self.fset(v)

    def __get__(self, obj: typing.Optional[typing.Any], _):
        if obj is None or isinstance(self.fget(), dict):
            return self
        return self.fget()

    def __delete__(self, *_):
        self.fdel()

    def __setitem__(self, key, value):
        data = self.fget()
        if not isinstance(data, dict):
            raise TypeError("Can't set key values on non-dict variables.")
        data[key] = value
        return self.fset(data)

    def __dict__(self):
        if not isinstance(self.fget(), dict):
            raise TypeError(f"{self} is not a dictionary")
        return self.fget()

    def __getitem__(self, key):
        return self.fget()[key]

    def get(self, key, default=None):
        data = self.fget()
        if not isinstance(data, dict):
            raise TypeError("Can't get key values on non-dict variables.")
        return data.get(key, default)

    def __contains__(self, element):

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Make the underlying getter return a dict (initialize it to `{}` instead of None).
  2. Assign the whole value instead of item assignment: `obj.prop = {'key': value}`.
  3. If the value is genuinely a list, build the list separately and assign it via the setter.
  4. Guard with isinstance before item assignment.

Example fix

// before
obj.tags["env"] = "prod"  # getter returns None

// after
if isinstance(obj.tags, dict):
    obj.tags["env"] = "prod"
else:
    obj.tags = {"env": "prod"}
Defensive patterns

Strategy: type-guard

Validate before calling

value = obj.prop
if not isinstance(value, dict):
    obj.prop = {}
obj.prop[key] = new_value

Type guard

def is_dict_value(v) -> TypeGuard[dict]:
    return isinstance(v, dict)

Try / catch

try:
    obj.prop[key] = value
except TypeError as e:
    if "non-dict" in str(e):
        obj.prop = {key: value}
    else:
        raise

Prevention

When it happens

Trigger: Executing `obj.prop[key] = value` where `obj.prop` is the wrapped property and its getter returns a list, string, None, or custom object instead of a dict.

Common situations: A config/opts getter that was changed to return a list or Optional[dict]; the underlying attribute still None before initialization; a refactored getter returning a dataclass or named tuple.

Related errors


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/5e772720f877641b. Report an issue: GitHub.