← Назад к вопросам
REST API на Flask
2.0 Middle🔥 211 комментариев
#FastAPI и Flask#REST API и HTTP
Условие
Реализуйте простой REST API для управления списком задач (TODO list) на Flask.
Эндпоинты
- GET /tasks — список всех задач
- GET /tasks/<id> — получить задачу по id
- POST /tasks — создать задачу
- PUT /tasks/<id> — обновить задачу
- DELETE /tasks/<id> — удалить задачу
Модель задачи
{
"id": 1,
"title": "Купить молоко",
"done": false
}
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
REST API на Flask
Базовое решение
from flask import Flask, request, jsonify
from datetime import datetime
from typing import Dict, List, Tuple, Any, Optional
app = Flask(__name__)
# Хранилище задач (в памяти)
tasks_db: Dict[int, Dict[str, Any]] = {}
next_id = 1
@app.route('/tasks', methods=['GET'])
def get_tasks() -> Tuple[Dict[str, List], int]:
"""Получить список всех задач"""
return jsonify(list(tasks_db.values())), 200
@app.route('/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id: int) -> Tuple[Dict[str, Any], int]:
"""Получить задачу по ID"""
task = tasks_db.get(task_id)
if not task:
return jsonify({"error": "Task not found"}), 404
return jsonify(task), 200
@app.route('/tasks', methods=['POST'])
def create_task() -> Tuple[Dict[str, Any], int]:
"""Создать новую задачу"""
global next_id
data = request.get_json()
if not data or 'title' not in data:
return jsonify({"error": "Title is required"}), 400
task = {
"id": next_id,
"title": data['title'],
"done": data.get('done', False),
"created_at": datetime.utcnow().isoformat()
}
tasks_db[next_id] = task
next_id += 1
return jsonify(task), 201
@app.route('/tasks/<int:task_id>', methods=['PUT'])
def update_task(task_id: int) -> Tuple[Dict[str, Any], int]:
"""Обновить задачу"""
if task_id not in tasks_db:
return jsonify({"error": "Task not found"}), 404
data = request.get_json()
task = tasks_db[task_id]
if 'title' in data:
task['title'] = data['title']
if 'done' in data:
task['done'] = data['done']
task['updated_at'] = datetime.utcnow().isoformat()
return jsonify(task), 200
@app.route('/tasks/<int:task_id>', methods=['DELETE'])
def delete_task(task_id: int) -> Tuple[Dict[str, Any], int]:
"""Удалить задачу"""
if task_id not in tasks_db:
return jsonify({"error": "Task not found"}), 404
deleted = tasks_db.pop(task_id)
return jsonify(deleted), 200
if __name__ == '__main__':
app.run(debug=True)
Улучшенное решение с валидацией
from flask import Flask, request, jsonify
from dataclasses import dataclass, asdict
from datetime import datetime
from typing import Dict, Optional
import uuid
app = Flask(__name__)
@dataclass
class Task:
"""Модель задачи"""
id: str
title: str
done: bool = False
created_at: str = None
updated_at: Optional[str] = None
def __post_init__(self):
if self.created_at is None:
self.created_at = datetime.utcnow().isoformat()
def to_dict(self):
return asdict(self)
class TaskStore:
"""Хранилище задач"""
def __init__(self):
self.tasks: Dict[str, Task] = {}
def create(self, title: str, done: bool = False) -> Task:
task_id = str(uuid.uuid4())
task = Task(id=task_id, title=title, done=done)
self.tasks[task_id] = task
return task
def get(self, task_id: str) -> Optional[Task]:
return self.tasks.get(task_id)
def get_all(self) -> list[Task]:
return list(self.tasks.values())
def update(self, task_id: str, title: Optional[str] = None, done: Optional[bool] = None) -> Optional[Task]:
task = self.get(task_id)
if not task:
return None
if title is not None:
task.title = title
if done is not None:
task.done = done
task.updated_at = datetime.utcnow().isoformat()
return task
def delete(self, task_id: str) -> Optional[Task]:
return self.tasks.pop(task_id, None)
store = TaskStore()
def validate_task_data(data: dict) -> Tuple[bool, Optional[str]]:
"""Валидация данных задачи"""
if not data:
return False, "Request body is empty"
if 'title' not in data:
return False, "Field 'title' is required"
title = data['title']
if not isinstance(title, str) or not title.strip():
return False, "Title must be a non-empty string"
if 'done' in data and not isinstance(data['done'], bool):
return False, "Field 'done' must be boolean"
return True, None
@app.route('/tasks', methods=['GET'])
def get_tasks():
"""Получить все задачи"""
tasks = [task.to_dict() for task in store.get_all()]
return jsonify(tasks), 200
@app.route('/tasks/<task_id>', methods=['GET'])
def get_task(task_id: str):
"""Получить задачу по ID"""
task = store.get(task_id)
if not task:
return jsonify({"error": "Task not found"}), 404
return jsonify(task.to_dict()), 200
@app.route('/tasks', methods=['POST'])
def create_task():
"""Создать задачу"""
data = request.get_json()
is_valid, error_message = validate_task_data(data)
if not is_valid:
return jsonify({"error": error_message}), 400
task = store.create(
title=data['title'].strip(),
done=data.get('done', False)
)
return jsonify(task.to_dict()), 201
@app.route('/tasks/<task_id>', methods=['PUT'])
def update_task(task_id: str):
"""Обновить задачу"""
task = store.get(task_id)
if not task:
return jsonify({"error": "Task not found"}), 404
data = request.get_json()
if not data:
return jsonify({"error": "Request body is empty"}), 400
# Валидируем, что if there's title or done
if 'title' in data:
if not isinstance(data['title'], str) or not data['title'].strip():
return jsonify({"error": "Title must be a non-empty string"}), 400
if 'done' in data and not isinstance(data['done'], bool):
return jsonify({"error": "Field 'done' must be boolean"}), 400
updated_task = store.update(
task_id,
title=data.get('title'),
done=data.get('done')
)
return jsonify(updated_task.to_dict()), 200
@app.route('/tasks/<task_id>', methods=['DELETE'])
def delete_task(task_id: str):
"""Удалить задачу"""
task = store.delete(task_id)
if not task:
return jsonify({"error": "Task not found"}), 404
return jsonify(task.to_dict()), 200
@app.errorhandler(404)
def not_found(error):
return jsonify({"error": "Endpoint not found"}), 404
@app.errorhandler(500)
def server_error(error):
return jsonify({"error": "Internal server error"}), 500
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
Тестирование API
import pytest
from flask import Flask
@pytest.fixture
def client():
"""Fixture для тестирования"""
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_create_task(client):
"""Тест создания задачи"""
response = client.post('/tasks', json={
'title': 'Test task',
'done': False
})
assert response.status_code == 201
data = response.get_json()
assert data['title'] == 'Test task'
assert data['done'] is False
assert 'id' in data
def test_get_tasks(client):
"""Тест получения списка задач"""
# Создаем задачу
client.post('/tasks', json={'title': 'Task 1'})
client.post('/tasks', json={'title': 'Task 2'})
# Получаем список
response = client.get('/tasks')
assert response.status_code == 200
tasks = response.get_json()
assert len(tasks) >= 2
def test_update_task(client):
"""Тест обновления задачи"""
# Создаем
create_resp = client.post('/tasks', json={'title': 'Original'})
task_id = create_resp.get_json()['id']
# Обновляем
response = client.put(f'/tasks/{task_id}', json={
'title': 'Updated',
'done': True
})
assert response.status_code == 200
data = response.get_json()
assert data['title'] == 'Updated'
assert data['done'] is True
def test_delete_task(client):
"""Тест удаления задачи"""
create_resp = client.post('/tasks', json={'title': 'To delete'})
task_id = create_resp.get_json()['id']
response = client.delete(f'/tasks/{task_id}')
assert response.status_code == 200
# Проверяем что удалена
get_resp = client.get(f'/tasks/{task_id}')
assert get_resp.status_code == 404
def test_invalid_data(client):
"""Тест валидации данных"""
response = client.post('/tasks', json={'done': True})
assert response.status_code == 400
Запуск и тестирование
# Установка зависимостей
pip install flask pytest
# Запуск приложения
python app.py
# Тестирование с curl
curl -X GET http://localhost:5000/tasks
curl -X POST http://localhost:5000/tasks \
-H "Content-Type: application/json" \
-d '{"title": "Купить молоко", "done": false}'
curl -X PUT http://localhost:5000/tasks/1 \
-H "Content-Type: application/json" \
-d '{"done": true}'
curl -X DELETE http://localhost:5000/tasks/1
# Тестирование с pytest
pytest test_app.py -v
Production-ready версия
Для production нужны улучшения:
- База данных — вместо памяти
- Аутентификация — JWT токены
- Логирование — структурированные логи
- Rate limiting — защита от DDoS
- CORS — для фронтенда
- Swagger документация — для API
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from flasgger import Swagger
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user:pass@localhost/tasks_db'
db = SQLAlchemy(app)
CORS(app)
limiter = Limiter(app=app, key_func=get_remote_address)
swagger = Swagger(app)
# Далее использовать SQLAlchemy модели вместо памяти