¿Configurando dinámicamente __tablename__ para sharding en SQLAlchemy?

Para manejar una tabla de base de datos en crecimiento, estamos fragmentando el nombre de la tabla. Entonces podríamos tener tablas de base de datos que se nombran así:

table_md5one table_md5two table_md5three 

Todas las tablas tienen exactamente el mismo esquema.

¿Cómo utilizamos SQLAlchemy y especificamos dinámicamente el nombre de tabla para la clase que corresponde a esto? Parece que las clases declarative_base () necesitan tener un nombre de tabla preespecificado.

Eventualmente habrá demasiadas tablas para especificar manualmente las clases derivadas de una clase principal / base. Queremos poder construir una clase que pueda tener el nombre de tabla configurado dinámicamente (tal vez pasado como un parámetro a una función).

OK, nos fuimos con la statement SQLAlchemy personalizada en lugar de la declarativa.

Así que creamos un objeto de tabla dinámica como este:

 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 

Donde ActualTableObject es la asignación de clase a la tabla.

En Aumentar la base , encontrará una forma de utilizar una clase Base personalizada que puede, por ejemplo, calcular dinámicamente el __tablename__ __tablename__:

 class Base(object): @declared_attr def __tablename__(cls): return cls.__name__.lower() 

El único problema aquí es que no sé de dónde viene tu hash, pero esto debería dar un buen punto de partida.

Si necesita este algoritmo no para todas sus tablas, sino solo para una, solo puede utilizar el atributo decla_attr en la tabla que le interesa descartar.

Prueba esto

 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) 

Debido a que insisto en usar clases declarativas con su __tablename__ dinámicamente especificado por un parámetro dado, después de días de fallar con otras soluciones y horas de estudiar los aspectos internos de SQLAlchemy, se me ocurre la siguiente solución que creo que es simple, elegante y sin condiciones de carrera.

 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 

Ya que tienen su propio class_registry , no obtendrás esa advertencia que dice:

Esta base declarativa ya contiene una clase con el mismo nombre de clase y nombre de módulo que mypackage.models.MyModel, y se reemplazará en la tabla de búsqueda de cadenas.

Por lo tanto, no podrá hacer referencia a ellos desde otros modelos con búsqueda de cadenas. Sin embargo, funciona perfectamente bien usar estos modelos declarados sobre la marcha también para claves externas:

 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) 

Si solo los utiliza para consultar / insertar / actualizar / eliminar sin dejar ninguna referencia, como la referencia de clave externa de otra tabla, ellos, sus clases base y también su clase_registro serán recolectados, por lo que no se dejará rastro.

puede escribir una función con el parámetro tablename y devolver la clase con la configuración de los atributos apropiados.

 def get_class(table_name): class GenericTable(Base): __tablename__ = table_name ID= Column(types.Integer, primary_key=True) def funcation(self): ...... return GenericTable 

Luego puedes crear una tabla usando:

 get_class("test").__table__.create(bind=engine) # See sqlachemy.engine