pulumi/pulumi · error · ValueError

Optional type with no non-None elements

Error message

Optional type with no non-None elements

What it means

Raised by unwrap_optional() in the Pulumi Python component analyzer. Given a typing annotation, it asserts the type is Optional (a Union containing None) and returns the single non-None element. If the annotation is optional-shaped per is_optional() but get_args() yields only NoneType elements (or no args at all), there is nothing to unwrap, so this ValueError is thrown.

Source

Thrown at sdk/python/lib/pulumi/provider/experimental/analyzer.py:881

        return _NoneType in get_args(typ)
    return False


def is_any(typ: type) -> bool:
    return typ is Any


def unwrap_optional(typ: type) -> type:
    """
    Returns the first type of the Union that is not NoneType.
    """
    if not is_optional(typ):
        raise ValueError("Not an optional type")
    elements = get_args(typ)
    for element in elements:
        if element is not _NoneType:
            return element
    raise ValueError("Optional type with no non-None elements")


def is_not_required(typ: Any) -> bool:
    if not _NotRequiredTypes:
        return False
    return get_origin(typ) in _NotRequiredTypes


def unwrap_not_required(typ: Any) -> type:
    if not is_not_required(typ):
        raise ValueError(f"{typ} is not a NotRequired type")
    return get_args(typ)[0]


def is_required(typ: Any) -> bool:
    if not _RequiredTypes:
        return False
    return get_origin(typ) in _RequiredTypes

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Fix the annotation to include a concrete inner type, e.g. Optional[str] instead of Optional[None] or bare None
  2. If the type is built dynamically, verify get_args(typ) before calling unwrap_optional
  3. Check for typos where the type argument was omitted
  4. If a property should just be optional, express it as Optional[InnerT] or NotRequired[InnerT] in the args class

Example fix

# before
class MyArgs(TypedDict):
    name: Optional[None]

# after
class MyArgs(TypedDict):
    name: Optional[str]
Defensive patterns

Strategy: type-guard

Validate before calling

from typing import get_args, get_origin, Union, Optional
import types
def check_optional_has_inner(typ):
    if get_origin(typ) not in (Union, types.UnionType):
        return False
    return any(a is not type(None) for a in get_args(typ))
assert check_optional_has_inner(MyArgs.__annotations__["name"])

Type guard

def is_real_optional(typ) -> bool:
    import types, typing
    origin = get_origin(typ)
    if origin is not Union and origin is not types.UnionType:
        return False
    args = get_args(typ)
    return type(None) in args and len(args) > 1

Try / catch

try:
    inner = unwrap_optional(typ)
except ValueError:
    inner = None  # or fix the annotation upstream

Prevention

When it happens

Trigger: Calling unwrap_optional() (directly or via type analysis in the experimental component provider) with a type whose get_args() contains no element other than NoneType — effectively a degenerate Optional[None]/Union[None] annotation on a component input or property.

Common situations: Annotating a component argument as Optional[None] or Union[None] by mistake; dynamic/programmatically-built annotations where the real inner type was dropped; odd results from get_type_hints on forward refs that resolve to None; typos like Optional() without a parameter.

Related errors


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