How do I distribute fonts with my python package using python -m build?

Question:

Problem statement

My package relies on matplotlib to use a specific font which may not be installed on the target device.
I’m trying to install ttf font files from the source distribution to the matplotlib fonts/ttf directory, after building the package.

With setuptools slowly removing parts of its CLI (python setup.py sdist/bdist_wheel) and the possibility to use build (python -m build), I decided to build my package with the latter.

What I tried

I tried modifying the setup.py from the outdated https://stackoverflow.com/a/34304823/8797886.
My question differs from this post in that I would like to use python -m build.

setup.py

import warnings
from functools import partial

from setuptools import setup
from setuptools.command.install import install

def _post_install():
    # Try to install custom fonts
    try:
        import os
        import shutil

        import matplotlib as mpl

        import mypkg

        # Find where matplotlib stores its True Type fonts
        mpl_data_dir = os.path.dirname(mpl.matplotlib_fname())
        mpl_ttf_dir = os.path.join(mpl_data_dir, "fonts", "ttf")

        # Copy the font files to matplotlib's True Type font directory
        # (I originally tried to move the font files instead of copy them,
        # but it did not seem to work, so I gave up.)
        cp_ttf_dir = os.path.join(os.path.dirname(mypkg.__file__), "ttf")
        for file_name in os.listdir(cp_ttf_dir):
            if file_name[-4:] == ".ttf":
                old_path = os.path.join(cp_ttf_dir, file_name)
                new_path = os.path.join(mpl_ttf_dir, file_name)
                shutil.move(old_path, new_path)
                print("moving" + old_path + " -> " + new_path)

        # Try to delete matplotlib's fontList cache
        mpl_cache_dir = mpl.get_cachedir()
        mpl_cache_dir_ls = os.listdir(mpl_cache_dir)
        if "fontList.cache" in mpl_cache_dir_ls:
            fontList_path = os.path.join(mpl_cache_dir, "fontList.cache")
            os.remove(fontList_path)
            print("Deleted the matplotlib fontList.cache")
    except:
        warnings.warn(
            "An issue occured while installing the custom fonts for mypkg."
        )
        raise

# Set up the machinery to install custom fonts.  Subclass the setup tools install
# class in order to run custom commands during installation.
class MoveTTF(install):
    def run(self):
        """
        Performs the usual install process and then copies the True Type fonts
        that come with shithappens into matplotlib's True Type font directory,
        and deletes the matplotlib fontList.cache.
        """
        # Perform the usual install process
        install.run(self)
        self.execute(_post_install, (), "Moving Custom Font to matplotlib.")


setup_movettf = partial(
    setup,
    package_data={"": ["ttf/*.ttf"]},
    cmdclass={"install": MoveTTF},
)

setup_movettf()

I used a partial setup, because I expect build to fill in all the other stuff from pyproject.toml.

pyproject.toml


[build-system]
requires = ["setuptools>=61.0", "matplotlib"]
build-backend = "setuptools.build_meta"

[project]
name = "mypkg"
etc...

[tool.setuptools.packages.find]
where = ["src"]

[tool.setuptools.package-data]
mypkg = ["ttf/*.ttf"]

What I get

When I use python -m build, I get

ModuleNotFoundError: No module named 'mypkg'

When I use python setup.py sdist and python setup.py bdist_wheel, the wheel is successfully build.
When installing the wheel with pip install dist/mypkg-[].whl, the install is successful and the matplotlib ttf library is populated with the custom fonts.

What I expect

I expect python -m build to work with importing mypkg post-install, like the setuptools cli does.

Asked By: Siem

||

Answers:

You can not do this with Python packaging tools. You need to go beyond that. Some platform specific packaging tools. Think .deb/apt on Debian/Ubuntu or full blown executable installers on Windows (or .msi if it still exists).

This is for operations at "install-time"…

… but you do not need to do this at "install-time", the code of your application or library can contain functions to do this installation of the fonts at run-time.

On the other hand, if you and your users only ever use the setup.py script, without using pip at all (or any other modern installer), then it will likely still work, because obviously you can put any code you want in setup.py without restriction.

There might be some more details in these:

Answered By: sinoroc