Skip to content

explicit

pyvider.cty.conversion.explicit

TODO: Add module docstring.

Classes

Functions

convert

convert(
    value: CtyValue[Any], target_type: CtyType[Any]
) -> CtyValue[Any]

Converts a CtyValue to a new CtyValue of the target CtyType.

Source code in pyvider/cty/conversion/explicit.py
def convert(value: CtyValue[Any], target_type: CtyType[Any]) -> CtyValue[Any]:  # noqa: C901
    """
    Converts a CtyValue to a new CtyValue of the target CtyType.
    """
    with error_boundary(
        context={
            "operation": "cty_value_conversion",
            "source_type": str(value.type),
            "target_type": str(target_type),
            "value_is_null": value.is_null,
            "value_is_unknown": value.is_unknown,
        }
    ):
        # Early exit cases
        if value.type.equal(target_type):
            return value

        if value.is_null:
            return CtyValue.null(target_type)
        if value.is_unknown:
            return CtyValue.unknown(target_type)

        # Capsule conversion with operations
        if isinstance(value.type, CtyCapsuleWithOps) and value.type.convert_fn:
            result = value.type.convert_fn(value.value, target_type)
            if result is None:
                error_message = ERR_CAPSULE_CANNOT_CONVERT.format(
                    value_type=value.type, target_type=target_type
                )
                raise CtyConversionError(
                    error_message,
                    source_value=value,
                    target_type=target_type,
                )
            if not isinstance(result, CtyValue):
                error_message = ERR_CUSTOM_CONVERTER_NON_CTYVALUE
                raise CtyConversionError(
                    error_message,
                    source_value=value,
                    target_type=target_type,
                )
            if not result.type.equal(target_type):
                error_message = ERR_CUSTOM_CONVERTER_WRONG_TYPE.format(
                    result_type=result.type, target_type=target_type
                )
                raise CtyConversionError(
                    error_message,
                    source_value=value,
                    target_type=target_type,
                )
            return result.with_marks(set(value.marks))

        # Dynamic type handling
        if isinstance(value.type, CtyDynamic):
            if not isinstance(value.value, CtyValue):
                error_message = ERR_DYNAMIC_VALUE_NOT_CTYVALUE
                raise CtyConversionError(error_message, source_value=value)
            return convert(value.value, target_type)

        if isinstance(target_type, CtyDynamic):
            return value.with_marks(set(value.marks))

        # String conversion
        if isinstance(target_type, CtyString) and not isinstance(value.type, CtyCapsule):
            raw = value.value
            new_val = ("true" if raw else "false") if isinstance(raw, bool) else str(raw)
            return CtyValue(target_type, new_val).with_marks(set(value.marks))

        # Number conversion
        if isinstance(target_type, CtyNumber):
            try:
                validated = target_type.validate(value.value)
                return validated.with_marks(set(value.marks))
            except CtyValidationError as e:
                error_message = ERR_CANNOT_CONVERT_VALIDATION.format(
                    value_type=value.type, target_type=target_type, message=e.message
                )
                raise CtyConversionError(
                    error_message,
                    source_value=value,
                    target_type=target_type,
                ) from e

        # Boolean conversion
        if isinstance(target_type, CtyBool):
            if isinstance(value.type, CtyString):
                s = str(value.value).lower()
                if s == "true":
                    return CtyValue(target_type, True).with_marks(set(value.marks))
                if s == "false":
                    return CtyValue(target_type, False).with_marks(set(value.marks))
            error_message = ERR_CANNOT_CONVERT_TO_BOOL.format(value_type=value.type)
            raise CtyConversionError(
                error_message,
                source_value=value,
                target_type=target_type,
            )

        # Collection conversions
        if isinstance(target_type, CtySet) and isinstance(value.type, CtyList | CtyTuple):
            converted: CtyValue[Any] = target_type.validate(value.value).with_marks(set(value.marks))
            return converted

        if isinstance(target_type, CtyList) and isinstance(value.type, CtySet | CtyTuple):
            converted = target_type.validate(value.value).with_marks(set(value.marks))
            return converted

        if isinstance(target_type, CtyList) and isinstance(value.type, CtyList):
            if target_type.element_type.equal(value.type.element_type):
                return value
            if isinstance(target_type.element_type, CtyDynamic):
                converted = target_type.validate(value.value).with_marks(set(value.marks))
                return converted

        # Object conversion
        if isinstance(target_type, CtyObject) and isinstance(value.type, CtyObject):
            new_attrs = {}
            source_attrs = value.value
            if not isinstance(source_attrs, dict):
                error_message = ERR_SOURCE_OBJECT_NOT_DICT
                raise CtyConversionError(error_message)
            source_attrs_dict = cast(dict[str, CtyValue[Any]], source_attrs)
            for name, target_attr_type in target_type.attribute_types.items():
                if name in source_attrs_dict:
                    new_attrs[name] = convert(source_attrs_dict[name], target_attr_type)
                elif name in target_type.optional_attributes:
                    new_attrs[name] = CtyValue.null(target_attr_type)
                else:
                    error_message = ERR_MISSING_REQUIRED_ATTRIBUTE.format(name=name)
                    raise CtyConversionError(error_message)
            converted = target_type.validate(new_attrs).with_marks(set(value.marks))
            return converted

        # Fallback - no conversion available
        error_message = ERR_CANNOT_CONVERT_GENERAL.format(value_type=value.type, target_type=target_type)
        raise CtyConversionError(
            error_message,
            source_value=value,
            target_type=target_type,
        )

unify

unify(types: Iterable[CtyType[Any]]) -> CtyType[Any]

Finds a single common CtyType that all of the given types can convert to. This is a wrapper that enables caching by converting input to a frozenset.

Source code in pyvider/cty/conversion/explicit.py
def unify(types: Iterable[CtyType[Any]]) -> CtyType[Any]:
    """
    Finds a single common CtyType that all of the given types can convert to.
    This is a wrapper that enables caching by converting input to a frozenset.
    """
    return _unify_frozen(frozenset(types))