Exemple #1
0
 def test_retry_fetch_translations(self):
     cds_host = 'https://some.host'
     cds_handler = CDSHandler(
         ['el', 'en'],
         'some_token',
         host=cds_host,
     )
     responses.add(responses.GET, cds_host + '/content/el', status=500)
     responses.add(responses.GET, cds_host + '/content/el', status=202)
     responses.add(responses.GET,
                   cds_host + '/content/el',
                   json={'data': {
                       'source': {
                           'string': "translation"
                       }
                   }},
                   status=200)
     translations = cds_handler.fetch_translations('el')
     assert (translations == {
         'el': (True, {
             'source': {
                 'string': "translation"
             }
         })
     })
Exemple #2
0
 def test_invalidate_no_secret(self):
     cds_handler = CDSHandler(
         ['el', 'en'],
         'some_token',
     )
     with pytest.raises(Exception):
         cds_handler.invalidate_cache(False)
Exemple #3
0
    def test_get_headers(self):
        cds_host = 'https://some.host'
        cds_handler = CDSHandler(['el', 'en'],
                                 'some_token',
                                 secret='some_secret',
                                 host=cds_host)
        assert cds_handler._get_headers() == {
            'Authorization': 'Bearer some_token',
            'Accept-Encoding': 'gzip',
            'Accept-Version': 'v2',
            'X-NATIVE-SDK': 'python',
        }

        assert cds_handler._get_headers(use_secret=True) == {
            'Authorization': 'Bearer some_token:some_secret',
            'Accept-Encoding': 'gzip',
            'Accept-Version': 'v2',
            'X-NATIVE-SDK': 'python',
        }

        headers = cds_handler._get_headers(use_secret=True, etag='something')
        assert headers == {
            'Authorization': 'Bearer some_token:some_secret',
            'Accept-Encoding': 'gzip',
            'Accept-Version': 'v2',
            'X-NATIVE-SDK': 'python',
            'If-None-Match': 'something',
        }
Exemple #4
0
 def test_retry_fetch_languages(self):
     cds_host = 'https://some.host'
     cds_handler = CDSHandler(
         ['el', 'en'],
         'some_token',
         host=cds_host,
     )
     responses.add(responses.GET, cds_host + '/languages', status=500)
     responses.add(responses.GET, cds_host + '/languages', status=202)
     responses.add(responses.GET,
                   cds_host + '/languages',
                   json={
                       'data': [{
                           'code': "el"
                       }, {
                           'code': "en"
                       }],
                       'meta': {
                           'some_key': "some_value"
                       }
                   },
                   status=200)
     languages_response = cds_handler.fetch_languages()
     assert self._lang_lists_equal(languages_response, [{
         'code': 'el'
     }, {
         'code': 'en'
     }])
Exemple #5
0
 def test_push_source_strings_no_secret(self):
     cds_handler = CDSHandler(
         ['el', 'en'],
         'some_token',
     )
     with pytest.raises(Exception):
         cds_handler.push_source_strings([], False)
Exemple #6
0
    def init(
        self, languages, token, secret=None, cds_host=None,
        missing_policy=None, error_policy=None
    ):
        """Create an instance of the core framework class.

        Also warms up the cache by fetching the translations from the CDS.

        :param list languages: a list of language codes for the languages
            configured in the application
        :param str token: the API token to use for connecting to the CDS
        :param str secret: the additional secret to use for pushing source
            content
        :param str cds_host: an optional host for the Content Delivery Service,
            defaults to the host provided by Transifex
        :param AbstractRenderingPolicy missing_policy: an optional policy
            to use for returning strings when a translation is missing
        :param AbstractErrorPolicy error_policy: an optional policy
            to determine how to handle rendering errors
        """
        self._languages = languages
        self._cache = MemoryCache()
        self._missing_policy = missing_policy or SourceStringPolicy()
        self._error_policy = error_policy or SourceStringErrorPolicy()
        self._cds_handler = CDSHandler(
            self._languages, token, secret=secret, host=cds_host
        )
        self.initialized = True
Exemple #7
0
    def test_push_source_strings(self, patched_logger):
        cds_host = 'https://some.host'
        cds_handler = CDSHandler(['el', 'en'],
                                 'some_token',
                                 secret='some_secret',
                                 host=cds_host)

        # test push no content
        responses.add(responses.POST,
                      cds_host + '/content/',
                      status=200,
                      json={'data': []})

        cds_handler.push_source_strings([], False)
        assert patched_logger.error.call_count == 0

        # test push with content
        responses.add(responses.POST,
                      cds_host + '/content/',
                      status=200,
                      json={'data': []})

        source_string = SourceString('some_string')
        cds_handler.push_source_strings([source_string], False)
        assert patched_logger.error.call_count == 0
        responses.reset()

        # test wrong data format
        responses.add(responses.POST,
                      cds_host + '/content/',
                      status=422,
                      json={
                          "status":
                          422,
                          "message":
                          "Invalid Payload",
                          "details": [{
                              "message": "\"string\" is required",
                              "path": ["some_key1", "string"],
                              "type": "any.required",
                              "context": {
                                  "key": "string",
                                  "label": "string"
                              }
                          }]
                      })
        # we don't care about the payload this time, just want to
        # see how the service handles the errors
        cds_handler.push_source_strings([], False)
        # The actual error message differs between Python 2 and Python 3
        messages = [
            'Error pushing source strings to CDS: UnknownError '
            '(`422 Client Error: {err} for url: '
            'https://some.host/content/`)'.format(err=x)
            for x in ('Unprocessable Entity', 'None')
        ]
        assert patched_logger.error.call_args[0][0] in messages
Exemple #8
0
    def test_invalidate(self, patched_logger):
        cds_host = 'https://some.host'
        cds_handler = CDSHandler(['el', 'en'],
                                 'some_token',
                                 secret='some_secret',
                                 host=cds_host)

        # test invalidate
        responses.add(responses.POST,
                      cds_host + '/invalidate',
                      status=200,
                      json={'data': {
                          'count': 5
                      }})

        cds_handler.invalidate_cache(False)
        assert patched_logger.error.call_count == 0

        # test purge
        responses.add(responses.POST,
                      cds_host + '/purge',
                      status=200,
                      json={'data': {
                          'count': 5
                      }})

        cds_handler.invalidate_cache(True)
        assert patched_logger.error.call_count == 0
        responses.reset()

        # test response error
        responses.add(responses.POST,
                      cds_host + '/invalidate',
                      status=422,
                      json={
                          "status": 422,
                      })
        # we don't care about the payload this time, just want to
        # see how the service handles the errors
        cds_handler.invalidate_cache(False)
        # The actual error message differs between Python 2 and Python 3
        messages = [
            'Error invalidating CDS: UnknownError '
            '(`422 Client Error: {err} for url: '
            'https://some.host/invalidate`)'.format(err=x)
            for x in ('Unprocessable Entity', 'None')
        ]
        assert patched_logger.error.call_args[0][0] in messages
Exemple #9
0
    def test_fetch_translations_etags_management(self, patched_logger):

        cds_host = 'https://some.host'
        cds_handler = CDSHandler(['el', 'en'], 'some_token', host=cds_host)

        # add response for languages
        responses.add(responses.GET,
                      cds_host + '/languages',
                      json={
                          "data": [
                              {
                                  "code": "el",
                              },
                              {
                                  "code": "en",
                              },
                          ],
                          "meta": {
                              "some_key": "some_value"
                          }
                      },
                      status=200)

        # add response for translations
        responses.add(responses.GET,
                      cds_host + '/content/el',
                      json={
                          'data': {
                              'key1': {
                                  'string': 'key1_el'
                              },
                              'key2': {
                                  'string': 'key2_el'
                              },
                          },
                          'meta': {
                              "some_key": "some_value"
                          }
                      },
                      status=200,
                      headers={'ETag': 'some_unique_tag_is_here'})

        responses.add(
            responses.GET,
            cds_host + '/content/en',
            # whatever, we don't care about the content of json repsone atm.
            json={},
            status=304)

        resp = cds_handler.fetch_translations()
        assert resp == {
            'el': (True, {
                'key1': {
                    'string': 'key1_el'
                },
                'key2': {
                    'string': 'key2_el'
                },
            }),
            'en': (False, {})
        }
        assert cds_handler.etags.get('el') == 'some_unique_tag_is_here'
Exemple #10
0
    def test_fetch_languages(self, patched_logger):
        cds_host = 'https://some.host'
        cds_handler = CDSHandler(['el', 'en'], 'some_token', host=cds_host)

        # correct response
        responses.add(responses.GET,
                      cds_host + '/languages',
                      json={
                          "data": [
                              {
                                  "code": "el",
                              },
                              {
                                  "code": "en",
                              },
                          ],
                          "meta": {
                              "some_key": "some_value"
                          }
                      },
                      status=200)

        languages_response = cds_handler.fetch_languages()
        assert self._lang_lists_equal(languages_response, [{
            'code': 'el'
        }, {
            'code': 'en'
        }])
        assert patched_logger.error.call_count == 0
        responses.reset()

        # wrong payload structure
        responses.add(responses.GET,
                      cds_host + '/languages',
                      json={
                          "wrong_key": [
                              {
                                  "code": "el",
                              },
                              {
                                  "code": "en",
                              },
                          ],
                          "meta": {
                              "some_key": "some_value"
                          }
                      },
                      status=200)

        assert cds_handler.fetch_languages() == []
        patched_logger.error.assert_called_with(
            'Error retrieving languages from CDS: Malformed response')
        responses.reset()

        # bad request
        responses.add(responses.GET,
                      cds_host + '/languages',
                      json={
                          "data": [
                              {
                                  "code": "el",
                              },
                              {
                                  "code": "en",
                              },
                          ],
                          "meta": {
                              "some_key": "some_value"
                          }
                      },
                      status=400)

        assert cds_handler.fetch_languages() == []
        patched_logger.error.assert_called_with(
            'Error retrieving languages from CDS: UnknownError (`400 Client '
            'Error: Bad Request for url: https://some.host/languages`)')
        responses.reset()

        # unauthorized
        responses.add(responses.GET,
                      cds_host + '/languages',
                      json={
                          "data": [
                              {
                                  "code": "el",
                              },
                              {
                                  "code": "en",
                              },
                          ],
                          "meta": {
                              "some_key": "some_value"
                          }
                      },
                      status=403)

        assert cds_handler.fetch_languages() == []
        patched_logger.error.assert_called_with(
            'Error retrieving languages from CDS: UnknownError (`403 Client '
            'Error: Forbidden for url: https://some.host/languages`)')
        responses.reset()

        # connection error
        assert cds_handler.fetch_languages() == []
        patched_logger.error.assert_called_with(
            'Error retrieving languages from CDS: ConnectionError')
        responses.reset()
Exemple #11
0
    def test_fetch_translations(self, patched_logger):
        cds_host = 'https://some.host'
        cds_handler = CDSHandler(['el', 'en', 'fr'],
                                 'some_token',
                                 host=cds_host)

        # add response for languages
        responses.add(responses.GET,
                      cds_host + '/languages',
                      json={
                          "data": [
                              {
                                  "code": "el",
                              },
                              {
                                  "code": "en",
                              },
                              {
                                  "code": "fr",
                              },
                          ],
                          "meta": {
                              "some_key": "some_value"
                          }
                      },
                      status=200)

        # add response for translations
        responses.add(responses.GET,
                      cds_host + '/content/el',
                      json={
                          'data': {
                              'key1': {
                                  'string': 'key1_el'
                              },
                              'key2': {
                                  'string': 'key2_el'
                              },
                          },
                          'meta': {
                              "some_key": "some_value"
                          }
                      },
                      status=200)

        responses.add(responses.GET,
                      cds_host + '/content/en',
                      json={
                          'data': {
                              'key1': {
                                  'string': 'key1_en'
                              },
                              'key2': {
                                  'string': 'key2_en'
                              },
                          },
                          'meta': {}
                      },
                      status=200)

        # add response bad status response for a language here
        responses.add(responses.GET, cds_host + '/content/fr', status=404)

        resp = cds_handler.fetch_translations()
        assert resp == {
            'el': (True, {
                'key1': {
                    'string': 'key1_el'
                },
                'key2': {
                    'string': 'key2_el'
                },
            }),
            'en': (True, {
                'key1': {
                    'string': 'key1_en'
                },
                'key2': {
                    'string': 'key2_en'
                },
            }),
            'fr': (False, {})  # that is due to the error status in response
        }

        responses.reset()

        # test fetch_languages fails with connection error
        responses.add(responses.GET, cds_host + '/languages', status=500)
        resp = cds_handler.fetch_translations()
        assert resp == {}

        patched_logger.error.assert_called_with(
            'Error retrieving languages from CDS: UnknownError '
            '(`500 Server Error: Internal Server Error for url: '
            'https://some.host/languages`)')
        responses.reset()
        patched_logger.reset_mock()

        # test language code
        responses.add(responses.GET,
                      cds_host + '/content/el',
                      json={
                          'data': {
                              'key1': {
                                  'string': 'key1_el'
                              },
                              'key2': {
                                  'string': 'key2_el'
                              },
                          },
                          'meta': {
                              "some_key": "some_value"
                          }
                      },
                      status=200)

        resp = cds_handler.fetch_translations(language_code='el')
        assert resp == {
            'el': (True, {
                'key1': {
                    'string': 'key1_el'
                },
                'key2': {
                    'string': 'key2_el'
                },
            })
        }
        responses.reset()
        assert patched_logger.error.call_count == 0

        # test connection_error
        resp = cds_handler.fetch_translations(language_code='el')
        patched_logger.error.assert_called_with(
            'Error retrieving translations from CDS: ConnectionError')
        assert resp == {'el': (False, {})}
Exemple #12
0
class TxNative(object):
    """The main class of the framework, responsible for orchestrating all
    behavior."""

    def __init__(self):
        # The class uses an untypical initialization scheme, defining
        # an init() method, instead of initializing inside the constructor
        # This is necessary for allowing it to be initialized by its clients
        # with proper arguments, while at the same time being very easy
        # to import and use a single "global" instance
        self._cache = None
        self._languages = []
        self._missing_policy = None
        self._cds_handler = None
        self.initialized = False

    def init(
        self, languages, token, secret=None, cds_host=None,
        missing_policy=None, error_policy=None
    ):
        """Create an instance of the core framework class.

        Also warms up the cache by fetching the translations from the CDS.

        :param list languages: a list of language codes for the languages
            configured in the application
        :param str token: the API token to use for connecting to the CDS
        :param str secret: the additional secret to use for pushing source
            content
        :param str cds_host: an optional host for the Content Delivery Service,
            defaults to the host provided by Transifex
        :param AbstractRenderingPolicy missing_policy: an optional policy
            to use for returning strings when a translation is missing
        :param AbstractErrorPolicy error_policy: an optional policy
            to determine how to handle rendering errors
        """
        self._languages = languages
        self._cache = MemoryCache()
        self._missing_policy = missing_policy or SourceStringPolicy()
        self._error_policy = error_policy or SourceStringErrorPolicy()
        self._cds_handler = CDSHandler(
            self._languages, token, secret=secret, host=cds_host
        )
        self.initialized = True

    def translate(
        self, source_string, language_code, is_source=False,
        _context=None, escape=True, params=None
    ):
        """Translate the given string to the provided language.

        :param unicode source_string: the source string to get the translation
            for e.g. 'Order: {num, plural, one {A table} other {{num} tables}}'
        :param str language_code: the language to translate to
        :param bool is_source: a boolean indicating whether `translate`
            is being used for the source language
        :param unicode _context: an optional context that accompanies
            the string
        :param bool escape: if True, the returned string will be HTML-escaped,
            otherwise it won't
        :param dict params: optional parameters to replace any placeholders
            found in the translation string
        :return: the rendered string
        :rtype: unicode
        """

        if params is None:
            params = {}

        self._check_initialization()

        translation_template = self.get_translation(source_string,
                                                    language_code,
                                                    _context,
                                                    is_source)

        return self.render_translation(translation_template,
                                       params,
                                       source_string,
                                       language_code,
                                       escape)

    def get_translation(self, source_string, language_code, _context,
                        is_source=False):
        """ Try to retrieve the translation.

            A translation is a serialized source_string with ICU format
            support, e.g.
            '{num, plural, one {Ένα τραπέζι} other {{num} τραπέζια}}'
        """

        if is_source:
            translation_template = source_string
        else:
            pluralized, plurals = parse_plurals(source_string)
            key = generate_key(string=source_string, context=_context)
            translation_template = self._cache.get(key, language_code)
            if (translation_template is not None and pluralized and
                    translation_template.startswith('{???')):
                variable_name = source_string[1:source_string.index(',')].\
                    strip()
                translation_template = ('{' +
                                        variable_name +
                                        translation_template[4:])
        return translation_template

    def render_translation(self, translation_template, params, source_string,
                           language_code, escape=False):
        """ Replace the variables in the ICU translation """

        try:
            return StringRenderer.render(
                source_string=source_string,
                string_to_render=translation_template,
                language_code=language_code,
                escape=escape,
                missing_policy=self._missing_policy,
                params=params,
            )
        except Exception:
            return self._error_policy.get(
                source_string=source_string,
                translation=translation_template,
                language_code=language_code,
                escape=escape, params=params,
            )

    def fetch_translations(self):
        """Fetch fresh content from the CDS."""
        self._check_initialization()
        self._cache.update(self._cds_handler.fetch_translations())

    def push_source_strings(self, strings, purge=False):
        """Push the given source strings to the CDS.

        :param list strings: a list of SourceString objects
        :param bool purge: True deletes destination source content not included
                           in pushed content.
                           False appends the pushed content to destination
                           source content.
        :return: a tuple containing the status code and the content of the
            response
        :rtype: tuple
        """
        self._check_initialization()
        response = self._cds_handler.push_source_strings(strings, purge)
        return response.status_code, json.loads(response.content)

    def _check_initialization(self):
        """Raise an exception if the class has not been initialized.

        :raise NotInitializedError: if the class hasn't been initialized
        """
        if not self.initialized:
            raise NotInitializedError(
                'TxNative is not initialized, make sure you call init() first.'
            )