Как создать полноценный REST API на Flask: пошаговое руководство

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Начинающие и опытные разработчики, желающие освоить создание REST API на Flask
  • Студенты и участники курсов по веб-разработке, интересующиеся практическими аспектами разработки API
  • Специалисты, ищущие советы по улучшению своих навыков и практик в области создания и тестирования веб-приложений

    REST API — это не просто модный термин, а основа современной веб-разработки. Когда мой первый проект требовал интеграции с мобильным приложением, я понял истинную мощь Flask для создания легких и функциональных API. За 5 лет работы с этим фреймворком я наблюдал, как разработчики тратят недели на то, что можно реализовать за день. В этом руководстве я расскажу, как избежать типичных ошибок и создать полноценный REST API на Flask с нуля — от установки до тестирования, с рабочими примерами и проверенными практиками. 🚀

Если вы хотите не просто прочитать руководство, а получить структурированные знания по разработке с Flask и другими Python-фреймворками, обратите внимание на курс Обучение Python-разработке от Skypro. Программа построена опытными практиками, включает реальные проекты и личный код-ревью, что позволит вам быстро перейти от создания простых API к комплексным веб-приложениям, востребованным на рынке труда.

Настройка окружения и базовая структура REST API на Flask

Начнем с создания изолированной среды разработки и установки необходимых компонентов для нашего REST API. Правильная настройка окружения — залог успешного и беспроблемного проекта. 🛠️

Создадим виртуальное окружение и установим Flask:

Bash
Скопировать код
# Создаем виртуальное окружение
python -m venv venv

# Активируем его (для Windows)
venv\Scripts\activate

# Активируем его (для Unix/MacOS)
source venv/bin/activate

# Устанавливаем Flask
pip install flask

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

project/
├── app.py # Основной файл приложения
├── models.py # Определения моделей данных
├── resources.py # Ресурсы API и их обработчики
├── schemas.py # Схемы сериализации/десериализации
└── venv/ # Виртуальное окружение

Теперь создадим минимальное Flask-приложение в файле app.py:

Python
Скопировать код
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/health', methods=['GET'])
def health_check():
return jsonify({'status': 'ok', 'message': 'API работает'})

if __name__ == '__main__':
app.run(debug=True)

Запустите приложение командой python app.py и откройте в браузере http://localhost:5000/api/health. Вы должны увидеть JSON-ответ, подтверждающий работоспособность вашего API.

Дмитрий Волков, технический руководитель

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

Для более сложных API рекомендую использовать дополнительные расширения Flask:

Расширение Назначение Команда установки
Flask-RESTful Упрощает создание RESTful API pip install flask-restful
Flask-SQLAlchemy ORM для работы с базами данных pip install flask-sqlalchemy
Flask-Marshmallow Сериализация/десериализация объектов pip install flask-marshmallow
Flask-JWT-Extended Аутентификация с использованием JWT pip install flask-jwt-extended

Применяя эти расширения, мы можем существенно упростить разработку и повысить качество нашего API.

Пошаговый план для смены профессии

Создание эндпоинтов и маршрутизация REST-запросов

Эндпоинты — это URI, по которым клиенты взаимодействуют с вашим API. Правильная организация маршрутов делает API интуитивно понятным и легким в использовании. 🧭

В REST архитектуре ресурсы обычно представлены существительными во множественном числе. Рассмотрим пример API для управления книгами:

Python
Скопировать код
from flask import Flask, jsonify, request

app = Flask(__name__)

# Имитация базы данных
books = [
{"id": 1, "title": "Фласк для начинающих", "author": "Александр Петров"},
{"id": 2, "title": "Python в действии", "author": "Мария Сидорова"}
]

# Получение всех книг
@app.route('/api/books', methods=['GET'])
def get_books():
return jsonify({"books": books})

# Получение книги по ID
@app.route('/api/books/<int:book_id>', methods=['GET'])
def get_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if book:
return jsonify(book)
return jsonify({"message": "Книга не найдена"}), 404

if __name__ == '__main__':
app.run(debug=True)

Обратите внимание на структуру URL: мы используем /api/books для получения списка всех книг и /api/books/<id> для получения конкретной книги. Это соответствует принципам RESTful API.

Для более сложных API удобнее использовать Flask-RESTful, который позволяет организовать код в виде классов-ресурсов:

Python
Скопировать код
from flask import Flask
from flask_restful import Api, Resource, reqparse

app = Flask(__name__)
api = Api(app)

# Те же данные, что и раньше
books = [
{"id": 1, "title": "Фласк для начинающих", "author": "Александр Петров"},
{"id": 2, "title": "Python в действии", "author": "Мария Сидорова"}
]

class BookResource(Resource):
def get(self, book_id=None):
if book_id is None:
return {"books": books}
book = next((book for book in books if book["id"] == book_id), None)
if book:
return book
return {"message": "Книга не найдена"}, 404

# Регистрация ресурсов
api.add_resource(BookResource, '/api/books', '/api/books/<int:book_id>')

if __name__ == '__main__':
app.run(debug=True)

Этот подход делает код более модульным и позволяет легко добавлять новые функции.

При разработке REST API важно следовать соглашениям об именовании маршрутов:

  • Используйте существительные во множественном числе: /books, /users
  • Идентифицируйте ресурсы в URL: /books/1, а не /get-book?id=1
  • Используйте вложенные ресурсы для связанных данных: /authors/1/books
  • Включите версионирование API: /api/v1/books

Следование этим принципам сделает ваш API более предсказуемым для клиентов.

Обработка HTTP-методов: GET, POST, PUT и DELETE во Flask

HTTP-методы определяют операции, которые клиент хочет выполнить с ресурсами. Каждый метод имеет свою семантику и назначение. Правильная обработка этих методов — ключ к созданию полноценного REST API. 📝

Расширим наш пример с книгами, добавив поддержку всех основных HTTP-методов:

Python
Скопировать код
from flask import Flask, jsonify, request

app = Flask(__name__)

# Имитация базы данных
books = [
{"id": 1, "title": "Фласк для начинающих", "author": "Александр Петров"},
{"id": 2, "title": "Python в действии", "author": "Мария Сидорова"}
]

# Получение всех книг (GET)
@app.route('/api/books', methods=['GET'])
def get_books():
return jsonify({"books": books})

# Получение книги по ID (GET)
@app.route('/api/books/<int:book_id>', methods=['GET'])
def get_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if book:
return jsonify(book)
return jsonify({"message": "Книга не найдена"}), 404

# Создание новой книги (POST)
@app.route('/api/books', methods=['POST'])
def create_book():
if not request.json:
return jsonify({"message": "Неверный формат данных"}), 400

# Присваиваем новый ID
new_id = max(book["id"] for book in books) + 1 if books else 1

# Создаем новую книгу из полученных данных
new_book = {
"id": new_id,
"title": request.json.get('title', ''),
"author": request.json.get('author', '')
}

books.append(new_book)
return jsonify(new_book), 201

# Обновление книги (PUT)
@app.route('/api/books/<int:book_id>', methods=['PUT'])
def update_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if not book:
return jsonify({"message": "Книга не найдена"}), 404

if not request.json:
return jsonify({"message": "Неверный формат данных"}), 400

# Обновляем данные книги
book['title'] = request.json.get('title', book['title'])
book['author'] = request.json.get('author', book['author'])

return jsonify(book)

# Удаление книги (DELETE)
@app.route('/api/books/<int:book_id>', methods=['DELETE'])
def delete_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if not book:
return jsonify({"message": "Книга не найдена"}), 404

books.remove(book)
return jsonify({"message": "Книга удалена"})

if __name__ == '__main__':
app.run(debug=True)

Теперь наш API поддерживает полный набор CRUD-операций (Create, Read, Update, Delete) через соответствующие HTTP-методы.

HTTP метод CRUD операция Пример URL Код успешного ответа
GET Read (чтение) /api/books или /api/books/1 200 OK
POST Create (создание) /api/books 201 Created
PUT Update (обновление) /api/books/1 200 OK
DELETE Delete (удаление) /api/books/1 200 OK или 204 No Content

При разработке API обязательно учитывайте идемпотентность методов: повторный вызов GET, PUT или DELETE с теми же параметрами должен давать тот же результат. Метод POST не является идемпотентным, так как каждый вызов создает новый ресурс.

Алексей Соколов, разработчик-архитектор

Вспоминаю проект, где мы реализовали API для системы управления медицинскими назначениями. Первая версия поддерживала только GET и POST запросы — казалось, этого достаточно. Но когда интегрировали мобильное приложение, обнаружили проблему: при плохом соединении клиенты дублировали запросы, создавая множественные записи. После нескольких инцидентов мы полностью переработали API, внедрив правильную обработку PUT и PATCH методов для обновлений и строгую валидацию идентификаторов. Это не только устранило дублирование, но и сделало возможным оффлайн-режим работы клиентов, что повысило удовлетворенность пользователей на 47%.

Для тестирования вашего API удобно использовать инструменты вроде Postman или curl. Например, для создания новой книги через curl:

Bash
Скопировать код
curl -X POST http://localhost:5000/api/books \
-H "Content-Type: application/json" \
-d '{"title": "Мастер и Маргарита", "author": "Михаил Булгаков"}'

Работа с данными: сериализация и валидация в REST API

Сериализация — процесс преобразования объектов Python в формат, понятный клиентам (обычно JSON). Валидация обеспечивает корректность получаемых данных. Эти процессы критичны для надежного API. 🛡️

Для упрощения этих задач рекомендую использовать библиотеку Marshmallow. Установим необходимые пакеты:

Bash
Скопировать код
pip install marshmallow flask-marshmallow marshmallow-sqlalchemy

Создадим схему для валидации и сериализации данных книги:

Python
Скопировать код
from flask import Flask, jsonify, request
from flask_marshmallow import Marshmallow
from marshmallow import ValidationError

app = Flask(__name__)
ma = Marshmallow(app)

# Схема для валидации и сериализации
class BookSchema(ma.Schema):
class Meta:
fields = ("id", "title", "author")

# Валидация данных
def validate_title(self, title):
if not title or len(title) < 3:
raise ValidationError("Название должно содержать не менее 3 символов")
return title

# Создаем экземпляры схем для одной книги и списка книг
book_schema = BookSchema()
books_schema = BookSchema(many=True)

# Имитация базы данных
books = [
{"id": 1, "title": "Фласк для начинающих", "author": "Александр Петров"},
{"id": 2, "title": "Python в действии", "author": "Мария Сидорова"}
]

@app.route('/api/books', methods=['GET'])
def get_books():
# Сериализация списка книг
return jsonify({"books": books_schema.dump(books)})

@app.route('/api/books/<int:book_id>', methods=['GET'])
def get_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if book:
# Сериализация одной книги
return jsonify(book_schema.dump(book))
return jsonify({"message": "Книга не найдена"}), 404

@app.route('/api/books', methods=['POST'])
def create_book():
if not request.json:
return jsonify({"message": "Неверный формат данных"}), 400

try:
# Валидация входных данных
validated_data = book_schema.load(request.json)

# Присваиваем новый ID
new_id = max(book["id"] for book in books) + 1 if books else 1
validated_data['id'] = new_id

books.append(validated_data)

# Сериализуем и возвращаем созданную книгу
return jsonify(book_schema.dump(validated_data)), 201

except ValidationError as err:
return jsonify({"errors": err.messages}), 400

if __name__ == '__main__':
app.run(debug=True)

Преимущества использования схем сериализации:

  • Автоматическая валидация входных данных с понятными сообщениями об ошибках
  • Контроль над тем, какие поля возвращаются клиенту
  • Возможность трансформации данных (например, форматирование дат)
  • Разделение логики валидации и бизнес-логики

Для более сложных случаев можно комбинировать Marshmallow с SQLAlchemy, что упрощает работу с базами данных:

Python
Скопировать код
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///books.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
ma = Marshmallow(app)

class Book(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
author = db.Column(db.String(100), nullable=False)

def __init__(self, title, author):
self.title = title
self.author = author

class BookSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Book
load_instance = True

# Создаем базу данных
with app.app_context():
db.create_all()

Теперь Marshmallow автоматически создаст схему на основе модели SQLAlchemy, что еще больше упрощает код.

Тестирование и документирование Flask REST API

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

Начнем с написания автоматических тестов для нашего API с использованием стандартного модуля unittest:

Python
Скопировать код
import unittest
import json
from app import app

class BookAPITestCase(unittest.TestCase):
def setUp(self):
self.app = app.test_client()
self.app.testing = True

def test_get_books(self):
response = self.app.get('/api/books')
data = json.loads(response.data)
self.assertEqual(response.status_code, 200)
self.assertTrue('books' in data)
self.assertIsInstance(data['books'], list)

def test_get_book(self):
response = self.app.get('/api/books/1')
data = json.loads(response.data)
self.assertEqual(response.status_code, 200)
self.assertEqual(data['id'], 1)

def test_create_book(self):
test_book = {
'title': 'Тестовая книга',
'author': 'Автор Тестов'
}
response = self.app.post('/api/books',
data=json.dumps(test_book),
content_type='application/json')
data = json.loads(response.data)
self.assertEqual(response.status_code, 201)
self.assertEqual(data['title'], 'Тестовая книга')

if __name__ == '__main__':
unittest.main()

Для запуска тестов выполните:

Bash
Скопировать код
python -m unittest test_api.py

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

Теперь перейдем к документированию API. Одним из лучших инструментов для этого является Swagger UI через расширение Flask-RESTX:

Bash
Скопировать код
pip install flask-restx

Применим его к нашему API:

Python
Скопировать код
from flask import Flask
from flask_restx import Api, Resource, fields

app = Flask(__name__)
api = Api(app, version='1.0', title='Book API',
description='API для управления книгами')

# Определяем пространство имен
ns = api.namespace('books', description='Операции с книгами')

# Определяем модель для документации
book_model = api.model('Book', {
'id': fields.Integer(readonly=True, description='Уникальный идентификатор'),
'title': fields.String(required=True, description='Название книги'),
'author': fields.String(required=True, description='Автор книги')
})

# Имитация базы данных
books = [
{"id": 1, "title": "Фласк для начинающих", "author": "Александр Петров"},
{"id": 2, "title": "Python в действии", "author": "Мария Сидорова"}
]

@ns.route('/')
class BookList(Resource):
@ns.doc('list_books')
@ns.marshal_list_with(book_model)
def get(self):
"""Получить список всех книг"""
return books

@ns.doc('create_book')
@ns.expect(book_model)
@ns.marshal_with(book_model, code=201)
def post(self):
"""Создать новую книгу"""
new_id = max(book["id"] for book in books) + 1 if books else 1
new_book = {
'id': new_id,
'title': api.payload['title'],
'author': api.payload['author']
}
books.append(new_book)
return new_book, 201

@ns.route('/<int:id>')
@ns.response(404, 'Книга не найдена')
@ns.param('id', 'Идентификатор книги')
class Book(Resource):
@ns.doc('get_book')
@ns.marshal_with(book_model)
def get(self, id):
"""Получить книгу по ID"""
for book in books:
if book['id'] == id:
return book
api.abort(404, "Книга с ID {} не найдена".format(id))

if __name__ == '__main__':
app.run(debug=True)

При запуске приложения вы можете открыть Swagger UI в браузере по адресу http://localhost:5000, где увидите интерактивную документацию вашего API.

Вот ключевые моменты для успешного тестирования и документирования API:

  • Пишите тесты параллельно с разработкой, а не после нее
  • Тестируйте как успешные сценарии, так и обработку ошибок
  • Документируйте все эндпоинты, параметры и возможные коды ответов
  • Приводите примеры запросов и ответов в документации
  • Настройте CI/CD для автоматического запуска тестов при изменении кода

Изучив основы создания REST API на Flask, вы заложили фундамент для разработки профессиональных веб-сервисов. Но истинное мастерство приходит с практикой — используйте полученные знания для создания собственных API, экспериментируйте с различными расширениями и не бойтесь внедрять API в реальные проекты. Помните: даже самые сложные системы начинались с простого эндпоинта, возвращающего "Hello, World!" в формате JSON.

Загрузка...