A Python demo app written with Flask-RESTful.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rypel b3e69d00b0 [readme] mention openapi spec and swagger instances as components 3 days ago
app [main, openapi, makefile] add /environment route; rename health to 'check' target and include call to /environment route 3 days ago
docker [docker-compose] fix declaration of test volume 4 days ago
docs [main, openapi, makefile] add /environment route; rename health to 'check' target and include call to /environment route 3 days ago
nginx/conf.d [nginx] remove restriction for /health route; check for api key header before entering location context 6 days ago
tests [tests, makefile] separate unit tests from integration tests, move unit-test-only config 4 days ago
.dockerignore [tests, makefile, dockerignore] add nose2 config, move coveragerc and ignore tests/ from build context 5 days ago
.editorconfig [makefile, readme, dotfiles] rename 3 weeks ago
.gitignore [gitignore, makefile] exclude .env and remove it 3 weeks ago
LICENSE [license] fill in copyright 8 months ago
Makefile [readme] mention openapi spec and swagger instances as components 3 days ago
Makefile.test [main, openapi, makefile] add /environment route; rename health to 'check' target and include call to /environment route 3 days ago
README.md [readme] mention openapi spec and swagger instances as components 3 days ago
TODO.md [readme, todo] reflect changes to repo layout; adjust wording 4 days ago
gunicorn.py [gunicorn, entrypoint] rename and move config top level, also set group, add docs URL 6 days ago
requirements.txt [requirements] link app & test reqs in top level reqs 5 days ago
run.py [run] fix import 6 days ago

README.md

flask-restful-demo

A Python app written in with the Flask-RESTful framework. It features a user management API with CRUDL operations and SQL persistence.

Components

The Flask app speaks to a Postgres database and is being served by a Gunicorn server. Any HTTP traffic towards the server is proxy’d through Nginx.

The server, database and proxy services run as Docker containers, all set up via Docker Compose. To speed up the image builds, BuildKit is enabled by default.

Make is used for tooling. Environment variables are used for project-wide configuration.

An OpenAPI Specification is provided. It can be rendered through Swagger UI and edited through Swagger Editor containers.

Prerequisites

Packages

Install these through your system’s package manager:

Build time

  • Python 3.8+
  • virtualenv 16+
  • GNU Make 4+
  • Docker 19.03+
  • Docker Compose 1.25+

Run time

  • cURL 7+
  • any browser

Docker

Add yourself to the docker group with:

sudo gpasswd -a ${USER} docker

Usage

Fast-forward to Development -> Environment and create a .env file.

Done? Then simply type:

make all

Source the .env file and export needed variables into environment:

set -a; source .env; set +a

Query the API with cURL (-i for header output):

# get all users
curl -iH "$API_KEY_HEADER" localhost/v1/users
# get users, filtered                               
curl -iH "$API_KEY_HEADER" localhost/v1/users?name-starts-with=foo
# create a user       
curl -iH "$API_KEY_HEADER" -H "$JSON_HEADER" -d '{"name":"foobar"}' localhost/v1/users
# get a user     
curl -iH "$API_KEY_HEADER" localhost/v1/users/1234
# update a user
curl -iH "$API_KEY_HEADER" -X PATCH -H "$JSON_HEADER" -d '{"name":"slurm"}' localhost/v1/users/1234
# delete a user
curl -iH "$API_KEY_HEADER" -X DELETE localhost/v1/users/1234     

Stop and remove all containers:

make down

Run time state (created users) get persisted in the database volume, which will be mounted on container start.

Development

For common tasks a toolchain is defined in the Makefile. Targets can be chained, e.g. make pull build up logs.

Environment

Project-wide configuration is persisted in the .env file as environment variables, used by various components during build and run time. make sources this file automatically and propagates its contents.

Create the file:

touch .env

Paste this content:

API_KEY=<SECRET>

FLASK_ENV=development
#FLASK_ENV=production

GUNICORN_HOST=server
GUNICORN_PORT=8000
GUNICORN_USER=gunicorn

NGINX_IMAGE=nginx:1.17-alpine

POSTGRES_DB=app
POSTGRES_HOST=database
POSTGRES_IMAGE=postgres:12.1-alpine
POSTGRES_PASSWORD=<SECRET>
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_VOLUME=postgres_data

PROJECT_NAME=flask-restful-demo

PYTHON_VERSION=3.8
PYTHON_IMAGE=python:${PYTHON_VERSION}-alpine

SERVER_IMAGE=gunicorn-flask:latest

SWAGGER_EDITOR_IMAGE=swaggerapi/swagger-editor:latest
SWAGGER_EDITOR_PORT=8002
SWAGGER_UI_IMAGE=swaggerapi/swagger-ui:latest
SWAGGER_UI_PORT=8001

# for convenience, e.g. using curl
API_KEY_HEADER="Api-Key: ${API_KEY}"
JSON_HEADER='Content-Type: application/json'

Now replace <SECRET> with randomness from openssl rand -base64 42 or similar.

Note: *_VOLUME and *_HOST values must correspond to their volume/service names in docker-compose.yml, since YAML does not allow for shell substitution in section keys.

Configuration

Values that simply change often, that are needed by multiple components or that are secrets must be declared in .env.

The rest should be defined in their respective config files:

  • proxy: nginx/conf.d/default.conf
  • server: gunicorn/config.py
  • app: flask/app/config.py
  • containers: docker/docker-compose.yml

Dependencies

Install virtualenv with Python, Pip and setuptools:

make venv

Install requirements defined in requirements.txt and **/requirements.txt files:

make install

Interactively upgrade installed requirements and, if successful, update all requirements files:

make upgrade

Linting

Run Flake8 to lint all python modules:

make lint

Unit testing

Run nose2 with test coverage report:

make unit-test
````

### Containers
Show final container config:
```shell script
make config

Pull python, database and proxy container images:

make pull

Build app/server container image:

make build

Build app/server container image, without using the cache:

make build-no-cache

Create and start all containers in background:

make up

Attach to the logs of running containers:

make logs

Stop and remove running containers:

make down

Integration testing

Call the health route:

make health

Run integration tests:

make integration-test

Specification

To use any other browser than Firefox to view/edit the specification, set this environment variable beforehand:

export BROWSER=chrome-or-whatever

Serve a local Swagger UI instance (auto-loads the openapi.yaml spec) and open it in browser:

make swagger-ui

Serve a local Swagger Editor instance and open it in browser:

make swagger-editor

There, import openapi.yaml manually.

Clean-up

Remove disposable run time state:

make clean

Remove virtualenv with installed requirements:

make clean-venv

Remove database volume (asks before actually removing):

make clean-volume

Remove app/server image:

make clean-image

Remove all build & runtime state:

make clean-all

Repo layout

Most important directories and files:

- app/
    - controller/
        - v1/                   -> route versioning
            - users.py          -> route entity (API resource)
        - auth.py               -> auth decorator
        - base.py               -> base controller
    - model/
        - database.py           -> database functions
        - user.py               -> entity/resource model
        - validation.py         -> validation functions
    - config.py                 -> app config
    - main.py                   -> create_app() factory
    - requirements.txt          -> app & server dependencies
- docker/
    - docker-compose.test.yml   -> test container setup
    - docker-compose.yml        -> default container setup
    - Dockerfile                -> image definition for app/server container
    - entrypoint.sh             -> wait condition & app/server start
- docs/
    - openapi.yaml              -> API specification
- nginx/
    - conf.d/default.conf       -> proxy config
- tests/
    - integration/
        - test_*.py             -> intgration tests
    - unit/
        - .coveragerc           -> coverage config
        - nose2.cfg             -> test runner config
        - test_*.py             -> unit tests
    - requirements.txt          -> unit & integration test dependencies
- venv/ [*]                     -> symlinked interpreter & local dependencies
- .dockerignore                 -> build context exclusions
- .editorconfig                 -> syntax guideline for IDEs
- .env [*]                      -> project-wide config (environment variables)
- .gitignore
- gunicorn.py                   -> server config
- LICENSE
- Makefile                      -> default toolchain
- Makefile.test                 -> integration test toolchain
- README.md
- requirements.txt              -> local tooling dependencies
- run.py                        -> WSGI entrypoint for server
- TODO.md                       -> planning & changelog

Additionally, the app & tests directories contain __init__.py files for package name-spacing and package-level functions.

[*] = not committed, but present at build time