Browse Source

Merge remote-tracking branch 'mitsuhiko/master'

pull/307/head
Pedro Algarvio 14 years ago
parent
commit
a9e09ec50e
  1. 11
      CHANGES
  2. 5
      MANIFEST.in
  3. 2
      Makefile
  4. 33
      README
  5. 9
      docs/api.rst
  6. 54
      docs/config.rst
  7. 16
      docs/design.rst
  8. 54
      docs/foreword.rst
  9. 10
      docs/patterns/sqlite3.rst
  10. 10
      docs/reqcontext.rst
  11. 32
      docs/testing.rst
  12. 8
      docs/upgrading.rst
  13. 21
      docs/views.rst
  14. 3
      examples/flaskr/flaskr.py
  15. 3
      examples/minitwit/minitwit.py
  16. 73
      flask/app.py
  17. 2
      flask/ctx.py
  18. 3
      flask/debughelpers.py
  19. 10
      flask/helpers.py
  20. 34
      flask/sessions.py
  21. 2
      flask/signals.py
  22. 100
      flask/testing.py
  23. 194
      flask/testsuite/__init__.py
  24. 1051
      flask/testsuite/basic.py
  25. 509
      flask/testsuite/blueprints.py
  26. 177
      flask/testsuite/config.py
  27. 38
      flask/testsuite/deprecations.py
  28. 38
      flask/testsuite/examples.py
  29. 295
      flask/testsuite/helpers.py
  30. 103
      flask/testsuite/signals.py
  31. 0
      flask/testsuite/static/index.html
  32. 0
      flask/testsuite/templates/_macro.html
  33. 0
      flask/testsuite/templates/context_template.html
  34. 0
      flask/testsuite/templates/escaping_template.html
  35. 0
      flask/testsuite/templates/mail.txt
  36. 0
      flask/testsuite/templates/nested/nested.txt
  37. 0
      flask/testsuite/templates/simple_template.html
  38. 0
      flask/testsuite/templates/template_filter.html
  39. 141
      flask/testsuite/templating.py
  40. 0
      flask/testsuite/test_apps/blueprintapp/__init__.py
  41. 0
      flask/testsuite/test_apps/blueprintapp/apps/__init__.py
  42. 0
      flask/testsuite/test_apps/blueprintapp/apps/admin/__init__.py
  43. 0
      flask/testsuite/test_apps/blueprintapp/apps/admin/static/css/test.css
  44. 0
      flask/testsuite/test_apps/blueprintapp/apps/admin/static/test.txt
  45. 0
      flask/testsuite/test_apps/blueprintapp/apps/admin/templates/admin/index.html
  46. 0
      flask/testsuite/test_apps/blueprintapp/apps/frontend/__init__.py
  47. 0
      flask/testsuite/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html
  48. 4
      flask/testsuite/test_apps/config_module_app.py
  49. 4
      flask/testsuite/test_apps/config_package_app/__init__.py
  50. 0
      flask/testsuite/test_apps/moduleapp/__init__.py
  51. 0
      flask/testsuite/test_apps/moduleapp/apps/__init__.py
  52. 0
      flask/testsuite/test_apps/moduleapp/apps/admin/__init__.py
  53. 0
      flask/testsuite/test_apps/moduleapp/apps/admin/static/css/test.css
  54. 0
      flask/testsuite/test_apps/moduleapp/apps/admin/static/test.txt
  55. 0
      flask/testsuite/test_apps/moduleapp/apps/admin/templates/index.html
  56. 0
      flask/testsuite/test_apps/moduleapp/apps/frontend/__init__.py
  57. 0
      flask/testsuite/test_apps/moduleapp/apps/frontend/templates/index.html
  58. 0
      flask/testsuite/test_apps/subdomaintestmodule/__init__.py
  59. 0
      flask/testsuite/test_apps/subdomaintestmodule/static/hello.txt
  60. 165
      flask/testsuite/testing.py
  61. 117
      flask/testsuite/views.py
  62. 35
      flask/views.py
  63. 3
      run-tests.py
  64. 0
      scripts/flaskext_test.py
  65. 13
      setup.py
  66. 2312
      tests/flask_tests.py

11
CHANGES

@ -32,6 +32,17 @@ Relase date to be decided, codename to be chosen.
conceptionally only instance depending and outside version control so it's
the perfect place to put configuration files etc. For more information
see :ref:`instance-folders`.
- Added the ``APPLICATION_ROOT`` configuration variable.
- Implemented :meth:`~flask.testing.TestClient.session_transaction` to
easily modify sessions from the test environment.
- Refactored test client internally. The ``APPLICATION_ROOT`` configuration
variable as well as ``SERVER_NAME`` are now properly used by the test client
as defaults.
- Added :attr:`flask.views.View.decorators` to support simpler decorating of
pluggable (class based) views.
- Fixed an issue where the test client if used with the with statement did not
trigger the execution of the teardown handlers.
- Added finer control over the session cookie parameters.
Version 0.7.3
-------------

5
MANIFEST.in

@ -1,4 +1,4 @@
include Makefile CHANGES LICENSE AUTHORS
include Makefile CHANGES LICENSE AUTHORS run-tests.py
recursive-include artwork *
recursive-include tests *
recursive-include examples *
@ -9,5 +9,8 @@ recursive-exclude tests *.pyc
recursive-exclude tests *.pyo
recursive-exclude examples *.pyc
recursive-exclude examples *.pyo
recursive-include flask/testsuite/static *
recursive-include flask/testsuite/templates *
recursive-include flask/testsuite/test_apps *
prune docs/_build
prune docs/_themes/.git

2
Makefile

@ -3,7 +3,7 @@
all: clean-pyc test
test:
python setup.py test
python run-tests.py
audit:
python setup.py audit

33
README

@ -11,24 +11,41 @@
~ Is it ready?
A preview release is out now, and I'm hoping for some
input about what you want from a microframework and
how it should look like. Consider the API to slightly
improve over time.
It's still not 1.0 but it's shaping up nicely and is
already widely used. Consider the API to slightly
improve over time but we don't plan on breaking it.
~ What do I need?
Jinja 2.4 and Werkzeug 0.6.1. `easy_install` will
install them for you if you do `easy_install Flask==dev`.
Jinja 2.4 and Werkzeug 0.6.1. `pip` or `easy_install` will
install them for you if you do `easy_install Flask`.
I encourage you to use a virtualenv. Check the docs for
complete installation and usage instructions.
~ Where are the docs?
Go to http://flask.pocoo.org/ for a prebuilt version of
the current documentation. Otherwise build them yourself
Go to http://flask.pocoo.org/docs/ for a prebuilt version
of the current documentation. Otherwise build them yourself
from the sphinx sources in the docs folder.
~ Where are the tests?
Good that you're asking. The tests are in the
flask/testsuite package. To run the tests use the
`run-tests.py` file:
$ python run-tests.py
If it's not enough output for you, you can use the
`--verbose` flag:
$ python run-tests.py --verbose
If you just want one particular testcase to run you can
provide it on the command line:
$ python run-tests.py test_to_run
~ Where can I get help?
Either use the #pocoo IRC channel on irc.freenode.net or

9
docs/api.rst

@ -218,6 +218,15 @@ implementation that Flask is using.
:members:
Test Client
-----------
.. currentmodule:: flask.testing
.. autoclass:: FlaskClient
:members:
Application Globals
-------------------

54
docs/config.rst

@ -70,6 +70,20 @@ The following configuration values are used internally by Flask:
very risky).
``SECRET_KEY`` the secret key
``SESSION_COOKIE_NAME`` the name of the session cookie
``SESSION_COOKIE_DOMAIN`` the domain for the session cookie. If
this is not set, the cookie will be
valid for all subdomains of
``SERVER_NAME``.
``SESSION_COOKIE_PATH`` the path for the session cookie. If
this is not set the cookie will be valid
for all of ``APPLICATION_ROOT`` or if
that is not set for ``'/'``.
``SESSION_COOKIE_HTTPONLY`` controls if the cookie should be set
with the httponly flag. Defaults to
`True`.
``SESSION_COOKIE_SECURE`` controls if the cookie should be set
with the secure flag. Defaults to
`False`.
``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as
:class:`datetime.timedelta` object.
``USE_X_SENDFILE`` enable/disable x-sendfile
@ -77,6 +91,13 @@ The following configuration values are used internally by Flask:
``SERVER_NAME`` the name and port number of the server.
Required for subdomain support (e.g.:
``'localhost:5000'``)
``APPLICATION_ROOT`` If the application does not occupy
a whole domain or subdomain this can
be set to the path where the application
is configured to live. This is for
session cookie as path value. If
domains are used, this should be
``None``.
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
reject incoming requests with a
content length greater than this by
@ -134,7 +155,10 @@ The following configuration values are used internally by Flask:
``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION``
.. versionadded:: 0.8
``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``
``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``,
``APPLICATION_ROOT``, ``SESSION_COOKIE_DOMAIN``,
``SESSION_COOKIE_PATH``, ``SESSION_COOKIE_HTTPONLY``,
``SESSION_COOKIE_SECURE``
Configuring from Files
----------------------
@ -291,25 +315,37 @@ With Flask 0.8 a new attribute was introduced:
version control and be deployment specific. It's the perfect place to
drop things that either change at runtime or configuration files.
To make it easier to put this folder into an ignore list for your version
control system it's called ``instance`` and placed directly next to your
package or module by default. This path can be overridden by specifying
the `instance_path` parameter to your application::
You can either explicitly provide the path of the instance folder when
creating the Flask application or you can let Flask autodetect the
instance folder. For explicit configuration use the `instance_path`
parameter::
app = Flask(__name__, instance_path='/path/to/instance/folder')
Default locations::
Please keep in mind that this path *must* be absolute when provided.
If the `instance_path` parameter is not provided the following default
locations are used:
- Uninstalled module::
Module situation:
/myapp.py
/instance
Package situation:
- Uninstalled package::
/myapp
/__init__.py
/instance
Please keep in mind that this path *must* be absolute when provided.
- Installed module or package::
$PREFIX/lib/python2.X/site-packages/myapp
$PREFIX/var/myapp-instance
``$PREFIX`` is the prefix of your Python installation. This can be
``/usr`` or the path to your virtualenv. You can print the value of
``sys.prefix`` to see what the prefix is set to.
Since the config object provided loading of configuration files from
relative filenames we made it possible to change the loading via filenames

16
docs/design.rst

@ -79,6 +79,22 @@ Furthermore this design makes it possible to use a factory function to
create the application which is very helpful for unittesting and similar
things (:ref:`app-factories`).
The Routing System
------------------
Flask uses the Werkzeug routing system which has was designed to
automatically order routes by complexity. This means that you can declare
routes in arbitrary order and they will still work as expected. This is a
requirement if you want to properly implement decorator based routing
since decorators could be fired in undefined order when the application is
split into multiple modules.
Another design decision with the Werkzeug routing system is that routes
in Werkzeug try to ensure that there is that URLs are unique. Werkzeug
will go quite far with that in that it will automatically redirect to a
canonical URL if a route is ambiguous.
One Template Engine
-------------------

54
docs/foreword.rst

@ -9,27 +9,30 @@ What does "micro" mean?
-----------------------
To me, the "micro" in microframework refers not only to the simplicity and
small size of the framework, but also to the typically limited complexity
and size of applications that are written with the framework. Also the
fact that you can have an entire application in a single Python file. To
be approachable and concise, a microframework sacrifices a few features
that may be necessary in larger or more complex applications.
For example, Flask uses thread-local objects internally so that you don't
have to pass objects around from function to function within a request in
order to stay threadsafe. While this is a really easy approach and saves
you a lot of time, it might also cause some troubles for very large
applications because changes on these thread-local objects can happen
anywhere in the same thread.
Flask provides some tools to deal with the downsides of this approach but
it might be an issue for larger applications because in theory
modifications on these objects might happen anywhere in the same thread.
small size of the framework, but also the fact that it does not make much
decisions for you. While Flask does pick a templating engine for you, we
won't make such decisions for your datastore or other parts.
For us however the term “micro” does not mean that the whole implementation
has to fit into a single Python file.
One of the design decisions with Flask was that simple tasks should be
simple and not take up a lot of code and yet not limit yourself. Because
of that we took a few design choices that some people might find
surprising or unorthodox. For example, Flask uses thread-local objects
internally so that you don't have to pass objects around from function to
function within a request in order to stay threadsafe. While this is a
really easy approach and saves you a lot of time, it might also cause some
troubles for very large applications because changes on these thread-local
objects can happen anywhere in the same thread. In order to solve these
problems we don't hide the thread locals for you but instead embrace them
and provide you with a lot of tools to make it as pleasant as possible to
work with them.
Flask is also based on convention over configuration, which means that
many things are preconfigured. For example, by convention, templates and
static files are in subdirectories within the Python source tree of the
application.
application. While this can be changed you usually don't have to.
The main reason however why Flask is called a "microframework" is the idea
to keep the core simple but extensible. There is no database abstraction
@ -40,22 +43,15 @@ was implemented in Flask itself. There are currently extensions for
object relational mappers, form validation, upload handling, various open
authentication technologies and more.
However Flask is not much code and it is built on a very solid foundation
and with that it is very easy to adapt for large applications. If you are
interested in that, check out the :ref:`becomingbig` chapter.
Since Flask is based on a very solid foundation there is not a lot of code
in Flask itself. As such it's easy to adapt even for lage applications
and we are making sure that you can either configure it as much as
possible by subclassing things or by forking the entire codebase. If you
are interested in that, check out the :ref:`becomingbig` chapter.
If you are curious about the Flask design principles, head over to the
section about :ref:`design`.
A Framework and an Example
--------------------------
Flask is not only a microframework; it is also an example. Based on
Flask, there will be a series of blog posts that explain how to create a
framework. Flask itself is just one way to implement a framework on top
of existing libraries. Unlike many other microframeworks, Flask does not
try to implement everything on its own; it reuses existing code.
Web Development is Dangerous
----------------------------

10
docs/patterns/sqlite3.rst

@ -24,7 +24,15 @@ So here is a simple example of how you can use SQLite 3 with Flask::
@app.teardown_request
def teardown_request(exception):
g.db.close()
if hasattr(g, 'db'):
g.db.close()
.. note::
Please keep in mind that the teardown request functions are always
executed, even if a before-request handler failed or was never
executed. Because of this we have to make sure here that the database
is there before we close it.
Connect on Demand
-----------------

10
docs/reqcontext.rst

@ -131,7 +131,9 @@ understand what is actually happening. The new behavior is quite simple:
4. At the end of the request the :meth:`~flask.Flask.teardown_request`
functions are executed. This always happens, even in case of an
unhandled exception down the road.
unhandled exception down the road or if a before-request handler was
not executed yet or at all (for example in test environments sometimes
you might want to not execute before-request callbacks).
Now what happens on errors? In production mode if an exception is not
caught, the 500 internal server handler is called. In development mode
@ -183,6 +185,12 @@ It's easy to see the behavior from the command line:
this runs after request
>>>
Keep in mind that teardown callbacks are always executed, even if
before-request callbacks were not executed yet but an exception happened.
Certain parts of the test system might also temporarily create a request
context without calling the before-request handlers. Make sure to write
your teardown-request handlers in a way that they will never fail.
.. _notes-on-proxies:
Notes On Proxies

32
docs/testing.rst

@ -273,3 +273,35 @@ is no longer available (because you are trying to use it outside of the actual r
However, keep in mind that any :meth:`~flask.Flask.after_request` functions
are already called at this point so your database connection and
everything involved is probably already closed down.
Accessing and Modifying Sessions
--------------------------------
.. versionadded:: 0.8
Sometimes it can be very helpful to access or modify the sessions from the
test client. Generally there are two ways for this. If you just want to
ensure that a session has certain keys set to certain values you can just
keep the context around and access :data:`flask.session`::
with app.test_client() as c:
rv = c.get('/')
assert flask.session['foo'] == 42
This however does not make it possible to also modify the session or to
access the session before a request was fired. Starting with Flask 0.8 we
provide a so called “session transaction” which simulates the appropriate
calls to open a session in the context of the test client and to modify
it. At the end of the transaction the session is stored. This works
independently of the session backend used::
with app.test_client() as c:
with c.session_transaction() as sess:
sess['a_key'] = 'a value'
# once this is reached the session was stored
Note that in this case you have to use the ``sess`` object instead of the
:data:`flask.session` proxy. The object however itself will provide the
same interface.

8
docs/upgrading.rst

@ -36,6 +36,11 @@ longer have to handle that error to avoid an internal server error showing
up for the user. If you were catching this down explicitly in the past
as `ValueError` you will need to change this.
Due to a bug in the test client Flask 0.7 did not trigger teardown
handlers when the test client was used in a with statement. This was
since fixed but might require some changes in your testsuites if you
relied on this behavior.
Version 0.7
-----------
@ -142,7 +147,8 @@ You are now encouraged to use this instead::
@app.teardown_request
def after_request(exception):
g.db.close()
if hasattr(g, 'db'):
g.db.close()
On the upside this change greatly improves the internal code flow and
makes it easier to customize the dispatching and error handling. This

21
docs/views.rst

@ -135,3 +135,24 @@ easily do that. Each HTTP method maps to a function with the same name
That way you also don't have to provide the
:attr:`~flask.views.View.methods` attribute. It's automatically set based
on the methods defined in the class.
Decorating Views
----------------
Since the view class itself is not the view function that is added to the
routing system it does not make much sense to decorate the class itself.
Instead you either have to decorate the return value of
:meth:`~flask.views.View.as_view` by hand::
view = rate_limited(UserAPI.as_view('users'))
app.add_url_rule('/users/', view_func=view)
Starting with Flask 0.8 there is also an alternative way where you can
specify a list of decorators to apply in the class declaration::
class UserAPI(MethodView):
decorators = [rate_limited]
Due to the implicit self from the caller's perspective you cannot use
regular view decorators on the individual methods of the view however,
keep this in mind.

3
examples/flaskr/flaskr.py

@ -50,7 +50,8 @@ def before_request():
@app.teardown_request
def teardown_request(exception):
"""Closes the database again at the end of the request."""
g.db.close()
if hasattr(g, 'db'):
g.db.close()
@app.route('/')

3
examples/minitwit/minitwit.py

@ -85,7 +85,8 @@ def before_request():
@app.teardown_request
def teardown_request(exception):
"""Closes the database again at the end of the request."""
g.db.close()
if hasattr(g, 'db'):
g.db.close()
@app.route('/')

73
flask/app.py

@ -231,11 +231,16 @@ class Flask(_PackageBoundObject):
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'SESSION_COOKIE_NAME': 'session',
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
'USE_X_SENDFILE': False,
'LOGGER_NAME': None,
'SERVER_NAME': None,
'APPLICATION_ROOT': None,
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'MAX_CONTENT_LENGTH': None,
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False
@ -705,6 +710,8 @@ class Flask(_PackageBoundObject):
rv = c.get('/?vodka=42')
assert request.args['vodka'] == '42'
See :class:`~flask.testing.FlaskClient` for more information.
.. versionchanged:: 0.4
added support for `with` block usage for the client.
@ -1217,14 +1224,24 @@ class Flask(_PackageBoundObject):
else:
raise e
self.logger.exception('Exception on %s [%s]' % (
request.path,
request.method
))
self.log_exception((exc_type, exc_value, tb))
if handler is None:
return InternalServerError()
return handler(e)
def log_exception(self, exc_info):
"""Logs an exception. This is called by :meth:`handle_exception`
if debugging is disabled and right before the handler is called.
The default implementation logs the exception as error on the
:attr:`logger`.
.. versionadded:: 0.8
"""
self.logger.error('Exception on %s [%s]' % (
request.path,
request.method
), exc_info=exc_info)
def raise_routing_exception(self, request):
"""Exceptions that are recording during routing are reraised with
this method. During debug we are not reraising redirect requests
@ -1306,17 +1323,18 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.7
"""
# This would be nicer in Werkzeug 0.7, which however currently
# is not released. Werkzeug 0.7 provides a method called
# allowed_methods() that returns all methods that are valid for
# a given path.
methods = []
try:
_request_ctx_stack.top.url_adapter.match(method='--')
except MethodNotAllowed, e:
methods = e.valid_methods
except HTTPException, e:
pass
adapter = _request_ctx_stack.top.url_adapter
if hasattr(adapter, 'allowed_methods'):
methods = adapter.allowed_methods()
else:
# fallback for Werkzeug < 0.7
methods = []
try:
adapter.match(method='--')
except MethodNotAllowed, e:
methods = e.valid_methods
except HTTPException, e:
pass
rv = self.response_class()
rv.allow.update(methods)
return rv
@ -1387,7 +1405,7 @@ class Flask(_PackageBoundObject):
This also triggers the :meth:`url_value_processor` functions before
the actualy :meth:`before_request` functions are called.
"""
bp = request.blueprint
bp = _request_ctx_stack.top.request.blueprint
funcs = self.url_value_preprocessors.get(None, ())
if bp is not None and bp in self.url_value_preprocessors:
@ -1437,7 +1455,7 @@ class Flask(_PackageBoundObject):
tighter control over certain resources under testing environments.
"""
funcs = reversed(self.teardown_request_funcs.get(None, ()))
bp = request.blueprint
bp = _request_ctx_stack.top.request.blueprint
if bp is not None and bp in self.teardown_request_funcs:
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
exc = sys.exc_info()[1]
@ -1482,19 +1500,12 @@ class Flask(_PackageBoundObject):
:func:`werkzeug.test.EnvironBuilder` for more information, this
function accepts the same arguments).
"""
from werkzeug.test import create_environ
environ_overrides = kwargs.setdefault('environ_overrides', {})
if self.config.get('SERVER_NAME'):
server_name = self.config.get('SERVER_NAME')
if ':' not in server_name:
http_host, http_port = server_name, '80'
else:
http_host, http_port = server_name.split(':', 1)
environ_overrides.setdefault('SERVER_NAME', server_name)
environ_overrides.setdefault('HTTP_HOST', server_name)
environ_overrides.setdefault('SERVER_PORT', http_port)
return self.request_context(create_environ(*args, **kwargs))
from flask.testing import make_test_environ_builder
builder = make_test_environ_builder(self, *args, **kwargs)
try:
return self.request_context(builder.get_environ())
finally:
builder.close()
def wsgi_app(self, environ, start_response):
"""The actual WSGI application. This is not implemented in

2
flask/ctx.py

@ -91,7 +91,7 @@ class RequestContext(object):
self.match_request()
# Support for deprecated functionality. This is doing away with
# XXX: Support for deprecated functionality. This is doing away with
# Flask 1.0
blueprint = self.request.blueprint
if blueprint is not None:

3
flask/debughelpers.py

@ -54,7 +54,8 @@ class FormDataRoutingRedirect(AssertionError):
buf.append(' Make sure to directly send your %s-request to this URL '
'since we can\'t make browsers or HTTP clients redirect '
'with form data.' % request.method)
'with form data reliably or without user interaction.' %
request.method)
buf.append('\n\nNote: this exception is only raised in debug mode')
AssertionError.__init__(self, ''.join(buf).encode('utf-8'))

10
flask/helpers.py

@ -145,6 +145,13 @@ def make_response(*args):
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
@ -477,6 +484,8 @@ def get_root_path(import_name):
directory = os.path.dirname(sys.modules[import_name].__file__)
return os.path.abspath(directory)
except AttributeError:
# this is necessary in case we are running from the interactive
# python shell. It will never be used for production code however
return os.getcwd()
@ -492,6 +501,7 @@ def find_package(import_name):
root_mod = sys.modules[import_name.split('.')[0]]
package_path = getattr(root_mod, '__file__', None)
if package_path is None:
# support for the interactive python shell
package_path = os.getcwd()
else:
package_path = os.path.abspath(os.path.dirname(package_path))

34
flask/sessions.py

@ -123,10 +123,34 @@ class SessionInterface(object):
"""Helpful helper method that returns the cookie domain that should
be used for the session cookie if session cookies are used.
"""
if app.config['SESSION_COOKIE_DOMAIN'] is not None:
return app.config['SESSION_COOKIE_DOMAIN']
if app.config['SERVER_NAME'] is not None:
# chop of the port which is usually not supported by browsers
return '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0]
def get_cookie_path(self, app):
"""Returns the path for which the cookie should be valid. The
default implementation uses the value from the SESSION_COOKIE_PATH``
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
uses ``/`` if it's `None`.
"""
return app.config['SESSION_COOKIE_PATH'] or \
app.config['APPLICATION_ROOT'] or '/'
def get_cookie_httponly(self, app):
"""Returns True if the session cookie should be httponly. This
currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
config var.
"""
return app.config['SESSION_COOKIE_HTTPONLY']
def get_cookie_secure(self, app):
"""Returns True if the cookie should be secure. This currently
just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
"""
return app.config['SESSION_COOKIE_SECURE']
def get_expiration_time(self, app, session):
"""A helper method that returns an expiration date for the session
or `None` if the session is linked to the browser session. The
@ -169,9 +193,13 @@ class SecureCookieSessionInterface(SessionInterface):
def save_session(self, app, session, response):
expires = self.get_expiration_time(app, session)
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
if session.modified and not session:
response.delete_cookie(app.session_cookie_name,
response.delete_cookie(app.session_cookie_name, path=path,
domain=domain)
else:
session.save_cookie(response, app.session_cookie_name,
expires=expires, httponly=True, domain=domain)
session.save_cookie(response, app.session_cookie_name, path=path,
expires=expires, httponly=httponly,
secure=secure, domain=domain)

2
flask/signals.py

@ -34,7 +34,7 @@ except ImportError:
'not installed.')
send = lambda *a, **kw: None
connect = disconnect = has_receivers_for = receivers_for = \
temporarily_connected_to = _fail
temporarily_connected_to = connected_to = _fail
del _fail
# the namespace for code signals. If you are not flask code, do

100
flask/testing.py

@ -10,44 +10,91 @@
:license: BSD, see LICENSE for more details.
"""
from contextlib import contextmanager
from werkzeug.test import Client, EnvironBuilder
from flask import _request_ctx_stack
def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs):
"""Creates a new test builder with some application defaults thrown in."""
http_host = app.config.get('SERVER_NAME')
app_root = app.config.get('APPLICATION_ROOT')
if base_url is None:
base_url = 'http://%s/' % (http_host or 'localhost')
if app_root:
base_url += app_root.lstrip('/')
return EnvironBuilder(path, base_url, *args, **kwargs)
class FlaskClient(Client):
"""Works like a regular Werkzeug test client but has some
knowledge about how Flask works to defer the cleanup of the
request context stack to the end of a with body when used
in a with statement.
"""Works like a regular Werkzeug test client but has some knowledge about
how Flask works to defer the cleanup of the request context stack to the
end of a with body when used in a with statement. For general information
about how to use this class refer to :class:`werkzeug.test.Client`.
Basic usage is outlined in the :ref:`testing` chapter.
"""
preserve_context = context_preserved = False
@contextmanager
def session_transaction(self, *args, **kwargs):
"""When used in combination with a with statement this opens a
session transaction. This can be used to modify the session that
the test client uses. Once the with block is left the session is
stored back.
with client.session_transaction() as session:
session['value'] = 42
Internally this is implemented by going through a temporary test
request context and since session handling could depend on
request variables this function accepts the same arguments as
:meth:`~flask.Flask.test_request_context` which are directly
passed through.
"""
if self.cookie_jar is None:
raise RuntimeError('Session transactions only make sense '
'with cookies enabled.')
app = self.application
environ_overrides = kwargs.pop('environ_overrides', {})
self.cookie_jar.inject_wsgi(environ_overrides)
outer_reqctx = _request_ctx_stack.top
with app.test_request_context(*args, **kwargs) as c:
sess = app.open_session(c.request)
if sess is None:
raise RuntimeError('Session backend did not open a session. '
'Check the configuration')
# Since we have to open a new request context for the session
# handling we want to make sure that we hide out own context
# from the caller. By pushing the original request context
# (or None) on top of this and popping it we get exactly that
# behavior. It's important to not use the push and pop
# methods of the actual request context object since that would
# mean that cleanup handlers are called
_request_ctx_stack.push(outer_reqctx)
try:
yield sess
finally:
_request_ctx_stack.pop()
resp = app.response_class()
if not app.session_interface.is_null_session(sess):
app.save_session(sess, resp)
headers = resp.get_wsgi_headers(c.request.environ)
self.cookie_jar.extract_wsgi(c.request.environ, headers)
def open(self, *args, **kwargs):
if self.context_preserved:
_request_ctx_stack.pop()
self.context_preserved = False
self._pop_reqctx_if_necessary()
kwargs.setdefault('environ_overrides', {}) \
['flask._preserve_context'] = self.preserve_context
as_tuple = kwargs.pop('as_tuple', False)
buffered = kwargs.pop('buffered', False)
follow_redirects = kwargs.pop('follow_redirects', False)
builder = make_test_environ_builder(self.application, *args, **kwargs)
builder = EnvironBuilder(*args, **kwargs)
if self.application.config.get('SERVER_NAME'):
server_name = self.application.config.get('SERVER_NAME')
if ':' not in server_name:
http_host, http_port = server_name, None
else:
http_host, http_port = server_name.split(':', 1)
if builder.base_url == 'http://localhost/':
# Default Generated Base URL
if http_port != None:
builder.host = http_host + ':' + http_port
else:
builder.host = http_host
old = _request_ctx_stack.top
try:
return Client.open(self, builder,
@ -58,10 +105,19 @@ class FlaskClient(Client):
self.context_preserved = _request_ctx_stack.top is not old
def __enter__(self):
if self.preserve_context:
raise RuntimeError('Cannot nest client invocations')
self.preserve_context = True
return self
def __exit__(self, exc_type, exc_value, tb):
self.preserve_context = False
self._pop_reqctx_if_necessary()
def _pop_reqctx_if_necessary(self):
if self.context_preserved:
_request_ctx_stack.pop()
# we have to use _request_ctx_stack.top.pop instead of
# _request_ctx_stack.pop since we want teardown handlers
# to be executed.
_request_ctx_stack.top.pop()
self.context_preserved = False

194
flask/testsuite/__init__.py

@ -0,0 +1,194 @@
# -*- coding: utf-8 -*-
"""
flask.testsuite
~~~~~~~~~~~~~~~
Tests Flask itself. The majority of Flask is already tested
as part of Werkzeug.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import os
import sys
import flask
import warnings
import unittest
from StringIO import StringIO
from functools import update_wrapper
from contextlib import contextmanager
from werkzeug.utils import import_string, find_modules
def add_to_path(path):
"""Adds an entry to sys.path_info if it's not already there."""
if not os.path.isdir(path):
raise RuntimeError('Tried to add nonexisting path')
def _samefile(x, y):
try:
return os.path.samefile(x, y)
except (IOError, OSError):
return False
for entry in sys.path:
try:
if os.path.samefile(path, entry):
return
except (OSError, IOError):
pass
sys.path.append(path)
def iter_suites():
"""Yields all testsuites."""
for module in find_modules(__name__):
mod = import_string(module)
if hasattr(mod, 'suite'):
yield mod.suite()
def find_all_tests(suite):
"""Yields all the tests and their names from a given suite."""
suites = [suite]
while suites:
s = suites.pop()
try:
suites.extend(s)
except TypeError:
yield s, '%s.%s.%s' % (
s.__class__.__module__,
s.__class__.__name__,
s._testMethodName
)
@contextmanager
def catch_warnings():
"""Catch warnings in a with block in a list"""
# make sure deprecation warnings are active in tests
warnings.simplefilter('default', category=DeprecationWarning)
filters = warnings.filters
warnings.filters = filters[:]
old_showwarning = warnings.showwarning
log = []
def showwarning(message, category, filename, lineno, file=None, line=None):
log.append(locals())
try:
warnings.showwarning = showwarning
yield log
finally:
warnings.filters = filters
warnings.showwarning = old_showwarning
@contextmanager
def catch_stderr():
"""Catch stderr in a StringIO"""
old_stderr = sys.stderr
sys.stderr = rv = StringIO()
try:
yield rv
finally:
sys.stderr = old_stderr
def emits_module_deprecation_warning(f):
def new_f(self, *args, **kwargs):
with catch_warnings() as log:
f(self, *args, **kwargs)
self.assert_(log, 'expected deprecation warning')
for entry in log:
self.assert_('Modules are deprecated' in str(entry['message']))
return update_wrapper(new_f, f)
class FlaskTestCase(unittest.TestCase):
"""Baseclass for all the tests that Flask uses. Use these methods
for testing instead of the camelcased ones in the baseclass for
consistency.
"""
def ensure_clean_request_context(self):
# make sure we're not leaking a request context since we are
# testing flask internally in debug mode in a few cases
self.assert_equal(flask._request_ctx_stack.top, None)
def setup(self):
pass
def teardown(self):
pass
def setUp(self):
self.setup()
def tearDown(self):
unittest.TestCase.tearDown(self)
self.ensure_clean_request_context()
self.teardown()
def assert_equal(self, x, y):
return self.assertEqual(x, y)
class BetterLoader(unittest.TestLoader):
"""A nicer loader that solves two problems. First of all we are setting
up tests from different sources and we're doing this programmatically
which breaks the default loading logic so this is required anyways.
Secondly this loader has a nicer interpolation for test names than the
default one so you can just do ``run-tests.py ViewTestCase`` and it
will work.
"""
def getRootSuite(self):
return suite()
def loadTestsFromName(self, name, module=None):
root = self.getRootSuite()
if name == 'suite':
return root
all_tests = []
for testcase, testname in find_all_tests(root):
if testname == name or \
testname.endswith('.' + name) or \
('.' + name + '.') in testname or \
testname.startswith(name + '.'):
all_tests.append(testcase)
if not all_tests:
raise LookupError('could not find test case for "%s"' % name)
if len(all_tests) == 1:
return all_tests[0]
rv = unittest.TestSuite()
for test in all_tests:
rv.addTest(test)
return rv
def setup_path():
add_to_path(os.path.abspath(os.path.join(
os.path.dirname(__file__), 'test_apps')))
def suite():
"""A testsuite that has all the Flask tests. You can use this
function to integrate the Flask tests into your own testsuite
in case you want to test that monkeypatches to Flask do not
break it.
"""
setup_path()
suite = unittest.TestSuite()
for other_suite in iter_suites():
suite.addTest(other_suite)
return suite
def main():
"""Runs the testsuite as command line application."""
try:
unittest.main(testLoader=BetterLoader(), defaultTest='suite')
except Exception, e:
print 'Error: %s' % e

1051
flask/testsuite/basic.py

File diff suppressed because it is too large Load Diff

509
flask/testsuite/blueprints.py

@ -0,0 +1,509 @@
# -*- coding: utf-8 -*-
"""
flask.testsuite.blueprints
~~~~~~~~~~~~~~~~~~~~~~~~~~
Blueprints (and currently modules)
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import flask
import unittest
import warnings
from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning
from werkzeug.exceptions import NotFound
from jinja2 import TemplateNotFound
# import moduleapp here because it uses deprecated features and we don't
# want to see the warnings
warnings.simplefilter('ignore', DeprecationWarning)
from moduleapp import app as moduleapp
warnings.simplefilter('default', DeprecationWarning)
class ModuleTestCase(FlaskTestCase):
@emits_module_deprecation_warning
def test_basic_module(self):
app = flask.Flask(__name__)
admin = flask.Module(__name__, 'admin', url_prefix='/admin')
@admin.route('/')
def admin_index():
return 'admin index'
@admin.route('/login')
def admin_login():
return 'admin login'
@admin.route('/logout')
def admin_logout():
return 'admin logout'
@app.route('/')
def index():
return 'the index'
app.register_module(admin)
c = app.test_client()
self.assert_equal(c.get('/').data, 'the index')
self.assert_equal(c.get('/admin/').data, 'admin index')
self.assert_equal(c.get('/admin/login').data, 'admin login')
self.assert_equal(c.get('/admin/logout').data, 'admin logout')
@emits_module_deprecation_warning
def test_default_endpoint_name(self):
app = flask.Flask(__name__)
mod = flask.Module(__name__, 'frontend')
def index():
return 'Awesome'
mod.add_url_rule('/', view_func=index)
app.register_module(mod)
rv = app.test_client().get('/')
self.assert_equal(rv.data, 'Awesome')
with app.test_request_context():
self.assert_equal(flask.url_for('frontend.index'), '/')
@emits_module_deprecation_warning
def test_request_processing(self):
catched = []
app = flask.Flask(__name__)
admin = flask.Module(__name__, 'admin', url_prefix='/admin')
@admin.before_request
def before_admin_request():
catched.append('before-admin')
@admin.after_request
def after_admin_request(response):
catched.append('after-admin')
return response
@admin.route('/')
def admin_index():
return 'the admin'
@app.before_request
def before_request():
catched.append('before-app')
@app.after_request
def after_request(response):
catched.append('after-app')
return response
@app.route('/')
def index():
return 'the index'
app.register_module(admin)
c = app.test_client()
self.assert_equal(c.get('/').data, 'the index')
self.assert_equal(catched, ['before-app', 'after-app'])
del catched[:]
self.assert_equal(c.get('/admin/').data, 'the admin')
self.assert_equal(catched, ['before-app', 'before-admin',
'after-admin', 'after-app'])
@emits_module_deprecation_warning
def test_context_processors(self):
app = flask.Flask(__name__)
admin = flask.Module(__name__, 'admin', url_prefix='/admin')
@app.context_processor
def inject_all_regualr():
return {'a': 1}
@admin.context_processor
def inject_admin():
return {'b': 2}
@admin.app_context_processor
def inject_all_module():
return {'c': 3}
@app.route('/')
def index():
return flask.render_template_string('{{ a }}{{ b }}{{ c }}')
@admin.route('/')
def admin_index():
return flask.render_template_string('{{ a }}{{ b }}{{ c }}')
app.register_module(admin)
c = app.test_client()
self.assert_equal(c.get('/').data, '13')
self.assert_equal(c.get('/admin/').data, '123')
@emits_module_deprecation_warning
def test_late_binding(self):
app = flask.Flask(__name__)
admin = flask.Module(__name__, 'admin')
@admin.route('/')
def index():
return '42'
app.register_module(admin, url_prefix='/admin')
self.assert_equal(app.test_client().get('/admin/').data, '42')
@emits_module_deprecation_warning
def test_error_handling(self):
app = flask.Flask(__name__)
admin = flask.Module(__name__, 'admin')
@admin.app_errorhandler(404)
def not_found(e):
return 'not found', 404
@admin.app_errorhandler(500)
def internal_server_error(e):
return 'internal server error', 500
@admin.route('/')
def index():
flask.abort(404)
@admin.route('/error')
def error():
1 // 0
app.register_module(admin)
c = app.test_client()
rv = c.get('/')
self.assert_equal(rv.status_code, 404)
self.assert_equal(rv.data, 'not found')
rv = c.get('/error')
self.assert_equal(rv.status_code, 500)
self.assert_equal('internal server error', rv.data)
def test_templates_and_static(self):
app = moduleapp
app.testing = True
c = app.test_client()
rv = c.get('/')
self.assert_equal(rv.data, 'Hello from the Frontend')
rv = c.get('/admin/')
self.assert_equal(rv.data, 'Hello from the Admin')
rv = c.get('/admin/index2')
self.assert_equal(rv.data, 'Hello from the Admin')
rv = c.get('/admin/static/test.txt')
self.assert_equal(rv.data.strip(), 'Admin File')
rv = c.get('/admin/static/css/test.css')
self.assert_equal(rv.data.strip(), '/* nested file */')
with app.test_request_context():
self.assert_equal(flask.url_for('admin.static', filename='test.txt'),
'/admin/static/test.txt')
with app.test_request_context():
try:
flask.render_template('missing.html')
except TemplateNotFound, e:
self.assert_equal(e.name, 'missing.html')
else:
self.assert_(0, 'expected exception')
with flask.Flask(__name__).test_request_context():
self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested')
def test_safe_access(self):
app = moduleapp
with app.test_request_context():
f = app.view_functions['admin.static']
try:
f('/etc/passwd')
except NotFound:
pass
else:
self.assert_(0, 'expected exception')
try:
f('../__init__.py')
except NotFound:
pass
else:
self.assert_(0, 'expected exception')
# testcase for a security issue that may exist on windows systems
import os
import ntpath
old_path = os.path
os.path = ntpath
try:
try:
f('..\\__init__.py')
except NotFound:
pass
else:
self.assert_(0, 'expected exception')
finally:
os.path = old_path
@emits_module_deprecation_warning
def test_endpoint_decorator(self):
from werkzeug.routing import Submount, Rule
from flask import Module
app = flask.Flask(__name__)
app.testing = True
app.url_map.add(Submount('/foo', [
Rule('/bar', endpoint='bar'),
Rule('/', endpoint='index')
]))
module = Module(__name__, __name__)
@module.endpoint('bar')
def bar():
return 'bar'
@module.endpoint('index')
def index():
return 'index'
app.register_module(module)
c = app.test_client()
self.assert_equal(c.get('/foo/').data, 'index')
self.assert_equal(c.get('/foo/bar').data, 'bar')
class BlueprintTestCase(FlaskTestCase):
def test_blueprint_specific_error_handling(self):
frontend = flask.Blueprint('frontend', __name__)
backend = flask.Blueprint('backend', __name__)
sideend = flask.Blueprint('sideend', __name__)
@frontend.errorhandler(403)
def frontend_forbidden(e):
return 'frontend says no', 403
@frontend.route('/frontend-no')
def frontend_no():
flask.abort(403)
@backend.errorhandler(403)
def backend_forbidden(e):
return 'backend says no', 403
@backend.route('/backend-no')
def backend_no():
flask.abort(403)
@sideend.route('/what-is-a-sideend')
def sideend_no():
flask.abort(403)
app = flask.Flask(__name__)
app.register_blueprint(frontend)
app.register_blueprint(backend)
app.register_blueprint(sideend)
@app.errorhandler(403)
def app_forbidden(e):
return 'application itself says no', 403
c = app.test_client()
self.assert_equal(c.get('/frontend-no').data, 'frontend says no')
self.assert_equal(c.get('/backend-no').data, 'backend says no')
self.assert_equal(c.get('/what-is-a-sideend').data, 'application itself says no')
def test_blueprint_url_definitions(self):
bp = flask.Blueprint('test', __name__)
@bp.route('/foo', defaults={'baz': 42})
def foo(bar, baz):
return '%s/%d' % (bar, baz)
@bp.route('/bar')
def bar(bar):
return unicode(bar)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23})
app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19})
c = app.test_client()
self.assert_equal(c.get('/1/foo').data, u'23/42')
self.assert_equal(c.get('/2/foo').data, u'19/42')
self.assert_equal(c.get('/1/bar').data, u'23')
self.assert_equal(c.get('/2/bar').data, u'19')
def test_blueprint_url_processors(self):
bp = flask.Blueprint('frontend', __name__, url_prefix='/<lang_code>')
@bp.url_defaults
def add_language_code(endpoint, values):
values.setdefault('lang_code', flask.g.lang_code)
@bp.url_value_preprocessor
def pull_lang_code(endpoint, values):
flask.g.lang_code = values.pop('lang_code')
@bp.route('/')
def index():
return flask.url_for('.about')
@bp.route('/about')
def about():
return flask.url_for('.index')
app = flask.Flask(__name__)
app.register_blueprint(bp)
c = app.test_client()
self.assert_equal(c.get('/de/').data, '/de/about')
self.assert_equal(c.get('/de/about').data, '/de/')
def test_templates_and_static(self):
from blueprintapp import app
c = app.test_client()
rv = c.get('/')
self.assert_equal(rv.data, 'Hello from the Frontend')
rv = c.get('/admin/')
self.assert_equal(rv.data, 'Hello from the Admin')
rv = c.get('/admin/index2')
self.assert_equal(rv.data, 'Hello from the Admin')
rv = c.get('/admin/static/test.txt')
self.assert_equal(rv.data.strip(), 'Admin File')
rv = c.get('/admin/static/css/test.css')
self.assert_equal(rv.data.strip(), '/* nested file */')
with app.test_request_context():
self.assert_equal(flask.url_for('admin.static', filename='test.txt'),
'/admin/static/test.txt')
with app.test_request_context():
try:
flask.render_template('missing.html')
except TemplateNotFound, e:
self.assert_equal(e.name, 'missing.html')
else:
self.assert_(0, 'expected exception')
with flask.Flask(__name__).test_request_context():
self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested')
def test_templates_list(self):
from blueprintapp import app
templates = sorted(app.jinja_env.list_templates())
self.assert_equal(templates, ['admin/index.html',
'frontend/index.html'])
def test_dotted_names(self):
frontend = flask.Blueprint('myapp.frontend', __name__)
backend = flask.Blueprint('myapp.backend', __name__)
@frontend.route('/fe')
def frontend_index():
return flask.url_for('myapp.backend.backend_index')
@frontend.route('/fe2')
def frontend_page2():
return flask.url_for('.frontend_index')
@backend.route('/be')
def backend_index():
return flask.url_for('myapp.frontend.frontend_index')
app = flask.Flask(__name__)
app.register_blueprint(frontend)
app.register_blueprint(backend)
c = app.test_client()
self.assert_equal(c.get('/fe').data.strip(), '/be')
self.assert_equal(c.get('/fe2').data.strip(), '/fe')
self.assert_equal(c.get('/be').data.strip(), '/fe')
def test_empty_url_defaults(self):
bp = flask.Blueprint('bp', __name__)
@bp.route('/', defaults={'page': 1})
@bp.route('/page/<int:page>')
def something(page):
return str(page)
app = flask.Flask(__name__)
app.register_blueprint(bp)
c = app.test_client()
self.assert_equal(c.get('/').data, '1')
self.assert_equal(c.get('/page/2').data, '2')
def test_route_decorator_custom_endpoint(self):
bp = flask.Blueprint('bp', __name__)
@bp.route('/foo')
def foo():
return flask.request.endpoint
@bp.route('/bar', endpoint='bar')
def foo_bar():
return flask.request.endpoint
@bp.route('/bar/123', endpoint='123')
def foo_bar_foo():
return flask.request.endpoint
@bp.route('/bar/foo')
def bar_foo():
return flask.request.endpoint
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.request.endpoint
c = app.test_client()
self.assertEqual(c.get('/').data, 'index')
self.assertEqual(c.get('/py/foo').data, 'bp.foo')
self.assertEqual(c.get('/py/bar').data, 'bp.bar')
self.assertEqual(c.get('/py/bar/123').data, 'bp.123')
self.assertEqual(c.get('/py/bar/foo').data, 'bp.bar_foo')
def test_route_decorator_custom_endpoint_with_dots(self):
bp = flask.Blueprint('bp', __name__)
@bp.route('/foo')
def foo():
return flask.request.endpoint
try:
@bp.route('/bar', endpoint='bar.bar')
def foo_bar():
return flask.request.endpoint
except AssertionError:
pass
else:
raise AssertionError('expected AssertionError not raised')
try:
@bp.route('/bar/123', endpoint='bar.123')
def foo_bar_foo():
return flask.request.endpoint
except AssertionError:
pass
else:
raise AssertionError('expected AssertionError not raised')
def foo_foo_foo():
pass
self.assertRaises(
AssertionError,
lambda: bp.add_url_rule(
'/bar/123', endpoint='bar.123', view_func=foo_foo_foo
)
)
self.assertRaises(
AssertionError,
bp.route('/bar/123', endpoint='bar.123'),
lambda: None
)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
c = app.test_client()
self.assertEqual(c.get('/py/foo').data, 'bp.foo')
# The rule's din't actually made it through
rv = c.get('/py/bar')
assert rv.status_code == 404
rv = c.get('/py/bar/123')
assert rv.status_code == 404
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(BlueprintTestCase))
suite.addTest(unittest.makeSuite(ModuleTestCase))
return suite

177
flask/testsuite/config.py

@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
"""
flask.testsuite.config
~~~~~~~~~~~~~~~~~~~~~~
Configuration and instances.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import os
import sys
import flask
import unittest
from flask.testsuite import FlaskTestCase
# config keys used for the ConfigTestCase
TEST_KEY = 'foo'
SECRET_KEY = 'devkey'
class ConfigTestCase(FlaskTestCase):
def common_object_test(self, app):
self.assert_equal(app.secret_key, 'devkey')
self.assert_equal(app.config['TEST_KEY'], 'foo')
self.assert_('ConfigTestCase' not in app.config)
def test_config_from_file(self):
app = flask.Flask(__name__)
app.config.from_pyfile(__file__.rsplit('.', 1)[0] + '.py')
self.common_object_test(app)
def test_config_from_object(self):
app = flask.Flask(__name__)
app.config.from_object(__name__)
self.common_object_test(app)
def test_config_from_class(self):
class Base(object):
TEST_KEY = 'foo'
class Test(Base):
SECRET_KEY = 'devkey'
app = flask.Flask(__name__)
app.config.from_object(Test)
self.common_object_test(app)
def test_config_from_envvar(self):
env = os.environ
try:
os.environ = {}
app = flask.Flask(__name__)
try:
app.config.from_envvar('FOO_SETTINGS')
except RuntimeError, e:
self.assert_("'FOO_SETTINGS' is not set" in str(e))
else:
self.assert_(0, 'expected exception')
self.assert_(not app.config.from_envvar('FOO_SETTINGS', silent=True))
os.environ = {'FOO_SETTINGS': __file__.rsplit('.', 1)[0] + '.py'}
self.assert_(app.config.from_envvar('FOO_SETTINGS'))
self.common_object_test(app)
finally:
os.environ = env
def test_config_missing(self):
app = flask.Flask(__name__)
try:
app.config.from_pyfile('missing.cfg')
except IOError, e:
msg = str(e)
self.assert_(msg.startswith('[Errno 2] Unable to load configuration '
'file (No such file or directory):'))
self.assert_(msg.endswith("missing.cfg'"))
else:
self.assert_(0, 'expected config')
self.assert_(not app.config.from_pyfile('missing.cfg', silent=True))
class InstanceTestCase(FlaskTestCase):
def test_explicit_instance_paths(self):
here = os.path.abspath(os.path.dirname(__file__))
try:
flask.Flask(__name__, instance_path='instance')
except ValueError, e:
self.assert_('must be absolute' in str(e))
else:
self.fail('Expected value error')
app = flask.Flask(__name__, instance_path=here)
self.assert_equal(app.instance_path, here)
def test_uninstalled_module_paths(self):
from config_module_app import app
here = os.path.abspath(os.path.dirname(__file__))
self.assert_equal(app.instance_path, os.path.join(here, 'test_apps', 'instance'))
def test_uninstalled_package_paths(self):
from config_package_app import app
here = os.path.abspath(os.path.dirname(__file__))
self.assert_equal(app.instance_path, os.path.join(here, 'test_apps', 'instance'))
def test_installed_module_paths(self):
import types
expected_prefix = os.path.abspath('foo')
mod = types.ModuleType('myapp')
mod.__file__ = os.path.join(expected_prefix, 'lib', 'python2.5',
'site-packages', 'myapp.py')
sys.modules['myapp'] = mod
try:
mod.app = flask.Flask(mod.__name__)
self.assert_equal(mod.app.instance_path,
os.path.join(expected_prefix, 'var',
'myapp-instance'))
finally:
sys.modules['myapp'] = None
def test_installed_package_paths(self):
import types
expected_prefix = os.path.abspath('foo')
package_path = os.path.join(expected_prefix, 'lib', 'python2.5',
'site-packages', 'myapp')
mod = types.ModuleType('myapp')
mod.__path__ = [package_path]
mod.__file__ = os.path.join(package_path, '__init__.py')
sys.modules['myapp'] = mod
try:
mod.app = flask.Flask(mod.__name__)
self.assert_equal(mod.app.instance_path,
os.path.join(expected_prefix, 'var',
'myapp-instance'))
finally:
sys.modules['myapp'] = None
def test_prefix_installed_paths(self):
import types
expected_prefix = os.path.abspath(sys.prefix)
package_path = os.path.join(expected_prefix, 'lib', 'python2.5',
'site-packages', 'myapp')
mod = types.ModuleType('myapp')
mod.__path__ = [package_path]
mod.__file__ = os.path.join(package_path, '__init__.py')
sys.modules['myapp'] = mod
try:
mod.app = flask.Flask(mod.__name__)
self.assert_equal(mod.app.instance_path,
os.path.join(expected_prefix, 'var',
'myapp-instance'))
finally:
sys.modules['myapp'] = None
def test_egg_installed_paths(self):
import types
expected_prefix = os.path.abspath(sys.prefix)
package_path = os.path.join(expected_prefix, 'lib', 'python2.5',
'site-packages', 'MyApp.egg', 'myapp')
mod = types.ModuleType('myapp')
mod.__path__ = [package_path]
mod.__file__ = os.path.join(package_path, '__init__.py')
sys.modules['myapp'] = mod
try:
mod.app = flask.Flask(mod.__name__)
self.assert_equal(mod.app.instance_path,
os.path.join(expected_prefix, 'var',
'myapp-instance'))
finally:
sys.modules['myapp'] = None
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ConfigTestCase))
suite.addTest(unittest.makeSuite(InstanceTestCase))
return suite

38
flask/testsuite/deprecations.py

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
"""
flask.testsuite.deprecations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests deprecation support.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import flask
import unittest
from flask.testsuite import FlaskTestCase, catch_warnings
class DeprecationsTestCase(FlaskTestCase):
def test_init_jinja_globals(self):
class MyFlask(flask.Flask):
def init_jinja_globals(self):
self.jinja_env.globals['foo'] = '42'
with catch_warnings() as log:
app = MyFlask(__name__)
@app.route('/')
def foo():
return app.jinja_env.globals['foo']
c = app.test_client()
self.assert_equal(c.get('/').data, '42')
self.assert_equal(len(log), 1)
self.assert_('init_jinja_globals' in str(log[0]['message']))
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(DeprecationsTestCase))
return suite

38
flask/testsuite/examples.py

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
"""
flask.testsuite.examples
~~~~~~~~~~~~~~~~~~~~~~~~
Tests the examples.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import os
import unittest
from flask.testsuite import add_to_path
def setup_path():
example_path = os.path.join(os.path.dirname(__file__),
os.pardir, os.pardir, 'examples')
add_to_path(os.path.join(example_path, 'flaskr'))
add_to_path(os.path.join(example_path, 'minitwit'))
def suite():
setup_path()
suite = unittest.TestSuite()
try:
from minitwit_tests import MiniTwitTestCase
except ImportError:
pass
else:
suite.addTest(unittest.makeSuite(MiniTwitTestCase))
try:
from flaskr_tests import FlaskrTestCase
except ImportError:
pass
else:
suite.addTest(unittest.makeSuite(FlaskrTestCase))
return suite

295
flask/testsuite/helpers.py

@ -0,0 +1,295 @@
# -*- coding: utf-8 -*-
"""
flask.testsuite.helpers
~~~~~~~~~~~~~~~~~~~~~~~
Various helpers.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import os
import flask
import unittest
from logging import StreamHandler
from StringIO import StringIO
from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr
from werkzeug.http import parse_options_header
def has_encoding(name):
try:
import codecs
codecs.lookup(name)
return True
except LookupError:
return False
class JSONTestCase(FlaskTestCase):
def test_json_bad_requests(self):
app = flask.Flask(__name__)
@app.route('/json', methods=['POST'])
def return_json():
return unicode(flask.request.json)
c = app.test_client()
rv = c.post('/json', data='malformed', content_type='application/json')
self.assert_equal(rv.status_code, 400)
def test_json_body_encoding(self):
app = flask.Flask(__name__)
app.testing = True
@app.route('/')
def index():
return flask.request.json
c = app.test_client()
resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'),
content_type='application/json; charset=iso-8859-15')
self.assert_equal(resp.data, u'Hällo Wörld'.encode('utf-8'))
def test_jsonify(self):
d = dict(a=23, b=42, c=[1, 2, 3])
app = flask.Flask(__name__)
@app.route('/kw')
def return_kwargs():
return flask.jsonify(**d)
@app.route('/dict')
def return_dict():
return flask.jsonify(d)
c = app.test_client()
for url in '/kw', '/dict':
rv = c.get(url)
self.assert_equal(rv.mimetype, 'application/json')
self.assert_equal(flask.json.loads(rv.data), d)
def test_json_attr(self):
app = flask.Flask(__name__)
@app.route('/add', methods=['POST'])
def add():
return unicode(flask.request.json['a'] + flask.request.json['b'])
c = app.test_client()
rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}),
content_type='application/json')
self.assert_equal(rv.data, '3')
def test_template_escaping(self):
app = flask.Flask(__name__)
render = flask.render_template_string
with app.test_request_context():
rv = render('{{ "</script>"|tojson|safe }}')
self.assert_equal(rv, '"<\\/script>"')
rv = render('{{ "<\0/script>"|tojson|safe }}')
self.assert_equal(rv, '"<\\u0000\\/script>"')
def test_modified_url_encoding(self):
class ModifiedRequest(flask.Request):
url_charset = 'euc-kr'
app = flask.Flask(__name__)
app.request_class = ModifiedRequest
app.url_map.charset = 'euc-kr'
@app.route('/')
def index():
return flask.request.args['foo']
rv = app.test_client().get(u'/?foo=정상처리'.encode('euc-kr'))
self.assert_equal(rv.status_code, 200)
self.assert_equal(rv.data, u'정상처리'.encode('utf-8'))
if not has_encoding('euc-kr'):
test_modified_url_encoding = None
class SendfileTestCase(FlaskTestCase):
def test_send_file_regular(self):
app = flask.Flask(__name__)
with app.test_request_context():
rv = flask.send_file('static/index.html')
self.assert_(rv.direct_passthrough)
self.assert_equal(rv.mimetype, 'text/html')
with app.open_resource('static/index.html') as f:
self.assert_equal(rv.data, f.read())
def test_send_file_xsendfile(self):
app = flask.Flask(__name__)
app.use_x_sendfile = True
with app.test_request_context():
rv = flask.send_file('static/index.html')
self.assert_(rv.direct_passthrough)
self.assert_('x-sendfile' in rv.headers)
self.assert_equal(rv.headers['x-sendfile'],
os.path.join(app.root_path, 'static/index.html'))
self.assert_equal(rv.mimetype, 'text/html')
def test_send_file_object(self):
app = flask.Flask(__name__)
with catch_warnings() as captured:
with app.test_request_context():
f = open(os.path.join(app.root_path, 'static/index.html'))
rv = flask.send_file(f)
with app.open_resource('static/index.html') as f:
self.assert_equal(rv.data, f.read())
self.assert_equal(rv.mimetype, 'text/html')
# mimetypes + etag
self.assert_equal(len(captured), 2)
app.use_x_sendfile = True
with catch_warnings() as captured:
with app.test_request_context():
f = open(os.path.join(app.root_path, 'static/index.html'))
rv = flask.send_file(f)
self.assert_equal(rv.mimetype, 'text/html')
self.assert_('x-sendfile' in rv.headers)
self.assert_equal(rv.headers['x-sendfile'],
os.path.join(app.root_path, 'static/index.html'))
# mimetypes + etag
self.assert_equal(len(captured), 2)
app.use_x_sendfile = False
with app.test_request_context():
with catch_warnings() as captured:
f = StringIO('Test')
rv = flask.send_file(f)
self.assert_equal(rv.data, 'Test')
self.assert_equal(rv.mimetype, 'application/octet-stream')
# etags
self.assert_equal(len(captured), 1)
with catch_warnings() as captured:
f = StringIO('Test')
rv = flask.send_file(f, mimetype='text/plain')
self.assert_equal(rv.data, 'Test')
self.assert_equal(rv.mimetype, 'text/plain')
# etags
self.assert_equal(len(captured), 1)
app.use_x_sendfile = True
with catch_warnings() as captured:
with app.test_request_context():
f = StringIO('Test')
rv = flask.send_file(f)
self.assert_('x-sendfile' not in rv.headers)
# etags
self.assert_equal(len(captured), 1)
def test_attachment(self):
app = flask.Flask(__name__)
with catch_warnings() as captured:
with app.test_request_context():
f = open(os.path.join(app.root_path, 'static/index.html'))
rv = flask.send_file(f, as_attachment=True)
value, options = parse_options_header(rv.headers['Content-Disposition'])
self.assert_equal(value, 'attachment')
# mimetypes + etag
self.assert_equal(len(captured), 2)
with app.test_request_context():
self.assert_equal(options['filename'], 'index.html')
rv = flask.send_file('static/index.html', as_attachment=True)
value, options = parse_options_header(rv.headers['Content-Disposition'])
self.assert_equal(value, 'attachment')
self.assert_equal(options['filename'], 'index.html')
with app.test_request_context():
rv = flask.send_file(StringIO('Test'), as_attachment=True,
attachment_filename='index.txt',
add_etags=False)
self.assert_equal(rv.mimetype, 'text/plain')
value, options = parse_options_header(rv.headers['Content-Disposition'])
self.assert_equal(value, 'attachment')
self.assert_equal(options['filename'], 'index.txt')
class LoggingTestCase(FlaskTestCase):
def test_logger_cache(self):
app = flask.Flask(__name__)
logger1 = app.logger
self.assert_(app.logger is logger1)
self.assert_equal(logger1.name, __name__)
app.logger_name = __name__ + '/test_logger_cache'
self.assert_(app.logger is not logger1)
def test_debug_log(self):
app = flask.Flask(__name__)
app.debug = True
@app.route('/')
def index():
app.logger.warning('the standard library is dead')
app.logger.debug('this is a debug statement')
return ''
@app.route('/exc')
def exc():
1/0
with app.test_client() as c:
with catch_stderr() as err:
c.get('/')
out = err.getvalue()
self.assert_('WARNING in helpers [' in out)
self.assert_(os.path.basename(__file__.rsplit('.', 1)[0] + '.py') in out)
self.assert_('the standard library is dead' in out)
self.assert_('this is a debug statement' in out)
with catch_stderr() as err:
try:
c.get('/exc')
except ZeroDivisionError:
pass
else:
self.assert_(False, 'debug log ate the exception')
def test_exception_logging(self):
out = StringIO()
app = flask.Flask(__name__)
app.logger_name = 'flask_tests/test_exception_logging'
app.logger.addHandler(StreamHandler(out))
@app.route('/')
def index():
1/0
rv = app.test_client().get('/')
self.assert_equal(rv.status_code, 500)
self.assert_('Internal Server Error' in rv.data)
err = out.getvalue()
self.assert_('Exception on / [GET]' in err)
self.assert_('Traceback (most recent call last):' in err)
self.assert_('1/0' in err)
self.assert_('ZeroDivisionError:' in err)
def test_processor_exceptions(self):
app = flask.Flask(__name__)
@app.before_request
def before_request():
if trigger == 'before':
1/0
@app.after_request
def after_request(response):
if trigger == 'after':
1/0
return response
@app.route('/')
def index():
return 'Foo'
@app.errorhandler(500)
def internal_server_error(e):
return 'Hello Server Error', 500
for trigger in 'before', 'after':
rv = app.test_client().get('/')
self.assert_equal(rv.status_code, 500)
self.assert_equal(rv.data, 'Hello Server Error')
def suite():
suite = unittest.TestSuite()
if flask.json_available:
suite.addTest(unittest.makeSuite(JSONTestCase))
suite.addTest(unittest.makeSuite(SendfileTestCase))
suite.addTest(unittest.makeSuite(LoggingTestCase))
return suite

103
flask/testsuite/signals.py

@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
"""
flask.testsuite.signals
~~~~~~~~~~~~~~~~~~~~~~~
Signalling.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import flask
import unittest
from flask.testsuite import FlaskTestCase
class SignalsTestCase(FlaskTestCase):
def test_template_rendered(self):
app = flask.Flask(__name__)
@app.route('/')
def index():
return flask.render_template('simple_template.html', whiskey=42)
recorded = []
def record(sender, template, context):
recorded.append((template, context))
flask.template_rendered.connect(record, app)
try:
app.test_client().get('/')
self.assert_equal(len(recorded), 1)
template, context = recorded[0]
self.assert_equal(template.name, 'simple_template.html')
self.assert_equal(context['whiskey'], 42)
finally:
flask.template_rendered.disconnect(record, app)
def test_request_signals(self):
app = flask.Flask(__name__)
calls = []
def before_request_signal(sender):
calls.append('before-signal')
def after_request_signal(sender, response):
self.assert_equal(response.data, 'stuff')
calls.append('after-signal')
@app.before_request
def before_request_handler():
calls.append('before-handler')
@app.after_request
def after_request_handler(response):
calls.append('after-handler')
response.data = 'stuff'
return response
@app.route('/')
def index():
calls.append('handler')
return 'ignored anyway'
flask.request_started.connect(before_request_signal, app)
flask.request_finished.connect(after_request_signal, app)
try:
rv = app.test_client().get('/')
self.assert_equal(rv.data, 'stuff')
self.assert_equal(calls, ['before-signal', 'before-handler',
'handler', 'after-handler',
'after-signal'])
finally:
flask.request_started.disconnect(before_request_signal, app)
flask.request_finished.disconnect(after_request_signal, app)
def test_request_exception_signal(self):
app = flask.Flask(__name__)
recorded = []
@app.route('/')
def index():
1/0
def record(sender, exception):
recorded.append(exception)
flask.got_request_exception.connect(record, app)
try:
self.assert_equal(app.test_client().get('/').status_code, 500)
self.assert_equal(len(recorded), 1)
self.assert_(isinstance(recorded[0], ZeroDivisionError))
finally:
flask.got_request_exception.disconnect(record, app)
def suite():
suite = unittest.TestSuite()
if flask.signals_available:
suite.addTest(unittest.makeSuite(SignalsTestCase))
return suite

0
tests/static/index.html → flask/testsuite/static/index.html

0
tests/templates/_macro.html → flask/testsuite/templates/_macro.html

0
tests/templates/context_template.html → flask/testsuite/templates/context_template.html

0
tests/templates/escaping_template.html → flask/testsuite/templates/escaping_template.html

0
tests/templates/mail.txt → flask/testsuite/templates/mail.txt

0
tests/templates/nested/nested.txt → flask/testsuite/templates/nested/nested.txt

0
tests/templates/simple_template.html → flask/testsuite/templates/simple_template.html

0
tests/templates/template_filter.html → flask/testsuite/templates/template_filter.html

141
flask/testsuite/templating.py

@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
"""
flask.testsuite.templating
~~~~~~~~~~~~~~~~~~~~~~~~~~
Template functionality
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import flask
import unittest
from flask.testsuite import FlaskTestCase
class TemplatingTestCase(FlaskTestCase):
def test_context_processing(self):
app = flask.Flask(__name__)
@app.context_processor
def context_processor():
return {'injected_value': 42}
@app.route('/')
def index():
return flask.render_template('context_template.html', value=23)
rv = app.test_client().get('/')
self.assert_equal(rv.data, '<p>23|42')
def test_original_win(self):
app = flask.Flask(__name__)
@app.route('/')
def index():
return flask.render_template_string('{{ config }}', config=42)
rv = app.test_client().get('/')
self.assert_equal(rv.data, '42')
def test_standard_context(self):
app = flask.Flask(__name__)
app.secret_key = 'development key'
@app.route('/')
def index():
flask.g.foo = 23
flask.session['test'] = 'aha'
return flask.render_template_string('''
{{ request.args.foo }}
{{ g.foo }}
{{ config.DEBUG }}
{{ session.test }}
''')
rv = app.test_client().get('/?foo=42')
self.assert_equal(rv.data.split(), ['42', '23', 'False', 'aha'])
def test_escaping(self):
text = '<p>Hello World!'
app = flask.Flask(__name__)
@app.route('/')
def index():
return flask.render_template('escaping_template.html', text=text,
html=flask.Markup(text))
lines = app.test_client().get('/').data.splitlines()
self.assert_equal(lines, [
'&lt;p&gt;Hello World!',
'<p>Hello World!',
'<p>Hello World!',
'<p>Hello World!',
'&lt;p&gt;Hello World!',
'<p>Hello World!'
])
def test_no_escaping(self):
app = flask.Flask(__name__)
with app.test_request_context():
self.assert_equal(flask.render_template_string('{{ foo }}',
foo='<test>'), '<test>')
self.assert_equal(flask.render_template('mail.txt', foo='<test>'),
'<test> Mail')
def test_macros(self):
app = flask.Flask(__name__)
with app.test_request_context():
macro = flask.get_template_attribute('_macro.html', 'hello')
self.assert_equal(macro('World'), 'Hello World!')
def test_template_filter(self):
app = flask.Flask(__name__)
@app.template_filter()
def my_reverse(s):
return s[::-1]
self.assert_('my_reverse' in app.jinja_env.filters.keys())
self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse)
self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba')
def test_template_filter_with_name(self):
app = flask.Flask(__name__)
@app.template_filter('strrev')
def my_reverse(s):
return s[::-1]
self.assert_('strrev' in app.jinja_env.filters.keys())
self.assert_equal(app.jinja_env.filters['strrev'], my_reverse)
self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba')
def test_template_filter_with_template(self):
app = flask.Flask(__name__)
@app.template_filter()
def super_reverse(s):
return s[::-1]
@app.route('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
self.assert_equal(rv.data, 'dcba')
def test_template_filter_with_name_and_template(self):
app = flask.Flask(__name__)
@app.template_filter('super_reverse')
def my_reverse(s):
return s[::-1]
@app.route('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
self.assert_equal(rv.data, 'dcba')
def test_custom_template_loader(self):
class MyFlask(flask.Flask):
def create_global_jinja_loader(self):
from jinja2 import DictLoader
return DictLoader({'index.html': 'Hello Custom World!'})
app = MyFlask(__name__)
@app.route('/')
def index():
return flask.render_template('index.html')
c = app.test_client()
rv = c.get('/')
self.assert_equal(rv.data, 'Hello Custom World!')
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TemplatingTestCase))
return suite

0
tests/blueprintapp/__init__.py → flask/testsuite/test_apps/blueprintapp/__init__.py

0
tests/blueprintapp/apps/__init__.py → flask/testsuite/test_apps/blueprintapp/apps/__init__.py

0
tests/blueprintapp/apps/admin/__init__.py → flask/testsuite/test_apps/blueprintapp/apps/admin/__init__.py

0
tests/blueprintapp/apps/admin/static/css/test.css → flask/testsuite/test_apps/blueprintapp/apps/admin/static/css/test.css

0
tests/blueprintapp/apps/admin/static/test.txt → flask/testsuite/test_apps/blueprintapp/apps/admin/static/test.txt

0
tests/blueprintapp/apps/admin/templates/admin/index.html → flask/testsuite/test_apps/blueprintapp/apps/admin/templates/admin/index.html

0
tests/blueprintapp/apps/frontend/__init__.py → flask/testsuite/test_apps/blueprintapp/apps/frontend/__init__.py

0
tests/blueprintapp/apps/frontend/templates/frontend/index.html → flask/testsuite/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html

4
flask/testsuite/test_apps/config_module_app.py

@ -0,0 +1,4 @@
import os
import flask
here = os.path.abspath(os.path.dirname(__file__))
app = flask.Flask(__name__)

4
flask/testsuite/test_apps/config_package_app/__init__.py

@ -0,0 +1,4 @@
import os
import flask
here = os.path.abspath(os.path.dirname(__file__))
app = flask.Flask(__name__)

0
tests/moduleapp/__init__.py → flask/testsuite/test_apps/moduleapp/__init__.py

0
tests/moduleapp/apps/__init__.py → flask/testsuite/test_apps/moduleapp/apps/__init__.py

0
tests/moduleapp/apps/admin/__init__.py → flask/testsuite/test_apps/moduleapp/apps/admin/__init__.py

0
tests/moduleapp/apps/admin/static/css/test.css → flask/testsuite/test_apps/moduleapp/apps/admin/static/css/test.css

0
tests/moduleapp/apps/admin/static/test.txt → flask/testsuite/test_apps/moduleapp/apps/admin/static/test.txt

0
tests/moduleapp/apps/admin/templates/index.html → flask/testsuite/test_apps/moduleapp/apps/admin/templates/index.html

0
tests/moduleapp/apps/frontend/__init__.py → flask/testsuite/test_apps/moduleapp/apps/frontend/__init__.py

0
tests/moduleapp/apps/frontend/templates/index.html → flask/testsuite/test_apps/moduleapp/apps/frontend/templates/index.html

0
tests/subdomaintestmodule/__init__.py → flask/testsuite/test_apps/subdomaintestmodule/__init__.py

0
tests/subdomaintestmodule/static/hello.txt → flask/testsuite/test_apps/subdomaintestmodule/static/hello.txt

165
flask/testsuite/testing.py

@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
"""
flask.testsuite.testing
~~~~~~~~~~~~~~~~~~~~~~~
Test client and more.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import flask
import unittest
from flask.testsuite import FlaskTestCase
class TestToolsTestCase(FlaskTestCase):
def test_environ_defaults_from_config(self):
app = flask.Flask(__name__)
app.testing = True
app.config['SERVER_NAME'] = 'example.com:1234'
app.config['APPLICATION_ROOT'] = '/foo'
@app.route('/')
def index():
return flask.request.url
ctx = app.test_request_context()
self.assert_equal(ctx.request.url, 'http://example.com:1234/foo/')
with app.test_client() as c:
rv = c.get('/')
self.assert_equal(rv.data, 'http://example.com:1234/foo/')
def test_environ_defaults(self):
app = flask.Flask(__name__)
app.testing = True
@app.route('/')
def index():
return flask.request.url
ctx = app.test_request_context()
self.assert_equal(ctx.request.url, 'http://localhost/')
with app.test_client() as c:
rv = c.get('/')
self.assert_equal(rv.data, 'http://localhost/')
def test_session_transactions(self):
app = flask.Flask(__name__)
app.testing = True
app.secret_key = 'testing'
@app.route('/')
def index():
return unicode(flask.session['foo'])
with app.test_client() as c:
with c.session_transaction() as sess:
self.assert_equal(len(sess), 0)
sess['foo'] = [42]
self.assert_equal(len(sess), 1)
rv = c.get('/')
self.assert_equal(rv.data, '[42]')
def test_session_transactions_no_null_sessions(self):
app = flask.Flask(__name__)
app.testing = True
with app.test_client() as c:
try:
with c.session_transaction() as sess:
pass
except RuntimeError, e:
self.assert_('Session backend did not open a session' in str(e))
else:
self.fail('Expected runtime error')
def test_session_transactions_keep_context(self):
app = flask.Flask(__name__)
app.testing = True
app.secret_key = 'testing'
with app.test_client() as c:
rv = c.get('/')
req = flask.request._get_current_object()
with c.session_transaction():
self.assert_(req is flask.request._get_current_object())
def test_session_transaction_needs_cookies(self):
app = flask.Flask(__name__)
app.testing = True
c = app.test_client(use_cookies=False)
try:
with c.session_transaction() as s:
pass
except RuntimeError, e:
self.assert_('cookies' in str(e))
else:
self.fail('Expected runtime error')
def test_test_client_context_binding(self):
app = flask.Flask(__name__)
@app.route('/')
def index():
flask.g.value = 42
return 'Hello World!'
@app.route('/other')
def other():
1/0
with app.test_client() as c:
resp = c.get('/')
self.assert_equal(flask.g.value, 42)
self.assert_equal(resp.data, 'Hello World!')
self.assert_equal(resp.status_code, 200)
resp = c.get('/other')
self.assert_(not hasattr(flask.g, 'value'))
self.assert_('Internal Server Error' in resp.data)
self.assert_equal(resp.status_code, 500)
flask.g.value = 23
try:
flask.g.value
except (AttributeError, RuntimeError):
pass
else:
raise AssertionError('some kind of exception expected')
def test_reuse_client(self):
app = flask.Flask(__name__)
c = app.test_client()
with c:
self.assert_equal(c.get('/').status_code, 404)
with c:
self.assert_equal(c.get('/').status_code, 404)
def test_test_client_calls_teardown_handlers(self):
app = flask.Flask(__name__)
called = []
@app.teardown_request
def remember(error):
called.append(error)
with app.test_client() as c:
self.assert_equal(called, [])
c.get('/')
self.assert_equal(called, [])
self.assert_equal(called, [None])
del called[:]
with app.test_client() as c:
self.assert_equal(called, [])
c.get('/')
self.assert_equal(called, [])
c.get('/')
self.assert_equal(called, [None])
self.assert_equal(called, [None, None])
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestToolsTestCase))
return suite

117
flask/testsuite/views.py

@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
"""
flask.testsuite.views
~~~~~~~~~~~~~~~~~~~~~
Pluggable views.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import flask
import flask.views
import unittest
from flask.testsuite import FlaskTestCase
from werkzeug.http import parse_set_header
class ViewTestCase(FlaskTestCase):
def common_test(self, app):
c = app.test_client()
self.assert_equal(c.get('/').data, 'GET')
self.assert_equal(c.post('/').data, 'POST')
self.assert_equal(c.put('/').status_code, 405)
meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow'])
self.assert_equal(sorted(meths), ['GET', 'HEAD', 'OPTIONS', 'POST'])
def test_basic_view(self):
app = flask.Flask(__name__)
class Index(flask.views.View):
methods = ['GET', 'POST']
def dispatch_request(self):
return flask.request.method
app.add_url_rule('/', view_func=Index.as_view('index'))
self.common_test(app)
def test_method_based_view(self):
app = flask.Flask(__name__)
class Index(flask.views.MethodView):
def get(self):
return 'GET'
def post(self):
return 'POST'
app.add_url_rule('/', view_func=Index.as_view('index'))
self.common_test(app)
def test_view_patching(self):
app = flask.Flask(__name__)
class Index(flask.views.MethodView):
def get(self):
1/0
def post(self):
1/0
class Other(Index):
def get(self):
return 'GET'
def post(self):
return 'POST'
view = Index.as_view('index')
view.view_class = Other
app.add_url_rule('/', view_func=view)
self.common_test(app)
def test_view_inheritance(self):
app = flask.Flask(__name__)
class Index(flask.views.MethodView):
def get(self):
return 'GET'
def post(self):
return 'POST'
class BetterIndex(Index):
def delete(self):
return 'DELETE'
app.add_url_rule('/', view_func=BetterIndex.as_view('index'))
c = app.test_client()
meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow'])
self.assert_equal(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST'])
def test_view_decorators(self):
app = flask.Flask(__name__)
def add_x_parachute(f):
def new_function(*args, **kwargs):
resp = flask.make_response(f(*args, **kwargs))
resp.headers['X-Parachute'] = 'awesome'
return resp
return new_function
class Index(flask.views.View):
decorators = [add_x_parachute]
def dispatch_request(self):
return 'Awesome'
app.add_url_rule('/', view_func=Index.as_view('index'))
c = app.test_client()
rv = c.get('/')
self.assert_equal(rv.headers['X-Parachute'], 'awesome')
self.assert_equal(rv.data, 'Awesome')
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ViewTestCase))
return suite

35
flask/views.py

@ -30,10 +30,38 @@ class View(object):
return 'Hello %s!' % name
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
When you want to decorate a pluggable view you will have to either do that
when the view function is created (by wrapping the return value of
:meth:`as_view`) or you can use the :attr:`decorators` attribute::
class SecretView(View):
methods = ['GET']
decorators = [superuser_required]
def dispatch_request(self):
...
The decorators stored in the decorators list are applied one after another
when the view function is created. Note that you can *not* use the class
based decorators since those would decorate the view class and not the
generated view function!
"""
#: A for which methods this pluggable view can handle.
methods = None
#: The canonical way to decorate class based views is to decorate the
#: return value of as_view(). However since this moves parts of the
#: logic from the class declaration to the place where it's hooked
#: into the routing system.
#:
#: You can place one or more decorators in this list and whenever the
#: view function is created the result is automatically decorated.
#:
#: .. versionadded:: 0.8
decorators = []
def dispatch_request(self):
"""Subclasses have to override this method to implement the
actual view functionc ode. This method is called with all
@ -54,6 +82,13 @@ class View(object):
def view(*args, **kwargs):
self = view.view_class(*class_args, **class_kwargs)
return self.dispatch_request(*args, **kwargs)
if cls.decorators:
view.__name__ = name
view.__module__ = cls.__module__
for decorator in cls.decorators:
view = decorator(view)
# we attach the view class to the view function for two reasons:
# first of all it allows us to easily figure out what class based
# view this thing came from, secondly it's also used for instanciating

3
run-tests.py

@ -0,0 +1,3 @@
#!/usr/bin/env python
from flask.testsuite import main
main()

0
tests/flaskext_test.py → scripts/flaskext_test.py

13
setup.py

@ -77,12 +77,6 @@ class run_audit(Command):
else:
print ("No problems found in sourcecode.")
def run_tests():
import os, sys
sys.path.append(os.path.join(os.path.dirname(__file__), 'tests'))
from flask_tests import suite
return suite()
setup(
name='Flask',
@ -94,7 +88,10 @@ setup(
description='A microframework based on Werkzeug, Jinja2 '
'and good intentions',
long_description=__doc__,
packages=['flask'],
packages=['flask', 'flask.testsuite'],
package_data={
'flask.testsuite': ['test_apps/*', 'static/*', 'templates/*']
},
zip_safe=False,
platforms='any',
install_requires=[
@ -112,5 +109,5 @@ setup(
'Topic :: Software Development :: Libraries :: Python Modules'
],
cmdclass={'audit': run_audit},
test_suite='__main__.run_tests'
test_suite='flask.testsuite.suite'
)

2312
tests/flask_tests.py

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save