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:
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
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")
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"))
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"))
(empty)
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?
Changes in import statement python3. In Python 3, implicit relative imports within packages are no longer available - only absolute imports and explicit relative imports are supported. In addition, star imports (e.g. from x import *) are only permitted in module level code.
The change from Python 2 to Python 3 was taken as an opportunity to "fix" some issues with Python 2. Among them promoting Unicode more uniformly throughout the language and to clear up some issues in the syntax such as print being a statement rather than a function.
Another key point is that modernizing your Python 2 code to also support Python 3 is largely automated for you. While you might have to make some API decisions thanks to Python 3 clarifying text data versus binary data, the lower-level work is now mostly done for you and thus can at least benefit from the automated changes immediately.
Another way to help port your code is to use a static type checker like mypy or pytype on your code. These tools can be used to analyze your code as if it’s being run under Python 2, then you can run the tool a second time as if your code is running under Python 3.
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.
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