setup.py: installing just a pth file?

Question:

As the final step in building a custom python, I need to add a myproject.pth.

Currently I’m doing this in a Makefile:

install:
        rm -f            ../../lib/python2.6/site-packages/myproject.pth
        cp myproject.pth ../../lib/python2.6/site-packages/myproject.pth

but I would like to encapsulate this in a setup.py. Unfortuntately the setup.py docs don’t seem to cover this trivial case! Any help appreciated. I tried this but it doesn’t work:

from setuptools import setup
setup(
    packages=['mypackage_pth'],
    package_dir={'mypackage_pth': '.'},
    package_data={'mypackage_pth': ['mypackage.pth']},
)
Asked By: Mark Harrison

||

Answers:

You’re looking for the data_files parameter to setup:

from distutils import sysconfig
site_packages_path = sysconfig.get_python_lib()

setup(...,
  data_files=[(site_packages_path, ["mypackage.pth"])]
 )
Answered By: stderr

Adapted from stderr’s answer. Not sure if you’re supposed to do this, but it works.

from distutils import sysconfig
site_packages_path = sysconfig.get_python_lib()

import shutil
shutil.copy('mypackage.pth', site_packages_path)

setup(...)
Answered By: S. Wolf

The right thing to do here, is to extend setuptools’ build_py, and copy the pth file from the source directory into the build directory, in the location where setuptools prepares all the files that go into site-packages there.

from setuptools.command.build_py import build_py


class build_py_with_pth_file(build_py):
     """Include the .pth file for this project, in the generated wheel."""

     def run(self):
         super().run()

         destination_in_wheel = "mypackage.pth"
         location_in_source_tree = "src/mypackage.pth"
 
         outfile = os.path.join(self.build_lib, destination_in_wheel)
         self.copy_file(location_in_source_tree, outfile, preserve_mode=0)

setup(
   ...,
   cmdclass={"build_py": build_py_with_pth_file},
)

All the other answers here (at the time of writing) are wrong in subtle ways.

data_files=[(site_packages_path, ["mypackage.pth"])]

This is semantically wrong — the pth file is NOT data. It’s code, much like how the various .py files in the rest of your project are code. More importantly, this is also functionally broken — in a slightly subtle but important manner.

This embeds the site_packages_path into the wheel. You’ll end up with a wheel containing a file path like:

my_package-1.0.0.data/data/lib/python3.9/site-packages/mypackage.pth

This wheel will only work on Python 3.9 (because that’s what the path is) but it will be very easy to tag as py3 (i.e. compatible with all Python versions).

This is non-trivial to detect, since you’ll need a development workflow that runs tests across multiple different Python versions, using the generated wheel.

shutil.copy('mypackage.pth', site_packages_path)

This is… bad.

While it will work across Python versions, this will "install" the pth file even if a user downloads the project, with pip download mypackage.

More importantly, a wheel generated for this project will not have any pth file associated with the project. So, subsequent installations will be not get the pth file installed (since pip will cache locally built wheels).

This can’t be reproduced by installing from the source directory locally and is similarly non-trivial: This is non-trivial to detect, since you’ll need a development workflow that installs the package in a different environment than the one you’ve built it from, to run the tests in a manner that they can detect this.

Answered By: pradyunsg
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.