[![pypi](https://img.shields.io/pypi/v/pydantic-resolve.svg)](https://pypi.python.org/pypi/pydantic-resolve)
[![Downloads](https://static.pepy.tech/personalized-badge/pydantic-resolve?period=month&units=abbreviation&left_color=grey&right_color=orange&left_text=Downloads)](https://pepy.tech/project/pydantic-resolve)
![Python Versions](https://img.shields.io/pypi/pyversions/pydantic-resolve)
[![CI](https://github.com/allmonday/pydantic_resolve/actions/workflows/ci.yml/badge.svg)](https://github.com/allmonday/pydantic_resolve/actions/workflows/ci.yml)
Pydantic-resolve is a schema based solution for data composition, it can provide you with 3 ~ 5 times the increase in development efficiency and reduce the amount of code by more than 50%.
1. It manages the deep data inside each schema, instead of visiting from outside by manual traversal.
2. It runs a Level Order Traversal (BFS) inside and execute `resolve` and `post` during this process.
3. It describes the relationship between data in a form close to ERD (entity relationship diagram)
## Install
~~User of pydantic v2, please use [pydantic2-resolve](https://github.com/allmonday/pydantic2-resolve) instead.~~
This lib now supports both pydantic v1 and v2 starts from v1.11.0
```shell
pip install pydantic-resolve
```
## Hello world
manage your data inside the schema.
```python
class Tree(BaseModel):
name: str
number: int
description: str = ''
def resolve_description(self):
return f"I'm {self.name}, my number is {self.number}"
children: list['Tree'] = []
tree = dict(
name='root',
number=1,
children=[
dict(
name='child1',
number=2,
children=[
dict(
name='child1-1',
number=3,
),
dict(
name='child1-2',
number=4,
),
]
)
]
)
async def main():
t = Tree.parse_obj(tree)
t = await Resolver().resolve(t)
print(t.json(indent=4))
import asyncio
asyncio.run(main())
```
output
```json
{
"name": "root",
"number": 1,
"description": "I'm root, my number is 1",
"children": [
{
"name": "child1",
"number": 2,
"description": "I'm child1, my number is 2",
"children": [
{
"name": "child1-1",
"number": 3,
"description": "I'm child1-1, my number is 3",
"children": []
},
{
"name": "child1-2",
"number": 4,
"description": "I'm child1-2, my number is 4",
"children": []
}
]
}
]
}
```
## Composing a subset from ERD definitions
![](./doc/imgs/concept.png)
define elements of ERD, schema (entity), dataloader (relationship).
then pick and compose them together according to your requirement and get the result.
```python
import asyncio
import json
from typing import Optional
from pydantic import BaseModel
from pydantic_resolve import Resolver, build_object, build_list, LoaderDepend
from aiodataloader import DataLoader
# Schema/ Entity
class Comment(BaseModel):
id: int
content: str
user_id: int
class Blog(BaseModel):
id: int
title: str
content: str
class User(BaseModel):
id: int
name: str
# Loaders/ relationships
class CommentLoader(DataLoader):
async def batch_load_fn(self, comment_ids):
comments = [
dict(id=1, content="world is beautiful", blog_id=1, user_id=1),
dict(id=2, content="Mars is beautiful", blog_id=2, user_id=2),
dict(id=3, content="I love Mars", blog_id=2, user_id=3),
]
return build_list(comments, comment_ids, lambda c: c['blog_id'])
class UserLoader(DataLoader):
async def batch_load_fn(self, user_ids):
users = [ dict(id=1, name="Alice"), dict(id=2, name="Bob"), ]
return build_object(users, user_ids, lambda u: u['id'])
# Compose schemas and dataloaders together
class CommentWithUser(Comment):
user: Optional[User] = None
def resolve_user(self, loader=LoaderDepend(UserLoader)):
return loader.load(self.user_id)
class BlogWithComments(Blog):
comments: list[CommentWithUser] = []
def resolve_comments(self, loader=LoaderDepend(CommentLoader)):
return loader.load(self.id)
# Run
async def main():
raw_blogs =[
dict(id=1, title="hello world", content="hello world detail"),
dict(id=2, title="hello Mars", content="hello Mars detail"),
]
blogs = await Resolver().resolve([BlogWithComments.parse_obj(b) for b in raw_blogs])
print(json.dumps(blogs, indent=2, default=lambda o: o.dict()))
asyncio.run(main())
```
output
```json
[
{
"id": 1,
"title": "hello world",
"content": "hello world detail",
"comments": [
{
"id": 1,
"content": "world is beautiful",
"user_id": 1,
"user": {
"id": 1,
"name": "Alice"
}
}
]
},
{
"id": 2,
"title": "hello Mars",
"content": "hello Mars detail",
"comments": [
{
"id": 2,
"content": "Mars is beautiful",
"user_id": 2,
"user": {
"id": 2,
"name": "Bob"
}
},
{
"id": 3,
"content": "I love Mars",
"user_id": 3,
"user": null
}
]
}
]
```
## Documents
- **Quick start**: https://allmonday.github.io/pydantic-resolve/about/
- **API**: https://allmonday.github.io/pydantic-resolve/reference_api/
- **Demo**: https://github.com/allmonday/pydantic-resolve-demo
- **Composition oriented pattern**: https://github.com/allmonday/composition-oriented-development-pattern
## Test and coverage
```shell
tox
```
```shell
tox -e coverage
python -m http.server
```
latest coverage: 98%
## Sponsor
If this code helps and you wish to support me
Paypal: https://www.paypal.me/tangkikodo
## Discussion
[Discord](https://discord.com/channels/1197929379951558797/1197929379951558800)
Raw data
{
"_id": null,
"home_page": "https://github.com/allmonday/pydantic_resolve",
"name": "pydantic-resolve",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.7",
"maintainer_email": null,
"keywords": "pydantic, fastapi",
"author": "tangkikodo",
"author_email": "allmonday@126.com",
"download_url": "https://files.pythonhosted.org/packages/38/35/1df7bd8396773075f98cb7ab5a4726df813c37460cd813fb46f5722d9141/pydantic_resolve-1.11.1.tar.gz",
"platform": null,
"description": "[![pypi](https://img.shields.io/pypi/v/pydantic-resolve.svg)](https://pypi.python.org/pypi/pydantic-resolve)\n[![Downloads](https://static.pepy.tech/personalized-badge/pydantic-resolve?period=month&units=abbreviation&left_color=grey&right_color=orange&left_text=Downloads)](https://pepy.tech/project/pydantic-resolve)\n![Python Versions](https://img.shields.io/pypi/pyversions/pydantic-resolve)\n[![CI](https://github.com/allmonday/pydantic_resolve/actions/workflows/ci.yml/badge.svg)](https://github.com/allmonday/pydantic_resolve/actions/workflows/ci.yml)\n\nPydantic-resolve is a schema based solution for data composition, it can provide you with 3 ~ 5 times the increase in development efficiency and reduce the amount of code by more than 50%.\n\n1. It manages the deep data inside each schema, instead of visiting from outside by manual traversal.\n2. It runs a Level Order Traversal (BFS) inside and execute `resolve` and `post` during this process.\n3. It describes the relationship between data in a form close to ERD (entity relationship diagram)\n\n## Install\n\n~~User of pydantic v2, please use [pydantic2-resolve](https://github.com/allmonday/pydantic2-resolve) instead.~~\n\nThis lib now supports both pydantic v1 and v2 starts from v1.11.0\n\n```shell\npip install pydantic-resolve\n```\n\n## Hello world\n\nmanage your data inside the schema.\n\n```python\nclass Tree(BaseModel):\n name: str\n number: int\n description: str = ''\n def resolve_description(self):\n return f\"I'm {self.name}, my number is {self.number}\"\n children: list['Tree'] = []\n\n\ntree = dict(\n name='root',\n number=1,\n children=[\n dict(\n name='child1',\n number=2,\n children=[\n dict(\n name='child1-1',\n number=3,\n ),\n dict(\n name='child1-2',\n number=4,\n ),\n ]\n )\n ]\n)\n\nasync def main():\n t = Tree.parse_obj(tree)\n t = await Resolver().resolve(t)\n print(t.json(indent=4))\n\nimport asyncio\nasyncio.run(main())\n```\n\noutput\n\n```json\n{\n \"name\": \"root\",\n \"number\": 1,\n \"description\": \"I'm root, my number is 1\",\n \"children\": [\n {\n \"name\": \"child1\",\n \"number\": 2,\n \"description\": \"I'm child1, my number is 2\",\n \"children\": [\n {\n \"name\": \"child1-1\",\n \"number\": 3,\n \"description\": \"I'm child1-1, my number is 3\",\n \"children\": []\n },\n {\n \"name\": \"child1-2\",\n \"number\": 4,\n \"description\": \"I'm child1-2, my number is 4\",\n \"children\": []\n }\n ]\n }\n ]\n}\n```\n\n## Composing a subset from ERD definitions\n\n![](./doc/imgs/concept.png)\n\ndefine elements of ERD, schema (entity), dataloader (relationship).\n\nthen pick and compose them together according to your requirement and get the result.\n\n```python\nimport asyncio\nimport json\nfrom typing import Optional\nfrom pydantic import BaseModel\nfrom pydantic_resolve import Resolver, build_object, build_list, LoaderDepend\nfrom aiodataloader import DataLoader\n\n# Schema/ Entity\nclass Comment(BaseModel):\n id: int\n content: str\n user_id: int\n\nclass Blog(BaseModel):\n id: int\n title: str\n content: str\n\nclass User(BaseModel):\n id: int\n name: str\n\n\n# Loaders/ relationships\nclass CommentLoader(DataLoader):\n async def batch_load_fn(self, comment_ids):\n comments = [\n dict(id=1, content=\"world is beautiful\", blog_id=1, user_id=1),\n dict(id=2, content=\"Mars is beautiful\", blog_id=2, user_id=2),\n dict(id=3, content=\"I love Mars\", blog_id=2, user_id=3),\n ]\n return build_list(comments, comment_ids, lambda c: c['blog_id'])\n\nclass UserLoader(DataLoader):\n async def batch_load_fn(self, user_ids):\n users = [ dict(id=1, name=\"Alice\"), dict(id=2, name=\"Bob\"), ]\n return build_object(users, user_ids, lambda u: u['id'])\n\n\n# Compose schemas and dataloaders together\nclass CommentWithUser(Comment):\n user: Optional[User] = None\n def resolve_user(self, loader=LoaderDepend(UserLoader)):\n return loader.load(self.user_id)\n\nclass BlogWithComments(Blog):\n comments: list[CommentWithUser] = []\n def resolve_comments(self, loader=LoaderDepend(CommentLoader)):\n return loader.load(self.id)\n\n\n# Run\nasync def main():\n raw_blogs =[\n dict(id=1, title=\"hello world\", content=\"hello world detail\"),\n dict(id=2, title=\"hello Mars\", content=\"hello Mars detail\"),\n ]\n blogs = await Resolver().resolve([BlogWithComments.parse_obj(b) for b in raw_blogs])\n print(json.dumps(blogs, indent=2, default=lambda o: o.dict()))\n\nasyncio.run(main())\n```\n\noutput\n\n```json\n[\n {\n \"id\": 1,\n \"title\": \"hello world\",\n \"content\": \"hello world detail\",\n \"comments\": [\n {\n \"id\": 1,\n \"content\": \"world is beautiful\",\n \"user_id\": 1,\n \"user\": {\n \"id\": 1,\n \"name\": \"Alice\"\n }\n }\n ]\n },\n {\n \"id\": 2,\n \"title\": \"hello Mars\",\n \"content\": \"hello Mars detail\",\n \"comments\": [\n {\n \"id\": 2,\n \"content\": \"Mars is beautiful\",\n \"user_id\": 2,\n \"user\": {\n \"id\": 2,\n \"name\": \"Bob\"\n }\n },\n {\n \"id\": 3,\n \"content\": \"I love Mars\",\n \"user_id\": 3,\n \"user\": null\n }\n ]\n }\n]\n```\n\n## Documents\n\n- **Quick start**: https://allmonday.github.io/pydantic-resolve/about/\n- **API**: https://allmonday.github.io/pydantic-resolve/reference_api/\n- **Demo**: https://github.com/allmonday/pydantic-resolve-demo\n- **Composition oriented pattern**: https://github.com/allmonday/composition-oriented-development-pattern\n\n## Test and coverage\n\n```shell\ntox\n```\n\n```shell\ntox -e coverage\npython -m http.server\n```\n\nlatest coverage: 98%\n\n## Sponsor\n\nIf this code helps and you wish to support me\n\nPaypal: https://www.paypal.me/tangkikodo\n\n## Discussion\n\n[Discord](https://discord.com/channels/1197929379951558797/1197929379951558800)\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "It just provide a pair of pre & post methods around pydantic fields, the rest is up to your imagination",
"version": "1.11.1",
"project_urls": {
"Homepage": "https://github.com/allmonday/pydantic_resolve",
"Repository": "https://github.com/allmonday/pydantic_resolve"
},
"split_keywords": [
"pydantic",
" fastapi"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "dbbc65c15bac87778da997e94abf2e6ae3a993501a8322f7f1f9a372adddde9c",
"md5": "bdce35f567bb98b42a2efc9cc5008ba4",
"sha256": "ff00d4620fe72b0febbd2e0f1b6e446b4fd0761d54e6fb4aa0672b3795b9bceb"
},
"downloads": -1,
"filename": "pydantic_resolve-1.11.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "bdce35f567bb98b42a2efc9cc5008ba4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.7",
"size": 20643,
"upload_time": "2024-11-21T01:14:43",
"upload_time_iso_8601": "2024-11-21T01:14:43.358186Z",
"url": "https://files.pythonhosted.org/packages/db/bc/65c15bac87778da997e94abf2e6ae3a993501a8322f7f1f9a372adddde9c/pydantic_resolve-1.11.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "38351df7bd8396773075f98cb7ab5a4726df813c37460cd813fb46f5722d9141",
"md5": "f6e3c99870f7b3897b1c02c97232012a",
"sha256": "9819e60e272cd63200e8ede9350d04ba6d2d52a38a573d78b650cc50891f81b9"
},
"downloads": -1,
"filename": "pydantic_resolve-1.11.1.tar.gz",
"has_sig": false,
"md5_digest": "f6e3c99870f7b3897b1c02c97232012a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.7",
"size": 17644,
"upload_time": "2024-11-21T01:14:44",
"upload_time_iso_8601": "2024-11-21T01:14:44.819434Z",
"url": "https://files.pythonhosted.org/packages/38/35/1df7bd8396773075f98cb7ab5a4726df813c37460cd813fb46f5722d9141/pydantic_resolve-1.11.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-21 01:14:44",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "allmonday",
"github_project": "pydantic_resolve",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"tox": true,
"lcname": "pydantic-resolve"
}