Getting Started

Part 1, Chapter 3


In this chapter, we'll set up the base project structure.


To speed up development, we'll start with a pre-built Flask API.

The code for this comes from the RESTful API that was built in the Test-Driven Development with Python, Flask, and Docker course. Be sure to review the course for more details on how it was implemented.

Start by cloning down the project:

$ git clone https://gitlab.com/testdriven/flask-tdd-docker.git flask-react-auth
$ cd flask-react-auth

Main Commands

Build the images:

$ docker-compose build

Take a quick look at the project structure, while the images are building:

├── .coverage
├── .coveragerc
├── .dockerignore
├── .gitignore
├── .gitlab-ci.yml
├── Dockerfile
├── Dockerfile.prod
├── README.md
├── docker-compose.yml
├── entrypoint.sh
├── htmlcov
├── manage.py
├── project
│   ├── __init__.py
│   ├── api
│   │   ├── __init__.py
│   │   ├── ping.py
│   │   └── users
│   │       ├── __init__.py
│   │       ├── admin.py
│   │       ├── models.py
│   │       └── views.py
│   ├── config.py
│   ├── db
│   │   ├── Dockerfile
│   │   └── create.sql
│   └── tests
│       ├── __init__.py
│       ├── conftest.py
│       ├── pytest.ini
│       ├── test_admin.py
│       ├── test_config.py
│       ├── test_ping.py
│       ├── test_users.py
│       └── utils.py
├── release.sh
├── requirements.txt
└── setup.cfg

Important things to note:

  1. The "project" directory holds the Flask API, configuration, and tests.
  2. The docker-compose.yml file holds the configuration for building the images and for the Flask app (based on the Dockerfile in the project root) and a Postgres database.

Run the containers once the build is complete:

$ docker-compose up -d

Navigate to http://localhost:5001/ping in your browser.

You should see:

{
  "message": "pong!",
  "status": "success"
}

Create the database:

$ docker-compose exec users python manage.py recreate_db

Seed the database:

$ docker-compose exec users python manage.py seed_db

Navigate to http://localhost:5001/users.

You should see:

{
  "status": "success",
  "data": {
    "users": [
      {
        "id": 1,
        "username": "michael",
        "email": "[email protected]",
        "active": true
      },
      {
        "id": 2,
        "username": "michaelherman",
        "email": "[email protected]",
        "active": true
      }
    ]
  }
}

Review, then run the tests with coverage:

$ docker-compose exec users pytest "project/tests" -p no:warnings --cov="project"

You should see:

======================================== test session starts ========================================
platform linux -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: /usr/src/app/project/tests, inifile: pytest.ini
plugins: cov-2.7.1
collected 20 items

project/tests/test_admin.py ..                                                                [ 10%]
project/tests/test_config.py ...                                                              [ 25%]
project/tests/test_ping.py .                                                                  [ 30%]
project/tests/test_users.py ..............                                                    [100%]

----------- coverage: platform linux, python 3.7.4-final-0 -----------
Name                            Stmts   Miss  Cover
---------------------------------------------------
project/__init__.py                20      1    95%
project/api/__init__.py             0      0   100%
project/api/ping.py                 8      0   100%
project/api/users/__init__.py       0      0   100%
project/api/users/admin.py          7      0   100%
project/api/users/models.py        19      0   100%
project/api/users/views.py         80      5    94%
project/config.py                  12      0   100%
---------------------------------------------------
TOTAL                             146      6    96%

Lint with Flake8:

$ docker-compose exec users flake8 project

Run Black and isort with check options:

$ docker-compose exec users black project --check
$ docker-compose exec users /bin/sh -c "isort project/*/*.py" --check-only

Need to make changes based on Black and isort recommendations?

$ docker-compose exec users black project
$ docker-compose exec users /bin/sh -c "isort project/*/*.py"

Other Commands

To stop the containers:

$ docker-compose stop

To bring down the containers:

$ docker-compose down

Want to force a build?

$ docker-compose build --no-cache

Remove images:

$ docker rmi $(docker images -q)

Postgres

Want to access the database via psql?

$ docker-compose exec users-db psql -U postgres

Then, you can connect to the database and run SQL queries. For example:

# \c users_dev
# select * from users;

Project Structure

Make sure you have a solid grasp of the project structure along with the above Docker commands. Again, review the Test-Driven Development with Python, Flask, and Docker course for more details.

Let's refactor the project structure before adding React.

Bring down the existing containers:

$ docker-compose down

Add a new folder called "services" to the project root. Within that folder, create a new folder called "users". Then move the following files and folders to the "users" folder:

  • .coverage
  • .coveragerc
  • .dockerignore
  • Dockerfile
  • Dockerfile.prod
  • entrypoint.sh
  • manage.py
  • "project"
  • requirements.txt
  • setup.cfg

The structure should now look like:

├── .gitignore
├── .gitlab-ci.yml
├── README.md
├── docker-compose.yml
├── release.sh
└── services
    └── users
        ├── .coverage
        ├── .coveragerc
        ├── .dockerignore
        ├── Dockerfile
        ├── Dockerfile.prod
        ├── entrypoint.sh
        ├── manage.py
        ├── project
        │   ├── __init__.py
        │   ├── api
        │   │   ├── __init__.py
        │   │   ├── ping.py
        │   │   └── users
        │   │       ├── __init__.py
        │   │       ├── admin.py
        │   │       ├── models.py
        │   │       └── views.py
        │   ├── config.py
        │   ├── db
        │   │   ├── Dockerfile
        │   │   └── create.sql
        │   └── tests
        │       ├── .pytest_cache
        │       │   ├── .gitignore
        │       │   ├── CACHEDIR.TAG
        │       │   ├── README.md
        │       │   └── v
        │       │       └── cache
        │       │           ├── nodeids
        │       │           └── stepwise
        │       ├── __init__.py
        │       ├── conftest.py
        │       ├── pytest.ini
        │       ├── test_admin.py
        │       ├── test_config.py
        │       ├── test_ping.py
        │       ├── test_users.py
        │       └── utils.py
        ├── requirements.txt
        └── setup.cfg

Make the following changes to docker-compose.yml:

version: '3.7'

services:

  users:
    build:
      context: ./services/users  # updated
      dockerfile: Dockerfile
    entrypoint: ['/usr/src/app/entrypoint.sh']
    volumes:
      - './services/users:/usr/src/app'  # updated
    ports:
      - 5001:5000
    environment:
      - FLASK_ENV=development
      - APP_SETTINGS=project.config.DevelopmentConfig
      - DATABASE_URL=postgresql://postgres:[email protected]:5432/users_dev
      - DATABASE_TEST_URL=postgresql://postgres:[email protected]:5432/users_test
    depends_on:
      - users-db

  users-db:
    build:
      context: ./services/users/project/db  # updated
      dockerfile: Dockerfile
    expose:
      - 5432
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres

Build and spin up the new containers:

$ docker-compose up -d --build

Ensure http://localhost:5001/ping still works.

Create and seed the database:

$ docker-compose exec users python manage.py recreate_db
$ docker-compose exec users python manage.py seed_db

Ensure http://localhost:5001/users works as well.

Run the tests, flake8, Black, and isort:

$ docker-compose exec users pytest "project/tests" -p no:warnings --cov="project"
$ docker-compose exec users flake8 project
$ docker-compose exec users black project --check
$ docker-compose exec users /bin/sh -c "isort project/*/*.py" --check-only
$ docker-compose exec users black project
$ docker-compose exec users /bin/sh -c "isort project/*/*.py"

Update the README:

# Authentication with Flask, React, and Docker

HTTPie

Finally, test all the routes with HTTPie.

GET all users:

$ http GET http://localhost:5001/users

HTTP/1.0 200 OK
Content-Length: 426
Content-Type: application/json
Date: Mon, 05 Aug 2019 04:33:07 GMT
Server: Werkzeug/0.15.5 Python/3.7.4

{
    "data": {
        "users": [
            {
                "active": true,
                "email": "[email protected]",
                "id": 1,
                "username": "michael"
            },
            {
                "active": true,
                "email": "[email protected]",
                "id": 2,
                "username": "michaelherman"
            }
        ]
    },
    "status": "success"
}

GET single user:

$ http GET http://localhost:5001/users/1

HTTP/1.0 200 OK
Content-Length: 159
Content-Type: application/json
Date: Mon, 05 Aug 2019 04:33:33 GMT
Server: Werkzeug/0.15.5 Python/3.7.4

{
    "data": {
        "active": true,
        "email": "[email protected]",
        "id": 1,
        "username": "michael"
    },
    "status": "success"
}

POST:

$ http --json POST http://localhost:5001/users username=someone email=[email protected]

HTTP/1.0 201 CREATED
Content-Length: 79
Content-Type: application/json
Date: Mon, 05 Aug 2019 04:36:05 GMT
Server: Werkzeug/0.15.5 Python/3.7.4

{
    "message": "[email protected] was added!",
    "status": "success"
}

PUT:

$ http --json PUT http://localhost:5001/users/3 username=foo email=[email protected]

HTTP/1.0 200 OK
Content-Length: 61
Content-Type: application/json
Date: Mon, 05 Aug 2019 04:36:47 GMT
Server: Werkzeug/0.15.5 Python/3.7.4

{
    "message": "3 was updated!",
    "status": "success"
}

DELETE:

$ http DELETE http://localhost:5001/users/3

HTTP/1.0 200 OK
Content-Length: 71
Content-Type: application/json
Date: Mon, 05 Aug 2019 04:37:28 GMT
Server: Werkzeug/0.15.5 Python/3.7.4

{
    "message": "[email protected] was removed!",
    "status": "success"
}



Mark as Completed