Add route to FastAPI (GetGloby example)

4 minute read

This is a basic example on how to add new routes to the GetGloby API:

Create the model

Make a folder inside models to organize your files, in this case we are going to add the translation settings.

mkdir -p src/models/translation/settings

NOTE: add an __init__.py file in each folder so python can import them.

Let’s create the models.py, schema.py and __init__.py files inside the settings folder to start coding.

touch models.py schema.py __init__.py

Models.py:

In this file we are going to define just the database model:

from sqlalchemy import Boolean
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer

from src.database import Base


class UserTranslationSettings(Base):
    __tablename__ = 'user_translation_settings'
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('user.id', ondelete='CASCADE'), nullable=False)
    title_case = Column(Boolean, nullable=False, default=True, server_default='true')
    punctuation_marks = Column(Boolean, nullable=False, default=True, server_default='true')
    replace_synonyms = Column(Boolean, nullable=False, default=True, server_default='true')
    shorten_sentences = Column(Boolean, nullable=False, default=True, server_default='true')
    use_known_translations = Column(Boolean, nullable=False, default=True, server_default='true')
    use_brand_terms = Column(Boolean, nullable=False, default=True, server_default='true')
    force_ad_lengths = Column(Boolean, nullable=False, default=False, server_default='false')

Let’s import this model to the models/__init__.py so alembic can read it and create the migration.

...
from src.models.translation.settings import models  # noqa[F401]
...
  • Create the revision:
    alembic revision --autogenerate
    
  • Update your local database:
    alembic upgrade head
    

Getter

Create the schema for the API:

Edit the schema.py file and create the requests and responses that we want to use in the new endpoint.

NOTE: using the orm_mode = True will get the data from the database when the object is created instead of requesting the data after accessing each attribute.

This change makes possible to just return the object in the endpoint and it will have all the information.

from typing import Optional

from pydantic import BaseModel


class Settings(BaseModel):
    title_case: bool
    punctuation_marks: bool
    replace_synonyms: bool
    shorten_sentences: bool
    use_known_translations: bool
    use_brand_terms: bool
    force_ad_lengths: bool

    class Config:
        orm_mode = True


class GetSettingsResponse(BaseModel):
    result: bool
    settings: Optional[Settings]

Create the database queries:

Let’s create the translation folder inside src/database.

mkdir -p src/database/translation/settings
touch src/database/translation/__init__.py
touch src/database/translation/settings/__init__.py

We are going to code our database functions inside the __init__.py file:

from typing import Optional

from sqlalchemy.orm import Session

from src.models.translation.settings.models import UserTranslationSettings
from src.models.translation.settings.schema import Settings


def get_translation_settings(db: Session, user_id: int) -> Optional[Settings]:
    res = db.query(UserTranslationSettings).filter_by(user_id=user_id).first()
    return res

Create the endpoint:

Let’s create the endpoint in the endpoint folder:

mkdir -p src/endpoints/translation/settings
touch src/endpoints/translation/__init__.py
touch src/endpoints/translation/settings/__init__.py

Code the endpoint inside the settings/__init__.py file:

from fastapi import APIRouter
from fastapi import Depends
from sqlalchemy.orm import Session

from src.auth.tokens.validators import get_current_user
from src.database import session_for_request
from src.database.translations import settings
from src.endpoints.constants import TRANSLATION_TAG
from src.models.translation.settings.schema import GetSettingsResponse

router = APIRouter()


@router.get('/get_translation_settings', tags=[TRANSLATION_TAG], response_model=GetSettingsResponse)
async def get_translation_settings(
        user_id: str = Depends(get_current_user),
        db: Session = Depends(session_for_request),
):
    r = settings.get_translation_settings(db, user_id)
    if r:
        return {'result': True, 'settings': r}
    return {'result': False}

NOTE: You may need to create the TAG in the src/endpoints/constants.py file, this is helpful to organize your endpoints when using Swagger

Add the new routes to the API:

In src/endpoints/router_helper.py add the new router:

from fastapi import FastAPI

from src.endpoints.translation.settings import router as settings_router


def add_routes(app: FastAPI):
    # Translation Routes
    app.include_router(settings_router)

NOTE: make sure you are calling this function on your main.py file

Setter

Validate attributes using the model:

We are going to add a function to update the attribute of our model after validating that the key exists.

This functions allow us to just send the request from the frontend directly to the model, and it will validate it and execute it.

Add to the models/translation/settings/models.py file the function update_a_setting:

class UserTranslationSettings(Base):
    __tablename__ = 'user_translation_settings'
    id = Column(Integer, primary_key=True)
    ...

    def update_a_setting(self, key, value):
        if hasattr(self, key):
            setattr(self, key, value)
            return True
        return False

Create a schema for the incoming message:

Let’s add a class to validate the parameters that the frontend is going to send us.

In this case our endpoint needs an identifier and a value.

Add to the models/translation/settings/schema.py file the class Setting:

class Setting(BaseModel):
    identifier: str
    value: bool

Create the database function to update the setting:

Let’s create a function that gets the translation settings from the current user and update (if possible) its value.

Add to the database/translations/settings/__init__.py file the function set_translation_setting:

def set_translation_setting(db: Session, user_id: int, setting: str, value: bool) -> bool:
    user_settings = get_translation_settings(db, user_id)
    updated = user_settings.update_a_setting(setting, value)
    if updated:
        db.commit()
        return True
    return False

Create the endpoint calling this function:

Let’s join everything together Add to the endpoints/translation/settings/__init__.py file the route set_translation_setting:

@router.post('/set_translation_setting', tags=[TRANSLATION_TAG], response_model=BoolResponse)
async def set_translation_setting(
        params: Setting,
        user_id: str = Depends(get_current_user),
        db: Session = Depends(session_for_request),
):
    r = settings.set_translation_setting(db, user_id, params.identifier, params.value)
    return {'result': r}