uplink
uplink copied to clipboard
Support *args **kwargs to built in converters (e.g. pydantic by_alias)
Is your feature request related to a problem? Please describe. Pydantic is supported out of the box but I can't use it to serialize when using field aliases. Pydantic has support for this but I don't see a way to pass this information in Uplink.
Say I have a pydantic model like the following. The API uses camelcase but Python api should use snake case.
from pydantic import BaseModel
def to_camel(string: str) -> str:
return "".join(word.capitalize() for word in string.split("_"))
class Case(BaseModel):
instance_name: str
instance_state: str
class Config:
alias_generator = to_camel
allow_population_by_field_name = True
In Uplink I have
from .models import Case
from uplink import Consumer, get, post, json, returns, Body
class CaseAPI(Consumer):
@get("scenario/{case_id}")
def get(self, case_id: int) -> Case:
"""Get case by case_id"""
@json
@returns.json
@post("scenario")
def create(self, payload: Body(type=Case)):
"""Create a new case"""
The GET works based on the alias_generator in model but the POST does not work (does not convert snake to camel case) because one needs to pass by_alias argument to the .json() method on Pydantic model class [doc]
Describe the solution you'd like I'd like to be able to pass *args and **kwargs to the converters that are built in. API something like
...
@json
@returns.json
@post("scenario")
def create(self, payload: Body(type=Case, by_alias=True)):
....
For now my work-around is just to use custom serialization function.
Cheers - Josh
I just hit this too
it seems like uplink does not call pydantic_model.dict() like you would expect, but instead uses this other function: https://github.com/prkumar/uplink/blob/d3ee283426873ad61243b5759b7cb52f8f712567/uplink/converters/pydantic_.py#L12
and in pydantic that method doesn't pass thru kwargs to dict()
https://github.com/samuelcolvin/pydantic/blob/6f46a5a1460728868f860840e3f6186aa40403d8/pydantic/json.py#L78
return obj.dict()
I'm looking into different approaches for how we can address this. The approach I'm favoring presently is to define a new decorator (e.g., converters.pydantic.dict_args(...)):
@json
@returns.json
@converters.pydantic.dict(by_alias=True)
@post("scenario")
def create(self, payload: Body(type=Case)):
pass
Passing args through Body sounds fine to me, too. However, It may require more effort for similar functionality. One upside of this approach is that we could clearly specify different model.dict args per Body argument. However, I don't think such a feature would be necessary, since there should only be one @Body-annotated argument per @json-decorated method.
As long as the by_alias works by really providing aliases requested here. I remember some surprising name effect collisions fro browsing through the pydantic project issue discussions after banging my head trying to hand-over snake case aliases as function arguments onto camel case model elements when in reality this was only working for pushing dicts/json through the parameter gate. But maybe that is exactly the use case here. I do not remember the name Samuel Colvin in retrospect would have preferred for the concept - I will try to find it again and update my words here, sorry for the noise.
Update: Here is my homework 🙈 https://github.com/samuelcolvin/pydantic/issues/565
@jkornblum How were you able to override the built-in Pydantic converter? I've created a custom converter, but it doesn't seem to take precedence.
edit: Ah, nevermind. I was missing the @json decorator.