Skip to content

huogerac/cookiecutter-flask-openapi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

17 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Cookiecutter Flask OpenAPI boilerplate

GitHub issues GitHub stars GitHub license code style: black

πŸ’» 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) 😎

Why using this boilerplate ❓

  • Focus on the business and creating value
  • Faster project setup
  • Standard project structure organization (easy to scale)
  • Better QA

What's Included (Features) πŸŽ‰

  • 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

Structure

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)

Requirements

  • Python 3.8 or 3.9 (Help us test in other versions)

  • Docker to run Postgres locally

Contribute πŸš€

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

Get Started (Usage)

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

Articles

API Design First Approach

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.

Understanding the structure from tests

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

Thanks and Inspirations

About

Cookiecutter Flask OpenAPI is a template for jumpstarting production-ready Flask projects quickly. It has a well organized and scalable structure. It uses API design first

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published