Design patterns for resource configuration schemas and output values
Config and Outputs are Pydantic models that define your resource’s interface. Config specifies what users configure, while Outputs defines what your resource exposes to dependent resources.
When the runtime processes a resource, it resolves all FieldReference values before calling your lifecycle methods. By the time on_create runs, self.config.database_url contains the actual string value, not the reference.
You don’t need to handle FieldReference resolution in your code. The runtime resolves all references to their actual values before invoking lifecycle methods.
Use Field[T] for config values that commonly come from other resources:
class AppConfig(Config): name: str # These might reference outputs from other resources database_url: Field[str] api_key: Field[str] # These are always user-provided port: int = 8080 log_level: str = "INFO"
In your lifecycle methods, call resolve() to get the typed resource instance:
async def on_create(self) -> AppOutputs: # Get the full database resource db = await self.config.database.resolve() # Access any field from config or outputs print(f"Connecting to {db.config.name}") print(f"URL: {db.outputs.connection_url}") return AppOutputs(db_url=db.outputs.connection_url)
The runtime resolves dependencies before calling your lifecycle handler. The resolve() method returns the pre-resolved instance. If the dependent resource is not yet READY, it will raise a RuntimeError.
class AppConfig(Config): # FieldReference: just need the connection URL string database_url: Field[str] # Dependency: need multiple values or the full resource database: Dependency[DatabaseResource]
from pydantic import field_validatorfrom pragma_sdk import Configclass BucketConfig(Config): name: str region: str @field_validator("name") @classmethod def validate_bucket_name(cls, v: str) -> str: if not v.islower(): raise ValueError("Bucket name must be lowercase") if not v.replace("-", "").isalnum(): raise ValueError("Bucket name can only contain letters, numbers, and hyphens") return v @field_validator("region") @classmethod def validate_region(cls, v: str) -> str: valid_regions = {"us-east-1", "us-west-2", "eu-west-1"} if v not in valid_regions: raise ValueError(f"Region must be one of: {valid_regions}") return v
class DatabaseConfig(Config): name: str size_gb: int enabled: bool# All of these work:config = DatabaseConfig(name="db", size_gb="100", enabled="true")config = DatabaseConfig(name="db", size_gb=100, enabled=1)
For strict typing without coercion, use strict mode on fields:
from pydantic import Field as PydanticFieldclass StrictConfig(Config): count: int = PydanticField(strict=True) # Rejects string "10"
Provide enough information for dependents to work without additional API calls:
# Minimal: Dependents need to make additional callsclass SecretOutputs(Outputs): secret_id: str# Better: Dependents have what they needclass SecretOutputs(Outputs): resource_name: str # Full GCP resource path version_name: str # Full version path version_id: str # Just the version number