如何将一个类映射到sqlalchemy orm中的多个数据库

时间:2015-08-19 20:26:10

标签: python orm sqlalchemy

我通过sqlalchemy使用sqlite数据库作为我的应用程序文件。我有一个单独的配置文件。

有些类的信息我会在我的应用程序文件中保留,我想在我的配置文件中复制。问题是我会根据可用性从一个或另一个来源加载它。

我在文档中看到了这一点,但我认为它并不直接适用,因为辅助映射不会保留信息。而且,主要的概念是模糊的。但是,两个数据库都会携带相同的信息,但可能不在同一版本上。 http://sqlalchemy.readthedocs.org/en/rel_1_0/orm/nonstandard_mappings.html#multiple-mappers-for-one-class

我会尝试用一个例子来说明一点:

我有一个A类,它代表一个多字段用户输入。我把它保存在我的应用程序文件中。

我的应用程序文件文件中的B类也由A类实例组成。

A类中的同一个实例可能会组成几个合适的B类实例。这些实例都存储在我的应用程序文件中。

我的问题是,在另一个会话中,使用全新的配置文件,我可能希望重用该A类实例。我不能只在应用程序文件上使用它,因为如果它得到更新,它将与使用它的所有应用程序文件相关。

另一方面,它不能只在配置文件中,因为用户可能与另一个共享他的应用程序文件,后者可能没有合适的配置,必须手动完成。

我需要在两个地方都拥有它,能够选择哪个数据库在运行时成为源,并且所有更改都会在两个数据库上同时存在。

可以在sqlalchemy + sqlite中完成吗?这是个好主意吗?这有经典的解决方案吗?

编辑: 我想我正在描述看起来像缓存的东西,而sqlalchemy却没有这样做。是否还有其他方法? sqlalchemy是否允许我在创建实例时将实例映射到数据库?这将允许将相同类的两个实例映射到不同的数据库。然后我会通过sqlalchemy监听更新事件,并向其他数据库发出相同的sql。我也不知道该怎么做。

另一个选择:根据联合查询映射我的类。 Sqlalchemy可能会允许任意选择,但是存在持久性问题。

另一种选择:向引擎添加一个层,使其同时连接到两个数据库,同时向读取和写入发出相同的命令。我可以处理重复的回报。

1 个答案:

答案 0 :(得分:0)

我想出了下面的mixin。我没有处理expunge或rollback,因为我不在我的应用程序中使用它们,也不知道如何处理它们。 看起来它正在发挥作用。我将继续扩展它来处理集合。

import os
from sqlalchemy import Column, Float, String, Enum, Integer, event
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import orm
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

class ReplicateMixin:
    @classmethod
    def get_or_create(cls,prime_session, sessoes = None, **kwargs):
        if sessoes is None:
            sessoes = []
        if not isinstance(sessoes, list):
            sessoes = [sessoes]
        sessoes = [prime_session] + sessoes #They are passed separatelly just to make explicit that the first might receive diferent treatment
        replicas = []

        for sessao in sessoes: #Gets a result or creates a new instance from each database
            instance = sessao.query(Datum).filter_by(**kwargs).first()
            if instance is None: 
                instance = cls(**kwargs)
                setattr(instance, "__new", True)
            sessao.add(instance) 
            instance.sessao = sessao
            replicas.append(instance)

        fittest = cls.__select_fittest(replicas) #Selects the instance whose data will prevail 
        prime = replicas.pop(0) #Instance from the session we will be issuing commits to. The others must simply follow.
        cls.__copy_data(fittest, prime, ReplicateMixin.__get_primary_keys(prime))

        setattr(prime, "__replicas", replicas) #The object will carry references to its copies
        return prime

    @staticmethod
    def __select_fittest(instances):
        """This method should contain logic for choosing the instance that has
        the most relevant information. It may be altered by child classes"""
        if getattr(instances[0], "__new", False):
            return instances[1]
        else:
            return instances[0] 

    @staticmethod
    def __copy_data(source, dest, primary_keys = None):
        primary_keys = [] if primary_keys is None else primary_keys
        for prop in orm.class_mapper(type(source)).iterate_properties:
            if (isinstance(prop, orm.ColumnProperty)
                and prop.key not in primary_keys):
                setattr(dest, prop.key,
                        getattr(source, prop.key))

    @staticmethod
    def __replicate(mapper, connection, original_obj):
        replicants = getattr(original_obj, "__replicas", []) #if it IS a replicant it will not have a __replicas attribute
        primary_keys = ReplicateMixin.__get_primary_keys(original_obj)
        for objeto in replicants:
            ReplicateMixin.__copy_data(original_obj, objeto, primary_keys)
            objeto.sessao.commit()

    @staticmethod
    def __replicate_del(mapper, conection, original_obj):
        replicants = getattr(original_obj, "__replicas", []) #if it IS a replicant it will not have a __replicas attribute
        for objeto in replicants:
            if objeto in objeto.sessao.new:
                objeto.sessao.expunge(objeto)
            else:
                objeto.sessao.delete(objeto)
            objeto.sessao.commit()

    @staticmethod
    def __get_primary_keys(mapped_object):
        return [key.name for key in orm.class_mapper(type(mapped_object)).primary_key]

    @classmethod
    def __declare_last__(cls):
        """Binds certain events to functions"""
        event.listen(cls, "before_insert", cls.__replicate)
        event.listen(cls, "before_update", cls.__replicate)
        event.listen(cls, "before_delete", cls.__replicate_del)
        #FIXME might not play well with rollback  

示例:

DeclarativeBase = declarative_base()
class Datum (ReplicateMixin, DeclarativeBase):
    __tablename__ = "xUnitTestData"
    Key = Column(Integer, primary_key=True)
    Value = Column(Float)
    nome = Column(String(10))
    def __repr__(self):
        return "{}; {}; {}".format(self.Key, self.Value, self.nome)




end_local = os.path.join(os.path.expanduser("~"), "Desktop", "local.bd")
end_remoto = os.path.join(os.path.expanduser("~"), "Desktop", "remoto.bd")
src_engine = create_engine('sqlite:///'+end_local, echo=False)
dst_engine = create_engine('sqlite:///'+end_remoto, echo=False)
DeclarativeBase.metadata.create_all(src_engine)
DeclarativeBase.metadata.create_all(dst_engine)
SessionSRC = sessionmaker(bind=src_engine)
SessionDST = sessionmaker(bind=dst_engine)


session1 = SessionSRC()
session2 = SessionDST()

item = Datum.pegar_ou_criar(session1, session2, Value = 0.5, nome = "terceiro")

item.Value = item.Value/2
print(item)
session1.delete(item)

session1.commit()
session1.close()