37

我想创建一个函数,给定表的名称,返回具有该表名的模型。例如:

class Model(Base):
    __tablename__ = 'table'
    ...a bunch of Columns

def getModelFromTableName(tablename):
   ...something magical

所以 getModelFromTableName('table') 应该返回 Model 类。

我的目标是在我正在制作的简单表单生成器中使用该函数,因为 FormAlchemy 不适用于 python3.2,我希望它能够很好地处理外键。

谁能给我任何关于如何让 getModelFromTableName 工作的指示?

这是我的一个想法(这可能是完全错误的,我之前没有使用过元类......)

如果我要让我的模型类继承自 Base 以及其他一些类 (TableReg) 并让 TableReg 的类元存储 Model。某些全局字典或单例中的表名。

我意识到这可能完全关闭,因为 Base 的元类做了一些我不想破坏的非常重要且非常漂亮的东西,但我认为必须有一种方法可以将一些构造函数代码附加到元类我的模型。或者我不明白。

4

9 回答 9

53

受到Eevee评论的启发:

def get_class_by_tablename(tablename):
  """Return class reference mapped to table.

  :param tablename: String with name of table.
  :return: Class reference or None.
  """
  for c in Base._decl_class_registry.values():
    if hasattr(c, '__tablename__') and c.__tablename__ == tablename:
      return c
于 2014-05-20T08:19:26.197 回答
12

因此,在 SQLAlchemy 版本 1.4.x(我更新到 2020-03-16 左右)中,它似乎_decl_class_registry不再存在。

我能够使用新的registry类属性(它不受保护,所以希望它不会被突然删除!)。

Base.TBLNAME_TO_CLASS = {}

for mapper in Base.registry.mappers:
    cls = mapper.class_
    classname = cls.__name__
    if not classname.startswith('_'):
        tblname = cls.__tablename__
        Base.TBLNAME_TO_CLASS[tblname] = cls

不确定这是否是最好的方法,但它是我如何做到的。

于 2021-03-17T03:57:46.557 回答
12

请注意OrangeTux的答案没有考虑架构。如果您在不同的模式中有表同音异义词,请使用:

def get_class_by_tablename(table_fullname):
  """Return class reference mapped to table.

  :param table_fullname: String with fullname of table.
  :return: Class reference or None.
  """
  for c in Base._decl_class_registry.values():
    if hasattr(c, '__table__') and c.__table__.fullname == table_fullname:
      return c

fullname是一个表属性见:

github.com/sqlalchemy/sqlalchemy/blob/master/lib/sqlalchemy/sql/schema.py#L530-L532

于 2016-02-04T11:11:41.293 回答
5

实用功能已添加到SQLAlchemy-Utils。有关更多信息,请参阅get_class_by_table 文档。SQLAlchemy-Utils 中的解决方案也能够涵盖单表继承场景。

import sqlalchemy as sa


def get_class_by_table(base, table, data=None):
    """
    Return declarative class associated with given table. If no class is found
    this function returns `None`. If multiple classes were found (polymorphic
    cases) additional `data` parameter can be given to hint which class
    to return.

    ::

        class User(Base):
            __tablename__ = 'entity'
            id = sa.Column(sa.Integer, primary_key=True)
            name = sa.Column(sa.String)


        get_class_by_table(Base, User.__table__)  # User class


    This function also supports models using single table inheritance.
    Additional data paratemer should be provided in these case.

    ::

        class Entity(Base):
            __tablename__ = 'entity'
            id = sa.Column(sa.Integer, primary_key=True)
            name = sa.Column(sa.String)
            type = sa.Column(sa.String)
            __mapper_args__ = {
                'polymorphic_on': type,
                'polymorphic_identity': 'entity'
            }

        class User(Entity):
            __mapper_args__ = {
                'polymorphic_identity': 'user'
            }


        # Entity class
        get_class_by_table(Base, Entity.__table__, {'type': 'entity'})

        # User class
        get_class_by_table(Base, Entity.__table__, {'type': 'user'})


    :param base: Declarative model base
    :param table: SQLAlchemy Table object
    :param data: Data row to determine the class in polymorphic scenarios
    :return: Declarative class or None.
    """
    found_classes = set(
        c for c in base._decl_class_registry.values()
        if hasattr(c, '__table__') and c.__table__ is table
    )
    if len(found_classes) > 1:
        if not data:
            raise ValueError(
                "Multiple declarative classes found for table '{0}'. "
                "Please provide data parameter for this function to be able "
                "to determine polymorphic scenarios.".format(
                    table.name
                )
            )
        else:
            for cls in found_classes:
                mapper = sa.inspect(cls)
                polymorphic_on = mapper.polymorphic_on.name
                if polymorphic_on in data:
                    if data[polymorphic_on] == mapper.polymorphic_identity:
                        return cls
            raise ValueError(
                "Multiple declarative classes found for table '{0}'. Given "
                "data row does not match any polymorphic identity of the "
                "found classes.".format(
                    table.name
                )
            )
    elif found_classes:
        return found_classes.pop()
    return None
于 2015-03-03T12:11:05.207 回答
2

对于 sqlalchemy 1.4.x(对于未来的读者来说也可能是 2.0.x)当模型分布在许多文件中时,您可以很好地扩展Erotemic答案以更方便(这种情况是在正确执行时查找 ORM 类的主要原因哎呀)。

参加这样的课程并Base从中受益:

from sqlalchemy.orm import declarative_base

class BaseModel:

    @classmethod
    def model_lookup_by_table_name(cls, table_name):
        registry_instance = getattr(cls, "registry")
        for mapper_ in registry_instance.mappers:
            model = mapper_.class_
            model_class_name = model.__tablename__
            if model_class_name == table_name:
                return model


Base = declarative_base(cls=BaseModel)

然后声明您的模型,即使在单独的模块中,也可以让您在cls.model_lookup_by_table_name(...)不导入任何内容的情况下使用方法,只要您是从 a 派生的Base

user_models.py

from sqlalchemy import Column, Integer

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)

    # ... and other columns

    def some_method(self):
        # successfully use lookup like this
        balance_model = self.model_lookup_by_table_name("balance")
        # ...
        return balance_model

balance_models.py

from sqlalchemy import Column, Integer

class Balance(Base):
    __tablename__ = "balance"

    id = Column(Integer, primary_key=True)

    # ... other columns

    def some_method(self):
        # lookup works on every model
        user_model = self.model_lookup_by_table_name("user")
        # ...
        return user_model

它按预期工作:

>>> User().some_method()
<class 'balance_models.Balance'>
>>> Balance().some_method()
<class 'user_models.User'>
>>> Base.model_lookup_by_table_name("user")
<class 'user_models.User'>
>>> Base.model_lookup_by_table_name("balance")
<class 'balance_models.Balance'>

您可以安全地缓存此方法的输出functools.lru_cache以提高性能(for在不需要时避免 python 循环)。此外,您可以以相同的方式添加更多查找,例如通过类名(不仅是本例中的表名)

于 2021-08-20T12:35:39.863 回答
1

一种方法是在 mixin__table_cls__Base或作为 mixin 定义一个类方法。请注意,我没有用任何继承测试过这个。一个例子,如果扩充Base

# Python 3.7.2, SQLAlchemy 1.3.13
###
# app/models.py
from sqlalchemy import Table
from sqlalchemy.ext.declarative import declarative_base

class Base(object):
    @classmethod
    def __table_cls__(cls, *args, **kwargs):
        t = Table(*args, **kwargs)
        t.decl_class = cls
        return t

Base = declarative_base(cls=Base)

设计一个表格示例:

# app/models.py
class Account(Base):  # Note this is the customized Base class
    __tablename__ = 'account'
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    account_number = Column(String)

在控制台中测试:

# Python 3.7.2 Console
>>> from app import models as m
>>> account_table = m.Base.metadata.tables['account']
>>> account_table.__class__
<class 'sqlalchemy.sql.schema.Table'>

>>> account_table.decl_class
<class 'app.models.Account'>

作为一个函数:

def get_class_by_table_name(table_name):
    tbl = Base.metadata.tables[table_name]
    return tbl.decl_class

旁注:由于很多答案都引用Base._decl_class_registry了 ,因此该declarative_base()函数还接受class_registry命名参数的字典。

>>> registered_classes = {}
>>> Base = declarative_base(cls=Base, class_registry=registered_classes)
>>> registered_classes.keys()
dict_keys(['Account', '_sa_module_registry', 'AccountType', ...])
于 2020-03-04T20:28:46.920 回答
1

如果您正在使用 sqlalchemy automap,则需要稍作修改。我花了几分钟调整它:

def get_class_by_tablename(tablename, Base):
  for c in Base._decl_class_registry.values():
    if c.__table__.name == tablename:
      return c
于 2018-09-27T15:04:20.153 回答
0

https://stackoverflow.com/a/23754464/12903047

对于 flask-sqlachemy 使用它:

db.Model.registry._class_registry.values()

于 2021-04-21T10:19:49.330 回答
-1

我打算删除它,但我认为评论中的讨论可能对想了解一些良好做法的人有用。用少许盐来回答这个问题。


像这样的东西可以解决问题:

def getModelFromTableName(sTable):
    """
    return the Model class with the given __tablename__
    """
    globals = globals()
    for k in globals:
        if type(globals[k]) == sqlalchemy.ext.declarative.DeclarativeMeta:
            try:
                if globals[k].__tablename__ == sTable:
                    return globals[k]
            except:
                pass
    return None
于 2012-07-30T09:03:12.267 回答