π» A full-featured Flask + API + OAS3 + JWT + SwaggerUI + ORM + Migrations + Great and Scalable structure project template
π It's using latest Flask 1.2
Flask 2 is coming soon π
It uses PostgreSQL everywhere, so Docker is necessary (SQLite with no Docker dependency is on the roadmap) π
- Focus on the business and creating value
- Faster project setup
- Standard project structure organization (easy to scale)
- Better QA
- API Design first using OpenAPI & Connexion
- API documentation using swagger UI
- Login using JWT
- Every layer is separated in context/domain
- Service layer for better tests and reuse
- Using Flask Factory to integrate with extensions
- Migrations using Alembic
- ORM using SQLAlchemy
- Optimized development and production settings
- Comes with user model ready to go, signup & signin
- Procfile for deploying to Heroku
- Customizable PostgreSQL version
- Tests using pytest
- Unit tests for the API layer
- Unit tests for the service layer
Development
- Code linter
- Code formatter (Black+iSort)
- Using .env file
- Docker support using docker-compose for development
- Docker using multistage (Production Ready Dockerfile)
- Postgres in development (using docker-compose)
- CI using Github Actions
This project is organized in:
- Layers π§ , which might not change in the project life cycle
- Modules π¦, for domain contexts, which might scale in terms of new features
- Configuration βοΈ separated based on the extensions
- The π Business modules
Hackernews-Clone
.
βββ hackernews
β βββ app.py π Entrypoint (create_app)
β βββ exceptions.py
β βββ π§
ext π Settings
β β βββ βοΈ configuration.py
β β βββ βοΈ api.py
β β βββ βοΈ database.py
β β ...
β βββ π§
api π API Routes
β β βββ π¦ auth.py
β β βββ π¦ news.py
β β βββ π¦ openapi.yaml π API Contract
β β ...
β βββ π§
services π Business rules
β β βββ π¦ auth.py π
β β βββ π¦ news.py π
β β βββ π¦ token.py π
β β ...
β βββ π§
models π ORM
β βββ π¦ news.py
β βββ π¦ users.py
β ...
βββ βοΈ migrations π Database versions
β βββ alembic.ini
β βββ env.py
β βββ script.py.mako
β βββ versions
βββ tests
β βββ conftest.py
β βββ api π Endpoint tests, input, output and validation
β βββ database π Database connection tests
β βββ services π Business rules tests
βββ requirements.txt
βββ pytest.ini
βββ uwsgi.ini π Application server settings
βββ wsgi.py π WSGI Deploy file (Gunicorn/uWSGI)
-
Python 3.8 or 3.9 (Help us test in other versions)
-
Docker to run Postgres locally
Any help is more than welcome...
- π It could be an Issue
- π» It could be using it and give a feedback
- π It could be a github star
- π€ It could be a Question
- π€ If you dislike this project, feel free to tell us what is wrong with it
Let's pretend you want to create a Flask project called "hackernewsclone". Rather than start from scratch by a app.py
and add each library, Flask extesion and various other configurations that always get forgotten until the worst possible moment, get this cookiecutter template do all the work.
First, get Cookiecutter. Trust me, it's awesome::
$ pip install "cookiecutter>=1.7.0"
Now run it against this repo::
$ cookiecutter https://github.com/huogerac/cookiecutter-flask-openapi/
You'll be prompted for some values. Provide them...
project_name [Hackernews Clone]:
project_slug [hackernews_clone]: hackernews
description [The Ultimate Flask Template]:
main_model [News]: News
main_model_lower [news]:
Select python_version:
1 - 3.8.10
2 - 3.9.5
Choose from 1, 2 [1]: 2
Select package_manager:
1 - requirements.txt
2 - Pipenv
Choose from 1, 2 [1]:
Select postgresql_version:
1 - 13.3-alpine
2 - 13.5
3 - 14.1
Choose from 1, 2, 3 [1]:
use_dockerfile [yes]:
use_github_actions_CI [yes]:
deploy_to_heroku [yes]:
keep_vscode_settings [yes]:
author_name [Roger Camargo]:
email [roger-camargo@example.com]: huogerac@gmail.com
version [0.1.0]:
[INFO]: - Using requirements.txt and virtualenv
[SUCCESS]: π Your project is created! β¨ π° β¨
What's next?
cd hackernews
Check the README_DOCKER π³
Check the README_VIRTUALENV π
[INFO]: β οΈ For more details, check the Makefile or run: make help
Then access π http://localhost:5000/api
-
π§π· Estrutura e organização de pastas em projetos Flask
-
π§π· API Design First
The big difference in this template is the way APIs are created, it uses the connexion to build up the API Contract (YAML) first. So the API documentation is created from the contract and not from code.
We can start new implementation from building tests (TDD)
# API tests
def test_should_return_title_is_a_required_field(token_valid_mock, client):
# Given a request with a missing required field
response = client.post(
"/api/news",
headers={"authorization": f"Bearer {token_valid_mock}"},
json={},
)
# Then
assert response.status_code == 400
assert response.json["detail"] == "'title' is a required property"
def test_should_reject_title_less_than_min_title_length(token_valid_mock, client):
# Given a request with an invalid title
response = client.post(
"/api/news",
headers={"authorization": f"Bearer {token_valid_mock}"},
json={"title": "tiny-title"},
)
# Then
assert response.status_code == 400
assert response.json["detail"] == "'tiny-title' is too short - 'title'"
@patch("hackernews.services.news.create_news")
def test_should_accept_null_description(news_mock, token_valid_mock, client):
news_mock.return_value = {}
# Given a request with an empty description (non string)
response = client.post(
"/api/news",
headers={"authorization": f"Bearer {token_valid_mock}"},
json={
"title": "A valid and simple title",
"description": None,
},
)
# Then
assert response.status_code == 201
news_mock.assert_called_once_with("A valid and simple title", None)
Then, we jump up directly to the API Contract. Note that the contract has information to do input validations
# hackernews/api/openapi.yaml
/api/news:
post:
operationId: hackernews.api.news.create_news
summary: Creates a news
tags:
- News
security:
- jwtAuth: [news:create]
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- title
properties:
title:
type: string
example: This is an awesome news
minLength: 12
description:
type: string
example: Some extra information about the news
nullable: true
responses:
201:
description: News created succesfully
content:
application/json:
schema:
$ref: "#/components/schemas/News"
400:
description: Invalid data
401:
description: No Authorization
components:
schemas:
News:
type: object
properties:
id:
type: integer
example: 42
title:
type: string
example: Python is a great option for backend and APIs
description:
type: string
example: This is along text within more detailed information about the news
nullable: true
created_at:
type: string
format: date-time
example: 2021-01-11T11:32:28Z
-
This template is based on cookiecutter-django. Thanks PyDanny and the Cookiecutter maintainers
-
This Flask structure is based on "Arquitetura Definitiva para o Projeto Web Com Python e Flask". Thanks Bruno Rocha and all yours awesome Flask contents