Skip to main content

Scaffolding

Pyproject.toml

Use poetry init to generate a pyproject.toml if you don't have one already. Run it from the repo root (it won't make a repo folder for you)., or run poetry new instead for brand new projects.

Here's a basic package setup for poetry.

When Poetry supports PEP-621 and PEP-631 some of these should be moved to [project] instead of [tool.poetry]. See this issue.

[tool.poetry]
name = "..."              # Name of the package
version = "0.1.0"
requires-python = "3.9"
description = "..."       # A short description
authors = ["Eivind Fonn <evfonn@gmail.com>"]
maintainers = ["Eivind Fonn <evfonn@gmail.com>"]
license = "..."           # I like LGPL-3.0-or-later
readme = "..."            # A filename in the root directory
homepage = "..."          # A githubpages URL or just the github URL
repository = "..."        # The github repo URL
documentation = "..."     # To githubpages if there is one
classifiers = []          # See https://pypi.org/classifiers/
                          # Add a classifier for the license too!
include = [
  "...",                  # Path to a file to include when building
  {                       # Or like this, where format is
    path = "...",         # "sdist" or "wheel" or both
    format = [],
  },
]

# This is the entry point that pip and other tools use.
# This is what invokes poetry to do the building.
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Compiled modules

Adding Cython modules is relatively easy. Add setuptools to build-system.requires, then add this section.

[tool.poetry.build]
generate-setup-file = true
script = "build.py"

Then the build.py file needs to provide for the Cython extension.

from setupools import Extension

from Cython.Build import cythonize

def build(setup_kwargs):
    setup_kwargs['ext_modules'] = cythonize(
        Extension("module", ["source.pyx"])
    )

Basically just update setup_kwargs as if you were using setuptools directly.

Dependencies

Add dependencies from the command line:

poetry add dependency
poetry add --group dev dependency
poetry add --optional -E extra -E extra dependency 

You can also specify that the dependency is required for certain combinations of Python version and/or platform. See poetry documentation for more details.

For specifying version requirements it's probably best to use ^ or ~. Use dep^version to require at least that version or later, but not a new major version number. Use dep~version to require at least that version or later, but not a new minor version number.

It's good to regularly run poetry update to update dependencies.

When Poetry supports PEP-621 and PEP-631 dependencies should be moved to [project] instead of [tool.poetry].

Development dependencies

The following packages should basically always be added as dev dependencies: ruff, mypy, pytest.

Pytest

For now you can configure pytest like this. Although there isn't that much to do to be honest.

[tool.pytest.ini_options]
testpaths = ["...", "..."]

When Pytest adds proper pyproject.toml support, options in the [tool.pytest] namespace will be supported as well.

Mypy

Here's a basic mypy configuration that should be ok to start with.

[tool.mypy]
plugins = ["numpy.typing.mypy_plugin"]   # If the package uses numpy heavily
files = ["source/**/*.py"]               # Saves having to specify paths at the CLI
disallow_untyped_defs = true
disallow_any_unimported = true
no_implicit_optional = true
check_untyped_defs = true
warn_return_any = true
show_error_codes = true

# Add path to stubs directory if you need to write stubs
mypy_path = "$MYPY_CONFIG_FILE_DIR/stubs"

You can add more specific module-level instructions by adding to tool.mypy.overrides:

[[tool.mypy.overrides]]
module = "some.module.path"
ignore_errors = true

Ruff

Use ruff both as a linter and as a formatter. See here for a full list of rules. It's probably a good idea to select per project what might be most useful.

[tool.ruff]
line-length = 110
exclude = []             # List of files to ignore (these are paths, not modules)

[tool.ruff.lint]
select = [
    "F",        # Pyflakes rules
    "W",        # PyCodeStyle warnings
    "E",        # PyCodeStyle errors
    "I",        # Sort imports properly
    "UP",       # Warn if certain things can changed due to newer Python versions
    "C4",       # Catch incorrect use of comprehensions, dict, list, etc
    "FA",       # Enforce from __future__ import annotations
    "ISC",      # Good use of string concatenation
    "ICN",      # Use common import conventions
    "RET",      # Good return practices
    "SIM",      # Common simplification rules
    "TID",      # Some good import practices
    "TCH",      # Enforce importing certain types in a TYPE_CHECKING block
    "PTH",      # Use pathlib instead of os.path
    "TD",       # Be diligent with TODO comments
    "NPY",      # Some numpy-specific things
]
ignore = [
    "E741",     # Ambiguous variable name
    "UP007",    # Only if your project targets Python 3.9 or earlier
    "SIM115",   # Complains if we use __enter__ inside an __enter__ method
    "ISC001",   # Conflicts with rust formatting
    "TD003",    # Issue links for each todo comment
]