Python With Docker-Compose | FastAPI [Part-2]
By: Ahmed Nafies
Tags: Programming, Coding, Fastapi, Python, Web Development

Intro

In this article we will cover how to create a python based REST API and utilise docker-compose to improve both development and production. However before you start reading this article, I would recommend understanding how to use python with docker in part-1.

Docker Compose

Docker compose is a tool to manage multiple containers in a one YAML file and manage these containers with one command and thus drastically simplifying our development.

In the previous tutorial, we created two containers, a container for python REST API and container for PostgreSQL-DB. We had to build the image for our python API every-time we changed the code, we had to run each container separately and manually insuring that out database container is running first. Moreover, We had to create a network before hand so that we connect the containers and we had to add these containers to that network and we called it mynet back then. With docker-compose we can forget about all of that.

Let’s see how we can utilise docker-compose

In the directory you should have the following files

main.py Dockerfile pipfile pipfile.lock

main.py

import uvicorn

from fastapi import FastAPI, Depends

from pydantic import BaseModel

from sqlalchemy import create_engine, Column, Integer, String

from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.orm import sessionmaker, Session SQLALCHEMY_DATABASE_URL = "postgresql://postgres:postgres@postgres/postgres" engine = create_engine(SQLALCHEMY_DATABASE_URL)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() class UserModel(Base):

__tablename__ = "users" id = Column(Integer, primary_key=True, index=True)

first_name = Column(String, unique=True, index=True)

last_name = Column(String)

age = Column(Integer,) Base.metadata.create_all(bind=engine) def get_db():

db = SessionLocal()

try:

yield db

finally:

db.close() app = FastAPI() class UserSchema(BaseModel):

first_name: str

last_name: str = None

age: int class Config:

orm_mode = True

async def create_user(user: UserSchema, db: Session = Depends(get_db)):

_user = UserModel(

first_name=user.first_name, last_name=user.last_name, age=user.age

)

db.add(_user)

db.commit()

db.refresh(_user)

return _user @app .post("/user/", response_model=UserSchema)async def create_user(user: UserSchema, db: Session = Depends(get_db)):_user = UserModel(first_name=user.first_name, last_name=user.last_name, age=user.agedb.add(_user)db.commit()db.refresh(_user)return _user

async def get_user(first_name: str, db: Session = Depends(get_db)):

_user = db.query(UserModel).filter_by(first_name=first_name).first()

return _user @app .get("/user/", response_model=UserSchema)async def get_user(first_name: str, db: Session = Depends(get_db)):_user = db.query(UserModel).filter_by(first_name=first_name).first()return _user if __name__ == "__main__":

uvicorn.run(app, host="0.0.0.0", port=8000)

Dockerfile

# Pull base image

FROM python:3.7

# Set environment varibles

ENV PYTHONDONTWRITEBYTECODE 1

ENV PYTHONUNBUFFERED 1

WORKDIR /code/

# Install dependencies

RUN pip install pipenv

COPY Pipfile Pipfile.lock /code/

RUN pipenv install --system --dev

COPY . /code/

EXPOSE 8000

CMD ["python", "main.py"]

Now lets add docker-compose.yml file

version: "3" services:

postgres:

image: postgres:11

ports:

- "5432:5432"

environment:

- POSTGRES_USER=postgres

- POSTGRES_PASSWORD=postgres

- POSTGRES_DB=postgres web:

build: .

volumes:

- .:/code

ports:

- "8000:8000"

depends_on:

- postgres

The above configuration will build both docker images and run both containers with one command.

$ docker-compose up --build

As you can see, docker-compose took care of everything (networks, image, containers .. etc. But have you noticed the following.

depends_on:

- postgres

This is a health check for postgres , to make sure that postgres is up and running before we run our app container.

Now lets add another container for pgadmin4 which is a graphical interface management tool for postgres

version: "3" services:

postgres:

image: postgres:11

ports:

- "5432:5432"

environment:

- POSTGRES_USER=postgres

- POSTGRES_PASSWORD=postgres

- POSTGRES_DB=postgres app:

build: .

volumes:

- .:/code

ports:

- "8000:8000"

depends_on:

- postgres pgadmin:

container_name: pgadmin

image: dpage/pgadmin4

environment:

- PGADMIN_DEFAULT_EMAIL=pgadmin4@pgadmin.org

- PGADMIN_DEFAULT_PASSWORD=admin

ports:

- "5050:80"

depends_on:

- postgres