Skip to content

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:

# Set of strings
allowed_ips = a_set(
    a_str(),
    description="Allowed IP addresses"
)

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:

metadata = a_dyn(
    description="Arbitrary metadata (type determined at 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