如何将我自己的Meta类与SQLAlchemy-Model一起用作父类

时间:2019-04-30 16:34:10

标签: python types sqlalchemy metaclass

我最近开始使用Flask作为后端框架。但是,最近我遇到了一个问题,我不知道该如何解决。作为最后的选择,我想在这里尝试更改。如果您能帮助我,我将不胜感激。

因此,我有一个从SQLAlchemy的db.Model继承的类:

from flask_sqlalchemy import SQLAlchemy


db = SQLAlchemy()


class User(db.Model):
  ...

我想创建自己的Meta类,以自动将一些Mixins插入此类:

class MyMetaClass(type):
  def __new__(meta, name, bases, class_dict):
    bases += (Mixin1, Mixin2, Mixin3)
    return type.__new__(meta, name, bases, class_dict)

但是,当我尝试将此Meta类与 User 类一起使用时:

class User(db.Model, metaclass=MyMetaClass):
   ...

我遇到以下错误:

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

有人知道如何解决这个问题吗?任何帮助将不胜感激。

(注意:我既不是Flask也不是Meta类的大师。因此,如果我理解错误,请原谅我缺乏理解)

2 个答案:

答案 0 :(得分:1)

最后,我设法解决了我的问题。万一其他人遇到同样的问题,我会在这里发布解决方案。

这是摘录自SQLAlchemy's website的片段:


在定义模型子类时,模型元类负责设置SQLAlchemy内部。 Flask-SQLAlchemy通过mixins添加了一些额外的行为。它的默认元类DefaultMeta继承了它们。

  • BindMetaMixin: bind_key 从类中提取并应用于表。请参阅具有绑定的多个数据库。
  • NameMetaMixin:如果模型未指定表名,但是指定了主键,则会自动生成一个名称。

您可以通过定义自己的元类并自己创建声明性基础来添加自己的行为。确保仍然从所需的mixin继承(或仅从默认元类继承)。

如上所示,将声明性基类而不是简单的模型基类传递给base_class将导致Flask-SQLAlchemy使用该基类,而不是使用默认的元类构造基类。

from flask_sqlalchemy import SQLAlchemy
from flask_sqlalchemy.model import DefaultMeta, Model

class CustomMeta(DefaultMeta):
    def __init__(cls, name, bases, d):
        # custom class setup could go here

        # be sure to call super
        super(CustomMeta, cls).__init__(name, bases, d)

    # custom class-only methods could go here

db = SQLAlchemy(model_class=declarative_base(
    cls=Model, metaclass=CustomMeta, name='Model'))

您还可以根据需要将任何其他参数传递给declarative_base()以自定义基类。


除此之外,我的最初目的实际上是向我的模型添加一些其他功能,这些功能是从 db.Model 继承的。为此,您实际上不需要使用Meta类。因此,对于那种情况,我们可以扩展 db.Model 类,如同一网页中所述。这是为每个模型提供整数主键或联接表继承的外键的示例:

from flask_sqlalchemy import Model, SQLAlchemy
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declared_attr, has_inherited_table

class IdModel(Model):
    @declared_attr
    def id(cls):
        for base in cls.__mro__[1:-1]:
            if getattr(base, '__table__', None) is not None:
                type = sa.ForeignKey(base.id)
                break
        else:
            type = sa.Integer

        return sa.Column(type, primary_key=True)

db = SQLAlchemy(model_class=IdModel)

class User(db.Model):
    name = db.Column(db.String)

class Employee(User):
    title = db.Column(db.String)

答案 1 :(得分:0)

实际上,如果您非常熟悉python中的元类机制,则问题很简单。我在SqlAlchemy中给你一个例子。

from sqlalchemy.ext.declarative import as_declarative, declared_attr

@as_declarative()
class Base():
    id = Column(Integer, primary_key=True, index=True)
    __name__: str
    @declared_attr
    def __tablename__(cls) -> str:
        return cls.__name__.lower()

    @property
    def url(self):
        return f'/{self.__class__.__name__.lower()}/{self.id}/'

class AnotherMetaClass(type):
    def __new__(cls, name, bases, attrs):
        pass
        # do something

class ModelMeta(Base.__class__, AnotherMetaClass):
    ...

class User(Base, metaclass=ModelMeta):
   pass

关键步骤是您应使BaseSqlAlchemy的元类与您自己设计的metclass相一致。一种解决方案是创建一个新的元类,它们是这两个类的子类。