Permalink
Browse files

Started work on an upgrade script for blueprints

  • Loading branch information...
1 parent 1f31ec4 commit 6dae36f94db40d242fee73d10467a25cfa90b580 @mitsuhiko mitsuhiko committed Jun 5, 2011
Showing with 178 additions and 4 deletions.
  1. +7 −4 flask/helpers.py
  2. +171 −0 scripts/flask-07-upgrade.py
View
11 flask/helpers.py
@@ -168,15 +168,18 @@ def url_for(endpoint, **values):
:param _external: if set to `True`, an absolute URL is generated.
"""
ctx = _request_ctx_stack.top
+ blueprint_name = request.blueprint
if not ctx.request._is_old_module:
if endpoint[:1] == '.':
- endpoint = request.blueprint + endpoint
+ if blueprint_name is not None:
+ endpoint = blueprint_name + endpoint
+ else:
+ endpoint = endpoint[1:]
else:
# TODO: get rid of this deprecated functionality in 1.0
if '.' not in endpoint:
- mod = ctx.request.blueprint
- if mod is not None:
- endpoint = mod + '.' + endpoint
+ if blueprint_name is not None:
+ endpoint = blueprint_name + '.' + endpoint
elif endpoint.startswith('.'):
endpoint = endpoint[1:]
external = values.pop('_external', False)
View
171 scripts/flask-07-upgrade.py
@@ -0,0 +1,171 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ flask-07-upgrade
+ ~~~~~~~~~~~~~~~~
+
+ This command line script scans a whole application tree and attempts to
+ output an unified diff with all the changes that are necessary to easily
+ upgrade the application to 0.7 and to not yield deprecation warnings.
+
+ This will also attempt to find `after_request` functions that don't modify
+ the response and appear to be better suited for `teardown_request`.
+
+ :copyright: (c) Copyright 2011 by Armin Ronacher.
+ :license: see LICENSE for more details.
+"""
+import re
+import os
+import sys
+import inspect
+import difflib
+import posixpath
+from optparse import OptionParser
+
+try:
+ import ast
+except ImportError:
+ ast = None
+
+
+_string_re_part = r"('([^'\\]*(?:\\.[^'\\]*)*)'" \
+ r'|"([^"\\]*(?:\\.[^"\\]*)*)")'
+_url_for_re = re.compile(r'\b(url_for\()(%s)' % _string_re_part)
+_after_request_re = re.compile(r'((?:@\S+\.(?:app_)?))(after_request)(\b\s*$)(?m)')
+
+
+def error(message):
+ print >> sys.stderr, 'error:', message
+ sys.exit(1)
+
+
+def make_diff(filename, old, new):
+ for line in difflib.unified_diff(old.splitlines(), new.splitlines(),
+ posixpath.normpath(posixpath.join('a', filename)),
+ posixpath.normpath(posixpath.join('b', filename)),
+ lineterm=''):
+ print line
+
+
+def fix_url_for(contents):
+ def handle_match(match):
+ prefix = match.group(1)
+ endpoint = ast.literal_eval(match.group(2))
+ if endpoint.startswith('.'):
+ endpoint = endpoint[1:]
+ else:
+ endpoint = '.' + endpoint
+ return prefix + repr(endpoint)
+ return _url_for_re.sub(handle_match, contents)
+
+
+def looks_like_teardown_function(node):
+ returns = [x for x in ast.walk(node) if isinstance(x, ast.Return)]
+ if len(returns) != 1:
+ return
+ return_def = returns[0]
+ resp_name = node.args.args[0]
+ if not isinstance(return_def.value, ast.Name) or \
+ return_def.value.id != resp_name.id:
+ return
+
+ for body_node in node.body:
+ for child in ast.walk(body_node):
+ if isinstance(child, ast.Name) and \
+ child.id == resp_name.id:
+ if child is not return_def.value:
+ return
+
+ return resp_name.id
+
+
+def fix_teardown_funcs(contents):
+
+ def is_return_line(line):
+ args = line.strip().split()
+ return args and args[0] == 'return'
+
+ def fix_single(match, lines, lineno):
+ if not lines[lineno + 1].startswith('def'):
+ return
+ block_lines = inspect.getblock(lines[lineno + 1:])
+ func_code = ''.join(block_lines)
+ if func_code[0].isspace():
+ node = ast.parse('if 1:\n' + func_code).body[0].body
+ else:
+ node = ast.parse(func_code).body[0]
+ response_param_name = looks_like_teardown_function(node)
+ if response_param_name is None:
+ return
+ before = lines[:lineno]
+ decorator = [match.group(1) +
+ match.group(2).replace('after_', 'teardown_') +
+ match.group(3)]
+ body = [line.replace(response_param_name, 'exception')
+ for line in block_lines if
+ not is_return_line(line)]
+ after = lines[lineno + len(block_lines) + 1:]
+ return before + decorator + body + after
+
+ content_lines = contents.splitlines(True)
+ while 1:
+ found_one = False
+ for idx, line in enumerate(content_lines):
+ match = _after_request_re.match(line)
+ if match is None:
+ continue
+ new_content_lines = fix_single(match, content_lines, idx)
+ if new_content_lines is not None:
+ content_lines = new_content_lines
+ break
+ else:
+ break
+
+ return ''.join(content_lines)
+
+
+def upgrade_python_file(filename, contents, teardown):
+ new_contents = fix_url_for(contents)
+ if teardown:
+ new_contents = fix_teardown_funcs(contents)
+ make_diff(filename, contents, new_contents)
+
+
+def upgrade_template_file(filename, contents):
+ new_contents = fix_url_for(contents)
+ make_diff(filename, contents, new_contents)
+
+
+def scan_path(path=None, teardown=True):
+ for dirpath, dirnames, filenames in os.walk(path):
+ for filename in filenames:
+ filename = os.path.join(dirpath, filename)
+ with open(filename) as f:
+ contents = f.read()
+ if filename.endswith('.py'):
+ upgrade_python_file(filename, contents, teardown)
+ elif '{% for' or '{% if' or '{{ url_for' in contents:
+ upgrade_template_file(filename, contents)
+
+
+def main():
+ """Entrypoint"""
+ if ast is None:
+ error('Python 2.6 or later is required to run the upgrade script.\n'
+ 'The runtime requirements for Flask 0.7 however are still '
+ 'Python 2.5.')
+
+ parser = OptionParser()
+ parser.add_option('-T', '--no-teardown-detection', dest='no_teardown',
+ action='store_true', help='Do not attempt to '
+ 'detect teardown function rewrites.')
+ options, args = parser.parse_args()
+ if not args:
+ args = ['.']
+
+ for path in args:
+ scan_path(path, teardown=not options.no_teardown)
+
+
+if __name__ == '__main__':
+ main()

0 comments on commit 6dae36f

Please sign in to comment.