Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
support.py725 linesDownload Raw Back to babel
1"""2    babel.support3    ~~~~~~~~~~~~~4 5    Several classes and functions that help with integrating and using Babel6    in applications.7 8    .. note: the code in this module is not used by Babel itself9 10    :copyright: (c) 2013-2024 by the Babel Team.11    :license: BSD, see LICENSE for more details.12"""13from __future__ import annotations14 15import decimal16import gettext17import locale18import os19from collections.abc import Iterator20from typing import TYPE_CHECKING, Any, Callable, Iterable21 22from babel.core import Locale23from babel.dates import format_date, format_datetime, format_time, format_timedelta24from babel.numbers import (25    format_compact_currency,26    format_compact_decimal,27    format_currency,28    format_decimal,29    format_percent,30    format_scientific,31)32 33if TYPE_CHECKING:34    from typing_extensions import Literal35 36    from babel.dates import _PredefinedTimeFormat37 38 39class Format:40    """Wrapper class providing the various date and number formatting functions41    bound to a specific locale and time-zone.42 43    >>> from babel.util import UTC44    >>> from datetime import date45    >>> fmt = Format('en_US', UTC)46    >>> fmt.date(date(2007, 4, 1))47    u'Apr 1, 2007'48    >>> fmt.decimal(1.2345)49    u'1.234'50    """51 52    def __init__(53        self,54        locale: Locale | str,55        tzinfo: datetime.tzinfo | None = None,56        *,57        numbering_system: Literal["default"] | str = "latn",58    ) -> None:59        """Initialize the formatter.60 61        :param locale: the locale identifier or `Locale` instance62        :param tzinfo: the time-zone info (a `tzinfo` instance or `None`)63        :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".64                                 The special value "default" will use the default numbering system of the locale.65        """66        self.locale = Locale.parse(locale)67        self.tzinfo = tzinfo68        self.numbering_system = numbering_system69 70    def date(71        self,72        date: datetime.date | None = None,73        format: _PredefinedTimeFormat | str = 'medium',74    ) -> str:75        """Return a date formatted according to the given pattern.76 77        >>> from datetime import date78        >>> fmt = Format('en_US')79        >>> fmt.date(date(2007, 4, 1))80        u'Apr 1, 2007'81        """82        return format_date(date, format, locale=self.locale)83 84    def datetime(85        self,86        datetime: datetime.date | None = None,87        format: _PredefinedTimeFormat | str = 'medium',88    ) -> str:89        """Return a date and time formatted according to the given pattern.90 91        >>> from datetime import datetime92        >>> from babel.dates import get_timezone93        >>> fmt = Format('en_US', tzinfo=get_timezone('US/Eastern'))94        >>> fmt.datetime(datetime(2007, 4, 1, 15, 30))95        u'Apr 1, 2007, 11:30:00\u202fAM'96        """97        return format_datetime(datetime, format, tzinfo=self.tzinfo, locale=self.locale)98 99    def time(100        self,101        time: datetime.time | datetime.datetime | None = None,102        format: _PredefinedTimeFormat | str = 'medium',103    ) -> str:104        """Return a time formatted according to the given pattern.105 106        >>> from datetime import datetime107        >>> from babel.dates import get_timezone108        >>> fmt = Format('en_US', tzinfo=get_timezone('US/Eastern'))109        >>> fmt.time(datetime(2007, 4, 1, 15, 30))110        u'11:30:00\u202fAM'111        """112        return format_time(time, format, tzinfo=self.tzinfo, locale=self.locale)113 114    def timedelta(115        self,116        delta: datetime.timedelta | int,117        granularity: Literal["year", "month", "week", "day", "hour", "minute", "second"] = "second",118        threshold: float = 0.85,119        format: Literal["narrow", "short", "medium", "long"] = "long",120        add_direction: bool = False,121    ) -> str:122        """Return a time delta according to the rules of the given locale.123 124        >>> from datetime import timedelta125        >>> fmt = Format('en_US')126        >>> fmt.timedelta(timedelta(weeks=11))127        u'3 months'128        """129        return format_timedelta(delta, granularity=granularity,130                                threshold=threshold,131                                format=format, add_direction=add_direction,132                                locale=self.locale)133 134    def number(self, number: float | decimal.Decimal | str) -> str:135        """Return an integer number formatted for the locale.136 137        >>> fmt = Format('en_US')138        >>> fmt.number(1099)139        u'1,099'140        """141        return format_decimal(number, locale=self.locale, numbering_system=self.numbering_system)142 143    def decimal(self, number: float | decimal.Decimal | str, format: str | None = None) -> str:144        """Return a decimal number formatted for the locale.145 146        >>> fmt = Format('en_US')147        >>> fmt.decimal(1.2345)148        u'1.234'149        """150        return format_decimal(number, format, locale=self.locale, numbering_system=self.numbering_system)151 152    def compact_decimal(153        self,154        number: float | decimal.Decimal | str,155        format_type: Literal['short', 'long'] = 'short',156        fraction_digits: int = 0,157    ) -> str:158        """Return a number formatted in compact form for the locale.159 160        >>> fmt = Format('en_US')161        >>> fmt.compact_decimal(123456789)162        u'123M'163        >>> fmt.compact_decimal(1234567, format_type='long', fraction_digits=2)164        '1.23 million'165        """166        return format_compact_decimal(167            number,168            format_type=format_type,169            fraction_digits=fraction_digits,170            locale=self.locale,171            numbering_system=self.numbering_system,172        )173 174    def currency(self, number: float | decimal.Decimal | str, currency: str) -> str:175        """Return a number in the given currency formatted for the locale.176        """177        return format_currency(number, currency, locale=self.locale, numbering_system=self.numbering_system)178 179    def compact_currency(180        self,181        number: float | decimal.Decimal | str,182        currency: str,183        format_type: Literal['short'] = 'short',184        fraction_digits: int = 0,185    ) -> str:186        """Return a number in the given currency formatted for the locale187        using the compact number format.188 189        >>> Format('en_US').compact_currency(1234567, "USD", format_type='short', fraction_digits=2)190        '$1.23M'191        """192        return format_compact_currency(number, currency, format_type=format_type, fraction_digits=fraction_digits,193                                       locale=self.locale, numbering_system=self.numbering_system)194 195    def percent(self, number: float | decimal.Decimal | str, format: str | None = None) -> str:196        """Return a number formatted as percentage for the locale.197 198        >>> fmt = Format('en_US')199        >>> fmt.percent(0.34)200        u'34%'201        """202        return format_percent(number, format, locale=self.locale, numbering_system=self.numbering_system)203 204    def scientific(self, number: float | decimal.Decimal | str) -> str:205        """Return a number formatted using scientific notation for the locale.206        """207        return format_scientific(number, locale=self.locale, numbering_system=self.numbering_system)208 209 210class LazyProxy:211    """Class for proxy objects that delegate to a specified function to evaluate212    the actual object.213 214    >>> def greeting(name='world'):215    ...     return 'Hello, %s!' % name216    >>> lazy_greeting = LazyProxy(greeting, name='Joe')217    >>> print(lazy_greeting)218    Hello, Joe!219    >>> u'  ' + lazy_greeting220    u'  Hello, Joe!'221    >>> u'(%s)' % lazy_greeting222    u'(Hello, Joe!)'223 224    This can be used, for example, to implement lazy translation functions that225    delay the actual translation until the string is actually used. The226    rationale for such behavior is that the locale of the user may not always227    be available. In web applications, you only know the locale when processing228    a request.229 230    The proxy implementation attempts to be as complete as possible, so that231    the lazy objects should mostly work as expected, for example for sorting:232 233    >>> greetings = [234    ...     LazyProxy(greeting, 'world'),235    ...     LazyProxy(greeting, 'Joe'),236    ...     LazyProxy(greeting, 'universe'),237    ... ]238    >>> greetings.sort()239    >>> for greeting in greetings:240    ...     print(greeting)241    Hello, Joe!242    Hello, universe!243    Hello, world!244    """245    __slots__ = ['_func', '_args', '_kwargs', '_value', '_is_cache_enabled', '_attribute_error']246 247    if TYPE_CHECKING:248        _func: Callable[..., Any]249        _args: tuple[Any, ...]250        _kwargs: dict[str, Any]251        _is_cache_enabled: bool252        _value: Any253        _attribute_error: AttributeError | None254 255    def __init__(self, func: Callable[..., Any], *args: Any, enable_cache: bool = True, **kwargs: Any) -> None:256        # Avoid triggering our own __setattr__ implementation257        object.__setattr__(self, '_func', func)258        object.__setattr__(self, '_args', args)259        object.__setattr__(self, '_kwargs', kwargs)260        object.__setattr__(self, '_is_cache_enabled', enable_cache)261        object.__setattr__(self, '_value', None)262        object.__setattr__(self, '_attribute_error', None)263 264    @property265    def value(self) -> Any:266        if self._value is None:267            try:268                value = self._func(*self._args, **self._kwargs)269            except AttributeError as error:270                object.__setattr__(self, '_attribute_error', error)271                raise272 273            if not self._is_cache_enabled:274                return value275            object.__setattr__(self, '_value', value)276        return self._value277 278    def __contains__(self, key: object) -> bool:279        return key in self.value280 281    def __bool__(self) -> bool:282        return bool(self.value)283 284    def __dir__(self) -> list[str]:285        return dir(self.value)286 287    def __iter__(self) -> Iterator[Any]:288        return iter(self.value)289 290    def __len__(self) -> int:291        return len(self.value)292 293    def __str__(self) -> str:294        return str(self.value)295 296    def __add__(self, other: object) -> Any:297        return self.value + other298 299    def __radd__(self, other: object) -> Any:300        return other + self.value301 302    def __mod__(self, other: object) -> Any:303        return self.value % other304 305    def __rmod__(self, other: object) -> Any:306        return other % self.value307 308    def __mul__(self, other: object) -> Any:309        return self.value * other310 311    def __rmul__(self, other: object) -> Any:312        return other * self.value313 314    def __call__(self, *args: Any, **kwargs: Any) -> Any:315        return self.value(*args, **kwargs)316 317    def __lt__(self, other: object) -> bool:318        return self.value < other319 320    def __le__(self, other: object) -> bool:321        return self.value <= other322 323    def __eq__(self, other: object) -> bool:324        return self.value == other325 326    def __ne__(self, other: object) -> bool:327        return self.value != other328 329    def __gt__(self, other: object) -> bool:330        return self.value > other331 332    def __ge__(self, other: object) -> bool:333        return self.value >= other334 335    def __delattr__(self, name: str) -> None:336        delattr(self.value, name)337 338    def __getattr__(self, name: str) -> Any:339        if self._attribute_error is not None:340            raise self._attribute_error341        return getattr(self.value, name)342 343    def __setattr__(self, name: str, value: Any) -> None:344        setattr(self.value, name, value)345 346    def __delitem__(self, key: Any) -> None:347        del self.value[key]348 349    def __getitem__(self, key: Any) -> Any:350        return self.value[key]351 352    def __setitem__(self, key: Any, value: Any) -> None:353        self.value[key] = value354 355    def __copy__(self) -> LazyProxy:356        return LazyProxy(357            self._func,358            enable_cache=self._is_cache_enabled,359            *self._args,  # noqa: B026360            **self._kwargs,361        )362 363    def __deepcopy__(self, memo: Any) -> LazyProxy:364        from copy import deepcopy365        return LazyProxy(366            deepcopy(self._func, memo),367            enable_cache=deepcopy(self._is_cache_enabled, memo),368            *deepcopy(self._args, memo),  # noqa: B026369            **deepcopy(self._kwargs, memo),370        )371 372 373class NullTranslations(gettext.NullTranslations):374 375    if TYPE_CHECKING:376        _info: dict[str, str]377        _fallback: NullTranslations | None378 379    DEFAULT_DOMAIN = None380 381    def __init__(self, fp: gettext._TranslationsReader | None = None) -> None:382        """Initialize a simple translations class which is not backed by a383        real catalog. Behaves similar to gettext.NullTranslations but also384        offers Babel's on *gettext methods (e.g. 'dgettext()').385 386        :param fp: a file-like object (ignored in this class)387        """388        # These attributes are set by gettext.NullTranslations when a catalog389        # is parsed (fp != None). Ensure that they are always present because390        # some *gettext methods (including '.gettext()') rely on the attributes.391        self._catalog: dict[tuple[str, Any] | str, str] = {}392        self.plural: Callable[[float | decimal.Decimal], int] = lambda n: int(n != 1)393        super().__init__(fp=fp)394        self.files = list(filter(None, [getattr(fp, 'name', None)]))395        self.domain = self.DEFAULT_DOMAIN396        self._domains: dict[str, NullTranslations] = {}397 398    def dgettext(self, domain: str, message: str) -> str:399        """Like ``gettext()``, but look the message up in the specified400        domain.401        """402        return self._domains.get(domain, self).gettext(message)403 404    def ldgettext(self, domain: str, message: str) -> str:405        """Like ``lgettext()``, but look the message up in the specified406        domain.407        """408        import warnings409        warnings.warn(410            'ldgettext() is deprecated, use dgettext() instead',411            DeprecationWarning,412            stacklevel=2,413        )414        return self._domains.get(domain, self).lgettext(message)415 416    def udgettext(self, domain: str, message: str) -> str:417        """Like ``ugettext()``, but look the message up in the specified418        domain.419        """420        return self._domains.get(domain, self).ugettext(message)421    # backward compatibility with 0.9422    dugettext = udgettext423 424    def dngettext(self, domain: str, singular: str, plural: str, num: int) -> str:425        """Like ``ngettext()``, but look the message up in the specified426        domain.427        """428        return self._domains.get(domain, self).ngettext(singular, plural, num)429 430    def ldngettext(self, domain: str, singular: str, plural: str, num: int) -> str:431        """Like ``lngettext()``, but look the message up in the specified432        domain.433        """434        import warnings435        warnings.warn(436            'ldngettext() is deprecated, use dngettext() instead',437            DeprecationWarning,438            stacklevel=2,439        )440        return self._domains.get(domain, self).lngettext(singular, plural, num)441 442    def udngettext(self, domain: str, singular: str, plural: str, num: int) -> str:443        """Like ``ungettext()`` but look the message up in the specified444        domain.445        """446        return self._domains.get(domain, self).ungettext(singular, plural, num)447    # backward compatibility with 0.9448    dungettext = udngettext449 450    # Most of the downwards code, until it gets included in stdlib, from:451    #    https://bugs.python.org/file10036/gettext-pgettext.patch452    #453    # The encoding of a msgctxt and a msgid in a .mo file is454    # msgctxt + "\x04" + msgid (gettext version >= 0.15)455    CONTEXT_ENCODING = '%s\x04%s'456 457    def pgettext(self, context: str, message: str) -> str | object:458        """Look up the `context` and `message` id in the catalog and return the459        corresponding message string, as an 8-bit string encoded with the460        catalog's charset encoding, if known.  If there is no entry in the461        catalog for the `message` id and `context` , and a fallback has been462        set, the look up is forwarded to the fallback's ``pgettext()``463        method. Otherwise, the `message` id is returned.464        """465        ctxt_msg_id = self.CONTEXT_ENCODING % (context, message)466        missing = object()467        tmsg = self._catalog.get(ctxt_msg_id, missing)468        if tmsg is missing:469            if self._fallback:470                return self._fallback.pgettext(context, message)471            return message472        return tmsg473 474    def lpgettext(self, context: str, message: str) -> str | bytes | object:475        """Equivalent to ``pgettext()``, but the translation is returned in the476        preferred system encoding, if no other encoding was explicitly set with477        ``bind_textdomain_codeset()``.478        """479        import warnings480        warnings.warn(481            'lpgettext() is deprecated, use pgettext() instead',482            DeprecationWarning,483            stacklevel=2,484        )485        tmsg = self.pgettext(context, message)486        encoding = getattr(self, "_output_charset", None) or locale.getpreferredencoding()487        return tmsg.encode(encoding) if isinstance(tmsg, str) else tmsg488 489    def npgettext(self, context: str, singular: str, plural: str, num: int) -> str:490        """Do a plural-forms lookup of a message id.  `singular` is used as the491        message id for purposes of lookup in the catalog, while `num` is used to492        determine which plural form to use.  The returned message string is an493        8-bit string encoded with the catalog's charset encoding, if known.494 495        If the message id for `context` is not found in the catalog, and a496        fallback is specified, the request is forwarded to the fallback's497        ``npgettext()`` method.  Otherwise, when ``num`` is 1 ``singular`` is498        returned, and ``plural`` is returned in all other cases.499        """500        ctxt_msg_id = self.CONTEXT_ENCODING % (context, singular)501        try:502            tmsg = self._catalog[(ctxt_msg_id, self.plural(num))]503            return tmsg504        except KeyError:505            if self._fallback:506                return self._fallback.npgettext(context, singular, plural, num)507            if num == 1:508                return singular509            else:510                return plural511 512    def lnpgettext(self, context: str, singular: str, plural: str, num: int) -> str | bytes:513        """Equivalent to ``npgettext()``, but the translation is returned in the514        preferred system encoding, if no other encoding was explicitly set with515        ``bind_textdomain_codeset()``.516        """517        import warnings518        warnings.warn(519            'lnpgettext() is deprecated, use npgettext() instead',520            DeprecationWarning,521            stacklevel=2,522        )523        ctxt_msg_id = self.CONTEXT_ENCODING % (context, singular)524        try:525            tmsg = self._catalog[(ctxt_msg_id, self.plural(num))]526            encoding = getattr(self, "_output_charset", None) or locale.getpreferredencoding()527            return tmsg.encode(encoding)528        except KeyError:529            if self._fallback:530                return self._fallback.lnpgettext(context, singular, plural, num)531            if num == 1:532                return singular533            else:534                return plural535 536    def upgettext(self, context: str, message: str) -> str:537        """Look up the `context` and `message` id in the catalog and return the538        corresponding message string, as a Unicode string.  If there is no entry539        in the catalog for the `message` id and `context`, and a fallback has540        been set, the look up is forwarded to the fallback's ``upgettext()``541        method.  Otherwise, the `message` id is returned.542        """543        ctxt_message_id = self.CONTEXT_ENCODING % (context, message)544        missing = object()545        tmsg = self._catalog.get(ctxt_message_id, missing)546        if tmsg is missing:547            if self._fallback:548                return self._fallback.upgettext(context, message)549            return str(message)550        assert isinstance(tmsg, str)551        return tmsg552 553    def unpgettext(self, context: str, singular: str, plural: str, num: int) -> str:554        """Do a plural-forms lookup of a message id.  `singular` is used as the555        message id for purposes of lookup in the catalog, while `num` is used to556        determine which plural form to use.  The returned message string is a557        Unicode string.558 559        If the message id for `context` is not found in the catalog, and a560        fallback is specified, the request is forwarded to the fallback's561        ``unpgettext()`` method.  Otherwise, when `num` is 1 `singular` is562        returned, and `plural` is returned in all other cases.563        """564        ctxt_message_id = self.CONTEXT_ENCODING % (context, singular)565        try:566            tmsg = self._catalog[(ctxt_message_id, self.plural(num))]567        except KeyError:568            if self._fallback:569                return self._fallback.unpgettext(context, singular, plural, num)570            tmsg = str(singular) if num == 1 else str(plural)571        return tmsg572 573    def dpgettext(self, domain: str, context: str, message: str) -> str | object:574        """Like `pgettext()`, but look the message up in the specified575        `domain`.576        """577        return self._domains.get(domain, self).pgettext(context, message)578 579    def udpgettext(self, domain: str, context: str, message: str) -> str:580        """Like `upgettext()`, but look the message up in the specified581        `domain`.582        """583        return self._domains.get(domain, self).upgettext(context, message)584    # backward compatibility with 0.9585    dupgettext = udpgettext586 587    def ldpgettext(self, domain: str, context: str, message: str) -> str | bytes | object:588        """Equivalent to ``dpgettext()``, but the translation is returned in the589        preferred system encoding, if no other encoding was explicitly set with590        ``bind_textdomain_codeset()``.591        """592        return self._domains.get(domain, self).lpgettext(context, message)593 594    def dnpgettext(self, domain: str, context: str, singular: str, plural: str, num: int) -> str:595        """Like ``npgettext``, but look the message up in the specified596        `domain`.597        """598        return self._domains.get(domain, self).npgettext(context, singular,599                                                         plural, num)600 601    def udnpgettext(self, domain: str, context: str, singular: str, plural: str, num: int) -> str:602        """Like ``unpgettext``, but look the message up in the specified603        `domain`.604        """605        return self._domains.get(domain, self).unpgettext(context, singular,606                                                          plural, num)607    # backward compatibility with 0.9608    dunpgettext = udnpgettext609 610    def ldnpgettext(self, domain: str, context: str, singular: str, plural: str, num: int) -> str | bytes:611        """Equivalent to ``dnpgettext()``, but the translation is returned in612        the preferred system encoding, if no other encoding was explicitly set613        with ``bind_textdomain_codeset()``.614        """615        return self._domains.get(domain, self).lnpgettext(context, singular,616                                                          plural, num)617 618    ugettext = gettext.NullTranslations.gettext619    ungettext = gettext.NullTranslations.ngettext620 621 622class Translations(NullTranslations, gettext.GNUTranslations):623    """An extended translation catalog class."""624 625    DEFAULT_DOMAIN = 'messages'626 627    def __init__(self, fp: gettext._TranslationsReader | None = None, domain: str | None = None):628        """Initialize the translations catalog.629 630        :param fp: the file-like object the translation should be read from631        :param domain: the message domain (default: 'messages')632        """633        super().__init__(fp=fp)634        self.domain = domain or self.DEFAULT_DOMAIN635 636    ugettext = gettext.GNUTranslations.gettext637    ungettext = gettext.GNUTranslations.ngettext638 639    @classmethod640    def load(641        cls,642        dirname: str | os.PathLike[str] | None = None,643        locales: Iterable[str | Locale] | str | Locale | None = None,644        domain: str | None = None,645    ) -> NullTranslations:646        """Load translations from the given directory.647 648        :param dirname: the directory containing the ``MO`` files649        :param locales: the list of locales in order of preference (items in650                        this list can be either `Locale` objects or locale651                        strings)652        :param domain: the message domain (default: 'messages')653        """654        if not domain:655            domain = cls.DEFAULT_DOMAIN656        filename = gettext.find(domain, dirname, _locales_to_names(locales))657        if not filename:658            return NullTranslations()659        with open(filename, 'rb') as fp:660            return cls(fp=fp, domain=domain)661 662    def __repr__(self) -> str:663        version = self._info.get('project-id-version')664        return f'<{type(self).__name__}: "{version}">'665 666    def add(self, translations: Translations, merge: bool = True):667        """Add the given translations to the catalog.668 669        If the domain of the translations is different than that of the670        current catalog, they are added as a catalog that is only accessible671        by the various ``d*gettext`` functions.672 673        :param translations: the `Translations` instance with the messages to674                             add675        :param merge: whether translations for message domains that have676                      already been added should be merged with the existing677                      translations678        """679        domain = getattr(translations, 'domain', self.DEFAULT_DOMAIN)680        if merge and domain == self.domain:681            return self.merge(translations)682 683        existing = self._domains.get(domain)684        if merge and isinstance(existing, Translations):685            existing.merge(translations)686        else:687            translations.add_fallback(self)688            self._domains[domain] = translations689 690        return self691 692    def merge(self, translations: Translations):693        """Merge the given translations into the catalog.694 695        Message translations in the specified catalog override any messages696        with the same identifier in the existing catalog.697 698        :param translations: the `Translations` instance with the messages to699                             merge700        """701        if isinstance(translations, gettext.GNUTranslations):702            self._catalog.update(translations._catalog)703            if isinstance(translations, Translations):704                self.files.extend(translations.files)705 706        return self707 708 709def _locales_to_names(710    locales: Iterable[str | Locale] | str | Locale | None,711) -> list[str] | None:712    """Normalize a `locales` argument to a list of locale names.713 714    :param locales: the list of locales in order of preference (items in715                    this list can be either `Locale` objects or locale716                    strings)717    """718    if locales is None:719        return None720    if isinstance(locales, Locale):721        return [str(locales)]722    if isinstance(locales, str):723        return [locales]724    return [str(locale) for locale in locales]725 
codekingpro/portable-devtools · Team Ai