13g10n
На главную

Бесшовная миграция на FastAPI

3 минуты

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

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

Я частично уже затрагивал тему ранее, но сегодня речь пойдёт о явном применении магии прогрессивного улучшения — лучшей в мире вещи, которая спасает время, деньги и нервные клетки людям разных профессий по всему миру!

Подопытным в примерах сегодня у нас выступает flask, но метод можно применить к любому WSGI-фреймворку (да, к вашей этой джанге тоже). Не будем особо изобретать и просто скопируем пример из официальной документации:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return '<p>Bloody hell, I\'m doomed!</p>'

Если архитектор был достаточно опытен и/или изначально о чём-то догадывался, то скорее всего API имеет похожую структуру:

api = Blueprint('api', __name__, url_prefix='/api/v1')
pages_api = Blueprint('pages', __name__, url_prefix='/pages')

@pages_api.get('/')
def get_pages():
    ...

@pages_api.post('/')
def create_page():
    ...

api.register_blueprint(pages_api)
app.register_blueprint(api)

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

Итак, мы хотим прогрессивно (а значит постепенно и с минимальными потерями) переводить наше API на FastAPI. Самым очевидным решением (по крайней мере по материалам из интернета) почему-то считается поднятие ещё одного сервера с другим портом и роутинг трафика на этот порт из впереди стоящего прокси.

Просто. Но можно проще.

Специально для нас, страдающих разработчиков из средневековья, умные люди добавили в FastAPI потрясающую вещьWSGIMiddleware. Вещь простая (всего 140 строк кода), но какая же гениальная! Нам дают возможность смонтировать любое WSGI приложение, при этом автоматически получая некоторые преимущества ASGI сервера при обработке параллельных запросов (проводил небольшие тесты, буст реально ощутим, но про это в другой раз).

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastapi.middleware.wsgi import WSGIMiddleware

from flask import Flask, request

flask_app = Flask(__name__)

@flask_app.route('/')
def hello_world():
    return '<p>Bloody hell, I\'m doomed!</p>'

app = FastAPI()

@app.get('/v2')
async def index():
    return HTMLResponse('<p>Finally fixed!</p>')

app.mount('/v1', WSGIMiddleware(flask_app))

Код вверху даёт нам два эндпоинта (/v1 и /v2), которые за собой содержат два разных фреймворка, но находятся в одном процессе и делят всё что можно и нельзя. Такого не добиться через метод с прокси.

Обратите внимание, что flask.request.path не содержит в себе префикса, за которым он смонтирован. Но и это не проблема, т.к. мы можем монтировать flask на корень — но здесь важен порядок регистрации эндпоинтов, чтобы не перетереть новый API старым:

flask_app = Flask(__name__)
app = FastAPI()

@app.get('/my-new-route')
async def my_new_route():
    ...

app.mount('/', WSGIMiddleware(flask_app))

Теперь, всё что нам остаётся это постепенно дополнять наше v2 API обновлёнными эндпоинтами и наслаждаться всеми преимуществами FastAPI и ASGI сервера. Никаких простоев, никаких параллельных команд, никаких прокси и траты времени на новый сервер — чистейшей воды читерство.

Для тех, кто использует старую версию flask — отличная возможность начать переносить IO операции на asyncio. Алхимия позволяет, а если вы писали запросы на core, а не злоупотребляли ORM, то вся работа сводится к добавлению dependency с асинхронной сессией.

PythonFastAPIFlask