为了处理不断增长的数据库表,我们对表名进行分片。所以我们可以有这样命名的数据库表:
table_md5one
table_md5two
table_md5three
所有表都具有完全相同的架构。
我们如何使用 SQLAlchemy 并动态指定与此对应的类的表名?看起来 declarative_base() 类需要预先指定表名。
最终将有太多的表来手动指定来自父/基类的派生类。我们希望能够构建一个可以动态设置表名的类(可能作为参数传递给函数。)
为了处理不断增长的数据库表,我们对表名进行分片。所以我们可以有这样命名的数据库表:
table_md5one
table_md5two
table_md5three
所有表都具有完全相同的架构。
我们如何使用 SQLAlchemy 并动态指定与此对应的类的表名?看起来 declarative_base() 类需要预先指定表名。
最终将有太多的表来手动指定来自父/基类的派生类。我们希望能够构建一个可以动态设置表名的类(可能作为参数传递给函数。)
好的,我们使用自定义 SQLAlchemy 声明而不是声明性声明。
所以我们像这样创建一个动态表对象:
from sqlalchemy import MetaData, Table, Column
def get_table_object(self, md5hash):
metadata = MetaData()
table_name = 'table_' + md5hash
table_object = Table(table_name, metadata,
Column('Column1', DATE, nullable=False),
Column('Column2', DATE, nullable=False)
)
clear_mappers()
mapper(ActualTableObject, table_object)
return ActualTableObject
其中 ActualTableObject 是映射到表的类。
在Augmenting the Base中,您找到了一种使用自定义Base
类的方法,例如,可以__tablename__
动态计算属性:
class Base(object):
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
这里唯一的问题是我不知道你的哈希来自哪里,但这应该是一个很好的起点。
如果您不是所有表都需要此算法,而只需要其中一个,则可以只在declared_attr
对分片感兴趣的表上使用。
您可以使用 tablename 参数编写一个函数,并通过设置适当的属性发回该类。
def get_class(table_name):
class GenericTable(Base):
__tablename__ = table_name
ID= Column(types.Integer, primary_key=True)
def funcation(self):
......
return GenericTable
然后您可以使用以下方法创建表:
get_class("test").__table__.create(bind=engine) # See sqlachemy.engine
因为我坚持使用__tablename__
由给定参数动态指定的声明性类,经过数天的其他解决方案失败和数小时的 SQLAlchemy 内部研究后,我提出了以下解决方案,我认为它简单、优雅且无竞争条件。
def get_model(suffix):
DynamicBase = declarative_base(class_registry=dict())
class MyModel(DynamicBase):
__tablename__ = 'table_{suffix}'.format(suffix=suffix)
id = Column(Integer, primary_key=True)
name = Column(String)
...
return MyModel
由于他们有自己的class_registry
,您不会收到警告说:
这个声明性基础已经包含一个与 mypackage.models.MyModel 具有相同类名和模块名的类,并将在字符串查找表中被替换。
因此,您将无法通过字符串查找从其他模型中引用它们。但是,将这些即时声明的模型用于外键也可以很好地工作:
ParentModel1 = get_model(123)
ParentModel2 = get_model(456)
class MyChildModel(BaseModel):
__tablename__ = 'table_child'
id = Column(Integer, primary_key=True)
name = Column(String)
parent_1_id = Column(Integer, ForeignKey(ParentModel1.id))
parent_2_id = Column(Integer, ForeignKey(ParentModel2.id))
parent_1 = relationship(ParentModel1)
parent_2 = relationship(ParentModel2)
如果您只使用它们来查询/插入/更新/删除而没有留下任何引用,例如来自另一个表的外键引用,它们、它们的基类以及它们的 class_registry 将被垃圾收集,因此不会留下任何痕迹。
试试这个
import zlib
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, BigInteger, DateTime, String
from datetime import datetime
BASE = declarative_base()
ENTITY_CLASS_DICT = {}
class AbsShardingClass(BASE):
__abstract__ = True
def get_class_name_and_table_name(hashid):
return 'ShardingClass%s' % hashid, 'sharding_class_%s' % hashid
def get_sharding_entity_class(hashid):
"""
@param hashid: hashid
@type hashid: int
@rtype AbsClientUserAuth
"""
if hashid not in ENTITY_CLASS_DICT:
class_name, table_name = get_class_name_and_table_name(hashid)
cls = type(class_name, (AbsShardingClass,),
{'__tablename__': table_name})
ENTITY_CLASS_DICT[hashid] = cls
return ENTITY_CLASS_DICT[hashid]
cls = get_sharding_entity_class(1)
print session.query(cls).get(100)
您可以使用通常的 declarative_base 并进行闭包来设置表名,而不是使用命令式创建 Table 对象,如下所示:
def make_class(Base, table_name):
class User(Base):
__tablename__ = table_name
id = Column(Integer, primary_key=True)
name= Column(String)
return User
Base = declarative_base()
engine = make_engine()
custom_named_usertable = make_class(Base, 'custom_name')
Base.metadata.create_all(engine)
session = make_session(engine)
new_user = custom_named_usertable(name='Adam')
session.add(new_user)
session.commit()
session.close()
engine.dispose()