Skip to content

Common Schema Patterns

This guide presents common schema design patterns used across successful Pyvider providers. Learn from real-world examples to build better schemas.

๐Ÿค– 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.

Pattern 1: Required + Computed ID

Every resource needs an ID that's computed after creation:

@classmethod
def get_schema(cls) -> PvsSchema:
    return s_resource({
        "name": a_str(required=True, description="Resource name"),
        "id": a_str(computed=True, description="Unique identifier"),
    })

Usage:

resource "mycloud_server" "web" {
  name = "web-server"
  # id is computed, don't set it
}

output "server_id" {
  value = mycloud_server.web.id
}

Pattern 2: Optional with Sensible Defaults

Provide defaults for common configurations:

"port": a_num(default=8080, description="Port number")
"enabled": a_bool(default=True, description="Whether enabled")
"timeout": a_num(default=30, description="Timeout in seconds")
"retries": a_num(default=3, description="Retry attempts")

Benefit: Users only specify what differs from defaults.

Pattern 3: Tags and Labels

Standard pattern for resource tagging:

"tags": a_list(a_str(), default=[], description="Resource tags")
"labels": a_map(a_str(), default={}, description="Key-value labels")

Usage:

resource "mycloud_server" "web" {
  name = "web-server"
  tags = ["production", "web", "critical"]
  labels = {
    environment = "prod"
    team        = "platform"
    cost_center = "engineering"
  }
}

Pattern 4: Connection Configuration Object

Group related connection settings:

"connection": a_obj({
    "host": a_str(required=True),
    "port": a_num(default=5432),
    "ssl": a_bool(default=True),
    "timeout": a_num(default=30),
}, description="Connection configuration")

Usage:

connection {
  host    = "db.example.com"
  port    = 5432
  ssl     = true
  timeout = 60
}

Pattern 5: Repeated Configuration Blocks

Use b_list for repeatable configuration:

@classmethod
def get_schema(cls) -> PvsSchema:
    return s_resource({
        "name": a_str(required=True),
        "rule": b_list("rule"),  # 0 or more
    })

Rule block definition:

"rule": b_list("rule", attributes={
    "protocol": a_str(required=True),
    "port": a_num(required=True),
    "source": a_str(default="0.0.0.0/0"),
})

Usage:

resource "mycloud_firewall" "web" {
  name = "web-firewall"

  rule {
    protocol = "tcp"
    port     = 443
    source   = "0.0.0.0/0"
  }

  rule {
    protocol = "tcp"
    port     = 80
    source   = "0.0.0.0/0"
  }
}

Pattern 6: Optional Single Configuration Block

Use b_single for optional advanced config:

"advanced": b_single("advanced", attributes={
    "cache_ttl": a_num(default=300),
    "compression": a_bool(default=True),
    "worker_count": a_num(default=4),
})

Usage:

resource "mycloud_api" "main" {
  name = "api-gateway"

  # Optional advanced configuration
  advanced {
    cache_ttl    = 600
    compression  = true
    worker_count = 8
  }
}

Pattern 7: Computed Status Fields

Report current status as computed attributes:

"status": a_str(computed=True, description="Current status")
"health": a_str(computed=True, description="Health check status")
"last_updated": a_str(computed=True, description="Last update timestamp")
"version": a_str(computed=True, description="Current version")

Pattern 8: Secret Management

Separate public IDs from sensitive secrets:

"username": a_str(required=True)  # Public
"password": a_str(required=True, sensitive=True)  # Secret
"connection_string": a_str(computed=True, sensitive=True)  # Generated secret

Pattern 9: Size/Tier Selection

Enum-like validation for tiers:

"size": a_str(
    default="medium",
    validators=[
        lambda x: x in ["small", "medium", "large", "xlarge"]
        or "Must be small, medium, large, or xlarge"
    ],
    description="Instance size"
)

Pattern 10: Timeouts Configuration

Standard timeout block pattern:

"timeouts": a_obj({
    "create": a_str(default="30m"),
    "update": a_str(default="30m"),
    "delete": a_str(default="10m"),
}, description="Operation timeouts")

Pattern 11: Conditional Requirements

Use resource-level validation for conditional requirements:

async def _validate_config(self, config: Config) -> list[str]:
    errors = []

    if config.ssl_enabled and not config.ssl_cert_path:
        errors.append("ssl_cert_path required when ssl_enabled is true")

    if config.backup_enabled and config.backup_retention < 1:
        errors.append("backup_retention must be >= 1 when backup_enabled")

    return errors

Pattern 12: Resource References

Link to other resources:

"vpc_id": a_str(required=True, description="VPC identifier")
"subnet_ids": a_list(a_str(), required=True, description="Subnet identifiers")
"security_group_ids": a_set(a_str(), description="Security group IDs")

Usage:

resource "mycloud_instance" "web" {
  vpc_id             = mycloud_vpc.main.id
  subnet_ids         = [mycloud_subnet.public.id]
  security_group_ids = [mycloud_sg.web.id, mycloud_sg.common.id]
}

Pattern 13: Min/Max Constraints

Scaling and sizing constraints:

"min_replicas": a_num(
    default=1,
    validators=[lambda x: x >= 1 or "Min 1 replica"]
)
"max_replicas": a_num(
    default=10,
    validators=[lambda x: x >= 1 or "Min 1 replica"]
)

# Then validate min <= max
async def _validate_config(self, config: Config) -> list[str]:
    if config.min_replicas > config.max_replicas:
        return ["min_replicas cannot exceed max_replicas"]
    return []

Pattern 14: Feature Flags

Boolean flags for optional features:

"monitoring_enabled": a_bool(default=True)
"backup_enabled": a_bool(default=False)
"auto_scaling_enabled": a_bool(default=False)
"encryption_enabled": a_bool(default=True)

Pattern 15: Lifecycle Customization

Allow users to control lifecycle behavior:

"lifecycle": a_obj({
    "prevent_destroy": a_bool(default=False),
    "create_before_destroy": a_bool(default=False),
    "ignore_changes": a_list(a_str(), default=[]),
}, description="Lifecycle configuration")

Anti-Patterns to Avoid

โŒ Anti-Pattern 1: Everything Required

# Bad: Forces users to specify everything
"timeout": a_num(required=True)
"retries": a_num(required=True)
"buffer_size": a_num(required=True)

# Good: Sensible defaults
"timeout": a_num(default=30)
"retries": a_num(default=3)
"buffer_size": a_num(default=4096)

โŒ Anti-Pattern 2: Too Many Top-Level Attributes

# Bad: Flat namespace
"db_host": a_str()
"db_port": a_num()
"db_user": a_str()
"db_password": a_str()
"db_ssl": a_bool()

# Good: Grouped in object
"database": a_obj({
    "host": a_str(required=True),
    "port": a_num(default=5432),
    "username": a_str(required=True),
    "password": a_str(required=True, sensitive=True),
    "ssl": a_bool(default=True),
})

โŒ Anti-Pattern 3: Unclear Computed Attributes

# Bad: User might try to set these
"id": a_str()  # Should be computed
"status": a_str()  # Should be computed

# Good: Clearly marked as computed
"id": a_str(computed=True)
"status": a_str(computed=True)

โŒ Anti-Pattern 4: No Validation

# Bad: No validation
"port": a_num()
"email": a_str()

# Good: With validation
"port": a_num(validators=[lambda x: 1 <= x <= 65535])
"email": a_str(validators=[lambda x: "@" in x])

Real-World Example

Complete resource combining multiple patterns:

@register_resource("mycloud_api_gateway")
class ApiGatewayResource(BaseResource):

    @classmethod
    def get_schema(cls) -> PvsSchema:
        return s_resource({
            # Pattern 1: Required + Computed ID
            "name": a_str(required=True),
            "id": a_str(computed=True),

            # Pattern 2: Defaults
            "port": a_num(default=443),
            "enabled": a_bool(default=True),

            # Pattern 3: Tags/Labels
            "tags": a_list(a_str(), default=[]),
            "labels": a_map(a_str(), default={}),

            # Pattern 6: Secret Management
            "api_key": a_str(required=True, sensitive=True),

            # Pattern 7: Status
            "status": a_str(computed=True),
            "health": a_str(computed=True),

            # Pattern 5: Repeated Blocks
            "route": b_list("route"),

            # Pattern 6: Optional Single Block
            "advanced": b_single("advanced"),
        })

Remember: Good schema patterns make your provider intuitive and easy to use. Follow these patterns to create consistent, user-friendly resources.