change in import handling / modules from python2 to python3?

I was trying to following the design pattern shown in this previous question related to SQLAlchemy and intended to share a common Base instance across multiple files. The code exactly as is works on python2 and python3.

However, when I move the files a.py, b.py, c.py, and base.py in a module (called model) and add the necessary init.py file, it continues to work on python2 but then produces an error on python3 (details below).

I have the following files:

model/base.py

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

model/a.py

from sqlalchemy import *
from base import Base
from sqlalchemy.orm import relationship

class A(Base):
    __tablename__ = "A"
    id  = Column(Integer, primary_key=True)
    Bs  = relationship("B", backref="A.id")
    Cs  = relationship("C", backref="A.id")

model/b.py

from sqlalchemy import *
from base import Base

class B(Base):
    __tablename__ = "B"
    id    = Column(Integer, primary_key=True)
    A_id  = Column(Integer, ForeignKey("A.id"))

model/c.py

from sqlalchemy import *
from base import Base

class C(Base):
    __tablename__ = "C"    
    id    = Column(Integer, primary_key=True)
    A_id  = Column(Integer, ForeignKey("A.id"))

model/init.py

(empty)

main.py

from sqlalchemy import create_engine
from sqlalchemy.orm import relationship, backref, sessionmaker

from model import base


from model import a
from model import b
from model import c

engine = create_engine("sqlite:///:memory:")
base.Base.metadata.create_all(engine, checkfirst=True)
Session = sessionmaker(bind=engine)
session = Session()

a1 = a.A()
b1 = b.B()
b2 = b.B()
c1 = c.C()
c2 = c.C()

a1.Bs.append(b1)
a1.Bs.append(b2)    
a1.Cs.append(c1)
a1.Cs.append(c2)    
session.add(a1)
session.commit()

python2 works:

$ python main.py ; echo $?
0

python3 errs with:

$ python3 main.py ; echo $?
Traceback (most recent call last):
  File "main.py", line 7, in <module>
    from model import a
  File "/home/shale/code/py/try/model/a.py", line 2, in <module>
    from base import Base
ImportError: No module named base
1

I ultimately solved this by putting the code from base.py into my init.py file (described as one answer below), but does anyone know why this produces an error in python3 but not in python2? What change is responsible for this in the first place?

Best answer

Python 3 switches to absolute imports by default, and disallows unqualified relative imports. The from base import Base line is such an import.

Python 3 will only look for top-level modules; you don’t have a base top-level module, only model.base. Use a full module path, or use relative qualifiers:

from .base import Base

The . at the start tells Python 3 to import starting from the current package.

You can enable the same behaviour in Python 2 by adding:

from __future__ import absolute_import

This is a change introduced by PEP 328, and the from future import is available from Python 2.5 onwards.