Question

Background: I'm one of the primary contributors to gae-boilerplate, an open source boilerplate project to help users jump start development on google app engine with webapp2 and the latest features. The latest feature i've worked on is i18n using Babel and gaepytz which is where the problem described below lies.

Problem: Translation is working with gettext but for translation of form error messages produced in our handlers and wtforms classes i need lazy_gettext as far as i'm aware. Lazy_gettext is not working when i call from webapp2_extras.i18n import lazy_gettext as _ in handlers.py. I get the below fatal error message when loading the page. It seems like the Babel Lazy proxy objects are created but then they are not invoked to render localized strings before being passed to a json serializer in the google app engine server code. I've looked through the babel sites, webapp2 docs (which i wish were more thorough), other frameworks, and some of the google code but I have not been able to figure out what i'm missing.

Error Message:

Traceback (most recent call last):
File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2\webapp2.py", line 1536, in __call__
  rv = self.handle_exception(request, response, e)
File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2\webapp2.py", line 1530, in __call__
  rv = self.router.dispatch(request, response)
File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2\webapp2.py", line 1278, in default_dispatcher
  return route.handler_adapter(request, response)
File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2\webapp2.py", line 1102, in __call__
  return handler.dispatch()
File "H:\jesse\python_dev\workspace\gae-boilerplate\lib\basehandler.py", line 54, in dispatch
  self.session_store.save_sessions(self.response)
File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2\webapp2_extras\sessions.py", line 420, in save_sessions
  session.save_session(response)
File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2\webapp2_extras\sessions.py", line 205, in save_session
  response, self.name, dict(self.session), **self.session_args)
File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2\webapp2_extras\sessions.py", line 423, in save_secure_cookie
  value = self.serializer.serialize(name, value)
File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2\webapp2_extras\securecookie.py", line 47, in serialize
  value = self._encode(value)
File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2\webapp2_extras\securecookie.py", line 92, in _encode
  return json.b64encode(value)
File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2\webapp2_extras\json.py", line 84, in b64encode
  return base64.b64encode(encode(value, *args, **kwargs))
File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2\webapp2_extras\json.py", line 55, in encode
  return json.dumps(value, *args, **kwargs).replace("</", "<\\/")
File "C:\Program Files (x86)\Google\google_appengine\lib\simplejson\simplejson\__init__.py", line 272, in dumps
  use_decimal=use_decimal, **kw).encode(obj)
File "C:\Program Files (x86)\Google\google_appengine\lib\simplejson\simplejson\encoder.py", line 216, in encode
  chunks = list(chunks)
File "C:\Program Files (x86)\Google\google_appengine\lib\simplejson\simplejson\encoder.py", line 485, in _iterencode
  for chunk in _iterencode_dict(o, _current_indent_level):
File "C:\Program Files (x86)\Google\google_appengine\lib\simplejson\simplejson\encoder.py", line 459, in _iterencode_dict
  for chunk in chunks:
File "C:\Program Files (x86)\Google\google_appengine\lib\simplejson\simplejson\encoder.py", line 380, in _iterencode_list
  for chunk in chunks:
File "C:\Program Files (x86)\Google\google_appengine\lib\simplejson\simplejson\encoder.py", line 380, in _iterencode_list
  for chunk in chunks:
File "C:\Program Files (x86)\Google\google_appengine\lib\simplejson\simplejson\encoder.py", line 495, in _iterencode
  o = _default(o)
File "C:\Program Files (x86)\Google\google_appengine\lib\simplejson\simplejson\encoder.py", line 190, in default
  raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <babel.support.LazyProxy object at 0x00000000084E81F8> is not JSON serializable

Environment details: All details about the version of python, webapp2, babel, etc can be found by scrolling down to the readme at github.com/coto/gae-boilerplate. I'm running the latest google app engine sdk 1.7.0

How to reproduce:

  1. Download the code from https://github.com/coto/gae-boilerplate
  2. Replace from webapp2_extras.i18n import gettext as _ in web/handlers.py with from webapp2_extras.i18n import lazy_gettext as _
  3. Run using the app engine development server (instructions provided at https://developers.google.com/appengine/docs/python/tools/devserver)
  4. sign up or login then logout (localhost:8080/logout/). The act of logging out will produce a flash message indicating you have logged out which will trigger the error as it is produced in the logout handler with a lazy_gettext wrapped message.

Any help would be greatly appreciated and would undoubtedly help other users of this project and webapp2. Also if you do look through the code on github, any best practices tips would be a bonus. Thank you!

Was it helpful?

Solution

This is really old but maybe someone else has the same issue.

Lazy gettext wraps the translation process for later so it isn't a real string but a different type of object. Some json libs checks the type of the expresion you are trying to jsonify so it gives you an exception. To fix this you can trigger the translation using the unicode builtin.

Ex.

a = lazy_gettext("wololoh")
jsonify({'msg': a}) # this will throw an exception
jsonify({'msg': unicode(a)}) # this should work correctly

hope it's useful

OTHER TIPS

Babel has a custom class called LazyProxy which basically just wraps a function and only calls it when it really needs the function's return value. The proxy tries to be as transparent as possible but I assume simplejson checks the actual class object and that's where it fails.

I'm not sure if simplejson uses a protocol to ask unknown classes for a serializable version of the instance (then we could implement that in the LazyProxy).

If you can somehow change your libraries, you could create also a custom serializer for LazyProxy instances (which should be really simple). Another option is to find the LazyProxy instance and use 'proxy.value' to get the proxied value (which I assume is json-serializable).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top