Springe zum Hauptinhalt

Switching from "setup.py install" to pip

When working on NoN I ran into the following message:

SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools

So I took a look at it, did a quick research and decided solving the problem should be doable for me.

Interesting/important links to start with:

What has setup.py ever done for us?

Among the obvious part, installing the application itself into the correct destination folder while also handling dependencies, one can specify plenty of additional files to be coped with like starter scripts, .desktop files, app icons, providing all kinds of the project's metadata.

If you are fierce enough or just don't know better you can write custom install/de-install/ruin the system routines - anything's possible.

For my personal use case the following tasks are required:

  1. install application files

  2. handle dependencies

  3. copy .desktop file

  4. copy starter script

  5. copy application icon

Converting setup.py to setup.cfg

You can however continue using setup.py with pip. setup.cfg is the TOML-formatted static configuration of the setuptools.setup function that is, according to the packaging tutorial, "simpler, easier to read, and avoids many common errors".

If you come from using $ python setup.py install like me you will already have a working setup.py script that can be easily transformed.

[metadata]

The first section of the file includes all the metadata so:

...
import info
...
with open(os.path.join(here, "README.md"), encoding="utf-8") as f:
long_description = "\n" + f.read()

setup(
    name=info.NAME,
    version=info.__version__,
    description=info.DESCRIPTION,
    long_description=long_description,
    long_description_content_type="text/markdown",
    license=info.__license__,
    ...,
)

becomes

[metadata]
name = non
version = 0.81
description = Knights Of Ni - a GTK+ manager for your Nikola powered website
long_description = file: README.md
long_description_content_type = text/markdown
license = MIT
...

For convenience and readability purposes I once put all the proper data into a separate info function so I didn't need to touch the install script which now has become dispensable.

[options]

The same applies for the remaining values that are listed in the options section.

All possible keywords/values are listed in the setuptools documentation. Some keys require a "section" data type that means these keys get their own subsection, p.e. "[options.data_files]".

So the old setup function

...
import info
...

setup(
    ...,
    python_requires=info.REQUIRES_PYTHON,
    packages=info.PACKAGES,
    package_dir=info.PACKAGE_DIR,
    install_requires=info.REQUIRED,
    include_package_data=True,
    data_files=info.DATAFILES,
    package_data=info.PACKAGE_DATA,
)

becomes

[options]
packages = non
python_requires = >=3.6
install_requires =
    Nikola
    PyGObject
    PyYAML
include_package_data = True

[options.data_files]
share/applications =
    data/non.desktop
share/icons/hicolor/scalable/apps =
    non/ui/duckyou.svg
bin =
    data/non

[options.package_data]
...

build

After plagiarizing the pyproject.toml file from the packaging tutorial we are ready to build and hopefully will end up with an installable archive.

But first make sure to have a current build package builder installed:

$ python -m pip install --upgrade build

Then run build from the project's directory:

$ python -m build
* Creating virtualenv isolated environment...
* Installing packages in isolated environment... (setuptools>=42)
* Getting dependencies for sdist...
running egg_info
writing non.egg-info/PKG-INFO
writing dependency_links to non.egg-info/dependency_links.txt
writing requirements to non.egg-info/requires.txt
[...]
Successfully built non-0.81.tar.gz and non-0.81-py3-none-any.whl

Install the archive with pip:

$ pip install dist/non-0.81-py3-none-any.whl

Now it's time to check if everything is in its place and working.

Minor adjustments & tidying up the project

Start script

The installation path is determined by the Python version so the .desktop file points to the new start script that figures out the installation path instead of directly pointing to the application:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# execution file to put in /usr/bin

import os
import non

os.chdir(os.path.dirname(non.__file__))

from non import application
setup.py

None of the (custom) commands are needed anymore, the file is obsolete.

info file

All information have been merged into setup.cfg, the file is obsolete.

README.md

Pip now handles the installation and deinstallation process, edit installation section.

Final thoughts

Making my application pip install-ready was easier than expected with an even more compliant result than before. 10/10 would use pip again.