【发布时间】:2021-11-22 14:02:56
【问题描述】:
我的模型有两个字段 amount 和 currency。 amount 字段目前是一种自定义类型,它处理 Decimal 值,但将它们作为整数保存。 Decimal 类型看起来像这样:
class Decimal(sa.types.TypeDecorator):
impl = sa.types.INTEGER
# This was computed by `math.log(2 ** 64, 10) - 1`. This is the maximum power of 10 we can
# store in a 64 bit integer.
MAX_POW = 18
def __init__(self, digits, decimal_places):
if digits < 0 or decimal_places < 0:
raise ValueError("Can't have negative amounts of digits or decimal places")
if (digits + decimal_places) > self.MAX_POW:
raise ValueError("We can't store such precision")
super().__init__()
self.digits = digits
self.decimal_places = decimal_places
def process_bind_param(self, value, dialect):
if value is None:
return value
if isinstance(value, int):
value = decimal.Decimal(value)
if not isinstance(value, decimal.Decimal):
raise TypeError("Values are expected to be python Decimals")
if count_integer_part(value) > self.digits:
raise ValueError("Can't store that many digits before the decimal point")
if count_fractional_part(value) > self.decimal_places:
raise ValueError("Can't store that many digits after the decimal point")
integer = value.scaleb(self.decimal_places)
assert integer % 1 == 0, "Integer should not have digits after the decimal point"
return int(integer)
def process_result_value(self, value, dialect):
if value is None:
return value
return decimal.Decimal(value).scaleb(-self.decimal_places)
def __repr__(self):
return f"Decimal({self.digits}, {self.decimal_places})"
目前所有持有货币值的字段都使用参数化的MoneyAmount
MoneyAmount = functools.partial(Decimal, digits=16, decimal_places=2)
我现在想支持具有两位以上小数位的货币。实际的小数位数应使用适当的查找表从currency 字段计算。我一直在尝试使用hybrid_property,但在定义hybrid_property.expression(或hybrid_property.comparator)时遇到了一些麻烦,我必须在SQL 表达式中定义相同的查找。
decimal_places: dict[str, int] = {
"BHD": 3,
}
class Table(declarative_base()):
__tablename__ = "test"
def __init__(self, **kwargs):
amount = kwargs.pop("amount", None)
super().__init__(**kwargs)
if amount is not None:
self.amount = amount
id = sa.Column(sa.Integer, primary_key=True)
_amount = sa.Column(sa.Integer, nullable=False, default=0)
currency = sa.Column(sa.String(3), nullable=False, default="")
@hybrid_property
def amount(self):
places = decimal_places.get(self.currency, 2)
return Decimal(self._amount).scaleb(-places)
@amount.setter
def amount(self, value):
if not isinstance(value, Decimal):
value = Decimal(value)
places = decimal_places.get(self.currency, 2)
integer = value.scaleb(places)
self._amount = int(integer)
@amount.expression
def amount(cls):
# TODO: What to do here?
我发现 this 旧博客帖子以某种方式处理相同的问题域,但使用数字/浮点类型来存储数据。 你们对如何解决这个问题有什么建议吗?
【问题讨论】:
-
在 GitHub 上回答 here.
标签: python sqlite sqlalchemy