This guide covers advanced features and patterns for building production-focused Pyvider providers, including error handling, retry logic, rate limiting, caching, and testing.
🤖 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.
frompyvider.exceptionsimport(ProviderError,ProviderConfigurationError,ResourceNotFoundError,APIError,)classMyCloudProvider(BaseProvider):asyncdefconfigure(self,config:dict)->None:try:awaitsuper().configure(config)# Validate API keyifnotawaitself._validate_api_key(config["api_key"]):raiseProviderConfigurationError("Invalid API key")# Initialize clientself.api_client=self._create_client(config)# Test connectionawaitself._test_connection()excepthttpx.HTTPErrorase:raiseProviderConfigurationError(f"Failed to connect: {e}")exceptExceptionase:raiseProviderError(f"Provider configuration failed: {e}")asyncdef_validate_api_key(self,api_key:str)->bool:"""Validate API key format and permissions."""ifnotapi_key.startswith("mck_"):returnFalse# Test API keytry:response=awaitself.api_client.get("/auth/validate")returnresponse.status_code==200exceptException:returnFalseasyncdef_test_connection(self)->None:"""Test API connectivity."""try:response=awaitself.api_client.get("/health")response.raise_for_status()excepthttpx.HTTPErrorase:raiseProviderConfigurationError(f"Health check failed: {e}. "f"Verify API endpoint and network connectivity.")
fromtenacityimport(retry,stop_after_attempt,wait_exponential,retry_if_exception_type,)classMyCloudProvider(BaseProvider):@retry(stop=stop_after_attempt(3),wait=wait_exponential(multiplier=1,min=2,max=10),retry=retry_if_exception_type(httpx.HTTPStatusError),)asyncdef_api_request(self,method:str,path:str,**kwargs):"""Make API request with automatic retry."""response=awaitself.api_client.request(method,path,**kwargs)# Don't retry client errors (4xx)if400<=response.status_code<500:response.raise_for_status()# Retry server errors (5xx) and network issuesifresponse.status_code>=500:response.raise_for_status()returnresponse
importasynciofromdatetimeimportdatetime,timedeltaclassMyCloudProvider(BaseProvider):def__init__(self):super().__init__(...)self.rate_limiter=RateLimiter(max_requests=100,time_window=timedelta(minutes=1))asyncdef_api_request(self,method:str,path:str,**kwargs):"""Make rate-limited API request."""awaitself.rate_limiter.acquire()returnawaitself.api_client.request(method,path,**kwargs)classRateLimiter:def__init__(self,max_requests:int,time_window:timedelta):self.max_requests=max_requestsself.time_window=time_windowself.requests=[]asyncdefacquire(self):"""Wait if rate limit reached."""now=datetime.now()# Remove old requestsself.requests=[req_timeforreq_timeinself.requestsifnow-req_time<self.time_window]# Wait if limit reachediflen(self.requests)>=self.max_requests:oldest=min(self.requests)wait_time=(oldest+self.time_window-now).total_seconds()ifwait_time>0:awaitasyncio.sleep(wait_time)self.requests.append(now)
# tests/test_integration.pyimportpytestimportos@pytest.mark.skipif(notos.getenv("MYCLOUD_API_KEY"),reason="Requires MYCLOUD_API_KEY environment variable")@pytest.mark.asyncioasyncdeftest_real_api_connection():"""Test connection to real API."""provider=MyCloudProvider()config={"api_key":os.getenv("MYCLOUD_API_KEY"),"region":"us-east-1",}awaitprovider.configure(config)# Test health checkresponse=awaitprovider.api_client.get("/health")assertresponse.status_code==200
asyncdefvalidate_config(self,config:dict)->list[str]:"""Validate before attempting to use config."""errors=[]# Check all requirementsifnotconfig.get("api_key"):errors.append("api_key is required")returnerrors
Remember: These advanced features help you build robust, production-focused providers that can handle real-world challenges like network failures, rate limits, and scale requirements.