From 8c966705e56fe5102c960dd1f467429b29249e78 Mon Sep 17 00:00:00 2001 From: Quentin Roy Date: Sat, 20 Oct 2012 19:11:44 +0200 Subject: [PATCH] Add compatibility with flask extensions for flask versions > 0.7 --- budget/api.py | 7 ++- budget/flaskext_compat.py | 127 ++++++++++++++++++++++++++++++++++++++ budget/forms.py | 8 ++- budget/models.py | 7 ++- budget/run.py | 7 ++- budget/web.py | 9 ++- 6 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 budget/flaskext_compat.py diff --git a/budget/api.py b/budget/api.py index ec664b6b..6edc8b9b 100644 --- a/budget/api.py +++ b/budget/api.py @@ -1,6 +1,11 @@ # -*- coding: utf-8 -*- + +# activate flask.ext compatibility module +import flaskext_compat +flaskext_compat.activate() + from flask import Blueprint, request -from flask_rest import RESTResource, need_auth +from flask.ext.rest import RESTResource, need_auth from models import db, Project, Person, Bill from forms import (ProjectForm, EditProjectForm, MemberForm, diff --git a/budget/flaskext_compat.py b/budget/flaskext_compat.py new file mode 100644 index 00000000..0a6afcd7 --- /dev/null +++ b/budget/flaskext_compat.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- + +""" + flaskext_compat + ~~~~~~~~~~~~~~~ + + Implements the ``flask.ext`` virtual package for versions of Flask + older than 0.7. This module is a noop if Flask 0.8 was detected. + + Usage:: + + import flaskext_compat + flaskext_compat.activate() + from flask.ext import foo + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +import sys +import os +import imp + + +class ExtensionImporter(object): + """This importer redirects imports from this submodule to other locations. + This makes it possible to transition from the old flaskext.name to the + newer flask_name without people having a hard time. + """ + + def __init__(self, module_choices, wrapper_module): + self.module_choices = module_choices + self.wrapper_module = wrapper_module + self.prefix = wrapper_module + '.' + self.prefix_cutoff = wrapper_module.count('.') + 1 + + def __eq__(self, other): + return self.__class__.__module__ == other.__class__.__module__ and \ + self.__class__.__name__ == other.__class__.__name__ and \ + self.wrapper_module == other.wrapper_module and \ + self.module_choices == other.module_choices + + def __ne__(self, other): + return not self.__eq__(other) + + def install(self): + sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self] + + def find_module(self, fullname, path=None): + if fullname.startswith(self.prefix): + return self + + def load_module(self, fullname): + if fullname in sys.modules: + return sys.modules[fullname] + modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] + for path in self.module_choices: + realname = path % modname + try: + __import__(realname) + except ImportError: + exc_type, exc_value, tb = sys.exc_info() + # since we only establish the entry in sys.modules at the + # end this seems to be redundant, but if recursive imports + # happen we will call into the move import a second time. + # On the second invocation we still don't have an entry for + # fullname in sys.modules, but we will end up with the same + # fake module name and that import will succeed since this + # one already has a temporary entry in the modules dict. + # Since this one "succeeded" temporarily that second + # invocation now will have created a fullname entry in + # sys.modules which we have to kill. + sys.modules.pop(fullname, None) + + # If it's an important traceback we reraise it, otherwise + # we swallow it and try the next choice. The skipped frame + # is the one from __import__ above which we don't care about. + if self.is_important_traceback(realname, tb): + raise exc_type, exc_value, tb.tb_next + continue + module = sys.modules[fullname] = sys.modules[realname] + if '.' not in modname: + setattr(sys.modules[self.wrapper_module], modname, module) + return module + raise ImportError('No module named %s' % fullname) + + def is_important_traceback(self, important_module, tb): + """Walks a traceback's frames and checks if any of the frames + originated in the given important module. If that is the case then we + were able to import the module itself but apparently something went + wrong when the module was imported. (Eg: import of an import failed). + """ + while tb is not None: + if self.is_important_frame(important_module, tb): + return True + tb = tb.tb_next + return False + + def is_important_frame(self, important_module, tb): + """Checks a single frame if it's important.""" + g = tb.tb_frame.f_globals + if '__name__' not in g: + return False + + module_name = g['__name__'] + + # Python 2.7 Behavior. Modules are cleaned up late so the + # name shows up properly here. Success! + if module_name == important_module: + return True + + # Some python versions will clean up modules so early that the + # module name at that point is no longer set. Try guessing from + # the filename then. + filename = os.path.abspath(tb.tb_frame.f_code.co_filename) + test_string = os.path.sep + important_module.replace('.', os.path.sep) + return test_string + '.py' in filename or \ + test_string + os.path.sep + '__init__.py' in filename + + +def activate(): + import flask + ext_module = imp.new_module('flask.ext') + ext_module.__path__ = [] + flask.ext = sys.modules['flask.ext'] = ext_module + importer = ExtensionImporter(['flask_%s', 'flaskext.%s'], 'flask.ext') + importer.install() diff --git a/budget/forms.py b/budget/forms.py index 4a810dee..bbf4b10d 100644 --- a/budget/forms.py +++ b/budget/forms.py @@ -1,7 +1,11 @@ -from flaskext.wtf import DateField, DecimalField, Email, Form, PasswordField, \ +# load flask.ext compatibility module +import flaskext_compat +flaskext_compat.activate() + +from flask.ext.wtf import DateField, DecimalField, Email, Form, PasswordField, \ Required, SelectField, SelectMultipleField, SubmitField, TextAreaField, \ TextField, ValidationError -from flaskext.babel import lazy_gettext as _ +from flask.ext.babel import lazy_gettext as _ from flask import request from wtforms.widgets import html_params diff --git a/budget/models.py b/budget/models.py index cac96643..5b3e486d 100644 --- a/budget/models.py +++ b/budget/models.py @@ -1,7 +1,10 @@ -from collections import defaultdict +# activate flask.ext compatibility module +import flaskext_compat +flaskext_compat.activate() +from collections import defaultdict from datetime import datetime -from flask_sqlalchemy import SQLAlchemy, BaseQuery +from flask.ext.sqlalchemy import SQLAlchemy, BaseQuery from flask import g from sqlalchemy import orm diff --git a/budget/run.py b/budget/run.py index 2c2cab53..b3ca8f63 100644 --- a/budget/run.py +++ b/budget/run.py @@ -1,5 +1,10 @@ from flask import Flask, g, request, session -from flaskext.babel import Babel + +# load flask.ext compatibility module +import flaskext_compat +flaskext_compat.activate() + +from flask.ext.babel import Babel from raven.contrib.flask import Sentry from web import main, db, mail diff --git a/budget/web.py b/budget/web.py index 489874c7..45d5e66b 100644 --- a/budget/web.py +++ b/budget/web.py @@ -11,8 +11,13 @@ and `add_project_id` for a quick overview) from flask import Blueprint, current_app, flash, g, redirect, \ render_template, request, session, url_for -from flaskext.mail import Mail, Message -from flaskext.babel import get_locale, gettext as _ + +# activate flask.ext compatibility module +import flaskext_compat +flaskext_compat.activate() + +from flask.ext.mail import Message, Mail +from flask.ext.babel import get_locale, gettext as _ from smtplib import SMTPRecipientsRefused import werkzeug