如何将我自己的 Meta class 与作为父级的 SQLAlchemy-Model 一起使用 class

How to use my own Meta class together with SQLAlchemy-Model as a parent class

我最近开始使用 Flask 作为我的后端框架。但是,最近我遇到了一个问题,我想不出如何解决它。作为最后的手段,我想在这里尝试我的改变。如果你能帮助我,我将不胜感激。

所以,我有一个 class 继承自 SQLAlchemy 的 db.Model:

from flask_sqlalchemy import SQLAlchemy


db = SQLAlchemy()


class User(db.Model):
  ...

我想创建自己的 Meta class 以自动向其中插入一些 Mixins class:

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

但是,当我尝试将此 Meta class 与 User class:

一起使用时
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的高手classes,所以如果我理解有误,请原谅我的不理解)

终于解决了我的问题。如果其他人遇到同样的问题,我会在这里发布解决方案。

这是摘自 SQLAlchemy's website:

的片段

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

  • BindMetaMixin:bind_key 从 class 中提取并应用于 table。查看具有绑定的多个数据库。
  • NameMetaMixin:如果模型没有指定 tablename 但指定了主键,则会自动生成一个名称。

您可以通过定义自己的元class 并自己创建声明性基础来添加自己的行为。确保仍然继承你想要的混合(或者只是继承默认的元class)。

如上所示,将声明性基础 class 而不是简单模型基础 class 传递给 base_class 将导致 Flask-SQLAlchemy 使用此基础而不是构造一个默认元class.

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() 以根据需要自定义基础 class。


除此之外,我最初的目的实际上是为我的模型添加一些额外的功能,这些功能继承自 db.Model。实际上,您不需要使用 Meta classes 来做到这一点。因此,对于这种情况,我们可以扩展 db.Model class,如同一网页中所述。这是一个给每个模型一个整数主键,或者一个用于连接的外键的例子-table 继承:

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)

其实如果你对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

关键步骤是SqlAlchemyBase的metaclass要和自己设计的metclass保持一致。一种解决方案是创建一个新的元类,它是它们的子类。