Dynamically setting __tablename__ for sharding in SQLAlchemy?

后端 未结 6 1514
我寻月下人不归
我寻月下人不归 2020-12-28 17:11

In order to handle a growing database table, we are sharding on table name. So we could have database tables that are named like this:

table_md5one
table_md5         


        
相关标签:
6条回答
  • 2020-12-28 17:21

    Because I insist to use declarative classes with their __tablename__ dynamically specified by given parameter, after days of failing with other solutions and hours of studying SQLAlchemy internals, I come up with the following solution that I believe is simple, elegant and race-condition free.

    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
    

    Since they have their own class_registry, you will not get that warning saying:

    This declarative base already contains a class with the same class name and module name as mypackage.models.MyModel, and will be replaced in the string-lookup table.

    Hence, you will not be able to reference them from other models with string lookup. However, it works perfectly fine to use these on-the-fly declared models for foreign keys as well:

    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)
    

    If you only use them to query/insert/update/delete without any reference left such as foreign key reference from another table, they, their base classes and also their class_registry will be garbage collected, so no trace will be left.

    0 讨论(0)
  • 2020-12-28 17:38

    OK, we went with the custom SQLAlchemy declaration rather than the declarative one.

    So we create a dynamic table object like this:

    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
    

    Where ActualTableObject is the class mapping to the table.

    0 讨论(0)
  • 2020-12-28 17:38

    you can write a function with tablename parameter and send back the class with setting appropriate attributes.

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

    Then you can create a table using:

    get_class("test").__table__.create(bind=engine)  # See sqlachemy.engine
    
    0 讨论(0)
  • 2020-12-28 17:40

    Try this

    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)
    
    0 讨论(0)
  • 2020-12-28 17:41

    Instead of using imperative creating Table object, you can use usual declarative_base and make a closure to set a table name as the following:

    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()
    
    0 讨论(0)
  • 2020-12-28 17:43

    In Augmenting the Base you find a way of using a custom Base class that can, for example, calculate the __tablename__ attribure dynamically:

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

    The only problem here is that I don't know where your hash comes from, but this should give a good starting point.

    If you require this algorithm not for all your tables but only for one you could just use the declared_attr on the table you are interested in sharding.

    0 讨论(0)
提交回复
热议问题