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 对象Stock
、Portfolio
、Regression
等。
“持久层”(位于单独的包 database
中),其中定义了所有 SQLAlchemy 元数据以及与 core
包对象的映射
我想要这种分离有几个原因:首先,我希望能够独立启动 core
包(既没有安装任何数据库,也没有安装 SQLAlchemy),其次,我认为这是一个很好的做法,因为在我的 core
包,我真的可以只专注于分析,而不是使用 SQLA Column
、relationships
等“污染”我的 Python 对象。
到目前为止,我使用低级 SQLAlchemy mapper
和 Table
设法做到了。
但是,只有一点我无法弄清楚。目前我的 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
其中orm
是sqlalchemy.orm
,并且是我的core
pkg 中sqlalchemy
的唯一 导入。我想摆脱它!
有人有想法吗?例如,也许有一种方法可以通过mapper
从database
包内部实例化_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 重构器的替代方案的主要内容,如果未能解决你的问题,请参考以下文章
SQLAlchemy ORM 转换为 pandas DataFrame