Всем привет, это моя первая статья на Habr, надеюсь, это будет кому-то интересно. На последнем проекте я как всегда делал какой-то REST API и вдруг меня посетила мысль что я что-то много копирую и вставляю одного и того же кода. И я решил почему бы не сделать генератор этого кода это оказалось не сложно.

Сразу оговорюсь что я использую https://github.com/tiangolo/full-stack-fastapi-postgresql и содержимое репозитория нужно положить в директорию backend/app/app что бы все заработало.

Думаю в чистом FastAPI проекте это тоже должно сработать.

По умолчанию в проектах этого типа есть два типа пользователей superuser и обыкновенный user соответсвенно CRUD будет создаваться относительно двух этих пользователей. Для генерации CRUD достаточно создать просто модель.

from sqlalchemy import Column, Integer, String
from app.db.base_class import Base


class Category(Base):
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, nullable=False)

Назовем этот файл models/category.py

И теперь вся магия для того что бы создать все endpoints/crud/schemas для этой модели достаточно запустить из каталога backend/app/app команду:

python3 codegen.py Category

Нужно не забыть поставить typer и jinja2 что бы скрипт заработал.

В результате мы получим вот такой файлик в директории codegen/generated

from typing import Any, List

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app import crud, models, schemas
from app.api import deps
from app.translate import _

router = APIRouter()


@router.get("/", response_model=List[schemas.Category])
def read_categories(
    db: Session = Depends(deps.get_db),
    skip: int = 0,
    limit: int = 100,
    current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
    """
    Retrieve category.
    """
    return crud.category.get_multi(db, skip=skip, limit=limit)


@router.post("/", response_model=schemas.Category)
def create_category(
    *,
    db: Session = Depends(deps.get_db),
    category_in: schemas.CategoryCreate,
    current_user: models.User = Depends(deps.get_current_active_superuser),
) -> Any:
    """
    Create new category.
    """

    return crud.category.create(db=db, obj_in=category_in)


@router.get("/{id}", response_model=schemas.Category)
def read_category(
    id: int,
    current_user: models.User = Depends(deps.get_current_active_superuser),
    db: Session = Depends(deps.get_db),
) -> Any:
    """
    Get a category.
    """
    category = crud.category.get(db=db, id=id)
    if not category:
        raise HTTPException(
            status_code=400, detail=_("Category doesn't exists")
        )
    return category


@router.put("/{id}", response_model=schemas.Category)
def update_category(
    *,
    db: Session = Depends(deps.get_db),
    id: int,
    category_in: schemas.CategoryUpdate,
    current_user: models.User = Depends(deps.get_current_active_superuser),
) -> Any:
    """
    Update a category.
    """
    category = crud.category.get(db=db, id=id)
    if not category:
        raise HTTPException(
            status_code=404,
            detail=_("Category doesn't exists"),
        )
    category = crud.category.update(db=db, db_obj=category, obj_in=category_in)
    return category


@router.delete("/{id}", response_model=schemas.Category)
def delete_category(
    *,
    db: Session = Depends(deps.get_db),
    id: int,
    current_user: models.User = Depends(deps.get_current_active_superuser),
) -> Any:
    """
    Delete an category.
    """
    category = crud.category.get(db=db, id=id)
    if not category:
        raise HTTPException(status_code=404, detail=_("Category doesn't exists"))

    return crud.category.remove(db=db, id=id)

И в консоли получим реквест:

Install new files? [y/N]:

Можно до того как копировать файлы просмотреть их подправить и нажать "y"

На самом деле что бы это все заработало осталось два шага чекнуть app/api/api_v1/api.py там могут быть проблемы. И добавить модель в models/__init__.py что бы сработал

alembic revision --autogenerated -m "new Model"

Забыл самое главное - это все ради новых мемберов FastAPI Ukraine.

Комментарии (19)


  1. dopusteam
    01.10.2021 18:17
    +19

    скорее всего не заработает с первого раза и вообще я обычно пишу по одной ошибке в строчке

    Думаю в чистом FastAPI проекте это тоже должно сработать

    Нужно не забыть поставить typer и jinja2 что бы скрипт заработал.

    На самом деле что бы это все заработало осталось два шага чекнуть app/api/api_v1/api.py там могут быть проблемы

    содержимое репозитория нужно положить в директорию backend/app/app что бы все заработало.

    Ну и зачем такое выкладывать тогда?

    Если эта статья наберет 100 лайков сделаем нормальный пакет

    Звучит как угроза. Замените на 'не наберёт'

    Что мешает сразу сделать нормально?

    Вы искали готовые решения?


  1. DGolub Автор
    01.10.2021 18:23
    -9

    Для меня это работает на нескольких проектах я думаю это полезно, просто нет гарантии что на твоем окружении прям все сразу заработает по этому я и предупреждаю.


  1. DGolub Автор
    01.10.2021 18:25
    -9

    Почему как угроза просто проверка на полезность если какому то количеству людей это зайдет тогда можно сделать pip а так зачем выкладывать в Пип то что никому не нужно, по-моему Хабр как раз для этого что бы проверить свою идею


    1. dopusteam
      01.10.2021 18:39
      +7

      зачем выкладывать в Пип то что никому не нужно

      Ну Вы сами сможете использовать это, например

      по-моему Хабр как раз для этого что бы проверить свою идею

      Сомневаюсь. Вряд ли кто то будет пробовать Вашу либу с таким количеством нюансов и подводных камней. Без тестов. Без нормальной установки. Без нормального описания


      1. DGolub Автор
        01.10.2021 19:27
        -4

        Ну добрые люди уже подсказали куда это положить сделаем красиво может будет более юзабельно


  1. DGolub Автор
    01.10.2021 18:28
    -7

    Кстати забыл написать это в статье но там будет разный endpoints в зависимости есть у вас owner_id в модели или нет


  1. IgorRJ
    01.10.2021 18:47
    +7

    Я уверен что получилось очень поверхностно и скорее всего не заработает с первого раза и вообще я обычно пишу по одной ошибке в строчке.

    Не льстите себе - посчитайте ошибки в этой строчке Вашего текста. Если сможете, конечно.


  1. ertaquo
    01.10.2021 19:24
    +4

    Зачем брать асинхронный фреймворк, но при этом заставлять его работать синхронно? У SQLAlchemy недавно появился асинхронный режим работы, используйте его.


    1. DGolub Автор
      01.10.2021 19:26
      -5

      Как это поможет задача то синхронная?


      1. ertaquo
        01.10.2021 19:31
        +2

        Асинхронный - в смысле, с использованием asyncio.

        Поможет это тем, что приложение не будет впустую ожидать, пока обработчик запроса получает данные из БД, а начнет обрабатывать в это время другой запрос.


        1. DGolub Автор
          01.10.2021 19:35
          -1

          это консольная утилита ты ее запускаешь и ждешь пока выполниться в чем суть оптимизации? Она не обращаться в бд она парисит и генерирует файлы


          1. ertaquo
            01.10.2021 19:45
            +3

            Окей. Какой смысл генерировать синхронно работающие методы при использовании асинхронного фреймворка?


            1. DGolub Автор
              01.10.2021 19:53
              -1

              Потому как не другой задачи кроме того что нужно сгенерировать зачем здесь асинхронщина?


              1. ertaquo
                01.10.2021 20:05
                +4

                Если вам не нужна асинхронщина, зачем вам здесь FastAPI? Flask тогда уж, благо он сихронный и имеет бОльшую популярность.


  1. DGolub Автор
    01.10.2021 20:20
    -6

    Если я использую FastAPI обязательно все должно быть асинхронным? Это ж вроде не JS я тут никому не должен


    1. dimuska139
      02.10.2021 23:33

      Не обязательно всё. Там можно делать синхронные вьюхи, которые запускаются в отдельных потоках (а не асинхронно). Но вообще, если у вас всё синхронное, то смысла использовать FastAPI особо нет. А при чём тут JS?


  1. DGolub Автор
    01.10.2021 20:26
    +1

    Коллеги тут подсказали более элегантное решение https://github.com/awtkns/fastapi-crudrouter


  1. zynaps76
    01.10.2021 23:32
    +7

    Простите, а можно Вас попросить ставить точки и запятые? Хотя бы иногда. :)


  1. eigrad
    02.10.2021 14:23

    Ещё вот такая штука у автора fastapi недавно вышла, с ней стало легче женить модельки с представлением - https://github.com/tiangolo/sqlmodel