[Sharing] pydantic-resolve, an easy way to generate lite-graphql-like nested data with aiodataloader
allmonday opened this issue · comments
aiodataloader is very helpful to use with graphql framework such as Graphene or strawberry for complex query result.
and then I start to wonder if I could use with aiodataloader alone (without including extra graphql system) to generate such nested data (only a small subset of graphql, get nested related informations) in some simple scenarios (like writing a small script)
so I build pydantic-resolve
repo,
a pure library which can work nicely with aiodataloader to do so.
by defining loader_fn:
import asyncio
from typing import List, Optional
from pydantic import BaseModel
from pydantic_resolve import Resolver, mapper, LoaderDepend
async def friends_batch_load_fn(names):
mock_db = {
'tangkikodo': ['tom', 'jerry'],
'john': ['mike', 'wallace'],
'trump': ['sam', 'jim'],
'sally': ['sindy', 'lydia'],
}
result = [mock_db.get(name, []) for name in names]
return result
async def contact_batch_load_fn(names):
mock_db = {
'tom': 100, 'jerry':200, 'mike': 3000, 'wallace': 400, 'sam': 500,
'jim': 600, 'sindy': 700, 'lydia': 800, 'tangkikodo': 900, 'john': 1000,
'trump': 1200, 'sally': 1300,
}
result = [mock_db.get(name, None) for name in names]
return result
and schema:
class Contact(BaseModel):
number: Optional[int]
class Friend(BaseModel):
name: str
contact: int = 0
@mapper(lambda n: Contact(number=n))
def resolve_contact(self, loader=LoaderDepend(contact_batch_load_fn)):
return loader.load(self.name)
class User(BaseModel):
name: str
age: int
greeting: str = ''
def resolve_greeting(self):
return f"hello, i'm {self.name}, {self.age} years old."
contact: int = 0
@mapper(lambda n: Contact(number=n))
def resolve_contact(self, loader=LoaderDepend(contact_batch_load_fn)):
return loader.load(self.name)
friends: List[Friend] = []
@mapper(lambda items: [Friend(name=item) for item in items]) # transform after data received
def resolve_friends(self, loader=LoaderDepend(friends_batch_load_fn)):
return loader.load(self.name)
class Root(BaseModel):
users: List[User] = []
def resolve_users(self):
return [
User(name="tangkikodo", age=19), # transform first
User(name='john', age=21),
# User(name='trump', age=59), # uncomment to resolve more
# User(name='sally', age=21),
# User(name='some one', age=0)
]
async def main():
import json
root = Root()
root = await Resolver().resolve(root)
dct = root.dict()
print(json.dumps(dct, indent=4))
asyncio.run(main())
and then it can export the nested data as we expected.
{
"users": [
{
"name": "tangkikodo",
"age": 19,
"greeting": "hello, i'm tangkikodo, 19 years old.",
"contact": {
"number": 900
},
"friends": [
{
"name": "tom",
"contact": {
"number": 100
}
},
{
"name": "jerry",
"contact": {
"number": 200
}
}
]
},
{
"name": "john",
"age": 21,
"greeting": "hello, i'm john, 21 years old.",
"contact": {
"number": 1000
},
"friends": [
{
"name": "mike",
"contact": {
"number": 3000
}
},
{
"name": "wallace",
"contact": {
"number": 400
}
}
]
}
]
}
pydantic-resolve
can also work with dataclass or just simple class instances and the loader instance lifecycle is isolated in each single Resolve().resolve(data)
call.
Hope this simple library can help someone, and thank again for the great work of aiodataloader
Thx.