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:

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

Important things to note:

  1. The "src" 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:5004/ping in your browser.

You should see:

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

Create the database:

$ docker-compose exec api python manage.py recreate_db

Seed the database:

$ docker-compose exec api python manage.py seed_db

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

You should see:

[
    {
        "id": 1,
        "username": "michael",
        "email": "[email protected]",
        "created_date": "2020-11-14T00:42:27.670323"
    },
    {
        "id": 2,
        "username": "michaelherman",
        "email": "[email protected]",
        "created_date": "2020-11-14T00:42:27.670323"
    }
]

Review then run the tests with coverage:

$ docker-compose exec api python -m pytest "src/tests" -p no:warnings --cov="src"

You should see:

======================================== test session starts ========================================
platform linux -- Python 3.9.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: /usr/src/app/src/tests, configfile: pytest.ini
plugins: cov-2.10.1, forked-1.3.0, xdist-2.1.0
collected 34 items

src/tests/test_admin.py ..                                                                [  5%]
src/tests/test_config.py ...                                                              [ 14%]
src/tests/test_ping.py .                                                                  [ 17%]
src/tests/test_users.py ..............                                                    [ 58%]
src/tests/test_users_unit.py ..............                                               [100%]

----------- coverage: platform linux, python 3.9.0-final-0 -----------
Name                      Stmts   Miss Branch BrPart  Cover
-----------------------------------------------------------
src/__init__.py              21      1      2      0    96%
src/api/__init__.py           6      0      0      0   100%
src/api/ping.py               6      0      0      0   100%
src/api/users/admin.py        7      0      0      0   100%
src/api/users/crud.py        22      0      0      0   100%
src/api/users/models.py      17      0      2      1    95%
src/api/users/views.py       63      0     10      0   100%
src/config.py                13      0      0      0   100%
-----------------------------------------------------------
TOTAL                       155      1     14      1    99%


======================================== 34 passed in 1.02s =========================================

Lint with Flake8:

$ docker-compose exec api flake8 src

Run Black and isort with check options:

$ docker-compose exec api black src --check
$ docker-compose exec api isort src --check-only

Need to make changes based on Black and isort recommendations?

$ docker-compose exec api black src
$ docker-compose exec api isort src

Other Commands

To stop the containers:

$ docker-compose stop

To bring down the containers:

$ docker-compose down

Bring down the containers and volumes:

$ docker-compose down -v

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 api-db psql -U postgres

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

# \c api_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
  • requirements.txt
  • requirements-dev.txt
  • setup.cfg
  • "src"

The structure should now look like:

├── .gitignore
├── .gitlab-ci.yml
├── docker-compose.yml
├── release.sh
└── services
    └── users
        ├── .coverage
        ├── .coveragerc
        ├── .dockerignore
        ├── Dockerfile
        ├── Dockerfile.prod
        ├── entrypoint.sh
        ├── manage.py
        ├── requirements-dev.txt
        ├── requirements.txt
        ├── setup.cfg
        └── src
            ├── __init__.py
            ├── api
            │   ├── __init__.py
            │   ├── ping.py
            │   └── users
            │       ├── __init__,py
            │       ├── admin.py
            │       ├── crud.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
                └── test_users_unit.py

Make the following changes to docker-compose.yml:

version: '3.8'

services:

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

  api-db:
    build:
      context: ./services/users/src/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:5004/ping still works.

Create and seed the database:

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

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

Run the tests, flake8, Black, and isort:

$ docker-compose exec api python -m pytest "src/tests" -p no:warnings --cov="src"
$ docker-compose exec api flake8 src
$ docker-compose exec api black src --check
$ docker-compose exec api isort src --check-only
$ docker-compose exec api black src
$ docker-compose exec api isort src

Update the README:

# Authentication with Flask, React, and Docker

HTTPie

Finally, test all the routes with HTTPie.

GET all users:

$ http GET http://localhost:5004/users

HTTP/1.0 200 OK
Content-Length: 316
Content-Type: application/json
Date: Sat, 14 Nov 2020 01:04:14 GMT
Server: Werkzeug/1.0.1 Python/3.9.0

[
    {
        "created_date": "2020-11-14T01:02:17.785788",
        "email": "[email protected]",
        "id": 1,
        "username": "michael"
    },
    {
        "created_date": "2020-11-14T01:02:17.785788",
        "email": "[email protected]",
        "id": 2,
        "username": "michaelherman"
    }
]

GET single user:

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

HTTP/1.0 200 OK
Content-Length: 128
Content-Type: application/json
Date: Sat, 14 Nov 2020 01:04:30 GMT
Server: Werkzeug/1.0.1 Python/3.9.0

{
    "created_date": "2020-11-14T01:02:17.785788",
    "email": "[email protected]",
    "id": 1,
    "username": "michael"
}

POST:

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

HTTP/1.0 201 CREATED
Content-Length: 54
Content-Type: application/json
Date: Sat, 14 Nov 2020 01:04:45 GMT
Server: Werkzeug/1.0.1 Python/3.9.0

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

PUT:

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

HTTP/1.0 200 OK
Content-Length: 36
Content-Type: application/json
Date: Sat, 14 Nov 2020 01:05:12 GMT
Server: Werkzeug/1.0.1 Python/3.9.0

{
    "message": "3 was updated!"
}

DELETE:

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

HTTP/1.0 200 OK
Content-Length: 46
Content-Type: application/json
Date: Sat, 14 Nov 2020 01:05:34 GMT
Server: Werkzeug/1.0.1 Python/3.9.0

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



Mark as Completed