Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python 3.6.0 implicit namespace package

I've found strange behavior with implicit namespace package in Python 3.6.0rc1. Could you please tell me if I am wrong or is it a Python 3.6 bug?

I am working with namespace package marrow which has two separated packages marrow.util and marrow.mailer. The second one depends on the first one.

Suppose we have marrow.util installed in site-packages for Python 2.7, 3.5 and 3.6:

$ ls -la /usr/lib/python*/site-packages/marrow
/usr/lib/python2.7/site-packages/marrow:
total 24
drwxr-xr-x.   3 root root  4096 Dec 23 12:23 .
drwxr-xr-x. 196 root root 16384 Dec 23 12:23 ..
drwxr-xr-x.   3 root root  4096 Dec 23 12:23 util

/usr/lib/python3.5/site-packages/marrow:
total 12
drwxr-xr-x.  3 root root 4096 Dec 23 12:24 .
drwxr-xr-x. 99 root root 4096 Dec 23 12:24 ..
drwxr-xr-x.  4 root root 4096 Dec 23 12:24 util

/usr/lib/python3.6/site-packages/marrow:
total 12
drwxr-xr-x.  3 root root 4096 Dec 23 14:25 .
drwxr-xr-x. 37 root root 4096 Dec 23 14:25 ..
drwxr-xr-x.  4 root root 4096 Dec 23 14:25 util

There are no __init__.py files here, which is correct because marrow is a namespace package. You can see this log message during installation:

Skipping installation of <deleted>/site-packages/marrow/__init__.py (namespace package)

And then you have the second part of marrow namespace package marrow.mailer built (but not installed) in some other directory. For example like this:

$ pwd
/builddir/build/BUILD/marrow.mailer-4.0.2

$ ls
coverage.xml debuglinks.list elfbins.list LICENSE.txt marrow.mailer.egg-info README.textile setup.py debugfiles.list debugsources.list example marrow PKG-INFO setup.cfg test

$ ls marrow/
__init__.py  __init__.pyc  mailer  __pycache__

When I run Python 2.7.12 or 3.5.2 in this folder and try to import marrow.util (from site-packages) it works as expected.

$ pwd
/builddir/build/BUILD/marrow.mailer-4.0.2

$ python2
Python 2.7.12 (default, Sep 29 2016, 12:52:02) 
[GCC 6.2.1 20160916 (Red Hat 6.2.1-2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import marrow.util
>>>

$ python3.5
Python 3.5.2 (default, Sep 14 2016, 11:28:32) 
[GCC 6.2.1 20160901 (Red Hat 6.2.1-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import marrow.util
>>>

But when I try to import the same module with Python 3.6 it fails:

$ python3.6
Python 3.6.0rc1 (default, Dec 10 2016, 14:50:33) 
[GCC 6.2.1 20160916 (Red Hat 6.2.1-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import marrow.util
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'marrow.util'
>>> 

I've found this issue when I tried to build marrow.mailer as RPM package in Mock. With Python 2.7 and 3.5 everything works but Python 3.6 cannot import marrow.util from site-packages and for this reason tests of marrow.mailer fails during the build of RPM.

Example traceback from failed tests:

Traceback:
test/test_addresses.py:8: in <module>
    from marrow.mailer.address import Address, AddressList, AutoConverter
marrow/mailer/__init__.py:12: in <module>
    from marrow.mailer.message import Message
marrow/mailer/message.py:21: in <module>
    from marrow.mailer.address import Address, AddressList, AutoConverter
marrow/mailer/address.py:12: in <module>
    from marrow.util.compat import basestring, unicode, unicodestr, native
E   ModuleNotFoundError: No module named 'marrow.util'

I cannot find anything related to this issue in Changelog for Python 3.6.

Thanks for any help.

EDIT: I've checked sys.path in Python 3.6 and everything looks good:

$ python3.6
Python 3.6.0rc1 (default, Dec 10 2016, 14:50:33) 
[GCC 6.2.1 20160916 (Red Hat 6.2.1-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/lib64/python36.zip', '/usr/lib64/python3.6', '/usr/lib64/python3.6/lib-dynload', '/usr/lib64/python3.6/site-packages', '/usr/lib/python3.6/site-packages']
>>>

EDIT 2:

Because I still cannot find any solution and I haven't any response, I've created a simple Bash script which can reproduce my situation. The only thing you need is Python 3.5 and Python 3.6.

#!/bin/bash

# Change this to run script with different Python
#PYTHON=python3.5                   # system Python 3.5
PYTHON=~/temp/Python-3.6.0/python # compiled Python 3.6

# Create venv and activate
$PYTHON -m venv venv
source ./venv/bin/activate

# Install marrow.util package as a part of namespace package marrow
pip install marrow.util

# Create simple folder structure
mkdir -p marrow/mailer

# Create structure of __init__.py files
# For namespace package with related content
cat >> marrow/__init__.py << EOL
try: # pragma: no cover
    __import__('pkg_resources').declare_namespace(__name__)
except ImportError: # pragma: no cover
    __import__('pkgutil').extend_path(__path__, __name__)
EOL

# For mailer module just with print()
cat >> marrow/mailer/__init__.py << EOL
print('Imported!!!')
EOL

# Testing
# Importing marrow.util installed via pip in venv
$PYTHON -c "import marrow.util"
# Importing marrow.mailer created manually in PWD
$PYTHON -c "import marrow.mailer"

# deactivate venv
deactivate

If you execute this script with Python 3.5 you will see that Python 3.5 can import marrow.util installed via pip but it cannot import marrow.mailer in the local folder. But Python 3.6 can import local module marrow.mailer and it cannot import module marrow.util.

like image 778
Lumír Balhar Avatar asked Dec 23 '16 13:12

Lumír Balhar


People also ask

What is Python namespace package?

Namespace packages allow you to split the sub-packages and modules within a single package across multiple, separate distribution packages (referred to as distributions in this document to avoid ambiguity). For example, if you have the following package structure: mynamespace/ __init__.py subpackage_a/ __init__.py ...

Is __ init __ py necessary?

The __init__.py files are required to make Python treat directories containing the file as packages. This prevents directories with a common name, such as string , unintentionally hiding valid modules that occur later on the module search path.

How do you create a namespace in Python?

A namespace gets created automatically when a module or package starts execution. Hence, create namespace in python, all you have to do is call a function/ object or import a module/package.

How is module namespace organized in a package?

Packages allow for a hierarchical structuring of the module namespace using dot notation. In the same way that modules help avoid collisions between global variable names, packages help avoid collisions between module names.


1 Answers

These packages are not using implicit namespacing ("native namespaces"), or if you do have versions that are, pin your dependencies to ensure you do not mix old and new style namespaces. They are utterly incompatible approaches.

You seem to be attempting, in your MCVE example code, to be constructing a namespace package (marrow/__init__.py declares via the old Python 2 explicit declaration substitution trick), A.K.A. pkg-resources-style namespace packages. This requires an argument to setup.py (actual packaging) and installation of metadata through package installation. Specifically, this method involves .pth file tricks (look in $VIRTUAL_ENV/lib/python3.?/site-packages) if installed "in development" and extraction/unpacking into that path for installation. Without that, there is not really a namespace, and your code won't be found, not with this older style. (The first, the installed one, will win.)

In a REPL, you can import the namespace, e.g. import marrow, then examine marrow.__path__ to see what was found/included as a diagnostic aid; my current WIP virtual environment on this machine has m.package, m.schema, and m.interface, which makes sense as I've been building releases of those recently. The more modern approach to native namespacing does allow for more free-form mixing, with the idea that a folder without an __init__.py just is a namespace, automatically merging across the PYTHONPATH, but that is not how namespaces used to work, alas. (All participants need that stub __init__.py with no other code present at all namespace levels.)

I am in the process of modernizing the entire Marrow ecosystem (I've begun with a few already, as noted above) to eliminate Python 2 legacy and begin embracing new Python 3 structures and approaches, including modern namespacing. Major version bump for everything and dependencies should be kept pinned less than these versions for any code needing the old namespaces still or on Python 2.

(I'm reacquiring a local Python 3.6 and 3.5 to investigate further.)

like image 50
amcgregor Avatar answered Sep 19 '22 11:09

amcgregor