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.
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__)
With
setup.py
, do I still need to add thenamespace_modules
parameter, and if so, would I usenamespace_modules=['org.common']
, ornamespace_modules=['org', 'common']
?
Could I forgo all of the above by just implementing this differently somehow? Perhaps something simpler or more "pythonic"?
In Python, a namespace package allows you to spread Python code among several projects. This is useful when you want to release related libraries as separate downloads.
So if you want to create a namespace, you just need to call a function, instantiate an object, import a module or import a package. For example, we can create a class called Namespace and when you create an object of that class, you're basically creating a namespace.
Late to the party, but never hurts to help fellow travellers down the namespace path in Python!
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:
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.
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.
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.
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()
.
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/ ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With