diff --git a/doc/source/api.rst b/doc/source/api.rst index 4c46c62..a44428a 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -2,12 +2,12 @@ API ===== -oslo.i18n +oslo_i18n ========= -.. automodule:: oslo.i18n +.. automodule:: oslo_i18n -.. autoclass:: oslo.i18n.TranslatorFactory +.. autoclass:: oslo_i18n.TranslatorFactory :members: .. seealso:: @@ -15,24 +15,24 @@ oslo.i18n An example of using a :class:`TranslatorFactory` is provided in :ref:`integration-module`. -.. autofunction:: oslo.i18n.enable_lazy +.. autofunction:: oslo_i18n.enable_lazy .. seealso:: :ref:`lazy-translation` -.. autofunction:: oslo.i18n.translate +.. autofunction:: oslo_i18n.translate -.. autofunction:: oslo.i18n.get_available_languages +.. autofunction:: oslo_i18n.get_available_languages -oslo.i18n.log +oslo_i18n.log ============= -.. automodule:: oslo.i18n.log +.. automodule:: oslo_i18n.log :members: -oslo.i18n.fixture +oslo_i18n.fixture ================= -.. automodule:: oslo.i18n.fixture +.. automodule:: oslo_i18n.fixture :members: diff --git a/doc/source/guidelines.rst b/doc/source/guidelines.rst index edb8926..2377d64 100644 --- a/doc/source/guidelines.rst +++ b/doc/source/guidelines.rst @@ -4,7 +4,7 @@ Text messages the user sees via exceptions or API calls should be translated using -:py:attr:`TranslatorFactory.primary `, which should +:py:attr:`TranslatorFactory.primary `, which should be installed as ``_()`` in the integration module. .. seealso:: diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 6dc0031..33dd03f 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -16,16 +16,16 @@ Creating an Integration Module To use oslo.i18n in a project, you will need to create a small integration module to hold an instance of -:class:`~oslo.i18n.TranslatorFactory` and references to +:class:`~oslo_i18n.TranslatorFactory` and references to the marker functions the factory creates. :: # app/i18n.py - from oslo import i18n + import oslo_i18n - _translators = i18n.TranslatorFactory(domain='myapp') + _translators = oslo_i18n.TranslatorFactory(domain='myapp') # The primary translation function using the well-known name "_" _ = _translators.primary @@ -151,14 +151,14 @@ To enable lazy translation, call :func:`enable_lazy`. :: - from oslo import i18n + import oslo_i18n - i18n.enable_lazy() + oslo_i18n.enable_lazy() Translating Messages ==================== -Use :func:`~oslo.i18n.translate` to translate strings to +Use :func:`~oslo_i18n.translate` to translate strings to a specific locale. :func:`translate` handles delayed translation and strings that have already been translated immediately. It should be used at the point where the locale to be used is known, which is often @@ -167,9 +167,9 @@ emitted. :: - from oslo import i18n + import oslo_i18n - trans_msg = i18n.translate(msg, desired_locale=my_locale) + trans_msg = oslo_i18n.translate(msg, desired_locale=my_locale) if desired_locale is not specified then the default locale is used. @@ -178,14 +178,14 @@ Available Languages Only the languages that have translations provided are available for translation. To determine which languages are available the -:func:`~oslo.i18n.get_available_languages` is provided. Since different languages +:func:`~oslo_i18n.get_available_languages` is provided. Since different languages can be installed for each domain, the domain must be specified. :: - from oslo import i18n + import oslo_i18n - avail_lang = i18n.get_available_languages('myapp') + avail_lang = oslo_i18n.get_available_languages('myapp') .. seealso:: diff --git a/oslo/i18n/__init__.py b/oslo/i18n/__init__.py index 4602749..e43c963 100644 --- a/oslo/i18n/__init__.py +++ b/oslo/i18n/__init__.py @@ -10,7 +10,22 @@ # License for the specific language governing permissions and limitations # under the License. -from ._factory import * -from ._gettextutils import * -from ._lazy import * -from ._translate import * +import warnings + +from oslo_i18n._factory import * +from oslo_i18n._gettextutils import * +from oslo_i18n._lazy import * +from oslo_i18n._translate import * + + +def deprecated(): + new_name = __name__.replace('.', '_') + warnings.warn( + ('The oslo namespace package is deprecated. Please use %s instead.' % + new_name), + DeprecationWarning, + stacklevel=3, + ) + + +deprecated() diff --git a/oslo/i18n/fixture.py b/oslo/i18n/fixture.py index 4a91a2f..eb08773 100644 --- a/oslo/i18n/fixture.py +++ b/oslo/i18n/fixture.py @@ -9,57 +9,5 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -"""Test fixtures for working with oslo.i18n. -""" - -import fixtures -import six - -from oslo.i18n import _message - - -class Translation(fixtures.Fixture): - """Fixture for managing translatable strings. - - This class provides methods for creating translatable strings - using both lazy translation and immediate translation. It can be - used to generate the different types of messages returned from - oslo.i18n to test code that may need to know about the type to - handle them differently (for example, error handling in WSGI apps, - or logging). - - Use this class to generate messages instead of toggling the global - lazy flag and using the regular translation factory. - - """ - - def __init__(self, domain='test-domain'): - """Initialize the fixture. - - :param domain: The translation domain. This is not expected to - coincide with an actual set of message - catalogs, but it can. - :type domain: str - """ - self.domain = domain - - def lazy(self, msg): - """Return a lazily translated message. - - :param msg: Input message string. May optionally include - positional or named string interpolation markers. - :type msg: str or unicode - - """ - return _message.Message(msg, domain=self.domain) - - def immediate(self, msg): - """Return a string as though it had been translated immediately. - - :param msg: Input message string. May optionally include - positional or named string interpolation markers. - :type msg: str or unicode - - """ - return six.text_type(msg) +from oslo_i18n.fixture import * # noqa diff --git a/oslo/i18n/log.py b/oslo/i18n/log.py index 8ae19e6..ac18766 100644 --- a/oslo/i18n/log.py +++ b/oslo/i18n/log.py @@ -1,7 +1,3 @@ -# Copyright 2012 Red Hat, Inc. -# Copyright 2013 IBM Corp. -# All Rights Reserved. -# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -14,84 +10,4 @@ # License for the specific language governing permissions and limitations # under the License. -"""logging utilities for translation -""" - -from logging import handlers - -from oslo.i18n import _translate - - -class TranslationHandler(handlers.MemoryHandler): - """Handler that translates records before logging them. - - When lazy translation is enabled in the application (see - :func:`~oslo.i18n.enable_lazy`), the :class:`TranslationHandler` - uses its locale configuration setting to determine how to - translate LogRecord objects before forwarding them to the - logging.Handler. - - When lazy translation is disabled, the message in the LogRecord is - converted to unicode without any changes and then forwarded to the - logging.Handler. - - The handler can be configured declaratively in the - ``logging.conf`` as follows:: - - [handlers] - keys = translatedlog, translator - - [handler_translatedlog] - class = handlers.WatchedFileHandler - args = ('/var/log/api-localized.log',) - formatter = context - - [handler_translator] - class = oslo.i18n.log.TranslationHandler - target = translatedlog - args = ('zh_CN',) - - If the specified locale is not available in the system, the handler will - log in the default locale. - - """ - - def __init__(self, locale=None, target=None): - """Initialize a TranslationHandler - - :param locale: locale to use for translating messages - :param target: logging.Handler object to forward - LogRecord objects to after translation - """ - # NOTE(luisg): In order to allow this handler to be a wrapper for - # other handlers, such as a FileHandler, and still be able to - # configure it using logging.conf, this handler has to extend - # MemoryHandler because only the MemoryHandlers' logging.conf - # parsing is implemented such that it accepts a target handler. - handlers.MemoryHandler.__init__(self, capacity=0, target=target) - self.locale = locale - - def setFormatter(self, fmt): - self.target.setFormatter(fmt) - - def emit(self, record): - # We save the message from the original record to restore it - # after translation, so other handlers are not affected by this - original_msg = record.msg - original_args = record.args - - try: - self._translate_and_log_record(record) - finally: - record.msg = original_msg - record.args = original_args - - def _translate_and_log_record(self, record): - record.msg = _translate.translate(record.msg, self.locale) - - # In addition to translating the message, we also need to translate - # 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)) - record.args = _translate.translate_args(record.args, self.locale) - - self.target.emit(record) +from oslo_i18n.log import * # noqa diff --git a/oslo_i18n/__init__.py b/oslo_i18n/__init__.py new file mode 100644 index 0000000..4602749 --- /dev/null +++ b/oslo_i18n/__init__.py @@ -0,0 +1,16 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ._factory import * +from ._gettextutils import * +from ._lazy import * +from ._translate import * diff --git a/oslo/i18n/_factory.py b/oslo_i18n/_factory.py similarity index 95% rename from oslo/i18n/_factory.py rename to oslo_i18n/_factory.py index c7a7217..73ab217 100644 --- a/oslo/i18n/_factory.py +++ b/oslo_i18n/_factory.py @@ -21,9 +21,9 @@ import os import six -from oslo.i18n import _lazy -from oslo.i18n import _locale -from oslo.i18n import _message +from oslo_i18n import _lazy +from oslo_i18n import _locale +from oslo_i18n import _message __all__ = [ @@ -75,7 +75,7 @@ class TranslatorFactory(object): m = t.gettext if six.PY3 else t.ugettext def f(msg): - """oslo.i18n.gettextutils translation function.""" + """oslo_i18n.gettextutils translation function.""" if _lazy.USE_LAZY: return _message.Message(msg, domain=domain) return m(msg) diff --git a/oslo/i18n/_gettextutils.py b/oslo_i18n/_gettextutils.py similarity index 97% rename from oslo/i18n/_gettextutils.py rename to oslo_i18n/_gettextutils.py index 466c2c9..75a8313 100644 --- a/oslo/i18n/_gettextutils.py +++ b/oslo_i18n/_gettextutils.py @@ -24,8 +24,8 @@ import os from babel import localedata import six -from oslo.i18n import _factory -from oslo.i18n import _locale +from oslo_i18n import _factory +from oslo_i18n import _locale __all__ = [ 'install', diff --git a/oslo/i18n/_i18n.py b/oslo_i18n/_i18n.py similarity index 90% rename from oslo/i18n/_i18n.py rename to oslo_i18n/_i18n.py index 2dd2e4f..7755571 100644 --- a/oslo/i18n/_i18n.py +++ b/oslo_i18n/_i18n.py @@ -16,10 +16,10 @@ """Translation support for messages in this library. """ -from oslo.i18n import _factory +from oslo_i18n import _factory # Create the global translation functions. -_translators = _factory.TranslatorFactory('oslo.i18n') +_translators = _factory.TranslatorFactory('oslo_i18n') # The primary translation function using the well-known name "_" _ = _translators.primary diff --git a/oslo/i18n/_lazy.py b/oslo_i18n/_lazy.py similarity index 100% rename from oslo/i18n/_lazy.py rename to oslo_i18n/_lazy.py diff --git a/oslo/i18n/_locale.py b/oslo_i18n/_locale.py similarity index 100% rename from oslo/i18n/_locale.py rename to oslo_i18n/_locale.py diff --git a/oslo/i18n/_message.py b/oslo_i18n/_message.py similarity index 97% rename from oslo/i18n/_message.py rename to oslo_i18n/_message.py index 534c273..ceb39e7 100644 --- a/oslo/i18n/_message.py +++ b/oslo_i18n/_message.py @@ -23,8 +23,8 @@ import os import six -from oslo.i18n import _locale -from oslo.i18n import _translate +from oslo_i18n import _locale +from oslo_i18n import _translate class Message(six.text_type): @@ -149,7 +149,7 @@ class Message(six.text_type): return six.text_type(param) def __add__(self, other): - from oslo.i18n._i18n import _ + from oslo_i18n._i18n import _ msg = _('Message objects do not support addition.') raise TypeError(msg) @@ -160,7 +160,7 @@ class Message(six.text_type): def __str__(self): # NOTE(luisg): Logging in python 2.6 tries to str() log records, # and it expects specifically a UnicodeError in order to proceed. - from oslo.i18n._i18n import _ + from oslo_i18n._i18n import _ msg = _('Message objects do not support str() because they may ' 'contain non-ascii characters. ' 'Please use unicode() or translate() instead.') diff --git a/oslo/i18n/_translate.py b/oslo_i18n/_translate.py similarity index 97% rename from oslo/i18n/_translate.py rename to oslo_i18n/_translate.py index 4b25b71..1809e1e 100644 --- a/oslo/i18n/_translate.py +++ b/oslo_i18n/_translate.py @@ -36,7 +36,7 @@ def translate(obj, desired_locale=None): it could not be translated """ - from oslo.i18n import _message # avoid circular dependency at module level + from oslo_i18n import _message # avoid circular dependency at module level message = obj if not isinstance(message, _message.Message): # If the object to translate is not already translatable, diff --git a/oslo_i18n/fixture.py b/oslo_i18n/fixture.py new file mode 100644 index 0000000..076c708 --- /dev/null +++ b/oslo_i18n/fixture.py @@ -0,0 +1,65 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Test fixtures for working with oslo_i18n. + +""" + +import fixtures +import six + +from oslo_i18n import _message + + +class Translation(fixtures.Fixture): + """Fixture for managing translatable strings. + + This class provides methods for creating translatable strings + using both lazy translation and immediate translation. It can be + used to generate the different types of messages returned from + oslo_i18n to test code that may need to know about the type to + handle them differently (for example, error handling in WSGI apps, + or logging). + + Use this class to generate messages instead of toggling the global + lazy flag and using the regular translation factory. + + """ + + def __init__(self, domain='test-domain'): + """Initialize the fixture. + + :param domain: The translation domain. This is not expected to + coincide with an actual set of message + catalogs, but it can. + :type domain: str + """ + self.domain = domain + + def lazy(self, msg): + """Return a lazily translated message. + + :param msg: Input message string. May optionally include + positional or named string interpolation markers. + :type msg: str or unicode + + """ + return _message.Message(msg, domain=self.domain) + + def immediate(self, msg): + """Return a string as though it had been translated immediately. + + :param msg: Input message string. May optionally include + positional or named string interpolation markers. + :type msg: str or unicode + + """ + return six.text_type(msg) diff --git a/oslo_i18n/log.py b/oslo_i18n/log.py new file mode 100644 index 0000000..563c1b3 --- /dev/null +++ b/oslo_i18n/log.py @@ -0,0 +1,97 @@ +# Copyright 2012 Red Hat, Inc. +# Copyright 2013 IBM Corp. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""logging utilities for translation +""" + +from logging import handlers + +from oslo_i18n import _translate + + +class TranslationHandler(handlers.MemoryHandler): + """Handler that translates records before logging them. + + When lazy translation is enabled in the application (see + :func:`~oslo_i18n.enable_lazy`), the :class:`TranslationHandler` + uses its locale configuration setting to determine how to + translate LogRecord objects before forwarding them to the + logging.Handler. + + When lazy translation is disabled, the message in the LogRecord is + converted to unicode without any changes and then forwarded to the + logging.Handler. + + The handler can be configured declaratively in the + ``logging.conf`` as follows:: + + [handlers] + keys = translatedlog, translator + + [handler_translatedlog] + class = handlers.WatchedFileHandler + args = ('/var/log/api-localized.log',) + formatter = context + + [handler_translator] + class = oslo_i18n.log.TranslationHandler + target = translatedlog + args = ('zh_CN',) + + If the specified locale is not available in the system, the handler will + log in the default locale. + + """ + + def __init__(self, locale=None, target=None): + """Initialize a TranslationHandler + + :param locale: locale to use for translating messages + :param target: logging.Handler object to forward + LogRecord objects to after translation + """ + # NOTE(luisg): In order to allow this handler to be a wrapper for + # other handlers, such as a FileHandler, and still be able to + # configure it using logging.conf, this handler has to extend + # MemoryHandler because only the MemoryHandlers' logging.conf + # parsing is implemented such that it accepts a target handler. + handlers.MemoryHandler.__init__(self, capacity=0, target=target) + self.locale = locale + + def setFormatter(self, fmt): + self.target.setFormatter(fmt) + + def emit(self, record): + # We save the message from the original record to restore it + # after translation, so other handlers are not affected by this + original_msg = record.msg + original_args = record.args + + try: + self._translate_and_log_record(record) + finally: + record.msg = original_msg + record.args = original_args + + def _translate_and_log_record(self, record): + record.msg = _translate.translate(record.msg, self.locale) + + # In addition to translating the message, we also need to translate + # 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)) + record.args = _translate.translate_args(record.args, self.locale) + + self.target.emit(record) diff --git a/oslo_i18n/tests/__init__.py b/oslo_i18n/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fakes.py b/oslo_i18n/tests/fakes.py similarity index 100% rename from tests/fakes.py rename to oslo_i18n/tests/fakes.py diff --git a/tests/test_factory.py b/oslo_i18n/tests/test_factory.py similarity index 97% rename from tests/test_factory.py rename to oslo_i18n/tests/test_factory.py index 762f1a3..771728e 100644 --- a/tests/test_factory.py +++ b/oslo_i18n/tests/test_factory.py @@ -18,9 +18,9 @@ import mock from oslotest import base as test_base import six -from oslo.i18n import _factory -from oslo.i18n import _lazy -from oslo.i18n import _message +from oslo_i18n import _factory +from oslo_i18n import _lazy +from oslo_i18n import _message class TranslatorFactoryTest(test_base.BaseTestCase): diff --git a/oslo_i18n/tests/test_fixture.py b/oslo_i18n/tests/test_fixture.py new file mode 100644 index 0000000..aca994f --- /dev/null +++ b/oslo_i18n/tests/test_fixture.py @@ -0,0 +1,38 @@ +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslotest import base as test_base +import six + +from oslo_i18n import _message +from oslo_i18n import fixture + + +class FixtureTest(test_base.BaseTestCase): + + def setUp(self): + super(FixtureTest, self).setUp() + self.trans_fixture = self.useFixture(fixture.Translation()) + + def test_lazy(self): + msg = self.trans_fixture.lazy('this is a lazy message') + self.assertIsInstance(msg, _message.Message) + self.assertEqual(msg.msgid, 'this is a lazy message') + + def test_immediate(self): + msg = self.trans_fixture.immediate('this is a lazy message') + # Python 2.6 does not have assertNotIsInstance + self.assertFalse(isinstance(msg, _message.Message)) + self.assertIsInstance(msg, six.text_type) + self.assertEqual(msg, u'this is a lazy message') diff --git a/tests/test_gettextutils.py b/oslo_i18n/tests/test_gettextutils.py similarity index 96% rename from tests/test_gettextutils.py rename to oslo_i18n/tests/test_gettextutils.py index af2e4a8..f433700 100644 --- a/tests/test_gettextutils.py +++ b/oslo_i18n/tests/test_gettextutils.py @@ -23,10 +23,10 @@ from oslotest import base as test_base from oslotest import moxstubout import six -from oslo.i18n import _factory -from oslo.i18n import _gettextutils -from oslo.i18n import _lazy -from oslo.i18n import _message +from oslo_i18n import _factory +from oslo_i18n import _gettextutils +from oslo_i18n import _lazy +from oslo_i18n import _message LOG = logging.getLogger(__name__) @@ -41,7 +41,7 @@ class GettextTest(test_base.BaseTestCase): self.mox = moxfixture.mox # remember so we can reset to it later in case it changes self._USE_LAZY = _lazy.USE_LAZY - self.t = _factory.TranslatorFactory('oslo.i18n.test') + self.t = _factory.TranslatorFactory('oslo_i18n.test') def tearDown(self): # reset to value before test diff --git a/oslo_i18n/tests/test_handler.py b/oslo_i18n/tests/test_handler.py new file mode 100644 index 0000000..b0c678e --- /dev/null +++ b/oslo_i18n/tests/test_handler.py @@ -0,0 +1,106 @@ +# Copyright 2012 Red Hat, Inc. +# Copyright 2013 IBM Corp. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +import mock +from oslotest import base as test_base +import six + +from oslo_i18n import _message +from oslo_i18n import log as i18n_log +from oslo_i18n.tests import fakes + +LOG = logging.getLogger(__name__) + + +class TranslationHandlerTestCase(test_base.BaseTestCase): + + def setUp(self): + super(TranslationHandlerTestCase, self).setUp() + + self.stream = six.StringIO() + self.destination_handler = logging.StreamHandler(self.stream) + self.translation_handler = i18n_log.TranslationHandler('zh_CN') + self.translation_handler.setTarget(self.destination_handler) + + self.logger = logging.getLogger('localehander_logger') + self.logger.setLevel(logging.DEBUG) + self.logger.addHandler(self.translation_handler) + + def test_set_formatter(self): + formatter = 'some formatter' + self.translation_handler.setFormatter(formatter) + self.assertEqual(formatter, self.translation_handler.target.formatter) + + @mock.patch('gettext.translation') + def test_emit_translated_message(self, mock_translation): + log_message = 'A message to be logged' + log_message_translation = 'A message to be logged in Chinese' + translations = {log_message: log_message_translation} + translations_map = {'zh_CN': translations} + translator = fakes.FakeTranslations.translator(translations_map) + mock_translation.side_effect = translator + + msg = _message.Message(log_message) + + self.logger.info(msg) + self.assertIn(log_message_translation, self.stream.getvalue()) + + @mock.patch('gettext.translation') + def test_emit_translated_message_with_args(self, mock_translation): + log_message = 'A message to be logged %s' + log_message_translation = 'A message to be logged in Chinese %s' + log_arg = 'Arg to be logged' + log_arg_translation = 'An arg to be logged in Chinese' + + translations = {log_message: log_message_translation, + log_arg: log_arg_translation} + translations_map = {'zh_CN': translations} + translator = fakes.FakeTranslations.translator(translations_map) + mock_translation.side_effect = translator + + msg = _message.Message(log_message) + arg = _message.Message(log_arg) + + self.logger.info(msg, arg) + self.assertIn(log_message_translation % log_arg_translation, + self.stream.getvalue()) + + @mock.patch('gettext.translation') + def test_emit_translated_message_with_named_args(self, mock_translation): + log_message = 'A message to be logged %(arg1)s $(arg2)s' + log_message_translation = 'Chinese msg to be logged %(arg1)s $(arg2)s' + log_arg_1 = 'Arg1 to be logged' + log_arg_1_translation = 'Arg1 to be logged in Chinese' + log_arg_2 = 'Arg2 to be logged' + log_arg_2_translation = 'Arg2 to be logged in Chinese' + + translations = {log_message: log_message_translation, + log_arg_1: log_arg_1_translation, + log_arg_2: log_arg_2_translation} + translations_map = {'zh_CN': translations} + translator = fakes.FakeTranslations.translator(translations_map) + mock_translation.side_effect = translator + + msg = _message.Message(log_message) + arg_1 = _message.Message(log_arg_1) + arg_2 = _message.Message(log_arg_2) + + self.logger.info(msg, {'arg1': arg_1, 'arg2': arg_2}) + translation = log_message_translation % {'arg1': log_arg_1_translation, + 'arg2': log_arg_2_translation} + self.assertIn(translation, self.stream.getvalue()) diff --git a/tests/test_lazy.py b/oslo_i18n/tests/test_lazy.py similarity index 97% rename from tests/test_lazy.py rename to oslo_i18n/tests/test_lazy.py index 9e79bc4..049d51a 100644 --- a/tests/test_lazy.py +++ b/oslo_i18n/tests/test_lazy.py @@ -16,7 +16,7 @@ from oslotest import base as test_base -from oslo.i18n import _lazy +from oslo_i18n import _lazy class LazyTest(test_base.BaseTestCase): diff --git a/tests/test_locale_dir_variable.py b/oslo_i18n/tests/test_locale_dir_variable.py similarity index 97% rename from tests/test_locale_dir_variable.py rename to oslo_i18n/tests/test_locale_dir_variable.py index ca87482..26321c1 100644 --- a/tests/test_locale_dir_variable.py +++ b/oslo_i18n/tests/test_locale_dir_variable.py @@ -15,7 +15,7 @@ from oslotest import base as test_base import testscenarios.testcase -from oslo.i18n import _locale +from oslo_i18n import _locale class LocaleDirVariableTest(testscenarios.testcase.WithScenarios, diff --git a/tests/test_logging.py b/oslo_i18n/tests/test_logging.py similarity index 97% rename from tests/test_logging.py rename to oslo_i18n/tests/test_logging.py index a146709..07e5c71 100644 --- a/tests/test_logging.py +++ b/oslo_i18n/tests/test_logging.py @@ -17,7 +17,7 @@ import mock from oslotest import base as test_base -from oslo.i18n import _factory +from oslo_i18n import _factory class LogLevelTranslationsTest(test_base.BaseTestCase): diff --git a/tests/test_message.py b/oslo_i18n/tests/test_message.py similarity index 99% rename from tests/test_message.py rename to oslo_i18n/tests/test_message.py index 6340729..0fab2fa 100644 --- a/tests/test_message.py +++ b/oslo_i18n/tests/test_message.py @@ -23,9 +23,9 @@ from oslotest import base as test_base import six import testtools -from oslo.i18n import _message -from tests import fakes -from tests import utils +from oslo_i18n import _message +from oslo_i18n.tests import fakes +from oslo_i18n.tests import utils LOG = logging.getLogger(__name__) diff --git a/oslo_i18n/tests/test_public_api.py b/oslo_i18n/tests/test_public_api.py new file mode 100644 index 0000000..d419105 --- /dev/null +++ b/oslo_i18n/tests/test_public_api.py @@ -0,0 +1,44 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""A few tests that use the public API to ensure the imports work. +""" + +import unittest + +import mock + +import oslo_i18n +from oslo_i18n import _lazy + + +class PublicAPITest(unittest.TestCase): + + def test_create_factory(self): + oslo_i18n.TranslatorFactory('domain') + + def test_install(self): + with mock.patch('six.moves.builtins'): + oslo_i18n.install('domain') + + def test_get_available_languages(self): + oslo_i18n.get_available_languages('domains') + + def test_toggle_lazy(self): + original = _lazy.USE_LAZY + try: + oslo_i18n.enable_lazy(True) + oslo_i18n.enable_lazy(False) + finally: + oslo_i18n.enable_lazy(original) + + def test_translate(self): + oslo_i18n.translate(u'string') diff --git a/tests/test_translate.py b/oslo_i18n/tests/test_translate.py similarity index 92% rename from tests/test_translate.py rename to oslo_i18n/tests/test_translate.py index 9e378dd..335b28c 100644 --- a/tests/test_translate.py +++ b/oslo_i18n/tests/test_translate.py @@ -19,10 +19,10 @@ from __future__ import unicode_literals import mock from oslotest import base as test_base -from oslo.i18n import _message -from oslo.i18n import _translate -from tests import fakes -from tests import utils +from oslo_i18n import _message +from oslo_i18n import _translate +from oslo_i18n.tests import fakes +from oslo_i18n.tests import utils class TranslateTest(test_base.BaseTestCase): diff --git a/tests/utils.py b/oslo_i18n/tests/utils.py similarity index 100% rename from tests/utils.py rename to oslo_i18n/tests/utils.py diff --git a/setup.cfg b/setup.cfg index 01f2acf..2054691 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,7 +22,7 @@ classifier = [files] packages = oslo - oslo.i18n + oslo_i18n namespace_packages = oslo diff --git a/tests/test_fixture.py b/tests/test_fixture.py index 66f9994..ece05ee 100644 --- a/tests/test_fixture.py +++ b/tests/test_fixture.py @@ -15,8 +15,8 @@ from oslotest import base as test_base import six -from oslo.i18n import _message from oslo.i18n import fixture +from oslo_i18n import _message class FixtureTest(test_base.BaseTestCase): diff --git a/tests/test_handler.py b/tests/test_handler.py index 01cc8cc..539eb72 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -20,9 +20,9 @@ import mock from oslotest import base as test_base import six -from oslo.i18n import _message from oslo.i18n import log as i18n_log -from tests import fakes +from oslo_i18n import _message +from oslo_i18n.tests import fakes LOG = logging.getLogger(__name__) diff --git a/tests/test_public_api.py b/tests/test_public_api.py index b14c218..afe9d34 100644 --- a/tests/test_public_api.py +++ b/tests/test_public_api.py @@ -17,7 +17,7 @@ import unittest import mock from oslo import i18n -from oslo.i18n import _lazy +from oslo_i18n import _lazy class PublicAPITest(unittest.TestCase): diff --git a/tests/test_warning.py b/tests/test_warning.py new file mode 100644 index 0000000..f9aa54f --- /dev/null +++ b/tests/test_warning.py @@ -0,0 +1,61 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import imp +import os +import warnings + +import mock +from oslotest import base as test_base +import six + + +class DeprecationWarningTest(test_base.BaseTestCase): + + @mock.patch('warnings.warn') + def test_warning(self, mock_warn): + import oslo.i18n + imp.reload(oslo.i18n) + self.assertTrue(mock_warn.called) + args = mock_warn.call_args + self.assertIn('oslo_i18n', args[0][0]) + self.assertIn('deprecated', args[0][0]) + self.assertTrue(issubclass(args[0][1], DeprecationWarning)) + + def test_real_warning(self): + with warnings.catch_warnings(record=True) as warning_msgs: + warnings.resetwarnings() + warnings.simplefilter('always', DeprecationWarning) + import oslo.i18n + + # Use a separate function to get the stack level correct + # so we know the message points back to this file. This + # corresponds to an import or reload, which isn't working + # inside the test under Python 3.3. That may be due to a + # difference in the import implementation not triggering + # warnings properly when the module is reloaded, or + # because the warnings module is mostly implemented in C + # and something isn't cleanly resetting the global state + # used to track whether a warning needs to be + # emitted. Whatever the cause, we definitely see the + # warnings.warn() being invoked on a reload (see the test + # above) and warnings are reported on the console when we + # run the tests. A simpler test script run outside of + # testr does correctly report the warnings. + def foo(): + oslo.i18n.deprecated() + + foo() + self.assertEqual(1, len(warning_msgs)) + msg = warning_msgs[0] + self.assertIn('oslo_i18n', six.text_type(msg.message)) + self.assertEqual('test_warning.py', os.path.basename(msg.filename)) diff --git a/tox.ini b/tox.ini index 036014e..5306c20 100644 --- a/tox.ini +++ b/tox.ini @@ -38,4 +38,4 @@ exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,__in [hacking] import_exceptions = - oslo.i18n._i18n._ + oslo_i18n._i18n._