Let's say that you're using Poetry to manage a Python PyPI package. As it stands, your project has a Makefile
that contains procedures for managing the installation, unit testing, and linting of your project. However, since this goes against the spirit of Poetry where you ideally have one configuration file to rule them all (pyproject.toml
), and because said Makefile
can be annoying on Windows builds, you would like to instead move the Makefile
functionality to be managed by Poetry directly by making use of Poetry's support for setuptools
scripts.
Here's an example of such a Makefile
:
PYMODULE := awesome_sauce
TESTS := tests
INSTALL_STAMP := .install.stamp
POETRY := $(shell command -v poetry 2> /dev/null)
MYPY := $(shell command -v mypy 2> /dev/null)
.DEFAULT_GOAL := help
.PHONY: all
all: install lint test
.PHONY: help
help:
@echo "Please use 'make <target>', where <target> is one of"
@echo ""
@echo " install install packages and prepare environment"
@echo " lint run the code linters"
@echo " test run all the tests"
@echo " all install, lint, and test the project"
@echo " clean remove all temporary files listed in .gitignore"
@echo ""
@echo "Check the Makefile to know exactly what each target is doing."
@echo "Most actions are configured in 'pyproject.toml'."
install: $(INSTALL_STAMP)
$(INSTALL_STAMP): pyproject.toml
@if [ -z $(POETRY) ]; then echo "Poetry could not be found. See https://python-poetry.org/docs/"; exit 2; fi
$(POETRY) run pip install --upgrade pip setuptools
$(POETRY) install
touch $(INSTALL_STAMP)
.PHONY: lint
lint: $(INSTALL_STAMP)
# Configured in pyproject.toml
# Skips mypy if not installed
@if [ -z $(MYPY) ]; then echo "Mypy not found, skipping..."; else echo "Running Mypy..."; $(POETRY) run mypy $(PYMODULE) $(TESTS); fi
@echo "Running Flake8..."; $(POETRY) run pflake8 # This is not a typo
@echo "Running Pylint..."; $(POETRY) run pylint $(PYMODULE)
.PHONY: test
test: $(INSTALL_STAMP)
# Configured in pyproject.toml
$(POETRY) run pytest
.PHONY: clean
clean:
# Delete all files in .gitignore
git clean -Xdf
But then, you face another problem; due to how these scripts operate, they're expected to be in a package or module of their own, so the first idea would be to simply include the scripts as part of the project package itself:
awesome_sauce
┣ awesome_sauce
┃ ┣ poetry_scripts
┃ ┃ ┣ __init__.py
┃ ┃ ┣ run_unit_tests.py
┃ ┃ ┗ run_linters.py
┃ ┣ __init__.py
┃ ┗ sauce.py
┣ docs
┣ tests
┣ .gitignore
┣ poetry.lock
┗ pyproject.toml
In this case, you could add the following to pyproject.toml
[tool.poetry.scripts]
tests = 'awesome_sauce.poetry_scripts.run_unit_tests:main'
linters = 'awesome_sauce.poetry_scripts.run_linters:main'
and they could be run as
poetry run tests
poetry run linters
But this isn't ideal; now your build configuration is part of the package and presumably goes straight into production, unless you pull off something fancy during the CI/CD process. It's dead weight to the end-users, and needlessly adds stuff to the package namespace.
Then, another idea would be to have everything in a top-level script:
awesome_sauce
┣ awesome_sauce
┃ ┣ __init__.py
┃ ┗ sauce.py
┣ docs
┣ tests
┣ .gitignore
┣ poetry.lock
┣ poetry_scripts.py
┗ pyproject.toml
which would change the lines in pyproject.toml
to
[tool.poetry.scripts]
tests = 'poetry_scripts:run_unit_tests'
linters = 'poetry_scripts:run_linters'
but this isn't necessarily ideal either, because now you potentially have a relatively lengthy script sitting at the project root, and it can be subjectively ugly. Though at least now it shouldn't be part of the production package, which is a net gain.
These two approaches could be combined, and the script package in the first version would simply be moved to the root, meaning the project now consists of two different packages.
awesome_sauce
┣ awesome_sauce
┃ ┣ __init__.py
┃ ┗ sauce.py
┣ docs
┣ poetry_scripts
┃ ┣ __init__.py
┃ ┣ run_unit_tests.py
┃ ┗ run_linters.py
┣ tests
┣ .gitignore
┣ poetry.lock
┗ pyproject.toml
[tool.poetry.scripts]
tests = 'poetry_scripts.run_unit_tests:main'
linters = 'poetry_scripts.run_linters:main'
but it isn't clear whether this is a better approach.
Does a consensus exist for handling the Poetry/setuptools
scripts within a given project? If so, I don't believe that has been previously documented in public.