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 using

1
$ 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
7
my-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 CLI

1
$ 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 example

1
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 example

1
$ 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 run

1
$ 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 run

1
$ 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 run

1
$ poetry run python script.py

or

1
$ 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 running

1
$ 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 type

1
$ 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 dependencies

1
$ poetry install

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
25
import 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
16
from 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 terminal

1
$ poetry run pytest

Poetry Run Pytest

You can even pass Pytest verbosity flags or any other Pytest CLI flags normally.

1
$ poetry run pytest -v -s

Poetry Run Pytest Verbose

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 run

1
$ poetry shell

This will spin up a virtual environment shell within the current terminal (creates one if it doesn’t exist).

Poetry Shell

You can now run commands as you normally would — the dependencies are already installed.

Poetry Shell Run Pytest

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 use poetry 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 your pyproject.toml and poetry.lock files.
  • poetry install — Reads the pyproject.toml file from the current project, resolves the dependencies, and installs them. If a poetry.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 the pyproject.toml and lock file.
  • poetry version minor— Bumps the minor version of your project (according to semantic versioning). Similar for MAJOR or PATCH .
  • 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.

Poetry Build

Poetry Build Dist

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 using

1
$ 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