diff --git a/AUTHORS b/AUTHORS index 7cc5da5b..7f5df399 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,10 +15,12 @@ Patches and Suggestions - Chris Grindstaff - Christopher Grebs - Daniel Neuhäuser +- Edmond Burnett - Florent Xicluna - Georg Brandl - Justin Quick - Kenneth Reitz +- Keyan Pishdadian - Marian Sigler - Matt Campell - Matthew Frazier @@ -29,4 +31,3 @@ Patches and Suggestions - Stephane Wirtel - Thomas Schranz - Zhao Xiaohong -- Edmond Burnett diff --git a/docs/signals.rst b/docs/signals.rst index c83d01c0..ecb49d5f 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -26,7 +26,7 @@ early by returning a response. In contrast all signal handlers are executed in undefined order and do not modify any data. The big advantage of signals over handlers is that you can safely -subscribe to them for the split of a second. These temporary +subscribe to them for just a split second. These temporary subscriptions are helpful for unittesting for example. Say you want to know what templates were rendered as part of a request: signals allow you to do exactly that. @@ -42,11 +42,11 @@ signal, you can use the :meth:`~blinker.base.Signal.disconnect` method. For all core Flask signals, the sender is the application that issued the signal. When you subscribe to a signal, be sure to also provide a sender -unless you really want to listen for signals of all applications. This is +unless you really want to listen for signals from all applications. This is especially true if you are developing an extension. -Here for example a helper context manager that can be used to figure out -in a unittest which templates were rendered and what variables were passed +For example, here is a helper context manager that can be used in a unittest +to determine which templates were rendered and what variables were passed to the template:: from flask import template_rendered @@ -82,10 +82,10 @@ variable. Whenever a template is rendered, the template object as well as context are appended to it. Additionally there is a convenient helper method -(:meth:`~blinker.base.Signal.connected_to`). that allows you to +(:meth:`~blinker.base.Signal.connected_to`) that allows you to temporarily subscribe a function to a signal with a context manager on its own. Because the return value of the context manager cannot be -specified that way one has to pass the list in as argument:: +specified that way, you have to pass the list in as an argument:: from flask import template_rendered @@ -208,8 +208,8 @@ The following signals exist in Flask: .. data:: flask.request_started :noindex: - This signal is sent before any request processing started but when the - request context was set up. Because the request context is already + This signal is sent when the request context is set up, before + any request processing happens. Because the request context is already bound, the subscriber can access the request with the standard global proxies such as :class:`~flask.request`. diff --git a/flask/__init__.py b/flask/__init__.py index f6aa67bc..a6ef98ca 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.11-dev' +__version__ = '0.11.dev0' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. diff --git a/flask/helpers.py b/flask/helpers.py index 080ea899..a78cb18e 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -427,12 +427,8 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, guessing requires a `filename` or an `attachment_filename` to be provided. - Please never pass filenames to this function from user sources without - checking them first. Something like this is usually sufficient to - avoid security problems:: - - if '..' in filename or filename.startswith('/'): - abort(404) + Please never pass filenames to this function from user sources; + you should use :func:`send_from_directory` instead. .. versionadded:: 0.2 diff --git a/scripts/flaskext_migrate.py b/scripts/flaskext_migrate.py new file mode 100644 index 00000000..e24ab95c --- /dev/null +++ b/scripts/flaskext_migrate.py @@ -0,0 +1,160 @@ +# Script which modifies source code away from the deprecated "flask.ext" +# format. +# +# Run in the terminal by typing: `python flaskext_migrate.py ` +# +# Author: Keyan Pishdadian 2015 + +from redbaron import RedBaron +import sys + + +def read_source(input_file): + """Parses the input_file into a RedBaron FST.""" + with open(input_file, "r") as source_code: + red = RedBaron(source_code.read()) + return red + + +def write_source(red, input_file): + """Overwrites the input_file once the FST has been modified.""" + with open(input_file, "w") as source_code: + source_code.write(red.dumps()) + + +def fix_imports(red): + """Wrapper which fixes "from" style imports and then "import" style.""" + red = fix_standard_imports(red) + red = fix_from_imports(red) + return red + + +def fix_from_imports(red): + """ + Converts "from" style imports to not use "flask.ext". + + Handles: + Case 1: from flask.ext.foo import bam --> from flask_foo import bam + Case 2: from flask.ext import foo --> import flask_foo as foo + """ + from_imports = red.find_all("FromImport") + for x, node in enumerate(from_imports): + values = node.value + if (values[0].value == 'flask') and (values[1].value == 'ext'): + # Case 1 + if len(node.value) == 3: + package = values[2].value + modules = node.modules() + module_string = _get_modules(modules) + if len(modules) > 1: + node.replace("from flask_%s import %s" + % (package, module_string)) + else: + name = node.names()[0] + node.replace("from flask_%s import %s as %s" + % (package, module_string, name)) + # Case 2 + else: + module = node.modules()[0] + node.replace("import flask_%s as %s" + % (module, module)) + return red + + +def fix_standard_imports(red): + """ + Handles import modification in the form: + import flask.ext.foo" --> import flask_foo + """ + imports = red.find_all("ImportNode") + for x, node in enumerate(imports): + try: + if (node.value[0].value[0].value == 'flask' and + node.value[0].value[1].value == 'ext'): + package = node.value[0].value[2].value + name = node.names()[0].split('.')[-1] + if name == package: + node.replace("import flask_%s" % (package)) + else: + node.replace("import flask_%s as %s" % (package, name)) + except IndexError: + pass + + return red + + +def _get_modules(module): + """ + Takes a list of modules and converts into a string. + + The module list can include parens, this function checks each element in + the list, if there is a paren then it does not add a comma before the next + element. Otherwise a comma and space is added. This is to preserve module + imports which are multi-line and/or occur within parens. While also not + affecting imports which are not enclosed. + """ + modules_string = [cur + ', ' if cur.isalnum() and next.isalnum() + else cur + for (cur, next) in zip(module, module[1:]+[''])] + + return ''.join(modules_string) + + +def fix_function_calls(red): + """ + Modifies function calls in the source to reflect import changes. + + Searches the AST for AtomtrailerNodes and replaces them. + """ + atoms = red.find_all("Atomtrailers") + for x, node in enumerate(atoms): + try: + if (node.value[0].value == 'flask' and + node.value[1].value == 'ext'): + params = _form_function_call(node) + node.replace("flask_%s%s" % (node.value[2], params)) + except IndexError: + pass + + return red + + +def _form_function_call(node): + """ + Reconstructs function call strings when making attribute access calls. + """ + node_vals = node.value + output = "." + for x, param in enumerate(node_vals[3::]): + if param.dumps()[0] == "(": + output = output[0:-1] + param.dumps() + return output + else: + output += param.dumps() + "." + + +def check_user_input(): + """Exits and gives error message if no argument is passed in the shell.""" + if len(sys.argv) < 2: + sys.exit("No filename was included, please try again.") + + +def fix_tester(ast): + """Wrapper which allows for testing when not running from shell.""" + ast = fix_imports(ast) + ast = fix_function_calls(ast) + return ast.dumps() + + +def fix(): + """Wrapper for user argument checking and import fixing.""" + check_user_input() + input_file = sys.argv[1] + ast = read_source(input_file) + ast = fix_imports(ast) + ast = fix_function_calls(ast) + write_source(ast, input_file) + + +if __name__ == "__main__": + fix() diff --git a/scripts/test_import_migration.py b/scripts/test_import_migration.py new file mode 100644 index 00000000..0220e70a --- /dev/null +++ b/scripts/test_import_migration.py @@ -0,0 +1,71 @@ +# Tester for the flaskext_migrate.py module located in flask/scripts/ +# +# Author: Keyan Pishdadian +import pytest +from redbaron import RedBaron +import flaskext_migrate as migrate + + +def test_simple_from_import(): + red = RedBaron("from flask.ext import foo") + output = migrate.fix_tester(red) + assert output == "import flask_foo as foo" + + +def test_from_to_from_import(): + red = RedBaron("from flask.ext.foo import bar") + output = migrate.fix_tester(red) + assert output == "from flask_foo import bar as bar" + + +def test_multiple_import(): + red = RedBaron("from flask.ext.foo import bar, foobar, something") + output = migrate.fix_tester(red) + assert output == "from flask_foo import bar, foobar, something" + + +def test_multiline_import(): + red = RedBaron("from flask.ext.foo import \ + bar,\ + foobar,\ + something") + output = migrate.fix_tester(red) + assert output == "from flask_foo import bar, foobar, something" + + +def test_module_import(): + red = RedBaron("import flask.ext.foo") + output = migrate.fix_tester(red) + assert output == "import flask_foo" + + +def test_named_module_import(): + red = RedBaron("import flask.ext.foo as foobar") + output = migrate.fix_tester(red) + assert output == "import flask_foo as foobar" + + +def test__named_from_import(): + red = RedBaron("from flask.ext.foo import bar as baz") + output = migrate.fix_tester(red) + assert output == "from flask_foo import bar as baz" + + +def test_parens_import(): + red = RedBaron("from flask.ext.foo import (bar, foo, foobar)") + output = migrate.fix_tester(red) + assert output == "from flask_foo import (bar, foo, foobar)" + + +def test_function_call_migration(): + red = RedBaron("flask.ext.foo(var)") + output = migrate.fix_tester(red) + assert output == "flask_foo(var)" + + +def test_nested_function_call_migration(): + red = RedBaron("import flask.ext.foo\n\n" + "flask.ext.foo.bar(var)") + output = migrate.fix_tester(red) + assert output == ("import flask_foo\n\n" + "flask_foo.bar(var)") diff --git a/setup.py b/setup.py index 550b24a4..52e2df64 100644 --- a/setup.py +++ b/setup.py @@ -42,13 +42,21 @@ Links `_ """ -from __future__ import print_function -from setuptools import Command, setup +import re +import ast +from setuptools import setup + + +_version_re = re.compile(r'__version__\s+=\s+(.*)') + +with open('flask/__init__.py', 'rb') as f: + version = str(ast.literal_eval(_version_re.search( + f.read().decode('utf-8')).group(1))) setup( name='Flask', - version='0.11-dev', + version=version, url='http://github.com/mitsuhiko/flask/', license='BSD', author='Armin Ronacher', diff --git a/tests/test_basic.py b/tests/test_basic.py index d6fe32fb..32a712bf 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -580,17 +580,27 @@ def test_request_preprocessing_early_return(): evts = [] @app.before_request - def before_request(): + def before_request1(): + evts.append(1) + + @app.before_request + def before_request2(): + evts.append(2) return "hello" + @app.before_request + def before_request3(): + evts.append(3) + return "bye" + @app.route('/') def index(): evts.append('index') return "damnit" rv = app.test_client().get('/').data.strip() - assert rv == 'hello' - assert not evts + assert rv == b'hello' + assert evts == [1, 2] def test_after_request_processing(): diff --git a/tox.ini b/tox.ini index ba2d2668..3e170d87 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,7 @@ commands = deps= pytest greenlet + redbaron lowest: Werkzeug==0.7 lowest: Jinja2==2.4