Expose and Collect
This set of advanced features provided by pydantic-resolve aims to solve the problem of data transmission and assembly across multiple layers, facilitating computations in scenarios with deeply nested structures.
Providing Data to All Descendant Nodes
The Company
type has three layers. Add an introduction
field to the Employee
type, containing company - department - employee name
.
class Company(BaseModel):
__pydantic_resolve_expose__ = { 'name': 'company_name' }
id: int
name: str
departments: List[Department]
class Department(BaseModel):
__pydantic_resolve_expose__ = { 'name': 'department_name' }
id: int
name: str
employees: List[Employee]
class Employee(BaseModel):
id: int
name: str
introduction: str = ''
def resolve_introduction(self, ancestor_context):
company = ancestor_context['company_name']
department = ancestor_context['department_name']
return f'{company}/{department}/{self.name}'
__pydantic_resolve_expose__
will convert the key in the definition to the value (alias) and then provide it to all descendant nodes.
The name
in Company
is converted to company_name
, and descendant nodes get the data from ancestor_context['company_name']
.
Note that the alias should be globally unique within the entire structure.
Collecting Data from Descendant Nodes
Now suppose there is a Report
class that has a company
data, and then uses complyees
to collect all Employee
's introduction
fields.
from pydantic_resolve import Collect
class Report(BaseModel):
companies: List[Company]
employees: List[str] = []
def post_employees(self, collector=Collector('reporter')):
return collector.values()
class Company(BaseModel):
__pydantic_resolve_expose__ = { 'name': 'company_name' }
id: int
name: str
departments: List[Department]
class Department(BaseModel):
__pydantic_resolve_expose__ = { 'name': 'department_name' }
id: int
name: str
employees: List[Employee]
class Employee(BaseModel):
__pydantic_resolve_collect__ = {'introduction': 'reporter'}
id: int
name: str
introduction: str = ''
def resolve_introduction(self, ancestor_context):
company = ancestor_context['company_name']
department = ancestor_context['department_name']
return f'{company}/{department}/{self.name}'
__pydantic_resolve_collect__ = {'introduction': 'reporter'}
means that the value of the introduction
field will be sent to the reporter
collector at the end of the node's lifecycle.
Note that the collector needs to be defined in the post method because it must wait for all descendant nodes to complete their calculations before collecting data.
employees: List[str] = []
def post_employees(self, collector=Collector('reporter')):
return collector.values()
Like DataLoader, the collector parameter names and quantities are not limited.
employees: List[str] = []
def post_employees(self, xxx=Collector('xxx'), yyy=Collector('yyy')):
return xxx.values() + yyy.values()
Unlike the global uniqueness requirement for aliases in Expose, the collector's name can be defined at multiple levels. The same name collector can collect data of different scales based on the range of descendants.
For example, Company
can also define employees
and use the same name collector as in Report
to collect data at the Company
level.
from pydantic_resolve import Collect
class Report(BaseModel):
companies: List[Company]
employees: List[str] = []
def post_employees(self, collector=Collector('reporter')):
return collector.values()
class Company(BaseModel):
__pydantic_resolve_expose__ = { 'name': 'company_name' }
id: int
name: str
departments: List[Department]
employees: List[str] = []
def post_employees(self, collector=Collector('reporter')):
return collector.values()
class Department(BaseModel):
__pydantic_resolve_expose__ = { 'name': 'department_name' }
id: int
name: str
employees: List[Employee]
class Employee(BaseModel):
__pydantic_resolve_collect__ = {'introduction': 'reporter'}
id: int
name: str
introduction: str = ''
def resolve_introduction(self, ancestor_context):
company = ancestor_context['company_name']
department = ancestor_context['department_name']
return f'{company}/{department}/{self.name}'