Installation and usage#

Packaging and distribution are key aspects of any Python project, and two that underwent a significant evolution over the last few years. The bottomline is: you should ship a pyproject.toml file with your project. For reference, the one we are distributing with this repository looks like

# See the Python packaging user guide for the ultimate reference on
# pyproject.toml, and more specifically
# https://packaging.python.org/en/latest/specifications/pyproject-toml/

# This section is mandatory in pyproject.toml for any buildable project.
# It specifies what tool(s) are needed to build your package and what
# backend to call for the actual build. hatchling
# https://github.com/pypa/hatch/tree/master/backend
# is a pretty popular choice.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

# This is probably the most important section, and the fields should
# be pretty much self-explaining. A couple of things:
# - declaring "version" as a dynamic field is handy because minimized
#   the places where the version string is hard-coded---we shall tell
#   hatch a few lines below which file we want to read to the version
#   string from;
# - you should list in this section all the dependencies your package
#   relies on, so they get automatically installed (if necessary) when
#   the package is pip-installed.
[project]
name = "metarep"
dynamic = ["version"]
description = "A repository about making repositories"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.7"
authors = [
  { name = "Luca Baldini", email = "luca.baldini@unipi.it" }
  ]
dependencies = [
  "numpy",
  ]

# Optional dependencies are listed here, grouped into logical sections.
# By default (e.g. upon "pip install .") optional dependencies are not
# installed, but they are when you do, e.g., "pip install .[dev]"
[project.optional-dependencies]
dev = [
    "pytest",
    "ruff",
    "pylint",
  ]
docs = [
    "sphinx",
    "sphinxawesome-theme",
  ]

# A few more useful fields...
[project.urls]
Homepage = "https://github.com/lucabaldini/metarep"
Issues = "https://github.com/lucabaldini/metarep/issues"

# Specify the python file that hatchling should read the version string from.
[tool.hatch.version]
path = "src/metarep/_version.py"

# ruff configuration, see
# https://docs.astral.sh/ruff/configuration/
# for the ultimate reference.
# Note we target the oldest Python version that we support, which helps
# in avoiding features that are only available in newer versions.
[tool.ruff]
target-version = "py37"
line-length = 100
src = ["src"]
exclude = ["docs"]

# By default the ruff configuration is fairly minimal, and you might
# want to specifically enable specific useful rules.
[tool.ruff.lint]
select = [
  "E",   # pycodestyle errors
  "F",   # pyflakes
  "I",   # isort (imports)
  "B",   # flake8-bugbear
  "UP",  # pyupgrade (kept safe for py37)
  "SIM", # flake8-simplify
  "C4",  # flake8-comprehensions
  "NPY", # NumPy-specific best practices
  "PERF" # performance gotchas
  ]
# And, of course, sometimes you want to disable rules that are enabled
# by default.
ignore = [
  "C408" # I sometimes like dict() calls better than literal dicts
  ]

# On the other hand, pylint is fairly noisy by default, and you might
# want to disable some rules.
[tool.pylint.'MESSAGES CONTROL']
disable = [
  "missing-docstring",
  "too-few-public-methods",
  "too-many-arguments",
  "too-many-positional-arguments",
  "too-many-instance-attributes",
  "too-many-locals",
  "use-dict-literal",
  ]

[tool.pylint.'BASIC']
good-names = ["i", "j", "k", "x", "y", "z"]

[tool.pylint.'FORMAT']
max-line-length = 100

pyproject files can be arbitrarily complex, and encompass any sort of meta information that goes along with your project, but, at the very basic level, they make your project installable. This means that including a properly formatted pyproject.toml file will allow a user to, e.g., pip install your package transprently—directly from PyPI, if you have added your package there,

pip install package

or locally at the very minimum

pip install .

This is it. All the files get copied into a place where they can be imported from, and it is the pyproject.toml file that makes that possible.

Note

If you have never heard about pip, the pip repo is a good starting point, and also a good example of a non-trivial Python package that you can take as an inspiration for your own endeavors.

Editable installation#

When you develop (as opposed to use) a package, you want to install it in editable mode.

pip install -e .

Invoking pip with the -e command-line switch will place a special link in the proper folder pointing back to you local version of the source files (instead of copying the source tree) so that you will always see the last version of the code as you modify it, e.g., in the local copy of your git repository. Needless to say, it is still the pyproject.toml file that makes all the magic.

Note

You can achieve the same result by just making sure that the PYTHONPATH environmental variable is pointing to the folder where your Python modules live, and in fact you might as well do that. That is not necessarily considered a good practice, as it departs completely from the installation path of a typical Python package that you use as a library, but you should still make sure you understand the basic internals of the Python import system.