Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
numbers.py1574 linesDownload Raw Back to babel
1"""2    babel.numbers3    ~~~~~~~~~~~~~4 5    Locale dependent formatting and parsing of numeric data.6 7    The default locale for the functions in this module is determined by the8    following environment variables, in that order:9 10     * ``LC_NUMERIC``,11     * ``LC_ALL``, and12     * ``LANG``13 14    :copyright: (c) 2013-2024 by the Babel Team.15    :license: BSD, see LICENSE for more details.16"""17# TODO:18#  Padding and rounding increments in pattern:19#  - https://www.unicode.org/reports/tr35/ (Appendix G.6)20from __future__ import annotations21 22import datetime23import decimal24import re25import warnings26from typing import TYPE_CHECKING, Any, cast, overload27 28from babel.core import Locale, default_locale, get_global29from babel.localedata import LocaleDataDict30 31if TYPE_CHECKING:32    from typing_extensions import Literal33 34LC_NUMERIC = default_locale('LC_NUMERIC')35 36 37class UnknownCurrencyError(Exception):38    """Exception thrown when a currency is requested for which no data is available.39    """40 41    def __init__(self, identifier: str) -> None:42        """Create the exception.43        :param identifier: the identifier string of the unsupported currency44        """45        Exception.__init__(self, f"Unknown currency {identifier!r}.")46 47        #: The identifier of the locale that could not be found.48        self.identifier = identifier49 50 51def list_currencies(locale: Locale | str | None = None) -> set[str]:52    """ Return a `set` of normalized currency codes.53 54    .. versionadded:: 2.5.055 56    :param locale: filters returned currency codes by the provided locale.57                   Expected to be a locale instance or code. If no locale is58                   provided, returns the list of all currencies from all59                   locales.60    """61    # Get locale-scoped currencies.62    if locale:63        return set(Locale.parse(locale).currencies)64    return set(get_global('all_currencies'))65 66 67def validate_currency(currency: str, locale: Locale | str | None = None) -> None:68    """ Check the currency code is recognized by Babel.69 70    Accepts a ``locale`` parameter for fined-grained validation, working as71    the one defined above in ``list_currencies()`` method.72 73    Raises a `UnknownCurrencyError` exception if the currency is unknown to Babel.74    """75    if currency not in list_currencies(locale):76        raise UnknownCurrencyError(currency)77 78 79def is_currency(currency: str, locale: Locale | str | None = None) -> bool:80    """ Returns `True` only if a currency is recognized by Babel.81 82    This method always return a Boolean and never raise.83    """84    if not currency or not isinstance(currency, str):85        return False86    try:87        validate_currency(currency, locale)88    except UnknownCurrencyError:89        return False90    return True91 92 93def normalize_currency(currency: str, locale: Locale | str | None = None) -> str | None:94    """Returns the normalized identifier of any currency code.95 96    Accepts a ``locale`` parameter for fined-grained validation, working as97    the one defined above in ``list_currencies()`` method.98 99    Returns None if the currency is unknown to Babel.100    """101    if isinstance(currency, str):102        currency = currency.upper()103    if not is_currency(currency, locale):104        return None105    return currency106 107 108def get_currency_name(109    currency: str,110    count: float | decimal.Decimal | None = None,111    locale: Locale | str | None = LC_NUMERIC,112) -> str:113    """Return the name used by the locale for the specified currency.114 115    >>> get_currency_name('USD', locale='en_US')116    u'US Dollar'117 118    .. versionadded:: 0.9.4119 120    :param currency: the currency code.121    :param count: the optional count.  If provided the currency name122                  will be pluralized to that number if possible.123    :param locale: the `Locale` object or locale identifier.124    """125    loc = Locale.parse(locale)126    if count is not None:127        try:128            plural_form = loc.plural_form(count)129        except (OverflowError, ValueError):130            plural_form = 'other'131        plural_names = loc._data['currency_names_plural']132        if currency in plural_names:133            currency_plural_names = plural_names[currency]134            if plural_form in currency_plural_names:135                return currency_plural_names[plural_form]136            if 'other' in currency_plural_names:137                return currency_plural_names['other']138    return loc.currencies.get(currency, currency)139 140 141def get_currency_symbol(currency: str, locale: Locale | str | None = LC_NUMERIC) -> str:142    """Return the symbol used by the locale for the specified currency.143 144    >>> get_currency_symbol('USD', locale='en_US')145    u'$'146 147    :param currency: the currency code.148    :param locale: the `Locale` object or locale identifier.149    """150    return Locale.parse(locale).currency_symbols.get(currency, currency)151 152 153def get_currency_precision(currency: str) -> int:154    """Return currency's precision.155 156    Precision is the number of decimals found after the decimal point in the157    currency's format pattern.158 159    .. versionadded:: 2.5.0160 161    :param currency: the currency code.162    """163    precisions = get_global('currency_fractions')164    return precisions.get(currency, precisions['DEFAULT'])[0]165 166 167def get_currency_unit_pattern(168    currency: str,169    count: float | decimal.Decimal | None = None,170    locale: Locale | str | None = LC_NUMERIC,171) -> str:172    """173    Return the unit pattern used for long display of a currency value174    for a given locale.175    This is a string containing ``{0}`` where the numeric part176    should be substituted and ``{1}`` where the currency long display177    name should be substituted.178 179    >>> get_currency_unit_pattern('USD', locale='en_US', count=10)180    u'{0} {1}'181 182    .. versionadded:: 2.7.0183 184    :param currency: the currency code.185    :param count: the optional count.  If provided the unit186                  pattern for that number will be returned.187    :param locale: the `Locale` object or locale identifier.188    """189    loc = Locale.parse(locale)190    if count is not None:191        plural_form = loc.plural_form(count)192        try:193            return loc._data['currency_unit_patterns'][plural_form]194        except LookupError:195            # Fall back to 'other'196            pass197 198    return loc._data['currency_unit_patterns']['other']199 200 201@overload202def get_territory_currencies(203    territory: str,204    start_date: datetime.date | None = ...,205    end_date: datetime.date | None = ...,206    tender: bool = ...,207    non_tender: bool = ...,208    include_details: Literal[False] = ...,209) -> list[str]:210    ...  # pragma: no cover211 212 213@overload214def get_territory_currencies(215    territory: str,216    start_date: datetime.date | None = ...,217    end_date: datetime.date | None = ...,218    tender: bool = ...,219    non_tender: bool = ...,220    include_details: Literal[True] = ...,221) -> list[dict[str, Any]]:222    ...  # pragma: no cover223 224 225def get_territory_currencies(226    territory: str,227    start_date: datetime.date | None = None,228    end_date: datetime.date | None = None,229    tender: bool = True,230    non_tender: bool = False,231    include_details: bool = False,232) -> list[str] | list[dict[str, Any]]:233    """Returns the list of currencies for the given territory that are valid for234    the given date range.  In addition to that the currency database235    distinguishes between tender and non-tender currencies.  By default only236    tender currencies are returned.237 238    The return value is a list of all currencies roughly ordered by the time239    of when the currency became active.  The longer the currency is being in240    use the more to the left of the list it will be.241 242    The start date defaults to today.  If no end date is given it will be the243    same as the start date.  Otherwise a range can be defined.  For instance244    this can be used to find the currencies in use in Austria between 1995 and245    2011:246 247    >>> from datetime import date248    >>> get_territory_currencies('AT', date(1995, 1, 1), date(2011, 1, 1))249    ['ATS', 'EUR']250 251    Likewise it's also possible to find all the currencies in use on a252    single date:253 254    >>> get_territory_currencies('AT', date(1995, 1, 1))255    ['ATS']256    >>> get_territory_currencies('AT', date(2011, 1, 1))257    ['EUR']258 259    By default the return value only includes tender currencies.  This260    however can be changed:261 262    >>> get_territory_currencies('US')263    ['USD']264    >>> get_territory_currencies('US', tender=False, non_tender=True,265    ...                          start_date=date(2014, 1, 1))266    ['USN', 'USS']267 268    .. versionadded:: 2.0269 270    :param territory: the name of the territory to find the currency for.271    :param start_date: the start date.  If not given today is assumed.272    :param end_date: the end date.  If not given the start date is assumed.273    :param tender: controls whether tender currencies should be included.274    :param non_tender: controls whether non-tender currencies should be275                       included.276    :param include_details: if set to `True`, instead of returning currency277                            codes the return value will be dictionaries278                            with detail information.  In that case each279                            dictionary will have the keys ``'currency'``,280                            ``'from'``, ``'to'``, and ``'tender'``.281    """282    currencies = get_global('territory_currencies')283    if start_date is None:284        start_date = datetime.date.today()285    elif isinstance(start_date, datetime.datetime):286        start_date = start_date.date()287    if end_date is None:288        end_date = start_date289    elif isinstance(end_date, datetime.datetime):290        end_date = end_date.date()291 292    curs = currencies.get(territory.upper(), ())293    # TODO: validate that the territory exists294 295    def _is_active(start, end):296        return (start is None or start <= end_date) and \297               (end is None or end >= start_date)298 299    result = []300    for currency_code, start, end, is_tender in curs:301        if start:302            start = datetime.date(*start)303        if end:304            end = datetime.date(*end)305        if ((is_tender and tender) or306                (not is_tender and non_tender)) and _is_active(start, end):307            if include_details:308                result.append({309                    'currency': currency_code,310                    'from': start,311                    'to': end,312                    'tender': is_tender,313                })314            else:315                result.append(currency_code)316 317    return result318 319 320def _get_numbering_system(locale: Locale, numbering_system: Literal["default"] | str = "latn") -> str:321    if numbering_system == "default":322        return locale.default_numbering_system323    else:324        return numbering_system325 326 327def _get_number_symbols(328    locale: Locale | str | None,329    *,330    numbering_system: Literal["default"] | str = "latn",331) -> LocaleDataDict:332    parsed_locale = Locale.parse(locale)333    numbering_system = _get_numbering_system(parsed_locale, numbering_system)334    try:335        return parsed_locale.number_symbols[numbering_system]336    except KeyError as error:337        raise UnsupportedNumberingSystemError(f"Unknown numbering system {numbering_system} for Locale {parsed_locale}.") from error338 339 340class UnsupportedNumberingSystemError(Exception):341    """Exception thrown when an unsupported numbering system is requested for the given Locale."""342    pass343 344 345def get_decimal_symbol(346    locale: Locale | str | None = LC_NUMERIC,347    *,348    numbering_system: Literal["default"] | str = "latn",349) -> str:350    """Return the symbol used by the locale to separate decimal fractions.351 352    >>> get_decimal_symbol('en_US')353    u'.'354    >>> get_decimal_symbol('ar_EG', numbering_system='default')355    u'٫'356    >>> get_decimal_symbol('ar_EG', numbering_system='latn')357    u'.'358 359    :param locale: the `Locale` object or locale identifier360    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".361                             The special value "default" will use the default numbering system of the locale.362    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.363    """364    return _get_number_symbols(locale, numbering_system=numbering_system).get('decimal', '.')365 366 367def get_plus_sign_symbol(368    locale: Locale | str | None = LC_NUMERIC,369    *,370    numbering_system: Literal["default"] | str = "latn",371) -> str:372    """Return the plus sign symbol used by the current locale.373 374    >>> get_plus_sign_symbol('en_US')375    u'+'376    >>> get_plus_sign_symbol('ar_EG', numbering_system='default')377    u'\u061c+'378    >>> get_plus_sign_symbol('ar_EG', numbering_system='latn')379    u'\u200e+'380 381    :param locale: the `Locale` object or locale identifier382    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".383                             The special value "default" will use the default numbering system of the locale.384    :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.385    """386    return _get_number_symbols(locale, numbering_system=numbering_system).get('plusSign', '+')387 388 389def get_minus_sign_symbol(390    locale: Locale | str | None = LC_NUMERIC,391    *,392    numbering_system: Literal["default"] | str = "latn",393) -> str:394    """Return the plus sign symbol used by the current locale.395 396    >>> get_minus_sign_symbol('en_US')397    u'-'398    >>> get_minus_sign_symbol('ar_EG', numbering_system='default')399    u'\u061c-'400    >>> get_minus_sign_symbol('ar_EG', numbering_system='latn')401    u'\u200e-'402 403    :param locale: the `Locale` object or locale identifier404    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".405                             The special value "default" will use the default numbering system of the locale.406    :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.407    """408    return _get_number_symbols(locale, numbering_system=numbering_system).get('minusSign', '-')409 410 411def get_exponential_symbol(412    locale: Locale | str | None = LC_NUMERIC,413    *,414    numbering_system: Literal["default"] | str = "latn",415) -> str:416    """Return the symbol used by the locale to separate mantissa and exponent.417 418    >>> get_exponential_symbol('en_US')419    u'E'420    >>> get_exponential_symbol('ar_EG', numbering_system='default')421    u'أس'422    >>> get_exponential_symbol('ar_EG', numbering_system='latn')423    u'E'424 425    :param locale: the `Locale` object or locale identifier426    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".427                             The special value "default" will use the default numbering system of the locale.428    :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.429    """430    return _get_number_symbols(locale, numbering_system=numbering_system).get('exponential', 'E')431 432 433def get_group_symbol(434    locale: Locale | str | None = LC_NUMERIC,435    *,436    numbering_system: Literal["default"] | str = "latn",437) -> str:438    """Return the symbol used by the locale to separate groups of thousands.439 440    >>> get_group_symbol('en_US')441    u','442    >>> get_group_symbol('ar_EG', numbering_system='default')443    u'٬'444    >>> get_group_symbol('ar_EG', numbering_system='latn')445    u','446 447    :param locale: the `Locale` object or locale identifier448    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".449                             The special value "default" will use the default numbering system of the locale.450    :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.451    """452    return _get_number_symbols(locale, numbering_system=numbering_system).get('group', ',')453 454 455def get_infinity_symbol(456    locale: Locale | str | None = LC_NUMERIC,457    *,458    numbering_system: Literal["default"] | str = "latn",459) -> str:460    """Return the symbol used by the locale to represent infinity.461 462    >>> get_infinity_symbol('en_US')463    u'∞'464    >>> get_infinity_symbol('ar_EG', numbering_system='default')465    u'∞'466    >>> get_infinity_symbol('ar_EG', numbering_system='latn')467    u'∞'468 469    :param locale: the `Locale` object or locale identifier470    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".471                             The special value "default" will use the default numbering system of the locale.472    :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.473    """474    return _get_number_symbols(locale, numbering_system=numbering_system).get('infinity', '∞')475 476 477def format_number(number: float | decimal.Decimal | str, locale: Locale | str | None = LC_NUMERIC) -> str:478    """Return the given number formatted for a specific locale.479 480    >>> format_number(1099, locale='en_US')  # doctest: +SKIP481    u'1,099'482    >>> format_number(1099, locale='de_DE')  # doctest: +SKIP483    u'1.099'484 485    .. deprecated:: 2.6.0486 487       Use babel.numbers.format_decimal() instead.488 489    :param number: the number to format490    :param locale: the `Locale` object or locale identifier491 492 493    """494    warnings.warn('Use babel.numbers.format_decimal() instead.', DeprecationWarning, stacklevel=2)495    return format_decimal(number, locale=locale)496 497 498def get_decimal_precision(number: decimal.Decimal) -> int:499    """Return maximum precision of a decimal instance's fractional part.500 501    Precision is extracted from the fractional part only.502    """503    # Copied from: https://github.com/mahmoud/boltons/pull/59504    assert isinstance(number, decimal.Decimal)505    decimal_tuple = number.normalize().as_tuple()506    # Note: DecimalTuple.exponent can be 'n' (qNaN), 'N' (sNaN), or 'F' (Infinity)507    if not isinstance(decimal_tuple.exponent, int) or decimal_tuple.exponent >= 0:508        return 0509    return abs(decimal_tuple.exponent)510 511 512def get_decimal_quantum(precision: int | decimal.Decimal) -> decimal.Decimal:513    """Return minimal quantum of a number, as defined by precision."""514    assert isinstance(precision, (int, decimal.Decimal))515    return decimal.Decimal(10) ** (-precision)516 517 518def format_decimal(519    number: float | decimal.Decimal | str,520    format: str | NumberPattern | None = None,521    locale: Locale | str | None = LC_NUMERIC,522    decimal_quantization: bool = True,523    group_separator: bool = True,524    *,525    numbering_system: Literal["default"] | str = "latn",526) -> str:527    """Return the given decimal number formatted for a specific locale.528 529    >>> format_decimal(1.2345, locale='en_US')530    u'1.234'531    >>> format_decimal(1.2346, locale='en_US')532    u'1.235'533    >>> format_decimal(-1.2346, locale='en_US')534    u'-1.235'535    >>> format_decimal(1.2345, locale='sv_SE')536    u'1,234'537    >>> format_decimal(1.2345, locale='de')538    u'1,234'539    >>> format_decimal(1.2345, locale='ar_EG', numbering_system='default')540    u'1٫234'541    >>> format_decimal(1.2345, locale='ar_EG', numbering_system='latn')542    u'1.234'543 544    The appropriate thousands grouping and the decimal separator are used for545    each locale:546 547    >>> format_decimal(12345.5, locale='en_US')548    u'12,345.5'549 550    By default the locale is allowed to truncate and round a high-precision551    number by forcing its format pattern onto the decimal part. You can bypass552    this behavior with the `decimal_quantization` parameter:553 554    >>> format_decimal(1.2346, locale='en_US')555    u'1.235'556    >>> format_decimal(1.2346, locale='en_US', decimal_quantization=False)557    u'1.2346'558    >>> format_decimal(12345.67, locale='fr_CA', group_separator=False)559    u'12345,67'560    >>> format_decimal(12345.67, locale='en_US', group_separator=True)561    u'12,345.67'562 563    :param number: the number to format564    :param format:565    :param locale: the `Locale` object or locale identifier566    :param decimal_quantization: Truncate and round high-precision numbers to567                                 the format pattern. Defaults to `True`.568    :param group_separator: Boolean to switch group separator on/off in a locale's569                            number format.570    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".571                             The special value "default" will use the default numbering system of the locale.572    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.573    """574    locale = Locale.parse(locale)575    if format is None:576        format = locale.decimal_formats[format]577    pattern = parse_pattern(format)578    return pattern.apply(579        number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator, numbering_system=numbering_system)580 581 582def format_compact_decimal(583    number: float | decimal.Decimal | str,584    *,585    format_type: Literal["short", "long"] = "short",586    locale: Locale | str | None = LC_NUMERIC,587    fraction_digits: int = 0,588    numbering_system: Literal["default"] | str = "latn",589) -> str:590    """Return the given decimal number formatted for a specific locale in compact form.591 592    >>> format_compact_decimal(12345, format_type="short", locale='en_US')593    u'12K'594    >>> format_compact_decimal(12345, format_type="long", locale='en_US')595    u'12 thousand'596    >>> format_compact_decimal(12345, format_type="short", locale='en_US', fraction_digits=2)597    u'12.34K'598    >>> format_compact_decimal(1234567, format_type="short", locale="ja_JP")599    u'123万'600    >>> format_compact_decimal(2345678, format_type="long", locale="mk")601    u'2 милиони'602    >>> format_compact_decimal(21000000, format_type="long", locale="mk")603    u'21 милион'604    >>> format_compact_decimal(12345, format_type="short", locale='ar_EG', fraction_digits=2, numbering_system='default')605    u'12٫34\xa0ألف'606 607    :param number: the number to format608    :param format_type: Compact format to use ("short" or "long")609    :param locale: the `Locale` object or locale identifier610    :param fraction_digits: Number of digits after the decimal point to use. Defaults to `0`.611    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".612                             The special value "default" will use the default numbering system of the locale.613    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.614    """615    locale = Locale.parse(locale)616    compact_format = locale.compact_decimal_formats[format_type]617    number, format = _get_compact_format(number, compact_format, locale, fraction_digits)618    # Did not find a format, fall back.619    if format is None:620        format = locale.decimal_formats[None]621    pattern = parse_pattern(format)622    return pattern.apply(number, locale, decimal_quantization=False, numbering_system=numbering_system)623 624 625def _get_compact_format(626    number: float | decimal.Decimal | str,627    compact_format: LocaleDataDict,628    locale: Locale,629    fraction_digits: int,630) -> tuple[decimal.Decimal, NumberPattern | None]:631    """Returns the number after dividing by the unit and the format pattern to use.632    The algorithm is described here:633    https://www.unicode.org/reports/tr35/tr35-45/tr35-numbers.html#Compact_Number_Formats.634    """635    if not isinstance(number, decimal.Decimal):636        number = decimal.Decimal(str(number))637    if number.is_nan() or number.is_infinite():638        return number, None639    format = None640    for magnitude in sorted([int(m) for m in compact_format["other"]], reverse=True):641        if abs(number) >= magnitude:642            # check the pattern using "other" as the amount643            format = compact_format["other"][str(magnitude)]644            pattern = parse_pattern(format).pattern645            # if the pattern is "0", we do not divide the number646            if pattern == "0":647                break648            # otherwise, we need to divide the number by the magnitude but remove zeros649            # equal to the number of 0's in the pattern minus 1650            number = cast(decimal.Decimal, number / (magnitude // (10 ** (pattern.count("0") - 1))))651            # round to the number of fraction digits requested652            rounded = round(number, fraction_digits)653            # if the remaining number is singular, use the singular format654            plural_form = locale.plural_form(abs(number))655            if plural_form not in compact_format:656                plural_form = "other"657            if number == 1 and "1" in compact_format:658                plural_form = "1"659            format = compact_format[plural_form][str(magnitude)]660            number = rounded661            break662    return number, format663 664 665class UnknownCurrencyFormatError(KeyError):666    """Exception raised when an unknown currency format is requested."""667 668 669def format_currency(670    number: float | decimal.Decimal | str,671    currency: str,672    format: str | NumberPattern | None = None,673    locale: Locale | str | None = LC_NUMERIC,674    currency_digits: bool = True,675    format_type: Literal["name", "standard", "accounting"] = "standard",676    decimal_quantization: bool = True,677    group_separator: bool = True,678    *,679    numbering_system: Literal["default"] | str = "latn",680) -> str:681    """Return formatted currency value.682 683    >>> format_currency(1099.98, 'USD', locale='en_US')684    '$1,099.98'685    >>> format_currency(1099.98, 'USD', locale='es_CO')686    u'US$1.099,98'687    >>> format_currency(1099.98, 'EUR', locale='de_DE')688    u'1.099,98\\xa0\\u20ac'689    >>> format_currency(1099.98, 'EGP', locale='ar_EG', numbering_system='default')690    u'\u200f1٬099٫98\xa0ج.م.\u200f'691 692    The format can also be specified explicitly.  The currency is693    placed with the '¤' sign.  As the sign gets repeated the format694    expands (¤ being the symbol, ¤¤ is the currency abbreviation and695    ¤¤¤ is the full name of the currency):696 697    >>> format_currency(1099.98, 'EUR', u'\xa4\xa4 #,##0.00', locale='en_US')698    u'EUR 1,099.98'699    >>> format_currency(1099.98, 'EUR', u'#,##0.00 \xa4\xa4\xa4', locale='en_US')700    u'1,099.98 euros'701 702    Currencies usually have a specific number of decimal digits. This function703    favours that information over the given format:704 705    >>> format_currency(1099.98, 'JPY', locale='en_US')706    u'\\xa51,100'707    >>> format_currency(1099.98, 'COP', u'#,##0.00', locale='es_ES')708    u'1.099,98'709 710    However, the number of decimal digits can be overridden from the currency711    information, by setting the last parameter to ``False``:712 713    >>> format_currency(1099.98, 'JPY', locale='en_US', currency_digits=False)714    u'\\xa51,099.98'715    >>> format_currency(1099.98, 'COP', u'#,##0.00', locale='es_ES', currency_digits=False)716    u'1.099,98'717 718    If a format is not specified the type of currency format to use719    from the locale can be specified:720 721    >>> format_currency(1099.98, 'EUR', locale='en_US', format_type='standard')722    u'\\u20ac1,099.98'723 724    When the given currency format type is not available, an exception is725    raised:726 727    >>> format_currency('1099.98', 'EUR', locale='root', format_type='unknown')728    Traceback (most recent call last):729        ...730    UnknownCurrencyFormatError: "'unknown' is not a known currency format type"731 732    >>> format_currency(101299.98, 'USD', locale='en_US', group_separator=False)733    u'$101299.98'734 735    >>> format_currency(101299.98, 'USD', locale='en_US', group_separator=True)736    u'$101,299.98'737 738    You can also pass format_type='name' to use long display names. The order of739    the number and currency name, along with the correct localized plural form740    of the currency name, is chosen according to locale:741 742    >>> format_currency(1, 'USD', locale='en_US', format_type='name')743    u'1.00 US dollar'744    >>> format_currency(1099.98, 'USD', locale='en_US', format_type='name')745    u'1,099.98 US dollars'746    >>> format_currency(1099.98, 'USD', locale='ee', format_type='name')747    u'us ga dollar 1,099.98'748 749    By default the locale is allowed to truncate and round a high-precision750    number by forcing its format pattern onto the decimal part. You can bypass751    this behavior with the `decimal_quantization` parameter:752 753    >>> format_currency(1099.9876, 'USD', locale='en_US')754    u'$1,099.99'755    >>> format_currency(1099.9876, 'USD', locale='en_US', decimal_quantization=False)756    u'$1,099.9876'757 758    :param number: the number to format759    :param currency: the currency code760    :param format: the format string to use761    :param locale: the `Locale` object or locale identifier762    :param currency_digits: use the currency's natural number of decimal digits763    :param format_type: the currency format type to use764    :param decimal_quantization: Truncate and round high-precision numbers to765                                 the format pattern. Defaults to `True`.766    :param group_separator: Boolean to switch group separator on/off in a locale's767                            number format.768    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".769                             The special value "default" will use the default numbering system of the locale.770    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.771    """772    if format_type == 'name':773        return _format_currency_long_name(number, currency, format=format,774                                          locale=locale, currency_digits=currency_digits,775                                          decimal_quantization=decimal_quantization, group_separator=group_separator,776                                          numbering_system=numbering_system)777    locale = Locale.parse(locale)778    if format:779        pattern = parse_pattern(format)780    else:781        try:782            pattern = locale.currency_formats[format_type]783        except KeyError:784            raise UnknownCurrencyFormatError(f"{format_type!r} is not a known currency format type") from None785 786    return pattern.apply(787        number, locale, currency=currency, currency_digits=currency_digits,788        decimal_quantization=decimal_quantization, group_separator=group_separator, numbering_system=numbering_system)789 790 791def _format_currency_long_name(792    number: float | decimal.Decimal | str,793    currency: str,794    format: str | NumberPattern | None = None,795    locale: Locale | str | None = LC_NUMERIC,796    currency_digits: bool = True,797    format_type: Literal["name", "standard", "accounting"] = "standard",798    decimal_quantization: bool = True,799    group_separator: bool = True,800    *,801    numbering_system: Literal["default"] | str = "latn",802) -> str:803    # Algorithm described here:804    # https://www.unicode.org/reports/tr35/tr35-numbers.html#Currencies805    locale = Locale.parse(locale)806    # Step 1.807    # There are no examples of items with explicit count (0 or 1) in current808    # locale data. So there is no point implementing that.809    # Step 2.810 811    # Correct number to numeric type, important for looking up plural rules:812    number_n = float(number) if isinstance(number, str) else number813 814    # Step 3.815    unit_pattern = get_currency_unit_pattern(currency, count=number_n, locale=locale)816 817    # Step 4.818    display_name = get_currency_name(currency, count=number_n, locale=locale)819 820    # Step 5.821    if not format:822        format = locale.decimal_formats[None]823 824    pattern = parse_pattern(format)825 826    number_part = pattern.apply(827        number, locale, currency=currency, currency_digits=currency_digits,828        decimal_quantization=decimal_quantization, group_separator=group_separator, numbering_system=numbering_system)829 830    return unit_pattern.format(number_part, display_name)831 832 833def format_compact_currency(834    number: float | decimal.Decimal | str,835    currency: str,836    *,837    format_type: Literal["short"] = "short",838    locale: Locale | str | None = LC_NUMERIC,839    fraction_digits: int = 0,840    numbering_system: Literal["default"] | str = "latn",841) -> str:842    """Format a number as a currency value in compact form.843 844    >>> format_compact_currency(12345, 'USD', locale='en_US')845    u'$12K'846    >>> format_compact_currency(123456789, 'USD', locale='en_US', fraction_digits=2)847    u'$123.46M'848    >>> format_compact_currency(123456789, 'EUR', locale='de_DE', fraction_digits=1)849    '123,5\xa0Mio.\xa0€'850 851    :param number: the number to format852    :param currency: the currency code853    :param format_type: the compact format type to use. Defaults to "short".854    :param locale: the `Locale` object or locale identifier855    :param fraction_digits: Number of digits after the decimal point to use. Defaults to `0`.856    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".857                             The special value "default" will use the default numbering system of the locale.858    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.859    """860    locale = Locale.parse(locale)861    try:862        compact_format = locale.compact_currency_formats[format_type]863    except KeyError as error:864        raise UnknownCurrencyFormatError(f"{format_type!r} is not a known compact currency format type") from error865    number, format = _get_compact_format(number, compact_format, locale, fraction_digits)866    # Did not find a format, fall back.867    if format is None or "¤" not in str(format):868        # find first format that has a currency symbol869        for magnitude in compact_format['other']:870            format = compact_format['other'][magnitude].pattern871            if '¤' not in format:872                continue873            # remove characters that are not the currency symbol, 0's or spaces874            format = re.sub(r'[^0\s\¤]', '', format)875            # compress adjacent spaces into one876            format = re.sub(r'(\s)\s+', r'\1', format).strip()877            break878    if format is None:879        raise ValueError('No compact currency format found for the given number and locale.')880    pattern = parse_pattern(format)881    return pattern.apply(number, locale, currency=currency, currency_digits=False, decimal_quantization=False,882                         numbering_system=numbering_system)883 884 885def format_percent(886    number: float | decimal.Decimal | str,887    format: str | NumberPattern | None = None,888    locale: Locale | str | None = LC_NUMERIC,889    decimal_quantization: bool = True,890    group_separator: bool = True,891    *,892    numbering_system: Literal["default"] | str = "latn",893) -> str:894    """Return formatted percent value for a specific locale.895 896    >>> format_percent(0.34, locale='en_US')897    u'34%'898    >>> format_percent(25.1234, locale='en_US')899    u'2,512%'900    >>> format_percent(25.1234, locale='sv_SE')901    u'2\\xa0512\\xa0%'902    >>> format_percent(25.1234, locale='ar_EG', numbering_system='default')903    u'2٬512%'904 905    The format pattern can also be specified explicitly:906 907    >>> format_percent(25.1234, u'#,##0\u2030', locale='en_US')908    u'25,123\u2030'909 910    By default the locale is allowed to truncate and round a high-precision911    number by forcing its format pattern onto the decimal part. You can bypass912    this behavior with the `decimal_quantization` parameter:913 914    >>> format_percent(23.9876, locale='en_US')915    u'2,399%'916    >>> format_percent(23.9876, locale='en_US', decimal_quantization=False)917    u'2,398.76%'918 919    >>> format_percent(229291.1234, locale='pt_BR', group_separator=False)920    u'22929112%'921 922    >>> format_percent(229291.1234, locale='pt_BR', group_separator=True)923    u'22.929.112%'924 925    :param number: the percent number to format926    :param format:927    :param locale: the `Locale` object or locale identifier928    :param decimal_quantization: Truncate and round high-precision numbers to929                                 the format pattern. Defaults to `True`.930    :param group_separator: Boolean to switch group separator on/off in a locale's931                            number format.932    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".933                             The special value "default" will use the default numbering system of the locale.934    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.935    """936    locale = Locale.parse(locale)937    if not format:938        format = locale.percent_formats[None]939    pattern = parse_pattern(format)940    return pattern.apply(941        number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator,942        numbering_system=numbering_system,943    )944 945 946def format_scientific(947        number: float | decimal.Decimal | str,948        format: str | NumberPattern | None = None,949        locale: Locale | str | None = LC_NUMERIC,950        decimal_quantization: bool = True,951        *,952        numbering_system: Literal["default"] | str = "latn",953) -> str:954    """Return value formatted in scientific notation for a specific locale.955 956    >>> format_scientific(10000, locale='en_US')957    u'1E4'958    >>> format_scientific(10000, locale='ar_EG', numbering_system='default')959    u'1أس4'960 961    The format pattern can also be specified explicitly:962 963    >>> format_scientific(1234567, u'##0.##E00', locale='en_US')964    u'1.23E06'965 966    By default the locale is allowed to truncate and round a high-precision967    number by forcing its format pattern onto the decimal part. You can bypass968    this behavior with the `decimal_quantization` parameter:969 970    >>> format_scientific(1234.9876, u'#.##E0', locale='en_US')971    u'1.23E3'972    >>> format_scientific(1234.9876, u'#.##E0', locale='en_US', decimal_quantization=False)973    u'1.2349876E3'974 975    :param number: the number to format976    :param format:977    :param locale: the `Locale` object or locale identifier978    :param decimal_quantization: Truncate and round high-precision numbers to979                                 the format pattern. Defaults to `True`.980    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".981                             The special value "default" will use the default numbering system of the locale.982    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.983    """984    locale = Locale.parse(locale)985    if not format:986        format = locale.scientific_formats[None]987    pattern = parse_pattern(format)988    return pattern.apply(989        number, locale, decimal_quantization=decimal_quantization, numbering_system=numbering_system)990 991 992class NumberFormatError(ValueError):993    """Exception raised when a string cannot be parsed into a number."""994 995    def __init__(self, message: str, suggestions: list[str] | None = None) -> None:996        super().__init__(message)997        #: a list of properly formatted numbers derived from the invalid input998        self.suggestions = suggestions999 1000 1001SPACE_CHARS = {1002    ' ',  # space1003    '\xa0',  # no-break space1004    '\u202f',  # narrow no-break space1005}1006 1007SPACE_CHARS_RE = re.compile('|'.join(SPACE_CHARS))1008 1009 1010def parse_number(1011    string: str,1012    locale: Locale | str | None = LC_NUMERIC,1013    *,1014    numbering_system: Literal["default"] | str = "latn",1015) -> int:1016    """Parse localized number string into an integer.1017 1018    >>> parse_number('1,099', locale='en_US')1019    10991020    >>> parse_number('1.099', locale='de_DE')1021    10991022 1023    When the given string cannot be parsed, an exception is raised:1024 1025    >>> parse_number('1.099,98', locale='de')1026    Traceback (most recent call last):1027        ...1028    NumberFormatError: '1.099,98' is not a valid number1029 1030    :param string: the string to parse1031    :param locale: the `Locale` object or locale identifier1032    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".1033                             The special value "default" will use the default numbering system of the locale.1034    :return: the parsed number1035    :raise `NumberFormatError`: if the string can not be converted to a number1036    :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.1037    """1038    group_symbol = get_group_symbol(locale, numbering_system=numbering_system)1039 1040    if (1041        group_symbol in SPACE_CHARS and  # if the grouping symbol is a kind of space,1042        group_symbol not in string and  # and the string to be parsed does not contain it,1043        SPACE_CHARS_RE.search(string)  # but it does contain any other kind of space instead,1044    ):1045        # ... it's reasonable to assume it is taking the place of the grouping symbol.1046        string = SPACE_CHARS_RE.sub(group_symbol, string)1047 1048    try:1049        return int(string.replace(group_symbol, ''))1050    except ValueError as ve:1051        raise NumberFormatError(f"{string!r} is not a valid number") from ve1052 1053 1054def parse_decimal(1055    string: str,1056    locale: Locale | str | None = LC_NUMERIC,1057    strict: bool = False,1058    *,1059    numbering_system: Literal["default"] | str = "latn",1060) -> decimal.Decimal:1061    """Parse localized decimal string into a decimal.1062 1063    >>> parse_decimal('1,099.98', locale='en_US')1064    Decimal('1099.98')1065    >>> parse_decimal('1.099,98', locale='de')1066    Decimal('1099.98')1067    >>> parse_decimal('12 345,123', locale='ru')1068    Decimal('12345.123')1069    >>> parse_decimal('1٬099٫98', locale='ar_EG', numbering_system='default')1070    Decimal('1099.98')1071 1072    When the given string cannot be parsed, an exception is raised:1073 1074    >>> parse_decimal('2,109,998', locale='de')1075    Traceback (most recent call last):1076        ...1077    NumberFormatError: '2,109,998' is not a valid decimal number1078 1079    If `strict` is set to `True` and the given string contains a number1080    formatted in an irregular way, an exception is raised:1081 1082    >>> parse_decimal('30.00', locale='de', strict=True)1083    Traceback (most recent call last):1084        ...1085    NumberFormatError: '30.00' is not a properly formatted decimal number. Did you mean '3.000'? Or maybe '30,00'?1086 1087    >>> parse_decimal('0.00', locale='de', strict=True)1088    Traceback (most recent call last):1089        ...1090    NumberFormatError: '0.00' is not a properly formatted decimal number. Did you mean '0'?1091 1092    :param string: the string to parse1093    :param locale: the `Locale` object or locale identifier1094    :param strict: controls whether numbers formatted in a weird way are1095                   accepted or rejected1096    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".1097                             The special value "default" will use the default numbering system of the locale.1098    :raise NumberFormatError: if the string can not be converted to a1099                              decimal number1100    :raise UnsupportedNumberingSystemError: if the numbering system is not supported by the locale.1101    """1102    locale = Locale.parse(locale)1103    group_symbol = get_group_symbol(locale, numbering_system=numbering_system)1104    decimal_symbol = get_decimal_symbol(locale, numbering_system=numbering_system)1105 1106    if not strict and (1107        group_symbol in SPACE_CHARS and  # if the grouping symbol is a kind of space,1108        group_symbol not in string and  # and the string to be parsed does not contain it,1109        SPACE_CHARS_RE.search(string)  # but it does contain any other kind of space instead,1110    ):1111        # ... it's reasonable to assume it is taking the place of the grouping symbol.1112        string = SPACE_CHARS_RE.sub(group_symbol, string)1113 1114    try:1115        parsed = decimal.Decimal(string.replace(group_symbol, '')1116                                       .replace(decimal_symbol, '.'))1117    except decimal.InvalidOperation as exc:1118        raise NumberFormatError(f"{string!r} is not a valid decimal number") from exc1119    if strict and group_symbol in string:1120        proper = format_decimal(parsed, locale=locale, decimal_quantization=False, numbering_system=numbering_system)1121        if string != proper and proper != _remove_trailing_zeros_after_decimal(string, decimal_symbol):1122            try:1123                parsed_alt = decimal.Decimal(string.replace(decimal_symbol, '')1124                                                   .replace(group_symbol, '.'))1125            except decimal.InvalidOperation as exc:1126                raise NumberFormatError(1127                    f"{string!r} is not a properly formatted decimal number. "1128                    f"Did you mean {proper!r}?",1129                    suggestions=[proper],1130                ) from exc1131            else:1132                proper_alt = format_decimal(1133                    parsed_alt,1134                    locale=locale,1135                    decimal_quantization=False,1136                    numbering_system=numbering_system,1137                )1138                if proper_alt == proper:1139                    raise NumberFormatError(1140                        f"{string!r} is not a properly formatted decimal number. "1141                        f"Did you mean {proper!r}?",1142                        suggestions=[proper],1143                    )1144                else:1145                    raise NumberFormatError(1146                        f"{string!r} is not a properly formatted decimal number. "1147                        f"Did you mean {proper!r}? Or maybe {proper_alt!r}?",1148                        suggestions=[proper, proper_alt],1149                    )1150    return parsed1151 1152 1153def _remove_trailing_zeros_after_decimal(string: str, decimal_symbol: str) -> str:1154    """1155    Remove trailing zeros from the decimal part of a numeric string.1156 1157    This function takes a string representing a numeric value and a decimal symbol.1158    It removes any trailing zeros that appear after the decimal symbol in the number.1159    If the decimal part becomes empty after removing trailing zeros, the decimal symbol1160    is also removed. If the string does not contain the decimal symbol, it is returned unchanged.1161 1162    :param string: The numeric string from which to remove trailing zeros.1163    :type string: str1164    :param decimal_symbol: The symbol used to denote the decimal point.1165    :type decimal_symbol: str1166    :return: The numeric string with trailing zeros removed from its decimal part.1167    :rtype: str1168 1169    Example:1170    >>> _remove_trailing_zeros_after_decimal("123.4500", ".")1171    '123.45'1172    >>> _remove_trailing_zeros_after_decimal("100.000", ".")1173    '100'1174    >>> _remove_trailing_zeros_after_decimal("100", ".")1175    '100'1176    """1177    integer_part, _, decimal_part = string.partition(decimal_symbol)1178 1179    if decimal_part:1180        decimal_part = decimal_part.rstrip("0")1181        if decimal_part:1182            return integer_part + decimal_symbol + decimal_part1183        return integer_part1184 1185    return string1186 1187 1188PREFIX_END = r'[^0-9@#.,]'1189NUMBER_TOKEN = r'[0-9@#.,E+]'1190 1191PREFIX_PATTERN = r"(?P<prefix>(?:'[^']*'|%s)*)" % PREFIX_END1192NUMBER_PATTERN = r"(?P<number>%s*)" % NUMBER_TOKEN1193SUFFIX_PATTERN = r"(?P<suffix>.*)"1194 1195number_re = re.compile(f"{PREFIX_PATTERN}{NUMBER_PATTERN}{SUFFIX_PATTERN}")1196 1197 1198def parse_grouping(p: str) -> tuple[int, int]:1199    """Parse primary and secondary digit grouping1200 

Showing the first 1,200 of 1574 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai