Skip to content

Computed Attributes

Computed attributes are values that are calculated or generated by the provider rather than provided by the user. They represent outputs from resource operations.

What are Computed Attributes?

Computed attributes: - Are set by the provider (not by Terraform users) - Represent outputs (IDs, timestamps, derived values) - Cannot be configured by users in Terraform - May be unknown during planning and only determined during apply

Basic Example

Define computed attributes with computed=True:

from pyvider.schema import s_resource, a_str, a_num, PvsSchema

@classmethod
def get_schema(cls) -> PvsSchema:
    return s_resource({
        # User inputs (required or with defaults)
        "name": a_str(required=True, description="Server name"),

        # Provider outputs (computed=True)
        "id": a_str(computed=True, description="Unique server ID"),
        "ip_address": a_str(computed=True, description="Assigned IP address"),
        "created_at": a_str(computed=True, description="Creation timestamp"),
        "status": a_str(computed=True, description="Current status"),
    })

Setting Computed Values

Set computed attributes in your resource lifecycle methods:

from pyvider.resources import register_resource, BaseResource
from pyvider.resources.context import ResourceContext
from pyvider.schema import s_resource, a_str, a_num, PvsSchema
import attrs
from datetime import datetime
import uuid

@attrs.define
class ServerConfig:
    name: str
    instance_type: str = "t2.micro"

@attrs.define
class ServerState:
    id: str
    name: str
    instance_type: str
    ip_address: str
    created_at: str
    status: str

@register_resource("server")
class Server(BaseResource):
    config_class = ServerConfig
    state_class = ServerState

    @classmethod
    def get_schema(cls) -> PvsSchema:
        return s_resource({
            # Inputs
            "name": a_str(required=True, description="Server name"),
            "instance_type": a_str(default="t2.micro", description="Instance type"),

            # Computed outputs
            "id": a_str(computed=True, description="Server ID"),
            "ip_address": a_str(computed=True, description="IP address"),
            "created_at": a_str(computed=True, description="Creation time"),
            "status": a_str(computed=True, description="Server status"),
        })

    async def _create_apply(self, ctx: ResourceContext) -> tuple[ServerState | None, None]:
        """Create server and set computed values."""
        if not ctx.config:
            return None, None

        # Generate computed values
        server_id = str(uuid.uuid4())
        ip_address = self._allocate_ip()
        created_at = datetime.utcnow().isoformat()

        # Call API to create server
        await self.api.create_server(
            id=server_id,
            name=ctx.config.name,
            instance_type=ctx.config.instance_type,
        )

        # Return state with computed values
        return ServerState(
            id=server_id,
            name=ctx.config.name,
            instance_type=ctx.config.instance_type,
            ip_address=ip_address,
            created_at=created_at,
            status="running",
        ), None

    async def read(self, ctx: ResourceContext) -> ServerState | None:
        """Refresh computed values from API."""
        if not ctx.state:
            return None

        # Fetch current state from API
        server = await self.api.get_server(ctx.state.id)

        if not server:
            return None  # Server deleted

        # Update computed values from API
        return ServerState(
            id=ctx.state.id,
            name=server.name,
            instance_type=server.instance_type,
            ip_address=server.ip_address,
            created_at=ctx.state.created_at,
            status=server.status,  # May have changed
        )

Common Computed Attribute Patterns

IDs and Identifiers

Generate unique IDs for resources:

@classmethod
def get_schema(cls) -> PvsSchema:
    return s_resource({
        "name": a_str(required=True),

        # Computed IDs
        "id": a_str(computed=True, description="Unique resource ID"),
        "arn": a_str(computed=True, description="Amazon Resource Name"),
        "resource_id": a_str(computed=True, description="External system ID"),
    })

async def _create_apply(self, ctx: ResourceContext):
    # Generate or receive ID from API
    resource_id = await self.api.create(ctx.config.name)

    return State(
        id=resource_id,
        arn=f"arn:aws:service:region:account:{resource_id}",
        resource_id=resource_id,
        name=ctx.config.name,
    ), None

Timestamps

Track creation and modification times:

from datetime import datetime

@classmethod
def get_schema(cls) -> PvsSchema:
    return s_resource({
        "name": a_str(required=True),

        # Timestamp fields
        "created_at": a_str(computed=True, description="Creation timestamp"),
        "updated_at": a_str(computed=True, description="Last update timestamp"),
    })

async def _create_apply(self, ctx: ResourceContext):
    now = datetime.utcnow().isoformat()

    return State(
        id=generate_id(),
        name=ctx.config.name,
        created_at=now,
        updated_at=now,
    ), None

async def _update_apply(self, ctx: ResourceContext):
    now = datetime.utcnow().isoformat()

    return State(
        id=ctx.state.id,
        name=ctx.config.name,
        created_at=ctx.state.created_at,  # Preserve original
        updated_at=now,  # Update timestamp
    ), None

Network Information

Compute network-related values:

@classmethod
def get_schema(cls) -> PvsSchema:
    return s_resource({
        "name": a_str(required=True),

        # Network information
        "ip_address": a_str(computed=True, description="Assigned IP"),
        "public_dns": a_str(computed=True, description="Public DNS name"),
        "private_dns": a_str(computed=True, description="Private DNS name"),
        "mac_address": a_str(computed=True, description="MAC address"),
    })

async def _create_apply(self, ctx: ResourceContext):
    # Get network info from API
    network_info = await self.api.allocate_network(ctx.config.name)

    return State(
        id=network_info.id,
        name=ctx.config.name,
        ip_address=network_info.ip,
        public_dns=network_info.public_dns,
        private_dns=network_info.private_dns,
        mac_address=network_info.mac,
    ), None

Derived Values

Calculate values based on other attributes:

@classmethod
def get_schema(cls) -> PvsSchema:
    return s_resource({
        "size_gb": a_num(required=True, description="Size in GB"),

        # Derived computed values
        "size_bytes": a_num(computed=True, description="Size in bytes"),
        "size_mb": a_num(computed=True, description="Size in MB"),
    })

async def _create_apply(self, ctx: ResourceContext):
    size_gb = ctx.config.size_gb

    return State(
        id=generate_id(),
        size_gb=size_gb,
        size_bytes=size_gb * 1024 * 1024 * 1024,
        size_mb=size_gb * 1024,
    ), None

Status and State

Track resource status:

@classmethod
def get_schema(cls) -> PvsSchema:
    return s_resource({
        "name": a_str(required=True),

        # Status fields
        "status": a_str(computed=True, description="Resource status"),
        "ready": a_bool(computed=True, description="Whether resource is ready"),
        "health": a_str(computed=True, description="Health check status"),
    })

async def read(self, ctx: ResourceContext):
    # Check current status
    health_check = await self.api.health_check(ctx.state.id)

    return State(
        id=ctx.state.id,
        name=ctx.state.name,
        status=health_check.status,
        ready=health_check.status == "healthy",
        health=health_check.message,
    )

Optional + Computed

Attributes can be both optional (user can provide) and computed (provider can calculate):

@classmethod
def get_schema(cls) -> PvsSchema:
    return s_resource({
        "name": a_str(required=True),

        # Optional + Computed: User can provide, or provider will generate
        "tags": a_map(a_str(), optional=True, computed=True, description="Resource tags"),
    })

async def _create_apply(self, ctx: ResourceContext):
    # Use user-provided tags, or generate defaults
    tags = ctx.config.tags if ctx.config.tags else {
        "managed_by": "terraform",
        "created_at": datetime.utcnow().isoformat(),
    }

    return State(
        id=generate_id(),
        name=ctx.config.name,
        tags=tags,
    ), None

Using Computed Attributes in Terraform

Users reference computed attributes in their configurations:

resource "mycloud_server" "web" {
  name          = "web-server"
  instance_type = "t2.micro"
}

# Use computed outputs
output "server_id" {
  value = mycloud_server.web.id
}

output "server_ip" {
  value = mycloud_server.web.ip_address
}

# Use computed values in other resources
resource "mycloud_dns_record" "web" {
  name  = "web.example.com"
  value = mycloud_server.web.ip_address  # Computed from server
  type  = "A"
}

# Conditional logic based on computed values
resource "mycloud_alert" "server_health" {
  server_id = mycloud_server.web.id
  enabled   = mycloud_server.web.status == "running"
}

Best Practices

1. Always Mark Outputs as Computed

# ✅ Good: Output clearly marked as computed
"id": a_str(computed=True, description="Resource ID")

# ❌ Bad: Output not marked (will confuse users)
"id": a_str(description="Resource ID")

2. Preserve Immutable Computed Values

async def _update_apply(self, ctx: ResourceContext):
    return State(
        id=ctx.state.id,  # ✅ Preserve ID
        created_at=ctx.state.created_at,  # ✅ Preserve creation time
        updated_at=datetime.utcnow().isoformat(),  # ✅ Update timestamp
    )

3. Handle Unknown Values During Planning

async def _create(self, ctx: ResourceContext, base_plan: dict):
    """Planning phase: Values may be unknown."""

    # Mark fields that will be known after apply
    base_plan["id"] = None  # Will be generated
    base_plan["ip_address"] = None  # Will be allocated

    return base_plan, None

4. Document What is Computed

"status": a_str(
    computed=True,
    description="Current server status (healthy, degraded, or offline)"
)

5. Make Computed Values Stable

# ✅ Good: Stable, deterministic
"endpoint": a_str(computed=True)

async def _create_apply(self, ctx):
    # Endpoint is deterministic based on inputs
    endpoint = f"https://{ctx.config.name}.api.example.com"
    return State(endpoint=endpoint), None

# ❌ Bad: Changes on every apply
async def _create_apply(self, ctx):
    # Random value changes every time
    endpoint = f"https://{uuid.uuid4()}.api.example.com"
    return State(endpoint=endpoint), None

See Also