Skip to content

Commit

Permalink
First commit containing the base template for a Flask API (#8)
Browse files Browse the repository at this point in the history
First commit containing the base template for a Flask API
  • Loading branch information
chouinar committed Sep 16, 2022
1 parent d32ded9 commit 1339fa6
Show file tree
Hide file tree
Showing 88 changed files with 6,455 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Ticket

{TICKET_LINK}

## Changes
> What was added, updated, or removed in this PR.
## Context for reviewers
> Testing instructions, background context, more in-depth details of the implementation, and anything else you'd like to call out or ask reviewers. Explain how the changes were verified.
## Testing
> Screenshots, GIF demos, code examples or output to help show the changes working as expected. ProTip: you can drag and drop or paste images into this textbox.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Mac filesystem file
.DS_Store


# IDE-specific files
.vscode/*
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Template Application Flask

Template application for a basic Flask API.

This application should run as-is with minimal setup (see below).
## Getting started

This application is dockerized. Take a look at [Dockerfile](./app/Dockerfile) to see how it works.

A very simple [docker-compose.yml](./docker-compose.yml) has been included to support local development and deployment. Take a look at [docker-compose.yml](./docker-compose.yml) for more information.

**How to run:**

1. In your terminal, `cd` to the app directory of this repo.
2. Make sure you have [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed & running.
3. Run `make init start` to build the image and start the container.
4. Navigate to `localhost:8080/v1/docs` to access the Swagger UI.
5. Run `make run-logs` to see the logs of the running API container
6. Run `make stop` when you are done to delete the container.

See: [app/README.md](/app/README.md) for further details on the API implementation.
21 changes: 21 additions & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Python compiled/optimized files
__pycache__/
*.py[cod]
*$py.class

# Python packaging stuff
dist/
*.egg-info

# Python testing stuff
.coverage*
coverage.*
.testmondata
.pytest_cache/

# Python environments
.env
.venv

# mypy
.mypy_cache
24 changes: 24 additions & 0 deletions app/.spectral.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Spectral rule file to use in Makefile. See: https://meta.stoplight.io/docs/spectral/docs/getting-started/rulesets.md
extends: spectral:oas

rules:
operation-description: off
no-$ref-siblings: info
path-kebab-case:
description: Paths must be kebab-case.
message: "Path {{property}} must be kebab-case"
severity: warn
given: $.paths[*]~
then:
function: pattern
functionOptions:
match: "^(\/([a-z0-9-]+|{[^}]+}))+$"
path-param-snake-case:
description: Parameters in paths must be snake_case.
message: "Parameter in path {{property}} must be snake_case"
severity: error
given: $.paths[*]~
then:
function: pattern
functionOptions:
match: "^(\/([^{^}]+|{[a-z0-9_]+}))+$"
26 changes: 26 additions & 0 deletions app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Use the official python3 image based on Debian 11 "Bullseye".
# https://hub.docker.com/_/python
FROM python:3-slim
# Keep container packages up-to-date.
RUN apt update \
&& apt upgrade --yes
# Install poetry, the package manager.
# https://python-poetry.org
RUN pip install poetry


# Set the application working directory.
WORKDIR /srv

COPY pyproject.toml poetry.lock ./
RUN poetry config virtualenvs.in-project false && poetry env use python
RUN poetry install --no-root

# Copy application files.
COPY . /srv

# Install application dependencies.
# https://python-poetry.org/docs/basic-usage/#installing-dependencies
RUN poetry install
# Run the application.
CMD ["poetry", "run", "python", "-m", "api"]
168 changes: 168 additions & 0 deletions app/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
##################################################
# Constants
##################################################

APP_NAME := main-app

# Adding this to the end of a script that outputs JSON will convert
# it to a readable format with timestamps and color-coding.
#
# Note that you can also change the LOG_FORMAT env var to switch
# between JSON & human readable format. This is left in place
# in the event JSON is output from a process we don't log.
DECODE_LOG := 2>&1 | python3 -u api/logging/util/decodelog.py

# A few commands need adjustments if they're run in CI, specify those here
# TODO - when CI gets hooked up, actually test this.
ifdef CI
DOCKER_EXEC_ARGS := -T -e CI -e PYTEST_ADDOPTS="--color=yes"
FLAKE8_FORMAT := '::warning file=api/%(path)s,line=%(row)d,col=%(col)d::%(path)s:%(row)d:%(col)d: %(code)s %(text)s'
MYPY_FLAGS := --no-pretty
MYPY_POSTPROC := | perl -pe "s/^(.+):(\d+):(\d+): error: (.*)/::warning file=api\/\1,line=\2,col=\3::\4/"
SPECTRAL_POSTPROC := --format=text | perl -pe "s/^\/tmp\/(.+):(\d+):(\d+) (error|warning) (.*)/::warning file=api\/\1,line=\2,col=\3::\4 \5/"
else
FLAKE8_FORMAT := default
endif

# By default, all python/poetry commands will run inside of the docker container
# if you wish to run this natively, add PY_RUN_APPROACH=local to your environment vars
# You can set this by either running `export PY_RUN_APPROACH=local` in your shell or add
# it to your ~/.zshrc file (and run `source ~/.zshrc`)
ifeq "$(PY_RUN_APPROACH)" "local"
PY_RUN_CMD := poetry run
else
PY_RUN_CMD := docker-compose run $(DOCKER_EXEC_ARGS) --rm $(APP_NAME) poetry run
endif

##################################################
# API Build & Run
##################################################

build:
docker-compose build

start:
docker-compose up --detach

run-logs: start
docker-compose logs --follow --no-color $(APP_NAME)


init: build init-db

clean-volumes: ## Remove project docker volumes (which includes the DB state)
docker-compose down --volumes

stop:
docker-compose down

##################################################
# DB & migrations
##################################################

#########################
# DB running / setup
#########################

# Docker starts the image for the DB but it's not quite
# fully ready, so add a 5 second sleep so upgrade doesn't
# fail because the DB hasn't started yet.
init-db: start-db sleep-5 db-upgrade

start-db:
docker-compose up --detach main-db

## Destroy current DB, setup new one
db-recreate: clean-docker-volumes init-db

#########################
# DB Migrations
#########################

alembic_config := ./api/db/migrations/alembic.ini
alembic_cmd := $(PY_RUN_CMD) alembic --config $(alembic_config)

db-upgrade: ## Apply pending migrations to db
$(PY_RUN_CMD) db-migrate-up

db-downgrade: ## Rollback last migration in db
$(PY_RUN_CMD) db-migrate-down

db-downgrade-all: ## Rollback all migrations
$(PY_RUN_CMD) db-migrate-down-all

check-migrate-msg:
ifndef MIGRATE_MSG
$(error MIGRATE_MSG is undefined)
endif

db-migrate-create: check-migrate-msg
$(alembic_cmd) revision --autogenerate -m "$(MIGRATE_MSG)"

MIGRATE_MERGE_MSG := Merge multiple heads
db-migrate-merge-heads: ## Create a new migration that depends on all existing `head`s
$(alembic_cmd) merge heads -m "$(MIGRATE_MERGE_MSG)" $(args)

db-migrate-current: ## Show current revision for a database
$(alembic_cmd) current $(args)

db-migrate-history: ## Show migration history
$(alembic_cmd) history $(args)

db-migrate-heads: ## Show migrations marked as a head
$(alembic_cmd) heads $(args)

##################################################
# Testing
##################################################

test:
$(PY_RUN_CMD) pytest $(args)

##################################################
# Formatting and linting
##################################################

format:
$(PY_RUN_CMD) isort --atomic api tests
$(PY_RUN_CMD) black api tests

lint: lint-spectral lint-py

lint-py: lint-flake lint-mypy lint-poetry-version

lint-flake:
$(PY_RUN_CMD) flake8 --format=$(FLAKE8_FORMAT) api tests

lint-mypy:
$(PY_RUN_CMD) mypy --show-error-codes $(MYPY_FLAGS) api $(MYPY_POSTPROC)

lint-poetry-version: ## Check poetry version
grep --quiet 'lock-version = "1.1"' poetry.lock

lint-spectral:
docker run --rm --tty --cap-drop=ALL --network=none --read-only --volume=$(PWD):/tmp:ro \
stoplight/spectral:6 lint /tmp/openapi.yml --ruleset /tmp/.spectral.yaml $(SPECTRAL_POSTPROC)

test-coverage:
$(PY_RUN_CMD) coverage run --branch --source=api -m pytest $(args)
$(PY_RUN_CMD) coverage report

test-coverage-report: ## Open HTML test coverage report
$(PY_RUN_CMD) coverage html --directory .coverage_report
open .coverage_report/index.html

##################################################
# Scripts
##################################################

generate-pet-csv:
$(PY_RUN_CMD) generate-pet-csv

##################################################
# Miscellaneous Utilities
##################################################

# Pauses for 5 seconds
sleep-5:
sleep 5
Empty file added app/README.md
Empty file.
Empty file added app/api/__init__.py
Empty file.
34 changes: 34 additions & 0 deletions app/api/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env python3

# If __main__.py is present in a Python module, it will be executed by default
# if that module is executed, e.g., `python -m my.module`.
#
# https://docs.python.org/3/library/__main__.html

import api.app
import api.logging
from api.app_config import AppConfig
from api.util.local import load_local_env_vars

logger = api.logging.get_logger(__package__)


def main() -> None:
load_local_env_vars()
app_config = AppConfig()

api.logging.init(__package__)
logger.info("Running API Application")

app = api.app.create_app()

if app_config.environment == "local":
# If python files are changed, the app will auto-reload
# Note this doesn't have the OpenAPI yaml file configured at the moment
app.run(port=8080, use_reloader=True, reloader_type="stat")
else:
# Don't enable the reloader if non-local
app.run(port=8080)


main()
Loading

0 comments on commit 1339fa6

Please sign in to comment.