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
    │   ├── 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

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": "2024-08-13T01:23:58.807236"
  },
  {
    "id": 2,
    "username": "michaelherman",
    "email": "[email protected]",
    "created_date": "2024-08-13T01:23:58.807236"
  }
]

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.12.5, pytest-8.3.2, pluggy-1.5.0
rootdir: /usr/src/app/src/tests
configfile: pytest.ini
plugins: xdist-3.6.1, cov-5.0.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.12.5-final-0 -----------
Name                        Stmts   Miss Branch BrPart  Cover
-------------------------------------------------------------
src/__init__.py                21      1      4      0    96%
src/api/__init__.py             6      0      0      0   100%
src/api/ping.py                 6      0      0      0   100%
src/api/users/__init__.py       0      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     44      0   100%
src/config.py                  16      1      2      1    89%
-------------------------------------------------------------
TOTAL                         158      2     52      2    98%


============================== 34 passed in 0.53s ==============================

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 this:

├── .gitignore
├── .gitlab-ci.yml
├── README.md
├── 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:

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_DEBUG=1
      - FLASK_ENV=development
      - APP_SETTINGS=src.config.DevelopmentConfig
      - DATABASE_URL=postgresql://postgres:postgres@api-db:5432/api_dev
      - DATABASE_TEST_URL=postgresql://postgres:postgres@api-db: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 the images 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.1 200 OK
Connection: close
Content-Length: 316
Content-Type: application/json
Date: Tue, 13 Aug 2024 01:51:06 GMT
Server: Werkzeug/3.0.3 Python/3.12.5

[
    {
        "created_date": "2024-08-13T01:50:58.561836",
        "email": "[email protected]",
        "id": 1,
        "username": "michael"
    },
    {
        "created_date": "2024-08-13T01:50:58.561836",
        "email": "[email protected]",
        "id": 2,
        "username": "michaelherman"
    }
]

GET single user:

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

HTTP/1.1 200 OK
Connection: close
Content-Length: 128
Content-Type: application/json
Date: Tue, 13 Aug 2024 01:51:43 GMT
Server: Werkzeug/3.0.3 Python/3.12.5

{
    "created_date": "2024-08-13T01:50:58.561836",
    "email": "[email protected]",
    "id": 1,
    "username": "michael"
}

POST:

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

HTTP/1.1 201 CREATED
Connection: close
Content-Length: 54
Content-Type: application/json
Date: Tue, 13 Aug 2024 01:52:14 GMT
Server: Werkzeug/3.0.3 Python/3.12.5

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

PUT:

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

HTTP/1.1 200 OK
Connection: close
Content-Length: 36
Content-Type: application/json
Date: Tue, 13 Aug 2024 01:52:32 GMT
Server: Werkzeug/3.0.3 Python/3.12.5

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

DELETE:

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

HTTP/1.1 200 OK
Connection: close
Content-Length: 46
Content-Type: application/json
Date: Tue, 13 Aug 2024 01:52:54 GMT
Server: Werkzeug/3.0.3 Python/3.12.5

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



Mark as Completed