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.
๐ค AI-Generated Content
This documentation was generated with AI assistance and is still being audited. Some, or potentially a lot, of this information may be inaccurate. Learn more.
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