Putting Your Trash Code in a Garbage Bag
A package on an image. It's a beautiful thing. First you are packaging up the code and then you are packaging up the dependencies. I once saw this meme where there was two images. On one side was a bunch of trash. And on the other side was trash placed in garbage bags. The caption was my code pre and post object oriented programming.
Anyways—
Let's start by creating a new poetry project:
poetry new pack
This will create the following directory structure:
pack/
├── pack/
│ └── __init__.py
├── tests/
│ └── __init__.py
└── pyproject.toml
Creating Your Package
Since we are really original people, we are going to create a package that says "hello world". Add this to the __init__.py
under the pack directory:
def greet(name):
return f"Hello, {name}!"
And let's add a test in pytest while we are at it (under test_greet.py
in the tests folder):
from my_package import greet
def test_greet():
assert greet("World") == "Hello, World!"
Then we need to add pytest
to our dependencies:
poetry add pytest
Afterward your pyproject.toml
should look like this:
[tool.poetry]
name = "pack"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
pytest = "^8.3.3"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Add an Entry Point
An entry point defines what will run when you run the package directly. Like if you do python run.py
and run.py is simply filled with functions, you would need an entry point to allow those functions to run.
Let's add one for our package. First put this in the __init__.py
for the package:
def greet(name):
return f"Hello, {name}!"
def main():
# Example usage of the greet function
name = "World" # You can modify this to take user input if desired
print(greet(name))
if __name__ == "__main__":
main()
Now main will run when the package runs. Next add this to the bottom of the pyproject.toml:
[tool.poetry.scripts]
pack-cli = "pack:main" # This defines the CLI command
Install dependencies and run pytest locally.
poetry install
poetry run pytest
Building the Docker Image
Ok now we have our trash code and we need to put it in its trash bags:
# Use the official Python image from the Docker Hub
FROM python:3.12-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Set the working directory inside the container
WORKDIR /app
# Copy poetry.lock and pyproject.toml files first for better caching of dependencies
COPY pyproject.toml poetry.lock* ./
# Install Poetry and dependencies
RUN pip install --no-cache-dir poetry && poetry install --no-dev
# Copy the rest of the application code, including tests
COPY . .
# Run tests using pytest (optional)
RUN poetry run pytest -v
# Command to run your application using the entry point defined in pyproject.toml
CMD ["poetry", "run", "pack-cli"] # This runs your CLI command defined in pyproject.toml
This pretty much does what we saw with the flask app. It installs the dependencies we need. Copies over the directory content. But now we run pytest
as part of the build step and run our package as our CMD.
Let's build and test it!
docker build -t pack .
docker run pack