mirror of https://github.com/mitsuhiko/flask.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
735 lines
28 KiB
735 lines
28 KiB
# -*- coding: utf-8 -*- |
|
""" |
|
flask.helpers |
|
~~~~~~~~~~~~~ |
|
|
|
Implements various helpers. |
|
|
|
:copyright: (c) 2011 by Armin Ronacher. |
|
:license: BSD, see LICENSE for more details. |
|
""" |
|
|
|
from __future__ import with_statement |
|
|
|
import imp |
|
import os |
|
import sys |
|
import pkgutil |
|
import posixpath |
|
import mimetypes |
|
from time import time |
|
from zlib import adler32 |
|
from threading import RLock |
|
from werkzeug.urls import url_quote |
|
|
|
# try to load the best simplejson implementation available. If JSON |
|
# is not installed, we add a failing class. |
|
json_available = True |
|
json = None |
|
try: |
|
import simplejson as json |
|
except ImportError: |
|
try: |
|
import json |
|
except ImportError: |
|
try: |
|
# Google Appengine offers simplejson via django |
|
from django.utils import simplejson as json |
|
except ImportError: |
|
json_available = False |
|
|
|
|
|
from werkzeug.datastructures import Headers |
|
from werkzeug.exceptions import NotFound |
|
|
|
# this was moved in 0.7 |
|
try: |
|
from werkzeug.wsgi import wrap_file |
|
except ImportError: |
|
from werkzeug.utils import wrap_file |
|
|
|
from jinja2 import FileSystemLoader |
|
|
|
from .globals import session, _request_ctx_stack, current_app, request |
|
|
|
|
|
def _assert_have_json(): |
|
"""Helper function that fails if JSON is unavailable.""" |
|
if not json_available: |
|
raise RuntimeError('simplejson not installed') |
|
|
|
|
|
# figure out if simplejson escapes slashes. This behaviour was changed |
|
# from one version to another without reason. |
|
if not json_available or '\\/' not in json.dumps('/'): |
|
|
|
def _tojson_filter(*args, **kwargs): |
|
if __debug__: |
|
_assert_have_json() |
|
return json.dumps(*args, **kwargs).replace('/', '\\/') |
|
else: |
|
_tojson_filter = json.dumps |
|
|
|
|
|
# sentinel |
|
_missing = object() |
|
|
|
|
|
# what separators does this operating system provide that are not a slash? |
|
# this is used by the send_from_directory function to ensure that nobody is |
|
# able to access files from outside the filesystem. |
|
_os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep] |
|
if sep not in (None, '/')) |
|
|
|
|
|
def _endpoint_from_view_func(view_func): |
|
"""Internal helper that returns the default endpoint for a given |
|
function. This always is the function name. |
|
""" |
|
assert view_func is not None, 'expected view func if endpoint ' \ |
|
'is not provided.' |
|
return view_func.__name__ |
|
|
|
|
|
def jsonify(*args, **kwargs): |
|
"""Creates a :class:`~flask.Response` with the JSON representation of |
|
the given arguments with an `application/json` mimetype. The arguments |
|
to this function are the same as to the :class:`dict` constructor. |
|
|
|
Example usage:: |
|
|
|
@app.route('/_get_current_user') |
|
def get_current_user(): |
|
return jsonify(username=g.user.username, |
|
email=g.user.email, |
|
id=g.user.id) |
|
|
|
This will send a JSON response like this to the browser:: |
|
|
|
{ |
|
"username": "admin", |
|
"email": "admin@localhost", |
|
"id": 42 |
|
} |
|
|
|
This requires Python 2.6 or an installed version of simplejson. For |
|
security reasons only objects are supported toplevel. For more |
|
information about this, have a look at :ref:`json-security`. |
|
|
|
.. versionadded:: 0.2 |
|
|
|
.. versionadded:: 0.9 |
|
If the ``padded`` argument is true, the JSON object will be padded |
|
for JSONP calls and the response mimetype will be changed to |
|
``application/javascript``. By default, the request arguments ``callback`` |
|
and ``jsonp`` will be used as the name for the callback function. |
|
This will work with jQuery and most other JavaScript libraries |
|
by default. |
|
|
|
If the ``padded`` argument is a string, jsonify will look for |
|
the request argument with the same name and use that value as the |
|
callback-function name. |
|
""" |
|
if __debug__: |
|
_assert_have_json() |
|
if 'padded' in kwargs: |
|
if isinstance(kwargs['padded'], str): |
|
callback = request.args.get(kwargs['padded']) or 'jsonp' |
|
else: |
|
callback = request.args.get('callback') or \ |
|
request.args.get('jsonp') or 'jsonp' |
|
del kwargs['padded'] |
|
json_str = json.dumps(dict(*args, **kwargs), indent=None) |
|
content = str(callback) + "(" + json_str + ")" |
|
return current_app.response_class(content, mimetype='application/javascript') |
|
return current_app.response_class(json.dumps(dict(*args, **kwargs), |
|
indent=None if request.is_xhr else 2), mimetype='application/json') |
|
|
|
|
|
def make_response(*args): |
|
"""Sometimes it is necessary to set additional headers in a view. Because |
|
views do not have to return response objects but can return a value that |
|
is converted into a response object by Flask itself, it becomes tricky to |
|
add headers to it. This function can be called instead of using a return |
|
and you will get a response object which you can use to attach headers. |
|
|
|
If view looked like this and you want to add a new header:: |
|
|
|
def index(): |
|
return render_template('index.html', foo=42) |
|
|
|
You can now do something like this:: |
|
|
|
def index(): |
|
response = make_response(render_template('index.html', foo=42)) |
|
response.headers['X-Parachutes'] = 'parachutes are cool' |
|
return response |
|
|
|
This function accepts the very same arguments you can return from a |
|
view function. This for example creates a response with a 404 error |
|
code:: |
|
|
|
response = make_response(render_template('not_found.html'), 404) |
|
|
|
The other use case of this function is to force the return value of a |
|
view function into a response which is helpful with view |
|
decorators:: |
|
|
|
response = make_response(view_function()) |
|
response.headers['X-Parachutes'] = 'parachutes are cool' |
|
|
|
Internally this function does the following things: |
|
|
|
- if no arguments are passed, it creates a new response argument |
|
- if one argument is passed, :meth:`flask.Flask.make_response` |
|
is invoked with it. |
|
- if more than one argument is passed, the arguments are passed |
|
to the :meth:`flask.Flask.make_response` function as tuple. |
|
|
|
.. versionadded:: 0.6 |
|
""" |
|
if not args: |
|
return current_app.response_class() |
|
if len(args) == 1: |
|
args = args[0] |
|
return current_app.make_response(args) |
|
|
|
|
|
def url_for(endpoint, **values): |
|
"""Generates a URL to the given endpoint with the method provided. |
|
|
|
Variable arguments that are unknown to the target endpoint are appended |
|
to the generated URL as query arguments. If the value of a query argument |
|
is `None`, the whole pair is skipped. In case blueprints are active |
|
you can shortcut references to the same blueprint by prefixing the |
|
local endpoint with a dot (``.``). |
|
|
|
This will reference the index function local to the current blueprint:: |
|
|
|
url_for('.index') |
|
|
|
For more information, head over to the :ref:`Quickstart <url-building>`. |
|
|
|
.. versionadded:: 0.9 |
|
The `_anchor` and `_method` parameters were added. |
|
|
|
:param endpoint: the endpoint of the URL (name of the function) |
|
:param values: the variable arguments of the URL rule |
|
:param _external: if set to `True`, an absolute URL is generated. |
|
:param _anchor: if provided this is added as anchor to the URL. |
|
:param _method: if provided this explicitly specifies an HTTP method. |
|
""" |
|
ctx = _request_ctx_stack.top |
|
blueprint_name = request.blueprint |
|
if not ctx.request._is_old_module: |
|
if endpoint[:1] == '.': |
|
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: |
|
if blueprint_name is not None: |
|
endpoint = blueprint_name + '.' + endpoint |
|
elif endpoint.startswith('.'): |
|
endpoint = endpoint[1:] |
|
external = values.pop('_external', False) |
|
anchor = values.pop('_anchor', None) |
|
method = values.pop('_method', None) |
|
ctx.app.inject_url_defaults(endpoint, values) |
|
rv = ctx.url_adapter.build(endpoint, values, method=method, |
|
force_external=external) |
|
if anchor is not None: |
|
rv += '#' + url_quote(anchor) |
|
return rv |
|
|
|
|
|
def get_template_attribute(template_name, attribute): |
|
"""Loads a macro (or variable) a template exports. This can be used to |
|
invoke a macro from within Python code. If you for example have a |
|
template named `_cider.html` with the following contents: |
|
|
|
.. sourcecode:: html+jinja |
|
|
|
{% macro hello(name) %}Hello {{ name }}!{% endmacro %} |
|
|
|
You can access this from Python code like this:: |
|
|
|
hello = get_template_attribute('_cider.html', 'hello') |
|
return hello('World') |
|
|
|
.. versionadded:: 0.2 |
|
|
|
:param template_name: the name of the template |
|
:param attribute: the name of the variable of macro to acccess |
|
""" |
|
return getattr(current_app.jinja_env.get_template(template_name).module, |
|
attribute) |
|
|
|
|
|
def flash(message, category='message'): |
|
"""Flashes a message to the next request. In order to remove the |
|
flashed message from the session and to display it to the user, |
|
the template has to call :func:`get_flashed_messages`. |
|
|
|
.. versionchanged:: 0.3 |
|
`category` parameter added. |
|
|
|
:param message: the message to be flashed. |
|
:param category: the category for the message. The following values |
|
are recommended: ``'message'`` for any kind of message, |
|
``'error'`` for errors, ``'info'`` for information |
|
messages and ``'warning'`` for warnings. However any |
|
kind of string can be used as category. |
|
""" |
|
session.setdefault('_flashes', []).append((category, message)) |
|
|
|
|
|
def get_flashed_messages(with_categories=False, category_filter=[]): |
|
"""Pulls all flashed messages from the session and returns them. |
|
Further calls in the same request to the function will return |
|
the same messages. By default just the messages are returned, |
|
but when `with_categories` is set to `True`, the return value will |
|
be a list of tuples in the form ``(category, message)`` instead. |
|
|
|
Filter the flashed messages to one or more categories by providing those |
|
categories in `category_filter`. This allows rendering categories in |
|
separate html blocks. The `with_categories` and `category_filter` |
|
arguments are distinct: |
|
|
|
* `with_categories` controls whether categories are returned with message |
|
text (`True` gives a tuple, where `False` gives just the message text). |
|
* `category_filter` filters the messages down to only those matching the |
|
provided categories. |
|
|
|
See :ref:`message-flashing-pattern` for examples. |
|
|
|
.. versionchanged:: 0.3 |
|
`with_categories` parameter added. |
|
|
|
.. versionchanged:: 0.9 |
|
`category_filter` parameter added. |
|
|
|
:param with_categories: set to `True` to also receive categories. |
|
:param category_filter: whitelist of categories to limit return values |
|
""" |
|
flashes = _request_ctx_stack.top.flashes |
|
if flashes is None: |
|
_request_ctx_stack.top.flashes = flashes = session.pop('_flashes') \ |
|
if '_flashes' in session else [] |
|
if category_filter: |
|
flashes = filter(lambda f: f[0] in category_filter, flashes) |
|
if not with_categories: |
|
return [x[1] for x in flashes] |
|
return flashes |
|
|
|
|
|
def send_file(filename_or_fp, mimetype=None, as_attachment=False, |
|
attachment_filename=None, add_etags=True, |
|
cache_timeout=60 * 60 * 12, conditional=False): |
|
"""Sends the contents of a file to the client. This will use the |
|
most efficient method available and configured. By default it will |
|
try to use the WSGI server's file_wrapper support. Alternatively |
|
you can set the application's :attr:`~Flask.use_x_sendfile` attribute |
|
to ``True`` to directly emit an `X-Sendfile` header. This however |
|
requires support of the underlying webserver for `X-Sendfile`. |
|
|
|
By default it will try to guess the mimetype for you, but you can |
|
also explicitly provide one. For extra security you probably want |
|
to send certain files as attachment (HTML for instance). The mimetype |
|
guessing requires a `filename` or an `attachment_filename` to be |
|
provided. |
|
|
|
Note `get_send_file_options` in :class:`flask.Flask` hooks the |
|
``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable to set the default |
|
cache_timeout. |
|
|
|
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) |
|
|
|
.. versionadded:: 0.2 |
|
|
|
.. versionadded:: 0.5 |
|
The `add_etags`, `cache_timeout` and `conditional` parameters were |
|
added. The default behaviour is now to attach etags. |
|
|
|
.. versionchanged:: 0.7 |
|
mimetype guessing and etag support for file objects was |
|
deprecated because it was unreliable. Pass a filename if you are |
|
able to, otherwise attach an etag yourself. This functionality |
|
will be removed in Flask 1.0 |
|
|
|
:param filename_or_fp: the filename of the file to send. This is |
|
relative to the :attr:`~Flask.root_path` if a |
|
relative path is specified. |
|
Alternatively a file object might be provided |
|
in which case `X-Sendfile` might not work and |
|
fall back to the traditional method. Make sure |
|
that the file pointer is positioned at the start |
|
of data to send before calling :func:`send_file`. |
|
:param mimetype: the mimetype of the file if provided, otherwise |
|
auto detection happens. |
|
:param as_attachment: set to `True` if you want to send this file with |
|
a ``Content-Disposition: attachment`` header. |
|
:param attachment_filename: the filename for the attachment if it |
|
differs from the file's filename. |
|
:param add_etags: set to `False` to disable attaching of etags. |
|
:param conditional: set to `True` to enable conditional responses. |
|
:param cache_timeout: the timeout in seconds for the headers. |
|
""" |
|
mtime = None |
|
if isinstance(filename_or_fp, basestring): |
|
filename = filename_or_fp |
|
file = None |
|
else: |
|
from warnings import warn |
|
file = filename_or_fp |
|
filename = getattr(file, 'name', None) |
|
|
|
# XXX: this behaviour is now deprecated because it was unreliable. |
|
# removed in Flask 1.0 |
|
if not attachment_filename and not mimetype \ |
|
and isinstance(filename, basestring): |
|
warn(DeprecationWarning('The filename support for file objects ' |
|
'passed to send_file is now deprecated. Pass an ' |
|
'attach_filename if you want mimetypes to be guessed.'), |
|
stacklevel=2) |
|
if add_etags: |
|
warn(DeprecationWarning('In future flask releases etags will no ' |
|
'longer be generated for file objects passed to the send_file ' |
|
'function because this behaviour was unreliable. Pass ' |
|
'filenames instead if possible, otherwise attach an etag ' |
|
'yourself based on another value'), stacklevel=2) |
|
|
|
if filename is not None: |
|
if not os.path.isabs(filename): |
|
filename = os.path.join(current_app.root_path, filename) |
|
if mimetype is None and (filename or attachment_filename): |
|
mimetype = mimetypes.guess_type(filename or attachment_filename)[0] |
|
if mimetype is None: |
|
mimetype = 'application/octet-stream' |
|
|
|
headers = Headers() |
|
if as_attachment: |
|
if attachment_filename is None: |
|
if filename is None: |
|
raise TypeError('filename unavailable, required for ' |
|
'sending as attachment') |
|
attachment_filename = os.path.basename(filename) |
|
headers.add('Content-Disposition', 'attachment', |
|
filename=attachment_filename) |
|
|
|
if current_app.use_x_sendfile and filename: |
|
if file is not None: |
|
file.close() |
|
headers['X-Sendfile'] = filename |
|
data = None |
|
else: |
|
if file is None: |
|
file = open(filename, 'rb') |
|
mtime = os.path.getmtime(filename) |
|
data = wrap_file(request.environ, file) |
|
|
|
rv = current_app.response_class(data, mimetype=mimetype, headers=headers, |
|
direct_passthrough=True) |
|
|
|
# if we know the file modification date, we can store it as the |
|
# the time of the last modification. |
|
if mtime is not None: |
|
rv.last_modified = int(mtime) |
|
|
|
rv.cache_control.public = True |
|
if cache_timeout: |
|
rv.cache_control.max_age = cache_timeout |
|
rv.expires = int(time() + cache_timeout) |
|
|
|
if add_etags and filename is not None: |
|
rv.set_etag('flask-%s-%s-%s' % ( |
|
os.path.getmtime(filename), |
|
os.path.getsize(filename), |
|
adler32( |
|
filename.encode('utf8') if isinstance(filename, unicode) |
|
else filename |
|
) & 0xffffffff |
|
)) |
|
if conditional: |
|
rv = rv.make_conditional(request) |
|
# make sure we don't send x-sendfile for servers that |
|
# ignore the 304 status code for x-sendfile. |
|
if rv.status_code == 304: |
|
rv.headers.pop('x-sendfile', None) |
|
return rv |
|
|
|
|
|
def safe_join(directory, filename): |
|
"""Safely join `directory` and `filename`. |
|
|
|
Example usage:: |
|
|
|
@app.route('/wiki/<path:filename>') |
|
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... |
|
|
|
:param directory: the base directory. |
|
:param filename: the untrusted filename relative to that directory. |
|
:raises: :class:`~werkzeug.exceptions.NotFound` if the resulting path |
|
would fall out of `directory`. |
|
""" |
|
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 |
|
or something similar. |
|
|
|
Example usage:: |
|
|
|
@app.route('/uploads/<path:filename>') |
|
def download_file(filename): |
|
return send_from_directory(app.config['UPLOAD_FOLDER'], |
|
filename, as_attachment=True) |
|
|
|
.. admonition:: Sending files and Performance |
|
|
|
It is strongly recommended to activate either `X-Sendfile` support in |
|
your webserver or (if no authentication happens) to tell the webserver |
|
to serve files for the given path on its own without calling into the |
|
web application for improved performance. |
|
|
|
.. versionadded:: 0.5 |
|
|
|
:param directory: the directory where all the files are stored. |
|
:param filename: the filename relative to that directory to |
|
download. |
|
:param options: optional keyword arguments that are directly |
|
forwarded to :func:`send_file`. |
|
""" |
|
filename = safe_join(directory, filename) |
|
if not os.path.isfile(filename): |
|
raise NotFound() |
|
options.setdefault('conditional', True) |
|
return send_file(filename, **options) |
|
|
|
|
|
def get_root_path(import_name): |
|
"""Returns the path to a package or cwd if that cannot be found. This |
|
returns the path of a package or the folder that contains a module. |
|
|
|
Not to be confused with the package path returned by :func:`find_package`. |
|
""" |
|
loader = pkgutil.get_loader(import_name) |
|
if loader is None or import_name == '__main__': |
|
# import name is not found, or interactive/main module |
|
return os.getcwd() |
|
# For .egg, zipimporter does not have get_filename until Python 2.7. |
|
if hasattr(loader, 'get_filename'): |
|
filepath = loader.get_filename(import_name) |
|
else: |
|
# Fall back to imports. |
|
__import__(import_name) |
|
filepath = sys.modules[import_name].__file__ |
|
# filepath is import_name.py for a module, or __init__.py for a package. |
|
return os.path.dirname(os.path.abspath(filepath)) |
|
|
|
|
|
def find_package(import_name): |
|
"""Finds a package and returns the prefix (or None if the package is |
|
not installed) as well as the folder that contains the package or |
|
module as a tuple. The package path returned is the module that would |
|
have to be added to the pythonpath in order to make it possible to |
|
import the module. The prefix is the path below which a UNIX like |
|
folder structure exists (lib, share etc.). |
|
""" |
|
root_mod_name = import_name.split('.')[0] |
|
loader = pkgutil.get_loader(root_mod_name) |
|
if loader is None or import_name == '__main__': |
|
# import name is not found, or interactive/main module |
|
package_path = os.getcwd() |
|
else: |
|
# For .egg, zipimporter does not have get_filename until Python 2.7. |
|
if hasattr(loader, 'get_filename'): |
|
filename = loader.get_filename(root_mod_name) |
|
elif hasattr(loader, 'archive'): |
|
# zipimporter's loader.archive points to the .egg or .zip |
|
# archive filename is dropped in call to dirname below. |
|
filename = loader.archive |
|
else: |
|
# At least one loader is missing both get_filename and archive: |
|
# Google App Engine's HardenedModulesHook |
|
# |
|
# Fall back to imports. |
|
__import__(import_name) |
|
filename = sys.modules[import_name].__file__ |
|
package_path = os.path.abspath(os.path.dirname(filename)) |
|
# package_path ends with __init__.py for a package |
|
if loader.is_package(root_mod_name): |
|
package_path = os.path.dirname(package_path) |
|
|
|
site_parent, site_folder = os.path.split(package_path) |
|
py_prefix = os.path.abspath(sys.prefix) |
|
if package_path.startswith(py_prefix): |
|
return py_prefix, package_path |
|
elif site_folder.lower() == 'site-packages': |
|
parent, folder = os.path.split(site_parent) |
|
# Windows like installations |
|
if folder.lower() == 'lib': |
|
base_dir = parent |
|
# UNIX like installations |
|
elif os.path.basename(parent).lower() == 'lib': |
|
base_dir = os.path.dirname(parent) |
|
else: |
|
base_dir = site_parent |
|
return base_dir, package_path |
|
return None, package_path |
|
|
|
|
|
class locked_cached_property(object): |
|
"""A decorator that converts a function into a lazy property. The |
|
function wrapped is called the first time to retrieve the result |
|
and then that calculated result is used the next time you access |
|
the value. Works like the one in Werkzeug but has a lock for |
|
thread safety. |
|
""" |
|
|
|
def __init__(self, func, name=None, doc=None): |
|
self.__name__ = name or func.__name__ |
|
self.__module__ = func.__module__ |
|
self.__doc__ = doc or func.__doc__ |
|
self.func = func |
|
self.lock = RLock() |
|
|
|
def __get__(self, obj, type=None): |
|
if obj is None: |
|
return self |
|
with self.lock: |
|
value = obj.__dict__.get(self.__name__, _missing) |
|
if value is _missing: |
|
value = self.func(obj) |
|
obj.__dict__[self.__name__] = value |
|
return value |
|
|
|
|
|
class _PackageBoundObject(object): |
|
|
|
def __init__(self, import_name, template_folder=None): |
|
#: The name of the package or module. Do not change this once |
|
#: it was set by the constructor. |
|
self.import_name = import_name |
|
|
|
#: location of the templates. `None` if templates should not be |
|
#: exposed. |
|
self.template_folder = template_folder |
|
|
|
#: Where is the app root located? |
|
self.root_path = get_root_path(self.import_name) |
|
|
|
self._static_folder = None |
|
self._static_url_path = None |
|
|
|
def _get_static_folder(self): |
|
if self._static_folder is not None: |
|
return os.path.join(self.root_path, self._static_folder) |
|
def _set_static_folder(self, value): |
|
self._static_folder = value |
|
static_folder = property(_get_static_folder, _set_static_folder) |
|
del _get_static_folder, _set_static_folder |
|
|
|
def _get_static_url_path(self): |
|
if self._static_url_path is None: |
|
if self.static_folder is None: |
|
return None |
|
return '/' + os.path.basename(self.static_folder) |
|
return self._static_url_path |
|
def _set_static_url_path(self, value): |
|
self._static_url_path = value |
|
static_url_path = property(_get_static_url_path, _set_static_url_path) |
|
del _get_static_url_path, _set_static_url_path |
|
|
|
@property |
|
def has_static_folder(self): |
|
"""This is `True` if the package bound object's container has a |
|
folder named ``'static'``. |
|
|
|
.. versionadded:: 0.5 |
|
""" |
|
return self.static_folder is not None |
|
|
|
@locked_cached_property |
|
def jinja_loader(self): |
|
"""The Jinja loader for this package bound object. |
|
|
|
.. versionadded:: 0.5 |
|
""" |
|
if self.template_folder is not None: |
|
return FileSystemLoader(os.path.join(self.root_path, |
|
self.template_folder)) |
|
|
|
def get_send_file_options(self, filename): |
|
"""Provides keyword arguments to send to :func:`send_from_directory`. |
|
|
|
This allows subclasses to change the behavior when sending files based |
|
on the filename. For example, to set the cache timeout for .js files |
|
to 60 seconds (note the options are keywords for :func:`send_file`):: |
|
|
|
class MyFlask(flask.Flask): |
|
def get_send_file_options(self, filename): |
|
options = super(MyFlask, self).get_send_file_options(filename) |
|
if filename.lower().endswith('.js'): |
|
options['cache_timeout'] = 60 |
|
options['conditional'] = True |
|
return options |
|
|
|
.. versionadded:: 0.9 |
|
""" |
|
return {} |
|
|
|
def send_static_file(self, filename): |
|
"""Function used internally to send static files from the static |
|
folder to the browser. |
|
|
|
.. versionadded:: 0.5 |
|
""" |
|
if not self.has_static_folder: |
|
raise RuntimeError('No static folder for this object') |
|
return send_from_directory(self.static_folder, filename, |
|
**self.get_send_file_options(filename)) |
|
|
|
def open_resource(self, resource, mode='rb'): |
|
"""Opens a resource from the application's resource folder. To see |
|
how this works, consider the following folder structure:: |
|
|
|
/myapplication.py |
|
/schema.sql |
|
/static |
|
/style.css |
|
/templates |
|
/layout.html |
|
/index.html |
|
|
|
If you want to open the `schema.sql` file you would do the |
|
following:: |
|
|
|
with app.open_resource('schema.sql') as f: |
|
contents = f.read() |
|
do_something_with(contents) |
|
|
|
:param resource: the name of the resource. To access resources within |
|
subfolders use forward slashes as separator. |
|
""" |
|
if mode not in ('r', 'rb'): |
|
raise ValueError('Resources can only be opened for reading') |
|
return open(os.path.join(self.root_path, resource), mode)
|
|
|