3

How to use a module as both a namespace package and as a container for classes in a specific subproject? That is, how can I import something directly from a namespace package instead of its subpackages?

An example can make the question clearer.

Three subpackages of calc

Suppose I want to create a namespace package called calc, and there are three projects on it (sscce: calcs.tar.gz). The first one, found at the calc-add directory, has the following structure:

calc-add/calc/
calc-add/calc/add.py
calc-add/calc/__init__.py

The content of calc-add/calc/__init__.py is just

__import__('pkg_resources').declare_namespace(__name__)

as required by namespace packages, and add.py has only the following function:

def op(n1, n2):
    return n1 + n2

There is another project, at the calc-sub directory, has the following structure:

calc-sub/calc/
calc-sub/calc/sub.py
calc-sub/calc/__init__.py

The __init__.py file is identical to the previous one and sub.py has only a simple function:

def op(n1, n2):
    return n1 - n2

Finally, I have a calc-main directory with the following content:

calc-main/calc/
calc-main/calc/main.py
calc-main/calc/__init__.py

Again, the __init__.py has only the mentioned line, yet main.py has the following code:

import calc.add, calc.sub

def apply(n1, n2, op):
    if op == "add":
        return calc.add.op(n1, n2)
    else:
        return calc.sub.op(n1, n2)

How do I call and how would I call the apply() function

If I invoke the Python interpreter with $PYTHONPATH set as below:

$ PYTHONPATH=$PYTHONPATH:../calc-add/:../calc-sub/ python

then I can call apply() this way:

>>> import calc.main
>>> calc.main.apply(2, 3, 'add')
5

However, I would like to call apply() directly from calc, as below:

>>> import calc
>>> calc.apply(2, 3, 'add')
5

It apparently works if I add the following line to calc-main/calc/__init__.py:

from main import apply

However, setuptools documenation is very clear:

You must NOT include any other code and data in a namespace package's __init__.py. Even though it may appear to work during development, or when projects are installed as .egg files, it will not work when the projects are installed using "system" packaging tools -- in such cases the __init__.py files will not be installed, let alone executed.

So how can I get what I want without breaking the restriction on __init__.py files? Is that possible?

brandizzi
  • 26,083
  • 8
  • 103
  • 158
  • I think you cannot work around this very easily... my solution was move everything to first level namespace "core" package, in your case it would be `calc.core`. – Mikko Ohtamaa Dec 29 '14 at 12:55
  • Also you can use dot in namespaced package names, e.g. `calc.add` instead of `calc-add`. Most of namespaced Python packages do this. – Mikko Ohtamaa Dec 29 '14 at 12:56
  • @MikkoOhtamaa don't worry, in my examples the package are indeed dot-separated. The hyphenated directories are only the root directories of my hypothetical projects. Also, I'm using almost exactly the solution you proposed so far - just calling the module `main` instead of `core`. I'll keep looking - and waiting - for an answer, however. – brandizzi Dec 29 '14 at 13:49
  • That restriction comes from the fact that any namespace package can be the first one on sys.path. Then it calls pkg_resources.declare_namespace and/or pkgutil.extend_path which scan rest of sys.path to find more copies of namespace package and add those to package's __path__. If you provide any other code in __init__.py then you MUST duplicate the same code in EACH __init__.py, since any copy can "win" the race to be imported first depending on sys.path. – Zart Feb 25 '15 at 16:46
  • And for the record Python has builtin `apply` function https://docs.python.org/2/library/functions.html#apply so maybe you shouldn't hijack this name. – Zart Feb 25 '15 at 16:49

0 Answers0