Рубрики
Без рубрики

Fastapi: Простая структура приложений с нуля

Как лечить простой проект fastapi с нуля. Помечено Fastapi, Python.

В этом посте в блоге мы создадим простой Fastapi Приложение с нуля. Это может служить хорошей отправной точкой для небольших до средних проектов.

Содержание серии 📖.

  • Часть 1: Укладка фундамента (этот пост)
  • Часть 2 : Миграция
  • Часть 3 : Докереновать

Что мы будем покрывать в этом посте? 📝

  • Генерировать базовый проект с Поэзия Отказ
  • Установить Fastapi. , SQLalchemy и другие зависимости.
  • Создайте необходимые файлы, которые будут служить основой приложения

Прежде чем начать … ⚠ ️

Я собираюсь сделать следующие допущения:

  • что у вас есть базовое понимание Python и Python типы ;
  • Что у вас уже установлено ниже:

Без дальнейшего ADO давайте начнем 🙂

Создать базовый проект с поэзией 🏃

Откройте терминал и введите команду ниже.

poetry new app

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

cd app

Структура каталогов должна выглядеть ниже.

.
├── app
│   └── __init__.py
├── pyproject.toml
├── README.rst
└── tests
    ├── __init__.py
    └── test_app.py

Давайте быстро перейдем на то, что у нас здесь.

приложение Каталог – наш главный пакет Python.

Каталог с __init__.py Файл в нем считается пакет в Python. Любой .py Файлы, которые мы добавляем к этому каталогу, будут рассматриваться модули этого пакета. Обычно этот файл пуст, но в этом случае поэзия прошла впереди и добавила __Version__ Отказ

pyproject.toml Файл – это то, куда будут добавлены все наши зависимости. Позже, когда мы начнем устанавливать наши зависимости, вы заметите Поэзия. Блок Файл будет создан, больше на что позже.

Readme Файл можно использовать для добавления подробной информации о проекте или любых полезных инструкциях, которые помогут другим разработчикам, работающим над проектом. Лично я предпочитаю написание документации в Markdown над реструктуреннымтекстом, поэтому я пойду вперед и переименую Readme.rst к Readme.md.md . Не стесняйтесь делать то же самое или оставьте его как есть.

mv README.rst README.md

Наконец, у нас есть наш тесты каталог, который содержит все тесты на единицу.

Установите fastapi и другие зависимости 📦

В этом разделе мы установим только необходимые зависимости, чтобы получить базовый Crud ( C rete, r EAD, u pdate, d lete) Приложение.

Что мы будем устанавливать?

  • Fastapi – это выходит, не говорящая 🙂
  • SQLalchemy Объект реляционный Mapper (ORM)
  • psycopg2-двоичный PostgreSQL адаптер базы данных
  • Увикурн Молния-быстрый ASGI Server
poetry add fastapi sqlalchemy psycopg2-binary uvicorn

Мы также установим следующие зависимости разработки, в основном для поддержания качества кода и для тестирования.

  • pteest Тестирование Рамки
  • Marpy Статический тип проверки для Python
  • SQLALCHEMY-STUBS Mypy Plub-In и типа заглушки для SQLALCHEMY
  • Flake8 для кода перекликание
  • Autoflake Удаляет неиспользуемый импорт и неиспользуемые переменные
  • Исторт Сортировать импорт заявления
  • черный Формирование кода Python
poetry add -D mypy sqlalchemy-stubs flake8 autoflake isort black 

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

На данный момент ничего не изменилось в нашей структуре каталогов, но вы заметите, что pyproject.toml Файл был обновлен и новый Поэзия. Блок файл был создан. Поэзия. Блок Файл блокирует установленные зависимости к определенной версии. Это особенно полезно, когда множественные разработчики работают над тем же проектом, чтобы убедиться, что все используют одни и те же версии каждого пакета.

Чтобы повторить нашу структуру каталогов должен выглядеть что-то подобное сейчас.

.
├── app
│   └── __init__.py
├── poetry.lock
├── pyproject.toml
├── README.md
└── tests
    ├── __init__.py
    └── test_app.py

Добавить файлы проекта 📄

В этом разделе мы начнем добавить файлы, которые состоят на нашем приложении.

Первый файл, который мы создадим, это main.py Файл, он будет служить точкой входа на наше приложение и дом всех наших маршрутов. Давайте создадим этот файл сейчас под приложение Пакетный каталог.

main.py

приложение/main.py.

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def index():
    return {"message": "Hello world!"}

На данный момент у нас на самом деле есть базовое приложение, которое мы можем запустить. Если мы вернемся к нашему терминалу и запустите следующие команды.

poetry shell
uvicorn app.main:app

Кончик: Если вы хотите, чтобы сервер перезагрузился в файл изменения, вы можете использовать наград Флаг, как так uvicorn app.main: App --reload

Теперь, если мы отправимся в браузер и ударим http://127.0.0.1:8000. Мы будем приветствовать {«сообщение»: «Hello World!»} Отказ Fastapi также дает нам документацию API из коробки, так что если вы сейчас перейдите к http://127.0.0.1:8000/docs. Теперь вы увидите UI Swagger. Довольно удивительно, верно! 🤘

Мы вернемся позже и обновим main.py файл, но на данный момент давайте ударим Ctrl + C. В терминале остановить UVicorn и продолжать добавлять остальные наши файлы.

Далее давайте создадим db.py в том же каталоге. Этот файл будет содержать нашу сессию базы данных и базовый класс, от которого все модели будут простираться.

db.py

приложение/db.py.

from typing import Any

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import as_declarative
from sqlalchemy.orm import sessionmaker

from .config import settings

engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)


@as_declarative()
class Base:
    id: Any

Информация: Вы можете прочитать больше о SessionMaker Функция здесь и as_declarative декоратор здесь Отказ

Возможно, вы заметили, что мы импортируем настройки от конфигурация Но мы еще не создали этот файл еще, так что давайте сделаем это сейчас.

config.py

приложение/config.py.

from typing import Any, Dict, Optional

from pydantic import BaseSettings, PostgresDsn, validator


class Settings(BaseSettings):
    POSTGRES_SERVER: str
    POSTGRES_USER: str
    POSTGRES_PASSWORD: str
    POSTGRES_DB: str

    SQLALCHEMY_DATABASE_URI: Optional[PostgresDsn] = None

    @validator("SQLALCHEMY_DATABASE_URI", pre=True)
    def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
        if isinstance(v, str):
            return v
        return PostgresDsn.build(
            scheme="postgresql",
            user=values.get("POSTGRES_USER"),
            password=values.get("POSTGRES_PASSWORD"),
            host=values.get("POSTGRES_SERVER"),
            path=f"/{values.get('POSTGRES_DB') or  ''}",
        )

    class Config:
        case_sensitive = True
        env_file = ".env"


settings = Settings()

Информация: при загрузке конфигураций из .env Файл . Python-Dotenv Пакет обязателен.

Здесь мы используем Pydantics Управление настройками Отказ По умолчанию Базовые базовы Класс попытается прочитать переменные среды, установленные на уровне системы, используя Os.environ Отказ Однако в нашем случае мы указываем, что мы хотели бы прочитать переменные среды для чтения из .env файл. Pydantic полагается на Python-Dotenv Пакет для достижения этого, давайте добавим его в зависимостью сейчас.

poetry add python-dotenv

И теперь мы создадим .env Файл в корне в каталоге проекта.

.env.env.

.env.env.

# PostgreSQL
POSTGRES_SERVER=localhost
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_DB=app

Обязательно отредактируйте этот файл, чтобы отразить вашу настройку.

.gitignore.

С .env Файл может содержать конфиденциальную информацию, которую мы не хотели бы совершить это для контроля версий. Так что теперь, вероятно, будет хорошее время, чтобы добавить .gitignore Файл на наш проект. Мы скопируем Python .gitignore Шаблон, предоставленный GitHub здесь Отказ .gitignore.

wget https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore
mv Python.gitignore .gitignore

Далее мы создадим Models.py и stchemas.py файл.

models.py

Models.py Файл будет содержать все наши модели, которые простираются от SQLALCHEMY База класс, который мы определены в db.py Мы создадим этот файл сейчас на примере Пользователь модель.

приложение/модели

from uuid import uuid4
from sqlalchemy import Column, String, Text
from sqlalchemy.dialects.postgresql import UUID

from .db import Base


class Post(Base):
    __tablename__ = "posts"

    id = Column(UUID(as_uuid=True), primary_key=True, index=True, default=uuid4)
    title = Column(String)
    body = Column(Text)

schemas.py

Давайте создадим stchemas.py файл сейчас. Этот файл будет содержать все наши Пидантические модели Отказ Под капотом Fastapi использует эти модели для проверки корпуса входящего запроса, анализировать тело ответа и генерировать Автоматические документы для нашего API. Действительно круто, по крайней мере, я так думаю! 👌

Приложение/Schemas.py.

from typing import Optional

from pydantic import BaseModel, UUID4


# Shared properties
class PostBase(BaseModel):
    title: Optional[str] = None
    body: Optional[str] = None


# Properties to receive via API on creation
class PostCreate(PostBase):
    title: str
    body: str


# Properties to receive via API on update
class PostUpdate(PostBase):
    pass


class PostInDBBase(PostBase):
    id: Optional[UUID4] = None

    class Config:
        orm_mode = True


# Additional properties to return via API
class Post(PostInDBBase):
    pass


# Additional properties stored in DB
class PostInDB(PostInDBBase):
    pass

Последний файл, который мы создадим сейчас, это Действия .py файл. Этот файл будет содержать все наши случаи или действия или действия, которые будут выполнены, такие как операции CRUD.

actions.py

Приложение/Action.py.

from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union

from fastapi.encoders import jsonable_encoder
from pydantic import UUID4, BaseModel
from sqlalchemy.orm import Session

from . import schemas
from .db import Base
from .models import Post

# Define custom types for SQLAlchemy model, and Pydantic schemas
ModelType = TypeVar("ModelType", bound=Base)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)


class BaseActions(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
    def __init__(self, model: Type[ModelType]):
        """Base class that can be extend by other action classes.
           Provides basic CRUD and listing operations.

        :param model: The SQLAlchemy model
        :type model: Type[ModelType]
        """
        self.model = model

    def get_all(
        self, db: Session, *, skip: int = 0, limit: int = 100
    ) -> List[ModelType]:
        return db.query(self.model).offset(skip).limit(limit).all()

    def get(self, db: Session, id: UUID4) -> Optional[ModelType]:
        return db.query(self.model).filter(self.model.id == id).first()

    def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType:
        obj_in_data = jsonable_encoder(obj_in)
        db_obj = self.model(**obj_in_data)  # type: ignore
        db.add(db_obj)
        db.commit()
        db.refresh(db_obj)
        return db_obj

    def update(
        self,
        db: Session,
        *,
        db_obj: ModelType,
        obj_in: Union[UpdateSchemaType, Dict[str, Any]]
    ) -> ModelType:
        obj_data = jsonable_encoder(db_obj)
        if isinstance(obj_in, dict):
            update_data = obj_in
        else:
            update_data = obj_in.dict(exclude_unset=True)
        for field in obj_data:
            if field in update_data:
                setattr(db_obj, field, update_data[field])
        db.add(db_obj)
        db.commit()
        db.refresh(db_obj)
        return db_obj

    def remove(self, db: Session, *, id: UUID4) -> ModelType:
        obj = db.query(self.model).get(id)
        db.delete(obj)
        db.commit()
        return obj


class PostActions(BaseActions[Post, schemas.PostCreate, schemas.PostUpdate]):
    """Post actions with basic CRUD operations"""

    pass


post = PostActions(Post)

Перед возвращением и обновлением нашего main.py Файл, давайте просмотрим нашу конечную структуру каталогов.

.
├── app
│   ├── actions.py
│   ├── config.py
│   ├── __init__.py
│   ├── main.py
│   ├── models.py
│   └── schemas.py
├── poetry.lock
├── pyproject.toml
├── README.md
└── tests
    ├── __init__.py
    └── test_app.py

Давайте обновим наше main.py Файл сейчас и подключите все точки.

Обновить main.py.

приложение/main.py.

from typing import Any, List

from fastapi import Depends, FastAPI, HTTPException
from pydantic import UUID4
from sqlalchemy.orm import Session
from starlette.status import HTTP_201_CREATED, HTTP_404_NOT_FOUND

from . import actions, models, schemas
from .db import SessionLocal, engine

# Create all tables in the database.
# Comment this out if you using migrations.
models.Base.metadata.create_all(bind=engine)

app = FastAPI()


# Dependency to get DB session.
def get_db():
    try:
        db = SessionLocal()
        yield db
    finally:
        db.close()


@app.get("/")
def index():
    return {"message": "Hello world!"}


@app.get("/posts", response_model=List[schemas.Post], tags=["posts"])
def list_posts(db: Session = Depends(get_db), skip: int = 0, limit: int = 100) -> Any:
    posts = actions.post.get_all(db=db, skip=skip, limit=limit)
    return posts


@app.post(
    "/posts", response_model=schemas.Post, status_code=HTTP_201_CREATED, tags=["posts"]
)
def create_post(*, db: Session = Depends(get_db), post_in: schemas.PostCreate) -> Any:
    post = actions.post.create(db=db, obj_in=post_in)
    return post


@app.put(
    "/posts/{id}",
    response_model=schemas.Post,
    responses={HTTP_404_NOT_FOUND: {"model": schemas.HTTPError}},
    tags=["posts"],
)
def update_post(
    *, db: Session = Depends(get_db), id: UUID4, post_in: schemas.PostUpdate,
) -> Any:
    post = actions.post.get(db=db, id=id)
    if not post:
        raise HTTPException(status_code=HTTP_404_NOT_FOUND, detail="Post not found")
    post = actions.post.update(db=db, db_obj=post, obj_in=post_in)
    return post


@app.get(
    "/posts/{id}",
    response_model=schemas.Post,
    responses={HTTP_404_NOT_FOUND: {"model": schemas.HTTPError}},
    tags=["posts"],
)
def get_post(*, db: Session = Depends(get_db), id: UUID4) -> Any:
    post = actions.post.get(db=db, id=id)
    if not post:
        raise HTTPException(status_code=HTTP_404_NOT_FOUND, detail="Post not found")
    return post


@app.delete(
    "/posts/{id}",
    response_model=schemas.Post,
    responses={HTTP_404_NOT_FOUND: {"model": schemas.HTTPError}},
    tags=["posts"],
)
def delete_post(*, db: Session = Depends(get_db), id: UUID4) -> Any:
    post = actions.post.get(db=db, id=id)
    if not post:
        raise HTTPException(status_code=HTTP_404_NOT_FOUND, detail="Post not found")
    post = actions.post.remove(db=db, id=id)
    return post

Наконец, если мы снова запустим сервер и нажмите http://127.0.0.1:8000/docs. Теперь у нас есть основные API, которые могут выполнять операции CRUD на нашей почве. 🚀

uvicorn app.main:app

Заключение 💡.

Если вы сделали это далеко, хорошо сделано! 👍.

Мы создали простое приложение, которое может служить хорошей отправной точкой для небольших до средних проектов. Есть еще ряд вещей, которые мы можем включить в этот базовый проект, такой как миграция или добавление докера в наш стек. ( Подсказка: мы осмотрим это в будущих постах, оставайтесь на улице 😉 )

Последний код для этого поста можно найти на Github Отказ

Если вам понравилось прочитать эту статью и хотел бы оставаться настроенными для дальше, или просто хочу подключиться, следуйте за мной в Twitter @alexvanzyl Отказ

Оригинал: “https://dev.to/alexvanzyl/fastapi-simple-application-structure-from-scratch-2mem”