2

I am trying to distribute a mplstyle I wrote such that I can share it easily. It boils down to copying a text file to the proper configuration direction (which is known for any architecture) during installation. I want to be able to install using either python setup.py install or pip install .... Currently I do not seem to get either of the two ways robust (see current approach below).

  • Installing with pip install ... does not seem to invoke the copying at all.
  • Installing with python setup.py install works well on my machine, but ReadTheDocs gives me the following error:

    python setup.py install --force
    
    running install
    error: [Errno 2] No such file or directory: u'/home/docs/.config/matplotlib/stylelib/goose.mplsty
    

What is the proper way to copy configuration files during installation in a robust way?

Current approach

File structure

setup.py
goosempl/
| __init__.py
| stylelib/
  | goose.mplstyle
  | ...

setup.py

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

class PostInstallCommand(install):

  def run(self):

    import goosempl
    goosempl.copy_style()

    install.run(self)

setup(
  name              = 'goosempl',
  ...,
  install_requires  = ['matplotlib>=2.0.0'],
  packages          = ['goosempl'],
  cmdclass          = {'install': PostInstallCommand},
  package_data      = {'goosempl/stylelib':['goosempl/stylelib/goose.mplstyle']},
)

goosempl/__init__.py

def copy_style():

  import os
  import matplotlib

  from pkg_resources import resource_string

  files = [
    'stylelib/goose.mplstyle',
  ]

  for fname in files:
    path = os.path.join(matplotlib.get_configdir(),fname)
    text = resource_string(__name__,fname).decode()

    print(path, text)

    open(path,'w').write(text)

Upload to PyPi

python setup.py bdist_wheel --universal
twine upload dist/*
Tom de Geus
  • 5,625
  • 2
  • 33
  • 77

1 Answers1

2

First of all, based on the project structure you've provided, you're not specifying the package_data correctly. If goosempl is a package and stylelib a directory inside it containing the mplstyle files (what I assume from your code), then your package_data configuration line should be:

package_data = {'goosempl': ['stylelib/goose.mplstyle']},

As described in Building and Distributing Packages with Setuptools:

The package_data argument is a dictionary that maps from package names to lists of glob patterns. The globs may include subdirectory names, if the data files are contained in a subdirectory of the package.

So your package is goosempl and stylelib/goose.mplstyle is the file to be included in package data for goosempl.

Your second issue (No such file or directory) is simple: in the copy_style() function, you never check if the parent directory of the file exists before writing the file. You should be able to reproduce this locally by removing the directory /home/<user>/.config/matplotlib/stylelib/ (or moving it temporarily).

The fix is also quite simple, actually there are lots of them. Use whatever you want to create missing directories.

  • distutils.dir_util.mkpath is suitable for both python2 and python3:

    for fname in files:
        path = os.path.join(matplotlib.get_configdir(), fname)
        distutils.dir_util.mkpath(os.dirname(path))
    
  • My preferred one is using pathlib, but it is available only since Python 3.4:

    for fname in files:
        path = pathlib.Path(matplotlib.get_configdir(), fname)
        path.parent.mkdir(parents=True, exist_ok=True)
    
hoefling
  • 59,418
  • 12
  • 147
  • 194
  • Thanks, this solves most of my problems. I used `os` in the end: `if not os.path.isdir(os.path.dirname(path)): os.makedirs(os.path.dirname(path))` (notice the small bug on my side). It does not, however, seem to fix the problem with pip. Installing from PyPi with pip does not seems to invoke the `PostInstallCommand` command. (It is only invoked on my machine before uploading to PyPi). – Tom de Geus Mar 05 '18 at 09:09
  • The `install` command won't be invoked by `pip`. It does not install from source by design. Instead, it builds a wheel and installs from it - but `wheel` format does not use the setup script - it does not even include it. So your `PostInstallCommand` won't be called. – hoefling Mar 05 '18 at 09:44
  • OK, I understand. Is there a way to copy these configuration files upon installing with `pip`? I understand that one can just include the copying command such that it is always invoked upon including the library, but this is really not what I am looking for as (i) this results in unnecessary checks that are run over and over, and (ii) in my particular case I want users to load the style, without ever needed to include the library. – Tom de Geus Mar 05 '18 at 10:02
  • Unfortunately, in your case there isn't - you want to place the file outside of the `site-packages` directory, and `wheel` doesn't allow that. See my [other answer](https://stackoverflow.com/a/47465374/2650249) to a similar question - the OP wanted to place a config file in the user's home dir on installation. The only workaround for that is not to build wheels in first place, building a source tar/zip/bz2 instead, and on installation tell `pip` not to build a wheel out of source distribution with the `--no-binary` flag. – hoefling Mar 05 '18 at 10:45