Search code examples
sqlalchemyfastapicamelcasingpost-processing

Simply format SQLAlchemy models returned by FastApi endpoint


Suppose I have a simple SQLAlchemy class and a simple Flask o FastAPI implementation like this:

from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel
Base = declarative_base()
    
class A(Base):
    __tablename__ = 'as'
    my_id = Column(String)
    
class AModel(BaseModel):
    myId:str = None

And a simple endpoint like this:

@app_router.get('/a')
def get_all_a(session:Session = Depends(get_session)):
    return session.query(A).all()

How could I ensure that the returned list of this endpoint yields in camelCase like this:

[{'myId': 'id1'},{'myId': 'id2'}, ...]

Note: My application is rather complex, as I also have pagination implemented and some post-processings require a little bit more that just snake_case to camelCase conversion, so the simplest solution would be the best.

I've tried overriding dict() methods and similar stuff with no luck, simply cannot understand how FastAPI processes the results to obtain a JSON.


Solution

  • 2023 may update:

    This solution is compatible with pydantic < v2.0 and will not necessarily reflect future release of this amazing library

    require a little bit more that just snake_case to camelCase conversion

    Well, if you don't use response_model you don't have a lot of choices.

    The solution is returning your dict with a conversion from snake_case to camelCase. You have functions that do it recursively.

    Why it is the best solution ?

    using regex it's super fast, faster than any lib that convert dict to obj like pydantic

    If you definitely don't want to do this, well your only solution is using pydantic models, attrs or dataclasses and convert your db query output to one of those models type with camelCase variable name (dirty).

    Since you are using fastapi you should use all the power of it.

    I would suggest this:

    from typing import List
    
    from sqlalchemy.ext.declarative import declarative_base
    from pydantic import BaseModel, Field
    from pydantic import parse_obj_as
    Base = declarative_base()
    
    class A(Base):
        __tablename__ = 'as'
        my_id = Column(String)
    
    class AModel(BaseModel):
        myId: str = Field(alias="my_id", default=None)
    
    @app_router.get('/a', response_model=List[AModel])
    def get_all_a(session:Session = Depends(get_session)):
        return parse_obj_as(List[AModel], session.query(A).all())
    

    Keep in mind that having classes variables in CamelCase is not a good practice.

    The gold answer would be to not return camel but snake and let your client do the work of conversion if needed.