Why flask can suck

26. August 2012. Tagged python, flask.

I have devided to settle on flask for my newest project on work. So far, so good.

I have worked for quite a long time happily on the project and nothing got in my way. I was literally absolutely happy. I could just work. Finally no php pain, no drupal pain, nothing.

Then I decided (I think for no reason) to upgrade to version 3 of celery. I did that and everything looked okay (I will so write tests for celery tasks in the future). After I did som developement, I finally noticed that there were no notifications sent at all. This is relevant, as all mailing stuff is handled by celery tasks.

So I checked that out and it was a complete mess. Missing contexts everywhere, nothing was working as expected. I spend two days with fixing one issue after the other. This was mainly due to the following reasons:

1. url_for and SERVER_NAME

Flask’s url_for depends on the request context, or the SERVER_NAME setting and application context. I first decided to go for the SERVER_NAME setting which worked fine in the shell (I just specifically ran the tasks). I deployed, and oh wonder, on the server everything was a mess. Apparently SERVER_NAME has the potential for totally breaking all assets and stuff, what it immediately did. So I had to write a decorator executing my tasks with request context:

1
2
3
4
5
6
7
8
9
10
class with_request_context(object):
	def __init__(self, f):
		self.f = f
		self.app = app
		self.request = '/'
		self.__name__ = f.__name__ + 'with_request_context'

	def __call__(self, *args):
		with self.app.test_request_context(self.request):
			return self.f(*args)

So, with this little decorator applied (and @celery.task applied afterwards) it finally works. I still had to have another snippet to generate absolute urls, as I did not set SERVER_NAME:

1
2
3
4
5
6
7
def hosturl(url):
	host = www.config['HOST']
	if host.endswith('/'):
		host = host[0:-1]
	if url.startswith('/'):
		url = url[1:]
	return '/'.join([host, url])

Problem 1: Fixed.

2. Flask-Babel’s @babel.localeselector

Well, guess what, my locale selection method relied on current_user and broke completely after the upgrade. It just returned only english before (which I did not notice) but afterwards only english. The problem was, there is no chance to pass arguments to flask-babel to manually determine the locale you want. The only idea was to change the default language for each user, which is nasty. So I basically forked flask-babel functionality with some extras. The result is here:

1
2
3
4
5
6
7
8
from babel import support
def man_gettext(string, domain, **variables):
	translations = support.Translations.load(os.path.join(www.root_path, 'translations'))
	return translations.dgettext(domain, string) % variables

def man_lazy_gettext(string, domain, **variables):
	from speaklater import make_lazy_string
	return make_lazy_string(man_gettext, string, domain, **variables)

These are the two pedants to gettext and lazy_gettext where you can decide which locale you want. Just do not forget to include the commands with the -k switch, when you want to search for strings to be translated with pybabel.

3. Lazy strings and E-Mails

I have absolutely no idea what happened after I had figured all this out, but what I believe has happened is this: Flask-Mail and I tried simplemail, too, started to reject lazy strings. Do not ask me why, I have no idea at all. When they tried to assert, that there was a body, speaklater went into an infinite getattr loop, apparently to retrieve the actual string.

To be honest, I was completely helpless with this. I just decided to drop translated emails for now, as gettext() did not offer any more, than a plain english string would.

If anyone has any idea or ever stumbles across the problem and solves it, please let me know. I will definitely try to work this out another time.