3

I am trying to include a python file in the build/lib directory created when running

python setup.py install

In particular, I would like to include a simple configuration file ('definitions.py') that defines a ROOT_DIR variable, which is then used by subpackages. The 'definitions.py' file contains:

import os
ROOT_DIR  = os.path.dirname(os.path.abspath(__file__))

My goal is to have configuration files within each subpackage ('config.py') call ROOT_DIR to build their own absolute paths:

from definitions import ROOT_DIR
PACKAGE_DIR = os.path.join(ROOT_DIR, 'package1/')

The idea is drawn from this stackoverflow answer: https://stackoverflow.com/a/25389715.
However, this 'definitions.py' file never shows up in the build directory when running 'setup.py install'.

Here is the directory structure of the project:

project
|
├── setup.py
|
├── definitions.py
|
├── package1
|   ├── __init__.py
|   ├── config.py
|   └── ...
|
├── package2
|   ├── __init__.py
|   └── ...
└── ...

My multiple attempts have failed (trying, e.g. the suggestions offered in https://stackoverflow.com/a/11848281). As far as I can tell, it's because definitions.py is in the top-level of my project structure (which lacks an __init__.py file).

I have tried:

1) ...using the 'package-data' variable in setuptools.setup()

package_data={'package': ['./definitions.py']}

but definitions.py does not show up in the build
(I think because definitions.py is not in a 'package' that has an __init__.py?).

2) ...using a MANIFEST.in file, but this also does not work
(I think because MANIFEST does not work with .py files?)

My question:
Is there a way to include definitions.py in the build directory?
Or, is there a better way to provide access to absolute paths built from the top-level directory for multiple sub-packages?

Danny Kaufman
  • 107
  • 1
  • 8
  • Relevant context is missing. 1. Are you packaging an *app* or *library* code? 2. What is actually in `definitions.py`? Is it basically just a configuration file, or is there logic? Perhaps edit the question to include the contents. – wim Oct 22 '18 at 21:10
  • edited to include context – Danny Kaufman Oct 22 '18 at 21:36
  • Would it be fair to say your goal is for the package to know the path of where it has been installed? Note that in the [example](https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure/25389715#25389715) you're following, the `definitions.py` module is contained *within* the installed project dir, so it's quite a different context to what you seem to be attempting here. – wim Oct 22 '18 at 21:42
  • yes, "goal is for the package to know the path of where it has been installed". Sorry for not being clearer... my current understanding is that the build/lib/ directory is where the package is installed, that's why I phrased it that way in the question. My thinking was that the most straightforward way to access absolute paths for each sub-package was to maintain the same structure in the installed package as in the development structure. – Danny Kaufman Oct 22 '18 at 21:46
  • 1
    Why does the code need to know where the code is installed? Is it in order to build a relative path to some data or resource files? If so, there are much better options available here. – wim Oct 22 '18 at 21:47
  • Disregard my answer, it won't do you any good (can't remove that since you've accepted that). In the `build` dir, your package is only prepared for installation; it is not installed in `build`. As for the paths, each module knows its path from the `__file__` attribute - isn't this not enough? You can call `os.path.dirname(os.path.abspath(__file__))` from any module. – hoefling Oct 22 '18 at 21:52
  • Hmmm, I think this is a prime example of learning that highlights why you didn't need to do the thing you wanted to do in the first place ...which is incredibly helpful. It is apparent that I need to think a bit more deeply whether or not using the os.path.dirname() approach (as suggested by @hoefling) or simply stripping out the absolute pathing (as suggested by @wim) makes the most sense for my project. – Danny Kaufman Oct 22 '18 at 21:57
  • Depending on what you're trying to do, there may be better ways of doing what you want - if you are looking for the way of accessing a top-level config file from subpackages, you should use `pkg_resources` instead. Django does what it does mainly because Django projects are rarely installed as Python packages; what's good for Django does not necessarily apply to any project. – hoefling Oct 22 '18 at 22:00
  • @DannyKaufman I have altered the answer in order to give an example of how to access non-code package resources without any path building at all. – hoefling Oct 22 '18 at 22:10
  • This is classic [XY problem](http://xyproblem.info): Asking about your attempted solution rather than your actual problem :) – wim Oct 22 '18 at 22:28

1 Answers1

2

If you are looking for a way to access a non-python data file in the installed module like in the question you've linked (a configuration file in the top-level package that should be accessible in subpackages), use pkg_resources machinery instead of inventing a custom path resolution. An example project structure:

project
├── setup.py
└── root
    ├── __init__.py
    ├── config.txt
    ├── sub1
    │   └── __init__.py
    └── sub2
        └── __init__.py

setup.py:

from setuptools import setup

setup(
    name='myproj',
    ...,
    packages=['root', 'root.sub1', 'root.sub2'],  # or setuptools.find_packages()
    package_data={'root': ['config.txt']}
)

Update:

As pointed out by wim in the comments, there's now a backport for importlib.resources (which is only available in Python 3.7 and onwards) - importlib_resources, which offers a modern resource machinery that utilizes pathlib:

# access the filepath
importlib_resources.path('root', 'config.txt')

# access the contents as string
importlib_resources.read_text('root', 'config.txt')

# access the contents as file-like object
importlib_resources.open_binary('root', 'config.txt')

Original answer

Using pkg_resources, you can access the root/config.txt from any spot of your package without having to perform any path resolution at all:

import pkg_resources

# access the filepath:
filepath = pkg_resources.resource_filename('root', 'config.txt')

# access the contents as string:
contents = pkg_resources.resource_string('root', 'config.txt')

# access the contents as file-like object:
contents = pkg_resources.resource_stream('root', 'config.txt')

etc.

hoefling
  • 59,418
  • 12
  • 147
  • 194
  • This will put `definitions.py` directly in site-packages dir. Unlikely what OP wanted, IMO. It should be namespaced in the project somehow! – wim Oct 22 '18 at 21:28
  • While I agree that the use case of `py_modules` is rather when you are packaging a single-module distribution, there are many packages that define a top-level module for various reasons; `colorama` uses it for constant definition, `pytest` for defining non-private imports etc. I don't do that, but it's debatable whether it's a good practice or not. – hoefling Oct 22 '18 at 21:41
  • Given the context the OP has now edited into the question, I have to give this a firm -1. It's not a good way for a package to discover the installed location. – wim Oct 22 '18 at 21:43
  • Hmm, didn't see that. – hoefling Oct 22 '18 at 21:43
  • @hoefling, this almost solves the problem, but it seems to require an \__init__.py at the root level. I was trying to avoid an \__init__.py at the root level based on trouble I've had during my limited experience with it before. Is it better-practice to include or exclude a root-level \__init__? – Danny Kaufman Oct 22 '18 at 22:18
  • 1
    `pkg_resources` is a part of setuptools (installer) and is sometimes a bit heavy/slow for such simple library task such as runtime resource management. It also creates an undesirable *runtime* dependency on setuptools. For a more modern approach: [`importlib.resources`](https://importlib-resources.readthedocs.io/en/latest/) – wim Oct 22 '18 at 22:30
  • Wait, do you mean the `__init__.py` in `root`? It is a necessity because this is the root package of your distribution; This is also the package you will be specifying in `packages` list in `setup.py` script. I have updated the answer with the complete project structure and a setup script essentials. – hoefling Oct 22 '18 at 22:34
  • @wim nice, didn't know there's a backport for that now! – hoefling Oct 22 '18 at 22:38
  • Thanks for clarifying @hoefling. If `project` is at the top with no \__init__ and `root` is a package within `project`, then my original question was specifically about including a config **.py** in `project`, not within `root`. However, given the comments so far, I am not sure this is even necessary since I primarily was using it to get absolute paths for sub-packages and other resources. Which, based on your and wim's other comments may be best achieved using `os.path.dirname()` or `importlib.resources`, or restructuring entirely. – Danny Kaufman Oct 22 '18 at 22:46