Add typing

Change-Id: I2a31ff8a9c034779b813f531ccf7b2cb166d5e07
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane
2025-07-31 15:41:43 +01:00
parent 97f55ec2ea
commit 3d24243f02
12 changed files with 180 additions and 81 deletions

View File

@@ -15,6 +15,7 @@
# under the License. # under the License.
"""Translation function factory""" """Translation function factory"""
from collections.abc import Callable
import gettext import gettext
import os import os
@@ -34,7 +35,10 @@ CONTEXT_SEPARATOR = _message.CONTEXT_SEPARATOR
class TranslatorFactory: class TranslatorFactory:
"Create translator functions" "Create translator functions"
def __init__(self, domain, localedir=None): domain: str
localedir: str | None
def __init__(self, domain: str, localedir: str | None = None) -> None:
"""Establish a set of translation functions for the domain. """Establish a set of translation functions for the domain.
:param domain: Name of translation domain, :param domain: Name of translation domain,
@@ -49,7 +53,9 @@ class TranslatorFactory:
localedir = os.environ.get(variable_name) localedir = os.environ.get(variable_name)
self.localedir = localedir self.localedir = localedir
def _make_translation_func(self, domain=None): def _make_translation_func(
self, domain: str | None = None
) -> Callable[[str], str | _message.Message]:
"""Return a translation function ready for use with messages. """Return a translation function ready for use with messages.
The returned function takes a single value, the unicode string The returned function takes a single value, the unicode string
@@ -74,7 +80,7 @@ class TranslatorFactory:
# on the python version. # on the python version.
m = t.gettext m = t.gettext
def f(msg): def f(msg: str) -> str | _message.Message:
"""oslo_i18n.gettextutils translation function.""" """oslo_i18n.gettextutils translation function."""
if _lazy.USE_LAZY: if _lazy.USE_LAZY:
return _message.Message(msg, domain=domain) return _message.Message(msg, domain=domain)
@@ -82,7 +88,9 @@ class TranslatorFactory:
return f return f
def _make_contextual_translation_func(self, domain=None): def _make_contextual_translation_func(
self, domain: str | None = None
) -> Callable[[str, str], str | _message.Message]:
"""Return a translation function ready for use with context messages. """Return a translation function ready for use with context messages.
The returned function takes two values, the context of The returned function takes two values, the context of
@@ -103,7 +111,7 @@ class TranslatorFactory:
# on the python version. # on the python version.
m = t.gettext m = t.gettext
def f(ctx, msg): def f(ctx: str, msg: str) -> str | _message.Message:
"""oslo.i18n.gettextutils translation with context function.""" """oslo.i18n.gettextutils translation with context function."""
if _lazy.USE_LAZY: if _lazy.USE_LAZY:
msgid = (ctx, msg) msgid = (ctx, msg)
@@ -120,7 +128,9 @@ class TranslatorFactory:
return f return f
def _make_plural_translation_func(self, domain=None): def _make_plural_translation_func(
self, domain: str | None = None
) -> Callable[[str, str, int], str | _message.Message]:
"""Return a plural translation function ready for use with messages. """Return a plural translation function ready for use with messages.
The returned function takes three values, the single form of The returned function takes three values, the single form of
@@ -142,7 +152,9 @@ class TranslatorFactory:
# on the python version. # on the python version.
m = t.ngettext m = t.ngettext
def f(msgsingle, msgplural, msgcount): def f(
msgsingle: str, msgplural: str, msgcount: int
) -> str | _message.Message:
"""oslo.i18n.gettextutils plural translation function.""" """oslo.i18n.gettextutils plural translation function."""
if _lazy.USE_LAZY: if _lazy.USE_LAZY:
msgid = (msgsingle, msgplural, msgcount) msgid = (msgsingle, msgplural, msgcount)
@@ -154,12 +166,12 @@ class TranslatorFactory:
return f return f
@property @property
def primary(self): def primary(self) -> Callable[[str], str | _message.Message]:
"The default translation function." "The default translation function."
return self._make_translation_func() return self._make_translation_func()
@property @property
def contextual_form(self): def contextual_form(self) -> Callable[[str, str], str | _message.Message]:
"""The contextual translation function. """The contextual translation function.
The returned function takes two values, the context of The returned function takes two values, the context of
@@ -171,7 +183,7 @@ class TranslatorFactory:
return self._make_contextual_translation_func() return self._make_contextual_translation_func()
@property @property
def plural_form(self): def plural_form(self) -> Callable[[str, str, int], str | _message.Message]:
"""The plural translation function. """The plural translation function.
The returned function takes three values, the single form of The returned function takes three values, the single form of
@@ -183,25 +195,27 @@ class TranslatorFactory:
""" """
return self._make_plural_translation_func() return self._make_plural_translation_func()
def _make_log_translation_func(self, level): def _make_log_translation_func(
self, level: str
) -> Callable[[str], str | _message.Message]:
return self._make_translation_func(self.domain + '-log-' + level) return self._make_translation_func(self.domain + '-log-' + level)
@property @property
def log_info(self): def log_info(self) -> Callable[[str], str | _message.Message]:
"Translate info-level log messages." "Translate info-level log messages."
return self._make_log_translation_func('info') return self._make_log_translation_func('info')
@property @property
def log_warning(self): def log_warning(self) -> Callable[[str], str | _message.Message]:
"Translate warning-level log messages." "Translate warning-level log messages."
return self._make_log_translation_func('warning') return self._make_log_translation_func('warning')
@property @property
def log_error(self): def log_error(self) -> Callable[[str], str | _message.Message]:
"Translate error-level log messages." "Translate error-level log messages."
return self._make_log_translation_func('error') return self._make_log_translation_func('error')
@property @property
def log_critical(self): def log_critical(self) -> Callable[[str], str | _message.Message]:
"Translate critical-level log messages." "Translate critical-level log messages."
return self._make_log_translation_func('critical') return self._make_log_translation_func('critical')

View File

@@ -20,6 +20,8 @@ import copy
import gettext import gettext
import locale import locale
import os import os
from typing import Any, Literal, overload
from collections.abc import Iterable
from oslo_i18n import _factory from oslo_i18n import _factory
from oslo_i18n import _locale from oslo_i18n import _locale
@@ -30,7 +32,7 @@ __all__ = [
] ]
def install(domain): def install(domain: str) -> None:
"""Install a _() function using the given translation domain. """Install a _() function using the given translation domain.
Given a translation domain, install a _() function using gettext's Given a translation domain, install a _() function using gettext's
@@ -49,7 +51,7 @@ def install(domain):
builtins.__dict__['_'] = tf.primary builtins.__dict__['_'] = tf.primary
_AVAILABLE_LANGUAGES = {} _AVAILABLE_LANGUAGES: dict[str, list[str]] = {}
# Copied from Babel so anyone using aliases that were previously provided by # Copied from Babel so anyone using aliases that were previously provided by
# the Babel implementation of get_available_languages continues to work. These # the Babel implementation of get_available_languages continues to work. These
# are not recommended for use in new code. # are not recommended for use in new code.
@@ -96,7 +98,7 @@ _BABEL_ALIASES = {
} }
def get_available_languages(domain): def get_available_languages(domain: str) -> list[str]:
"""Lists the available languages for the given translation domain. """Lists the available languages for the given translation domain.
:param domain: the domain to get languages for :param domain: the domain to get languages for
@@ -106,7 +108,7 @@ def get_available_languages(domain):
localedir = os.environ.get(_locale.get_locale_dir_variable_name(domain)) localedir = os.environ.get(_locale.get_locale_dir_variable_name(domain))
def find(x): def find(x: str) -> str | None:
return gettext.find(domain, localedir=localedir, languages=[x]) return gettext.find(domain, localedir=localedir, languages=[x])
# NOTE(mrodden): en_US should always be available (and first in case # NOTE(mrodden): en_US should always be available (and first in case
@@ -125,10 +127,54 @@ def get_available_languages(domain):
_original_find = gettext.find _original_find = gettext.find
_FIND_CACHE = {} _FIND_CACHE: dict[
tuple[str, str | None, tuple[str, ...] | None, bool | int], Any
] = {}
def cached_find(domain, localedir=None, languages=None, all=0): @overload
def cached_find(
domain: str,
localedir: str | None = None,
languages: Iterable[str] | None = None,
all: Literal[False] = False,
) -> str | None: ...
@overload
def cached_find(
domain: str,
localedir: str | None = None,
languages: Iterable[str] | None = None,
*,
all: Literal[True],
) -> list[str]: ...
@overload
def cached_find(
domain: str,
localedir: str | None,
languages: Iterable[str] | None,
all: Literal[True],
) -> list[str]: ...
@overload
def cached_find(
domain: str,
localedir: str | None = None,
languages: Iterable[str] | None = None,
all: bool = False,
) -> Any: ...
def cached_find(
domain: str,
localedir: str | None = None,
languages: Iterable[str] | None = None,
all: bool = False,
) -> Any:
"""A version of gettext.find using a cache. """A version of gettext.find using a cache.
gettext.find looks for mo files on the disk using os.path.exists. Those gettext.find looks for mo files on the disk using os.path.exists. Those
@@ -149,4 +195,4 @@ def cached_find(domain, localedir=None, languages=None, all=0):
return result return result
gettext.find = cached_find gettext.find = cached_find # type: ignore[assignment]

View File

@@ -21,7 +21,7 @@ __all__ = [
USE_LAZY = False USE_LAZY = False
def enable_lazy(enable=True): def enable_lazy(enable: bool = True) -> None:
"""Convenience function for configuring _() to use lazy gettext """Convenience function for configuring _() to use lazy gettext
Call this at the start of execution to enable the gettextutils._ Call this at the start of execution to enable the gettextutils._

View File

@@ -15,7 +15,7 @@
# under the License. # under the License.
def get_locale_dir_variable_name(domain): def get_locale_dir_variable_name(domain: str) -> str:
"""Build environment variable name for local dir. """Build environment variable name for local dir.
Convert a translation domain name to a variable for specifying Convert a translation domain name to a variable for specifying

View File

@@ -20,6 +20,7 @@ import gettext
import locale import locale
import logging import logging
import os import os
from typing import Any
import warnings import warnings
from oslo_i18n import _locale from oslo_i18n import _locale
@@ -40,16 +41,23 @@ class Message(str):
and can be treated as such. and can be treated as such.
""" """
# Declare attributes that are set in __new__
msgid: str | tuple[str, str] | tuple[str, str, int]
domain: str
params: Any
has_contextual_form: bool
has_plural_form: bool
def __new__( def __new__(
cls, cls,
msgid, msgid: str | tuple[str, str] | tuple[str, str, int],
msgtext=None, msgtext: str | None = None,
params=None, params: Any = None,
domain='oslo', domain: str = 'oslo',
has_contextual_form=False, has_contextual_form: bool = False,
has_plural_form=False, has_plural_form: bool = False,
*args, *args: Any,
): ) -> 'Message':
"""Create a new Message object. """Create a new Message object.
In order for translation to work gettext requires a message ID, this In order for translation to work gettext requires a message ID, this
@@ -72,7 +80,7 @@ class Message(str):
msg.has_plural_form = has_plural_form msg.has_plural_form = has_plural_form
return msg return msg
def translation(self, desired_locale=None): def translation(self, desired_locale: str | None = None) -> str:
"""Translate this message to the desired locale. """Translate this message to the desired locale.
:param desired_locale: The desired locale to translate the message to, :param desired_locale: The desired locale to translate the message to,
@@ -105,12 +113,12 @@ class Message(str):
@staticmethod @staticmethod
def _translate_msgid( def _translate_msgid(
msgid, msgid: str | tuple[str, str] | tuple[str, str, int],
domain, domain: str,
desired_locale=None, desired_locale: str | None = None,
has_contextual_form=False, has_contextual_form: bool = False,
has_plural_form=False, has_plural_form: bool = False,
): ) -> str:
if not desired_locale: if not desired_locale:
system_locale = locale.getlocale(locale.LC_CTYPE) system_locale = locale.getlocale(locale.LC_CTYPE)
# If the system locale is not available to the runtime use English # If the system locale is not available to the runtime use English
@@ -132,6 +140,9 @@ class Message(str):
if not has_contextual_form and not has_plural_form: if not has_contextual_form and not has_plural_form:
# This is the most common case, so check it first. # This is the most common case, so check it first.
translator = lang.gettext translator = lang.gettext
# narrow type
if not isinstance(msgid, str):
raise ValueError("Simple msgid must be a string")
translated_message = translator(msgid) translated_message = translator(msgid)
elif has_contextual_form and has_plural_form: elif has_contextual_form and has_plural_form:
@@ -140,7 +151,13 @@ class Message(str):
raise ValueError("Unimplemented.") raise ValueError("Unimplemented.")
elif has_contextual_form: elif has_contextual_form:
(msgctx, msgtxt) = msgid # narrow type
if not isinstance(msgid, tuple) or len(msgid) != 2:
raise ValueError(
"contextual msgid must be a tuple of (context, text)"
)
msgctx, msgtxt = msgid
translator = lang.gettext translator = lang.gettext
msg_with_ctx = f"{msgctx}{CONTEXT_SEPARATOR}{msgtxt}" msg_with_ctx = f"{msgctx}{CONTEXT_SEPARATOR}{msgtxt}"
@@ -151,19 +168,24 @@ class Message(str):
translated_message = msgtxt translated_message = msgtxt
elif has_plural_form: elif has_plural_form:
(msgsingle, msgplural, msgcount) = msgid # narrow type
translator = lang.ngettext if not isinstance(msgid, tuple) or len(msgid) != 3:
translated_message = translator(msgsingle, msgplural, msgcount) raise ValueError(
"plural msgid must be a tuple of (singular, plural, count)"
)
msgsingle, msgplural, msgcount = msgid
translated_message = lang.ngettext(msgsingle, msgplural, msgcount)
return translated_message return translated_message
def _safe_translate(self, translated_message, translated_params): def _safe_translate(
self, translated_message: str, translated_params: Any
) -> str:
"""Trap translation errors and fall back to default translation. """Trap translation errors and fall back to default translation.
:param translated_message: the requested translation :param translated_message: the requested translation
:param translated_params: the params to be inserted :param translated_params: the params to be inserted
:return: if parameter insertion is successful then it is the :return: if parameter insertion is successful then it is the
translated_message with the translated_params inserted, if the translated_message with the translated_params inserted, if the
requested translation fails then it is the default translation requested translation fails then it is the default translation
@@ -195,7 +217,7 @@ class Message(str):
return translated_message return translated_message
def __mod__(self, other): def __mod__(self, other: Any) -> 'Message':
# When we mod a Message we want the actual operation to be performed # When we mod a Message we want the actual operation to be performed
# by the base class (i.e. unicode()), the only thing we do here is # by the base class (i.e. unicode()), the only thing we do here is
# save the original msgid and the parameters in case of a translation # save the original msgid and the parameters in case of a translation
@@ -206,7 +228,7 @@ class Message(str):
) )
return modded return modded
def _sanitize_mod_params(self, other): def _sanitize_mod_params(self, other: Any) -> Any:
"""Sanitize the object being modded with this Message. """Sanitize the object being modded with this Message.
- Add support for modding 'None' so translation supports it - Add support for modding 'None' so translation supports it
@@ -216,7 +238,7 @@ class Message(str):
translated, it will be used as it was when the Message was created translated, it will be used as it was when the Message was created
""" """
if other is None: if other is None:
params = (other,) params: Any = (other,)
elif isinstance(other, dict): elif isinstance(other, dict):
# Merge the dictionaries # Merge the dictionaries
# Copy each item in case one does not support deep copy. # Copy each item in case one does not support deep copy.
@@ -233,7 +255,7 @@ class Message(str):
params = self._copy_param(other) params = self._copy_param(other)
return params return params
def _copy_param(self, param): def _copy_param(self, param: Any) -> Any:
try: try:
return copy.deepcopy(param) return copy.deepcopy(param)
except Exception: except Exception:
@@ -241,11 +263,11 @@ class Message(str):
# python code-like objects that can't be deep-copied # python code-like objects that can't be deep-copied
return str(param) return str(param)
def __add__(self, other): def __add__(self, other: Any) -> str:
from oslo_i18n._i18n import _ from oslo_i18n._i18n import _
msg = _('Message objects do not support addition.') msg = _('Message objects do not support addition.')
raise TypeError(msg) raise TypeError(msg)
def __radd__(self, other): def __radd__(self, other: Any) -> str:
return self.__add__(other) return self.__add__(other)

View File

@@ -19,7 +19,7 @@ __all__ = [
] ]
def translate(obj, desired_locale=None): def translate(obj: object, desired_locale: str | None = None) -> object:
"""Gets the translated unicode representation of the given object. """Gets the translated unicode representation of the given object.
If the object is not translatable it is returned as-is. If the object is not translatable it is returned as-is.
@@ -48,7 +48,7 @@ def translate(obj, desired_locale=None):
return obj return obj
def translate_args(args, desired_locale=None): def translate_args(args: object, desired_locale: str | None = None) -> object:
"""Translates all the translatable elements of the given arguments object. """Translates all the translatable elements of the given arguments object.
This method is used for translating the translatable values in method This method is used for translating the translatable values in method

View File

@@ -12,6 +12,7 @@
"""Test fixtures for working with oslo_i18n.""" """Test fixtures for working with oslo_i18n."""
import gettext import gettext
from typing import Any
import fixtures import fixtures
@@ -19,7 +20,7 @@ from oslo_i18n import _lazy
from oslo_i18n import _message from oslo_i18n import _message
class Translation(fixtures.Fixture): class Translation(fixtures.Fixture): # type: ignore[misc]
"""Fixture for managing translatable strings. """Fixture for managing translatable strings.
This class provides methods for creating translatable strings This class provides methods for creating translatable strings
@@ -34,7 +35,7 @@ class Translation(fixtures.Fixture):
""" """
def __init__(self, domain='test-domain'): def __init__(self, domain: str = 'test-domain') -> None:
"""Initialize the fixture. """Initialize the fixture.
:param domain: The translation domain. This is not expected to :param domain: The translation domain. This is not expected to
@@ -44,7 +45,7 @@ class Translation(fixtures.Fixture):
""" """
self.domain = domain self.domain = domain
def lazy(self, msg): def lazy(self, msg: str) -> _message.Message:
"""Return a lazily translated message. """Return a lazily translated message.
:param msg: Input message string. May optionally include :param msg: Input message string. May optionally include
@@ -54,7 +55,7 @@ class Translation(fixtures.Fixture):
""" """
return _message.Message(msg, domain=self.domain) return _message.Message(msg, domain=self.domain)
def immediate(self, msg): def immediate(self, msg: str) -> str:
"""Return a string as though it had been translated immediately. """Return a string as though it had been translated immediately.
:param msg: Input message string. May optionally include :param msg: Input message string. May optionally include
@@ -65,10 +66,10 @@ class Translation(fixtures.Fixture):
return str(msg) return str(msg)
class ToggleLazy(fixtures.Fixture): class ToggleLazy(fixtures.Fixture): # type: ignore[misc]
"""Fixture to toggle lazy translation on or off for a test.""" """Fixture to toggle lazy translation on or off for a test."""
def __init__(self, enabled): def __init__(self, enabled: bool) -> None:
"""Force lazy translation on or off. """Force lazy translation on or off.
:param enabled: Flag controlling whether to enable or disable :param enabled: Flag controlling whether to enable or disable
@@ -79,12 +80,12 @@ class ToggleLazy(fixtures.Fixture):
self._enabled = enabled self._enabled = enabled
self._original_value = _lazy.USE_LAZY self._original_value = _lazy.USE_LAZY
def setUp(self): def setUp(self) -> None:
super().setUp() super().setUp()
self.addCleanup(self._restore_original) self.addCleanup(self._restore_original)
_lazy.enable_lazy(self._enabled) _lazy.enable_lazy(self._enabled)
def _restore_original(self): def _restore_original(self) -> None:
_lazy.enable_lazy(self._original_value) _lazy.enable_lazy(self._original_value)
@@ -99,25 +100,26 @@ class _PrefixTranslator(gettext.NullTranslations):
""" """
def __init__(self, fp=None, prefix='noprefix'): def __init__(self, fp: Any = None, prefix: str = 'noprefix') -> None:
gettext.NullTranslations.__init__(self, fp) gettext.NullTranslations.__init__(self, fp)
self.prefix = prefix self.prefix = prefix
def gettext(self, message): def gettext(self, message: str) -> str:
msg = gettext.NullTranslations.gettext(self, message) msg = gettext.NullTranslations.gettext(self, message)
return self.prefix + msg return self.prefix + msg
def ugettext(self, message): def ugettext(self, message: str) -> str:
msg = gettext.NullTranslations.ugettext(self, message) # Note: ugettext is deprecated, fallback to gettext
msg = gettext.NullTranslations.gettext(self, message)
return self.prefix + msg return self.prefix + msg
def _prefix_translations(*x, **y): def _prefix_translations(*x: Any, **y: Any) -> _PrefixTranslator:
"""Use message id prefixed with domain and language as translation""" """Use message id prefixed with domain and language as translation"""
return _PrefixTranslator(prefix=x[0] + '/' + y['languages'][0] + ': ') return _PrefixTranslator(prefix=x[0] + '/' + y['languages'][0] + ': ')
class PrefixLazyTranslation(fixtures.Fixture): class PrefixLazyTranslation(fixtures.Fixture): # type: ignore[misc]
"""Fixture to prefix lazy translation enabled messages """Fixture to prefix lazy translation enabled messages
Use of this fixture will cause messages supporting lazy translation to Use of this fixture will cause messages supporting lazy translation to
@@ -140,12 +142,14 @@ class PrefixLazyTranslation(fixtures.Fixture):
_DEFAULT_LANG = 'en_US' _DEFAULT_LANG = 'en_US'
def __init__(self, languages=None, locale=None): def __init__(
self, languages: list[str] | None = None, locale: Any = None
) -> None:
super().__init__() super().__init__()
self.languages = languages or [PrefixLazyTranslation._DEFAULT_LANG] self.languages = languages or [PrefixLazyTranslation._DEFAULT_LANG]
self.locale = locale self.locale = locale
def setUp(self): def setUp(self) -> None:
super().setUp() super().setUp()
self.useFixture(ToggleLazy(True)) self.useFixture(ToggleLazy(True))
self.useFixture( self.useFixture(

View File

@@ -16,6 +16,7 @@
"""logging utilities for translation""" """logging utilities for translation"""
import logging
from logging import handlers from logging import handlers
from oslo_i18n import _translate from oslo_i18n import _translate
@@ -55,7 +56,9 @@ class TranslationHandler(handlers.MemoryHandler):
""" """
def __init__(self, locale=None, target=None): def __init__(
self, locale: str | None = None, target: logging.Handler | None = None
) -> None:
"""Initialize a TranslationHandler """Initialize a TranslationHandler
:param locale: locale to use for translating messages :param locale: locale to use for translating messages
@@ -70,10 +73,11 @@ class TranslationHandler(handlers.MemoryHandler):
handlers.MemoryHandler.__init__(self, capacity=0, target=target) handlers.MemoryHandler.__init__(self, capacity=0, target=target)
self.locale = locale self.locale = locale
def setFormatter(self, fmt): def setFormatter(self, fmt: logging.Formatter | None) -> None:
if self.target is not None:
self.target.setFormatter(fmt) self.target.setFormatter(fmt)
def emit(self, record): def emit(self, record: logging.LogRecord) -> None:
# We save the message from the original record to restore it # We save the message from the original record to restore it
# after translation, so other handlers are not affected by this # after translation, so other handlers are not affected by this
original_msg = record.msg original_msg = record.msg
@@ -85,12 +89,13 @@ class TranslationHandler(handlers.MemoryHandler):
record.msg = original_msg record.msg = original_msg
record.args = original_args record.args = original_args
def _translate_and_log_record(self, record): def _translate_and_log_record(self, record: logging.LogRecord) -> None:
record.msg = _translate.translate(record.msg, self.locale) record.msg = _translate.translate(record.msg, self.locale)
# In addition to translating the message, we also need to translate # In addition to translating the message, we also need to translate
# arguments that were passed to the log method that were not part # arguments that were passed to the log method that were not part
# of the main message e.g., log.info(_('Some message %s'), this_one)) # of the main message e.g., log.info(_('Some message %s'), this_one))
record.args = _translate.translate_args(record.args, self.locale) record.args = _translate.translate_args(record.args, self.locale) # type: ignore[assignment]
if self.target is not None:
self.target.emit(record) self.target.emit(record)

View File

@@ -41,8 +41,10 @@ class TranslationHandlerTestCase(test_base.BaseTestCase):
self.logger.addHandler(self.translation_handler) self.logger.addHandler(self.translation_handler)
def test_set_formatter(self): def test_set_formatter(self):
formatter = 'some formatter' formatter = logging.Formatter()
self.translation_handler.setFormatter(formatter) self.translation_handler.setFormatter(formatter)
# narrow types https://github.com/python/mypy/issues/5088
assert self.translation_handler.target is not None
self.assertEqual(formatter, self.translation_handler.target.formatter) self.assertEqual(formatter, self.translation_handler.target.formatter)
@mock.patch('gettext.translation') @mock.patch('gettext.translation')

View File

@@ -346,7 +346,7 @@ class MessageTestCase(test_base.BaseTestCase):
obj = utils.SomeObject(message) obj = utils.SomeObject(message)
unicoded_obj = str(obj) unicoded_obj = str(obj)
self.assertEqual(es_translation, unicoded_obj.translation('es')) self.assertEqual(es_translation, unicoded_obj.translation('es')) # type: ignore[attr-defined]
@mock.patch('gettext.translation') @mock.patch('gettext.translation')
def test_translate_multiple_languages(self, mock_translation): def test_translate_multiple_languages(self, mock_translation):
@@ -520,8 +520,8 @@ class MessageTestCase(test_base.BaseTestCase):
obj = utils.SomeObject(msg) obj = utils.SomeObject(msg)
unicoded_obj = str(obj) unicoded_obj = str(obj)
self.assertEqual(expected_translation, unicoded_obj.translation('es')) self.assertEqual(expected_translation, unicoded_obj.translation('es')) # type: ignore[attr-defined]
self.assertEqual(default_translation, unicoded_obj.translation('XX')) self.assertEqual(default_translation, unicoded_obj.translation('XX')) # type: ignore[attr-defined]
@mock.patch('gettext.translation') @mock.patch('gettext.translation')
def test_translate_message_with_message_parameter(self, mock_translation): def test_translate_message_with_message_parameter(self, mock_translation):

View File

@@ -46,3 +46,6 @@ docstring-code-format = true
[tool.ruff.lint] [tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "S", "UP"] select = ["E4", "E7", "E9", "F", "S", "UP"]
ignore = ["F403"] ignore = ["F403"]
[tool.ruff.lint.per-file-ignores]
"oslo_i18n/tests/*" = ["S"]

View File

@@ -54,6 +54,9 @@ commands =
# We only enable the hacking (H) checks # We only enable the hacking (H) checks
select = H select = H
show-source = true show-source = true
# Ignore warnings that conflict with ruff-format
# H301: one import per line
ignore = H301
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,__init__.py exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,__init__.py
[hacking] [hacking]