Skip to content

Index

pyvider.hcl

pyvider-hcl: HCL parsing with CTY type system integration.

This package provides HCL (HashiCorp Configuration Language) parsing capabilities with seamless integration into the pyvider ecosystem through the CTY type system.

Classes

HclError

HclError(
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any
)

Bases: FoundationError

Base class for errors related to HCL processing in Pyvider.

Source code in provide/foundation/errors/base.py
def __init__(
    self,
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any,
) -> None:
    self.message = message
    self.code = code or self._default_code()
    self.context = context or {}
    self.context.update(extra_context)
    self.cause = cause
    if cause:
        self.__cause__ = cause
    super().__init__(message)

HclFactoryError

Bases: ValueError

Custom exception for errors during HCL factory operations.

HclParsingError

HclParsingError(
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any
)

Bases: HclError

Raised when HCL parsing or schema validation fails.

This is an attrs-based exception class for structured error reporting.

Source code in provide/foundation/errors/base.py
def __init__(
    self,
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any,
) -> None:
    self.message = message
    self.code = code or self._default_code()
    self.context = context or {}
    self.context.update(extra_context)
    self.cause = cause
    if cause:
        self.__cause__ = cause
    super().__init__(message)
Functions
__str__
__str__() -> str

Provides a detailed error message including source location if available.

Source code in pyvider/hcl/exceptions.py
def __str__(self) -> str:
    """Provides a detailed error message including source location if available."""
    if self.source_file and self.line is not None and self.column is not None:
        return f"{self.message} (at {self.source_file}, line {self.line}, column {self.column})"
    elif self.source_file and self.line is not None:
        return f"{self.message} (at {self.source_file}, line {self.line})"
    elif self.source_file:
        return f"{self.message} (at {self.source_file})"
    return self.message

HclTypeParsingError

Bases: ValueError

Custom exception for errors during HCL type string parsing.

Functions

auto_infer_cty_type

auto_infer_cty_type(raw_data: Any) -> CtyValue[Any]

Automatically infer CTY type from raw Python data.

This function takes Python data structures (typically from HCL parsing) and automatically infers appropriate CTY types using pyvider-cty's canonical inference implementation.

Parameters:

Name Type Description Default
raw_data Any

Python data structure to infer types for

required

Returns:

Type Description
CtyValue[Any]

CTY value with inferred types

Example

data = {"name": "test", "count": 5} result = auto_infer_cty_type(data) isinstance(result.type, CtyObject) True

Note

This delegates to pyvider.cty.conversion.infer_cty_type_from_raw() which provides sophisticated type inference including: - List element type analysis (e.g., [1,2,3] → list(number)) - Object attribute inference - Type unification for mixed collections - Caching and cycle detection

Source code in pyvider/hcl/parser/inference.py
def auto_infer_cty_type(raw_data: Any) -> CtyValue[Any]:
    """Automatically infer CTY type from raw Python data.

    This function takes Python data structures (typically from HCL parsing)
    and automatically infers appropriate CTY types using pyvider-cty's
    canonical inference implementation.

    Args:
        raw_data: Python data structure to infer types for

    Returns:
        CTY value with inferred types

    Example:
        >>> data = {"name": "test", "count": 5}
        >>> result = auto_infer_cty_type(data)
        >>> isinstance(result.type, CtyObject)
        True

    Note:
        This delegates to pyvider.cty.conversion.infer_cty_type_from_raw()
        which provides sophisticated type inference including:
        - List element type analysis (e.g., [1,2,3] → list(number))
        - Object attribute inference
        - Type unification for mixed collections
        - Caching and cycle detection
    """
    # Use pyvider-cty's canonical inference implementation
    inferred_type = infer_cty_type_from_raw(raw_data)
    return inferred_type.validate(raw_data)  # type: ignore[no-any-return]

create_resource_cty

create_resource_cty(
    r_type: str,
    r_name: str,
    attributes_py: dict[str, Any],
    attributes_schema_py: dict[str, str] | None = None,
) -> CtyValue[Any]

Create a Terraform resource CTY structure.

Parameters:

Name Type Description Default
r_type str

Resource type (e.g., "aws_instance")

required
r_name str

Resource name

required
attributes_py dict[str, Any]

Resource attributes as Python dict

required
attributes_schema_py dict[str, str] | None

Optional type strings for attributes

None

Returns:

Type Description
CtyValue[Any]

CTY value representing Terraform resource structure

Raises:

Type Description
HclFactoryError

If validation fails

Example

resource = create_resource_cty( ... r_type="aws_instance", ... r_name="web", ... attributes_py={"ami": "ami-123", "instance_type": "t2.micro"}, ... attributes_schema_py={"ami": "string", "instance_type": "string"} ... )

Source code in pyvider/hcl/factories/resources.py
def create_resource_cty(  # noqa: C901
    r_type: str,
    r_name: str,
    attributes_py: dict[str, Any],
    attributes_schema_py: dict[str, str] | None = None,
) -> CtyValue[Any]:
    """Create a Terraform resource CTY structure.

    Args:
        r_type: Resource type (e.g., "aws_instance")
        r_name: Resource name
        attributes_py: Resource attributes as Python dict
        attributes_schema_py: Optional type strings for attributes

    Returns:
        CTY value representing Terraform resource structure

    Raises:
        HclFactoryError: If validation fails

    Example:
        >>> resource = create_resource_cty(
        ...     r_type="aws_instance",
        ...     r_name="web",
        ...     attributes_py={"ami": "ami-123", "instance_type": "t2.micro"},
        ...     attributes_schema_py={"ami": "string", "instance_type": "string"}
        ... )
    """
    logger.debug("🏭⏳ Creating resource", r_type=r_type, r_name=r_name)

    if not r_type or not r_type.strip():
        logger.error("🏭❌ Empty resource type")
        raise HclFactoryError("Resource type 'r_type' cannot be empty.")

    if not r_name or not r_name.strip():
        logger.error("🏭❌ Empty resource name")
        raise HclFactoryError("Resource name 'r_name' cannot be empty.")

    attributes_cty_schema: dict[str, CtyType[Any]] = {}

    if attributes_schema_py is not None:
        for attr_name, attr_type_str in attributes_schema_py.items():
            try:
                attributes_cty_schema[attr_name] = parse_hcl_type_string(attr_type_str)
            except HclTypeParsingError as e:
                logger.error(
                    "🏭❌ Attribute type parsing failed",
                    r_type=r_type,
                    r_name=r_name,
                    attr_name=attr_name,
                    type_str=attr_type_str,
                    error=str(e),
                )
                raise HclFactoryError(
                    f"Invalid type string for attribute '{attr_name}' ('{attr_type_str}') "
                    f"in resource '{r_type}.{r_name}': {e}"
                ) from e

        for attr_name in attributes_py:
            if attr_name not in attributes_cty_schema:
                logger.error(
                    "🏭❌ Missing type for attribute",
                    r_type=r_type,
                    r_name=r_name,
                    attr_name=attr_name,
                )
                raise HclFactoryError(
                    f"Missing type string in attributes_schema_py for attribute '{attr_name}' "
                    f"of resource '{r_type}.{r_name}'."
                )

        resource_attributes_obj_type = CtyObject(attributes_cty_schema)
        try:
            resource_attributes_obj_type.validate(attributes_py)
        except CtyValidationError as e:
            logger.error(
                "🏭❌ Attribute validation failed",
                r_type=r_type,
                r_name=r_name,
                error=str(e),
            )
            raise HclFactoryError(
                f"One or more attributes for resource '{r_type}.{r_name}' are not compatible "
                f"with the provided schema: {e}"
            ) from e
    else:
        logger.debug("🏭⏳ Inferring attribute types", r_type=r_type, r_name=r_name)
        inferred_attributes_cty = auto_infer_cty_type(attributes_py)
        if isinstance(inferred_attributes_cty.type, CtyObject):
            attributes_cty_schema = inferred_attributes_cty.type.attribute_types
        else:
            logger.error("🏭❌ Type inference failed", r_type=r_type, r_name=r_name)
            raise HclFactoryError("Could not infer object type from attributes.")

    root_py_struct = {"resource": [{r_type: [{r_name: attributes_py}]}]}
    root_schema = CtyObject(
        {
            "resource": CtyList(
                element_type=CtyObject(
                    {r_type: CtyList(element_type=CtyObject({r_name: CtyObject(attributes_cty_schema)}))}
                )
            )
        }
    )

    try:
        result = root_schema.validate(root_py_struct)
        return result  # type: ignore[no-any-return]
    except CtyError as e:
        logger.error("🏭❌ Resource creation failed", r_type=r_type, r_name=r_name, error=str(e))
        raise HclFactoryError(f"Internal error creating resource CtyValue: {e}") from e

create_variable_cty

create_variable_cty(
    name: str,
    type_str: str,
    default_py: Any | None = None,
    description: str | None = None,
    sensitive: bool | None = None,
    nullable: bool | None = None,
) -> CtyValue[Any]

Create a Terraform variable CTY structure.

Parameters:

Name Type Description Default
name str

Variable name (must be valid identifier)

required
type_str str

HCL type string (e.g., "string", "list(number)")

required
default_py Any | None

Optional default value

None
description str | None

Optional description

None
sensitive bool | None

Optional sensitive flag

None
nullable bool | None

Optional nullable flag

None

Returns:

Type Description
CtyValue[Any]

CTY value representing Terraform variable structure

Raises:

Type Description
HclFactoryError

If validation fails

Example

var = create_variable_cty( ... name="region", ... type_str="string", ... default_py="us-west-2" ... )

Source code in pyvider/hcl/factories/variables.py
def create_variable_cty(  # noqa: C901
    name: str,
    type_str: str,
    default_py: Any | None = None,
    description: str | None = None,
    sensitive: bool | None = None,
    nullable: bool | None = None,
) -> CtyValue[Any]:
    """Create a Terraform variable CTY structure.

    Args:
        name: Variable name (must be valid identifier)
        type_str: HCL type string (e.g., "string", "list(number)")
        default_py: Optional default value
        description: Optional description
        sensitive: Optional sensitive flag
        nullable: Optional nullable flag

    Returns:
        CTY value representing Terraform variable structure

    Raises:
        HclFactoryError: If validation fails

    Example:
        >>> var = create_variable_cty(
        ...     name="region",
        ...     type_str="string",
        ...     default_py="us-west-2"
        ... )
    """
    logger.debug("🏭⏳ Creating variable", name=name, type_str=type_str)

    if not name or not name.isidentifier():
        logger.error("🏭❌ Invalid variable name", name=name)
        raise HclFactoryError(f"Invalid variable name: '{name}'. Must be a valid identifier.")

    try:
        parsed_variable_type = parse_hcl_type_string(type_str)
    except HclTypeParsingError as e:
        logger.error("🏭❌ Type string parsing failed", name=name, type_str=type_str, error=str(e))
        raise HclFactoryError(f"Invalid type string for variable '{name}': {e}") from e

    variable_attrs_py: dict[str, Any] = {"type": type_str}

    if description is not None:
        variable_attrs_py["description"] = description
    if sensitive is not None:
        variable_attrs_py["sensitive"] = sensitive
    if nullable is not None:
        variable_attrs_py["nullable"] = nullable

    if default_py is not None:
        try:
            parsed_variable_type.validate(default_py)
        except CtyValidationError as e:
            logger.error(
                "🏭❌ Default value validation failed",
                name=name,
                type_str=type_str,
                error=str(e),
            )
            raise HclFactoryError(
                f"Default value for variable '{name}' is not compatible with type '{type_str}': {e}"
            ) from e
        variable_attrs_py["default"] = default_py

    variable_attrs_schema: dict[str, CtyType[Any]] = {"type": CtyString()}
    if "description" in variable_attrs_py:
        variable_attrs_schema["description"] = CtyString()
    if "sensitive" in variable_attrs_py:
        variable_attrs_schema["sensitive"] = CtyBool()
    if "nullable" in variable_attrs_py:
        variable_attrs_schema["nullable"] = CtyBool()
    if "default" in variable_attrs_py:
        variable_attrs_schema["default"] = parsed_variable_type

    root_py_struct = {"variable": [{name: variable_attrs_py}]}
    root_schema = CtyObject(
        {"variable": CtyList(element_type=CtyObject({name: CtyObject(variable_attrs_schema)}))}
    )

    try:
        result = root_schema.validate(root_py_struct)
        return result  # type: ignore[no-any-return]
    except CtyError as e:
        logger.error("🏭❌ Variable creation failed", name=name, error=str(e))
        raise HclFactoryError(f"Internal error creating variable CtyValue: {e}") from e

parse_hcl_to_cty

parse_hcl_to_cty(
    hcl_content: str, schema: CtyType[Any] | None = None
) -> CtyValue[Any]

Parse HCL directly into validated CtyValues using pyvider.cty types.

Parameters:

Name Type Description Default
hcl_content str

HCL string to parse

required
schema CtyType[Any] | None

Optional CTY type schema for validation

None

Returns:

Type Description
CtyValue[Any]

Parsed and validated CTY value

Raises:

Type Description
HclParsingError

If parsing or validation fails

Example

hcl = 'name = "example"' result = parse_hcl_to_cty(hcl) result.value["name"].value 'example'

Source code in pyvider/hcl/parser/base.py
def parse_hcl_to_cty(hcl_content: str, schema: CtyType[Any] | None = None) -> CtyValue[Any]:
    """Parse HCL directly into validated CtyValues using pyvider.cty types.

    Args:
        hcl_content: HCL string to parse
        schema: Optional CTY type schema for validation

    Returns:
        Parsed and validated CTY value

    Raises:
        HclParsingError: If parsing or validation fails

    Example:
        >>> hcl = 'name = "example"'
        >>> result = parse_hcl_to_cty(hcl)
        >>> result.value["name"].value
        'example'
    """

    try:
        raw_data = hcl2.loads(hcl_content)  # type: ignore[attr-defined]
    except Exception as e:
        raise HclParsingError(message=f"Failed to parse HCL: {e}") from e

    if schema:
        try:
            validated_value = schema.validate(raw_data)
            return validated_value
        except (CtySchemaError, CtyValidationError) as e:
            raise HclParsingError(message=f"Schema validation failed after HCL parsing: {e}") from e
    else:
        inferred_value = auto_infer_cty_type(raw_data)
        return inferred_value

parse_terraform_config

parse_terraform_config(
    config_path: Path,
) -> TerraformConfig

Parse Terraform configuration file.

This is a placeholder function for future Terraform-specific configuration parsing. Currently returns a placeholder dict.

Parameters:

Name Type Description Default
config_path Path

Path to Terraform configuration file

required

Returns:

Type Description
TerraformConfig

Placeholder dict with status information

Note

This function is not fully implemented yet. Future versions will provide: - Provider block parsing - Resource block parsing with validation - Variable, output, locals blocks - Module block parsing - Data source block parsing

Example

from pathlib import Path result = parse_terraform_config(Path("main.tf")) result["status"] 'not_implemented'

Source code in pyvider/hcl/terraform/config.py
def parse_terraform_config(config_path: Path) -> TerraformConfig:
    """Parse Terraform configuration file.

    This is a placeholder function for future Terraform-specific configuration parsing.
    Currently returns a placeholder dict.

    Args:
        config_path: Path to Terraform configuration file

    Returns:
        Placeholder dict with status information

    Note:
        This function is not fully implemented yet. Future versions will provide:
        - Provider block parsing
        - Resource block parsing with validation
        - Variable, output, locals blocks
        - Module block parsing
        - Data source block parsing

    Example:
        >>> from pathlib import Path
        >>> result = parse_terraform_config(Path("main.tf"))
        >>> result["status"]
        'not_implemented'
    """
    logger.warning(
        "Terraform config parsing not implemented",
        config_path=str(config_path),
    )

    # In a real implementation:
    # 1. Read the file content from config_path
    # 2. Use hcl2.loads() or specialized HCL parser
    # 3. Identify and process Terraform-specific blocks:
    #    - provider blocks (e.g., "provider "aws" { ... }")
    #    - resource blocks (e.g., "resource "aws_instance" "my_instance" { ... }")
    #    - variable blocks (e.g., "variable "image_id" { ... }")
    #    - output, locals, data blocks, etc.
    # 4. Validate structure and content
    # 5. Convert to structured TerraformConfig object
    # 6. Handle errors with context

    return {"status": "not_implemented", "path": str(config_path)}

parse_with_context

parse_with_context(
    content: str, source_file: Path | None = None
) -> Any

Parse HCL content with enhanced error context.

This function parses HCL content and provides rich error context if parsing fails. It returns the raw parsed data (dict/list), not CTY values.

Parameters:

Name Type Description Default
content str

HCL content string to parse

required
source_file Path | None

Optional source file path for error reporting

None

Returns:

Type Description
Any

Raw parsed data (typically dict or list)

Raises:

Type Description
HclParsingError

If parsing fails, with source location information

Example

content = 'name = "example"' data = parse_with_context(content) data['name'] 'example'

Source code in pyvider/hcl/parser/context.py
def parse_with_context(content: str, source_file: Path | None = None) -> Any:
    """Parse HCL content with enhanced error context.

    This function parses HCL content and provides rich error context if parsing fails.
    It returns the raw parsed data (dict/list), not CTY values.

    Args:
        content: HCL content string to parse
        source_file: Optional source file path for error reporting

    Returns:
        Raw parsed data (typically dict or list)

    Raises:
        HclParsingError: If parsing fails, with source location information

    Example:
        >>> content = 'name = "example"'
        >>> data = parse_with_context(content)
        >>> data['name']
        'example'
    """
    source_str = str(source_file) if source_file else "string input"

    try:
        return hcl2.loads(content)  # type: ignore[attr-defined]
    except Exception as e:
        logger.error(
            "HCL parsing failed",
            source=source_str,
            error=str(e),
            exc_info=True,
        )
        raise HclParsingError(
            message=str(e),
            source_file=str(source_file) if source_file else None,
        ) from e

pretty_print_cty

pretty_print_cty(value: CtyValue[Any]) -> None

Pretty print a CTY value to stdout.

Parameters:

Name Type Description Default
value CtyValue[Any]

CTY value to print

required
Example

from pyvider.cty import CtyString val = CtyString().validate("test") pretty_print_cty(val) "test"

Source code in pyvider/hcl/output/formatting.py
def pretty_print_cty(value: CtyValue[Any]) -> None:
    """Pretty print a CTY value to stdout.

    Args:
        value: CTY value to print

    Example:
        >>> from pyvider.cty import CtyString
        >>> val = CtyString().validate("test")
        >>> pretty_print_cty(val)
        "test"
    """
    print(_pretty_print_cty_recursive(value, 0))