37

The topic of namespace packages seems a bit confusing for the uninitiated, and it doesn't help that prior versions of Python have implemented it in a few different ways or that a lot of the Q&A on StackOverflow are dated. I am looking for a solution in Python 3.5 or later.

#The scenario: I'm in the process of refactoring a bunch of Python code into modules and submodules, and working to get each of these projects set up to operate independently of each other while sitting in the same namespace.

We're eventually going to be using an internal PyPi server, serving these packages to our internal network and don't want to confuse them with external (public) PyPi packages.

Example: I have 2 modules, and I would like to be able to perform the following:

from org.client.client1 import mod1
from org.common import config

The reflected modules would be separated as such:

Repository 1:

org_client_client1_mod1/
  setup.py
  mod1/
    __init__.py
    somefile.py

Repository 2:

org_common_config/
  setup.py
  config/
    __init__.py
    someotherfile.py

My Git repositories are already setup as org_client_client1_mod1 and org_common_config, so I just need to perform the setup on the packaging and __init__.py files, I believe.

Questions:

#1

With the __init__.py, which of these should I be using (if any)?:

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

Or:

import pkg_resources
pkg_resources.declare_namespace(__name__)

#2

With setup.py, do I still need to add the namespace_modules parameter, and if so, would I use namespace_modules=['org.common'], or namespace_modules=['org', 'common']?

#3

Could I forgo all of the above by just implementing this differently somehow? Perhaps something simpler or more "pythonic"?

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
Bobby
  • 1,439
  • 2
  • 16
  • 30
  • I don't know much about repositories or setup.py, but I can say that [you probably shouldn't be using an `__init__.py` at all if you're on 3.5+](https://www.python.org/dev/peps/pep-0420/). – user2357112 Jan 12 '17 at 19:22
  • 1
    Yes, I read the PEP420, and it definitely seems to add to the confusion for me. It speaks of the approaches, and then says that you don't need to do it because you can just add it to `setup.py`. – Bobby Jan 12 '17 at 19:55
  • Hey @Bobby, would you consider accept an answer? [Rite's one](https://stackoverflow.com/a/57187958/2989289) is very complete and seems to fulfill your doubts – artu-hnrq Mar 19 '21 at 16:00

2 Answers2

18

Late to the party, but never hurts to help fellow travellers down the namespace path in Python!

#1:

With the __init__.py, which of these should I be using (if any)?:

It depends, There are three ways to do namespace packages as listed here:

  1. Use native namespace packages. This type of namespace package is defined in PEP 420 and is available in Python 3.3 and later. This is recommended if packages in your namespace only ever need to support Python 3 and installation via pip.

  2. Use pkgutil-style namespace packages. This is recommended for new packages that need to support Python 2 and 3 and installation via both pip and python setup.py install.

  3. Use pkg_resources-style namespace packages. This method is recommended if you need compatibility with packages already using this method or if your package needs to be zip-safe.

If you are using #2 (pkgutil-style) or #3 (pkg_resources-style), then you will have to use the corresponding style for __init__.py files. If you use native namespaces then no __init__.py in the namespace directory.

#2:

With setup.py, do I still need to add the namespace_modules parameter, and if so, would I use namespace_modules=['org.common'], or namespace_modules=['org', 'common']?

If your choice of namespace package is not native style, then yes, you will need namespace_packages in your setup().

#3:

Could I forgo all of the above by just implementing this differently somehow? Perhaps something simpler or more "pythonic"?

Since you ended up down to a complex topic in python, it seems you know what you are doing, what you want and identified that creating a Python Namespace package is the way to do it. This would be considered a pythonic way to solve a problem.


Adding to your questions, here are a few things I discovered:

I read PEP420, the Python Packaging guide and spent a lot of time understanding the namespace packages, and I generally understood how it worked. I read through a couple of answers here, here, here, and this thread on SO as well - the example here and on the Git link shared by Rob.

My problem however was after I created my package. As all the instructions and sample code explicitly listed the package in the setuptools.setup(package=[]) function, my code failed. My sub-packages/directories were not included. Digging deeper, I found out that setuptools has a find_namespace_package() function that helps in adding sub-packages too

EDIT:

Link to find_namespace_packages() (setuptools version greater than 40.1.0): https://setuptools.readthedocs.io/en/latest/setuptools.html#find-namespace-packages

EDIT (08/09/2019):

To complete the answer, let me also restructure with an example.

The following solution is assuming Python 3.3+ which has support for implicit namespace packages

Since you are looking for a solution for Python version 3.5 or later, let's take the code samples provided and elaborate further.

Let's assume the following:

Namespace/Python package name : org

Distribution packages: org_client, org_common

Python: 3.3+

setuptools: 40.1.0

For you to do the following

from org.client.client1 import mod1
from org.common import config

And keeping your top level directories the same, viz. org_client_client1_mod1 and org_common_config, you can change your structure to the following

Repository 1:

org_client_client1_mod1/
  setup.py
  org/
    client/
      client1/
        __init__.py
        submod1/
          __init__.py
        mod1/
          __init__.py
          somefile.py
        file1.py

Updated setup.py

from setuptools import find_namespace_packages, setup
setup(
    name="org_client",
    ...
    packages=find_namespace_packages(), # Follows similar lookup as find_packages()
    ...
)

Repository 2:

org_common_config/
  setup.py
  org/
    common/
      __init__.py
      config/
        __init__.py
        someotherfile.py

Updated setup.py:

from setuptools import find_namespace_packages, setup
setup(
    name="org_common",
    ...
    packages=find_namespace_packages(), # Follows similar lookup as find_packages()
    ...
)

To install (using pip):

(venv) $ pip3 install org_common_config/
(venv) $ pip3 install org_client_client1_mod1/

Updated pip list will show the following:

(venv) $ pip3 list
...
org_client
org_common
...

But they won't be importable, for importing you will have to follow org.client and org.common notation.

To understand why, you can browse here (assuming inside venv):

(venv) $ cd venv/lib/python3.5/site-packages/
(venv) $ ls -l | grep org

You'll see that there's no org_client or org_common directories, they are interpreted as a namespace package.

(venv) $ cd venv/lib/python3.5/site-packages/org/
(venv) $ ls -l
client/
common/
...
rite2hhh
  • 372
  • 2
  • 15
  • assuming I distribute multiple `org.` packages, is there a way I can just install them all from (my personal) pypi server by using `pip install org`? – DiCaprio May 23 '20 at 20:21
  • That's a good question, but I'm afraid I do not have the answer, re-reading through the PEP420 doc (https://www.python.org/dev/peps/pep-0420/#terminology), seems like the whole point of having separate "distribution" packages would be defeated (although I can see why this is appealing). I don't believe pip supports wildcards for indexed packages. We distribute using OS packages instead of wheels. Since you have an internal PyPI, you might as well try it out – rite2hhh May 26 '20 at 21:14
  • I tried it out, I know OpenStack has namespace packages (oslo.*), pip didn't install complaining it couldn't find any package. It would actually never work, how would versions work in that case? if separate distributions have different versions/schemes, it'd be a nightmare – rite2hhh May 26 '20 at 21:18
  • 1
    We solved this by introducing a meta-package `org` that basically is empty and depends on all of our internal `org.*` packages. This adds another layer that has to be maintained but also allows fine-grained version control for the released composition of distributions. – DiCaprio May 27 '20 at 09:24
  • That would've been my suggestion. It's a self induced cost, but I must say that I agree to the granular control; shared packages are best for DRY. As a follow-up question, did you run into any conflicts? Do imports work as expected? What happens if you have installed individual packages first, and then install this meta-package? curious to know... – rite2hhh May 27 '20 at 16:45
  • Actually works like a charm. Since every distribution is considered to be its own package, you may install `org.test` and then `org`. Since we set fixed versions for the dependencies in `org` it just checks if the local version can be used, or a different one has to be downloaded. – DiCaprio May 28 '20 at 10:36
  • sweet... have you tried nested namespaces? `org.product.test` where `org` being a top level namespace, `product` underneath it and then `test` - again, it might be an overkill to have such deep hierarchy (managing the directories gets messy) – rite2hhh May 28 '20 at 16:20
  • 1
    Not jet. The docs read like there is no difference, and I hope thats true :D – DiCaprio May 28 '20 at 16:38
5

This is a tough subject. All the -'s, _'s, and __init__.py's everywhere don't exactly make it easy on us.

First, I'll answer your questions:

With the __init__.py, which of these should I be using (if any)?

  • __init__.py can be completely empty, it just needs to be in the correct place. Namely (pun) they should be in any subpackage containing python code (excluding setup.py.) Follow those rules and you should be fine.

With setup.py, do I still need to add the namespace_modules parameter, and if so, would I use namespace_modules=['org.common'], or namespace_modules=['org', 'common']?

  • Nope! Only name= and packages=. However, note the format of the packages= arg compared against the directory structure.
  • Here's the format of the package= arg: setup.py for namespace package
  • Here's the corresponding directory structure: example_package/a/<subpackage>

Could I forgo all of the above by just implementing this differently somehow? Perhaps something simpler or more "pythonic"?

  • If you want to be able to install multiple features individually, but under the same top-level namespace, you're on the right track.

I'll spend the rest of this answer re-implementing your namespace package in native format:

I'll put all helpful documentation I've been able to find at the bottom of the post.

K so I'm going to assume you want native namespace packages. First let's look at the current structure of your 2 repos:

org_client_client1_mod1/
  setup.py
  mod1/
    __init__.py
    somefile.py

&

org_common_config/
  setup.py
  config/
    __init__.py
    someotherfile.py

This^ would be too easy!!!

To get what you want:

My brain isn't elastic enough to know if we can go 3-levels deep with namespace packages, but to do what you want, here's what I'm pretty sure you'd want to do:

org-client/
  setup.py
  org/
    client/
      client1/
        __init__.py
        mod1/
          __init__.py
          somefile.py

&

org-common-but-also-note-this-name-doesnt-matter/
  setup.py
  org/
    common/
      __init__.py
      config/
        __init__.py
        someotherfile.py

Basically then the key is going to be specifying the correct name= & packages= args to stuptools.setup() inside of each setup.py.

These are going to be:

name='org_client',
...
packages=['org.client']

&

name='org_common'
...
packages['org.common']

respectively.

Then just install each one with pip install . inside each top-level dir.

Installing the first one will give you access to the somefile.py module, and installing the second will give you access to someotherfile.py. It also won't get confused about you trying to install 2 packages named org in the same environment.

K so the most helpful section of the docs: https://packaging.python.org/guides/packaging-namespace-packages/#packaging-namespace-packages

And then here's how I actually came to understand this: https://github.com/pypa/sample-namespace-packages/tree/master/native

Rob Truxal
  • 5,856
  • 4
  • 22
  • 39
  • Upvote for the packaging.python.com link, I looked at several pages on python.org but hadn't found that one. – Mike C Mar 25 '22 at 15:18