DevGang
Авторизоваться

Обновление API Streamfinity — использование Python и FastAPI

Узнайте, как стриминговая компания заново изобретает свой API с помощью скорости и гибкости FastAPI.

В бурлящем мире цифрового потокового вещания восходящая звезда Streamfinity* стремилась оставить свой след благодаря обширной библиотеке фильмов, исключительному сервису и ультрасовременному предложению по управлению цифровыми правами (DRM).

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

Однако по мере расширения компании становилось все труднее поддерживать ее устаревшее решение, основанное на ASP.NET Веб-формы. Технологический ландшафт изменился, и внешние компании требовали интерфейса к сокровищнице данных Streamfinity.

Streamfinity нуждался в бесшовном REST API для адаптации и процветания, позволяющем этим внешним партнерам без особых усилий интегрировать свои видеосервисы в свои веб-сайты и мобильные приложения.

Между тем, за последние пару лет к компании присоединились несколько разработчиков с обширным опытом работы на Python, готовых принять вызов и помочь продвинуть Streamfinity на новые высоты.

Именно тогда вмешался технический директор Харпер, дальновидный лидер. Она смело решила воспользоваться возможностью и принять революцию в области Python.

Харпер поручил энергичной команде разработчиков создать прототип REST API с использованием Python и FastAP I — современного высокопроизводительного веб-фреймворка, предназначенного для быстрого создания API.

Команда разработчиков была вне себя от радости и взволнована тем, что отправилась в путешествие по улучшению сервиса Streamfinity и расширению его охвата и влияния на рынок цифрового потокового вещания.

Присоединяйтесь к нам в этой увлекательной истории, когда мы исследуем трансформацию Streamfinity, погружаемся в мир Python и FastAPI и становимся свидетелями создания API, который навсегда изменит судьбу компании под руководством Харпера.

Не стесняйтесь ознакомиться с прототипом API Streamfinity в этом репозитории GitHub.

* Пожалуйста, обратите внимание, что Streamfinity - вымышленная компания, созданная для этой статьи, чтобы проиллюстрировать практическое применение FastAPI в реальном контексте.

Разработка REST API для Streamfinity

Команда разработчиков Streamfinity собралась на совместную сессию, известную как "Волчья стая". На собраниях такого типа команда разработчиков собирается в одной комнате, коллективно работая над достижением общей цели.

Основной целью этой первоначальной встречи было изучение дизайна основных аспектов REST API Streamfinity. Харпер, дальновидный технический директор, дал несколько ценных указаний команде.

Она подчеркнула важность соблюдения принципов REST, приведения их в соответствие с потребностями и ожиданиями их внешних партнеров. Кроме того, Харпер посоветовал команде сосредоточиться на основных функциональных возможностях, а не пытаться создать комплексное решение.

Кроме того, Харпер подчеркнул необходимость включения аутентификации в прототип, гарантируя, что только авторизованные пользователи и партнеры смогут получить доступ к критически важным функциям API.

Руководствуясь этими рекомендациями, команда разработчиков с энтузиазмом приступила к преобразованию API Streamfinity с использованием Python и FastAPI.

Понимание принципов проектирования API и лучших практик

Команда Streamfinity начала свой путь с определения принципов разработки своего REST API. Опираясь на знания и опыт членов команды, которые ранее работали с REST API, они совместно сформулировали следующие руководящие принципы для обеспечения надежного и удобного дизайна.

  • Используйте стандартные методы HTTP — используйте стандартные методы HTTP (GET, POST, PUT, DELETE и т. д.) для операций CRUD (создание, чтение, обновление, удаление), обеспечивая согласованность и простоту использования для потребителей API.
  • URL-адреса на основе ресурсов во множественном числе — создавайте конечные точки API с четкими URL-адресами на основе ресурсов, представляющими определенные объекты или коллекции с использованием множественного числа (например, /movies, /actors, /subscriptions).
  • Используйте правильные коды состояния — возвращайте соответствующие коды состояния HTTP, чтобы указать результат запроса API (например, 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 404 Not Found).
  • Разбиение на страницы и фильтрация. Включите возможности разбиения на страницы и фильтрации для обработки больших наборов данных и предоставления пользователям возможности эффективного извлечения определенных подмножеств данных.
  • JSON в качестве основного формата данных. Используйте JSON (обозначение объектов JavaScript) в качестве основного формата данных для запросов и ответов API, поскольку он легкий, удобный для чтения и широко поддерживается.
  • Четкая и исчерпывающая документация. Предоставляйте исчерпывающую и доступную документацию для пользователей API, включая объяснения доступных конечных точек, форматов данных и примеры использования.

Ключевые конечные точки API для потоковой передачи и DRM-сервисов

Используя свои четко определенные принципы проектирования API, команда Streamfinity описала набор основных конечных точек REST. Эти конечные точки заложили бы основу для API Streamfinity, обеспечив надежную и эффективную платформу для поддержки их сервисов.

Команда разработала следующие основные конечные точки:

  • /movies

GET: Извлеките список фильмов с дополнительной фильтрацией, сортировкой и разбивкой по страницам.

POST: Добавить новый фильм в базу данных.

  • /movies/{movie_id}

GET: Извлеките подробную информацию о конкретном фильме по его идентификатору.

PUT: Обновите сведения о конкретном фильме по его идентификатору.

DELETE: Удалите определенный фильм из базы данных по его идентификатору.

  • /actors

GET: Извлеките список участников с необязательной фильтрацией, сортировкой и разбивкой на страницы.

POST: Добавьте нового субъекта в базу данных.

  • /actors/{actor_id}

GET: Извлеките сведения о конкретном субъекте по его идентификатору.

PUT: Обновите сведения о конкретном субъекте по его идентификатору.

DELETE: Удалите конкретного субъекта из базы данных по его идентификатору.

  • /api/movies/{movie_id}/actors/{actor_id}

POST: добавить актера в фильм

DELETE: удалить актера из фильма

  • /users

POST: Зарегистрируйте нового пользователя в системе.

  • /users/{user_id}

GET: Извлеките сведения о конкретном пользователе по его идентификатору.

PUT: Обновите сведения о конкретном пользователе по его идентификатору.

DELETE: Удалите определенного пользователя из базы данных по его идентификатору.

  • /subscriptions

GET: Получите список доступных планов подписки.

POST: Добавьте новый тарифный план подписки в базу данных.

  • /subscriptions/{subscription_id}

GET: Получите подробную информацию о конкретном плане подписки по его идентификатору.

PUT: Обновите сведения о конкретном плане подписки по его идентификатору.

DELETE: Удалите подписку из базы данных по ее идентификатору.

  • /users/{user_id}/subscriptions

GET: Извлеките список активных подписок пользователя по идентификатору пользователя.

POST: Добавьте новую подписку для конкретного пользователя по его идентификатору.

  • /users/{user_id}/subscriptions/{subscription_id}

PUT: Обновите сведения о подписке конкретного пользователя с помощью идентификатора пользователя и идентификатора подписки.

DELETE: Отмените подписку конкретного пользователя по его идентификатору и идентификатору подписки.

  • /token

POST: Аутентифицируйте пользователя и сгенерируйте токен доступа.

Настройка среды разработки

Прежде чем команда разработчиков смогла разработать прототип, они должны были настроить проект на Python.

Чтобы упростить процесс настройки среды разработки для проекта Streamfinity, Девон Пауэрс, инженер команды DevOps, предложил использовать Poetry.

Девон был не только опытным разработчиком Python, но и сосредоточился на оптимизации процессов сборки и развертывания Streamfinity.

Poetry - это мощный инструмент управления зависимостями для Python, который упрощает управление пакетами и обработку виртуальной среды. Используя Poetry, команда разработчиков могла быстро погрузиться в проект без необходимости вручную настраивать параметры или управлять зависимостями.

Такой подход позволил команде сосредоточиться на разработке функций и выпуске высококачественного продукта.

Девон выполнил следующие шаги для создания проекта Stream finity с использованием Poetry.

  1. Установка Poetry: Прежде чем команда разработчиков смогла начать использовать Poetry, им необходимо было установить ее. Они следовали инструкциям по установке, приведенным в официальной документации Poetry.
  2. Создайте новый проект Poetry: Эйвери создал новый проект Poetry для Streamfinity, запустив команду poetry new streamfinity-fastapi. Эта команда создала новый каталог с именем streamfinity-fastapi с базовой структурой проекта и необходимыми файлами, включая pyproject.toml, файл конфигурации Poetry.
  3. Добавление зависимостей: чтобы управлять зависимостями проекта, Девон добавил их в файл pyproject.toml. Например, чтобы добавить FastAPI и SQLModel, он использовал следующие команды: poetry add fastapi и poetry add sqlmodel.
  4. Инициализировать виртуальную среду: чтобы создать изолированную виртуальную среду для проекта, команда разработчиков выполнила следующую команду в корневом каталоге проекта: poetry install. Эта команда установила все зависимости, указанные в файле pyproject.toml, в виртуальную среду.

Настроив среду разработки с использованием Poetry, Девон упростил команде разработчиков работу над проектом Streamfinity. Теперь команда могла сосредоточиться на внедрении функций без управления зависимостями или ручной настройки параметров.

Новые разработчики, присоединяющиеся к команде, могут быстро приступить к работе, клонировав репозиторий Streamfinity FastAPI и установив необходимые зависимости с помощью Poetry.

После настройки своей локальной среды разработки новый разработчик может погрузиться в существующую кодовую базу, ознакомиться с документацией по API и внести свой вклад в проект, решая открытые проблемы, внедряя новые функции или расширяя существующую функциональность.

Создание моделей базы данных с помощью SQLModel

Пока команда Streamfinity углублялась в создание моделей баз данных, Дариус Найт, опытный разработчик с опытом работы в SQLAlchemy, взял на себя инициативу в разработке моделей Actor и Movie.

Он прекрасно понимал, что отношения «многие ко многим» между этими сущностями были одним из наиболее сложных аспектов базовой библиотеки.

Дариус решил использовать SQLModel, мощный инструмент, основанный на SQLAlchemy, который легко интегрируется с FastAPI.

Оба фреймворка были созданы одним и тем же разработчиком, Себастьяном Рамиресом. SQLModel использует аннотации типа Python, Pydantic и SQLAlchemy для обеспечения эффективной и выразительной работы.

Хотя существующие модели фильмов и актеров Streamfinity были более всеобъемлющими, команда стремилась создать упрощенную, но репрезентативную версию моделей актеров и фильмов для своего прототипа.

Чтобы ускорить процесс разработки и избежать необходимости настройки серверов баз данных, команда решила использовать SQLite в качестве базы данных для своего прототипа.

Модели Actor и Movie

Дариус разработал подход к проектированию для классов MovieInput и Movie, который он представил команде. Он рекомендовал разделить каждый класс модели на отдельные входные и выходные классы.

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

Обсудив предложение Дариуса, команда приняла его стратегию дизайна. Дариус приступил к созданию классов MovieInput и Movie, как показано ниже.

Важно отметить, что модель использует подсказки типов, чтобы облегчить автоматическую проверку модели в FastAPI позже. Однако текущий дизайн еще не включает связь с моделью Actor.

class MovieInput(SQLModel):
    title: str
    length: int
    synopsis: str
    release_year: int
    director: str
    genre: str
    rating: int | None = None

class Movie(MovieInput, table=True):
    id: int | None = Field(primary_key=True, default=None)

Кроме того, Дариус разработал модель Actor, которую можно увидеть ниже.

class ActorInput(SQLModel):
    first_name: str
    last_name: str
    date_of_birth: date
    nationality: str
    biography: str | None = None
    profile_picture_url: str | None = None

class Actor(ActorInput, table=True):
    id: int | None = Field(primary_key=True, default=None)

Подключение Movie к модели Actor

Пришло время определить взаимосвязь между моделями Movie и Actor. Это включало создание отношения «многие ко многим» (N:M), поскольку в фильме может участвовать несколько актеров, а актер может появляться в разных фильмах.

Дариус знал, что им необходимо представить таблицу или модель связывания, которая соединяет модели Movie и Actor, чтобы установить отношения «многие ко многим». Эта связывающая таблица также является моделью SQLModel, как и другие модели. Они решили назвать его MovieActorLink, как показано ниже.

Модель MovieActorLink содержит только два поля внешнего ключа, которые ссылаются на первичные ключи моделей Movie и Actor.

class MovieActorLink(SQLModel, table=True):
    movie_id: int = Field(foreign_key="movie.id",
                          primary_key=True, default=None)
    actor_id: int = Field(foreign_key="actor.id",
                          primary_key=True, default=None)

Пришло время настроить извлечение фильма со всеми связанными с ним актерами или всех фильмов актера из базы данных. Для этого модель Movie должна включать список актеров, а модель Actor — список фильмов.

Обе модели используют класс Relationship и атрибут back_populates, ссылаясь на MovieActorLink, как показано ниже. Эта конфигурация позволяет легко извлекать связанные данные между моделями Movie и Actor.

class Movie(MovieInput, table=True):
    id: int | None = Field(primary_key=True, default=None)
    actors: list["Actor"] = Relationship(back_populates="movies",
                                         link_model=MovieActorLink)

class Actor(ActorInput, table=True):
    id: int | None = Field(primary_key=True, default=None)
    movies: list["Movie"] = Relationship(back_populates="actors",
                                         link_model=MovieActorLink)

Команда была в восторге от простоты и эффективности использования SQLModel для создания и настройки отношений между их моделями. В кратчайшие сроки они создали все необходимые классы моделей (Movie, Actor, User, Subscription).

Посетите этот репозиторий GitHub, чтобы увидеть полный набор классов моделей, разработанных командой.

Создание REST API с помощью FastAPI

Эвери Маверик, опытный разработчик, ответственный за разработку большей части REST API, очень хотела взять на себя управление его реализацией с помощью FastAPI. Команда тщательно разработала свои модели баз данных, и Эвери должна была продемонстрировать свой опыт.

Подключение к базе данных

Ее первой задачей было установить подключение к базе данных SQLite. Чтобы достичь этого, Эвери создала метод get_session, который будет объединен с классом Depends от FastAPI для внедрения зависимостей.

Вот фрагмент кода для подключения к базе данных и метода get_session. Эвери использовала функцию генератора с контекстным менеджером, чтобы гарантировать, что соединения с базой данных были правильно закрыты после использования.

engine = create_engine(
    "sqlite:///./db/streamfinity.db",
    echo=True,
    connect_args={"check_same_thread": False}
)

def get_session() -> Generator[Session, Any, None]:
    with Session(engine) as session:
        yield session

Первый маршрут, получить актера по id

Создав соединение, Эвери сосредоточилась на структурировании API с помощью класса APIRouter FastAPI. Разделив каждую часть API на разные файлы, она создала чистую и организованную кодовую базу, которую будет легко поддерживать и расширять в будущем.

Эвери понимала основные функции FastAPI, такие как автоматическая проверка, документирование и внедрение зависимостей, и знала, что использование этих функций упростит процесс разработки и приведет к созданию высокопроизводительного и надежного API.

Теперь Эвери была готова воплотить API в жизнь. Она начала с простого маршрута для получения одного Actor по его идентификатору. Этот маршрут состоял из нескольких шагов.

Во-первых, она добавила декоратор @router.get("/{actor_id}"), указывающий, что эта функция представляет метод GET и захватывает единственный параметр пути. Это позволило API обрабатывать запросы для конкретных участников на основе их идентификаторов.

Затем она внедрила сеанс SQLModel, используя функцию get_session. Чтение actor из базы данных было легко выполнено с помощью функции session.get(Actor, act_id).

router = APIRouter(prefix="/api/actors")

@router.get("/{actor_id}")
def get_actor(actor_id: int,
              session: Session = Depends(get_session)) -> Actor:
    actor: Actor | None = session.get(Actor, actor_id)
    if actor:
        return actor

    raise HTTPException(status_code=404, detail=f"Actor with id={actor_id} not found")

Поиск актеров

Следующей задачей Эвери было создание маршрута для поиска актеров на основе их атрибутов. Она разработала приведенный ниже маршрут, в котором использовались необязательные параметры запроса для фильтрации актеров по таким признакам, как name, birthdate и nationality.

Соответствующий фильтр будет применен к запросу с использованием метода where, если какой-либо из этих параметров будет предоставлен. Сессия SQLModel снова была введена с помощью функции get_session.

Как указано в разделе «Ключевые конечные точки API для служб потоковой передачи и DRM», конечная точка должна поддерживать функции сортировки и разбиения на страницы. Для этого Эвери добавила параметры запроса skip и limit для реализации нумерации страниц.

Это позволило пользователям контролировать количество возвращаемых записей и начальную точку в наборе данных. Кроме того, она ввела параметр запроса order для облегчения сортировки, позволяющий пользователям указывать предпочтительный порядок сортировки результатов.

Как только запрос был создан на основе предоставленных параметров, он был выполнен для получения отфильтрованного списка участников. Маршрут вернул этот список, облегчив пользователям поиск актеров на основе определенных критериев.

@router.get("/")
def get_actors(name: str | None = Query(None),
               birthdate: date | None = Query(None),
               nationality: str | None = Query(None),
               skip: int = Query(0, description="The number of records to skip"),
               limit: int = Query(10, description="The maximum nr of records to get"),
               sort: str = Query("id", description="The field to sort the results by"),
               order: str = Query("asc", description="The sort order: 'asc' or 'desc'"),
               session: Session = Depends(get_session)) -> list[Actor]:

    query = select(Actor)

    if name:
        query = query.where(Actor.last_name == name)
    if birthdate:
        query = query.where(Actor.date_of_birth == birthdate)
    if nationality:
        query = query.where(Actor.nationality == nationality)

    # Sorting
    if order.lower() == "desc":
        query = query.order_by(getattr(Actor, sort).desc())
    else:
        query = query.order_by(getattr(Actor, sort))

    # Pagination
    query = query.offset(skip).limit(limit)

    return session.exec(query).all()

Добавление нового актера

Эвери реализовала маршрут для добавления нового актера в базу данных с помощью метода POST. Она разработала маршрут, как показано ниже.

В этом маршруте Эвери использовала декоратор @router.post("/"), чтобы указать, что эта функция представляет метод POST для добавления нового актера. Для параметра response_model была задана модель Актера, что гарантирует автоматическую проверку и сериализацию ответа на основе определения модели.

@router.post("/", response_model=Actor)
def add_actor(actor_input: ActorInput,
              session: Session = Depends(get_session)) -> Actor:
    new_actor: Actor = Actor.from_orm(actor_input)
    session.add(new_actor)
    session.commit()
    session.refresh(new_actor)
    return new_actor

Функция add_actor принимает параметр act_input для ActorInput, представляющий данные нового актера. SQLModel Session снова была введена с помощью функции get_session.

Эвери создала новый экземпляр Actor внутри функции, вызвав Actor.from_orm(actor_input). Это позволило ей преобразовать входные данные в экземпляр модели Actor. Затем она добавила в сеанс new_actor, зафиксировала транзакцию для сохранения данных в базе данных и обновила экземпляр new_actor для получения любых сгенерированных полей, таких как первичные ключи.

Наконец, вновь созданный актер был возвращен в качестве ответа, что позволило пользователям добавлять новых актеров в базу данных через API.

Удаление существующего актера

Затем Эвери реализовала маршрут для удаления субъекта из базы данных. Для достижения этой цели он создал метод DELETE, чтобы удалить указанного актера на основе его идентификатора. Она выполнила следующие действия:

  1. Добавлен декоратор @router.delete("/{actor_id}", status_code=204), указывающий, что эта функция представляет метод DELETE, извлекает единственный параметр пути (actor_id) и возвращает код состояния 204 (No Content) на успешное удаление.
  2. SQLModel Session был снова введен с помощью функции get_session.
  3. Она использовала метод session.get для извлечения Actor на основе предоставленного actor_id.
  4. Если актор был найден, использовался метод session.delete для удаления актора из базы данных, а для сохранения изменений вызывался метод session.commit.
  5. Если actor не был найден, будет отправлен код состояния HTTP 404, указывающий, что действующее лицо не может быть обнаружено.
@router.delete("/{actor_id}", status_code=204)
def delete_actor(actor_id: int,
                 session: Session = Depends(get_session)) -> None:
    actor: Actor | None = session.get(Actor, actor_id)
    if actor:
        session.delete(actor)
        session.commit()
    else:
        raise HTTPException(status_code=404, detail=f"Actor with id={actor_id} not found")

Обновление актера

Наконец, Эвери реализовала маршрут для обновления существующего актера в базе данных. Она создала метод PUT для обновления указанного актера на основе его идентификатора и предоставленных данных. Для этого она выполнила следующие шаги:

  1. Добавлен декоратор @router.put("/{actor_id}", response_model=Actor), указывающий, что эта функция представляет метод PUT, извлекает один параметр пути (actor_id) и возвращает обновленные данные актера в ответе.
  2. Она еще раз внедрила SQLModel Session, используя функцию get_session.
  3. Мы использовали метод session.get для получения Актера на основе предоставленного Actor_id.
  4. Если актер был найден, она обновляла атрибуты актера новыми данными из предоставленного экземпляра ActorInput и вызывала метод session.commit для сохранения изменений.
  5. Если actor не был найден, будет отправлен код состояния HTTP 404, указывающий, что действующее лицо не может быть обнаружено.

Вот реализованный маршрут:

@router.put("/{actor_id}", response_model=Actor)
def update_actor(actor_id: int, new_actor: ActorInput,
                 session: Session = Depends(get_session)) -> Actor:
    actor: Actor | None = session.get(Actor, actor_id)
    if actor:
        for field, value in new_actor.dict().items():
            if value is not None:
                setattr(actor, field, value)
        session.commit()
        return actor
    else:
        raise HTTPException(status_code=404, detail=f"Actor with id={actor_id} not found")

Имея такой маршрут, команда могла бы легко обновлять сведения об участниках в базе данных по мере необходимости. Маршрут эффективно обработал процесс обновления и вернул обновленные данные участника в ответе, гарантируя, что API остается удобным для пользователя и с ним легко работать.

Реализация оставшихся конечных точек и маршрутов

Эвери представила реализованные конечные точки и маршруты остальной команде разработчиков, продемонстрировав, насколько простым был процесс с использованием FastAPI. Команда была очень впечатлена результатами и простотой реализации.

Имея четкое представление о процессе, команда разделила оставшиеся конечные точки между собой. Они реализовали все необходимые маршруты и конечные точки всего за несколько часов, чтобы сделать свое приложение полностью функциональным.

Чтобы изучить все остальные маршруты, посетите папку «маршрутизаторы» в этом репозитории GitHub. Здесь вы найдете все конечные точки прототипа команды.

Документация API

Согласно указаниям Харпера и изложенным в рекомендациях, REST API Streamfinity должен предоставлять исчерпывающую и удобную документацию для пользователей API. Эта документация должна включать объяснения доступных конечных точек, форматов данных и примеры использования.

Лукасу Дюбуа было поручено изучить функциональность документации FastAPI. Он провел демонстрацию, чтобы продемонстрировать следующие аспекты:

FastAPI автоматически создает документацию для ваших конечных точек API, облегчая разработчикам понимание доступных операций и их использования. Запустив проект FastAPI и получив доступ к конечной точке /docs, вы можете просмотреть автоматически сгенерированную документацию, которая предлагает обзор конечных точек API, их поддерживаемых методов HTTP, параметров запроса, кодов состояния ответа и строк документации из вашего кода.

Документация, созданная FastAPI, изображение источника
Документация, созданная FastAPI, изображение источника

Страница документации позволяет напрямую взаимодействовать с конечными точками API, нажимая «Try it out» и выполняя запросы. Эта функция устраняет необходимость в отдельном клиенте, упрощая процесс тестирования.

FastAPI создает эту документацию на основе спецификации OpenAPI, широко распространенного стандарта для описания RESTful API. Посетив конечную точку /openapi.json, вы можете получить доступ к машиночитаемому документу JSON спецификации OpenAPI. Этот документ может служить различным целям, таким как создание клиентского кода или его интеграция с другими инструментами.

Кроме того, FastAPI предоставляет альтернативный формат документации в конечной точке /redoc. Эта страница содержит ту же информацию, что и конечная точка /docs, но представляет ее в другом формате и включает ссылку на документ JSON спецификации OpenAPI.

Лукас продемонстрировал, что ссылку на конечную точку openapi.json можно легко скопировать и импортировать непосредственно в Postman — инструмент, о котором команда разработчиков уже знала.

Команда встретила эту функцию с большим энтузиазмом, поскольку она устраняла необходимость в написании дополнительной документации - задача, которая особенно нравилась лишь некоторым разработчикам.

Импорт openapi.json из документации FastAPI в Postman
Импорт openapi.json из документации FastAPI в Postman

Защита API

Как вы, возможно, помните из введения, Харпер подчеркнул важность включения аутентификации в прототип, чтобы гарантировать, что только авторизованные пользователи и партнеры могут получить доступ к критически важным функциям API. Эвери решила использовать встроенную в FastAPI функциональность OAuth2 для выполнения этого требования.

Эвери использовала класс OAuth2PasswordBearer, предоставляемый FastAPI, упрощая настройку потока аутентификации OAuth2 за счет обработки извлечения токена доступа и проверки формата. Это позволило Эвери сосредоточиться на реализации желаемого механизма аутентификации, не увязая в базовых деталях.

Чтобы еще больше упростить интеграцию аутентификации в API, Эвери воспользовалась преимуществами системы внедрения зависимостей FastAPI. Создав функцию зависимости с помощью OAuth2PasswordBearer, Эвери может легко включить аутентификацию в любой конечной точке, просто добавив функцию в качестве параметра.

Во-первых, она позаботилась о том, чтобы при добавлении нового пользователя заданный пароль перед сохранением в базе данных хешировался.

@router.post("/", response_model=User, status_code=201)
def add_user(user_input: UserInput, session: Session = Depends(get_session)) -> User:
    hashed_password = get_password_hash(user_input.password)
    user_input.password = hashed_password

    new_user: User = User.from_orm(user_input)
    session.add(new_user)
    session.commit()
    session.refresh(new_user)
    return new_user

Функция get_password_hash использует помощник CryptContext из библиотеки passlib для генерации хэша для пароля.

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def get_password_hash(password: str):
    return pwd_context.hash(password)

Затем она добавила конечную точку токена, чтобы пользователи API могли получить токен JWT перед доступом к ограниченной конечной точке.

@router.post("/token", response_model=Token)
def login_for_access_token(
    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
    session: Session = Depends(get_session)
):
    query = select(User).where(User.email == form_data.username)
    user: User | None = session.exec(query).first()

    if user is None:
        raise_401_exception()

    assert user is not None

    if not verify_password(form_data.password, user.password):
        raise_401_exception()

    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(data={"sub": user.email},
                                       expires_delta=access_token_expires)

    return {"access_token": access_token, "token_type": "bearer"}

def raise_401_exception():
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Incorrect username or password",
        headers={"WWW-Authenticate": "Bearer"},
    )

Наконец, Эвери интегрировала требование аутентификации в необходимые конечные точки, гарантируя, что только авторизованные пользователи с действительными токенами могут получить доступ к ограниченным ресурсам. Например, она добавила аутентификацию в конечную точку AddActor.

Это было достигнуто за счет реализации новой функции get_current_user и внедрения ее в функцию add_actor с использованием системы внедрения зависимостей FastAPI. Это гарантирует, что конечная точка доступна только для аутентифицированных пользователей с действительным токеном, что повышает безопасность API.

@router.post("/", response_model=Actor, status_code=201)
def add_actor(actor_input: ActorInput,
              current_user: User = Depends(get_current_user),
              session: Session = Depends(get_session)) -> Actor:
    new_actor: Actor = Actor.from_orm(actor_input)
    session.add(new_actor)
    session.commit()
    session.refresh(new_actor)
    return new_actor

Функция get_current_user берет токен доступа из заголовка запроса, декодирует и проверяет его с помощью функции jwt.decode из библиотеки jose.

Если токен действителен, он извлекает идентификатор пользователя (например, имя пользователя или адрес электронной почты) из полезной нагрузки токена и извлекает информацию о пользователе из базы данных или другого источника данных. Если токен недействителен или пользователь не найден, возникает неавторизованное исключение HTTP 401.

def get_current_user(token: str = Depends(oauth2_scheme),
                     session: Session = Depends(get_session)) -> User:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str | None = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
                                detail="Could not validate credentials")
    except JWTError:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
                            detail="Could not validate credentials")

    query = select(User).where(User.email == username)
    user: User | None = session.exec(query).first()
    if user is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                            detail="User not found")

    return user

Эвери завершила свою презентацию перед командой разработчиков, продемонстрировав реализованные функции аутентификации и объяснив, как она интегрировала их в приложение. Она также отметила, что, хотя ей понравилась общая интеграция, она не была полностью удовлетворена ручным кодированием и декодированием токенов JWT.

В ходе своего исследования Эвери обнаружила несколько внешних библиотек и плагинов, таких как fastapi-jwt-auth, которые могут упростить процесс управления токенами JWT. Эти библиотеки предлагают дополнительные инструменты и декораторы для упрощения работы с JWT, делая процесс более удобным и менее подверженным ошибкам.

Однако из-за нехватки времени у Эвери не было возможности протестировать и интегрировать эти внешние плагины в текущую реализацию. Она предложила команде разработчиков изучить эти варианты, чтобы еще больше оптимизировать процесс аутентификации и сделать его более эффективным.

Написание тестовых случаев для конечных точек API Streamfinity.

По мере разработки Streamfinity API вся команда знала, что важно обеспечить надлежащую функциональность и надежность. Для этого они решили работать вместе, чтобы написать тестовые примеры для всех конечных точек API.

Команда использовала встроенный в FastAPI TestClient для создания своих тестовых случаев. Этот мощный инструмент позволил им написать тестовые функции для каждой конечной точки, охватывающие различные сценарии. Они тщательно протестировали такие действия, как создание, извлечение, обновление и удаление ресурсов, а также проверку аутентификации и авторизации.

Вот пример теста для конечной точки add_actor:

from fastapi.testclient import TestClient

client = TestClient(app)

def test_add_actor():
    actor_data = test_actor.dict()
    actor_data["date_of_birth"] = test_actor.date_of_birth.isoformat()
    response = client.post("/api/actors/", json=actor_data,
                           headers={"Authorization": f"Bearer {access_token}"})
    assert response.status_code == status.HTTP_201_CREATED
    assert response.json()["first_name"] == test_actor.first_name
    assert response.json()["last_name"] == test_actor.last_name

Использование TestClient позволило команде эффективно работать вместе и тщательно протестировать каждый аспект API. Этот совместный подход упростил выявление потенциальных проблем и позволил решить их до развертывания, что привело к созданию более надежного и надежного API.

Все тесты, созданные командой, можно найти в репозитории GitHub прототипа Streamfinity.

Демонстрация продукта

После напряженной недели быстрой разработки команда была готова представить прототип REST API Streamfinity Харперу, техническому директору компании. Демонстрация стала важной вехой и стала кульминацией усилий команды.

Команда разработчиков искусно использовала Postman, популярный клиент API, для демонстрации различных конечных точек API. Презентация была посвящена не только функциям, но и продемонстрировала простоту использования и универсальность API. Харперу показали, как можно запускать различные функции и как можно манипулировать данными через эти конечные точки.

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

Одним из основных моментов было то, как документацию можно было легко импортировать в Postman. Эта функция станет ценным ресурсом для потенциальных клиентов, предлагая подробное руководство и практические примеры API.

Демонстрация сопровождалась ощутимым энтузиазмом команды разработчиков. Их особенно порадовали возможности FastAPI, современной, быстрой (высокопроизводительной) веб-инфраструктуры, которую они использовали для создания API. Консенсус был единодушным — этот прототип заслуживает того, чтобы его продвигали, и следует разработать первую бета-версию части API.

Однако команда также признала, что области все еще требуют дальнейшего изучения. Одним из ключевых аспектов был плагин для аутентификации — важная функция для обеспечения безопасности и целостности API. Команда также признала необходимость изучения способов развертывания и запуска приложения FastAPI в производственной среде.

Эти области все еще были относительно неизведанной территорией, и были необходимы дальнейшие исследования и разработки.

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

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

В заключение можно сказать, что демонстрация прошла успешно и стала первым значительным шагом к реализации REST API Streamfinity. Команда, воодушевленная успешной демонстрацией, стремилась продолжить свою работу, решить нерешенные задачи и приблизить API к его окончательной форме.

Не стесняйтесь исследовать прототип API Streamfinity в этом репозитории GitHub.

Источник:

#Python #FastAPI
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу