In Part 1 of this series, we created a simple Python distribution package called "pygreet." In Part 2, we wrote tests and ran them using pytest.
Other than pytest, we did not install anything that wasn't already included in the Python standard library. Often, though, a package will have several dependencies. How do we best handle these?
Simply put, place package dependencies in the install_requires
list in setup.py
. Place development dependencies, such as test frameworks and linters, in requirements.txt
.
Let's explore further.
Add a function with a dependency
Take the pygreet package we have been building and add a friendly time-telling function.
"""Functions useful for sending greetings."""
import arrow
def greet(greeting="Hello", recipient="World"):
"""Greet someone."""
return f"{greeting}, {recipient}"
def greet_city(tz):
"""Greet a location."""
now = arrow.now(tz)
friendly_time = now.format("h:mm a")
location = tz.split("/")[-1].replace("_"," ")
greeting = greet("Hello", location)
greeting += f"! The time is {friendly_time}."
return greeting
We have added two things: import arrow
and the greet_city
function, which takes a timezone as input.
Include list of dependencies in install_requires
in setup.py
Because arrow is an external dependency, let's add it to setup.py
:
"""Package configuration."""
from setuptools import find_packages, setup
setup(
name="pygreet",
version="0.1",
packages=find_packages(where="src"),
package_dir={"": "src"},
install_requires=["arrow"],
)
(Re-)installing package also installs dependencies
Once this is saved, installing our package again (in editable mode, just as before) should bring in arrow and its dependencies.
$ pip install -e .
The output should include "Successfully installed arrow..." and other packages, with version numbers.
Test
We cannot forget to write a test for our new function. In the tests
directory, a new file called test_greet_city.py
contains:
import greet
def test_greet_city():
result = greet.greet_city("Asia/Shanghai")
assert "Hello, Shanghai! The time is" in result
Run pytest
in the current directory. Feel proud.
Development dependencies
So, place dependent packages your software needs to run in the install_requires
list in setup.py
.
Dependencies you the developer want in order to build your software go in requirements.txt
.
Such as pytest. Of course, if you are installing the development dependencies you need, then you will also want the actual package you are building to be installed as well. So, let's add both in a requirements.txt
file:
-e .
pytest
You can run this file with:
pip install -r requirements.txt
I like to think of requirements.txt
this way: a list of arguments, each of which is passed to pip
, line by line. So, running the above requirements.txt
file is equivalent to:
pip install -e .
pip install pytest
Your source code tree should look something like this now:
pygreet/
├── requirements.txt
├── setup.py
├── src
│ └── greet.py
├── tests
│ ├── test_greet.py
│ └── test_greet_city.py
└── venv
The next time you are on a fresh system, you can download/checkout/pull your Python code, setup your virtual environment, pip install
your requirements, and keep coding.
Versions and upgrading
I have chosen not to specify versions of each package, or upper/lower bounds for version numbers. This way, at any time, I can
pip install -Ur requirements.txt
And upgrade all the packages. This works great, unless I am not paying attention to breaking API changes in any of the dependent packages. If that happens, there will be pain.
The Python packaging guide details usage of both install_requires
and requirements.txt
.
You might also try pip freeze
to see package versions. You may use the pip freeze
output to create a requirements.txt
file with pinned versions. That way, there will be no surprises, although your packages will grow out of date and insecure unless you update the versions.
This series is introductory in nature, and I hope you find it helpful. Please continue to explore, and enjoy, Python packaging and dependency management!
Top comments (0)