Packaging¶
Packaging your code is the process of organizing your project files so that it can be built and installed by other users. As well as your source code, there are a few files that are needed so that your project “pip-installable”, i.e. can be installed with pip and uploaded to PyPI.
While the Python Packaging Authority (PyPA) provides a comprehensive guide on packaging Python projects, this project instead uses Poetry. Poetry is one of many tools that can be used to manage Python packages and environments. It simplifies/automates a lot of small aspects than if we were to use approach described by PyPA.
uv is another popular tool for Python packaging, written in Rust.
Poetry setup¶
As a first-time setup, Poetry needs to be installed. The process is described on
this page. I recommend using the
pipx method:
Install
pipx: https://pipx.pypa.io/stable/installation/Install Poetry:
pipx install poetry
Project structure¶
A typical (minimal) file structure for a Python project to be “pip-installable” looks like so:
project/
|-- __init__.py
|-- code.py
|-- # other files
README.rst # or .md, .txt, etc
pyproject.toml # or setup.cfg or setup.py
where:
The folder
projectcontains your source code files and an__init__.py, which could be empty.README.rstwhich is not necessary for building the Python, but is often the first file that new users will look into to learn about the project, how to install it, and how to use it. On GitHub and PyPI, it will be rendered as the “homepage” for your project.pyproject.tomlis a configuration file for building the project. More on that below. In the past, Python projects usedsetup.pyfor this purpose, butpyproject.tomlis becoming more and more the norm. As this project used to usesetup.py, more on that can be found below.
Project Management and Packaging with Poetry¶
This page provides an overview of how to use Poetry:
For a new project, you can run
poetry new poetry-demo. This will create a new folder with the project structure described above, including an emptytestsfolder.For an existing project, you can run
poetry init. Through the command line, you will be asked a series of questions to fill in thepyproject.tomlfile.
The project can then be built (locally) with the command below:
(project_env) poetry install
The project can be imported in your Python script as:
import project
Note
One useful feature of Poetry is that with a single command you can
install new dependencies and update the pyproject.toml file.
For example, to install pandas and add to pyproject.toml,
you can run:
(project_env) poetry add pandas
You can also create groups of dependencies, e.g. for development dependencies, by running:
(project_env) poetry add --group dev sphinx
So that during installation, you can specify which group of dependencies:
(project_env) poetry install --with dev
More on managing dependencies can be found here.
pyproject.toml¶
Using a pyproject.toml file is not unique to Poetry, but is becoming
more and more the norm for Python projects. Below is the pyproject.toml
file for this project.
1[tool.poetry]
2name = "pydevtips"
3version = "0.0.4"
4description = "Functions and scripts to demonstrate Python development tips."
5authors = ["Eric Bezzam <ebezzam@gmail.com>"]
6license = "MIT"
7
8# -- manually added --
9readme = "README.rst"
10package-mode = true # https://python-poetry.org/docs/basic-usage/#operating-modes
11# --------------------
12
13[tool.poetry.dependencies]
14python = "^3.10"
15numpy = "^2.1.2"
16scipy = "^1.14.1"
17matplotlib = "^3.9.2"
18hydra-core = "^1.3.2"
19tqdm = "^4.66.5"
20
21
22[tool.poetry.group.dev.dependencies]
23black = "^24.10.0"
24isort = "^5.13.2"
25flake8 = "^7.1.1"
26pytest = "^8.3.3"
27pre-commit = "^4.0.1"
28twine = "^5.1.1"
29
30[build-system]
31requires = ["poetry-core"]
32build-backend = "poetry.core.masonry.api"
33
34#### -- manually added (below)
35
36[project.urls]
37Homepage = "https://github.com/ebezzam/python-dev-tips"
38Issues = "https://github.com/ebezzam/python-dev-tips/issues"
39Documentation = "https://pydevtips.readthedocs.io"
40
41[tool.isort]
42profile = "black"
43
44[tool.black]
45line-length = 100
46include = '\.pyi?$'
47exclude = '''
48/(
49 \.git
50 | build
51 | dist
52)/
53'''
Everything except the sections labeled with “manually added” was automatically generated by Poetry.
More info on configuring the pyproject.toml file can be found at the links below:
Poetry configuration: https://python-poetry.org/docs/pyproject/
Publishing to PyPI with Poetry¶
Poetry also makes building and deploying to PyPI easy. This article sums up the process well:
(First-time) setup:
Create an account on PyPI: https://pypi.org/account/register/
Create a token: https://pypi.org/manage/account/
Add the token to Poetry:
poetry config pypi-token.pypi <your-token>
Update the version number in the
pyproject.tomlfile. See Semantic Versioning for recommendations on picking version numbers.Build the package:
poetry buildUpload to PyPI:
poetry publish. Check https://pypi.org/project/pydevtips/ 🎉
If there are issues in publishing to PyPI, you can check the logs with:
poetry publish --dry-run
Or if there are issues with rendering the README file, you can check the logs with (twine is required):
twine check dist/pydevtips-X.X.X.tar.gz # replace X.X.X with version number
Creating a new release on GitHub¶
For a new release, you need to create a new tag on GitHub. This can be done with the commands below:
git tag -a X.X.X -m "version X.X.X"
git push origin X.X.X
You can create a new release with the following steps:
From the tags page, click (the rightmost) “…” dropdown menu.
Select “Create release”.
At the bottom, press “Publish release”.
setup.py (old way)¶
The use of setup.py is an older way to Python projects, e.g, with setuptools.
Below is a previously-used setup.py file for this project.
1import setuptools
2
3with open("README.rst", "r", encoding="utf-8") as fh:
4 long_description = fh.read()
5
6setuptools.setup(
7 name="pydevtips",
8 version="0.0.2",
9 author="Eric Bezzam",
10 author_email="ebezzam@gmail.com",
11 description="Functions and scripts to demonstrate Python development tips.",
12 long_description=long_description,
13 long_description_content_type="text/x-rst",
14 url="https://github.com/ebezzam/python-dev-tips",
15 packages=setuptools.find_packages(),
16 classifiers=[
17 "Programming Language :: Python :: 3",
18 "Operating System :: OS Independent",
19 ],
20 python_requires=">=3.8",
21 install_requires=[
22 "numpy",
23 "scipy",
24 "matplotlib",
25 "hydra-core",
26 "tqdm",
27 ],
28 include_package_data=True,
29)
Lines 3-4: use the contents of
README.rstto be rendered for the homepage on PyPI. Be sure to set the correct file extension and set Line 13 accordingly.Line 7: specifies the name of the package / in which folder the source code is located, such that the package can be installed with
pip install pydevtipsif on PyPI and imported asimport pydevtips.Line 8: sets the package version, which should be (typically) modified before uploading a new version to PyPI (below).
Line 9-10: for your name and contact info.
Line 20-26: specifies the Python version and package dependencies.
The project can then be built (locally) with the command below:
(project_env) pip install -e .
The project can be imported in your Python script as:
import project
For a more in-depth description, check out this article.
Uploading your project to PyPI is traditionally done with the twine library.
# inside virtual environment
(project_env) pip install twine
In the steps below, replace “X.X.X” with the appropriate version number, matching the one
in your setup.py file. See Semantic Versioning for
recommendations on picking version numbers.
# edit version in `setup.py`
# build package
(project_env) python setup.py sdist bdist_wheel
# -- creates zip in dist folder
# upload to pypi
(project_env) python -m twine upload dist/pydevtips-X.X.X.tar.gz
# -- X.X.X is the version number in setup.py
# -- enter username and password
# -- check https://pypi.org/project/pydevtips/X.X.X/
# new tag on GitHub
git tag -a X.X.X -m "version X.X.X"
git push origin X.X.X
If you project is hosted on GitHub, you can create a new release by:
Clicking (the rightmost) “…” dropdown menu (from the tags page).
Selecting “Create release”.
At the bottom pressing “Publish release”.