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:
- The "src" directory holds the Flask API, configuration, and tests.
- 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