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¶
- Schema Overview - Complete schema system guide
- Attributes - All attribute types
- Best Practices - Schema design guidelines
- Creating Resources - Resource implementation