SQLAlchemy ORM 重构器的替代方案

Posted

技术标签:

【中文标题】SQLAlchemy ORM 重构器的替代方案【英文标题】:Alternative to SQLAlchemy ORM reconstructor 【发布时间】:2017-03-15 05:11:35 【问题描述】:

我目前正在使用 Python (3.6) 开发一个小应用程序,该应用程序能够处理股票投资组合,并对其进行一些财务分析。

这些功能只是我的投资组合和股票的 CRUD 界面(创建、读取、更新、删除)。它们可以保存在数据库中(使用 PostgreSQL 和 SQLAlchemy 作为 ORM)

问题是,我的目标是在以下之间实现绝对解耦

“业务逻辑”(位于包core 中),其中有我的Python 对象StockPortfolioRegression 等。 “持久层”(位于单独的包 database 中),其中定义了所有 SQLAlchemy 元数据以及与 core 包对象的映射

我想要这种分离有几个原因:首先,我希望能够独立启动 core 包(既没有安装任何数据库,也没有安装 SQLAlchemy),其次,我认为这是一个很好的做法,因为在我的 core 包,我真的可以只专注于分析,而不是使用 SQLA Columnrelationships 等“污染”我的 Python 对象。

到目前为止,我使用低级 SQLAlchemy mapperTable 设法做到了。 但是,只有一点我无法弄清楚。目前我的 Python Stock 对象是这样的:

class Stock:

    def __init__(self, ticker: str, exchange: Exchange, name: str=None):
        self.ticker = ticker
        self.exchange = exchange
        self.name = name
        self._data = None

    ...

需要_data 属性来存储我的财务数据,我想实例化它。

但是,您可能知道,当我从数据库加载Stock 时(在我实际使用database pkg 的情况下),例如通过session.query(Stock).first() 之类的命令,然后是@987654342 SQLA 不调用@函数。我唯一能做的就是添加一个函数:

@orm.reconstructor
def init_on_load(self):
    self._data = None

其中ormsqlalchemy.orm,并且是我的core pkg 中sqlalchemy唯一 导入。我想摆脱它!

有人有想法吗?例如,也许有一种方法可以通过mapperdatabase 包内部实例化_data 属性,但我不知道。

谢谢

【问题讨论】:

如果你只需要一个默认值(在这种情况下为None),你能不把_data = None放在类级别吗? "reconstructor() 是进入更大的“实例级”事件系统的快捷方式,可以使用事件 API 订阅该事件 - 有关这些事件的完整 API 描述,请参阅 InstanceEvents。” ,其中load 您可能会感兴趣。 感谢您的回答。我将查看“事件管理”部分,特别是此加载事件,并将在我的解决方案中发布答案,并引用您的话。或者,如果您愿意,可以重新发布您的评论作为答案,以便我验证! @univerio 但在这种情况下,我的所有实例都将共享相同的 _data,不是吗? 如果您希望默认值是可变对象,它们将共享相同的_data,例如_data = ,但如果你只是想让它成为一个值,例如_data = None,那么它们将共享相同的值,但这并不重要。如果你希望它是一个可变对象,你可以放置一个描述符来初始化第一次访问时的值。 【参考方案1】:

编辑:

回复评论,“数据映射器不是侵入性的,因此您可以在数据层和业务层使用一个数据类”。仅当项目很小时才如此。但是当项目变得更大时,底层数据存储不会以与您的业务逻辑相同的速度发生变化。然后你需要一些中间层,例如适配器、变压器等。

无论如何,如果您的问题与解耦无关,只是想让它起作用,那么请使用 @orm.reconstructor 或任何技巧。否则,请尽可能使用最少的 hack 或解决方法。

========================================

根据我的经验,ORM 类与底层 OMR 框架相结合,无论您选择哪种框架,SQLAlchemy、MongoEngine 等。

如果你想抽象数据存储,将来可以切换数据引擎。然后最好在一些普通的旧类上编写您的核心业务逻辑。然后在存储模块中,提供一个将 ORM 类转换为普通类的函数。

例如

# module core.objects
class Stock:
    def __init(self, name, price):
        self.name = name
        self.price = price

# business logic
class BusinessLogic:
    @staticmethod
    def logic(stocks: list[Stock]):
        pass

然后在存储包中

from core.objects import Stock as CoreStock
# This is the ORM dependent class
class Stock(Model):
    name = columns.Text()
    price = columns.Float()

    @classmethod
    def to_core_objects(instances):
        return [CoreStock(instance.name, instance.price) for instance in instances]

现在可以轻松切换存储引擎、mysql、csv等,编写单元测试也更轻松。

【讨论】:

“ORM 类与底层 OMR 框架相结合”对于基于 Active Record 的 ORM 来说是正确的,但对于数据映射器不一定如此(OP 已经使用映射分离了普通类中的逻辑和持久性)。 ORM 是 orm。这是侵入性的,数据映射器仅适用于一个 ORM 框架,并且它有自己的怪癖。否则,OP没有这样的问题。意图是一回事,执行是另一回事。 @IljaEverilä No hacks required。 reconstructor() 装饰器只是加载实例事件周围的一个便利函数,它允许所需的解耦。 顺便说一句,我并不是想说适配器等是完全没有必要的,尤其是在大型项目中。但在这种特殊情况下,有一个符合要求的干净解决方案。

以上是关于SQLAlchemy ORM 重构器的替代方案的主要内容,如果未能解决你的问题,请参考以下文章

python ORM框架:SqlAlchemy

SQLAlchemy ORM 转换为 pandas DataFrame

数据访问层中标准 ORM 的替代方案是啥? [关闭]

SQLAlchemy 比 Django 自带的 ORM 好在哪里

应用程序设置管理器的单例模式的替代方案

python中支持向量机分类器的替代方案?