Complete API reference for data sources and the BaseDataSource class.
🤖 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.
Important:
- Data sources use the same ResourceContext API as resources
- Access configuration via ctx.config
- Return None if config is unavailable
- Unlike resources, data sources re-fetch data on every Terraform operation
frompyvider.schemaimports_data_source,a_str,a_num,a_list@classmethoddefget_schema(cls)->PvsSchema:returns_data_source({# Inputs (from user)"filter":a_str(required=True,description="Filter expression"),"limit":a_num(default=10,description="Max results"),# Outputs (computed by data source)"id":a_str(computed=True,description="Query ID"),"servers":a_list(a_str(),computed=True,description="Server list"),"count":a_num(computed=True,description="Number of servers"),})
Rules:
- id field is required (must be deterministic)
- All fields are outputs (returned to Terraform)
- Use appropriate types (list, dict, str, int, bool)
asyncdefread(self,config:Config)->Data:# Generate stable ID from inputsquery_id=f"{config.endpoint}:{config.filter}:{config.limit}"results=awaitapi.query(...)returnData(id=query_id,# Same config always = same IDresults=results,)
importhashlibimportjsonasyncdefread(self,config:Config)->Data:# Create hash of all config valuesconfig_str=json.dumps({"endpoint":config.endpoint,"filter":config.filter,"limit":config.limit,},sort_keys=True)query_id=hashlib.md5(config_str.encode()).hexdigest()returnData(id=query_id,...)
asyncdefread(self,config:Config)->Data:result=awaitapi.get(config.resource_id)ifnotresult:# Return empty data instead of NonereturnData(id=config.resource_id,found=False,value=None,)returnData(id=result["id"],found=True,value=result["value"],)
@attrs.defineclassFileInfoData:id:strexists:bool# Use bool for yes/no outputsreadable:boolsize:int|None# None if doesn't existasyncdefread(self,config:Config)->FileInfoData:frompathlibimportPathpath=Path(config.path)returnFileInfoData(id=str(path.absolute()),exists=path.exists(),readable=path.exists()andos.access(path,os.R_OK),size=path.stat().st_sizeifpath.exists()elseNone,)
frompyvider.schemaimports_data_source,a_list,a_map,a_str@classmethoddefget_schema(cls)->PvsSchema:returns_data_source({"query":a_str(required=True),"id":a_str(computed=True),# List of strings"tags":a_list(a_str(),computed=True),# List of objects"servers":a_list(a_map(a_str()),# Each server is a mapcomputed=True),})
fromfunctoolsimportlru_cacheclassServerQuery(BaseDataSource):@lru_cache(maxsize=128)asyncdef_fetch_servers(self,filter_str:str,limit:int)->list[dict]:"""Cached query."""returnawaitapi.list_servers(filter=filter_str,limit=limit)asyncdefread(self,config:Config)->Data:# Query is cached by filter+limitservers=awaitself._fetch_servers(config.filter,config.limit)returnData(id=f"{config.filter}:{config.limit}",servers=servers,count=len(servers),)
asyncdefread(self,config:Config)->Data:# Validate inputserrors=[]ifnotconfig.endpoint.startswith("/"):errors.append("Endpoint must start with /")ifconfig.limit<1orconfig.limit>1000:errors.append("Limit must be between 1 and 1000")iferrors:returnData(id="error",results=[],error="; ".join(errors),)# Proceed with query...
# Good: Idempotentasyncdefread(self,config:Config)->Data:# Same query always returns same resultsservers=awaitapi.list_servers(filter=config.filter)returnData(id=config.filter,servers=servers)# Bad: Not idempotentasyncdefread(self,config:Config)->Data:# Returns different results each time!servers=awaitapi.list_recent_servers()returnData(id=str(uuid.uuid4()),servers=servers)
# Good: Single API callasyncdefread(self,config:Config)->Data:response=awaitapi.get_user_with_posts(config.user_id)returnData(id=config.user_id,user=response["user"],posts=response["posts"],)# Bad: Multiple API callsasyncdefread(self,config:Config)->Data:user=awaitapi.get_user(config.user_id)posts=awaitapi.get_posts(config.user_id)# Separate callreturnData(id=config.user_id,user=user,posts=posts)
importpytestfrommy_provider.data_sources.server_queryimportServerQuery,ServerQueryConfig@pytest.mark.asyncioasyncdeftest_server_query():ds=ServerQuery()config=ServerQueryConfig(filter="status=running",limit=5)data=awaitds.read(config)assertdata.count<=5assertlen(data.servers)==data.countassertdata.id=="status=running:5"# Deterministic ID@pytest.mark.asyncioasyncdeftest_server_query_error_handling():ds=ServerQuery()config=ServerQueryConfig(filter="invalid!",limit=5)data=awaitds.read(config)# Should return data with error, not raiseassertdata.errorisnotNoneassertdata.servers==[]