How to Build and Publish Python Packages With Poetry
Python packages allow you to organize related code into reusable modules that can be easily shared and installed. They are a fundamental building block for creating scalable, maintainable Python applications.
Traditionally, packaging Python code meant wrangling setup.py files, requirements.txt, virtual environments, and more. But a tool called Poetry aims to simplify the entire Python packaging workflow. In this guide, we‘ll walk through how to use Poetry to create, manage, and publish Python packages.
What is Poetry?
Poetry is a modern Python dependency management and packaging tool that streamlines the process of creating and sharing Python packages. It uses a standardized format (pyproject.toml) for configuration and offers helpful commands for common package management tasks.
Some key features and benefits of Poetry include:
- Isolation of package dependencies in virtual environments
- Automatic dependency resolution to avoid conflicts
- Single source of truth for package metadata and dependencies (pyproject.toml)
- Simple commands for creating, building, and publishing packages
With Poetry, you can spend less time fiddling with packaging tools and more time focusing on writing great Python code. Let‘s see how it works.
Setting Up a Python Package With Poetry
We‘ll use Poetry to create an example Python package called "mathtools" with some handy math and stats functionality. First, make sure you have Poetry installed. You can find installation instructions for different platforms in the Poetry docs.
With Poetry ready, open your terminal and navigate to the directory where you want to create the package. Then run the following command:
poetry new mathtools
This will generate the basic structure for a Python package called "mathtools":
mathtools
├── mathtools
│ └── __init__.py
├── pyproject.toml
├── README.md
└── tests
└── __init__.py
The key files to note are:
- pyproject.toml – the Poetry config file where you define package metadata, dependencies, and more
- mathtools/init.py – the package entry point
- tests/ – a directory for unit tests
Next, open pyproject.toml and you should see some boilerplate configuration:
[tool.poetry]
name = "mathtools"
version = "0.1.0"
description = ""
authors = ["Your Name <[email protected]>"]
[tool.poetry.dependencies]
python = "^3.9"
[tool.poetry.dev-dependencies]
pytest = "^5.2"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Fill in a description, author details, and any other metadata. We‘ll also add NumPy as a package dependency. Update your pyproject.toml to look like this:
[tool.poetry]
name = "mathtools"
version = "0.1.0"
description = "A package providing math and statistics utilities"
authors = ["Emily Smith <[email protected]>"]
[tool.poetry.dependencies]
python = "^3.9"
numpy = "^1.22.3"
[tool.poetry.dev-dependencies]
pytest = "^7.1.1"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Notice we added NumPy under [tool.poetry.dependencies]. Also note that Poetry uses version constraints to specify dependency versions (e.g. ^1.22.3 means any version >= 1.22.3 but < 2.0.0).
To install the dependencies and create a virtual environment for the package, run:
poetry install
You‘re now ready to start implementing the package!
Implementing Package Functionality
Let‘s add some modules to the mathtools package. Create the following files and directories:
mathtools
├── mathtools
│ ├── __init__.py
│ ├── arithmetic.py
│ ├── stats.py
│ ├── random.py
│ └── linalg.py
├── pyproject.toml
├── README.md
└── tests
We‘ll put functions for basic arithmetic in arithmetic.py, statistical functions in stats.py, random number utilities in random.py, and matrix/vector operations in linalg.py.
Here‘s an example implementation of arithmetic.py:
def add(x, y):
"""Compute the sum of x and y."""
return x + y
def subtract(x, y):
"""Compute the difference of x and y."""
return x - y
def multiply(x, y):
"""Compute the product of x and y."""
return x * y
def divide(x, y):
"""Compute the quotient of x and y."""
if y == 0:
raise ValueError(‘Cannot divide by zero!‘)
return x / y
def power(x, y):
"""Compute x raised to the power of y."""
return x ** y
And here‘s stats.py using NumPy:
import numpy as np
def mean(data):
"""Compute the arithmetic mean of a dataset."""
return np.mean(data)
def median(data):
"""Compute the median (middle value) of a dataset."""
return np.median(data)
def mode(data):
"""Compute the mode (most common value) of a dataset."""
return np.argmax(np.bincount(data))
def variance(data):
"""Compute the variance of a dataset."""
return np.var(data)
def stdev(data):
"""Compute the standard deviation of a dataset."""
return np.std(data)
You can implement the other modules in a similar fashion. The key thing to note is that we‘re leveraging Poetry‘s automatic dependency management. We can import NumPy without worrying about manual installation – Poetry handles it for us behind the scenes.
Writing Unit Tests
To verify our package is working correctly, let‘s add some unit tests. Poetry generated a tests/ directory for us. Rename the existing test_mathtools.py to test_arithmetic.py and add the following test cases:
from mathtools.arithmetic import add, subtract, multiply, divide, power
import pytest
def test_add():
assert add(1, 2) == 3
assert add(-1, 1) == 0
assert add(0, 0) == 0
def test_subtract():
assert subtract(2, 1) == 1
assert subtract(-1, 1) == -2
assert subtract(0, 0) == 0
def test_multiply():
assert multiply(2, 3) == 6
assert multiply(-1, 1) == -1
assert multiply(0, 5) == 0
def test_divide():
assert divide(6, 2) == 3
assert divide(-6, 2) == -3
assert divide(5, 2) == 2.5
with pytest.raises(ValueError):
divide(1, 0)
def test_power():
assert power(2, 3) == 8
assert power(-2, 2) == 4
assert power(2, -2) == 0.25
We use pytest‘s assert statement to verify the expected outputs. We can also test exception handling using pytest.raises().
To run the test suite, simply type:
poetry run pytest
If all goes well, you should see output like:
=========================== test session starts ============================
platform darwin -- Python 3.9.12, pytest-7.1.1, pluggy-1.0.0
rootdir: /mathtools
collected 5 items
tests/test_arithmetic.py ..... [100%]
============================ 5 passed in 0.02s =============================
Congrats, you‘ve got a working, tested Python package! Now let‘s see how to share it with the world.
Publishing to PyPI
To make your package available to others, you can publish it to the Python Package Index (PyPI). First, make sure you have an account on https://pypi.org. Then create a PyPI API token from your account settings.
Next, configure Poetry to use your PyPI token by running:
poetry config pypi-token.pypi your-token
To build your package into a distribution, run:
poetry build
This will create two files in the dist/ directory: a source archive (.tar.gz) and a built distribution (.whl).
Finally, to upload your package to PyPI, simply type:
poetry publish
Assuming everything went smoothly, you should now see your package on PyPI! Others can install it using something like:
pip install mathtools
Or if they are also using Poetry:
poetry add mathtools
Wrapping Up
In this guide, we‘ve seen how Poetry can simplify the process of creating and publishing Python packages. We covered how to:
- Set up a new Python package project with Poetry
- Define package metadata and dependencies
- Implement modules and import third-party packages
- Write unit tests to verify functionality
- Publish a package to PyPI for others to use
Whether you‘re creating a simple utility or a complex library, Poetry provides a clean, standardized workflow for Python packaging. By adopting Poetry, you can spend less time on tedious packaging tasks and more time on doing what you love – writing great code!
To dive deeper into Poetry‘s features, check out the official documentation at https://python-poetry.org/docs/. You‘ll find info on advanced configuration, managing environments, scripts and plugins, and more.
I hope this guide has been a helpful introduction to Python packaging with Poetry. Now go forth and share your poetic Python creations with the world!