This page documents Pyvider's exception hierarchy and error handling patterns for provider development.
🤖 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.
graph TB
E[Exception]
E --> FND[FoundationError]
FND --> PE[PyviderError]
PE --> CE[ConversionError]
PE --> FCE[FrameworkConfigurationError]
PE --> PLE[PluginError]
PE --> PVE[PyviderValueError]
PE --> PRE[ProviderError]
PE --> RE[ResourceError]
PE --> FE[FunctionError]
PE --> SE[SchemaError]
PE --> VE[ValidationError]
PE --> REG[ComponentRegistryError]
PE --> SER[SerializationError]
PE --> GE[GRPCError]
style PE fill:#f9f,stroke:#333,stroke-width:2px
style FND fill:#bbf,stroke:#333,stroke-width:2px
All Pyvider exceptions inherit from FoundationError (from the provide.foundation library), which provides rich error context, automatic telemetry integration, and Terraform diagnostic generation support. You don't need to import or use FoundationError directly - use the Pyvider-specific exception classes below.
frompyvider.exceptionsimportPyviderErrorclassPyviderError(FoundationError):""" Base exception for all Pyvider errors. Attributes: message: Human-readable error message details: Additional error context (dict) cause: Original exception that caused this error """def__init__(self,message:str,details:dict|None=None,cause:Exception|None=None):self.message=messageself.details=detailsor{}self.cause=causesuper().__init__(message)
frompyvider.exceptionsimportProviderError# Provider not configuredraiseProviderError("Provider not configured with credentials")# Invalid configurationraiseProviderError("Invalid provider configuration",details={"field":"region","value":"invalid-region"})# Authentication failureraiseProviderError("Authentication failed",details={"endpoint":"https://api.example.com"},cause=original_exception)
frompyvider.exceptionsimportResourceError# Resource not foundraiseResourceError("Resource not found",details={"resource_id":"i-12345","type":"instance"})# Creation failedraiseResourceError("Failed to create resource",details={"reason":"Quota exceeded","limit":10,"current":10})# Update conflictraiseResourceError("Resource was modified outside of Terraform",details={"expected_version":"1.2","actual_version":"1.3"})# Delete protectionraiseResourceError("Cannot delete resource with deletion protection enabled",details={"resource_id":"db-prod-001","protection":True})
frompyvider.exceptionsimportGrpcErrorimportgrpc# Connection errorraiseGrpcError("Failed to connect to Terraform",details={"address":"localhost:50051"},cause=grpc_exception)# Protocol errorraiseGrpcError("Protocol version mismatch",details={"expected":"6","got":"5"})
# Good - Rich contextraiseResourceError("Failed to create S3 bucket",details={"bucket_name":config.bucket_name,"region":config.region,"error_code":"BucketAlreadyExists","suggestion":"Choose a different bucket name"},cause=boto_exception)# Bad - Minimal contextraiseResourceError("Bucket creation failed")
try:response=awaitapi_client.create_resource(...)exceptHttpErrorase:# Preserve original exceptionraiseResourceError(f"API call failed: {e.status_code}",details={"endpoint":e.url,"method":"POST"},cause=e# Original exception preserved)
raiseValidationError("Invalid instance type for selected region",details={"instance_type":"t3.micro","region":"us-gov-west-1","valid_types":["t2.micro","t2.small"],"suggestion":"Use t2.micro for government regions"})
asyncdef_create_apply(self,ctx:ResourceContext)->tuple[State|None,None]:"""Create resource with structured error handling."""try:# Validate inputs firstself._validate_config(ctx.config)# Attempt creationresult=awaitself._create_resource(ctx.config)# Validate resultifnotresult.id:raiseResourceError("Resource created but no ID returned",details={"response":result})returnself._build_state(result)exceptValidationError:# Re-raise validation errors as-israiseexceptClientErrorase:# Map client errors to resource errorsife.code=="QUOTA_EXCEEDED":raiseResourceError("Quota exceeded for resource type",details={"type":config.resource_type,"quota":e.quota,"used":e.used},cause=e)elife.code=="PERMISSION_DENIED":raiseProviderError("Insufficient permissions",details={"required_permission":e.permission},cause=e)else:raiseResourceError(f"Unexpected error: {e.message}",cause=e)exceptExceptionase:# Catch-all for unexpected errorsraiseResourceError("Unexpected error during resource creation",details={"config":config.__dict__},cause=e)
frompyvider.exceptionsimportResourceErrorclassQuotaExceededError(ResourceError):"""Raised when a quota limit is exceeded."""def__init__(self,resource_type:str,limit:int,current:int):super().__init__(f"Quota exceeded for {resource_type}",details={"resource_type":resource_type,"limit":limit,"current":current,"available":limit-current})classResourceLockedError(ResourceError):"""Raised when a resource is locked and cannot be modified."""def__init__(self,resource_id:str,locked_by:str,locked_until:str):super().__init__(f"Resource {resource_id} is locked",details={"resource_id":resource_id,"locked_by":locked_by,"locked_until":locked_until})
importpytestfrompyvider.exceptionsimportResourceError,ValidationErrorfrompyvider.resources.contextimportResourceContext@pytest.mark.asyncioasyncdeftest_resource_creation_error_handling(resource):"""Test that errors are properly handled."""# Test validation errorwithpytest.raises(ValidationError)asexc_info:ctx=ResourceContext(config=Resource.Config(name=""))# Empty nameawaitresource._create_apply(ctx)assert"Required field"instr(exc_info.value)assertexc_info.value.details["field"]=="name"# Test resource error with causewithpytest.raises(ResourceError)asexc_info:ctx=ResourceContext(config=Resource.Config(name="test",invalid_field=True))awaitresource._create_apply(ctx)assertexc_info.value.causeisnotNoneassert"invalid_field"inexc_info.value.details
importstructloglogger=structlog.get_logger()try:result=awaitoperation()exceptPyviderErrorase:logger.error("Operation failed",error_type=type(e).__name__,message=str(e),details=e.details,has_cause=e.causeisnotNone,exc_info=True# Include traceback)raise