From 45b97d14e3ab7a8b7e6010bb6490a87b00c2ae2b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 14 Mar 2011 14:19:12 -0400 Subject: [PATCH 01/17] Started work on app dispatch docs --- docs/patterns/appdispatch.rst | 78 +++++++++++++++++++++++++++++++++++ docs/patterns/index.rst | 1 + 2 files changed, 79 insertions(+) create mode 100644 docs/patterns/appdispatch.rst diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst new file mode 100644 index 00000000..91858450 --- /dev/null +++ b/docs/patterns/appdispatch.rst @@ -0,0 +1,78 @@ +.. _app-dispatch: + +Application Dispatching +======================= + +Sometimes you might want to use multiple instances of the same application +with different configurations. Assuming the application is created inside +a function and you can call that function to instanciate it, that is +really easy to implement. In order to develop your application to support +creating new instances in functions have a look at the +:ref:`app-factories` pattern. + + +Dispatch by Subdomain +--------------------- + +A very common example would be creating applications per subdomain. For +instance you configure your webserver to dispatch all requests for all +subdomains to your application and you then use the subdomain information +to create user-specific instances. + +Once you have your server set up to listen on all subdomains you can use a +very simple WSGI application to do the dynamic application creation. + +The code for the dispatching looks roughly like this: + +.. sourcecode:: python + + from threading import Lock + + class SubdomainDispatcher(object): + + def __init__(self, domain, create_app): + self.domain = domain + self.create_app = create_app + self.lock = Lock() + self.instances = {} + + def get_application(self, host): + host = host.split(':')[0] + assert host.endswith(self.domain), 'Configuration error' + subdomain = host[:-len(self.domain)].rstrip('.') + with self.lock: + app = self.instances.get(subdomain) + if app is None: + app = self.make_app(subdomain) + self.instances[subdomain] = app + return app + + def make_app(self, subdomain): + return self.create_app(subdomain) + + def __call__(self, environ, start_response): + app = self.get_application(environ['HTTP_HOST']) + return app(environ, start_response) + + +If you want to use it, you can do something like this: + +.. sourcecode:: python + + from myapplication import create_app, get_user_for_subdomain + from werkzeug.exceptions import NotFound + + def make_app(subdomain): + user = get_user_for_subdomain(subdomain) + if user is None: + # if there is no user for that subdomain we still have + # to return a WSGI application that handles that request. + # We can then just return the NotFound() exception as + # application which will render a default 404 page. + # You might also redirect the user to the main page then + return NotFound() + + # otherwise create the application for the specific user + return create_app(user) + + application = SubdomainDispatcher('example.com', make_app) diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst index 68cff143..ed231163 100644 --- a/docs/patterns/index.rst +++ b/docs/patterns/index.rst @@ -18,6 +18,7 @@ Snippet Archives `_. packages appfactories + appdispatch distribute fabric sqlite3 From 60de3f295b0dc90635998772e7651b09aa75fab1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 14 Mar 2011 16:44:58 -0400 Subject: [PATCH 02/17] Updated appdispatch pattern --- docs/patterns/appdispatch.rst | 112 ++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 18 deletions(-) diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst index 91858450..7849b2ef 100644 --- a/docs/patterns/appdispatch.rst +++ b/docs/patterns/appdispatch.rst @@ -3,6 +3,43 @@ Application Dispatching ======================= +Application dispatching is the process of combining multiple Flask +applications on the WSGI level. You can not only combine Flask +applications into something larger but any WSGI application. This would +even allow you to run a Django and a Flask application in the same +interpreter side by side if you want. The usefulness of this depends on +how the applications work internally. + +This is fundamentally different from the :ref:`module approach +` is that in this case you are running the same or +different Flask applications that are entirely isolated from each other. +They run different configurations and are dispatched on the WSGI level. + +Combining Applications +---------------------- + +If you have entirely separated applications and you want them to work next +to each other in the same Python interpreter process you can take +advantage of the :class:`werkzeug.wsgi.DispatcherMiddleware`. The idea +here is that each Flask application is a valid WSGI application and they +are combined by the dispatcher middleware into a larger one that +dispatched based on prefix. + +For example you could have your main application run on `/` and your +backend interface on `/admin`:: + + from werkzeug.wsgi import DispatcherMiddleware + from frontend_app import application as frontend + from backend_app import application as backend + + application = DispatcherMiddleware(frontend, { + '/backend': backend + }) + + +Dispatch by Subdomain +--------------------- + Sometimes you might want to use multiple instances of the same application with different configurations. Assuming the application is created inside a function and you can call that function to instanciate it, that is @@ -10,21 +47,17 @@ really easy to implement. In order to develop your application to support creating new instances in functions have a look at the :ref:`app-factories` pattern. - -Dispatch by Subdomain ---------------------- - A very common example would be creating applications per subdomain. For instance you configure your webserver to dispatch all requests for all subdomains to your application and you then use the subdomain information -to create user-specific instances. +to create user-specific instances. Once you have your server set up to +listen on all subdomains you can use a very simple WSGI application to do +the dynamic application creation. -Once you have your server set up to listen on all subdomains you can use a -very simple WSGI application to do the dynamic application creation. - -The code for the dispatching looks roughly like this: - -.. sourcecode:: python +The perfect level for abstraction in that regard is the WSGI layer. You +write your own WSGI application that looks at the request that comes and +and delegates it to your Flask application. If that application does not +exist yet, it is dynamically created and remembered:: from threading import Lock @@ -43,21 +76,16 @@ The code for the dispatching looks roughly like this: with self.lock: app = self.instances.get(subdomain) if app is None: - app = self.make_app(subdomain) + app = self.create_app(subdomain) self.instances[subdomain] = app return app - def make_app(self, subdomain): - return self.create_app(subdomain) - def __call__(self, environ, start_response): app = self.get_application(environ['HTTP_HOST']) return app(environ, start_response) -If you want to use it, you can do something like this: - -.. sourcecode:: python +This dispatcher can then be used like this:: from myapplication import create_app, get_user_for_subdomain from werkzeug.exceptions import NotFound @@ -76,3 +104,51 @@ If you want to use it, you can do something like this: return create_app(user) application = SubdomainDispatcher('example.com', make_app) + + +Dispatch by Path +---------------- + +Dispatching by a path on the URL is very similar. Instead of looking at +the `Host` header to figure out the subdomain one simply looks at the +request path up to the first slash:: + + from threading import Lock + from werkzeug.wsgi import pop_path_info, peek_path_info + + class PathDispatcher(object): + + def __init__(self, default_app, create_app): + self.default_app = default_app + self.create_app = create_app + self.lock = Lock() + self.instances = {} + + def get_application(self, prefix): + with self.lock: + app = self.instances.get(prefix) + if app is None: + app = self.create_app(prefix) + if app is not None: + self.instances[prefix] = app + return app + + def __call__(self, environ, start_response): + app = self.get_application(peek_path_info(environ)) + if app is not None: + pop_path_info(environ) + else: + app = self.default_app + return app(environ, start_response) + +The big difference between this and the subdomain one is that this one +falls back to another application if the creator function returns `None`:: + + from myapplication import create_app, default_app, get_user_for_prefix + + def make_app(prefix): + user = get_user_for_prefix(prefix) + if user is not None: + return create_app(user) + + application = PathDispatcher('example.com', default_app, make_app) From c1c20ac108bab9daea0c4741e19a68a2186db6e3 Mon Sep 17 00:00:00 2001 From: Matt Dawson Date: Mon, 14 Mar 2011 19:23:56 -0400 Subject: [PATCH 03/17] Modify extensionsdev documentation. --- docs/extensiondev.rst | 125 +++++++++++++++++++++++++++--------------- 1 file changed, 80 insertions(+), 45 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index e468d8a1..a5cc2aa4 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -137,7 +137,6 @@ Now this is where your extension code goes. But how exactly should such an extension look like? What are the best practices? Continue reading for some insight. - Initializing Extensions ----------------------- @@ -165,8 +164,8 @@ classes: a remote application that uses OAuth. What to use depends on what you have in mind. For the SQLite 3 extension -we will need to use the class based approach because we have to use a -controller object that can be used to connect to the database. +we will use the class based approach because it will provide users with a +manager object that handles opening and closing database connections. The Extension Code ------------------ @@ -175,87 +174,124 @@ Here's the contents of the `flaskext/sqlite3.py` for copy/paste:: from __future__ import absolute_import import sqlite3 - from flask import g + + from flask import _request_ctx_stack class SQLite3(object): - + def __init__(self, app): self.app = app self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') - - self.app.before_request(self.before_request) self.app.after_request(self.after_request) + self.app.before_request(self.before_request) def connect(self): return sqlite3.connect(self.app.config['SQLITE3_DATABASE']) def before_request(self): - g.sqlite3_db = self.connect() + ctx = _request_ctx_stack.top + ctx.sqlite3_db = self.connect() def after_request(self, response): - g.sqlite3_db.close() + ctx = _request_ctx_stack.top + ctx.sqlite3_db.close() return response -So here's what the lines of code do: + def get_db(self): + ctx = _request_ctx_stack.top + if ctx is not None: + return ctx.sqlite3_db -1. the ``__future__`` import is necessary to activate absolute imports. - This is needed because otherwise we could not call our module - `sqlite3.py` and import the top-level `sqlite3` module which actually - implements the connection to SQLite. -2. We create a class for our extension that sets a default configuration - for the SQLite 3 database if it's not there (:meth:`dict.setdefault`) - and connects two functions as before and after request handlers. -3. Then it implements a `connect` function that returns a new database - connection and the two handlers. +So here's what these lines of code do: -So why did we decide on a class based approach here? Because using that +1. The ``__future__`` import is necessary to activate absolute imports. + Otherwise we could not call our module `sqlite3.py` and import the + top-level `sqlite3` module which actually implements the connection to + SQLite. +2. We create a class for our extension that requires a supplied `app` object, + sets a configuration for the database if it's not there + (:meth:`dict.setdefault`), and attaches `before_request` and + `after_request` handlers. +3. Next, we define a `connect` function that opens a database connection. +4. Then we set up the request handlers we bound to the app above. Note here + that we're attaching our database connection to the top request context via + `_request_ctx_stack.top`. Extensions should use the top context and not the + `g` object to store things like database connections. +5. Finally, we add a `get_db` function that simplifies access to the context's + database. + +So why did we decide on a class based approach here? Because using our extension looks something like this:: - from flask import Flask, g + from flask import Flask from flaskext.sqlite3 import SQLite3 app = Flask(__name__) app.config.from_pyfile('the-config.cfg') - db = SQLite(app) + manager = SQLite3(app) + db = manager.get_db() -Either way you can use the database from the views like this:: +You can then use the database from views like this:: @app.route('/') def show_all(): - cur = g.sqlite3_db.cursor() + cur = db.cursor() cur.execute(...) -But how would you open a database connection from outside a view function? -This is where the `db` object now comes into play: +Opening a database connection from outside a view function is simple. >>> from yourapplication import db ->>> con = db.connect() ->>> cur = con.cursor() +>>> cur = db.cursor() +>>> cur.execute(...) -If you don't need that, you can go with initialization functions. +Adding an `init_app` Function +----------------------------- -Initialization Functions ------------------------- +In practice, you'll almost always want to permit users to initialize your +extension and provide an app object after the fact. This can help avoid +circular import problems when a user is breaking their app into multiple files. +Our extension could add an `init_app` function as follows:: -Here's what the module would look like with initialization functions:: + class SQLite3(object): - from __future__ import absolute_import - import sqlite3 - from flask import g + def __init__(self, app=None): + if app is not None: + self.app = app + self.init_app(self.app) + else: + self.app = None - def init_sqlite3(app): - app = app - app.config.setdefault('SQLITE3_DATABASE', ':memory:') + def init_app(self, app): + self.app = app + self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') + self.app.after_request(self.after_request) + self.app.before_request(self.before_request) - @app.before_request - def before_request(): - g.sqlite3_db = sqlite3.connect(self.app.config['SQLITE3_DATABASE']) + def connect(self): + return sqlite3.connect(app.config['SQLITE3_DATABASE']) - @app.after_request - def after_request(response): - g.sqlite3_db.close() + def before_request(self): + ctx = _request_ctx_stack.top + ctx.sqlite3_db = self.connect() + + def after_request(self, response): + ctx = _request_ctx_stack.top + ctx.sqlite3_db.close() return response + def get_db(self): + ctx = _request_ctx_stack.top + if ctx is not None: + return ctx.sqlite3_db + +The user could then initialize the extension in one file:: + + manager = SQLite3() + +and bind their app to the extension in another file:: + + manager.init_app(app) + Learn from Others ----------------- @@ -276,7 +312,6 @@ designing the API. The best Flask extensions are extensions that share common idioms for the API. And this can only work if collaboration happens early. - Approved Extensions ------------------- From 43cf3f307fc872b37de8b8fe986d4af17f767d63 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 15 Mar 2011 11:25:17 -0400 Subject: [PATCH 04/17] Improved wording --- docs/patterns/appdispatch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst index 7849b2ef..2f8be093 100644 --- a/docs/patterns/appdispatch.rst +++ b/docs/patterns/appdispatch.rst @@ -10,7 +10,7 @@ even allow you to run a Django and a Flask application in the same interpreter side by side if you want. The usefulness of this depends on how the applications work internally. -This is fundamentally different from the :ref:`module approach +The fundamental difference from the :ref:`module approach ` is that in this case you are running the same or different Flask applications that are entirely isolated from each other. They run different configurations and are dispatched on the WSGI level. From 97efffad9a9858c0d966723d543e1186629631d4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 15 Mar 2011 11:47:59 -0400 Subject: [PATCH 05/17] Enable deprecation warnings --- tests/flask_tests.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 04b54777..46455e99 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -47,6 +47,10 @@ SECRET_KEY = 'devkey' def catch_warnings(): """Catch warnings in a with block in a list""" import warnings + + # make sure deprecation warnings are active in tests + warnings.simplefilter('default', category=DeprecationWarning) + filters = warnings.filters warnings.filters = filters[:] old_showwarning = warnings.showwarning From 67581795c4b1d5d27aaadf71f82bb6862887ba74 Mon Sep 17 00:00:00 2001 From: Aaron Kavlie Date: Mon, 14 Mar 2011 23:32:33 -0400 Subject: [PATCH 06/17] Improved botched docstring wording for silent failure. Signed-off-by: Armin Ronacher --- flask/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/config.py b/flask/config.py index b276f168..b588dfa1 100644 --- a/flask/config.py +++ b/flask/config.py @@ -89,7 +89,7 @@ class Config(dict): app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) :param variable_name: name of the environment variable - :param silent: set to `True` if you want silent to fail for missing + :param silent: set to `True` if you want silent failure for missing files. :return: bool. `True` if able to load config, `False` otherwise. """ @@ -113,7 +113,7 @@ class Config(dict): :param filename: the filename of the config. This can either be an absolute filename or a filename relative to the root path. - :param silent: set to `True` if you want silent to fail for missing + :param silent: set to `True` if you want silent failure for missing files. .. versionadded:: 0.7 From 4c8c503326f8b26787945082007a294dff4c255b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 18 Mar 2011 09:00:24 +0100 Subject: [PATCH 07/17] break line --- flask/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index fad49945..49c8fe10 100644 --- a/flask/app.py +++ b/flask/app.py @@ -718,7 +718,9 @@ class Flask(_PackageBoundObject): return f def teardown_request(self, f): - """Register a function to be run at the end of each request, regardless of whether there was an exception or not.""" + """Register a function to be run at the end of each request, + regardless of whether there was an exception or not. + """ self.teardown_request_funcs.setdefault(None, []).append(f) return f From 0ecc686372f577ccbcbcb818e1386ceb7bd9391d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 17 Apr 2011 19:03:06 +0200 Subject: [PATCH 08/17] Fixed a typo --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index ed8a5d5c..458d6252 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -321,7 +321,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if not attachment_filename and not mimetype \ and isinstance(filename, basestring): warn(DeprecationWarning('The filename support for file objects ' - 'passed to send_file is not deprecated. Pass an ' + 'passed to send_file is now deprecated. Pass an ' 'attach_filename if you want mimetypes to be guessed.'), stacklevel=2) if add_etags: From a2225bf57e7c20f5c63c3c2b79db397f3724c1dc Mon Sep 17 00:00:00 2001 From: Noufal Ibrahim Date: Mon, 28 Mar 2011 17:01:21 +0530 Subject: [PATCH 09/17] Added a note on actually starting the application Signed-off-by: Armin Ronacher --- docs/tutorial/setup.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/setup.rst b/docs/tutorial/setup.rst index 64bf3b66..e9e4d679 100644 --- a/docs/tutorial/setup.rst +++ b/docs/tutorial/setup.rst @@ -70,7 +70,14 @@ server if we want to run that file as a standalone application:: app.run() With that out of the way you should be able to start up the application -without problems. When you head over to the server you will get an 404 +without problems. Do this with the following command:: + + python flaskr.py + +You will see a message telling you that server has started along with +the address at which you can access it. + +When you head over to the server in your browser you will get an 404 page not found error because we don't have any views yet. But we will focus on that a little later. First we should get the database working. From 709ecefee1859cf16cc8bdfa642e39fbc6b4421e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Roy?= Date: Wed, 30 Mar 2011 22:58:41 -0400 Subject: [PATCH 10/17] Updating documentation for app.after_request decorator. Signed-off-by: Armin Ronacher --- flask/app.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 49c8fe10..20fa42d9 100644 --- a/flask/app.py +++ b/flask/app.py @@ -713,7 +713,10 @@ class Flask(_PackageBoundObject): return f def after_request(self, f): - """Register a function to be run after each request.""" + """Register a function to be run after each request. Your function + must take one parameter, a :attr:`response_class` object and return + a new response object or the same (see :meth:`process_response`). + """ self.after_request_funcs.setdefault(None, []).append(f) return f From 0e4cd2e6512c66ba004df0f35f3a21e8cfcdf77c Mon Sep 17 00:00:00 2001 From: Kanak Kshetri Date: Thu, 31 Mar 2011 15:53:17 -0400 Subject: [PATCH 11/17] Fixed a typo that was preventing second code block from appearing in a code block Signed-off-by: Armin Ronacher --- docs/tutorial/dbcon.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index f700a329..b2a626f9 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -24,7 +24,7 @@ db connection in the interactive debugger:: return response If you want to guarantee that the connection is always closed in debug mode, you -can close it in a function decorated with :meth:`~flask.Flask.teardown_request`: +can close it in a function decorated with :meth:`~flask.Flask.teardown_request`:: @app.before_request def before_request(): From 7ed3196e8d1861acd027c409c47e51cce812ba63 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 4 Apr 2011 16:53:04 +0200 Subject: [PATCH 12/17] Add safe_join: returns the filename used by send_from_directory. Signed-off-by: Armin Ronacher --- docs/api.rst | 2 ++ flask/__init__.py | 2 +- flask/helpers.py | 34 +++++++++++++++++++++++++++------- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index c2d90ce6..88d026ed 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -244,6 +244,8 @@ Useful Functions and Classes .. autofunction:: send_from_directory +.. autofunction:: safe_join + .. autofunction:: escape .. autoclass:: Markup diff --git a/flask/__init__.py b/flask/__init__.py index 3a232e5e..1274e766 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -19,7 +19,7 @@ from .app import Flask, Request, Response from .config import Config from .helpers import url_for, jsonify, json_available, flash, \ send_file, send_from_directory, get_flashed_messages, \ - get_template_attribute, make_response + get_template_attribute, make_response, safe_join from .globals import current_app, g, request, session, _request_ctx_stack from .ctx import has_request_context from .module import Module diff --git a/flask/helpers.py b/flask/helpers.py index 458d6252..9d9af4bf 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -388,6 +388,32 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, return rv +def safe_join(directory, filename): + """Safely join `directory` and `filename`. + + :param directory: the base directory. + :param filename: the untrusted filename relative to that directory. + :raises: :class:`~werkzeug.exceptions.NotFound` if the retsulting path + would fall out of `directory`. + + Example usage:: + + @app.route('/wiki/') + def wiki_page(filename): + filename = safe_join(app.config['WIKI_FOLDER'], filename) + with open(filename, 'rb') as fd: + content = fd.read() # Read and process the file content... + + """ + filename = posixpath.normpath(filename) + for sep in _os_alt_seps: + if sep in filename: + raise NotFound() + if os.path.isabs(filename) or filename.startswith('../'): + raise NotFound() + return os.path.join(directory, filename) + + def send_from_directory(directory, filename, **options): """Send a file from a given directory with :func:`send_file`. This is a secure way to quickly expose static files from an upload folder @@ -415,13 +441,7 @@ def send_from_directory(directory, filename, **options): :param options: optional keyword arguments that are directly forwarded to :func:`send_file`. """ - filename = posixpath.normpath(filename) - for sep in _os_alt_seps: - if sep in filename: - raise NotFound() - if os.path.isabs(filename) or filename.startswith('../'): - raise NotFound() - filename = os.path.join(directory, filename) + filename = safe_join(directory, filename) if not os.path.isfile(filename): raise NotFound() return send_file(filename, conditional=True, **options) From 3c7b5a68f1ede9c870ae76f44e2dde6102843e54 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 18 Apr 2011 16:48:40 +0200 Subject: [PATCH 13/17] Changelog entry --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 9420dac6..f5c70477 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,7 @@ Release date to be announced, codename to be selected - Added `teardown_request` decorator, for functions that should run at the end of a request regardless of whether an exception occurred. - Implemented :func:`flask.has_request_context` +- Added :func:`safe_join` Version 0.6.1 ------------- From 1dd83964f0c1fb3c4772cf24880f5aeb3f282f66 Mon Sep 17 00:00:00 2001 From: streety Date: Mon, 18 Apr 2011 02:49:31 -0700 Subject: [PATCH 14/17] Change to match headers in https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/contrib/fixers.py Signed-off-by: Armin Ronacher --- docs/deploying/others.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/others.rst b/docs/deploying/others.rst index 8f08ecd1..153fb7cf 100644 --- a/docs/deploying/others.rst +++ b/docs/deploying/others.rst @@ -72,7 +72,7 @@ problematic values in the WSGI environment usually are `REMOTE_ADDR` and but you might want to write your own WSGI middleware for specific setups. The most common setup invokes the host being set from `X-Forwarded-Host` -and the remote address from `X-Forward-For`:: +and the remote address from `X-Forwarded-For`:: from werkzeug.contrib.fixers import ProxyFix app.wsgi_app = ProxyFix(app.wsgi_app) From d8fcd4260e9194a03267ddb913a8b5d7cf0948d2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 18 Apr 2011 23:19:59 +0200 Subject: [PATCH 15/17] Whitespace normalization --- flask/helpers.py | 2 +- tests/flask_tests.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 9d9af4bf..e04b7f28 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -390,7 +390,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, def safe_join(directory, filename): """Safely join `directory` and `filename`. - + :param directory: the base directory. :param filename: the untrusted filename relative to that directory. :raises: :class:`~werkzeug.exceptions.NotFound` if the retsulting path diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 19168180..265f89f1 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -642,6 +642,7 @@ class BasicFunctionalityTestCase(unittest.TestCase): app.config.update( SERVER_NAME='localhost.localdomain:5000' ) + @app.route('/') def index(): return None From e774e3a69eeb9a811a22e73ff87267e8bf4f9027 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 18 Apr 2011 23:20:24 +0200 Subject: [PATCH 16/17] Switch params and example --- flask/helpers.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index e04b7f28..418c4b17 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -391,11 +391,6 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, def safe_join(directory, filename): """Safely join `directory` and `filename`. - :param directory: the base directory. - :param filename: the untrusted filename relative to that directory. - :raises: :class:`~werkzeug.exceptions.NotFound` if the retsulting path - would fall out of `directory`. - Example usage:: @app.route('/wiki/') @@ -404,6 +399,10 @@ def safe_join(directory, filename): with open(filename, 'rb') as fd: content = fd.read() # Read and process the file content... + :param directory: the base directory. + :param filename: the untrusted filename relative to that directory. + :raises: :class:`~werkzeug.exceptions.NotFound` if the retsulting path + would fall out of `directory`. """ filename = posixpath.normpath(filename) for sep in _os_alt_seps: From 017117778ec477205eaba6acab951951808a60a5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 25 Apr 2011 16:02:35 +0200 Subject: [PATCH 17/17] Document that None skips in query strings. This fixes #224 --- flask/helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 418c4b17..ec513d8f 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -166,7 +166,8 @@ def url_for(endpoint, **values): ==================== ======================= ============================= Variable arguments that are unknown to the target endpoint are appended - to the generated URL as query arguments. + to the generated URL as query arguments. If the value of a query argument + is `None`, the whole pair is skipped. For more information, head over to the :ref:`Quickstart `.