How To Run Pytest With Poetry (A Step-by-Step Guide)
Have you ever found it challenging trying to run Pytest or Python scripts via Poetry?
Perhaps Poetry fails to detect your virtual environment or uses the default Pytest version instead of the one you want.
How do you ensure Poetry runs your code within the virtual environment?
What about handling dependency management and lock files in Poetry? How do you group dependencies and manage them effectively?
This article answers all your Poetry and Python virtual environment questions.
You’ll learn how to install Poetry, set it up, manage dependencies, useful commands, and best practices for creating and publishing Python packages with Poetry.
As a bonus, you’ll also learn how to use Poetry with Tox to create a powerful testing workflow.
Let’s get into it.
What is a Virtual Environment?
A virtual environment is an isolated space that allows you to manage project-specific dependencies without affecting your global Python installation.
This is great because it avoids conflicts between different projects’ dependencies, ensuring each project has precisely what it needs to run correctly on any system - Windows, Mac, or Linux.
Some popular virtual environment and package management tooling in Python include venv
, conda
, pipenv
and poetry
.
The detail of how to use each tool is out of the scope of this article and we’ll focus purely on Poetry as it’s one of the newer and better tools out there.
What Is Poetry? (Why another tool)
If you’re wondering — There’s already pip
, venv
, conda
, pipenv,
and other dependency management tools, why do I need another one?
You’re right. This one is a bit different.
Poetry is a cool new tool that integrates features like dependency resolution, packaging, and virtual environment management into one.
Existing tools like pip
and venv
handle dependency management decently but suck at conflict resolution and require integration with external tools like setup.py
to build packages.
Unlike venv
, which only creates virtual environments, and conda
, which manages environments but is optimized for data science and handles non-Python packages, Poetry provides a more holistic approach.
Poetry uses the pyproject.toml
file for configuration and automatically creates and manages virtual environments, much like pipenv
, but also resolves dependencies in a deterministic manner.
With that in mind, let’s learn how to set up Poetry, install dependencies, start virtual environments, run tests, build packages, and more.
How To Use Poetry
Now let’s cut to the chase and explore how to set and use Poetry.
Install Poetry
To install Poetry, follow the instructions here from the official website.
The docs will guide you on how to use pipx
to install Poetry and set up tab completions for Bash, Fish, Zsh, or Oh My Zsh.
Project setup
You can create a new project using1
$ poetry new my-project
This will create a new Poetry project with the pyproject.toml
file and other scaffolding.1
2
3
4
5
6
7my-project
├── pyproject.toml
├── README.md
├── my_project
│ └── __init__.py
└── tests
└── __init__.py
Alternatively, if you already have a project you can run $ poetry init
to generate the pyproject.toml
file interactively.
Based on the Python version in your interpreter, Poetry will lock in that version in the TOML file ensuring that your project uses that version of Python when running.
I use Pyenv to manage Python versions locally and include a .python-version
file in the repo to help the interpreter detect the Python version automatically.
An example pyproject.toml
file is as below.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24[tool.poetry]
name = "mortgage-calculator"
version = "0.1.0"
description = "Simple mortgage calculator"
authors = ["<AUTHOR>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
numpy-financial = "^1.0.0"
[tool.poetry.group.test.dependencies]
pytest-randomly = "^3.15.0"
pytest = "^8.2.0"
[tool.poetry.group.dev.dependencies]
ruff = "^0.4.4"
black = "^24.4.2"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
The first section includes some metadata about the project like the name, version, description, and authors.
The tool.poetry.dependencies
section lists the project’s dependencies with the option to group them - test
and dev
in this case.
Lastly, the build-system
section specifies the build system requirements.
Install Dependencies/Packages
Now the biggest one and likely the reason you’re here is to understand how Poetry manages dependencies.
Dependencies can be specified in the tool.poetry.dependencies
section.
Instead of adding each dependency by hand, it’s cleaner to do it via the CLI1
$ poetry add pendulum
If a version is not specified, Poetry will use the latest version of the package.
You can also have dependency groups for example1
2
3[tool.poetry.group.test.dependencies]
pytest-randomly = "^3.15.0"
pytest = "^8.2.0"
We can add packages to groups using the -G
flag for example1
$ poetry add -G dev black
will add the black
package to the dev
dependencies group.
It’s a good idea to group dependencies, for example, dependencies needed to develop, test or run the app.
You can read up on dependencies and groups in the official documentation.
Lastly, to install a specific version of a package you can run1
$ poetry add django@^4.0.0
This would translate to the following entry in pyproject.toml
1
Django = "^4.0.0"
This means all PATCH and MINOR versions will be installed for example up to 4.X.X
but not 5.X.X
.
Or, you can also run1
$ poetry add django@latest
to add the latest package version of Django to your pyproject.toml
file.
You can read more about version constraints here.
The install
command reads the pyproject.toml
file from the current project, resolves the dependencies and installs them.1
$ poetry install
If there is a poetry.lock
file in the current directory, it will use the exact versions from there instead of resolving them.
This ensures that everyone using the library will get the same versions of the dependencies.
If there is no poetry.lock
file, Poetry will create one after dependency resolution.
There are several other CLI commands and flags that you can check out here.
How To Run Scripts And Tests With Poetry
Running Python scripts or Pytest via Poetry is also easy.
After installing your dependencies, you can run1
$ poetry run python script.py
or1
$ poetry run pytest
This is great. But having to include poetry
on every command is not too convenient.
No problem, you can start a virtual environment shell right within the project by running1
$ poetry shell
This will start up a shell in a .venv
folder in your local project with dependencies installed.
You can then run python
or pytest
CLI commands as normal.
Let’s illustrate the above with a simple example.
Real Example
Clone the repo here.
Alternatively, you can initialize a project via git or simply type1
$ poetry new <PROJECT_NAME>
into the terminal to have Poetry create one for you.
Once you have the project set up, you can use the existing pyproject.toml
file and dependencies or set one up interactively using poetry init
.
Install Dependencies
Next, run the below to install dependencies1
$ poetry install
Example Code
Our example code is a very simple Mortgage Calculator.
mortgage_calculator/calculator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import numpy_financial as npf
class MortgageCalculator:
def __init__(self, principal: float, interest_rate: float, term: int):
self.principal = principal
self.interest_rate = interest_rate
self.term = (
term * 12
) # Assuming 'term' was initially in years, converting it to months.
def calculate_monthly_payment_interest_only(self) -> float:
"""Calculates the monthly payment for an interest-only mortgage."""
return round(self.principal * (self.interest_rate / 12), 2)
def calculate_monthly_payment_repayment(self) -> float:
"""Calculates the monthly payment for a repayment mortgage using numpy-financial."""
monthly_rate = self.interest_rate / 12
payment = npf.pmt(monthly_rate, self.term, -self.principal)
return round(payment, 2)
def calculate_total_repayment(self) -> float:
"""Calculates the total amount to be repaid over the life of the mortgage."""
monthly_payment = self.calculate_monthly_payment_repayment()
return round(monthly_payment * self.term, 2)
This is a simple class that calculates
- monthly interest-only repayments
- monthly repayments
- total re-payment
We didn’t need to use numpy-financial
but I included it to show the use of dependencies.
Unit Tests
Our simple tests
tests/test_mortgage_calculator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from mortgage_calculator.calculator import MortgageCalculator
def test_calculate_monthly_payment_interest_only():
mortgage = MortgageCalculator(100000, 0.03, 30)
assert mortgage.calculate_monthly_payment_interest_only() == 250.0
def test_calculate_monthly_payment_repayment():
mortgage = MortgageCalculator(100000, 0.03, 30)
assert mortgage.calculate_monthly_payment_repayment() == 421.60
def test_calculate_total_repayment():
mortgage = MortgageCalculator(100000, 0.03, 30)
assert mortgage.calculate_total_repayment() == 151776.0
Now it’s time to run our tests with Poetry.
Run Tests With Poetry
To run your tests with Poetry, from the project root, run the below command in your terminal1
$ poetry run pytest
You can even pass Pytest verbosity flags or any other Pytest CLI flags normally.1
$ poetry run pytest -v -s
Run Tests in Poetry Shell
How about if you like things simple and prefer to call your scripts or run tests directly using python main.py
or run pytest
in the terminal?
That’s simple too.
First run1
$ poetry shell
This will spin up a virtual environment shell within the current terminal (creates one if it doesn’t exist).
You can now run commands as you normally would — the dependencies are already installed.
You can exit the shell by typing exit
.
Now that you’ve learned to run Pytest with Poetry, let’s look at some useful Poetry commands.
Useful Poetry Commands
poetry show
—Lists the packages installed in your current project’s virtual environment. You can usepoetry show --tree
to view dependencies in a tree format to help understand the hierarchical structure of package dependencies.poetry add
— Add new dependencies to your project. It automatically updates yourpyproject.toml
andpoetry.lock
files.poetry install
— Reads thepyproject.toml
file from the current project, resolves the dependencies, and installs them. If apoetry.lock
file exists, it will use the exact versions from there instead of resolving them.poetry env
— Shows information about the current environment or even removes virtual environments associated with the project.poetry shell
— Spawns a shell, like bash or zsh, within the virtual environment created by Poetry.poetry remove
— Removes a package that is no longer necessary from thepyproject.toml
and lock file.poetry version minor
— Bumps the minor version of your project (according to semantic versioning). Similar forMAJOR
orPATCH
.poetry publish
— Publishes a project to PyPI or another configured repository.
Using Poetry with Tox
If you’re not familiar with Tox, it’s a generic virtual environment management and test command line tool for (from the docs):
checking your package builds and installs correctly under different environments (such as different Python implementations, versions or installation dependencies),
running your tests in each of the environments with the test tool of choice,
acting as a frontend to continuous integration servers, greatly reducing boilerplate and merging CI and shell-based testing.
Poetry integrates well with tox and can be easily run as part of the pipeline.
We covered how to set up and run Poetry in tox
in great detail in this article, I encourage you to check it out.
Building Packages With Poetry
Building a package with Poetry is easy.
Step 1: Configure the pyproject.toml
Before you begin the building process, ensure your pyproject.toml
file is correctly set up.
This file contains all the metadata for your project, such as name, version, description, dependencies, and more.
Step 2: Add Files to Your Package
Organize your project files.
Ensure all the code you want to include in your package is within the project directory and is appropriately referenced in the pyproject.toml
.
Poetry will automatically include all files in the directory and subdirectories where the pyproject.toml
resides, unless specified otherwise.
Step 3: Build the Package
With your pyproject.toml
ready and files organized, you can build your package. Run the following command in your terminal:1
$ poetry build
This command will generate the distribution packages in the dist/
directory.
You’ll find there two types of files: a wheel (*.whl
), which is a built distribution that can be installed quickly, and a source archive (*.tar.gz
), which includes your source code and can be built by the end-user.
Step 4: Verify Your Build
After building the package, it’s a good idea to check the contents of your distribution files to ensure everything is included as expected.
You can inspect the contents of the wheel file using tools like wheel
or simply unpack the tarball:1
tar -xvf dist/example-package-0.1.0.tar.gz
Look through the unpacked files to make sure all your modules, data files, and metadata are correct.
Step 5: Ready for Distribution
Once verified, your package is ready to be published.
Use poetry publish
to upload it to a package index like PyPI, or a custom repository.
Users can then install it like any other package.
For more complete instructions including configuring your Pypi credentials, this detailed guide has you covered.
BONUS (Generate requirements.txt file)
Now how about if your project still requires a requirements.txt
file? Maybe you’re working with a team that’s not yet on board with Poetry or a legacy system that requires it.
No problem. You can easily generate one from Poetry using1
$ poetry export -f requirements.txt --output requirements.txt
This comes with a caveat though. The requirements.txt
file generated will only include the top-level dependencies and not the sub-dependencies.
The complete list of command-line options can be found here.
Conclusion
OK this is it. I hope you found this article useful and inspiring to start using Poetry for your Python projects.
In this article, you learned how to set up Poetry, install dependencies, run scripts and tests, build packages, and more. You learnt why Poetry was introduced and how it differs from other tools like pipenv
and venv
.
Next we covered a real example of a simple Mortgage Calculator project and how to run Pytest with Poetry.
Lastly we looked at some useful Poetry commands and how to generate a requirements.txt
file should you need it.
With this in mind, I highly encourage you to start using Poetry for your Python projects as it makes dependency management and packaging a breeze. And frankly once you start using it, you won’t look back.
If you have any ideas for improvement or like me to cover any topics please comment below or send me a message via Twitter, GitHub or Email.
Till the next time… Cheers!
Additional Reading
Poetry Documentation
Example Code Used
How to Create and Use Virtual Environments in Python With Poetry
How To Test And Build Python Packages With Pytest, Tox And Poetry
7 Simple Ways To Fix The “Pytest Command Not Found” Error