Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When inheriting SQLAlchemy class from abstract class exception thrown: metaclass conflict: the metaclass of a derived class must be

The following code is a very simple implementation of a SqlAlchemy ORM with one simple table. The Mytable class tries to inherit from BaseAbstract.

The code throws the following exception:

Message: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

from abc import ABC
from sqlalchemy import Column, Integer, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

class BaseAbstract(ABC):
    """description of class"""

SQLALCHEMY_DATABASE_URI =\
   'mssql+pyodbc://(local)/TestDB?driver=SQL+Server+Native+Client+11.0'
SQLALCHEMY_TRACK_MODIFICATIONS = False

engine = create_engine(SQLALCHEMY_DATABASE_URI, echo=True)
Session = sessionmaker(bind=engine)
session = Session()

Base = declarative_base()
metadata = Base.metadata

class Mytable(Base, BaseAbstract):
    __tablename__ = 'myTable'

    id = Column(Integer, primary_key=True)
    firstNum = Column(Integer, nullable=False)
    secondNum = Column(Integer, nullable=False)

If you change the class declaration line to

class Mytable(Base):

the code will work fine. Also if you change class BaseAbstract(ABC): to class BaseAbstract(object): the code will again work fine. How do I inherit from an abstract class in SQLAlchemy?

like image 422
Barka Avatar asked Mar 30 '18 21:03

Barka


1 Answers

Mixing metaclasses is not easy and you should avoid it. SQLAlchemy offers a way to handle abstract base classes or augmenting the base, and on the other hand what you're trying to do looks a lot like a mixin.

You can instruct SQLAlchemy to skip creating a table and a mapper for a class using __abstract__:

Base = declarative_base()

class BaseAbstract(Base):
    """description of class"""
    __abstract__ = True

class Mytable(BaseAbstract):
    ...

You could also augment the Base class:

class BaseAbstract:
    """description of class"""

Base = declarative_base(cls=BaseAbstract)

class Mytable(Base):
    ...

But in my opinion the easiest solution is to forego using an "abstract base" altogether and think of it as a mixin, as you had done already in a way:

class CommonMixin:
    """description of class"""

Base = declarative_base()

class Mytable(CommonMixin, Base):
    ...

But if you insist on using an actual abc.ABC abstract base class, register your model classes as virtual subclasses:

class BaseAbstract(ABC):
    """description of class"""

Base = declarative_base()

@BaseAbstract.register
class Mytable(Base):
    ...

The downside is that @abc.abstractmethod decorated methods are not checked upon instantiating virtual subclasses.

If the above do not fulfill your needs and you want to use ABC for checking that required methods are implemented, you could try and do as the exception instructed and create a new metaclass that is the combination of DeclarativeMeta and ABCMeta:

In [6]: class DeclarativeABCMeta(DeclarativeMeta, abc.ABCMeta):
   ...:     pass
   ...: 

In [7]: Base = declarative_base(metaclass=DeclarativeABCMeta)

In [8]: class BaseAbstract(abc.ABC):
   ...:     @abc.abstractmethod
   ...:     def foo(self):
   ...:         pass
   ...:     

In [13]: class MyTable(Base, BaseAbstract):
    ...:     __tablename__ = 'mytable'
    ...:     id = Column(Integer, primary_key=True)
    ...:     

In [14]: MyTable()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-1686a36a17c6> in <module>()
----> 1 MyTable()

TypeError: "Can't instantiate abstract class MyTable with abstract methods foo"

In [18]: class MyOtherTable(Base, BaseAbstract):
    ...:     __tablename__ = 'myothertable'
    ...:     id = Column(Integer, primary_key=True)
    ...:     def foo(self):
    ...:         return 'bar'
    ...:     

In [19]: MyOtherTable()
Out[19]: <__main__.MyOtherTable at 0x7f01b4b592b0>

I cannot vouch for this, though. It might contain more than a few surprises.

like image 154
Ilja Everilä Avatar answered Sep 25 '22 07:09

Ilja Everilä