Source code for shuup.utils.money

# This file is part of Shuup.
#
# Copyright (c) 2012-2021, Shuup Commerce Inc. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.

import decimal

from . import babel_precision_provider, numbers

DEFAULT_PRECISION = decimal.Decimal("0.01")


[docs]class Money(numbers.UnittedDecimal): """ Money value with currency. The pure decimal value is available from the base classes `~shuup.utils.numbers.UnittedDecimal.value` property (preferred way) or by casting to `Decimal`. Money objects with different currencies cannot be compared or calculated with and will raise `~shuup.utils.numbers.UnitMixupError`. See `__new__`. """ def __new__(cls, value="0", currency=None, *args, **kwargs): """ Create new Money instance with given value and currency. If no currency is given explicitly and `value` has a property named `currency`, then that will be used. Otherwise currency is a required argument and not passing one will raise a TypeError. :param str|numbers.Number value: Value as string or number. :param str|None currency: Currency as ISO-4217 code (3-letter string) or None. """ if currency is None and hasattr(value, "currency"): currency = value.currency if not currency: raise TypeError("Error! %s: currency must be given." % cls.__name__) instance = super(Money, cls).__new__(cls, value, *args, **kwargs) instance.currency = currency return instance def __repr__(self): cls_name = type(self).__name__ return "%s('%s', %r)" % (cls_name, self.value, self.currency) def __reduce_ex__(self, protocol): return (type(self), (self.value, self.currency)) def __str__(self): return "%s %s" % (self.value, self.currency) @classmethod
[docs] def from_data(cls, value, currency): return cls(value, currency)
[docs] def unit_matches_with(self, other): return self.currency == getattr(other, "currency", None)
[docs] def new(self, value): return type(self)(value, currency=self.currency)
[docs] def as_rounded(self, digits=None, rounding=decimal.ROUND_HALF_EVEN): """ Get rounded value of self. Return the value rounded to given `digits` if specified, otherwise to the precision of ``self.currency`` as returned by the precision provider, see `set_precision_provider`. :type digits: int|None :param digits: Number of digits to round to or None. :type rounding: str :param rounding: Rounding mode to use. :rtype: Money :return: A new `Money` instance with the rounded value. """ precision = _get_precision(self.currency, digits) return self.new(self.value.quantize(precision, rounding=rounding))
[docs]def set_precision_provider(precision_provider): """ Set precision provider for Money instances. Default precision provider is `shuup.utils.babel_precision_provider.get_precision`. :type precision_provider: Callable[[str], decimal.Decimal|None] :param precision_provider: Function which will return precision for given currency code, or None for unhandled currency codes, e.g. ``func('USD')`` could return ``Decimal('0.01')``. """ assert callable(precision_provider) global _precision_provider _precision_provider = precision_provider
_precision_provider = babel_precision_provider.get_precision def _get_precision(currency, digits): if digits is None: return _precision_provider(currency) or DEFAULT_PRECISION precision = _digits_to_precision.get(digits) if precision is None: precision = decimal.Decimal("0.1") ** digits _digits_to_precision[digits] = precision return precision _digits_to_precision = {2: decimal.Decimal("0.01")}