Attributes¶
Attributes define the individual fields in your provider's schemas. Pyvider uses factory functions to create attributes with proper typing and validation.
Overview¶
Attributes represent simple and complex values in Terraform configurations: - Simple types: strings, numbers, booleans - Collection types: lists, maps, sets - Complex types: objects, tuples - Special types: dynamic, unknown, null
Factory Functions¶
Pyvider provides a_* factory functions to create attributes:
from pyvider.schema import (
a_str, a_num, a_bool, # Simple types
a_list, a_map, a_set, a_tuple, # Collections
a_obj, # Complex objects
a_dyn, # Dynamic type
)
Simple Types¶
String Attributes¶
Use a_str() for text values:
from pyvider.schema import a_str, s_resource
@classmethod
def get_schema(cls):
return s_resource({
"name": a_str(
required=True,
description="Resource name"
),
"region": a_str(
default="us-east-1",
description="AWS region"
),
"password": a_str(
required=True,
sensitive=True, # Masked in logs
description="Admin password"
),
})
Number Attributes¶
Use a_num() for numeric values (integers or floats):
port = a_num(
default=8080,
description="Port number",
validators=[
lambda x: 1 <= x <= 65535 or "Port must be between 1 and 65535"
]
)
timeout = a_num(
default=30.5,
description="Timeout in seconds"
)
Boolean Attributes¶
Use a_bool() for true/false values:
enabled = a_bool(
default=True,
description="Whether the feature is enabled"
)
debug_mode = a_bool(
default=False,
description="Enable debug logging"
)
Collection Types¶
Lists¶
Use a_list() for ordered collections:
# List of strings
tags = a_list(
a_str(),
default=[],
description="Resource tags"
)
# List of numbers
ports = a_list(
a_num(),
description="Allowed ports"
)
# List of objects
rules = a_list(
a_obj({
"port": a_num(required=True),
"protocol": a_str(required=True),
}),
description="Firewall rules"
)
Maps¶
Use a_map() for key-value pairs:
# Map of strings
labels = a_map(
a_str(),
default={},
description="Label key-value pairs"
)
# Map of numbers
quotas = a_map(
a_num(),
description="Resource quotas by type"
)
Sets¶
Use a_set() for unordered unique collections:
Tuples¶
Use a_tuple() for fixed-length ordered collections with different types:
# Tuple with specific types for each element
coordinates = a_tuple(
[a_num(), a_num()], # [latitude, longitude]
description="Geographic coordinates"
)
# Mixed types
metadata = a_tuple(
[a_str(), a_num(), a_bool()], # [name, count, active]
description="Resource metadata tuple"
)
Complex Types¶
Objects¶
Use a_obj() for nested structures:
config = a_obj({
"timeout": a_num(default=30),
"retries": a_num(default=3),
"endpoint": a_str(required=True),
"tls_enabled": a_bool(default=True),
}, description="Connection configuration")
# Nested objects
server_config = a_obj({
"host": a_str(required=True),
"port": a_num(default=443),
"auth": a_obj({
"username": a_str(required=True),
"password": a_str(required=True, sensitive=True),
}, description="Authentication credentials"),
}, description="Server configuration")
Attribute Properties¶
Required vs Optional¶
# Required attribute (must be provided)
name = a_str(
required=True,
description="Required resource name"
)
# Optional attribute with default
region = a_str(
default="us-east-1",
description="Optional AWS region"
)
# Optional attribute without default (can be null)
description = a_str(
description="Optional description"
)
Computed Attributes¶
Computed attributes are set by the provider, not by users:
@classmethod
def get_schema(cls):
return s_resource({
# User provides this
"name": a_str(required=True, description="Server name"),
# Provider computes these
"id": a_str(computed=True, description="Unique identifier"),
"ip_address": a_str(computed=True, description="Assigned IP"),
"created_at": a_str(computed=True, description="Creation timestamp"),
})
Sensitive Attributes¶
Sensitive attributes are masked in logs and outputs:
api_key = a_str(
required=True,
sensitive=True, # Value will be masked
description="API key for authentication"
)
credentials = a_obj({
"username": a_str(required=True),
"password": a_str(required=True, sensitive=True),
}, description="Login credentials")
Default Values¶
# Simple defaults
port = a_num(default=8080)
enabled = a_bool(default=True)
region = a_str(default="us-east-1")
# Collection defaults
tags = a_list(a_str(), default=[])
labels = a_map(a_str(), default={})
# Object defaults
config = a_obj({
"timeout": a_num(default=30),
"retries": a_num(default=3),
}, description="Configuration with defaults")
Validators¶
Add validation logic to attributes:
from pyvider.schema import a_str, a_num
# Single validator
port = a_num(
validators=[
lambda x: 1 <= x <= 65535 or "Port must be between 1 and 65535"
]
)
# Multiple validators
username = a_str(
required=True,
validators=[
lambda x: len(x) >= 3 or "Username must be at least 3 characters",
lambda x: x.isalnum() or "Username must be alphanumeric",
lambda x: not x.startswith("_") or "Username cannot start with underscore",
]
)
# Validators for collections
tags = a_list(
a_str(),
validators=[
lambda x: len(x) <= 10 or "Maximum 10 tags allowed",
lambda x: all(len(tag) <= 50 for tag in x) or "Tag length must be <= 50 chars",
]
)
Special Types¶
Dynamic Type¶
Use a_dyn() when the type is not known until runtime:
Unknown and Null Values¶
For testing and advanced scenarios:
from pyvider.schema import a_unknown, a_null, a_str
# Create unknown values
unknown_string = a_unknown(a_str())
# Create null values
null_number = a_null(a_num())
Complete Example¶
Here's a comprehensive resource schema using various attribute types:
from pyvider.schema import (
s_resource,
a_str, a_num, a_bool,
a_list, a_map, a_obj,
)
@classmethod
def get_schema(cls):
return s_resource({
# Required simple types
"name": a_str(
required=True,
description="Server name"
),
"instance_type": a_str(
required=True,
description="Instance type (e.g., t2.micro)"
),
# Optional with defaults
"port": a_num(
default=8080,
description="Port number",
validators=[lambda x: 1 <= x <= 65535 or "Invalid port"]
),
"enabled": a_bool(
default=True,
description="Whether server is enabled"
),
# Sensitive
"admin_password": a_str(
required=True,
sensitive=True,
description="Administrator password"
),
# Computed
"id": a_str(
computed=True,
description="Unique identifier"
),
"ip_address": a_str(
computed=True,
description="Assigned IP address"
),
# Collections
"tags": a_list(
a_str(),
default=[],
description="Resource tags"
),
"labels": a_map(
a_str(),
default={},
description="Key-value labels"
),
# Complex objects
"config": a_obj({
"timeout": a_num(default=30),
"retries": a_num(default=3),
"endpoints": a_list(a_str(), default=[]),
}, description="Server configuration"),
# Nested complex structure
"monitoring": a_obj({
"enabled": a_bool(default=True),
"interval": a_num(default=60),
"alerts": a_list(
a_obj({
"type": a_str(required=True),
"threshold": a_num(required=True),
"email": a_str(required=True),
}),
default=[]
),
}, description="Monitoring configuration"),
})
Corresponding Terraform Configuration¶
The schema above would be used in Terraform like this:
resource "mycloud_server" "web" {
name = "web-server"
instance_type = "t2.micro"
port = 8080
enabled = true
admin_password = "secure-password" # Sensitive
tags = ["production", "web"]
labels = {
environment = "prod"
team = "platform"
}
config {
timeout = 60
retries = 5
endpoints = ["https://api.example.com"]
}
monitoring {
enabled = true
interval = 120
alerts {
type = "cpu"
threshold = 80
email = "[email protected]"
}
alerts {
type = "memory"
threshold = 90
email = "[email protected]"
}
}
}
See Also¶
- Schema Overview - Complete schema system guide
- Blocks - Nested block structures
- Types - Detailed type reference
- Validators - Validation patterns
- Best Practices - Schema design recommendations