Closed jonathanunderwood closed 5 years ago
Hi @jonathanunderwood thanks for the submission. I can certainly attempt to implement something like what you suggested, and have been thinking about it as well. Just not sure yet how much magic makes sense to add here. For example, a somewhat logical next step for a feature like this is to add some handling or discovery of relationships, includes, and links. Most JSON api implementations try to do this in some way or another. For now, this library is focused mainly on structural typing and validation of JSON api documents, but I could see it providing more data transformation functionality.
If you're interested, this is the sort of approach I've been taking on this:
from pydantic import BaseModel
from pydantic_jsonapi import JsonApiModel
from my_data_model import Item
class ItemAttributes(BaseModel):
name: str
quantity: int
price: float
class Config:
orm_mode = True
ItemRequest, ItemResponse = JsonApiModel('item', ItemAttributes)
def marshal_item(item: Item) -> dict:
return {
'id': item.id,
'type': 'item',
'attributes': ItemAttributes.from_orm(item).dict()
'relationships': {
'sold_at': {
'data': {'type': 'store', 'id': item.store.id}
}
}
}
# some http endpoint
@app.get('/item')
def get_item():
item = db.query(Item).one()
return ItemResponse(data=marshal_item(item)).json()
There's some amount of duplication with this approach, but it's very flexible in how you want to use json api and there's no magic. Any structure you produce with a function like marshal_item
here ends up getting validated against the JSON api spec
Yes, that's essenetially the approach I have been taking. In your example, presumably your ItemAttributes
would also have an id
field, which is Optional
?
@jonathanunderwood id
field is a bit tricky since it's not considered an attribute in JSONAPI.
Incidentally, the approach you outline is for marshalling from an ORM model directly. Which is a partial solution. But I find myself often wanting to create a JSONAPI response/request from the corresponding attributes pydantic object.
@jonathanunderwood please take a look at #11 I've added a new method resource_object
, which comes pretty close to your request here.
With that change, my above example could be changed to look something like this:
ItemRequest, ItemResponse = JsonApiModel('item', ItemAttributes)
# some http endpoint
@app.get('/item')
def get_item():
item = db.query(Item).one()
return ItemResponse(
data=ItemResponse.resource_object(
id=item.id,
attributes=item
)
).json()
or for lists:
@app.get('/items')
def get_items():
items = db.query(Item).all()
return ItemResponse(
data=[
ItemResponse.resource_object(
id=item.id,
attributes=item
) for item in items
]
).json()
released #11 in 0.9.0
This looks very handy, thanks for adding it. I'll give it a try next week. Thanks again.
We've been using this locally for a few days, and it works very well! Many thanks for adding it.
Is your feature request related to a problem? Please describe. Constructing a request/response object is more arduous than it needs to be, and can't be done from ORM models.
Describe the solution you'd like
This would also make it super easy to construct request/response objects from ORM models.
It wouldn't necessarily have to be the constructor, it could be a different class method e.g.
item_req = ItemRequest.from_primitive(item)
Describe alternatives you've considered Right now, I mangle by hand.
Additional context Add any other context or screenshots about the feature request here.