# HG changeset patch # User Steve Losh # Date 1311009732 14400 # Node ID fa1f7726b08b297f0066f17cac3f350553fe8b05 # Parent 543110450d50f0b0def5f4279ccd36e506bf4014# Parent 48541d29e527decb9923dd6a1ab8536e8a62cbd2 Merge. diff -r 543110450d50 -r fa1f7726b08b .hgignore --- a/.hgignore Mon Jul 18 13:21:39 2011 -0400 +++ b/.hgignore Mon Jul 18 13:22:12 2011 -0400 @@ -9,3 +9,4 @@ *.swp *.log docs/_build +*.un~ diff -r 543110450d50 -r fa1f7726b08b README.markdown --- a/README.markdown Mon Jul 18 13:21:39 2011 -0400 +++ b/README.markdown Mon Jul 18 13:22:12 2011 -0400 @@ -1,9 +1,5 @@ -hg-review is close to being released! -===================================== - -Feedback is appreciated, but things may still break before v1.0.0! -================================================================== - +hg-review is still beta! Things may break! +=========================================== Installing ========== @@ -46,4 +42,10 @@ Check out the [documentation][docs] to learn more. +If you've still got questions, there's a mailing list (hosted by +[Librelist][]). Send an email (no subject/body necessary) to +[hg.review@librelist.com][ml] to subscribe. + [docs]: http://sjl.bitbucket.org/hg-review/ +[Librelist]: http://librelist.com/ +[ml]: mailto:hg.review@librelist.com diff -r 543110450d50 -r fa1f7726b08b bundled/flask/.gitignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/.gitignore Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,10 @@ +.DS_Store +*.pyc +*.pyo +env +env* +dist +*.egg +*.egg-info +_mailinglist +.tox diff -r 543110450d50 -r fa1f7726b08b bundled/flask/.gitmodules --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/.gitmodules Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,3 @@ +[submodule "docs/_themes"] + path = docs/_themes + url = git://github.com/mitsuhiko/flask-sphinx-themes.git diff -r 543110450d50 -r fa1f7726b08b bundled/flask/AUTHORS --- a/bundled/flask/AUTHORS Mon Jul 18 13:21:39 2011 -0400 +++ b/bundled/flask/AUTHORS Mon Jul 18 13:22:12 2011 -0400 @@ -9,16 +9,22 @@ Patches and Suggestions ``````````````````````` +- Adam Zapletal +- Ali Afshar - Chris Edgemon - Chris Grindstaff +- Christopher Grebs - Florent Xicluna - Georg Brandl - Justin Quick - Kenneth Reitz - Marian Sigler +- Matt Campell - Matthew Frazier +- Michael van Tellingen - Ron DuPlain - Sebastien Estienne - Simon Sapin - Stephane Wirtel +- Thomas Schranz - Zhao Xiaohong diff -r 543110450d50 -r fa1f7726b08b bundled/flask/CHANGES --- a/bundled/flask/CHANGES Mon Jul 18 13:21:39 2011 -0400 +++ b/bundled/flask/CHANGES Mon Jul 18 13:22:12 2011 -0400 @@ -3,10 +3,134 @@ Here you can see the full list of changes between each Flask release. +Version 0.7 +----------- + +Release date to be announced, codename to be selected + +- Added :meth:`~flask.Flask.make_default_options_response` + which can be used by subclasses to alter the default + behaviour for `OPTIONS` responses. +- Unbound locals now raise a proper :exc:`RuntimeError` instead + of an :exc:`AttributeError`. +- Mimetype guessing and etag support based on file objects is now + deprecated for :func:`flask.send_file` because it was unreliable. + Pass filenames instead or attach your own etags and provide a + proper mimetype by hand. +- Static file handling for modules now requires the name of the + static folder to be supplied explicitly. The previous autodetection + was not reliable and caused issues on Google's App Engine. Until + 1.0 the old behaviour will continue to work but issue dependency + warnings. +- fixed a problem for Flask to run on jython. +- added a `PROPAGATE_EXCEPTIONS` configuration variable that can be + used to flip the setting of exception propagation which previously + was linked to `DEBUG` alone and is now linked to either `DEBUG` or + `TESTING`. +- Flask no longer internally depends on rules being added through the + `add_url_rule` function and can now also accept regular werkzeug + rules added to the url map. +- Added an `endpoint` method to the flask application object which + allows one to register a callback to an arbitrary endpoint with + a decorator. + +Version 0.6.1 +------------- + +Bugfix release, released on December 31st 2010 + +- Fixed an issue where the default `OPTIONS` response was + not exposing all valid methods in the `Allow` header. +- Jinja2 template loading syntax now allows "./" in front of + a template load path. Previously this caused issues with + module setups. +- Fixed an issue where the subdomain setting for modules was + ignored for the static folder. +- Fixed a security problem that allowed clients to download arbitrary files + if the host server was a windows based operating system and the client + uses backslashes to escape the directory the files where exposed from. + +Version 0.6 +----------- + +Released on July 27th 2010, codename Whisky + +- after request functions are now called in reverse order of + registration. +- OPTIONS is now automatically implemented by Flask unless the + application explicitly adds 'OPTIONS' as method to the URL rule. + In this case no automatic OPTIONS handling kicks in. +- static rules are now even in place if there is no static folder + for the module. This was implemented to aid GAE which will + remove the static folder if it's part of a mapping in the .yml + file. +- the :attr:`~flask.Flask.config` is now available in the templates + as `config`. +- context processors will no longer override values passed directly + to the render function. +- added the ability to limit the incoming request data with the + new ``MAX_CONTENT_LENGTH`` configuration value. +- the endpoint for the :meth:`flask.Module.add_url_rule` method + is now optional to be consistent with the function of the + same name on the application object. +- added a :func:`flask.make_response` function that simplifies + creating response object instances in views. +- added signalling support based on blinker. This feature is currently + optional and supposed to be used by extensions and applications. If + you want to use it, make sure to have `blinker`_ installed. +- refactored the way URL adapters are created. This process is now + fully customizable with the :meth:`~flask.Flask.create_url_adapter` + method. +- modules can now register for a subdomain instead of just an URL + prefix. This makes it possible to bind a whole module to a + configurable subdomain. + +.. _blinker: http://pypi.python.org/pypi/blinker + +Version 0.5.2 +------------- + +Bugfix Release, released on July 15th 2010 + +- fixed another issue with loading templates from directories when + modules were used. + +Version 0.5.1 +------------- + +Bugfix Release, released on July 6th 2010 + +- fixes an issue with template loading from directories when modules + where used. + +Version 0.5 +----------- + +Released on July 6th 2010, codename Calvados + +- fixed a bug with subdomains that was caused by the inability to + specify the server name. The server name can now be set with + the `SERVER_NAME` config key. This key is now also used to set + the session cookie cross-subdomain wide. +- autoescaping is no longer active for all templates. Instead it + is only active for ``.html``, ``.htm``, ``.xml`` and ``.xhtml``. + Inside templates this behaviour can be changed with the + ``autoescape`` tag. +- refactored Flask internally. It now consists of more than a + single file. +- :func:`flask.send_file` now emits etags and has the ability to + do conditional responses builtin. +- (temporarily) dropped support for zipped applications. This was a + rarely used feature and led to some confusing behaviour. +- added support for per-package template and static-file directories. +- removed support for `create_jinja_loader` which is no longer used + in 0.5 due to the improved module support. +- added a helper function to expose files from any directory. + Version 0.4 ----------- -Release date to be announced, codename to be selected. +Released on June 18th 2010, codename Rakia - added the ability to register application wide error handlers from modules. @@ -19,11 +143,12 @@ - because the Python standard library caches loggers, the name of the logger is configurable now to better support unittests. - added `TESTING` switch that can activate unittesting helpers. +- the logger switches to `DEBUG` mode now if debug is enabled. Version 0.3.1 ------------- -Bugfix release, released May 28th +Bugfix release, released on May 28th 2010 - fixed a error reporting bug with :meth:`flask.Config.from_envvar` - removed some unused code from flask @@ -34,7 +159,7 @@ Version 0.3 ----------- -Released on May 28th, codename Schnaps +Released on May 28th 2010, codename Schnaps - added support for categories for flashed messages. - the application now configures a :class:`logging.Handler` and will @@ -50,7 +175,7 @@ Version 0.2 ----------- -Released on May 12th, codename Jägermeister +Released on May 12th 2010, codename Jägermeister - various bugfixes - integrated JSON support diff -r 543110450d50 -r fa1f7726b08b bundled/flask/LICENSE --- a/bundled/flask/LICENSE Mon Jul 18 13:21:39 2011 -0400 +++ b/bundled/flask/LICENSE Mon Jul 18 13:22:12 2011 -0400 @@ -3,9 +3,9 @@ Some rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: +Redistribution and use in source and binary forms of the software as well +as documentation, with or without modification, are permitted provided +that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. @@ -19,14 +19,15 @@ promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/MANIFEST.in --- a/bundled/flask/MANIFEST.in Mon Jul 18 13:21:39 2011 -0400 +++ b/bundled/flask/MANIFEST.in Mon Jul 18 13:22:12 2011 -0400 @@ -1,9 +1,12 @@ include Makefile CHANGES LICENSE AUTHORS +recursive-include artwork * recursive-include tests * recursive-include examples * recursive-include docs * recursive-exclude docs *.pyc recursive-exclude docs *.pyo +recursive-exclude tests *.pyc +recursive-exclude tests *.pyo recursive-exclude examples *.pyc recursive-exclude examples *.pyo prune docs/_build diff -r 543110450d50 -r fa1f7726b08b bundled/flask/Makefile --- a/bundled/flask/Makefile Mon Jul 18 13:21:39 2011 -0400 +++ b/bundled/flask/Makefile Mon Jul 18 13:22:12 2011 -0400 @@ -1,10 +1,19 @@ -.PHONY: clean-pyc test upload-docs +.PHONY: clean-pyc ext-test test upload-docs docs audit all: clean-pyc test test: python setup.py test +audit: + python setup.py audit + +tox-test: + PYTHONDONTWRITEBYTECODE= tox + +ext-test: + python tests/flaskext_test.py --browse + release: python setup.py release sdist upload @@ -20,3 +29,6 @@ scp -r docs/_build/dirhtml/* pocoo.org:/var/www/flask.pocoo.org/docs/ scp -r docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf scp -r docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/ + +docs: + $(MAKE) -C docs html diff -r 543110450d50 -r fa1f7726b08b bundled/flask/artwork/LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/artwork/LICENSE Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,20 @@ +Copyright (c) 2010 by Armin Ronacher. + +Some rights reserved. + +This logo or a modified version may be used by anyone to refer to the +Flask project, but does not indicate endorsement by the project. + +Redistribution and use in source (the SVG file) and binary forms (rendered +PNG files etc.) of the image, with or without modification, are permitted +provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice and this list of conditions. + +* The names of the contributors to the Flask software (see AUTHORS) may + not be used to endorse or promote products derived from this software + without specific prior written permission. + +Note: we would appreciate that you make the image a link to +http://flask.pocoo.org/ if you use it on a web page. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/artwork/logo-full.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/artwork/logo-full.svg Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/.gitignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/.gitignore Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +_build diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/Makefile Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,118 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp epub latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) _build/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask" + @echo "# ln -s _build/devhelp $$HOME/.local/share/devhelp/Flask" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +latexpdf: latex + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex + @echo "Running LaTeX files through pdflatex..." + make -C _build/latex all-pdf + @echo "pdflatex finished; the PDF files are in _build/latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/_static/debugger.png Binary file bundled/flask/docs/_static/debugger.png has changed diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/_static/flask.png Binary file bundled/flask/docs/_static/flask.png has changed diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/_static/flaskr.png Binary file bundled/flask/docs/_static/flaskr.png has changed diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/_static/logo-full.png Binary file bundled/flask/docs/_static/logo-full.png has changed diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/_static/no.png Binary file bundled/flask/docs/_static/no.png has changed diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/_static/touch-icon.png Binary file bundled/flask/docs/_static/touch-icon.png has changed diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/_static/yes.png Binary file bundled/flask/docs/_static/yes.png has changed diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/_templates/sidebarintro.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/_templates/sidebarintro.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,21 @@ +

About Flask

+

+ Flask is a micro webdevelopment framework for Python. You are currently + looking at the documentation of the development version. Things are + not stable yet, but if you have some feedback, + let me know. +

+

Other Formats

+

+ You can download the documentation in other formats as well: +

+ +

Useful Links

+ diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/_templates/sidebarlogo.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/_templates/sidebarlogo.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,3 @@ + diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/api.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/api.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,441 @@ +.. _api: + +API +=== + +.. module:: flask + +This part of the documentation covers all the interfaces of Flask. For +parts where Flask depends on external libraries, we document the most +important right here and provide links to the canonical documentation. + + +Application Object +------------------ + +.. autoclass:: Flask + :members: + :inherited-members: + + +Module Objects +-------------- + +.. autoclass:: Module + :members: + :inherited-members: + +Incoming Request Data +--------------------- + +.. autoclass:: Request + +.. class:: request + + To access incoming request data, you can use the global `request` + object. Flask parses incoming request data for you and gives you + access to it through that global object. Internally Flask makes + sure that you always get the correct data for the active thread if you + are in a multithreaded environment. + + This is a proxy. See :ref:`notes-on-proxies` for more information. + + The request object is an instance of a :class:`~werkzeug.Request` + subclass and provides all of the attributes Werkzeug defines. This + just shows a quick overview of the most important ones. + + .. attribute:: form + + A :class:`~werkzeug.MultiDict` with the parsed form data from `POST` + or `PUT` requests. Please keep in mind that file uploads will not + end up here, but instead in the :attr:`files` attribute. + + .. attribute:: args + + A :class:`~werkzeug.MultiDict` with the parsed contents of the query + string. (The part in the URL after the question mark). + + .. attribute:: values + + A :class:`~werkzeug.CombinedMultiDict` with the contents of both + :attr:`form` and :attr:`args`. + + .. attribute:: cookies + + A :class:`dict` with the contents of all cookies transmitted with + the request. + + .. attribute:: stream + + If the incoming form data was not encoded with a known mimetype + the data is stored unmodified in this stream for consumption. Most + of the time it is a better idea to use :attr:`data` which will give + you that data as a string. The stream only returns the data once. + + .. attribute:: data + + Contains the incoming request data as string in case it came with + a mimetype Flask does not handle. + + .. attribute:: files + + A :class:`~werkzeug.MultiDict` with files uploaded as part of a + `POST` or `PUT` request. Each file is stored as + :class:`~werkzeug.FileStorage` object. It basically behaves like a + standard file object you know from Python, with the difference that + it also has a :meth:`~werkzeug.FileStorage.save` function that can + store the file on the filesystem. + + .. attribute:: environ + + The underlying WSGI environment. + + .. attribute:: method + + The current request method (``POST``, ``GET`` etc.) + + .. attribute:: path + .. attribute:: script_root + .. attribute:: url + .. attribute:: base_url + .. attribute:: url_root + + Provides different ways to look at the current URL. Imagine your + application is listening on the following URL:: + + http://www.example.com/myapplication + + And a user requests the following URL:: + + http://www.example.com/myapplication/page.html?x=y + + In this case the values of the above mentioned attributes would be + the following: + + ============= ====================================================== + `path` ``/page.html`` + `script_root` ``/myapplication`` + `base_url` ``http://www.example.com/myapplication/page.html`` + `url` ``http://www.example.com/myapplication/page.html?x=y`` + `url_root` ``http://www.example.com/myapplication/`` + ============= ====================================================== + + .. attribute:: is_xhr + + `True` if the request was triggered via a JavaScript + `XMLHttpRequest`. This only works with libraries that support the + ``X-Requested-With`` header and set it to `XMLHttpRequest`. + Libraries that do that are prototype, jQuery and Mochikit and + probably some more. + + .. attribute:: json + + Contains the parsed body of the JSON request if the mimetype of + the incoming data was `application/json`. This requires Python 2.6 + or an installed version of simplejson. + +Response Objects +---------------- + +.. autoclass:: flask.Response + :members: set_cookie, data, mimetype + + .. attribute:: headers + + A :class:`Headers` object representing the response headers. + + .. attribute:: status_code + + The response status as integer. + + +Sessions +-------- + +If you have the :attr:`Flask.secret_key` set you can use sessions in Flask +applications. A session basically makes it possible to remember +information from one request to another. The way Flask does this is by +using a signed cookie. So the user can look at the session contents, but +not modify it unless he knows the secret key, so make sure to set that to +something complex and unguessable. + +To access the current session you can use the :class:`session` object: + +.. class:: session + + The session object works pretty much like an ordinary dict, with the + difference that it keeps track on modifications. + + This is a proxy. See :ref:`notes-on-proxies` for more information. + + The following attributes are interesting: + + .. attribute:: new + + `True` if the session is new, `False` otherwise. + + .. attribute:: modified + + `True` if the session object detected a modification. Be advised + that modifications on mutable structures are not picked up + automatically, in that situation you have to explicitly set the + attribute to `True` yourself. Here an example:: + + # this change is not picked up because a mutable object (here + # a list) is changed. + session['objects'].append(42) + # so mark it as modified yourself + session.modified = True + + .. attribute:: permanent + + If set to `True` the session lives for + :attr:`~flask.Flask.permanent_session_lifetime` seconds. The + default is 31 days. If set to `False` (which is the default) the + session will be deleted when the user closes the browser. + + +Application Globals +------------------- + +To share data that is valid for one request only from one function to +another, a global variable is not good enough because it would break in +threaded environments. Flask provides you with a special object that +ensures it is only valid for the active request and that will return +different values for each request. In a nutshell: it does the right +thing, like it does for :class:`request` and :class:`session`. + +.. data:: g + + Just store on this whatever you want. For example a database + connection or the user that is currently logged in. + + This is a proxy. See :ref:`notes-on-proxies` for more information. + + +Useful Functions and Classes +---------------------------- + +.. data:: current_app + + Points to the application handling the request. This is useful for + extensions that want to support multiple applications running side + by side. + + This is a proxy. See :ref:`notes-on-proxies` for more information. + +.. autofunction:: url_for + +.. function:: abort(code) + + Raises an :exc:`~werkzeug.exception.HTTPException` for the given + status code. For example to abort request handling with a page not + found exception, you would call ``abort(404)``. + + :param code: the HTTP error code. + +.. autofunction:: redirect + +.. autofunction:: make_response + +.. autofunction:: send_file + +.. autofunction:: send_from_directory + +.. autofunction:: escape + +.. autoclass:: Markup + :members: escape, unescape, striptags + +Message Flashing +---------------- + +.. autofunction:: flash + +.. autofunction:: get_flashed_messages + +Returning JSON +-------------- + +.. autofunction:: jsonify + +.. data:: json + + If JSON support is picked up, this will be the module that Flask is + using to parse and serialize JSON. So instead of doing this yourself:: + + try: + import simplejson as json + except ImportError: + import json + + You can instead just do this:: + + from flask import json + + For usage examples, read the :mod:`json` documentation. + + The :func:`~json.dumps` function of this json module is also available + as filter called ``|tojson`` in Jinja2. Note that inside `script` + tags no escaping must take place, so make sure to disable escaping + with ``|safe`` if you intend to use it inside `script` tags: + + .. sourcecode:: html+jinja + + + + Note that the ``|tojson`` filter escapes forward slashes properly. + +Template Rendering +------------------ + +.. autofunction:: render_template + +.. autofunction:: render_template_string + +.. autofunction:: get_template_attribute + +Configuration +------------- + +.. autoclass:: Config + :members: + +Useful Internals +---------------- + +.. data:: _request_ctx_stack + + The internal :class:`~werkzeug.LocalStack` that is used to implement + all the context local objects used in Flask. This is a documented + instance and can be used by extensions and application code but the + use is discouraged in general. + + The following attributes are always present on each layer of the + stack: + + `app` + the active Flask application. + + `url_adapter` + the URL adapter that was used to match the request. + + `request` + the current request object. + + `session` + the active session object. + + `g` + an object with all the attributes of the :data:`flask.g` object. + + `flashes` + an internal cache for the flashed messages. + + Example usage:: + + from flask import _request_ctx_stack + + def get_session(): + ctx = _request_ctx_stack.top + if ctx is not None: + return ctx.session + + .. versionchanged:: 0.4 + + The request context is automatically popped at the end of the request + for you. In debug mode the request context is kept around if + exceptions happen so that interactive debuggers have a chance to + introspect the data. With 0.4 this can also be forced for requests + that did not fail and outside of `DEBUG` mode. By setting + ``'flask._preserve_context'`` to `True` on the WSGI environment the + context will not pop itself at the end of the request. This is used by + the :meth:`~flask.Flask.test_client` for example to implement the + deferred cleanup functionality. + + You might find this helpful for unittests where you need the + information from the context local around for a little longer. Make + sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in + that situation, otherwise your unittests will leak memory. + +Signals +------- + +.. when modifying this list, also update the one in signals.rst + +.. versionadded:: 0.6 + +.. data:: signals_available + + `True` if the signalling system is available. This is the case + when `blinker`_ is installed. + +.. data:: template_rendered + + This signal is sent when a template was successfully rendered. The + signal is invoked with the instance of the template as `template` + and the context as dictionary (named `context`). + +.. data:: request_started + + This signal is sent before any request processing started but when the + request context was set up. Because the request context is already + bound, the subscriber can access the request with the standard global + proxies such as :class:`~flask.request`. + +.. data:: request_finished + + This signal is sent right before the response is sent to the client. + It is passed the response to be sent named `response`. + +.. data:: got_request_exception + + This signal is sent when an exception happens during request processing. + It is sent *before* the standard exception handling kicks in and even + in debug mode, where no exception handling happens. The exception + itself is passed to the subscriber as `exception`. + +.. currentmodule:: None + +.. class:: flask.signals.Namespace + + An alias for :class:`blinker.base.Namespace` if blinker is available, + otherwise a dummy class that creates fake signals. This class is + available for Flask extensions that want to provide the same fallback + system as Flask itself. + + .. method:: signal(name, doc=None) + + Creates a new signal for this namespace if blinker is available, + otherwise returns a fake signal that has a send method that will + do nothing but will fail with a :exc:`RuntimeError` for all other + operations, including connecting. + +.. _blinker: http://pypi.python.org/pypi/blinker + +.. _notes-on-proxies: + +Notes On Proxies +---------------- + +Some of the objects provided by Flask are proxies to other objects. The +reason behind this is that these proxies are shared between threads and +they have to dispatch to the actual object bound to a thread behind the +scenes as necessary. + +Most of the time you don't have to care about that, but there are some +exceptions where it is good to know that this object is an actual proxy: + +- The proxy objects do not fake their inherited types, so if you want to + perform actual instance checks, you have to do that on the instance + that is being proxied (see `_get_current_object` below). +- if the object reference is important (so for example for sending + :ref:`signals`) + +If you need to get access to the underlying object that is proxied, you +can use the :meth:`~werkzeug.LocalProxy._get_current_object` method:: + + app = current_app._get_current_object() + my_signal.send(app) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/becomingbig.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/becomingbig.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,88 @@ +.. _becomingbig: + +Becoming Big +============ + +Your application is becoming more and more complex? If you suddenly +realize that Flask does things in a way that does not work out for your +application there are ways to deal with that. + +Flask is powered by Werkzeug and Jinja2, two libraries that are in use at +a number of large websites out there and all Flask does is bring those +two together. Being a microframework Flask does not do much more than +combining existing libraries - there is not a lot of code involved. +What that means for large applications is that it's very easy to take the +code from Flask and put it into a new module within the applications and +expand on that. + +Flask is designed to be extended and modified in a couple of different +ways: + +- Flask extensions. For a lot of reusable functionality you can create + extensions. For extensions a number of hooks exist throughout Flask + with signals and callback functions. + +- Subclassing. The majority of functionality can be changed by creating + a new subclass of the :class:`~flask.Flask` class and overriding + methods provided for this exact purpose. + +- Forking. If nothing else works out you can just take the Flask + codebase at a given point and copy/paste it into your application + and change it. Flask is designed with that in mind and makes this + incredible easy. You just have to take the package and copy it + into your application's code and rename it (for example to + `framework`). Then you can start modifying the code in there. + +Why consider Forking? +--------------------- + +The majority of code of Flask is within Werkzeug and Jinja2. These +libraries do the majority of the work. Flask is just the paste that glues +those together. For every project there is the point where the underlying +framework gets in the way (due to assumptions the original developers +had). This is natural because if this would not be the case, the +framework would be a very complex system to begin with which causes a +steep learning curve and a lot of user frustration. + +This is not unique to Flask. Many people use patched and modified +versions of their framework to counter shortcomings. This idea is also +reflected in the license of Flask. You don't have to contribute any +changes back if you decide to modify the framework. + +The downside of forking is of course that Flask extensions will most +likely break because the new framework has a different import name. +Furthermore integrating upstream changes can be a complex process, +depending on the number of changes. Because of that, forking should be +the very last resort. + +Scaling like a Pro +------------------ + +For many web applications the complexity of the code is less an issue than +the scaling for the number of users or data entries expected. Flask by +itself is only limited in terms of scaling by your application code, the +data store you want to use and the Python implementation and webserver you +are running on. + +Scaling well means for example that if you double the amount of servers +you get about twice the performance. Scaling bad means that if you add a +new server the application won't perform any better or would not even +support a second server. + +There is only one limiting factor regarding scaling in Flask which are +the context local proxies. They depend on context which in Flask is +defined as being either a thread, process or greenlet. If your server +uses some kind of concurrency that is not based on threads or greenlets, +Flask will no longer be able to support these global proxies. However the +majority of servers are using either threads, greenlets or separate +processes to achieve concurrency which are all methods well supported by +the underlying Werkzeug library. + +Dialogue with the Community +--------------------------- + +The Flask developers are very interested to keep everybody happy, so as +soon as you find an obstacle in your way, caused by Flask, don't hesitate +to contact the developers on the mailinglist or IRC channel. The best way +for the Flask and Flask-extension developers to improve it for larger +applications is getting feedback from users. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/changelog.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/changelog.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +.. include:: ../CHANGES diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/conf.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/conf.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +# +# Flask documentation build configuration file, created by +# sphinx-quickstart on Tue Apr 6 15:24:58 2010. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.append(os.path.abspath('_themes')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Flask' +copyright = u'2010, Armin Ronacher' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +import pkg_resources +try: + release = pkg_resources.get_distribution('Flask').version +except pkg_resources.DistributionNotFound: + print 'To build the documentation, The distribution information of Flask' + print 'Has to be available. Either install the package into your' + print 'development environment or run "setup.py develop" to setup the' + print 'metadata. A virtualenv is recommended!' + sys.exit(1) +del pkg_resources + +if 'dev' in release: + release = release.split('dev')[0] + 'dev' +version = '.'.join(release.split('.')[:2]) + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'flask' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = { + 'touch_icon': 'touch-icon.png' +} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ['_themes'] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. Do not set, template magic! +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], + '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', + 'sourcelink.html', 'searchbox.html'] +} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +html_use_modindex = False + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +html_show_sphinx = False + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Flaskdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('latexindex', 'Flask.tex', u'Flask Documentation', + u'Armin Ronacher', 'manual'), +] + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +latex_use_modindex = False + +latex_elements = { + 'fontpkg': r'\usepackage{mathpazo}', + 'papersize': 'a4paper', + 'pointsize': '12pt', + 'preamble': r'\usepackage{flaskstyle}' +} +latex_use_parts = True + +latex_additional_files = ['flaskstyle.sty', 'logo.pdf'] + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +#epub_title = '' +#epub_author = '' +#epub_publisher = '' +#epub_copyright = '' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +intersphinx_mapping = { + 'http://docs.python.org/dev': None, + 'http://werkzeug.pocoo.org/docs/': None, + 'http://www.sqlalchemy.org/docs/': None, + 'http://wtforms.simplecodes.com/docs/0.5/': None, + 'http://discorporate.us/projects/Blinker/docs/1.1/': None +} + +pygments_style = 'flask_theme_support.FlaskyStyle' + +# fall back if theme is not there +try: + __import__('flask_theme_support') +except ImportError, e: + print '-' * 74 + print 'Warning: Flask themes unavailable. Building with default theme' + print 'If you want the Flask themes, run this command and build again:' + print + print ' git submodule update --init' + print '-' * 74 + + pygments_style = 'tango' + html_theme = 'default' + html_theme_options = {} diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/config.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/config.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,236 @@ +.. _config: + +Configuration Handling +====================== + +.. versionadded:: 0.3 + +Applications need some kind of configuration. There are different things +you might want to change like toggling debug mode, the secret key, and a +lot of very similar things. + +The way Flask is designed usually requires the configuration to be +available when the application starts up. You can hardcode the +configuration in the code, which for many small applications is not +actually that bad, but there are better ways. + +Independent of how you load your config, there is a config object +available which holds the loaded configuration values: +The :attr:`~flask.Flask.config` attribute of the :class:`~flask.Flask` +object. This is the place where Flask itself puts certain configuration +values and also where extensions can put their configuration values. But +this is also where you can have your own configuration. + +Configuration Basics +-------------------- + +The :attr:`~flask.Flask.config` is actually a subclass of a dictionary and +can be modified just like any dictionary:: + + app = Flask(__name__) + app.config['DEBUG'] = True + +Certain configuration values are also forwarded to the +:attr:`~flask.Flask` object so that you can read and write them from +there:: + + app.debug = True + +To update multiple keys at once you can use the :meth:`dict.update` +method:: + + app.config.update( + DEBUG=True, + SECRET_KEY='...' + ) + +Builtin Configuration Values +---------------------------- + +The following configuration values are used internally by Flask: + +.. tabularcolumns:: |p{6.5cm}|p{8.5cm}| + +=============================== ========================================= +``DEBUG`` enable/disable debug mode +``TESTING`` enable/disable testing mode +``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the + propagation of exceptions. If not set or + explicitly set to `None` this is + implicitly true if either `TESTING` or + `DEBUG` is true. +``SECRET_KEY`` the secret key +``SESSION_COOKIE_NAME`` the name of the session cookie +``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as + :class:`datetime.timedelta` object. +``USE_X_SENDFILE`` enable/disable x-sendfile +``LOGGER_NAME`` the name of the logger +``SERVER_NAME`` the name of the server. Required for + subdomain support (e.g.: ``'localhost'``) +``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will + reject incoming requests with a + content length greater than this by + returning a 413 status code. +=============================== ========================================= + +.. admonition:: More on ``SERVER_NAME`` + + The ``SERVER_NAME`` key is used for the subdomain support. Because + Flask cannot guess the subdomain part without the knowledge of the + actual server name, this is required if you want to work with + subdomains. This is also used for the session cookie. + + Please keep in mind that not only Flask has the problem of not knowing + what subdomains are, your web browser does as well. Most modern web + browsers will not allow cross-subdomain cookies to be set on a + server name without dots in it. So if your server name is + ``'localhost'`` you will not be able to set a cookie for + ``'localhost'`` and every subdomain of it. Please chose a different + server name in that case, like ``'myapplication.local'`` and add + this name + the subdomains you want to use into your host config + or setup a local `bind`_. + +.. _bind: https://www.isc.org/software/bind + +.. versionadded:: 0.4 + ``LOGGER_NAME`` + +.. versionadded:: 0.5 + ``SERVER_NAME`` + +.. versionadded:: 0.6 + ``MAX_CONTENT_LENGTH`` + +.. versionadded:: 0.7 + ``PROPAGATE_EXCEPTIONS`` + +Configuring from Files +---------------------- + +Configuration becomes more useful if you can configure from a file, and +ideally that file would be outside of the actual application package so that +you can install the package with distribute (:ref:`distribute-deployment`) +and still modify that file afterwards. + +So a common pattern is this:: + + app = Flask(__name__) + app.config.from_object('yourapplication.default_settings') + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + +This first loads the configuration from the +`yourapplication.default_settings` module and then overrides the values +with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS` +environment variable points to. This environment variable can be set on +Linux or OS X with the export command in the shell before starting the +server:: + + $ export YOURAPPLICATION_SETTINGS=/path/to/settings.cfg + $ python run-app.py + * Running on http://127.0.0.1:5000/ + * Restarting with reloader... + +On Windows systems use the `set` builtin instead:: + + >set YOURAPPLICATION_SETTINGS=\path\to\settings.cfg + +The configuration files themselves are actual Python files. Only values +in uppercase are actually stored in the config object later on. So make +sure to use uppercase letters for your config keys. + +Here is an example configuration file:: + + DEBUG = False + SECRET_KEY = '?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83' + +Make sure to load the configuration very early on so that extensions have +the ability to access the configuration when starting up. There are other +methods on the config object as well to load from individual files. For a +complete reference, read the :class:`~flask.Config` object's +documentation. + + +Configuration Best Practices +---------------------------- + +The downside with the approach mentioned earlier is that it makes testing +a little harder. There is no one 100% solution for this problem in +general, but there are a couple of things you can do to improve that +experience: + +1. create your application in a function and register modules on it. + That way you can create multiple instances of your application with + different configurations attached which makes unittesting a lot + easier. You can use this to pass in configuration as needed. + +2. Do not write code that needs the configuration at import time. If you + limit yourself to request-only accesses to the configuration you can + reconfigure the object later on as needed. + + +Development / Production +------------------------ + +Most applications need more than one configuration. There will at least +be a separate configuration for a production server and one used during +development. The easiest way to handle this is to use a default +configuration that is always loaded and part of version control, and a +separate configuration that overrides the values as necessary as mentioned +in the example above:: + + app = Flask(__name__) + app.config.from_object('yourapplication.default_settings') + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + +Then you just have to add a separate `config.py` file and export +``YOURAPPLICATION_SETTINGS=/path/to/config.py`` and you are done. However +there are alternative ways as well. For example you could use imports or +subclassing. + +What is very popular in the Django world is to make the import explicit in +the config file by adding an ``from yourapplication.default_settings +import *`` to the top of the file and then overriding the changes by hand. +You could also inspect an environment variable like +``YOURAPPLICATION_MODE`` and set that to `production`, `development` etc +and import different hardcoded files based on that. + +An interesting pattern is also to use classes and inheritance for +configuration:: + + class Config(object): + DEBUG = False + TESTING = False + DATABASE_URI = 'sqlite://:memory:' + + class ProductionConfig(Config): + DATABASE_URI = 'mysql://user@localhost/foo' + + class DevelopmentConfig(Config): + DEBUG = True + + class TestinConfig(Config): + TESTING = True + +To enable such a config you just have to call into +:meth:`~flask.Config.from_object`:: + + app.config.from_object('configmodule.ProductionConfig') + +There are many different ways and it's up to you how you want to manage +your configuration files. However here a list of good recommendations: + +- keep a default configuration in version control. Either populate the + config with this default configuration or import it in your own + configuration files before overriding values. +- use an environment variable to switch between the configurations. + This can be done from outside the Python interpreter and makes + development and deployment much easier because you can quickly and + easily switch between different configs without having to touch the + code at all. If you are working often on different projects you can + even create your own script for sourcing that activates a virtualenv + and exports the development configuration for you. +- Use a tool like `fabric`_ in production to push code and + configurations separately to the production server(s). For some + details about how to do that, head over to the :ref:`deploy` pattern. + +.. _fabric: http://fabfile.org/ diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/contents.rst.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/contents.rst.inc Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,52 @@ +User's Guide +------------ + +This part of the documentation, which is mostly prose, begins with some +background information about Flask, then focuses on step-by-step +instructions for web development with Flask. + +.. toctree:: + :maxdepth: 2 + + foreword + installation + quickstart + tutorial/index + templating + testing + errorhandling + config + signals + shell + patterns/index + deploying/index + becomingbig + +API Reference +------------- + +If you are looking for information on a specific function, class or +method, this part of the documentation is for you. + +.. toctree:: + :maxdepth: 2 + + api + +Additional Notes +---------------- + +Design notes, legal information and changelog are here for the interested. + +.. toctree:: + :maxdepth: 2 + + design + htmlfaq + security + unicode + extensiondev + styleguide + upgrading + changelog + license diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/deploying/cgi.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/deploying/cgi.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,47 @@ +CGI +=== + +If all other deployment methods do not work, CGI will work for sure. CGI +is supported by all major servers but usually has a less-than-optimal +performance. + +This is also the way you can use a Flask application on Google's +`App Engine`_, there however the execution does happen in a CGI-like +environment. The application's performance is unaffected because of that. + +.. admonition:: Watch Out + + Please make sure in advance that your ``app.run()`` call you might + have in your application file, is inside an ``if __name__ == + '__main__':`` or moved to a separate file. Just make sure it's not + called because this will always start a local WSGI server which we do + not want if we deploy that application to CGI / app engine. + +.. _App Engine: http://code.google.com/appengine/ + +Creating a `.cgi` file +---------------------- + +First you need to create the CGI application file. Let's call it +`yourapplication.cgi`:: + + #!/usr/bin/python + from wsgiref.handlers import CGIHandler + from yourapplication import app + + CGIHandler().run(app) + +Server Setup +------------ + +Usually there are two ways to configure the server. Either just copy the +`.cgi` into a `cgi-bin` (and use `mod_rewrite` or something similar to +rewrite the URL) or let the server point to the file directly. + +In Apache for example you can put a like like this into the config: + +.. sourcecode:: apache + + ScriptAlias /app /path/to/the/application.cgi + +For more information consult the documentation of your webserver. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/deploying/fastcgi.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/deploying/fastcgi.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,139 @@ +FastCGI +======= + +A very popular deployment setup on servers like `lighttpd`_ and `nginx`_ +is FastCGI. To use your WSGI application with any of them you will need +a FastCGI server first. + +The most popular one is `flup`_ which we will use for this guide. Make +sure to have it installed. + +.. admonition:: Watch Out + + Please make sure in advance that your ``app.run()`` call you might + have in your application file, is inside an ``if __name__ == + '__main__':`` or moved to a separate file. Just make sure it's not + called because this will always start a local WSGI server which we do + not want if we deploy that application to FastCGI. + +Creating a `.fcgi` file +----------------------- + +First you need to create the FastCGI server file. Let's call it +`yourapplication.fcgi`:: + + #!/usr/bin/python + from flup.server.fcgi import WSGIServer + from yourapplication import app + + WSGIServer(app).run() + +This is enough for Apache to work, however lighttpd and nginx need a +socket to communicate with the FastCGI server. For that to work you +need to pass the path to the socket to the +:class:`~flup.server.fcgi.WSGIServer`:: + + WSGIServer(application, bindAddress='/path/to/fcgi.sock').run() + +The path has to be the exact same path you define in the server +config. + +Save the `yourapplication.fcgi` file somewhere you will find it again. +It makes sense to have that in `/var/www/yourapplication` or something +similar. + +Make sure to set the executable bit on that file so that the servers +can execute it: + +.. sourcecode:: text + + # chmod +x /var/www/yourapplication/yourapplication.fcgi + +Configuring lighttpd +-------------------- + +A basic FastCGI configuration for lighttpd looks like that:: + + fastcgi.server = ("/yourapplication" => + "yourapplication" => ( + "socket" => "/tmp/yourapplication-fcgi.sock", + "bin-path" => "/var/www/yourapplication/yourapplication.fcgi", + "check-local" => "disable" + ) + ) + +This configuration binds the application to `/yourapplication`. If you +want the application to work in the URL root you have to work around a +lighttpd bug with the :class:`~werkzeug.contrib.fixers.LighttpdCGIRootFix` +middleware. + +Make sure to apply it only if you are mounting the application the URL +root. + +Configuring nginx +----------------- + +Installing FastCGI applications on nginx is a bit different because by default +no FastCGI parameters are forwarded. + +A basic flask FastCGI configuration for nginx looks like this:: + + location = /yourapplication { rewrite ^ /yourapplication/ last; } + location /yourapplication { try_files $uri @yourapplication; } + location @yourapplication { + include fastcgi_params; + fastcgi_split_path_info ^(/yourapplication)(.*)$; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; + } + +This configuration binds the application to `/yourapplication`. If you want +to have it in the URL root it's a bit simpler because you don't have to figure +out how to calculate `PATH_INFO` and `SCRIPT_NAME`:: + + location / { try_files $uri @yourapplication; } + location @yourapplication { + include fastcgi_params; + fastcgi_param PATH_INFO $fastcgi_script_name; + fastcgi_param SCRIPT_NAME ""; + fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; + } + +Since Nginx doesn't load FastCGI apps, you have to do it by yourself. You +can either write an `init.d` script for that or execute it inside a screen +session:: + + $ screen + $ /var/www/yourapplication/yourapplication.fcgi + +Debugging +--------- + +FastCGI deployments tend to be hard to debug on most webservers. Very often the +only thing the server log tells you is something along the lines of "premature +end of headers". In order to debug the application the only thing that can +really give you ideas why it breaks is switching to the correct user and +executing the application by hand. + +This example assumes your application is called `application.fcgi` and that your +webserver user is `www-data`:: + + $ su www-data + $ cd /var/www/yourapplication + $ python application.fcgi + Traceback (most recent call last): + File "yourapplication.fcgi", line 4, in + ImportError: No module named yourapplication + +In this case the error seems to be "yourapplication" not being on the python +path. Common problems are: + +- relative paths being used. Don't rely on the current working directory +- the code depending on environment variables that are not set by the + web server. +- different python interpreters being used. + +.. _lighttpd: http://www.lighttpd.net/ +.. _nginx: http://nginx.net/ +.. _flup: http://trac.saddi.com/flup diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/deploying/index.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/deploying/index.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,19 @@ +Deployment Options +================== + +Depending on what you have available there are multiple ways to run Flask +applications. A very common method is to use the builtin server during +development and maybe behind a proxy for simple applications, but there +are more options available. + +If you have a different WSGI server look up the server documentation about +how to use a WSGI app with it. Just remember that your application object +is the actual WSGI application. + +.. toctree:: + :maxdepth: 2 + + mod_wsgi + cgi + fastcgi + others diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/deploying/mod_wsgi.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/deploying/mod_wsgi.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,166 @@ +.. _mod_wsgi-deployment: + +mod_wsgi (Apache) +================= + +If you are using the `Apache`_ webserver you should consider using `mod_wsgi`_. + +.. admonition:: Watch Out + + Please make sure in advance that your ``app.run()`` call you might + have in your application file, is inside an ``if __name__ == + '__main__':`` or moved to a separate file. Just make sure it's not + called because this will always start a local WSGI server which we do + not want if we deploy that application to mod_wsgi. + +.. _Apache: http://httpd.apache.org/ + +Installing `mod_wsgi` +--------------------- + +If you don't have `mod_wsgi` installed yet you have to either install it using +a package manager or compile it yourself. + +The mod_wsgi `installation instructions`_ cover source installations on UNIX +systems. + +If you are using Ubuntu/Debian you can apt-get it and activate it as follows: + +.. sourcecode:: text + + # apt-get install libapache2-mod-wsgi + +On FreeBSD install `mod_wsgi` by compiling the `www/mod_wsgi` port or by using +pkg_add: + +.. sourcecode:: text + + # pkg_add -r mod_wsgi + +If you are using pkgsrc you can install `mod_wsgi` by compiling the +`www/ap2-wsgi` package. + +If you encounter segfaulting child processes after the first apache reload you +can safely ignore them. Just restart the server. + +Creating a `.wsgi` file +----------------------- + +To run your application you need a `yourapplication.wsgi` file. This file +contains the code `mod_wsgi` is executing on startup to get the application +object. The object called `application` in that file is then used as +application. + +For most applications the following file should be sufficient:: + + from yourapplication import app as application + +If you don't have a factory function for application creation but a singleton +instance you can directly import that one as `application`. + +Store that file somewhere that you will find it again (e.g.: +`/var/www/yourapplication`) and make sure that `yourapplication` and all +the libraries that are in use are on the python load path. If you don't +want to install it system wide consider using a `virtual python`_ instance. + +Configuring Apache +------------------ + +The last thing you have to do is to create an Apache configuration file for +your application. In this example we are telling `mod_wsgi` to execute the +application under a different user for security reasons: + +.. sourcecode:: apache + + + ServerName example.com + + WSGIDaemonProcess yourapplication user=user1 group=group1 threads=5 + WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi + + + WSGIProcessGroup yourapplication + WSGIApplicationGroup %{GLOBAL} + Order deny,allow + Allow from all + + + +For more information consult the `mod_wsgi wiki`_. + +.. _mod_wsgi: http://code.google.com/p/modwsgi/ +.. _installation instructions: http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide +.. _virtual python: http://pypi.python.org/pypi/virtualenv +.. _mod_wsgi wiki: http://code.google.com/p/modwsgi/wiki/ + +Troubleshooting +--------------- + +If your application does not run, follow this guide to troubleshoot: + +**Problem:** application does not run, errorlog shows SystemExit ignored + You have a ``app.run()`` call in your application file that is not + guarded by an ``if __name__ == '__main__':`` condition. Either remove + that :meth:`~flask.Flask.run` call from the file and move it into a + separate `run.py` file or put it into such an if block. + +**Problem:** application gives permission errors + Probably caused by your application running as the wrong user. Make + sure the folders the application needs access to have the proper + privileges set and the application runs as the correct user (``user`` + and ``group`` parameter to the `WSGIDaemonProcess` directive) + +**Problem:** application dies with an error on print + Keep in mind that mod_wsgi disallows doing anything with + :data:`sys.stdout` and :data:`sys.stderr`. You can disable this + protection from the config by setting the `WSGIRestrictStdout` to + ``off``: + + .. sourcecode:: apache + + WSGIRestrictStdout Off + + Alternatively you can also replace the standard out in the .wsgi file + with a different stream:: + + import sys + sys.stdout = sys.stderr + +**Problem:** accessing resources gives IO errors + Your application probably is a single .py file you symlinked into the + site-packages folder. Please be aware that this does not work, + instead you either have to put the folder into the pythonpath the file + is stored in, or convert your application into a package. + + The reason for this is that for non-installed packages, the module + filename is used to locate the resources and for symlinks the wrong + filename is picked up. + +Support for Automatic Reloading +------------------------------- + +To help deployment tools you can activate support for automatic reloading. +Whenever something changes the `.wsgi` file, `mod_wsgi` will reload all +the daemon processes for us. + +For that, just add the following directive to your `Directory` section: + +.. sourcecode:: apache + + WSGIScriptReloading On + +Working with Virtual Environments +--------------------------------- + +Virtual environments have the advantage that they never install the +required dependencies system wide so you have a better control over what +is used where. If you want to use a virtual environment with mod_wsgi you +have to modify your `.wsgi` file slightly. + +Add the following lines to the top of your `.wsgi` file:: + + activate_this = '/path/to/env/bin/activate_this.py' + execfile(activate_this, dict(__file__=activate_this)) + +This sets up the load paths according to the settings of the virtual +environment. Keep in mind that the path has to be absolute. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/deploying/others.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/deploying/others.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,98 @@ +Other Servers +============= + +There are popular servers written in Python that allow the execution of +WSGI applications as well. Keep in mind though that some of these servers +were written for very specific applications and might not work as well for +standard WSGI application such as Flask powered ones. + + +Tornado +-------- + +`Tornado`_ is an open source version of the scalable, non-blocking web +server and tools that power `FriendFeed`_. Because it is non-blocking and +uses epoll, it can handle thousands of simultaneous standing connections, +which means it is ideal for real-time web services. Integrating this +service with Flask is a trivial task:: + + from tornado.wsgi import WSGIContainer + from tornado.httpserver import HTTPServer + from tornado.ioloop import IOLoop + from yourapplication import app + + http_server = HTTPServer(WSGIContainer(app)) + http_server.listen(5000) + IOLoop.instance().start() + + +.. _Tornado: http://www.tornadoweb.org/ +.. _FriendFeed: http://friendfeed.com/ + + +Gevent +------- + +`Gevent`_ is a coroutine-based Python networking library that uses +`greenlet`_ to provide a high-level synchronous API on top of `libevent`_ +event loop:: + + from gevent.wsgi import WSGIServer + from yourapplication import app + + http_server = WSGIServer(('', 5000), app) + http_server.serve_forever() + +.. _Gevent: http://www.gevent.org/ +.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html +.. _libevent: http://monkey.org/~provos/libevent/ + + +Gunicorn +-------- + +`Gunicorn`_ 'Green Unicorn' is a WSGI HTTP Server for UNIX. It's a pre-fork +worker model ported from Ruby's Unicorn project. It supports both `eventlet`_ +and `greenlet`_. Running a Flask application on this server is quite simple:: + + gunicorn myproject:app + +.. _Gunicorn: http://gunicorn.org/ +.. _eventlet: http://eventlet.net/ +.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html + + +Proxy Setups +------------ + +If you deploy your application behind an HTTP proxy you will need to +rewrite a few headers in order for the application to work. The two +problematic values in the WSGI environment usually are `REMOTE_ADDR` and +`HTTP_HOST`. Werkzeug ships a fixer that will solve some common setups, +but you might want to write your own WSGI middleware for specific setups. + +The most common setup invokes the host being set from `X-Forwarded-Host` +and the remote address from `X-Forward-For`:: + + from werkzeug.contrib.fixers import ProxyFix + app.wsgi_app = ProxyFix(app.wsgi_app) + +Please keep in mind that it is a security issue to use such a middleware +in a non-proxy setup because it will blindly trust the incoming +headers which might be forged by malicious clients. + +If you want to rewrite the headers from another header, you might want to +use a fixer like this:: + + class CustomProxyFix(object): + + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + host = environ.get('HTTP_X_FHOST', '') + if host: + environ['HTTP_HOST'] = host + return self.app(environ, start_response) + + app.wsgi_app = CustomProxyFix(app.wsgi_app) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/design.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/design.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,175 @@ +.. _design: + +Design Decisions in Flask +========================= + +If you are curious why Flask does certain things the way it does and not +differently, this section is for you. This should give you an idea about +some of the design decisions that may appear arbitrary and surprising at +first, especially in direct comparison with other frameworks. + + +The Explicit Application Object +------------------------------- + +A Python web application based on WSGI has to have one central callable +object that implements the actual application. In Flask this is an +instance of the :class:`~flask.Flask` class. Each Flask application has +to create an instance of this class itself and pass it the name of the +module, but why can't Flask do that itself? + +Without such an explicit application object the following code:: + + from flask import Flask + app = Flask(__name__) + + @app.route('/') + def index(): + return 'Hello World!' + +Would look like this instead:: + + from hypothetical_flask import route + + @route('/') + def index(): + return 'Hello World!' + +There are three major reasons for this. The most important one is that +implicit application objects require that there may only be one instance at +the time. There are ways to fake multiple applications with a single +application object, like maintaining a stack of applications, but this +causes some problems I won't outline here in detail. Now the question is: +when does a microframework need more than one application at the same +time? A good example for this is unittesting. When you want to test +something it can be very helpful to create a minimal application to test +specific behavior. When the application object is deleted everything it +allocated will be freed again. + +Another thing that becomes possible when you have an explicit object lying +around in your code is that you can subclass the base class +(:class:`~flask.Flask`) to alter specific behaviour. This would not be +possible without hacks if the object were created ahead of time for you +based on a class that is not exposed to you. + +But there is another very important reason why Flask depends on an +explicit instantiation of that class: the package name. Whenever you +create a Flask instance you usually pass it `__name__` as package name. +Flask depends on that information to properly load resources relative +to your module. With Python's outstanding support for reflection it can +then access the package to figure out where the templates and static files +are stored (see :meth:`~flask.Flask.open_resource`). Now obviously there +are frameworks around that do not need any configuration and will still be +able to load templates relative to your application module. But they have +to use the current working directory for that, which is a very unreliable +way to determine where the application is. The current working directory +is process-wide and if you are running multiple applications in one +process (which could happen in a webserver without you knowing) the paths +will be off. Worse: many webservers do not set the working directory to +the directory of your application but to the document root which does not +have to be the same folder. + +The third reason is "explicit is better than implicit". That object is +your WSGI application, you don't have to remember anything else. If you +want to apply a WSGI middleware, just wrap it and you're done (though +there are better ways to do that so that you do not lose the reference +to the application object :meth:`~flask.Flask.wsgi_app`). + +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`). + +One Template Engine +------------------- + +Flask decides on one template engine: Jinja2. Why doesn't Flask have a +pluggable template engine interface? You can obviously use a different +template engine, but Flask will still configure Jinja2 for you. While +that limitation that Jinja2 is *always* configured will probably go away, +the decision to bundle one template engine and use that will not. + +Template engines are like programming languages and each of those engines +has a certain understanding about how things work. On the surface they +all work the same: you tell the engine to evaluate a template with a set +of variables and take the return value as string. + +But that's about where similarities end. Jinja2 for example has an +extensive filter system, a certain way to do template inheritance, support +for reusable blocks (macros) that can be used from inside templates and +also from Python code, uses Unicode for all operations, supports +iterative template rendering, configurable syntax and more. On the other +hand an engine like Genshi is based on XML stream evaluation, template +inheritance by taking the availability of XPath into account and more. +Mako on the other hand treats templates similar to Python modules. + +When it comes to connecting a template engine with an application or +framework there is more than just rendering templates. For instance, +Flask uses Jinja2's extensive autoescaping support. Also it provides +ways to access macros from Jinja2 templates. + +A template abstraction layer that would not take the unique features of +the template engines away is a science on its own and a too large +undertaking for a microframework like Flask. + +Furthermore extensions can then easily depend on one template language +being present. You can easily use your own templating language, but an +extension could still depend on Jinja itself. + + +Micro with Dependencies +----------------------- + +Why does Flask call itself a microframework and yet it depends on two +libraries (namely Werkzeug and Jinja2). Why shouldn't it? If we look +over to the Ruby side of web development there we have a protocol very +similar to WSGI. Just that it's called Rack there, but besides that it +looks very much like a WSGI rendition for Ruby. But nearly all +applications in Ruby land do not work with Rack directly, but on top of a +library with the same name. This Rack library has two equivalents in +Python: WebOb (formerly Paste) and Werkzeug. Paste is still around but +from my understanding it's sort of deprecated in favour of WebOb. The +development of WebOb and Werkzeug started side by side with similar ideas +in mind: be a good implementation of WSGI for other applications to take +advantage. + +Flask is a framework that takes advantage of the work already done by +Werkzeug to properly interface WSGI (which can be a complex task at +times). Thanks to recent developments in the Python package +infrastructure, packages with dependencies are no longer an issue and +there are very few reasons against having libraries that depend on others. + + +Thread Locals +------------- + +Flask uses thread local objects (context local objects in fact, they +support greenlet contexts as well) for request, session and an extra +object you can put your own things on (:data:`~flask.g`). Why is that and +isn't that a bad idea? + +Yes it is usually not such a bright idea to use thread locals. They cause +troubles for servers that are not based on the concept of threads and make +large applications harder to maintain. However Flask is just not designed +for large applications or asynchronous servers. Flask wants to make it +quick and easy to write a traditional web application. + +Also see the :ref:`becomingbig` section of the documentation for some +inspiration for larger applications based on Flask. + + +What Flask is, What Flask is Not +-------------------------------- + +Flask will never have a database layer. It will not have a form library +or anything else in that direction. Flask itself just bridges to Werkzeug +to implement a proper WSGI application and to Jinja2 to handle templating. +It also binds to a few common standard library packages such as logging. +Everything else is up for extensions. + +Why is this the case? Because people have different preferences and +requirements and Flask could not meet those if it would force any of this +into the core. The majority of web applications will need a template +engine in some sort. However not every application needs a SQL database. + +The idea of Flask is to build a good foundation for all applications. +Everything else is up to you or extensions. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/errorhandling.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/errorhandling.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,237 @@ +.. _application-errors: + +Handling Application Errors +=========================== + +.. versionadded:: 0.3 + +Applications fail, servers fail. Sooner or later you will see an exception +in production. Even if your code is 100% correct, you will still see +exceptions from time to time. Why? Because everything else involved will +fail. Here some situations where perfectly fine code can lead to server +errors: + +- the client terminated the request early and the application was still + reading from the incoming data. +- the database server was overloaded and could not handle the query. +- a filesystem is full +- a harddrive crashed +- a backend server overloaded +- a programming error in a library you are using +- network connection of the server to another system failed. + +And that's just a small sample of issues you could be facing. So how do we +deal with that sort of problem? By default if your application runs in +production mode, Flask will display a very simple page for you and log the +exception to the :attr:`~flask.Flask.logger`. + +But there is more you can do, and we will cover some better setups to deal +with errors. + +Error Mails +----------- + +If the application runs in production mode (which it will do on your +server) you won't see any log messages by default. Why is that? Flask +tries to be a zero-configuration framework. Where should it drop the logs +for you if there is no configuration? Guessing is not a good idea because +chances are, the place it guessed is not the place where the user has +permission to create a logfile. Also, for most small applications nobody +will look at the logs anyways. + +In fact, I promise you right now that if you configure a logfile for the +application errors you will never look at it except for debugging an issue +when a user reported it for you. What you want instead is a mail the +second the exception happened. Then you get an alert and you can do +something about it. + +Flask uses the Python builtin logging system, and it can actually send +you mails for errors which is probably what you want. Here is how you can +configure the Flask logger to send you mails for exceptions:: + + ADMINS = ['yourname@example.com'] + if not app.debug: + import logging + from logging.handlers import SMTPHandler + mail_handler = SMTPHandler('127.0.0.1', + 'server-error@example.com', + ADMINS, 'YourApplication Failed') + mail_handler.setLevel(logging.ERROR) + app.logger.addHandler(mail_handler) + +So what just happened? We created a new +:class:`~logging.handlers.SMTPHandler` that will send mails with the mail +server listening on ``127.0.0.1`` to all the `ADMINS` from the address +*server-error@example.com* with the subject "YourApplication Failed". If +your mail server requires credentials, these can also be provided. For +that check out the documentation for the +:class:`~logging.handlers.SMTPHandler`. + +We also tell the handler to only send errors and more critical messages. +Because we certainly don't want to get a mail for warnings or other +useless logs that might happen during request handling. + +Before you run that in production, please also look at :ref:`logformat` to +put more information into that error mail. That will save you from a lot +of frustration. + + +Logging to a File +----------------- + +Even if you get mails, you probably also want to log warnings. It's a +good idea to keep as much information around that might be required to +debug a problem. Please note that Flask itself will not issue any +warnings in the core system, so it's your responsibility to warn in the +code if something seems odd. + +There are a couple of handlers provided by the logging system out of the +box but not all of them are useful for basic error logging. The most +interesting are probably the following: + +- :class:`~logging.FileHandler` - logs messages to a file on the + filesystem. +- :class:`~logging.handlers.RotatingFileHandler` - logs messages to a file + on the filesystem and will rotate after a certain number of messages. +- :class:`~logging.handlers.NTEventLogHandler` - will log to the system + event log of a Windows system. If you are deploying on a Windows box, + this is what you want to use. +- :class:`~logging.handlers.SysLogHandler` - sends logs to a UNIX + syslog. + +Once you picked your log handler, do like you did with the SMTP handler +above, just make sure to use a lower setting (I would recommend +`WARNING`):: + + if not app.debug: + import logging + from themodule import TheHandler YouWant + file_handler = TheHandlerYouWant(...) + file_handler.setLevel(logging.WARNING) + app.logger.addHandler(file_handler) + +.. _logformat: + +Controlling the Log Format +-------------------------- + +By default a handler will only write the message string into a file or +send you that message as mail. A log record stores more information, +and it makes a lot of sense to configure your logger to also contain that +information so that you have a better idea of why that error happened, and +more importantly, where it did. + +A formatter can be instantiated with a format string. Note that +tracebacks are appended to the log entry automatically. You don't have to +do that in the log formatter format string. + +Here some example setups: + +Email +````` + +:: + + from logging import Formatter + mail_handler.setFormatter(Formatter(''' + Message type: %(levelname)s + Location: %(pathname)s:%(lineno)d + Module: %(module)s + Function: %(funcName)s + Time: %(asctime)s + + Message: + + %(message)s + ''')) + +File logging +```````````` + +:: + + from logging import Formatter + file_handler.setFormatter(Formatter( + '%(asctime)s %(levelname)s: %(message)s ' + '[in %(pathname)s:%(lineno)d]' + )) + + +Complex Log Formatting +`````````````````````` + +Here is a list of useful formatting variables for the format string. Note +that this list is not complete, consult the official documentation of the +:mod:`logging` package for a full list. + +.. tabularcolumns:: |p{3cm}|p{12cm}| + ++------------------+----------------------------------------------------+ +| Format | Description | ++==================+====================================================+ +| ``%(levelname)s``| Text logging level for the message | +| | (``'DEBUG'``, ``'INFO'``, ``'WARNING'``, | +| | ``'ERROR'``, ``'CRITICAL'``). | ++------------------+----------------------------------------------------+ +| ``%(pathname)s`` | Full pathname of the source file where the | +| | logging call was issued (if available). | ++------------------+----------------------------------------------------+ +| ``%(filename)s`` | Filename portion of pathname. | ++------------------+----------------------------------------------------+ +| ``%(module)s`` | Module (name portion of filename). | ++------------------+----------------------------------------------------+ +| ``%(funcName)s`` | Name of function containing the logging call. | ++------------------+----------------------------------------------------+ +| ``%(lineno)d`` | Source line number where the logging call was | +| | issued (if available). | ++------------------+----------------------------------------------------+ +| ``%(asctime)s`` | Human-readable time when the LogRecord` was | +| | created. By default this is of the form | +| | ``"2003-07-08 16:49:45,896"`` (the numbers after | +| | the comma are millisecond portion of the time). | +| | This can be changed by subclassing the formatter | +| | and overriding the | +| | :meth:`~logging.Formatter.formatTime` method. | ++------------------+----------------------------------------------------+ +| ``%(message)s`` | The logged message, computed as ``msg % args`` | ++------------------+----------------------------------------------------+ + +If you want to further customize the formatting, you can subclass the +formatter. The formatter has three interesting methods: + +:meth:`~logging.Formatter.format`: + handles the actual formatting. It is passed a + :class:`~logging.LogRecord` object and has to return the formatted + string. +:meth:`~logging.Formatter.formatTime`: + called for `asctime` formatting. If you want a different time format + you can override this method. +:meth:`~logging.Formatter.formatException` + called for exception formatting. It is passed an :attr:`~sys.exc_info` + tuple and has to return a string. The default is usually fine, you + don't have to override it. + +For more information, head over to the official documentation. + + +Other Libraries +--------------- + +So far we only configured the logger your application created itself. +Other libraries might log themselves as well. For example, SQLAlchemy uses +logging heavily in its core. While there is a method to configure all +loggers at once in the :mod:`logging` package, I would not recommend using +it. There might be a situation in which you want to have multiple +separate applications running side by side in the same Python interpreter +and then it becomes impossible to have different logging setups for those. + +Instead, I would recommend figuring out which loggers you are interested +in, getting the loggers with the :func:`~logging.getLogger` function and +iterating over them to attach handlers:: + + from logging import getLogger + loggers = [app.logger, getLogger('sqlalchemy'), + getLogger('otherlibrary')] + for logger in loggers: + logger.addHandler(mail_handler) + logger.addHandler(file_handler) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/extensiondev.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/extensiondev.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,327 @@ +Flask Extension Development +=========================== + +Flask, being a microframework, often requires some repetitive steps to get +a third party library working. Because very often these steps could be +abstracted to support multiple projects the `Flask Extension Registry`_ +was created. + +If you want to create your own Flask extension for something that does not +exist yet, this guide to extension development will help you get your +extension running in no time and to feel like users would expect your +extension to behave. + +.. _Flask Extension Registry: http://flask.pocoo.org/extensions/ + +Anatomy of an Extension +----------------------- + +Extensions are all located in a package called ``flaskext.something`` +where "something" is the name of the library you want to bridge. So for +example if you plan to add support for a library named `simplexml` to +Flask, you would name your extension's package ``flaskext.simplexml``. + +The name of the actual extension (the human readable name) however would +be something like "Flask-SimpleXML". Make sure to include the name +"Flask" somewhere in that name and that you check the capitalization. +This is how users can then register dependencies to your extension in +their `setup.py` files. + +The magic that makes it possible to have your library in a package called +``flaskext.something`` is called a "namespace package". Check out the +guide below how to create something like that. + +But how do extensions look like themselves? An extension has to ensure +that it works with multiple Flask application instances at once. This is +a requirement because many people will use patterns like the +:ref:`app-factories` pattern to create their application as needed to aid +unittests and to support multiple configurations. Because of that it is +crucial that your application supports that kind of behaviour. + +Most importantly the extension must be shipped with a `setup.py` file and +registered on PyPI. Also the development checkout link should work so +that people can easily install the development version into their +virtualenv without having to download the library by hand. + +Flask extensions must be licensed as BSD or MIT or a more liberal license +to be enlisted on the Flask Extension Registry. Keep in mind that the +Flask Extension Registry is a moderated place and libraries will be +reviewed upfront if they behave as required. + +"Hello Flaskext!" +----------------- + +So let's get started with creating such a Flask extension. The extension +we want to create here will provide very basic support for SQLite3. + +There is a script on github called `Flask Extension Wizard`_ which helps +you create the initial folder structure. But for this very basic example +we want to create all by hand to get a better feeling for it. + +First we create the following folder structure:: + + flask-sqlite3/ + flaskext/ + __init__.py + sqlite3.py + setup.py + LICENSE + +Here's the contents of the most important files: + +flaskext/__init__.py +```````````````````` + +The only purpose of this file is to mark the package as namespace package. +This is required so that multiple modules from different PyPI packages can +reside in the same Python package:: + + __import__('pkg_resources').declare_namespace(__name__) + +If you want to know exactly what is happening there, checkout the +distribute or setuptools docs which explain how this works. + +Just make sure to not put anything else in there! + +setup.py +```````` + +The next file that is absolutely required is the `setup.py` file which is +used to install your Flask extension. The following contents are +something you can work with:: + + """ + Flask-SQLite3 + ------------- + + This is the description for that library + """ + from setuptools import setup + + + setup( + name='Flask-SQLite3', + version='1.0', + url='http://example.com/flask-sqlite3/', + license='BSD', + author='Your Name', + author_email='your-email@example.com', + description='Very short description', + long_description=__doc__, + packages=['flaskext'], + namespace_packages=['flaskext'], + zip_safe=False, + platforms='any', + install_requires=[ + 'Flask' + ], + classifiers=[ + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + 'Topic :: Software Development :: Libraries :: Python Modules' + ] + ) + +That's a lot of code but you can really just copy/paste that from existing +extensions and adapt. This is also what the wizard creates for you if you +use it. + +flaskext/sqlite3.py +``````````````````` + +Now this is where your extension code goes. But how exactly should such +an extension look like? What are the best practices? Continue reading +for some insight. + + +Initializing Extensions +----------------------- + +Many extensions will need some kind of initialization step. For example, +consider your application is currently connecting to SQLite like the +documentation suggests (:ref:`sqlite3`) you will need to provide a few +functions and before / after request handlers. So how does the extension +know the name of the application object? + +Quite simple: you pass it to it. + +There are two recommended ways for an extension to initialize: + +initialization functions: + If your extension is called `helloworld` you might have a function + called ``init_helloworld(app[, extra_args])`` that initializes the + extension for that application. It could attach before / after + handlers etc. + +classes: + Classes work mostly like initialization functions but can later be + used to further change the behaviour. For an example look at how the + `OAuth extension`_ works: there is an `OAuth` object that provides + some helper functions like `OAuth.remote_app` to create a reference to + a remote application that uses OAuth. + +What to use depends on what you have in mind. For the SQLite 3 extension +we will need to use the class based approach because we have to use a +controller object that can be used to connect to the database. + +The Extension Code +------------------ + +Here's the contents of the `flaskext/sqlite3.py` for copy/paste:: + + from __future__ import absolute_import + import sqlite3 + from flask import g + + class SQLite3(object): + + def __init__(self, app): + self.app = app + self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') + + self.app.before_request(self.before_request) + self.app.after_request(self.after_request) + + def connect(self): + return sqlite3.connect(self.app.config['SQLITE3_DATABASE']) + + def before_request(self): + g.sqlite3_db = self.connect() + + def after_request(self, response): + g.sqlite3_db.close() + return response + +So here's what the lines of code do: + +1. the ``__future__`` import is necessary to activate absolute imports. + This is needed because otherwise we could not call our module + `sqlite3.py` and import the top-level `sqlite3` module which actually + implements the connection to SQLite. +2. We create a class for our extension that sets a default configuration + for the SQLite 3 database if it's not there (:meth:`dict.setdefault`) + and connects two functions as before and after request handlers. +3. Then it implements a `connect` function that returns a new database + connection and the two handlers. + +So why did we decide on a class based approach here? Because using that +extension looks something like this:: + + from flask import Flask, g + from flaskext.sqlite3 import SQLite3 + + app = Flask(__name__) + app.config.from_pyfile('the-config.cfg') + db = SQLite(app) + +Either way you can use the database from the views like this:: + + @app.route('/') + def show_all(): + cur = g.sqlite3_db.cursor() + cur.execute(...) + +But how would you open a database connection from outside a view function? +This is where the `db` object now comes into play: + +>>> from yourapplication import db +>>> con = db.connect() +>>> cur = con.cursor() + +If you don't need that, you can go with initialization functions. + +Initialization Functions +------------------------ + +Here's what the module would look like with initialization functions:: + + from __future__ import absolute_import + import sqlite3 + from flask import g + + def init_sqlite3(app): + app = app + app.config.setdefault('SQLITE3_DATABASE', ':memory:') + + @app.before_request + def before_request(): + g.sqlite3_db = sqlite3.connect(self.app.config['SQLITE3_DATABASE']) + + @app.after_request + def after_request(response): + g.sqlite3_db.close() + return response + +Learn from Others +----------------- + +This documentation only touches the bare minimum for extension +development. If you want to learn more, it's a very good idea to check +out existing extensions on the `Flask Extension Registry`_. If you feel +lost there is still the `mailinglist`_ and the `IRC channel`_ to get some +ideas for nice looking APIs. Especially if you do something nobody before +you did, it might be a very good idea to get some more input. This not +only to get an idea about what people might want to have from an +extension, but also to avoid having multiple developers working on pretty +much the same side by side. + +Remember: good API design is hard, so introduce your project on the +mailinglist, and let other developers give you a helping hand with +designing the API. + +The best Flask extensions are extensions that share common idioms for the +API. And this can only work if collaboration happens early. + + +Approved Extensions +------------------- + +Flask also has the concept of approved extensions. Approved extensions +are tested as part of Flask itself to ensure extensions do not break on +new releases. These approved extensions are listed on the `Flask +Extension Registry`_ and marked appropriately. If you want your own +extension to be approved you have to follow these guidelines: + +1. An approved Flask extension must provide exactly one package or module + inside the `flaskext` namespace package. +2. It must ship a testsuite that can either be invoked with ``make test`` + or ``python setup.py test``. For testsuites invoked with ``make + test`` the extension has to ensure that all dependencies for the test + are installed automatically, in case of ``python setup.py test`` + dependencies for tests alone can be specified in the `setup.py` + file. The testsuite also has to be part of the distribution. +3. APIs of approved extensions will be checked for the following + characteristics: + + - an approved extension has to support multiple applications + running in the same Python process. + - it must be possible to use the factory pattern for creating + applications. + +4. The license must be BSD/MIT/WTFPL licensed. +5. The naming scheme for official extensions is *Flask-ExtensionName* or + *ExtensionName-Flask*. +6. Approved extensions must define all their dependencies in the + `setup.py` file unless a dependency cannot be met because it is not + available on PyPI. +7. The extension must have documentation that uses one of the two Flask + themes for Sphinx documentation. +8. The setup.py description (and thus the PyPI description) has to + link to the documentation, website (if there is one) and there + must be a link to automatically install the development version + (``PackageName==dev``). +9. The ``zip_safe`` flag in the setup script must be set to ``False``, + even if the extension would be safe for zipping. +10. An extension currently has to support Python 2.5, 2.6 as well as + Python 2.7 + + +.. _Flask Extension Wizard: + http://github.com/mitsuhiko/flask-extension-wizard +.. _OAuth extension: http://packages.python.org/Flask-OAuth/ +.. _mailinglist: http://flask.pocoo.org/mailinglist/ +.. _IRC channel: http://flask.pocoo.org/community/irc/ diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/flaskext.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/flaskext.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,86 @@ +# flasky extensions. flasky pygments style based on tango style +from pygments.style import Style +from pygments.token import Keyword, Name, Comment, String, Error, \ + Number, Operator, Generic, Whitespace, Punctuation, Other, Literal + + +class FlaskyStyle(Style): + background_color = "#f8f8f8" + default_style = "" + + styles = { + # No corresponding class for the following: + #Text: "", # class: '' + Whitespace: "underline #f8f8f8", # class: 'w' + Error: "#a40000 border:#ef2929", # class: 'err' + Other: "#000000", # class 'x' + + Comment: "italic #8f5902", # class: 'c' + Comment.Preproc: "noitalic", # class: 'cp' + + Keyword: "bold #004461", # class: 'k' + Keyword.Constant: "bold #004461", # class: 'kc' + Keyword.Declaration: "bold #004461", # class: 'kd' + Keyword.Namespace: "bold #004461", # class: 'kn' + Keyword.Pseudo: "bold #004461", # class: 'kp' + Keyword.Reserved: "bold #004461", # class: 'kr' + Keyword.Type: "bold #004461", # class: 'kt' + + Operator: "#582800", # class: 'o' + Operator.Word: "bold #004461", # class: 'ow' - like keywords + + Punctuation: "bold #000000", # class: 'p' + + # because special names such as Name.Class, Name.Function, etc. + # are not recognized as such later in the parsing, we choose them + # to look the same as ordinary variables. + Name: "#000000", # class: 'n' + Name.Attribute: "#c4a000", # class: 'na' - to be revised + Name.Builtin: "#004461", # class: 'nb' + Name.Builtin.Pseudo: "#3465a4", # class: 'bp' + Name.Class: "#000000", # class: 'nc' - to be revised + Name.Constant: "#000000", # class: 'no' - to be revised + Name.Decorator: "#888", # class: 'nd' - to be revised + Name.Entity: "#ce5c00", # class: 'ni' + Name.Exception: "bold #cc0000", # class: 'ne' + Name.Function: "#000000", # class: 'nf' + Name.Property: "#000000", # class: 'py' + Name.Label: "#f57900", # class: 'nl' + Name.Namespace: "#000000", # class: 'nn' - to be revised + Name.Other: "#000000", # class: 'nx' + Name.Tag: "bold #004461", # class: 'nt' - like a keyword + Name.Variable: "#000000", # class: 'nv' - to be revised + Name.Variable.Class: "#000000", # class: 'vc' - to be revised + Name.Variable.Global: "#000000", # class: 'vg' - to be revised + Name.Variable.Instance: "#000000", # class: 'vi' - to be revised + + Number: "#990000", # class: 'm' + + Literal: "#000000", # class: 'l' + Literal.Date: "#000000", # class: 'ld' + + String: "#4e9a06", # class: 's' + String.Backtick: "#4e9a06", # class: 'sb' + String.Char: "#4e9a06", # class: 'sc' + String.Doc: "italic #8f5902", # class: 'sd' - like a comment + String.Double: "#4e9a06", # class: 's2' + String.Escape: "#4e9a06", # class: 'se' + String.Heredoc: "#4e9a06", # class: 'sh' + String.Interpol: "#4e9a06", # class: 'si' + String.Other: "#4e9a06", # class: 'sx' + String.Regex: "#4e9a06", # class: 'sr' + String.Single: "#4e9a06", # class: 's1' + String.Symbol: "#4e9a06", # class: 'ss' + + Generic: "#000000", # class: 'g' + Generic.Deleted: "#a40000", # class: 'gd' + Generic.Emph: "italic #000000", # class: 'ge' + Generic.Error: "#ef2929", # class: 'gr' + Generic.Heading: "bold #000080", # class: 'gh' + Generic.Inserted: "#00A000", # class: 'gi' + Generic.Output: "#888", # class: 'go' + Generic.Prompt: "#745334", # class: 'gp' + Generic.Strong: "bold #000000", # class: 'gs' + Generic.Subheading: "bold #800080", # class: 'gu' + Generic.Traceback: "bold #a40000", # class: 'gt' + } diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/flaskstyle.sty --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/flaskstyle.sty Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,118 @@ +\definecolor{TitleColor}{rgb}{0,0,0} +\definecolor{InnerLinkColor}{rgb}{0,0,0} + +\renewcommand{\maketitle}{% + \begin{titlepage}% + \let\footnotesize\small + \let\footnoterule\relax + \ifsphinxpdfoutput + \begingroup + % This \def is required to deal with multi-line authors; it + % changes \\ to ', ' (comma-space), making it pass muster for + % generating document info in the PDF file. + \def\\{, } + \pdfinfo{ + /Author (\@author) + /Title (\@title) + } + \endgroup + \fi + \begin{flushright}% + %\sphinxlogo% + {\center + \vspace*{3cm} + \includegraphics{logo.pdf} + \vspace{3cm} + \par + {\rm\Huge \@title \par}% + {\em\LARGE \py@release\releaseinfo \par} + {\large + \@date \par + \py@authoraddress \par + }}% + \end{flushright}%\par + \@thanks + \end{titlepage}% + \cleardoublepage% + \setcounter{footnote}{0}% + \let\thanks\relax\let\maketitle\relax + %\gdef\@thanks{}\gdef\@author{}\gdef\@title{} +} + +\fancypagestyle{normal}{ + \fancyhf{} + \fancyfoot[LE,RO]{{\thepage}} + \fancyfoot[LO]{{\nouppercase{\rightmark}}} + \fancyfoot[RE]{{\nouppercase{\leftmark}}} + \fancyhead[LE,RO]{{ \@title, \py@release}} + \renewcommand{\headrulewidth}{0.4pt} + \renewcommand{\footrulewidth}{0.4pt} +} + +\fancypagestyle{plain}{ + \fancyhf{} + \fancyfoot[LE,RO]{{\thepage}} + \renewcommand{\headrulewidth}{0pt} + \renewcommand{\footrulewidth}{0.4pt} +} + +\titleformat{\section}{\Large}% + {\py@TitleColor\thesection}{0.5em}{\py@TitleColor}{\py@NormalColor} +\titleformat{\subsection}{\large}% + {\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} +\titleformat{\subsubsection}{}% + {\py@TitleColor\thesubsubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} +\titleformat{\paragraph}{\large}% + {\py@TitleColor}{0em}{\py@TitleColor}{\py@NormalColor} + +\ChNameVar{\raggedleft\normalsize} +\ChNumVar{\raggedleft \bfseries\Large} +\ChTitleVar{\raggedleft \rm\Huge} + +\renewcommand\thepart{\@Roman\c@part} +\renewcommand\part{% + \pagestyle{empty} + \if@noskipsec \leavevmode \fi + \cleardoublepage + \vspace*{6cm}% + \@afterindentfalse + \secdef\@part\@spart} + +\def\@part[#1]#2{% + \ifnum \c@secnumdepth >\m@ne + \refstepcounter{part}% + \addcontentsline{toc}{part}{\thepart\hspace{1em}#1}% + \else + \addcontentsline{toc}{part}{#1}% + \fi + {\parindent \z@ %\center + \interlinepenalty \@M + \normalfont + \ifnum \c@secnumdepth >\m@ne + \rm\Large \partname~\thepart + \par\nobreak + \fi + \MakeUppercase{\rm\Huge #2}% + \markboth{}{}\par}% + \nobreak + \vskip 8ex + \@afterheading} +\def\@spart#1{% + {\parindent \z@ %\center + \interlinepenalty \@M + \normalfont + \huge \bfseries #1\par}% + \nobreak + \vskip 3ex + \@afterheading} + +% use inconsolata font +\usepackage{inconsolata} + +% fix single quotes, for inconsolata. (does not work) +%%\usepackage{textcomp} +%%\begingroup +%% \catcode`'=\active +%% \g@addto@macro\@noligs{\let'\textsinglequote} +%% \endgroup +%%\endinput diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/foreword.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/foreword.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,109 @@ +Foreword +======== + +Read this before you get started with Flask. This hopefully answers some +questions about the purpose and goals of the project, and when you +should or should not be using it. + +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. + +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. + +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 +layer, no form validation or anything else where different libraries +already exist that can handle that. However Flask knows the concept of +extensions that can add this functionality into your application as if it +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. + +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 +---------------------------- + +I'm not joking. Well, maybe a little. If you write a web +application, you are probably allowing users to register and leave their +data on your server. The users are entrusting you with data. And even if +you are the only user that might leave data in your application, you still +want that data to be stored securely. + +Unfortunately, there are many ways the security of a web application can be +compromised. Flask protects you against one of the most common security +problems of modern web applications: cross-site scripting (XSS). Unless +you deliberately mark insecure HTML as secure, Flask and the underlying +Jinja2 template engine have you covered. But there are many more ways to +cause security problems. + +The documentation will warn you about aspects of web development that +require attention to security. Some of these security concerns +are far more complex than one might think, and we all sometimes underestimate +the likelihood that a vulnerability will be exploited, until a clever +attacker figures out a way to exploit our applications. And don't think +that your application is not important enough to attract an attacker. +Depending on the kind of attack, chances are that automated bots are +probing for ways to fill your database with spam, links to malicious +software, and the like. + +So always keep security in mind when doing web development. + +The Status of Python 3 +---------------------- + +Currently the Python community is in the process of improving libraries to +support the new iteration of the Python programming language. +Unfortunately there are a few problems with Python 3, namely the missing +consent on what WSGI for Python 3 should look like. These problems are +partially caused by changes in the language that went unreviewed for too +long, also partially the ambitions of everyone involved to drive the WSGI +standard forward. + +Because of that we strongly recommend against using Python 3 for web +development of any kind and wait until the WSGI situation is resolved. +You will find a couple of frameworks and web libraries on PyPI that claim +Python 3 support, but this support is based on the broken WSGI +implementation provided by Python 3.0 and 3.1 which will most likely +change in the near future. + +Werkzeug and Flask will be ported to Python 3 as soon as a solution for +WSGI is found, and we will provide helpful tips how to upgrade existing +applications to Python 3. Until then, we strongly recommend using Python +2.6 and 2.7 with activated Python 3 warnings during development, as well +as the Unicode literals `__future__` feature. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/htmlfaq.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/htmlfaq.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,207 @@ +HTML/XHTML FAQ +============== + +The Flask documentation and example applications are using HTML5. You +may notice that in many situations, when end tags are optional they are +not used, so that the HTML is cleaner and faster to load. Because there +is much confusion about HTML and XHTML among developers, this document tries +to answer some of the major questions. + + +History of XHTML +---------------- + +For a while, it appeared that HTML was about to be replaced by XHTML. +However, barely any websites on the Internet are actual XHTML (which is +HTML processed using XML rules). There are a couple of major reasons +why this is the case. One of them is Internet Explorer's lack of proper +XHTML support. The XHTML spec states that XHTML must be served with the MIME +type `application/xhtml+xml`, but Internet Explorer refuses to read files +with that MIME type. +While it is relatively easy to configure Web servers to serve XHTML properly, +few people do. This is likely because properly using XHTML can be quite +painful. + +One of the most important causes of pain is XML's draconian (strict and +ruthless) error handling. When an XML parsing error is encountered, +the browser is supposed to show the user an ugly error message, instead +of attempting to recover from the error and display what it can. Most of +the (X)HTML generation on the web is based on non-XML template engines +(such as Jinja, the one used in Flask) which do not protect you from +accidentally creating invalid XHTML. There are XML based template engines, +such as Kid and the popular Genshi, but they often come with a larger +runtime overhead and, are not as straightforward to use because they have +to obey XML rules. + +The majority of users, however, assumed they were properly using XHTML. +They wrote an XHTML doctype at the top of the document and self-closed all +the necessary tags (``
`` becomes ``
`` or ``

`` in XHTML). +However, even if the document properly validates as XHTML, what really +determines XHTML/HTML processing in browsers is the MIME type, which as +said before is often not set properly. So the valid XHTML was being treated +as invalid HTML. + +XHTML also changed the way JavaScript is used. To properly work with XHTML, +programmers have to use the namespaced DOM interface with the XHTML +namespace to query for HTML elements. + +History of HTML5 +---------------- + +Development of the HTML5 specification was started in 2004 under the name +"Web Applications 1.0" by the Web Hypertext Application Technology Working +Group, or WHATWG (which was formed by the major browser vendors Apple, +Mozilla, and Opera) with the goal of writing a new and improved HTML +specification, based on existing browser behaviour instead of unrealistic +and backwards-incompatible specifications. + +For example, in HTML4 ``Hello``. However, since people were using +XHTML-like tags along the lines of ````, browser vendors implemented +the XHTML syntax over the syntax defined by the specification. + +In 2007, the specification was adopted as the basis of a new HTML +specification under the umbrella of the W3C, known as HTML5. Currently, +it appears that XHTML is losing traction, as the XHTML 2 working group has +been disbanded and HTML5 is being implemented by all major browser vendors. + +HTML versus XHTML +----------------- + +The following table gives you a quick overview of features available in +HTML 4.01, XHTML 1.1 and HTML5. (XHTML 1.0 is not included, as it was +superseded by XHTML 1.1 and the barely-used XHTML5.) + +.. tabularcolumns:: |p{9cm}|p{2cm}|p{2cm}|p{2cm}| + ++-----------------------------------------+----------+----------+----------+ +| | HTML4.01 | XHTML1.1 | HTML5 | ++=========================================+==========+==========+==========+ +| ``value`` | |Y| [1]_ | |N| | |N| | ++-----------------------------------------+----------+----------+----------+ +| ``
`` supported | |N| | |Y| | |Y| [2]_ | ++-----------------------------------------+----------+----------+----------+ +| `` + +Another method is using Google's `AJAX Libraries API +`_ to load jQuery: + +.. sourcecode:: html + + + +In this case you don't have to put jQuery into your static folder, it will +instead be loaded from Google directly. This has the advantage that your +website will probably load faster for users if they went to at least one +other website before using the same jQuery version from Google because it +will already be in the browser cache. Downside is that if you don't have +network connectivity during development jQuery will not load. + +Where is My Site? +----------------- + +Do you know where your application is? If you are developing the answer +is quite simple: it's on localhost port something and directly on the root +of that server. But what if you later decide to move your application to +a different location? For example to ``http://example.com/myapp``? On +the server side this never was a problem because we were using the handy +:func:`~flask.url_for` function that could answer that question for +us, but if we are using jQuery we should not hardcode the path to +the application but make that dynamic, so how can we do that? + +A simple method would be to add a script tag to our page that sets a +global variable to the prefix to the root of the application. Something +like this: + +.. sourcecode:: html+jinja + + + +The ``|safe`` is necessary so that Jinja does not escape the JSON encoded +string with HTML rules. Usually this would be necessary, but we are +inside a `script` block here where different rules apply. + +.. admonition:: Information for Pros + + In HTML the `script` tag is declared `CDATA` which means that entities + will not be parsed. Everything until ```` is handled as script. + This also means that there must never be any ``"|tojson|safe }}`` is rendered as + ``"<\/script>"``). + + +JSON View Functions +------------------- + +Now let's create a server side function that accepts two URL arguments of +numbers which should be added together and then sent back to the +application in a JSON object. This is a really ridiculous example and is +something you usually would do on the client side alone, but a simple +example that shows how you would use jQuery and Flask nonetheless:: + + from flask import Flask, jsonify, render_template, request + app = Flask(__name__) + + @app.route('/_add_numbers') + def add_numbers(): + a = request.args.get('a', 0, type=int) + b = request.args.get('b', 0, type=int) + return jsonify(result=a + b) + + @app.route('/') + def index(): + return render_template('index.html') + +As you can see I also added an `index` method here that renders a +template. This template will load jQuery as above and have a little form +we can add two numbers and a link to trigger the function on the server +side. + +Note that we are using the :meth:`~werkzeug.MultiDict.get` method here +which will never fail. If the key is missing a default value (here ``0``) +is returned. Furthermore it can convert values to a specific type (like +in our case `int`). This is especially handy for code that is +triggered by a script (APIs, JavaScript etc.) because you don't need +special error reporting in that case. + +The HTML +-------- + +Your index.html template either has to extend a `layout.html` template with +jQuery loaded and the `$SCRIPT_ROOT` variable set, or do that on the top. +Here's the HTML code needed for our little application (`index.html`). +Notice that we also drop the script directly into the HTML here. It is +usually a better idea to have that in a separate script file: + +.. sourcecode:: html + + +

jQuery Example

+

+ + = + ? +

calculate server side + +I won't got into detail here about how jQuery works, just a very quick +explanation of the little bit of code above: + +1. ``$(function() { ... })`` specifies code that should run once the + browser is done loading the basic parts of the page. +2. ``$('selector')`` selects an element and lets you operate on it. +3. ``element.bind('event', func)`` specifies a function that should run + when the user clicked on the element. If that function returns + `false`, the default behaviour will not kick in (in this case, navigate + to the `#` URL). +4. ``$.getJSON(url, data, func)`` sends a `GET` request to `url` and will + send the contents of the `data` object as query parameters. Once the + data arrived, it will call the given function with the return value as + argument. Note that we can use the `$SCRIPT_ROOT` variable here that + we set earlier. + +If you don't get the whole picture, download the `sourcecode +for this example +`_ +from github. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/patterns/lazyloading.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/patterns/lazyloading.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,104 @@ +Lazily Loading Views +==================== + +Flask is usually used with the decorators. Decorators are simple and you +have the URL right next to the function that is called for that specific +URL. However there is a downside to this approach: it means all your code +that uses decorators has to be imported upfront or Flask will never +actually find your function. + +This can be a problem if your application has to import quick. It might +have to do that on systems like Google's App Engine or other systems. So +if you suddenly notice that your application outgrows this approach you +can fall back to a centralized URL mapping. + +The system that enables having a central URL map is the +:meth:`~flask.Flask.add_url_rule` function. Instead of using decorators, +you have a file that sets up the application with all URLs. + +Converting to Centralized URL Map +--------------------------------- + +Imagine the current application looks somewhat like this:: + + from flask import Flask + app = Flask(__name__) + + @app.route('/') + def index(): + pass + + @app.route('/user/') + def user(username): + pass + +Then the centralized approach you would have one file with the views +(`views.py`) but without any decorator:: + + def index(): + pass + + def user(username): + pass + +And then a file that sets up an application which maps the functions to +URLs:: + + from flask import Flask + from yourapplication import views + app = Flask(__name__) + app.add_url_rule('/', view_func=views.index) + app.add_url_rule('/user/', view_func=views.user) + +Loading Late +------------ + +So far we only split up the views and the routing, but the module is still +loaded upfront. The trick to actually load the view function as needed. +This can be accomplished with a helper class that behaves just like a +function but internally imports the real function on first use:: + + from werkzeug import import_string, cached_property + + class LazyView(object): + + def __init__(self, import_name): + self.__module__, self.__name__ = import_name.rsplit('.', 1) + self.import_name = import_name + + @cached_property + def view(self): + return import_string(self.import_name) + + def __call__(self, *args, **kwargs): + return self.view(*args, **kwargs) + +What's important here is is that `__module__` and `__name__` are properly +set. This is used by Flask internally to figure out how to name the +URL rules in case you don't provide a name for the rule yourself. + +Then you can define your central place to combine the views like this:: + + from flask import Flask + from yourapplication.helpers import LazyView + app = Flask(__name__) + app.add_url_rule('/', + view_func=LazyView('yourapplication.views.index')) + app.add_url_rule('/user/', + view_func=LazyView('yourapplication.views.user')) + +You can further optimize this in terms of amount of keystrokes needed to +write this by having a function that calls into +:meth:`~flask.Flask.add_url_rule` by prefixing a string with the project +name and a dot, and by wrapping `view_func` in a `LazyView` as needed:: + + def url(url_rule, import_name, **options): + view = LazyView('yourapplication.' + import_name) + app.add_url_rule(url_rule, view_func=view, **options) + + url('/', 'views.index') + url('/user/', 'views.user') + +One thing to keep in mind is that before and after request handlers have +to be in a file that is imported upfront to work properly on the first +request. The same goes for any kind of remaining decorator. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/patterns/mongokit.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/patterns/mongokit.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,144 @@ +.. mongokit-pattern: + +MongoKit in Flask +================= + +Using a document database rather than a full DBMS gets more common these days. +This pattern shows how to use MongoKit, a document mapper library, to +integrate with MongoDB. + +This pattern requires a running MongoDB server and the MongoKit library +installed. + +There are two very common ways to use MongoKit. I will outline each of them +here: + + +Declarative +----------- + +The default behaviour of MongoKit is the declarative one that is based on +common ideas from Django or the SQLAlchemy declarative extension. + +Here an example `app.py` module for your application:: + + from flask import Flask + from mongokit import Connection, Document + + # configuration + MONGODB_HOST = 'localhost' + MONGODB_PORT = 27017 + + # create the little application object + app = Flask(__name__) + app.config.from_object(__name__) + + # connect to the database + connection = Connection(app.config['MONGODB_HOST'], + app.config['MONGODB_PORT']) + + +To define your models, just subclass the `Document` class that is imported +from MongoKit. If you've seen the SQLAlchemy pattern you may wonder why we do +not have a session and even do not define a `init_db` function here. On the +one hand, MongoKit does not have something like a session. This sometimes +makes it more to type but also makes it blazingly fast. On the other hand, +MongoDB is schemaless. This means you can modify the data structure from one +insert query to the next without any problem. MongoKit is just schemaless +too, but implements some validation to ensure data integrity. + +Here is an example document (put this also into `app.py`, e.g.):: + + def max_length(length): + def validate(value): + if len(value) <= length: + return True + raise Exception('%s must be at most %s characters long' % length) + return validate + + class User(Document): + structure = { + 'name': unicode, + 'email': unicode, + } + validators = { + 'name': max_length(50), + 'email': max_length(120) + } + use_dot_notation = True + def __repr__(self): + return '' % (self.name) + + # register the User document with our current connection + connection.register([User]) + + +This example shows you how to define your schema (named structure), a +validator for the maximum character length and uses a special MongoKit feature +called `use_dot_notation`. Per default MongoKit behaves like a python +dictionary but with `use_dot_notation` set to `True` you can use your +documents like you use models in nearly any other ORM by using dots to +separate between attributes. + +You can insert entries into the database like this: + +>>> from yourapplication.database import connection +>>> from yourapplication.models import User +>>> collection = connection['test'].users +>>> user = collection.User() +>>> user['name'] = u'admin' +>>> user['email'] = u'admin@localhost' +>>> user.save() + +Note that MongoKit is kinda strict with used column types, you must not use a +common `str` type for either `name` or `email` but unicode. + +Querying is simple as well: + +>>> list(collection.User.find()) +[] +>>> collection.User.find_one({'name': u'admin'}) + + +.. _MongoKit: http://bytebucket.org/namlook/mongokit/ + + +PyMongo Compatibility Layer +--------------------------- + +If you just want to use PyMongo, you can do that with MongoKit as well. You +may use this process if you need the best performance to get. Note that this +example does not show how to couple it with Flask, see the above MongoKit code +for examples:: + + from MongoKit import Connection + + connection = Connection() + +To insert data you can use the `insert` method. We have to get a +collection first, this is somewhat the same as a table in the SQL world. + +>>> collection = connection['test'].users +>>> user = {'name': u'admin', 'email': u'admin@localhost'} +>>> collection.insert(user) + +print list(collection.find()) +print collection.find_one({'name': u'admin'}) + +MongoKit will automatically commit for us. + +To query your database, you use the collection directly: + +>>> list(collection.find()) +[{u'_id': ObjectId('4c271729e13823182f000000'), u'name': u'admin', u'email': u'admin@localhost'}] +>>> collection.find_one({'name': u'admin'}) +{u'_id': ObjectId('4c271729e13823182f000000'), u'name': u'admin', u'email': u'admin@localhost'} + +These results are also dict-like objects: + +>>> r = collection.find_one({'name': u'admin'}) +>>> r['email'] +u'admin@localhost' + +For more information about MongoKit, head over to the +`website `_. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/patterns/packages.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/patterns/packages.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,286 @@ +.. _larger-applications: + +Larger Applications +=================== + +For larger applications it's a good idea to use a package instead of a +module. That is quite simple. Imagine a small application looks like +this:: + + /yourapplication + /yourapplication.py + /static + /style.css + /templates + layout.html + index.html + login.html + ... + +Simple Packages +--------------- + +To convert that into a larger one, just create a new folder +`yourapplication` inside the existing one and move everything below it. +Then rename `yourapplication.py` to `__init__.py`. (Make sure to delete +all `.pyc` files first, otherwise things would most likely break) + +You should then end up with something like that:: + + /yourapplication + /yourapplication + /__init__.py + /static + /style.css + /templates + layout.html + index.html + login.html + ... + +But how do you run your application now? The naive ``python +yourapplication/__init__.py`` will not work. Let's just say that Python +does not want modules in packages to be the startup file. But that is not +a big problem, just add a new file called `runserver.py` next to the inner +`yourapplication` folder with the following contents:: + + from yourapplication import app + app.run(debug=True) + +What did we gain from this? Now we can restructure the application a bit +into multiple modules. The only thing you have to remember is the +following quick checklist: + +1. the `Flask` application object creation has to be in the + `__init__.py` file. That way each module can import it safely and the + `__name__` variable will resolve to the correct package. +2. all the view functions (the ones with a :meth:`~flask.Flask.route` + decorator on top) have to be imported when in the `__init__.py` file. + Not the object itself, but the module it is in. Do the importing at + the *bottom* of the file. + +Here's an example `__init__.py`:: + + from flask import Flask + app = Flask(__name__) + + import yourapplication.views + +And this is what `views.py` would look like:: + + from yourapplication import app + + @app.route('/') + def index(): + return 'Hello World!' + +You should then end up with something like that:: + + /yourapplication + /yourapplication + /__init__.py + /views.py + /static + /style.css + /templates + layout.html + index.html + login.html + ... + +.. admonition:: Circular Imports + + Every Python programmer hates them, and yet we just added some: + circular imports (That's when two modules depend on each other. In this + case `views.py` depends on `__init__.py`). Be advised that this is a + bad idea in general but here it is actually fine. The reason for this is + that we are not actually using the views in `__init__.py` and just + ensuring the module is imported and we are doing that at the bottom of + the file. + + There are still some problems with that approach but if you want to use + decorators there is no way around that. Check out the + :ref:`becomingbig` section for some inspiration how to deal with that. + + +.. _working-with-modules: + +Working with Modules +-------------------- + +For larger applications with more than a dozen views it makes sense to +split the views into modules. First let's look at the typical structure of +such an application:: + + /yourapplication + /yourapplication + /__init__.py + /views + __init__.py + admin.py + frontend.py + /static + /style.css + /templates + layout.html + index.html + login.html + ... + +The views are stored in the `yourapplication.views` package. Just make +sure to place an empty `__init__.py` file in there. Let's start with the +`admin.py` file in the view package. + +First we have to create a :class:`~flask.Module` object with the name of +the package. This works very similar to the :class:`~flask.Flask` object +you have already worked with, it just does not support all of the methods, +but most of them are the same. + +Long story short, here's a nice and concise example:: + + from flask import Module + + admin = Module(__name__) + + @admin.route('/') + def index(): + pass + + @admin.route('/login') + def login(): + pass + + @admin.route('/logout') + def logout(): + pass + +Do the same with the `frontend.py` and then make sure to register the +modules in the application (`__init__.py`) like this:: + + from flask import Flask + from yourapplication.views.admin import admin + from yourapplication.views.frontend import frontend + + app = Flask(__name__) + app.register_module(admin, url_prefix='/admin') + app.register_module(frontend) + +We register the modules with the app so that it can add them to the +URL map for our application. Note the prefix argument to the admin +module: by default when we register a module, that module's end-points +will be registered on `/` unless we specify this argument. + +So what is different when working with modules? It mainly affects URL +generation. Remember the :func:`~flask.url_for` function? When not +working with modules it accepts the name of the function as first +argument. This first argument is called the "endpoint". When you are +working with modules you can use the name of the function like you did +without, when generating modules from a function or template in the same +module. If you want to generate the URL to another module, prefix it with +the name of the module and a dot. + +Confused? Let's clear that up with some examples. Imagine you have a +method in one module (say `admin`) and you want to redirect to a +different module (say `frontend`). This would look like this:: + + @admin.route('/to_frontend') + def to_frontend(): + return redirect(url_for('frontend.index')) + + @frontend.route('/') + def index(): + return "I'm the frontend index" + +Now let's say we only want to redirect to a different function in the same +module. Then we can either use the full qualified endpoint name like we +did in the example above, or we just use the function name:: + + @frontend.route('/to_index') + def to_index(): + return redirect(url_for('index')) + + @frontend.route('/') + def index(): + return "I'm the index" + +.. _modules-and-resources: + +Modules and Resources +--------------------- + +.. versionadded:: 0.5 + +If a module is located inside an actual Python package it may contain +static files and templates. Imagine you have an application like this:: + + + /yourapplication + __init__.py + /apps + __init__.py + /frontend + __init__.py + views.py + /static + style.css + /templates + index.html + about.html + ... + /admin + __init__.py + views.py + /static + style.css + /templates + list_items.html + show_item.html + ... + +The static folders automatically become exposed as URLs. For example if +the `admin` module is exported with an URL prefix of ``/admin`` you can +access the style css from its static folder by going to +``/admin/static/style.css``. The URL endpoint for the static files of the +admin would be ``'admin.static'``, similar to how you refer to the regular +static folder of the whole application as ``'static'``. + +If you want to refer to the templates you just have to prefix it with the +name of the module. So for the admin it would be +``render_template('admin/list_items.html')`` and so on. It is not +possible to refer to templates without the prefixed module name. This is +explicit unlike URL rules. + +You also need to explicitly pass the ``url_prefix`` argument when +registering your modules this way:: + + # in yourapplication/__init__.py + from flask import Flask + from yourapplication.apps.admin.views import admin + from yourapplication.apps.frontend.views import frontend + + + app = Flask(__name__) + app.register_module(admin, url_prefix='/admin') + app.register_module(frontend, url_prefix='/frontend') + +This is because Flask cannot infer the prefix from the package names. + +.. admonition:: References to Static Folders + + Please keep in mind that if you are using unqualified endpoints by + default Flask will always assume the module's static folder, even if + there is no such folder. + + If you want to refer to the application's static folder, use a leading + dot:: + + # this refers to the application's static folder + url_for('.static', filename='static.css') + + # this refers to the current module's static folder + url_for('static', filename='static.css') + + This is the case for all endpoints, not just static folders, but for + static folders it's more common that you will stumble upon this because + most applications will have a static folder in the application and not + a specific module. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/patterns/sqlalchemy.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/patterns/sqlalchemy.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,216 @@ +.. _sqlalchemy-pattern: + +SQLAlchemy in Flask +=================== + +Many people prefer `SQLAlchemy`_ for database access. In this case it's +encouraged to use a package instead of a module for your flask application +and drop the models into a separate module (:ref:`larger-applications`). +While that is not necessary, it makes a lot of sense. + +There are four very common ways to use SQLAlchemy. I will outline each +of them here: + +Flask-SQLAlchemy Extension +-------------------------- + +Because SQLAlchemy is a common database abstraction layer and object +relational mapper that requires a little bit of configuration effort, +there is a Flask extension that handles that for you. This is recommended +if you want to get started quickly. + +You can download `Flask-SQLAlchemy`_ from `PyPI +`_. + +.. _Flask-SQLAlchemy: http://packages.python.org/Flask-SQLAlchemy/ + + +Declarative +----------- + +The declarative extension in SQLAlchemy is the most recent method of using +SQLAlchemy. It allows you to define tables and models in one go, similar +to how Django works. In addition to the following text I recommend the +official documentation on the `declarative`_ extension. + +Here the example `database.py` module for your application:: + + from sqlalchemy import create_engine + from sqlalchemy.orm import scoped_session, sessionmaker + from sqlalchemy.ext.declarative import declarative_base + + engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) + db_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine)) + Base = declarative_base() + Base.query = db_session.query_property() + + def init_db(): + # import all modules here that might define models so that + # they will be registered properly on the metadata. Otherwise + # you will have to import them first before calling init_db() + import yourapplication.models + Base.metadata.create_all(bind=engine) + +To define your models, just subclass the `Base` class that was created by +the code above. If you are wondering why we don't have to care about +threads here (like we did in the SQLite3 example above with the +:data:`~flask.g` object): that's because SQLAlchemy does that for us +already with the :class:`~sqlalchemy.orm.scoped_session`. + +To use SQLAlchemy in a declarative way with your application, you just +have to put the following code into your application module. Flask will +automatically remove database sessions at the end of the request for you:: + + from yourapplication.database import db_session + + @app.after_request + def shutdown_session(response): + db_session.remove() + return response + +Here is an example model (put this into `models.py`, e.g.):: + + from sqlalchemy import Column, Integer, String + from yourapplication.database import Base + + class User(Base): + __tablename__ = 'users' + id = Column(Integer, primary_key=True) + name = Column(String(50), unique=True) + email = Column(String(120), unique=True) + + def __init__(self, name=None, email=None): + self.name = name + self.email = email + + def __repr__(self): + return '' % (self.name) + +To create the database you can use the `init_db` function: + +>>> from yourapplication.database import init_db +>>> init_db() + +You can insert entries into the database like this: + +>>> from yourapplication.database import db_session +>>> from yourapplication.models import User +>>> u = User('admin', 'admin@localhost') +>>> db_session.add(u) +>>> db_session.commit() + +Querying is simple as well: + +>>> User.query.all() +[] +>>> User.query.filter(User.name == 'admin').first() + + +.. _SQLAlchemy: http://www.sqlalchemy.org/ +.. _declarative: + http://www.sqlalchemy.org/docs/reference/ext/declarative.html + +Manual Object Relational Mapping +-------------------------------- + +Manual object relational mapping has a few upsides and a few downsides +versus the declarative approach from above. The main difference is that +you define tables and classes separately and map them together. It's more +flexible but a little more to type. In general it works like the +declarative approach, so make sure to also split up your application into +multiple modules in a package. + +Here is an example `database.py` module for your application:: + + from sqlalchemy import create_engine, MetaData + from sqlalchemy.orm import scoped_session, sessionmaker + + engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) + metadata = MetaData() + db_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine)) + def init_db(): + metadata.create_all(bind=engine) + +As for the declarative approach you need to close the session after +each request. Put this into your application module:: + + from yourapplication.database import db_session + + @app.after_request + def shutdown_session(response): + db_session.remove() + return response + +Here is an example table and model (put this into `models.py`):: + + from sqlalchemy import Table, Column, Integer, String + from sqlalchemy.orm import mapper + from yourapplication.database import metadata, db_session + + class User(object): + query = db_session.query_property() + + def __init__(self, name=None, email=None): + self.name = name + self.email = email + + def __repr__(self): + return '' % (self.name, self.email) + + users = Table('users', metadata, + Column('id', Integer, primary_key=True), + Column('name', String(50), unique=True), + Column('email', String(120), unique=True) + ) + mapper(User, users) + +Querying and inserting works exactly the same as in the example above. + + +SQL Abstraction Layer +--------------------- + +If you just want to use the database system (and SQL) abstraction layer +you basically only need the engine:: + + from sqlalchemy import create_engine, MetaData + + engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) + metadata = MetaData(bind=engine) + +Then you can either declare the tables in your code like in the examples +above, or automatically load them:: + + users = Table('users', metadata, autoload=True) + +To insert data you can use the `insert` method. We have to get a +connection first so that we can use a transaction: + +>>> con = engine.connect() +>>> con.execute(users.insert(name='admin', email='admin@localhost')) + +SQLAlchemy will automatically commit for us. + +To query your database, you use the engine directly or use a connection: + +>>> users.select(users.c.id == 1).execute().first() +(1, u'admin', u'admin@localhost') + +These results are also dict-like tuples: + +>>> r = users.select(users.c.id == 1).execute().first() +>>> r['name'] +u'admin' + +You can also pass strings of SQL statements to the +:meth:`~sqlalchemy.engine.base.Connection.execute` method: + +>>> engine.execute('select * from users where id = :1', [1]).first() +(1, u'admin', u'admin@localhost') + +For more information about SQLAlchemy, head over to the +`website `_. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/patterns/sqlite3.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/patterns/sqlite3.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,87 @@ +.. _sqlite3: + +Using SQLite 3 with Flask +========================= + +In Flask you can implement the opening of database connections at the +beginning of the request and closing at the end with the +:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.after_request` +decorators in combination with the special :class:`~flask.g` object. + +So here is a simple example of how you can use SQLite 3 with Flask:: + + import sqlite3 + from flask import g + + DATABASE = '/path/to/database.db' + + def connect_db(): + return sqlite3.connect(DATABASE) + + @app.before_request + def before_request(): + g.db = connect_db() + + @app.after_request + def after_request(response): + g.db.close() + return response + +.. _easy-querying: + +Easy Querying +------------- + +Now in each request handling function you can access `g.db` to get the +current open database connection. To simplify working with SQLite, a +helper function can be useful:: + + def query_db(query, args=(), one=False): + cur = g.db.execute(query, args) + rv = [dict((cur.description[idx][0], value) + for idx, value in enumerate(row)) for row in cur.fetchall()] + return (rv[0] if rv else None) if one else rv + +This handy little function makes working with the database much more +pleasant than it is by just using the raw cursor and connection objects. + +Here is how you can use it:: + + for user in query_db('select * from users'): + print user['username'], 'has the id', user['user_id'] + +Or if you just want a single result:: + + user = query_db('select * from users where username = ?', + [the_username], one=True) + if user is None: + print 'No such user' + else: + print the_username, 'has the id', user['user_id'] + +To pass variable parts to the SQL statement, use a question mark in the +statement and pass in the arguments as a list. Never directly add them to +the SQL statement with string formatting because this makes it possible +to attack the application using `SQL Injections +`_. + +Initial Schemas +--------------- + +Relational databases need schemas, so applications often ship a +`schema.sql` file that creates the database. It's a good idea to provide +a function that creates the database based on that schema. This function +can do that for you:: + + from contextlib import closing + + def init_db(): + with closing(connect_db()) as db: + with app.open_resource('schema.sql') as f: + db.cursor().executescript(f.read()) + db.commit() + +You can then create such a database from the python shell: + +>>> from yourapplication import init_db +>>> init_db() diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/patterns/templateinheritance.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/patterns/templateinheritance.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,69 @@ +.. _template-inheritance: + +Template Inheritance +==================== + +The most powerful part of Jinja is template inheritance. Template inheritance +allows you to build a base "skeleton" template that contains all the common +elements of your site and defines **blocks** that child templates can override. + +Sounds complicated but is very basic. It's easiest to understand it by starting +with an example. + + +Base Template +------------- + +This template, which we'll call ``layout.html``, defines a simple HTML skeleton +document that you might use for a simple two-column page. It's the job of +"child" templates to fill the empty blocks with content: + +.. sourcecode:: html+jinja + + + + + {% block head %} + + {% block title %}{% endblock %} - My Webpage + {% endblock %} + + +

{% block content %}{% endblock %}
+ + + +In this example, the ``{% block %}`` tags define four blocks that child templates +can fill in. All the `block` tag does is to tell the template engine that a +child template may override those portions of the template. + +Child Template +-------------- + +A child template might look like this: + +.. sourcecode:: html+jinja + + {% extends "layout.html" %} + {% block title %}Index{% endblock %} + {% block head %} + {{ super() }} + + {% endblock %} + {% block content %} +

Index

+

+ Welcome on my awesome homepage. + {% endblock %} + +The ``{% extends %}`` tag is the key here. It tells the template engine that +this template "extends" another template. When the template system evaluates +this template, first it locates the parent. The extends tag must be the +first tag in the template. To render the contents of a block defined in +the parent template, use ``{{ super() }}``. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/patterns/viewdecorators.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/patterns/viewdecorators.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,168 @@ +View Decorators +=============== + +Python has a really interesting feature called function decorators. This +allow some really neat things for web applications. Because each view in +Flask is a function decorators can be used to inject additional +functionality to one or more functions. The :meth:`~flask.Flask.route` +decorator is the one you probably used already. But there are use cases +for implementing your own decorator. For instance, imagine you have a +view that should only be used by people that are logged in to. If a user +goes to the site and is not logged in, he should be redirected to the +login page. This is a good example of a use case where a decorator is an +excellent solution. + +Login Required Decorator +------------------------ + +So let's implement such a decorator. A decorator is a function that +returns a function. Pretty simple actually. The only thing you have to +keep in mind when implementing something like this is to update the +`__name__`, `__module__` and some other attributes of a function. This is +often forgotten, but you don't have to do that by hand, there is a +function for that that is used like a decorator (:func:`functools.wraps`). + +This example assumes that the login page is called ``'login'`` and that +the current user is stored as `g.user` and `None` if there is no-one +logged in:: + + from functools import wraps + from flask import g, request, redirect, url_for + + def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user is None: + return redirect(url_for('login', next=request.url)) + return f(*args, **kwargs) + return decorated_function + +So how would you use that decorator now? Apply it as innermost decorator +to a view function. When applying further decorators, always remember +that the :meth:`~flask.Flask.route` decorator is the outermost:: + + @app.route('/secret_page') + @login_required + def secret_page(): + pass + +Caching Decorator +----------------- + +Imagine you have a view function that does an expensive calculation and +because of that you would like to cache the generated results for a +certain amount of time. A decorator would be nice for that. We're +assuming you have set up a cache like mentioned in :ref:`caching-pattern`. + +Here an example cache function. It generates the cache key from a +specific prefix (actually a format string) and the current path of the +request. Notice that we are using a function that first creates the +decorator that then decorates the function. Sounds awful? Unfortunately +it is a little bit more complex, but the code should still be +straightforward to read. + +The decorated function will then work as follows + +1. get the unique cache key for the current request base on the current + path. +2. get the value for that key from the cache. If the cache returned + something we will return that value. +3. otherwise the original function is called and the return value is + stored in the cache for the timeout provided (by default 5 minutes). + +Here the code:: + + from functools import wraps + from flask import request + + def cached(timeout=5 * 60, key='view/%s'): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + cache_key = key % request.path + rv = cache.get(cache_key) + if rv is not None: + return rv + rv = f(*args, **kwargs) + cache.set(cache_key, rv, timeout=timeout) + return rv + return decorated_function + return decorator + +Notice that this assumes an instantiated `cache` object is available, see +:ref:`caching-pattern` for more information. + + +Templating Decorator +-------------------- + +A common pattern invented by the TurboGears guys a while back is a +templating decorator. The idea of that decorator is that you return a +dictionary with the values passed to the template from the view function +and the template is automatically rendered. With that, the following +three examples do exactly the same:: + + @app.route('/') + def index(): + return render_template('index.html', value=42) + + @app.route('/') + @templated('index.html') + def index(): + return dict(value=42) + + @app.route('/') + @templated() + def index(): + return dict(value=42) + +As you can see, if no template name is provided it will use the endpoint +of the URL map with dots converted to slashes + ``'.html'``. Otherwise +the provided template name is used. When the decorated function returns, +the dictionary returned is passed to the template rendering function. If +`None` is returned, an empty dictionary is assumed, if something else than +a dictionary is returned we return it from the function unchanged. That +way you can still use the redirect function or return simple strings. + +Here the code for that decorator:: + + from functools import wraps + from flask import request + + def templated(template=None): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + template_name = template + if template_name is None: + template_name = request.endpoint \ + .replace('.', '/') + '.html' + ctx = f(*args, **kwargs) + if ctx is None: + ctx = {} + elif not isinstance(ctx, dict): + return ctx + return render_template(template_name, **ctx) + return decorated_function + return decorator + + +Endpoint Decorator +------------------ + +When you want to use the werkzeug routing system for more flexibility you +need to map the endpoint as defined in the :class:`~werkzeug.routing.Rule` +to a view function. This is possible with this decorator. For example:: + + from flask import Flask + from werkzeug.routing import Rule + + app = Flask(__name__) + app.url_map.add(Rule('/', endpoint='index')) + + @app.endpoint('index') + def my_index(): + return "Hello world" + + + diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/patterns/wtforms.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/patterns/wtforms.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,124 @@ +Form Validation with WTForms +============================ + +When you have to work with form data submitted by a browser view code +quickly becomes very hard to read. There are libraries out there designed +to make this process easier to manage. One of them is `WTForms`_ which we +will handle here. If you find yourself in the situation of having many +forms, you might want to give it a try. + +When you are working with WTForms you have to define your forms as classes +first. I recommend breaking up the application into multiple modules +(:ref:`larger-applications`) for that and adding a separate module for the +forms. + +.. admonition:: Getting most of WTForms with an Extension + + The `Flask-WTF`_ extension expands on this pattern and adds a few + handful little helpers that make working with forms and Flask more + fun. You can get it from `PyPI + `_. + +.. _Flask-WTF: http://packages.python.org/Flask-WTF/ + +The Forms +--------- + +This is an example form for a typical registration page:: + + from wtforms import Form, BooleanField, TextField, validators + + class RegistrationForm(Form): + username = TextField('Username', [validators.Length(min=4, max=25)]) + email = TextField('Email Address', [validators.Length(min=6, max=35)]) + password = PasswordField('New Password', [ + validators.Required(), + validators.EqualTo('confirm', message='Passwords must match') + ]) + confirm = PasswordField('Repeat Password') + accept_tos = BooleanField('I accept the TOS', [validators.Required()]) + +In the View +----------- + +In the view function, the usage of this form looks like this:: + + @app.route('/register', methods=['GET', 'POST']) + def register(): + form = RegistrationForm(request.form) + if request.method == 'POST' and form.validate(): + user = User(form.username.data, form.email.data, + form.password.data) + db_session.add(user) + flash('Thanks for registering') + return redirect(url_for('login')) + return render_template('register.html', form=form) + +Notice that we are implying that the view is using SQLAlchemy here +(:ref:`sqlalchemy-pattern`) but this is no requirement of course. Adapt +the code as necessary. + +Things to remember: + +1. create the form from the request :attr:`~flask.request.form` value if + the data is submitted via the HTTP `POST` method and + :attr:`~flask.request.args` if the data is submitted as `GET`. +2. to validate the data, call the :func:`~wtforms.form.Form.validate` + method which will return `True` if the data validates, `False` + otherwise. +3. to access individual values from the form, access `form..data`. + +Forms in Templates +------------------ + +Now to the template side. When you pass the form to the templates you can +easily render them there. Look at the following example template to see +how easy this is. WTForms does half the form generation for us already. +To make it even nicer, we can write a macro that renders a field with +label and a list of errors if there are any. + +Here's an example `_formhelpers.html` template with such a macro: + +.. sourcecode:: html+jinja + + {% macro render_field(field) %} +

{{ field.label }} +
{{ field(**kwargs)|safe }} + {% if field.errors %} +
    + {% for error in field.errors %}
  • {{ error }}{% endfor %} +
+ {% endif %} +
+ {% endmacro %} + +This macro accepts a couple of keyword arguments that are forwarded to +WTForm's field function that renders the field for us. The keyword +arguments will be inserted as HTML attributes. So for example you can +call ``render_field(form.username, class='username')`` to add a class to +the input element. Note that WTForms returns standard Python unicode +strings, so we have to tell Jinja2 that this data is already HTML escaped +with the `|safe` filter. + +Here the `register.html` template for the function we used above which +takes advantage of the `_formhelpers.html` template: + +.. sourcecode:: html+jinja + + {% from "_formhelpers.html" import render_field %} +
+
+ {{ render_field(form.username) }} + {{ render_field(form.email) }} + {{ render_field(form.password) }} + {{ render_field(form.confirm) }} + {{ render_field(form.accept_tos) }} +
+

+

+ +For more information about WTForms, head over to the `WTForms +website`_. + +.. _WTForms: http://wtforms.simplecodes.com/ +.. _WTForms website: http://wtforms.simplecodes.com/ diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/quickstart.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/quickstart.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,745 @@ +.. _quickstart: + +Quickstart +========== + +Eager to get started? This page gives a good introduction in how to get +started with Flask. This assumes you already have Flask installed. If +you do not, head over to the :ref:`installation` section. + + +A Minimal Application +--------------------- + +A minimal Flask application looks something like that:: + + from flask import Flask + app = Flask(__name__) + + @app.route('/') + def hello_world(): + return "Hello World!" + + if __name__ == '__main__': + app.run() + +Just save it as `hello.py` or something similar and run it with your +Python interpreter. Make sure to not call your application `flask.py` +because this would conflict with Flask itself. + +:: + + $ python hello.py + * Running on http://127.0.0.1:5000/ + +Head over to `http://127.0.0.1:5000/ `_, you should +see your hello world greeting. + +So what did that code do? + +1. First we imported the :class:`~flask.Flask` class. An instance of this + class will be our WSGI application. The first argument is the name of + the application's module. If you are using a single module (like here) + you should use `__name__` because depending on if it's started as + application or imported as module the name will be different + (``'__main__'`` versus the actual import name). For more information + on that, have a look at the :class:`~flask.Flask` documentation. +2. Next we create an instance of it. We pass it the name of the module / + package. This is needed so that Flask knows where it should look for + templates, static files and so on. +3. Then we use the :meth:`~flask.Flask.route` decorator to tell Flask + what URL should trigger our function. +4. The function then has a name which is also used to generate URLs to + that particular function, and returns the message we want to display in + the user's browser. +5. Finally we use the :meth:`~flask.Flask.run` function to run the + local server with our application. The ``if __name__ == '__main__':`` + makes sure the server only runs if the script is executed directly from + the Python interpreter and not used as imported module. + +To stop the server, hit control-C. + +.. _public-server: + +.. admonition:: Externally Visible Server + + If you run the server you will notice that the server is only available + from your own computer, not from any other in the network. This is the + default because in debugging mode a user of the application can execute + arbitrary Python code on your computer. If you have `debug` disabled + or trust the users on your network, you can make the server publicly + available. + + Just change the call of the :meth:`~flask.Flask.run` method to look + like this:: + + app.run(host='0.0.0.0') + + This tells your operating system to listen on a public IP. + + +Debug Mode +---------- + +The :meth:`~flask.Flask.run` method is nice to start a local +development server, but you would have to restart it manually after each +change you do to code. That is not very nice and Flask can do better. If +you enable the debug support the server will reload itself on code changes +and also provide you with a helpful debugger if things go wrong. + +There are two ways to enable debugging. Either set that flag on the +application object:: + + app.debug = True + app.run() + +Or pass it to run:: + + app.run(debug=True) + +Both will have exactly the same effect. + +.. admonition:: Attention + + Even though the interactive debugger does not work in forking environments + (which makes it nearly impossible to use on production servers), it still + allows the execution of arbitrary code. That makes it a major security + risk and therefore it **must never be used on production machines**. + +Screenshot of the debugger in action: + +.. image:: _static/debugger.png + :align: center + :class: screenshot + :alt: screenshot of debugger in action + + +Routing +------- + +Modern web applications have beautiful URLs. This helps people remember +the URLs which is especially handy for applications that are used from +mobile devices with slower network connections. If the user can directly +go to the desired page without having to hit the index page it is more +likely he will like the page and come back next time. + +As you have seen above, the :meth:`~flask.Flask.route` decorator is used +to bind a function to a URL. Here are some basic examples:: + + @app.route('/') + def index(): + return 'Index Page' + + @app.route('/hello') + def hello(): + return 'Hello World' + +But there is more to it! You can make certain parts of the URL dynamic +and attach multiple rules to a function. + +Variable Rules +`````````````` + +To add variable parts to a URL you can mark these special sections as +````. Such a part is then passed as keyword argument to +your function. Optionally a converter can be specified by specifying a +rule with ````. Here are some nice examples:: + + @app.route('/user/') + def show_user_profile(username): + # show the user profile for that user + pass + + @app.route('/post/') + def show_post(post_id): + # show the post with the given id, the id is an integer + pass + +The following converters exist: + +=========== =========================================== +`int` accepts integers +`float` like `int` but for floating point values +`path` like the default but also accepts slashes +=========== =========================================== + +.. admonition:: Unique URLs / Redirection Behaviour + + Flask's URL rules are based on Werkzeug's routing module. The idea + behind that module is to ensure nice looking and also unique URLs based + on behaviour Apache and earlier servers coined. + + Take these two rules:: + + @app.route('/projects/') + def projects(): + pass + + @app.route('/about') + def about(): + pass + + They look rather similar, the difference is the trailing slash in the + URL *definition*. In the first case, the canonical URL for the + `projects` endpoint has a trailing slash. It's similar to a folder in + that sense. Accessing it without a trailing slash will cause Flask to + redirect to the canonical URL with the trailing slash. + + However in the second case the URL is defined without a slash so it + behaves similar to a file and accessing the URL with a trailing slash + will be a 404 error. + + Why is this? This allows relative URLs to continue working if users + access the page when they forget a trailing slash. This behaviour is + also consistent with how Apache and other servers work. Also, the URLs + will stay unique which helps search engines not indexing the same page + twice. + + +.. _url-building: + +URL Building +```````````` + +If it can match URLs, can it also generate them? Of course it can. To +build a URL to a specific function you can use the :func:`~flask.url_for` +function. It accepts the name of the function as first argument and a +number of keyword arguments, each corresponding to the variable part of +the URL rule. Unknown variable parts are appended to the URL as query +parameter. Here are some examples: + +>>> from flask import Flask, url_for +>>> app = Flask(__name__) +>>> @app.route('/') +... def index(): pass +... +>>> @app.route('/login') +... def login(): pass +... +>>> @app.route('/user/') +... def profile(username): pass +... +>>> with app.test_request_context(): +... print url_for('index') +... print url_for('login') +... print url_for('login', next='/') +... print url_for('profile', username='John Doe') +... +/ +/login +/login?next=/ +/user/John%20Doe + +(This also uses the :meth:`~flask.Flask.test_request_context` method +explained below. It basically tells Flask to think we are handling a +request even though we are not, we are in an interactive Python shell. +Have a look at the explanation below. :ref:`context-locals`). + +Why would you want to build URLs instead of hardcoding them in your +templates? There are three good reasons for this: + +1. reversing is often more descriptive than hardcoding the URLs. Also and + more importantly you can change URLs in one go without having to change + the URLs all over the place. +2. URL building will handle escaping of special characters and Unicode + data transparently for you, you don't have to deal with that. +3. If your application is placed outside the URL root (so say in + ``/myapplication`` instead of ``/``), :func:`~flask.url_for` will + handle that properly for you. + + +HTTP Methods +```````````` + +HTTP (the protocol web applications are speaking) knows different methods +to access URLs. By default a route only answers to `GET` requests, but +that can be changed by providing the `methods` argument to the +:meth:`~flask.Flask.route` decorator. Here are some examples:: + + @app.route('/login', methods=['GET', 'POST']) + def login(): + if request.method == 'POST': + do_the_login() + else: + show_the_login_form() + +If `GET` is present, `HEAD` will be added automatically for you. You +don't have to deal with that. It will also make sure that `HEAD` requests +are handled like the `HTTP RFC`_ (the document describing the HTTP +protocol) demands, so you can completely ignore that part of the HTTP +specification. Likewise as of Flask 0.6, `OPTIONS` is implemented for you +as well automatically. + +You have no idea what an HTTP method is? Worry not, here is a quick +introduction to HTTP methods and why they matter: + +The HTTP method (also often called "the verb") tells the server what the +clients wants to *do* with the requested page. The following methods are +very common: + +`GET` + The browser tells the server to just *get* the information stored on + that page and send it. This is probably the most common method. + +`HEAD` + The browser tells the server to get the information, but it is only + interested in the *headers*, not the content of the page. An + application is supposed to handle that as if a `GET` request was + received but to not deliver the actual content. In Flask you don't + have to deal with that at all, the underlying Werkzeug library handles + that for you. + +`POST` + The browser tells the server that it wants to *post* some new + information to that URL and that the server must ensure the data is + stored and only stored once. This is how HTML forms are usually + transmitting data to the server. + +`PUT` + Similar to `POST` but the server might trigger the store procedure + multiple times by overwriting the old values more than once. Now you + might be asking why is this useful, but there are some good reasons + to do it this way. Consider that the connection gets lost during + transmission: in this situation a system between the browser and the + server might receive the request safely a second time without breaking + things. With `POST` that would not be possible because it must only + be triggered once. + +`DELETE` + Remove the information at the given location. + +`OPTIONS` + Provides a quick way for a client to figure out which methods are + supported by this URL. Starting with Flask 0.6, this is implemented + for you automatically. + +Now the interesting part is that in HTML4 and XHTML1, the only methods a +form can submit to the server are `GET` and `POST`. But with JavaScript +and future HTML standards you can use the other methods as well. Furthermore +HTTP has become quite popular lately and browsers are no longer the only +clients that are using HTTP. For instance, many revision control system +use it. + +.. _HTTP RFC: http://www.ietf.org/rfc/rfc2068.txt + +Static Files +------------ + +Dynamic web applications need static files as well. That's usually where +the CSS and JavaScript files are coming from. Ideally your web server is +configured to serve them for you, but during development Flask can do that +as well. Just create a folder called `static` in your package or next to +your module and it will be available at `/static` on the application. + +To generate URLs to that part of the URL, use the special ``'static'`` URL +name:: + + url_for('static', filename='style.css') + +The file has to be stored on the filesystem as ``static/style.css``. + +Rendering Templates +------------------- + +Generating HTML from within Python is not fun, and actually pretty +cumbersome because you have to do the HTML escaping on your own to keep +the application secure. Because of that Flask configures the `Jinja2 +`_ template engine for you automatically. + +To render a template you can use the :func:`~flask.render_template` +method. All you have to do is to provide the name of the template and the +variables you want to pass to the template engine as keyword arguments. +Here's a simple example of how to render a template:: + + from flask import render_template + + @app.route('/hello/') + @app.route('/hello/') + def hello(name=None): + return render_template('hello.html', name=name) + +Flask will look for templates in the `templates` folder. So if your +application is a module, that folder is next to that module, if it's a +package it's actually inside your package: + +**Case 1**: a module:: + + /application.py + /templates + /hello.html + +**Case 2**: a package:: + + /application + /__init__.py + /templates + /hello.html + +For templates you can use the full power of Jinja2 templates. Head over +to the :ref:`templating` section of the documentation or the official +`Jinja2 Template Documentation +`_ for more information. + +Here is an example template: + +.. sourcecode:: html+jinja + + + Hello from Flask + {% if name %} +

Hello {{ name }}!

+ {% else %} +

Hello World!

+ {% endif %} + +Inside templates you also have access to the :class:`~flask.request`, +:class:`~flask.session` and :class:`~flask.g` [#]_ objects +as well as the :func:`~flask.get_flashed_messages` function. + +Templates are especially useful if inheritance is used. If you want to +know how that works, head over to the :ref:`template-inheritance` pattern +documentation. Basically template inheritance makes it possible to keep +certain elements on each page (like header, navigation and footer). + +Automatic escaping is enabled, so if name contains HTML it will be escaped +automatically. If you can trust a variable and you know that it will be +safe HTML (because for example it came from a module that converts wiki +markup to HTML) you can mark it as safe by using the +:class:`~jinja2.Markup` class or by using the ``|safe`` filter in the +template. Head over to the Jinja 2 documentation for more examples. + +Here is a basic introduction to how the :class:`~jinja2.Markup` class works: + +>>> from flask import Markup +>>> Markup('Hello %s!') % 'hacker' +Markup(u'Hello <blink>hacker</blink>!') +>>> Markup.escape('hacker') +Markup(u'<blink>hacker</blink>') +>>> Markup('Marked up » HTML').striptags() +u'Marked up \xbb HTML' + +.. versionchanged:: 0.5 + + Autoescaping is no longer enabled for all templates. The following + extensions for templates trigger autoescaping: ``.html``, ``.htm``, + ``.xml``, ``.xhtml``. Templates loaded from a string will have + autoescaping disabled. + +.. [#] Unsure what that :class:`~flask.g` object is? It's something in which + you can store information for your own needs, check the documentation of + that object (:class:`~flask.g`) and the :ref:`sqlite3` for more + information. + + +Accessing Request Data +---------------------- + +For web applications it's crucial to react to the data a client sent to +the server. In Flask this information is provided by the global +:class:`~flask.request` object. If you have some experience with Python +you might be wondering how that object can be global and how Flask +manages to still be threadsafe. The answer are context locals: + + +.. _context-locals: + +Context Locals +`````````````` + +.. admonition:: Insider Information + + If you want to understand how that works and how you can implement + tests with context locals, read this section, otherwise just skip it. + +Certain objects in Flask are global objects, but not of the usual kind. +These objects are actually proxies to objects that are local to a specific +context. What a mouthful. But that is actually quite easy to understand. + +Imagine the context being the handling thread. A request comes in and the +webserver decides to spawn a new thread (or something else, the +underlying object is capable of dealing with other concurrency systems +than threads as well). When Flask starts its internal request handling it +figures out that the current thread is the active context and binds the +current application and the WSGI environments to that context (thread). +It does that in an intelligent way that one application can invoke another +application without breaking. + +So what does this mean to you? Basically you can completely ignore that +this is the case unless you are doing something like unittesting. You +will notice that code that depends on a request object will suddenly break +because there is no request object. The solution is creating a request +object yourself and binding it to the context. The easiest solution for +unittesting is by using the :meth:`~flask.Flask.test_request_context` +context manager. In combination with the `with` statement it will bind a +test request so that you can interact with it. Here is an example:: + + from flask import request + + with app.test_request_context('/hello', method='POST'): + # now you can do something with the request until the + # end of the with block, such as basic assertions: + assert request.path == '/hello' + assert request.method == 'POST' + +The other possibility is passing a whole WSGI environment to the +:meth:`~flask.Flask.request_context` method:: + + from flask import request + + with app.request_context(environ): + assert request.method == 'POST' + +The Request Object +`````````````````` + +The request object is documented in the API section and we will not cover +it here in detail (see :class:`~flask.request`). Here is a broad overview of +some of the most common operations. First of all you have to import it from +the `flask` module:: + + from flask import request + +The current request method is available by using the +:attr:`~flask.request.method` attribute. To access form data (data +transmitted in a `POST` or `PUT` request) you can use the +:attr:`~flask.request.form` attribute. Here is a full example of the two +attributes mentioned above:: + + @app.route('/login', methods=['POST', 'GET']) + def login(): + error = None + if request.method == 'POST': + if valid_login(request.form['username'], + request.form['password']): + return log_the_user_in(request.form['username']) + else: + error = 'Invalid username/password' + # this is executed if the request method was GET or the + # credentials were invalid + +What happens if the key does not exist in the `form` attribute? In that +case a special :exc:`KeyError` is raised. You can catch it like a +standard :exc:`KeyError` but if you don't do that, a HTTP 400 Bad Request +error page is shown instead. So for many situations you don't have to +deal with that problem. + +To access parameters submitted in the URL (``?key=value``) you can use the +:attr:`~flask.request.args` attribute:: + + searchword = request.args.get('q', '') + +We recommend accessing URL parameters with `get` or by catching the +`KeyError` because users might change the URL and presenting them a 400 +bad request page in that case is not user friendly. + +For a full list of methods and attributes of the request object, head over +to the :class:`~flask.request` documentation. + + +File Uploads +```````````` + +You can handle uploaded files with Flask easily. Just make sure not to +forget to set the ``enctype="multipart/form-data"`` attribute on your HTML +form, otherwise the browser will not transmit your files at all. + +Uploaded files are stored in memory or at a temporary location on the +filesystem. You can access those files by looking at the +:attr:`~flask.request.files` attribute on the request object. Each +uploaded file is stored in that dictionary. It behaves just like a +standard Python :class:`file` object, but it also has a +:meth:`~werkzeug.FileStorage.save` method that allows you to store that +file on the filesystem of the server. Here is a simple example showing how +that works:: + + from flask import request + + @app.route('/upload', methods=['GET', 'POST']) + def upload_file(): + if request.method == 'POST': + f = request.files['the_file'] + f.save('/var/www/uploads/uploaded_file.txt') + ... + +If you want to know how the file was named on the client before it was +uploaded to your application, you can access the +:attr:`~werkzeug.FileStorage.filename` attribute. However please keep in +mind that this value can be forged so never ever trust that value. If you +want to use the filename of the client to store the file on the server, +pass it through the :func:`~werkzeug.secure_filename` function that +Werkzeug provides for you:: + + from flask import request + from werkzeug import secure_filename + + @app.route('/upload', methods=['GET', 'POST']) + def upload_file(): + if request.method == 'POST': + f = request.files['the_file'] + f.save('/var/www/uploads/' + secure_filename(f.filename)) + ... + +For some better examples, checkout the :ref:`uploading-files` pattern. + +Cookies +``````` + +To access cookies you can use the :attr:`~flask.request.cookies` +attribute. Again this is a dictionary with all the cookies the client +transmits. If you want to use sessions, do not use the cookies directly +but instead use the :ref:`sessions` in Flask that add some security on top +of cookies for you. + + +Redirects and Errors +-------------------- + +To redirect a user to somewhere else you can use the +:func:`~flask.redirect` function. To abort a request early with an error +code use the :func:`~flask.abort` function. Here an example how this works:: + + from flask import abort, redirect, url_for + + @app.route('/') + def index(): + return redirect(url_for('login')) + + @app.route('/login') + def login(): + abort(401) + this_is_never_executed() + +This is a rather pointless example because a user will be redirected from +the index to a page he cannot access (401 means access denied) but it +shows how that works. + +By default a black and white error page is shown for each error code. If +you want to customize the error page, you can use the +:meth:`~flask.Flask.errorhandler` decorator:: + + from flask import render_template + + @app.errorhandler(404) + def page_not_found(error): + return render_template('page_not_found.html'), 404 + +Note the ``404`` after the :func:`~flask.render_template` call. This +tells Flask that the status code of that page should be 404 which means +not found. By default 200 is assumed which translates to: all went well. + +.. _sessions: + +Sessions +-------- + +Besides the request object there is also a second object called +:class:`~flask.session` that allows you to store information specific to a +user from one request to the next. This is implemented on top of cookies +for you and signs the cookies cryptographically. What this means is that +the user could look at the contents of your cookie but not modify it, +unless he knows the secret key used for signing. + +In order to use sessions you have to set a secret key. Here is how +sessions work:: + + from flask import Flask, session, redirect, url_for, escape, request + + app = Flask(__name__) + + @app.route('/') + def index(): + if 'username' in session: + return 'Logged in as %s' % escape(session['username']) + return 'You are not logged in' + + @app.route('/login', methods=['GET', 'POST']) + def login(): + if request.method == 'POST': + session['username'] = request.form['username'] + return redirect(url_for('index')) + return ''' +
+

+

+

+ ''' + + @app.route('/logout') + def logout(): + # remove the username from the session if its there + session.pop('username', None) + return redirect(url_for('index')) + + # set the secret key. keep this really secret: + app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' + +The here mentioned :func:`~flask.escape` does escaping for you if you are +not using the template engine (like in this example). + +.. admonition:: How to generate good secret keys + + The problem with random is that it's hard to judge what random is. And + a secret key should be as random as possible. Your operating system + has ways to generate pretty random stuff based on a cryptographic + random generator which can be used to get such a key: + + >>> import os + >>> os.urandom(24) + '\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O`_ for more +information. + +Hooking in WSGI Middlewares +--------------------------- + +If you want to add a WSGI middleware to your application you can wrap the +internal WSGI application. For example if you want to use one of the +middlewares from the Werkzeug package to work around bugs in lighttpd, you +can do it like this:: + + from werkzeug.contrib.fixers import LighttpdCGIRootFix + app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/security.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/security.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,168 @@ +Security Considerations +======================= + +Web applications usually face all kinds of security problems and it's very +hard to get everything right. Flask tries to solve a few of these things +for you, but there are a couple more you have to take care of yourself. + +.. _xss: + +Cross-Site Scripting (XSS) +-------------------------- + +Cross site scripting is the concept of injecting arbitrary HTML (and with +it JavaScript) into the context of a website. To remedy this, developers +have to properly escape text so that it cannot include arbitrary HTML +tags. For more information on that have a look at the Wikipedia article +on `Cross-Site Scripting +`_. + +Flask configures Jinja2 to automatically escape all values unless +explicitly told otherwise. This should rule out all XSS problems caused +in templates, but there are still other places where you have to be +careful: + +- generating HTML without the help of Jinja2 +- calling :class:`~flask.Markup` on data submitted by users +- sending out HTML from uploaded files, never do that, use the + `Content-Disposition: attachment` header to prevent that problem. +- sending out textfiles from uploaded files. Some browsers are using + content-type guessing based on the first few bytes so users could + trick a browser to execute HTML. + +Another thing that is very important are unquoted attributes. While +Jinja2 can protect you from XSS issues by escaping HTML, there is one +thing it cannot protect you from: XSS by attribute injection. To counter +this possible attack vector, be sure to always quote your attributes with +either double or single quotes when using Jinja expressions in them: + +.. sourcecode:: html+jinja + + the text + +Why is this necessary? Because if you would not be doing that, an +attacker could easily inject custom JavaScript handlers. For example an +attacker could inject this piece of HTML+JavaScript: + +.. sourcecode:: html + + onmouseover=alert(document.cookie) + +When the user would then move with the mouse over the link, the cookie +would be presented to the user in an alert window. But instead of showing +the cookie to the user, a good attacker might also execute any other +JavaScript code. In combination with CSS injections the attacker might +even make the element fill out the entire page so that the user would +just have to have the mouse anywhere on the page to trigger the attack. + +Cross-Site Request Forgery (CSRF) +--------------------------------- + +Another big problem is CSRF. This is a very complex topic and I won't +outline it here in detail just mention what it is and how to theoretically +prevent it. + +If your authentication information is stored in cookies, you have implicit +state management. The state of "being logged in" is controlled by a +cookie, and that cookie is sent with each request to a page. +Unfortunately that includes requests triggered by 3rd party sites. If you +don't keep that in mind, some people might be able to trick your +application's users with social engineering to do stupid things without +them knowing. + +Say you have a specific URL that, when you sent `POST` requests to will +delete a user's profile (say `http://example.com/user/delete`). If an +attacker now creates a page that sends a post request to that page with +some JavaScript he just has to trick some users to load that page and +their profiles will end up being deleted. + +Imagine you were to run Facebook with millions of concurrent users and +someone would send out links to images of little kittens. When users +would go to that page, their profiles would get deleted while they are +looking at images of fluffy cats. + +How can you prevent that? Basically for each request that modifies +content on the server you would have to either use a one-time token and +store that in the cookie **and** also transmit it with the form data. +After receiving the data on the server again, you would then have to +compare the two tokens and ensure they are equal. + +Why does Flask not do that for you? The ideal place for this to happen is +the form validation framework, which does not exist in Flask. + +.. _json-security: + +JSON Security +------------- + +JSON itself is a high-level serialization format, so there is barely +anything that could cause security problems, right? You can't declare +recursive structures that could cause problems and the only thing that +could possibly break are very large responses that can cause some kind of +denial of service at the receiver's side. + +However there is a catch. Due to how browsers work the CSRF issue comes +up with JSON unfortunately. Fortunately there is also a weird part of the +JavaScript specification that can be used to solve that problem easily and +Flask is kinda doing that for you by preventing you from doing dangerous +stuff. Unfortunately that protection is only there for +:func:`~flask.jsonify` so you are still at risk when using other ways to +generate JSON. + +So what is the issue and how to avoid it? The problem are arrays at +toplevel in JSON. Imagine you send the following data out in a JSON +request. Say that's exporting the names and email addresses of all your +friends for a part of the user interface that is written in JavaScript. +Not very uncommon: + +.. sourcecode:: javascript + + [ + {"username": "admin", + "email": "admin@localhost"} + ] + +And it is doing that of course only as long as you are logged in and only +for you. And it is doing that for all `GET` requests to a certain URL, +say the URL for that request is +``http://example.com/api/get_friends.json``. + +So now what happens if a clever hacker is embedding this to his website +and social engineers a victim to visiting his site: + +.. sourcecode:: html + + + + + +If you know a bit of JavaScript internals you might know that it's +possible to patch constructors and register callbacks for setters. An +attacker can use this (like above) to get all the data you exported in +your JSON file. The browser will totally ignore the ``application/json`` +mimetype if ``text/javascript`` is defined as content type in the script +tag and evaluate that as JavaScript. Because toplevel array elements are +allowed (albeit useless) and we hooked in our own constructor, after that +page loaded the data from the JSON response is in the `captured` array. + +Because it is a syntax error in JavaScript to have an object literal +(``{...}``) toplevel an attacker could not just do a request to an +external URL with the script tag to load up the data. So what Flask does +is to only allow objects as toplevel elements when using +:func:`~flask.jsonify`. Make sure to do the same when using an ordinary +JSON generate function. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/shell.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/shell.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,110 @@ +Working with the Shell +====================== + +.. versionadded:: 0.3 + +One of the reasons everybody loves Python is the interactive shell. It +basically allows you to execute Python commands in real time and +immediately get results back. Flask itself does not come with an +interactive shell, because it does not require any specific setup upfront, +just import your application and start playing around. + +There are however some handy helpers to make playing around in the shell a +more pleasant experience. The main issue with interactive console +sessions is that you're not triggering a request like a browser does which +means that :data:`~flask.g`, :data:`~flask.request` and others are not +available. But the code you want to test might depend on them, so what +can you do? + +This is where some helper functions come in handy. Keep in mind however +that these functions are not only there for interactive shell usage, but +also for unittesting and other situations that require a faked request +context. + +Diving into Context Locals +-------------------------- + +Say you have a utility function that returns the URL the user should be +redirected to. Imagine it would always redirect to the URL's ``next`` +parameter or the HTTP referrer or the index page:: + + from flask import request, url_for + + def redirect_url(): + return request.args.get('next') or \ + request.referrer or \ + url_for('index') + +As you can see, it accesses the request object. If you try to run this +from a plain Python shell, this is the exception you will see: + +>>> redirect_url() +Traceback (most recent call last): + File "", line 1, in +AttributeError: 'NoneType' object has no attribute 'request' + +That makes a lot of sense because we currently do not have a request we +could access. So we have to make a request and bind it to the current +context. The :attr:`~flask.Flask.test_request_context` method can create +us a request context: + +>>> ctx = app.test_request_context('/?next=http://example.com/') + +This context can be used in two ways. Either with the `with` statement +(which unfortunately is not very handy for shell sessions). The +alternative way is to call the `push` and `pop` methods: + +>>> ctx.push() + +From that point onwards you can work with the request object: + +>>> redirect_url() +u'http://example.com/' + +Until you call `pop`: + +>>> ctx.pop() +>>> redirect_url() +Traceback (most recent call last): + File "", line 1, in +AttributeError: 'NoneType' object has no attribute 'request' + + +Firing Before/After Request +--------------------------- + +By just creating a request context, you still don't have run the code that +is normally run before a request. This probably results in your database +being unavailable, the current user not being stored on the +:data:`~flask.g` object etc. + +This however can easily be done yourself. Just call +:meth:`~flask.Flask.preprocess_request`: + +>>> ctx = app.test_request_context() +>>> ctx.push() +>>> app.preprocess_request() + +Keep in mind that the :meth:`~flask.Flask.preprocess_request` function +might return a response object, in that case just ignore it. + +To shutdown a request, you need to trick a bit before the after request +functions (triggered by :meth:`~flask.Flask.process_response`) operate on +a response object: + +>>> app.process_response(app.response_class()) + +>>> ctx.pop() + + +Further Improving the Shell Experience +-------------------------------------- + +If you like the idea of experimenting in a shell, create yourself a module +with stuff you want to star import into your interactive session. There +you could also define some more helper methods for common things such as +initializing the database, dropping tables etc. + +Just put them into a module (like `shelltools` and import from there): + +>>> from shelltools import * diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/signals.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/signals.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,239 @@ +.. _signals: + +Signals +======= + +.. versionadded:: 0.6 + +Starting with Flask 0.6, there is integrated support for signalling in +Flask. This support is provided by the excellent `blinker`_ library and +will gracefully fall back if it is not available. + +What are signals? Signals help you decouple applications by sending +notifications when actions occur elsewhere in the core framework or +another Flask extensions. In short, signals allow certain senders to +notify subscribers that something happened. + +Flask comes with a couple of signals and other extensions might provide +more. Also keep in mind that signals are intended to notify subscribers +and should not encourage subscribers to modify data. You will notice that +there are signals that appear to do the same thing like some of the +builtin decorators do (eg: :data:`~flask.request_started` is very similar +to :meth:`~flask.Flask.before_request`). There are however difference in +how they work. The core :meth:`~flask.Flask.before_request` handler for +example is executed in a specific order and is able to abort the request +early by returning a response. In contrast all signal handlers are +executed in undefined order and do not modify any data. + +The big advantage of signals over handlers is that you can safely +subscribe to them for the split of a second. These temporary +subscriptions are helpful for unittesting for example. Say you want to +know what templates were rendered as part of a request: signals allow you +to do exactly that. + +Subscribing to Signals +---------------------- + +To subscribe to a signal, you can use the +:meth:`~blinker.base.Signal.connect` method of a signal. The first +argument is the function that should be called when the signal is emitted, +the optional second argument specifies a sender. To unsubscribe from a +signal, you can use the :meth:`~blinker.base.Signal.disconnect` method. + +For all core Flask signals, the sender is the application that issued the +signal. When you subscribe to a signal, be sure to also provide a sender +unless you really want to listen for signals of all applications. This is +especially true if you are developing an extension. + +Here for example a helper context manager that can be used to figure out +in a unittest which templates were rendered and what variables were passed +to the template:: + + from flask import template_rendered + from contextlib import contextmanager + + @contextmanager + def captured_templates(app): + recorded = [] + def record(sender, template, context): + recorded.append((template, context)) + template_rendered.connect(record, app) + try: + yield recorded + finally: + template_rendered.disconnect(record, app) + +This can now easily be paired with a test client:: + + with captured_templates(app) as templates: + rv = app.test_client().get('/') + assert rv.status_code == 200 + assert len(templates) == 1 + template, context = templates[0] + assert template.name == 'index.html' + assert len(context['items']) == 10 + +All the template rendering in the code issued by the application `app` +in the body of the `with` block will now be recorded in the `templates` +variable. Whenever a template is rendered, the template object as well as +context are appended to it. + +Additionally there is a convenient helper method +(:meth:`~blinker.base.Signal.connected_to`). that allows you to +temporarily subscribe a function to a signal with is a context manager on +its own. Because the return value of the context manager cannot be +specified that way one has to pass the list in as argument:: + + from flask import template_rendered + + def captured_templates(app, recorded): + def record(sender, template, context): + recorded.append((template, context)) + return template_rendered.connected_to(record, app) + +The example above would then look like this:: + + templates = [] + with captured_templates(app, templates): + ... + template, context = templates[0] + +.. admonition:: Blinker API Changes + + The :meth:`~blinker.base.Signal.connected_to` method arrived in Blinker + with version 1.1. + +Creating Signals +---------------- + +If you want to use signals in your own application, you can use the +blinker library directly. The most common use case are named signals in a +custom :class:`~blinker.base.Namespace`.. This is what is recommended +most of the time:: + + from blinker import Namespace + my_signals = Namespace() + +Now you can create new signals like this:: + + model_saved = my_signals.signal('model-saved') + +The name for the signal here makes it unique and also simplifies +debugging. You can access the name of the signal with the +:attr:`~blinker.base.NamedSignal.name` attribute. + +.. admonition:: For Extension Developers + + If you are writing a Flask extension and you to gracefully degrade for + missing blinker installations, you can do so by using the + :class:`flask.signals.Namespace` class. + +Sending Signals +--------------- + +If you want to emit a signal, you can do so by calling the +:meth:`~blinker.base.Signal.send` method. It accepts a sender as first +argument and optionally some keyword arguments that are forwarded to the +signal subscribers:: + + class Model(object): + ... + + def save(self): + model_saved.send(self) + +Try to always pick a good sender. If you have a class that is emitting a +signal, pass `self` as sender. If you emitting a signal from a random +function, you can pass ``current_app._get_current_object()`` as sender. + +.. admonition:: Passing Proxies as Senders + + Never pass :data:`~flask.current_app` as sender to a signal. Use + ``current_app._get_current_object()`` instead. The reason for this is + that :data:`~flask.current_app` is a proxy and not the real application + object. + +Decorator Based Signal Subscriptions +------------------------------------ + +With Blinker 1.1 you can also easily subscribe to signals by using the new +:meth:`~blinker.base.NamedSignal.connect_via` decorator:: + + from flask import template_rendered + + @template_rendered.connect_via(app) + def when_template_rendered(sender, template, context): + print 'Template %s is rendered with %s' % (template.name, context) + +Core Signals +------------ + +.. when modifying this list, also update the one in api.rst + +The following signals exist in Flask: + +.. data:: flask.template_rendered + :noindex: + + This signal is sent when a template was successfully rendered. The + signal is invoked with the instance of the template as `template` + and the context as dictionary (named `context`). + + Example subscriber:: + + def log_template_renders(sender, template, context): + sender.logger.debug('Rendering template "%s" with context %s', + template.name or 'string template', + context) + + from flask import template_rendered + template_rendered.connect(log_template_renders, app) + +.. data:: flask.request_started + :noindex: + + This signal is sent before any request processing started but when the + request context was set up. Because the request context is already + bound, the subscriber can access the request with the standard global + proxies such as :class:`~flask.request`. + + Example subscriber:: + + def log_request(sender): + sender.logger.debug('Request context is set up') + + from flask import request_started + request_started.connect(log_request, app) + +.. data:: flask.request_finished + :noindex: + + This signal is sent right before the response is sent to the client. + It is passed the response to be sent named `response`. + + Example subscriber:: + + def log_response(sender, response): + sender.logger.debug('Request context is about to close down. ' + 'Response: %s', response) + + from flask import request_finished + request_finished.connect(log_response, app) + +.. data:: flask.got_request_exception + :noindex: + + This signal is sent when an exception happens during request processing. + It is sent *before* the standard exception handling kicks in and even + in debug mode, where no exception handling happens. The exception + itself is passed to the subscriber as `exception`. + + Example subscriber:: + + def log_exception(sender, exception): + sender.logger.debug('Got exception during processing: %s', exception) + + from flask import got_request_exception + got_request_exception.connect(log_exception, app) + +.. _blinker: http://pypi.python.org/pypi/blinker diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/styleguide.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/styleguide.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,200 @@ +Pocoo Styleguide +================ + +The Pocoo styleguide is the styleguide for all Pocoo Projects, including +Flask. This styleguide is a requirement for Patches to Flask and a +recommendation for Flask extensions. + +In general the Pocoo Styleguide closely follows :pep:`8` with some small +differences and extensions. + +General Layout +-------------- + +Indentation: + 4 real spaces. No tabs, no exceptions. + +Maximum line length: + 79 characters with a soft limit for 84 if absolutely necessary. Try + to avoid too nested code by cleverly placing `break`, `continue` and + `return` statements. + +Continuing long statements: + To continue a statement you can use backslashes in which case you should + align the next line with the last dot or equal sign, or indent four + spaces:: + + this_is_a_very_long(function_call, 'with many parameters') \ + .that_returns_an_object_with_an_attribute + + MyModel.query.filter(MyModel.scalar > 120) \ + .order_by(MyModel.name.desc()) \ + .limit(10) + + If you break in a statement with parentheses or braces, align to the + braces:: + + this_is_a_very_long(function_call, 'with many parameters', + 23, 42, 'and even more') + + For lists or tuples with many items, break immediately after the + opening brace:: + + items = [ + 'this is the first', 'set of items', 'with more items', + 'to come in this line', 'like this' + ] + +Blank lines: + Top level functions and classes are separated by two lines, everything + else by one. Do not use too many blank lines to separate logical + segments in code. Example:: + + def hello(name): + print 'Hello %s!' % name + + + def goodbye(name): + print 'See you %s.' % name + + + class MyClass(object): + """This is a simple docstring""" + + def __init__(self, name): + self.name = name + + def get_annoying_name(self): + return self.name.upper() + '!!!!111' + +Expressions and Statements +-------------------------- + +General whitespace rules: + - No whitespace for unary operators that are not words + (e.g.: ``-``, ``~`` etc.) as well on the inner side of parentheses. + - Whitespace is placed between binary operators. + + Good:: + + exp = -1.05 + value = (item_value / item_count) * offset / exp + value = my_list[index] + value = my_dict['key'] + + Bad:: + + exp = - 1.05 + value = ( item_value / item_count ) * offset / exp + value = (item_value/item_count)*offset/exp + value=( item_value/item_count ) * offset/exp + value = my_list[ index ] + value = my_dict ['key'] + +Yoda statements are a nogo: + Never compare constant with variable, always variable with constant: + + Good:: + + if method == 'md5': + pass + + Bad:: + + if 'md5' == method: + pass + +Comparisons: + - against arbitrary types: ``==`` and ``!=`` + - against singletons with ``is`` and ``is not`` (eg: ``foo is not + None``) + - never compare something with `True` or `False` (for example never + do ``foo == False``, do ``not foo`` instead) + +Negated containment checks: + use ``foo not in bar`` instead of ``not foo in bar`` + +Instance checks: + ``isinstance(a, C)`` instead of ``type(A) is C``, but try to avoid + instance checks in general. Check for features. + + +Naming Conventions +------------------ + +- Class names: ``CamelCase``, with acronyms kept uppercase (``HTTPWriter`` + and not ``HttpWriter``) +- Variable names: ``lowercase_with_underscores`` +- Method and function names: ``lowercase_with_underscores`` +- Constants: ``UPPERCASE_WITH_UNDERSCORES`` +- precompiled regular expressions: ``name_re`` + +Protected members are prefixed with a single underscore. Double +underscores are reserved for mixin classes. + +On classes with keywords, trailing underscores are appended. Clashes with +builtins are allowed and **must not** be resolved by appending an +underline to the variable name. If the function needs to access a +shadowed builtin, rebind the builtin to a different name instead. + +Function and method arguments: + - class methods: ``cls`` as first parameter + - instance methods: ``self`` as first parameter + - lambdas for properties might have the first parameter replaced + with ``x`` like in ``display_name = property(lambda x: x.real_name + or x.username)`` + + +Docstrings +---------- + +Docstring conventions: + All docstrings are formatted with reStructuredText as understood by + Sphinx. Depending on the number of lines in the docstring, they are + laid out differently. If it's just one line, the closing triple + quote is on the same line as the opening, otherwise the text is on + the same line as the opening quote and the triple quote that closes + the string on its own line:: + + def foo(): + """This is a simple docstring""" + + + def bar(): + """This is a longer docstring with so much information in there + that it spans three lines. In this case the closing triple quote + is on its own line. + """ + +Module header: + The module header consists of an utf-8 encoding declaration (if non + ASCII letters are used, but it is recommended all the time) and a + standard docstring:: + + # -*- coding: utf-8 -*- + """ + package.module + ~~~~~~~~~~~~~~ + + A brief description goes here. + + :copyright: (c) YEAR by AUTHOR. + :license: LICENSE_NAME, see LICENSE_FILE for more details. + """ + + Please keep in mind that proper copyrights and license files are a + requirement for approved Flask extensions. + + +Comments +-------- + +Rules for comments are similar to docstrings. Both are formatted with +reStructuredText. If a comment is used to document an attribute, put a +colon after the opening pound sign (``#``):: + + class User(object): + #: the name of the user as unicode string + name = Column(String) + #: the sha1 hash of the password + inline salt + pw_hash = Column(String) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/templating.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/templating.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,188 @@ +Templates +========= + +Flask leverages Jinja2 as template engine. You are obviously free to use +a different template engine, but you still have to install Jinja2 to run +Flask itself. This requirement is necessary to enable rich extensions. +An extension can depend on Jinja2 being present. + +This section only gives a very quick introduction into how Jinja2 +is integrated into Flask. If you want information on the template +engine's syntax itself, head over to the official `Jinja2 Template +Documentation `_ for +more information. + +Jinja Setup +----------- + +Unless customized, Jinja2 is configured by Flask as follows: + +- autoescaping is enabled for all templates ending in ``.html``, + ``.htm``, ``.xml`` as well as ``.xhtml`` +- a template has the ability to opt in/out autoescaping with the + ``{% autoescape %}`` tag. +- Flask inserts a couple of global functions and helpers into the + Jinja2 context, additionally to the values that are present by + default. + +Standard Context +---------------- + +The following global variables are available within Jinja2 templates +by default: + +.. data:: config + :noindex: + + The current configuration object (:data:`flask.config`) + + .. versionadded:: 0.6 + +.. data:: request + :noindex: + + The current request object (:class:`flask.request`) + +.. data:: session + :noindex: + + The current session object (:class:`flask.session`) + +.. data:: g + :noindex: + + The request-bound object for global variables (:data:`flask.g`) + +.. function:: url_for + :noindex: + + The :func:`flask.url_for` function. + +.. function:: get_flashed_messages + :noindex: + + The :func:`flask.get_flashed_messages` function. + +.. admonition:: The Jinja Context Behaviour + + These variables are added to the context of variables, they are not + global variables. The difference is that by default these will not + show up in the context of imported templates. This is partially caused + by performance considerations, partially to keep things explicit. + + What does this mean for you? If you have a macro you want to import, + that needs to access the request object you have two possibilities: + + 1. you explicitly pass the request to the macro as parameter, or + the attribute of the request object you are interested in. + 2. you import the macro "with context". + + Importing with context looks like this: + + .. sourcecode:: jinja + + {% from '_helpers.html' import my_macro with context %} + +Standard Filters +---------------- + +These filters are available in Jinja2 additionally to the filters provided +by Jinja2 itself: + +.. function:: tojson + :noindex: + + This function converts the given object into JSON representation. This + is for example very helpful if you try to generate JavaScript on the + fly. + + Note that inside `script` tags no escaping must take place, so make + sure to disable escaping with ``|safe`` if you intend to use it inside + `script` tags: + + .. sourcecode:: html+jinja + + + + That the ``|tojson`` filter escapes forward slashes properly for you. + +Controlling Autoescaping +------------------------ + +Autoescaping is the concept of automatically escaping special characters +of you. Special characters in the sense of HTML (or XML, and thus XHTML) +are ``&``, ``>``, ``<``, ``"`` as well as ``'``. Because these characters +carry specific meanings in documents on their own you have to replace them +by so called "entities" if you want to use them for text. Not doing so +would not only cause user frustration by the inability to use these +characters in text, but can also lead to security problems. (see +:ref:`xss`) + +Sometimes however you will need to disable autoescaping in templates. +This can be the case if you want to explicitly inject HTML into pages, for +example if they come from a system that generate secure HTML like a +markdown to HTML converter. + +There are three ways to accomplish that: + +- In the Python code, wrap the HTML string in a :class:`~flask.Markup` + object before passing it to the template. This is in general the + recommended way. +- Inside the template, use the ``|safe`` filter to explicitly mark a + string as safe HTML (``{{ myvariable|safe }}``) +- Temporarily disable the autoescape system altogether. + +To disable the autoescape system in templates, you can use the ``{% +autoescape %}`` block: + +.. sourcecode:: html+jinja + + {% autoescape false %} +

autoescaping is disabled here +

{{ will_not_be_escaped }} + {% endautoescape %} + +Whenever you do this, please be very cautious about the variables you are +using in this block. + +Registering Filters +------------------- + +If you want to register your own filters in Jinja2 you have two ways to do +that. You can either put them by hand into the +:attr:`~flask.Flask.jinja_env` of the application or use the +:meth:`~flask.Flask.template_filter` decorator. + +The two following examples work the same and both reverse an object:: + + @app.template_filter('reverse') + def reverse_filter(s): + return s[::-1] + + def reverse_filter(s): + return s[::-1] + app.jinja_env.filters['reverse'] = reverse_filter + +In case of the decorator the argument is optional if you want to use the +function name as name of the filter. + +Context Processors +------------------ + +To inject new variables automatically into the context of a template +context processors exist in Flask. Context processors run before the +template is rendered and have the ability to inject new values into the +template context. A context processor is a function that returns a +dictionary. The keys and values of this dictionary are then merged with +the template context:: + + @app.context_processor + def inject_user(): + return dict(user=g.user) + +The context processor above makes a variable called `user` available in +the template with the value of `g.user`. This example is not very +interesting because `g` is available in templates anyways, but it gives an +idea how this works. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/testing.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/testing.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,244 @@ +.. _testing: + +Testing Flask Applications +========================== + + **Something that is untested is broken.** + +Not sure where that is coming from, and it's not entirely correct, but +also not that far from the truth. Untested applications make it hard to +improve existing code and developers of untested applications tend to +become pretty paranoid. If an application has automated tests, you can +safely change things, and you will instantly know if your change broke +something. + +Flask gives you a couple of ways to test applications. It mainly does +that by exposing the Werkzeug test :class:`~werkzeug.Client` class to your +code and handling the context locals for you. You can then use that with +your favourite testing solution. In this documentation we will use the +:mod:`unittest` package that comes preinstalled with each Python +installation. + +The Application +--------------- + +First we need an application to test for functionality. For the testing +we will use the application from the :ref:`tutorial`. If you don't have +that application yet, get the sources from `the examples`_. + +.. _the examples: + http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ + +The Testing Skeleton +-------------------- + +In order to test that, we add a second module ( +`flaskr_tests.py`) and create a unittest skeleton there:: + + import os + import flaskr + import unittest + import tempfile + + class FlaskrTestCase(unittest.TestCase): + + def setUp(self): + self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() + self.app = flaskr.app.test_client() + flaskr.init_db() + + def tearDown(self): + os.close(self.db_fd) + os.unlink(flaskr.app.config['DATABASE']) + + if __name__ == '__main__': + unittest.main() + +The code in the :meth:`~unittest.TestCase.setUp` method creates a new test +client and initializes a new database. That function is called before +each individual test function. To delete the database after the test, we +close the file and remove it from the filesystem in the +:meth:`~unittest.TestCase.tearDown` method. What the test client does is +give us a simple interface to the application. We can trigger test +requests to the application, and the client will also keep track of cookies +for us. + +Because SQLite3 is filesystem-based we can easily use the tempfile module +to create a temporary database and initialize it. The +:func:`~tempfile.mkstemp` function does two things for us: it returns a +low-level file handle and a random file name, the latter we use as +database name. We just have to keep the `db_fd` around so that we can use +the :func:`os.close` function to close the file. + +If we now run that test suite, we should see the following output:: + + $ python flaskr_tests.py + + ---------------------------------------------------------------------- + Ran 0 tests in 0.000s + + OK + +Even though it did not run any tests, we already know that our flaskr +application is syntactically valid, otherwise the import would have died +with an exception. + +The First Test +-------------- + +Now we can add the first test. Let's check that the application shows +"No entries here so far" if we access the root of the application (``/``). +For that we modify our created test case class so that it looks like +this:: + + class FlaskrTestCase(unittest.TestCase): + + def setUp(self): + self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() + self.app = flaskr.app.test_client() + flaskr.init_db() + + def tearDown(self): + os.close(self.db_fd) + os.unlink(flaskr.DATABASE) + + def test_empty_db(self): + rv = self.app.get('/') + assert 'No entries here so far' in rv.data + +Test functions begin with the word `test`. Every function named like that +will be picked up automatically. By using `self.app.get` we can send an +HTTP `GET` request to the application with the given path. The return +value will be a :class:`~flask.Flask.response_class` object. We can now +use the :attr:`~werkzeug.BaseResponse.data` attribute to inspect the +return value (as string) from the application. In this case, we ensure +that ``'No entries here so far'`` is part of the output. + +Run it again and you should see one passing test:: + + $ python flaskr_tests.py + . + ---------------------------------------------------------------------- + Ran 1 test in 0.034s + + OK + +Of course you can submit forms with the test client as well, which we will +use now to log our user in. + +Logging In and Out +------------------ + +The majority of the functionality of our application is only available for +the administrative user, so we need a way to log our test client in to the +application and out of it again. For that we fire some requests to the +login and logout pages with the required form data (username and +password). Because the login and logout pages redirect, we tell the +client to `follow_redirects`. + +Add the following two methods to your `FlaskrTestCase` class:: + + def login(self, username, password): + return self.app.post('/login', data=dict( + username=username, + password=password + ), follow_redirects=True) + + def logout(self): + return self.app.get('/logout', follow_redirects=True) + +Now we can easily test if logging in and out works and that it fails with +invalid credentials. Add this new test to the class:: + + def test_login_logout(self): + rv = self.login('admin', 'default') + assert 'You were logged in' in rv.data + rv = self.logout() + assert 'You were logged out' in rv.data + rv = self.login('adminx', 'default') + assert 'Invalid username' in rv.data + rv = self.login('admin', 'defaultx') + assert 'Invalid password' in rv.data + +Test Adding Messages +-------------------- + +Now we can also test that adding messages works. Add a new test method +like this:: + + def test_messages(self): + self.login('admin', 'default') + rv = self.app.post('/add', data=dict( + title='', + text='HTML allowed here' + ), follow_redirects=True) + assert 'No entries here so far' not in rv.data + assert '<Hello>' in rv.data + assert 'HTML allowed here' in rv.data + +Here we check that HTML is allowed in the text but not in the title, +which is the intended behavior. + +Running that should now give us three passing tests:: + + $ python flaskr_tests.py + ... + ---------------------------------------------------------------------- + Ran 3 tests in 0.332s + + OK + +For more complex tests with headers and status codes, check out the +`MiniTwit Example`_ from the sources. That one contains a larger test +suite. + + +.. _MiniTwit Example: + http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/ + + +Other Testing Tricks +-------------------- + +Besides using the test client we used above, there is also the +:meth:`~flask.Flask.test_request_context` method that in combination with +the `with` statement can be used to activate a request context +temporarily. With that you can access the :class:`~flask.request`, +:class:`~flask.g` and :class:`~flask.session` objects like in view +functions. Here's a full example that showcases this:: + + app = flask.Flask(__name__) + + with app.test_request_context('/?name=Peter'): + assert flask.request.path == '/' + assert flask.request.args['name'] == 'Peter' + +All the other objects that are context bound can be used the same. + +If you want to test your application with different configurations and +there does not seem to be a good way to do that, consider switching to +application factories (see :ref:`app-factories`). + + +Keeping the Context Around +-------------------------- + +.. versionadded:: 0.4 + +Sometimes it can be helpful to trigger a regular request but keep the +context around for a little longer so that additional introspection can +happen. With Flask 0.4 this is possible by using the +:meth:`~flask.Flask.test_client` with a `with` block:: + + app = flask.Flask(__name__) + + with app.test_client() as c: + rv = c.get('/?tequila=42') + assert request.args['tequila'] == '42' + +If you would just be using the :meth:`~flask.Flask.test_client` without +the `with` block, the `assert` would fail with an error because `request` +is no longer available (because used outside of an actual request). +Keep in mind however that :meth:`~flask.Flask.after_request` functions +are already called at that point so your database connection and +everything involved is probably already closed down. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/tutorial/css.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/tutorial/css.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,31 @@ +.. _tutorial-css: + +Step 7: Adding Style +==================== + +Now that everything else works, it's time to add some style to the +application. Just create a stylesheet called `style.css` in the `static` +folder we created before: + +.. sourcecode:: css + + body { font-family: sans-serif; background: #eee; } + a, h1, h2 { color: #377BA8; } + h1, h2 { font-family: 'Georgia', serif; margin: 0; } + h1 { border-bottom: 2px solid #eee; } + h2 { font-size: 1.2em; } + + .page { margin: 2em auto; width: 35em; border: 5px solid #ccc; + padding: 0.8em; background: white; } + .entries { list-style: none; margin: 0; padding: 0; } + .entries li { margin: 0.8em 1.2em; } + .entries li h2 { margin-left: -1em; } + .add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } + .add-entry dl { font-weight: bold; } + .metanav { text-align: right; font-size: 0.8em; padding: 0.3em; + margin-bottom: 1em; background: #fafafa; } + .flash { background: #CEE5F5; padding: 0.5em; + border: 1px solid #AACBE2; } + .error { background: #F0D6D6; padding: 0.5em; } + +Continue with :ref:`tutorial-testing`. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/tutorial/dbcon.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/tutorial/dbcon.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,37 @@ +.. _tutorial-dbcon: + +Step 4: Request Database Connections +------------------------------------ + +Now we know how we can open database connections and use them for scripts, +but how can we elegantly do that for requests? We will need the database +connection in all our functions so it makes sense to initialize them +before each request and shut them down afterwards. + +Flask allows us to do that with the :meth:`~flask.Flask.before_request` and +:meth:`~flask.Flask.after_request` decorators:: + + @app.before_request + def before_request(): + g.db = connect_db() + + @app.after_request + def after_request(response): + g.db.close() + return response + +Functions marked with :meth:`~flask.Flask.before_request` are called before +a request and passed no arguments, functions marked with +:meth:`~flask.Flask.after_request` are called after a request and +passed the response that will be sent to the client. They have to return +that response object or a different one. In this case we just return it +unchanged. + +We store our current database connection on the special :data:`~flask.g` +object that flask provides for us. This object stores information for one +request only and is available from within each function. Never store such +things on other objects because this would not work with threaded +environments. That special :data:`~flask.g` object does some magic behind +the scenes to ensure it does the right thing. + +Continue to :ref:`tutorial-views`. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/tutorial/dbinit.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/tutorial/dbinit.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,67 @@ +.. _tutorial-dbinit: + +Step 3: Creating The Database +============================= + +Flaskr is a database powered application as outlined earlier, and more +precisely, an application powered by a relational database system. Such +systems need a schema that tells them how to store that information. So +before starting the server for the first time it's important to create +that schema. + +Such a schema can be created by piping the `schema.sql` file into the +`sqlite3` command as follows:: + + sqlite3 /tmp/flaskr.db < schema.sql + +The downside of this is that it requires the sqlite3 command to be +installed which is not necessarily the case on every system. Also one has +to provide the path to the database there which leaves some place for +errors. It's a good idea to add a function that initializes the database +for you to the application. + +If you want to do that, you first have to import the +:func:`contextlib.closing` function from the contextlib package. If you +want to use Python 2.5 it's also necessary to enable the `with` statement +first (`__future__` imports must be the very first import):: + + from __future__ import with_statement + from contextlib import closing + +Next we can create a function called `init_db` that initializes the +database. For this we can use the `connect_db` function we defined +earlier. Just add that function below the `connect_db` function:: + + def init_db(): + with closing(connect_db()) as db: + with app.open_resource('schema.sql') as f: + db.cursor().executescript(f.read()) + db.commit() + +The :func:`~contextlib.closing` helper function allows us to keep a +connection open for the duration of the `with` block. The +:func:`~flask.Flask.open_resource` method of the application object +supports that functionality out of the box, so it can be used in the +`with` block directly. This function opens a file from the resource +location (your `flaskr` folder) and allows you to read from it. We are +using this here to execute a script on the database connection. + +When we connect to a database we get a connection object (here called +`db`) that can give us a cursor. On that cursor there is a method to +execute a complete script. Finally we only have to commit the changes. +SQLite 3 and other transactional databases will not commit unless you +explicitly tell it to. + +Now it is possible to create a database by starting up a Python shell and +importing and calling that function:: + +>>> from flaskr import init_db +>>> init_db() + +.. admonition:: Troubleshooting + + If you get an exception later that a table cannot be found check that + you did call the `init_db` function and that your table names are + correct (singular vs. plural for example). + +Continue with :ref:`tutorial-dbcon` diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/tutorial/folders.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/tutorial/folders.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,23 @@ +.. _tutorial-folders: + +Step 0: Creating The Folders +============================ + +Before we get started, let's create the folders needed for this +application:: + + /flaskr + /static + /templates + +The `flaskr` folder is not a python package, but just something where we +drop our files. Directly into this folder we will then put our database +schema as well as main module in the following steps. The files inside +the `static` folder are available to users of the application via `HTTP`. +This is the place where css and javascript files go. Inside the +`templates` folder Flask will look for `Jinja2`_ templates. The +templates you create later in the tutorial will go in this directory. + +Continue with :ref:`tutorial-schema`. + +.. _Jinja2: http://jinja.pocoo.org/2/ diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/tutorial/index.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/tutorial/index.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,32 @@ +.. _tutorial: + +Tutorial +======== + +You want to develop an application with Python and Flask? Here you have +the chance to learn that by example. In this tutorial we will create a +simple microblog application. It only supports one user that can create +text-only entries and there are no feeds or comments, but it still +features everything you need to get started. We will use Flask and SQLite +as database which comes out of the box with Python, so there is nothing +else you need. + +If you want the full sourcecode in advance or for comparison, check out +the `example source`_. + +.. _example source: + http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ + +.. toctree:: + :maxdepth: 2 + + introduction + folders + schema + setup + dbinit + dbcon + views + templates + css + testing diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/tutorial/introduction.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/tutorial/introduction.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,33 @@ +.. _tutorial-introduction: + +Introducing Flaskr +================== + +We will call our blogging application flaskr here, feel free to chose a +less web-2.0-ish name ;) Basically we want it to do the following things: + +1. let the user sign in and out with credentials specified in the + configuration. Only one user is supported. +2. when the user is logged in he or she can add new entries to the page + consisting of a text-only title and some HTML for the text. This HTML + is not sanitized because we trust the user here. +3. the page shows all entries so far in reverse order (newest on top) and + the user can add new ones from there if logged in. + +We will be using SQLite3 directly for that application because it's good +enough for an application of that size. For larger applications however +it makes a lot of sense to use `SQLAlchemy`_ that handles database +connections in a more intelligent way, allows you to target different +relational databases at once and more. You might also want to consider +one of the popular NoSQL databases if your data is more suited for those. + +Here a screenshot from the final application: + +.. image:: ../_static/flaskr.png + :align: center + :class: screenshot + :alt: screenshot of the final application + +Continue with :ref:`tutorial-folders`. + +.. _SQLAlchemy: http://www.sqlalchemy.org/ diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/tutorial/schema.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/tutorial/schema.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,25 @@ +.. _tutorial-schema: + +Step 1: Database Schema +======================= + +First we want to create the database schema. For this application only a +single table is needed and we only want to support SQLite so that is quite +easy. Just put the following contents into a file named `schema.sql` in +the just created `flaskr` folder: + +.. sourcecode:: sql + + drop table if exists entries; + create table entries ( + id integer primary key autoincrement, + title string not null, + text string not null + ); + +This schema consists of a single table called `entries` and each row in +this table has an `id`, a `title` and a `text`. The `id` is an +automatically incrementing integer and a primary key, the other two are +strings that must not be null. + +Continue with :ref:`tutorial-setup`. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/tutorial/setup.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/tutorial/setup.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,83 @@ +.. _tutorial-setup: + +Step 2: Application Setup Code +============================== + +Now that we have the schema in place we can create the application module. +Let's call it `flaskr.py` inside the `flaskr` folder. For starters we +will add the imports we will need as well as the config section. For +small applications it's a possibility to drop the configuration directly +into the module which we will be doing here. However a cleaner solution +would be to create a separate `.ini` or `.py` file and load that or import +the values from there. + +:: + + # all the imports + import sqlite3 + from flask import Flask, request, session, g, redirect, url_for, \ + abort, render_template, flash + + # configuration + DATABASE = '/tmp/flaskr.db' + DEBUG = True + SECRET_KEY = 'development key' + USERNAME = 'admin' + PASSWORD = 'default' + +Next we can create our actual application and initialize it with the +config from the same file:: + + # create our little application :) + app = Flask(__name__) + app.config.from_object(__name__) + +:meth:`~flask.Config.from_object` will look at the given object (if it's a +string it will import it) and then look for all uppercase variables +defined there. In our case, the configuration we just wrote a few lines +of code above. You can also move that into a separate file. + +It is also a good idea to be able to load a configuration from a +configurable file. This is what :meth:`~flask.Config.from_envvar` can +do:: + + app.config.from_envvar('FLASKR_SETTINGS', silent=True) + +That way someone can set an environment variable called +:envvar:`FLASKR_SETTINGS` to specify a config file to be loaded which will +then override the default values. The silent switch just tells Flask to +not complain if no such environment key is set. + +The `secret_key` is needed to keep the client-side sessions secure. +Choose that key wisely and as hard to guess and complex as possible. The +debug flag enables or disables the interactive debugger. Never leave +debug mode activated in a production system because it will allow users to +execute code on the server! + +We also add a method to easily connect to the database specified. That +can be used to open a connection on request and also from the interactive +Python shell or a script. This will come in handy later. + +:: + + def connect_db(): + return sqlite3.connect(app.config['DATABASE']) + +Finally we just add a line to the bottom of the file that fires up the +server if we want to run that file as a standalone application:: + + if __name__ == '__main__': + app.run() + +With that out of the way you should be able to start up the application +without problems. When you head over to the server you will get an 404 +page not found error because we don't have any views yet. But we will +focus on that a little later. First we should get the database working. + +.. admonition:: Externally Visible Server + + Want your server to be publicly available? Check out the + :ref:`externally visible server ` section for more + information. + +Continue with :ref:`tutorial-dbinit`. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/tutorial/templates.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/tutorial/templates.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,111 @@ +.. _tutorial-templates: + +Step 6: The Templates +===================== + +Now we should start working on the templates. If we request the URLs now +we would only get an exception that Flask cannot find the templates. The +templates are using `Jinja2`_ syntax and have autoescaping enabled by +default. This means that unless you mark a value in the code with +:class:`~flask.Markup` or with the ``|safe`` filter in the template, +Jinja2 will ensure that special characters such as ``<`` or ``>`` are +escaped with their XML equivalents. + +We are also using template inheritance which makes it possible to reuse +the layout of the website in all pages. + +Put the following templates into the `templates` folder: + +.. _Jinja2: http://jinja.pocoo.org/2/documentation/templates + +layout.html +----------- + +This template contains the HTML skeleton, the header and a link to log in +(or log out if the user was already logged in). It also displays the +flashed messages if there are any. The ``{% block body %}`` block can be +replaced by a block of the same name (``body``) in a child template. + +The :class:`~flask.session` dict is available in the template as well and +you can use that to check if the user is logged in or not. Note that in +Jinja you can access missing attributes and items of objects / dicts which +makes the following code work, even if there is no ``'logged_in'`` key in +the session: + +.. sourcecode:: html+jinja + + + Flaskr + +

+

Flaskr

+
+ {% if not session.logged_in %} + log in + {% else %} + log out + {% endif %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block body %}{% endblock %} +
+ +show_entries.html +----------------- + +This template extends the `layout.html` template from above to display the +messages. Note that the `for` loop iterates over the messages we passed +in with the :func:`~flask.render_template` function. We also tell the +form to submit to your `add_entry` function and use `POST` as `HTTP` +method: + +.. sourcecode:: html+jinja + + {% extends "layout.html" %} + {% block body %} + {% if session.logged_in %} +
+
+
Title: +
+
Text: +
+
+
+
+ {% endif %} +
    + {% for entry in entries %} +
  • {{ entry.title }}

    {{ entry.text|safe }} + {% else %} +
  • Unbelievable. No entries here so far + {% endfor %} +
+ {% endblock %} + +login.html +---------- + +Finally the login template which basically just displays a form to allow +the user to login: + +.. sourcecode:: html+jinja + + {% extends "layout.html" %} + {% block body %} +

Login

+ {% if error %}

Error: {{ error }}{% endif %} +

+
+
Username: +
+
Password: +
+
+
+
+ {% endblock %} + +Continue with :ref:`tutorial-css`. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/tutorial/testing.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/tutorial/testing.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,10 @@ +.. _tutorial-testing: + +Bonus: Testing the Application +============================== + +Now that you have finished the application and everything works as +expected, it's probably not a bad idea to add automated tests to simplify +modifications in the future. The application above is used as a basic +example of how to perform unittesting in the :ref:`testing` section of the +documentation. Go there to see how easy it is to test Flask applications. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/tutorial/views.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/tutorial/views.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,98 @@ +.. _tutorial-views: + +Step 5: The View Functions +========================== + +Now that the database connections are working we can start writing the +view functions. We will need four of them: + +Show Entries +------------ + +This view shows all the entries stored in the database. It listens on the +root of the application and will select title and text from the database. +The one with the highest id (the newest entry) will be on top. The rows +returned from the cursor are tuples with the columns ordered like specified +in the select statement. This is good enough for small applications like +here, but you might want to convert them into a dict. If you are +interested in how to do that, check out the :ref:`easy-querying` example. + +The view function will pass the entries as dicts to the +`show_entries.html` template and return the rendered one:: + + @app.route('/') + def show_entries(): + cur = g.db.execute('select title, text from entries order by id desc') + entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()] + return render_template('show_entries.html', entries=entries) + +Add New Entry +------------- + +This view lets the user add new entries if he's logged in. This only +responds to `POST` requests, the actual form is shown on the +`show_entries` page. If everything worked out well we will +:func:`~flask.flash` an information message to the next request and +redirect back to the `show_entries` page:: + + @app.route('/add', methods=['POST']) + def add_entry(): + if not session.get('logged_in'): + abort(401) + g.db.execute('insert into entries (title, text) values (?, ?)', + [request.form['title'], request.form['text']]) + g.db.commit() + flash('New entry was successfully posted') + return redirect(url_for('show_entries')) + +Note that we check that the user is logged in here (the `logged_in` key is +present in the session and `True`). + +.. admonition:: Security Note + + Be sure to use question marks when building SQL statements, as done in the + example above. Otherwise, your app will be vulnerable to SQL injection when + you use string formatting to build SQL statements. + See :ref:`sqlite3` for more. + +Login and Logout +---------------- + +These functions are used to sign the user in and out. Login checks the +username and password against the ones from the configuration and sets the +`logged_in` key in the session. If the user logged in successfully, that +key is set to `True`, and the user is redirected back to the `show_entries` +page. In addition, a message is flashed that informs the user that he or +she was logged in successfully. If an error occurred, the template is +notified about that, and the user is asked again:: + + @app.route('/login', methods=['GET', 'POST']) + def login(): + error = None + if request.method == 'POST': + if request.form['username'] != app.config['USERNAME']: + error = 'Invalid username' + elif request.form['password'] != app.config['PASSWORD']: + error = 'Invalid password' + else: + session['logged_in'] = True + flash('You were logged in') + return redirect(url_for('show_entries')) + return render_template('login.html', error=error) + +The logout function, on the other hand, removes that key from the session +again. We use a neat trick here: if you use the :meth:`~dict.pop` method +of the dict and pass a second parameter to it (the default), the method +will delete the key from the dictionary if present or do nothing when that +key is not in there. This is helpful because now we don't have to check +if the user was logged in. + +:: + + @app.route('/logout') + def logout(): + session.pop('logged_in', None) + flash('You were logged out') + return redirect(url_for('show_entries')) + +Continue with :ref:`tutorial-templates`. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/unicode.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/unicode.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,107 @@ +Unicode in Flask +================ + +Flask like Jinja2 and Werkzeug is totally Unicode based when it comes to +text. Not only these libraries, also the majority of web related Python +libraries that deal with text. If you don't know Unicode so far, you +should probably read `The Absolute Minimum Every Software Developer +Absolutely, Positively Must Know About Unicode and Character Sets +`_. This part of the +documentation just tries to cover the very basics so that you have a +pleasant experience with Unicode related things. + +Automatic Conversion +-------------------- + +Flask has a few assumptions about your application (which you can change +of course) that give you basic and painless Unicode support: + +- the encoding for text on your website is UTF-8 +- internally you will always use Unicode exclusively for text except + for literal strings with only ASCII character points. +- encoding and decoding happens whenever you are talking over a protocol + that requires bytes to be transmitted. + +So what does this mean to you? + +HTTP is based on bytes. Not only the protocol, also the system used to +address documents on servers (so called URIs or URLs). However HTML which +is usually transmitted on top of HTTP supports a large variety of +character sets and which ones are used, are transmitted in an HTTP header. +To not make this too complex Flask just assumes that if you are sending +Unicode out you want it to be UTF-8 encoded. Flask will do the encoding +and setting of the appropriate headers for you. + +The same is true if you are talking to databases with the help of +SQLAlchemy or a similar ORM system. Some databases have a protocol that +already transmits Unicode and if they do not, SQLAlchemy or your other ORM +should take care of that. + +The Golden Rule +--------------- + +So the rule of thumb: if you are not dealing with binary data, work with +Unicode. What does working with Unicode in Python 2.x mean? + +- as long as you are using ASCII charpoints only (basically numbers, + some special characters of latin letters without umlauts or anything + fancy) you can use regular string literals (``'Hello World'``). +- if you need anything else than ASCII in a string you have to mark + this string as Unicode string by prefixing it with a lowercase `u`. + (like ``u'Hänsel und Gretel'``) +- if you are using non-Unicode characters in your Python files you have + to tell Python which encoding your file uses. Again, I recommend + UTF-8 for this purpose. To tell the interpreter your encoding you can + put the ``# -*- coding: utf-8 -*-`` into the first or second line of + your Python source file. +- Jinja is configured to decode the template files from UTF-8. So make + sure to tell your editor to save the file as UTF-8 there as well. + +Encoding and Decoding Yourself +------------------------------ + +If you are talking with a filesystem or something that is not really based +on Unicode you will have to ensure that you decode properly when working +with Unicode interface. So for example if you want to load a file on the +filesystem and embed it into a Jinja2 template you will have to decode it +from the encoding of that file. Here the old problem that text files do +not specify their encoding comes into play. So do yourself a favour and +limit yourself to UTF-8 for text files as well. + +Anyways. To load such a file with Unicode you can use the built-in +:meth:`str.decode` method:: + + def read_file(filename, charset='utf-8'): + with open(filename, 'r') as f: + return f.read().decode(charset) + +To go from Unicode into a specific charset such as UTF-8 you can use the +:meth:`unicode.encode` method:: + + def write_file(filename, contents, charset='utf-8'): + with open(filename, 'w') as f: + f.write(contents.encode(charset)) + +Configuring Editors +------------------- + +Most editors save as UTF-8 by default nowadays but in case your editor is +not configured to do this you have to change it. Here some common ways to +set your editor to store as UTF-8: + +- Vim: put ``set enc=utf-8`` to your ``.vimrc`` file. + +- Emacs: either use an encoding cookie or put this into your ``.emacs`` + file:: + + (prefer-coding-system 'utf-8) + (setq default-buffer-file-coding-system 'utf-8) + +- Notepad++: + + 1. Go to *Settings -> Preferences ...* + 2. Select the "New Document/Default Directory" tab + 3. Select "UTF-8 without BOM" as encoding + + It is also recommended to use the Unix newline format, you can select + it in the same panel but this is not a requirement. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/docs/upgrading.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/docs/upgrading.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,137 @@ +Upgrading to Newer Releases +=========================== + +Flask itself is changing like any software is changing over time. Most of +the changes are the nice kind, the kind where you don't have to change +anything in your code to profit from a new release. + +However every once in a while there are changes that do require some +changes in your code or there are changes that make it possible for you to +improve your own code quality by taking advantage of new features in +Flask. + +This section of the documentation enumerates all the changes in Flask from +release to release and how you can change your code to have a painless +updating experience. + +If you want to use the `easy_install` command to upgrade your Flask +installation, make sure to pass it the ``-U`` parameter:: + + $ easy_install -U Flask + +Version 0.7 +----------- + +Due to a bug in earlier implementations the request local proxies now +raise a :exc:`RuntimeError` instead of an :exc:`AttributeError` when they +are unbound. If you caught these exceptions with :exc:`AttributeError` +before, you should catch them with :exc:`RuntimeError` now. + +Additionally the :func:`~flask.send_file` function is now issuing +deprecation warnings if you depend on functionality that will be removed +in Flask 1.0. Previously it was possible to use etags and mimetypes +when file objects were passed. This was unreliable and caused issues +for a few setups. If you get a deprecation warning, make sure to +update your application to work with either filenames there or disable +etag attaching and attach them yourself. + +Old code:: + + return send_file(my_file_object) + return send_file(my_file_object) + +New code:: + + return send_file(my_file_object, add_etags=False) + +Version 0.6 +----------- + +Flask 0.6 comes with a backwards incompatible change which affects the +order of after-request handlers. Previously they were called in the order +of the registration, now they are called in reverse order. This change +was made so that Flask behaves more like people expected it to work and +how other systems handle request pre- and postprocessing. If you +depend on the order of execution of post-request functions, be sure to +change the order. + +Another change that breaks backwards compatibility is that context +processors will no longer override values passed directly to the template +rendering function. If for example `request` is as variable passed +directly to the template, the default context processor will not override +it with the current request object. This makes it easier to extend +context processors later to inject additional variables without breaking +existing template not expecting them. + +Version 0.5 +----------- + +Flask 0.5 is the first release that comes as a Python package instead of a +single module. There were a couple of internal refactoring so if you +depend on undocumented internal details you probably have to adapt the +imports. + +The following changes may be relevant to your application: + +- autoescaping no longer happens for all templates. Instead it is + configured to only happen on files ending with ``.html``, ``.htm``, + ``.xml`` and ``.xhtml``. If you have templates with different + extensions you should override the + :meth:`~flask.Flask.select_jinja_autoescape` method. +- Flask no longer supports zipped applications in this release. This + functionality might come back in future releases if there is demand + for this feature. Removing support for this makes the Flask internal + code easier to understand and fixes a couple of small issues that make + debugging harder than necessary. +- The `create_jinja_loader` function is gone. If you want to customize + the Jinja loader now, use the + :meth:`~flask.Flask.create_jinja_environment` method instead. + +Version 0.4 +----------- + +For application developers there are no changes that require changes in +your code. In case you are developing on a Flask extension however, and +that extension has a unittest-mode you might want to link the activation +of that mode to the new ``TESTING`` flag. + +Version 0.3 +----------- + +Flask 0.3 introduces configuration support and logging as well as +categories for flashing messages. All these are features that are 100% +backwards compatible but you might want to take advantage of them. + +Configuration Support +````````````````````` + +The configuration support makes it easier to write any kind of application +that requires some sort of configuration. (Which most likely is the case +for any application out there). + +If you previously had code like this:: + + app.debug = DEBUG + app.secret_key = SECRET_KEY + +You no longer have to do that, instead you can just load a configuration +into the config object. How this works is outlined in :ref:`config`. + +Logging Integration +``````````````````` + +Flask now configures a logger for you with some basic and useful defaults. +If you run your application in production and want to profit from +automatic error logging, you might be interested in attaching a proper log +handler. Also you can start logging warnings and errors into the logger +when appropriately. For more information on that, read +:ref:`application-errors`. + +Categories for Flash Messages +````````````````````````````` + +Flash messages can now have categories attached. This makes it possible +to render errors, warnings or regular messages differently for example. +This is an opt-in feature because it requires some rethinking in the code. + +Read all about that in the :ref:`message-flashing-pattern` pattern. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/flaskr/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/flaskr/README Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,28 @@ + + / Flaskr / + + a minimal blog application + + + ~ What is Flaskr? + + A sqlite powered thumble blog application + + ~ How do I use it? + + 1. edit the configuration in the flaskr.py file or + export an FLASKR_SETTINGS environment variable + pointing to a configuration file. + + 2. fire up a python shell and run this: + + >>> from flaskr import init_db; init_db() + + 3. now you can run the flaskr.py file with your + python interpreter and the application will + greet you on http://localhost:5000/ + + ~ Is it tested? + + You betcha. Run the `flaskr_tests.py` file to see + the tests pass. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/flaskr/flaskr.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/flaskr/flaskr.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" + Flaskr + ~~~~~~ + + A microblog example application written as Flask tutorial with + Flask and sqlite3. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +from __future__ import with_statement +from sqlite3 import dbapi2 as sqlite3 +from contextlib import closing +from flask import Flask, request, session, g, redirect, url_for, abort, \ + render_template, flash + +# configuration +DATABASE = '/tmp/flaskr.db' +DEBUG = True +SECRET_KEY = 'development key' +USERNAME = 'admin' +PASSWORD = 'default' + +# create our little application :) +app = Flask(__name__) +app.config.from_object(__name__) +app.config.from_envvar('FLASKR_SETTINGS', silent=True) + + +def connect_db(): + """Returns a new connection to the database.""" + return sqlite3.connect(app.config['DATABASE']) + + +def init_db(): + """Creates the database tables.""" + with closing(connect_db()) as db: + with app.open_resource('schema.sql') as f: + db.cursor().executescript(f.read()) + db.commit() + + +@app.before_request +def before_request(): + """Make sure we are connected to the database each request.""" + g.db = connect_db() + + +@app.after_request +def after_request(response): + """Closes the database again at the end of the request.""" + g.db.close() + return response + + +@app.route('/') +def show_entries(): + cur = g.db.execute('select title, text from entries order by id desc') + entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()] + return render_template('show_entries.html', entries=entries) + + +@app.route('/add', methods=['POST']) +def add_entry(): + if not session.get('logged_in'): + abort(401) + g.db.execute('insert into entries (title, text) values (?, ?)', + [request.form['title'], request.form['text']]) + g.db.commit() + flash('New entry was successfully posted') + return redirect(url_for('show_entries')) + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + error = None + if request.method == 'POST': + if request.form['username'] != app.config['USERNAME']: + error = 'Invalid username' + elif request.form['password'] != app.config['PASSWORD']: + error = 'Invalid password' + else: + session['logged_in'] = True + flash('You were logged in') + return redirect(url_for('show_entries')) + return render_template('login.html', error=error) + + +@app.route('/logout') +def logout(): + session.pop('logged_in', None) + flash('You were logged out') + return redirect(url_for('show_entries')) + + +if __name__ == '__main__': + app.run() diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/flaskr/flaskr_tests.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/flaskr/flaskr_tests.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +""" + Flaskr Tests + ~~~~~~~~~~~~ + + Tests the Flaskr application. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import os +import flaskr +import unittest +import tempfile + + +class FlaskrTestCase(unittest.TestCase): + + def setUp(self): + """Before each test, set up a blank database""" + self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() + self.app = flaskr.app.test_client() + flaskr.init_db() + + def tearDown(self): + """Get rid of the database again after each test.""" + os.close(self.db_fd) + os.unlink(flaskr.app.config['DATABASE']) + + def login(self, username, password): + return self.app.post('/login', data=dict( + username=username, + password=password + ), follow_redirects=True) + + def logout(self): + return self.app.get('/logout', follow_redirects=True) + + # testing functions + + def test_empty_db(self): + """Start with a blank database.""" + rv = self.app.get('/') + assert 'No entries here so far' in rv.data + + def test_login_logout(self): + """Make sure login and logout works""" + rv = self.login(flaskr.app.config['USERNAME'], + flaskr.app.config['PASSWORD']) + assert 'You were logged in' in rv.data + rv = self.logout() + assert 'You were logged out' in rv.data + rv = self.login(flaskr.app.config['USERNAME'] + 'x', + flaskr.app.config['PASSWORD']) + assert 'Invalid username' in rv.data + rv = self.login(flaskr.app.config['USERNAME'], + flaskr.app.config['PASSWORD'] + 'x') + assert 'Invalid password' in rv.data + + def test_messages(self): + """Test that messages work""" + self.login(flaskr.app.config['USERNAME'], + flaskr.app.config['PASSWORD']) + rv = self.app.post('/add', data=dict( + title='', + text='HTML allowed here' + ), follow_redirects=True) + assert 'No entries here so far' not in rv.data + assert '<Hello>' in rv.data + assert 'HTML allowed here' in rv.data + + +if __name__ == '__main__': + unittest.main() diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/flaskr/schema.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/flaskr/schema.sql Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,6 @@ +drop table if exists entries; +create table entries ( + id integer primary key autoincrement, + title string not null, + text string not null +); diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/flaskr/static/style.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/flaskr/static/style.css Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,18 @@ +body { font-family: sans-serif; background: #eee; } +a, h1, h2 { color: #377BA8; } +h1, h2 { font-family: 'Georgia', serif; margin: 0; } +h1 { border-bottom: 2px solid #eee; } +h2 { font-size: 1.2em; } + +.page { margin: 2em auto; width: 35em; border: 5px solid #ccc; + padding: 0.8em; background: white; } +.entries { list-style: none; margin: 0; padding: 0; } +.entries li { margin: 0.8em 1.2em; } +.entries li h2 { margin-left: -1em; } +.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } +.add-entry dl { font-weight: bold; } +.metanav { text-align: right; font-size: 0.8em; padding: 0.3em; + margin-bottom: 1em; background: #fafafa; } +.flash { background: #CEE5F5; padding: 0.5em; + border: 1px solid #AACBE2; } +.error { background: #F0D6D6; padding: 0.5em; } diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/flaskr/templates/layout.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/flaskr/templates/layout.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,17 @@ + +Flaskr + +
+

Flaskr

+
+ {% if not session.logged_in %} + log in + {% else %} + log out + {% endif %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block body %}{% endblock %} +
diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/flaskr/templates/login.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/flaskr/templates/login.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,14 @@ +{% extends "layout.html" %} +{% block body %} +

Login

+ {% if error %}

Error: {{ error }}{% endif %} +

+
+
Username: +
+
Password: +
+
+
+
+{% endblock %} diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/flaskr/templates/show_entries.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/flaskr/templates/show_entries.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,21 @@ +{% extends "layout.html" %} +{% block body %} + {% if session.logged_in %} +
+
+
Title: +
+
Text: +
+
+
+
+ {% endif %} +
    + {% for entry in entries %} +
  • {{ entry.title }}

    {{ entry.text|safe }} + {% else %} +
  • Unbelievable. No entries here so far + {% endfor %} +
+{% endblock %} diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/jqueryexample/jqueryexample.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/jqueryexample/jqueryexample.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" + jQuery Example + ~~~~~~~~~~~~~~ + + A simple application that shows how Flask and jQuery get along. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +from flask import Flask, jsonify, render_template, request +app = Flask(__name__) + + +@app.route('/_add_numbers') +def add_numbers(): + """Add two numbers server side, ridiculous but well...""" + a = request.args.get('a', 0, type=int) + b = request.args.get('b', 0, type=int) + return jsonify(result=a + b) + + +@app.route('/') +def index(): + return render_template('index.html') + + +if __name__ == '__main__': + app.run() diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/jqueryexample/templates/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/jqueryexample/templates/index.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,21 @@ +{% extends "layout.html" %} +{% block body %} + +

jQuery Example

+

+ + = + ? +

calculate server side +{% endblock %} diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/jqueryexample/templates/layout.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/jqueryexample/templates/layout.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,8 @@ + +jQuery Example + + +{% block body %}{% endblock %} diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/minitwit/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/minitwit/README Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,28 @@ + + / MiniTwit / + + because writing todo lists is not fun + + + ~ What is MiniTwit? + + A SQLite and Flask powered twitter clone + + ~ How do I use it? + + 1. edit the configuration in the minitwit.py file or + export an MINITWIT_SETTINGS environment variable + pointing to a configuration file. + + 2. fire up a python shell and run this: + + >>> from minitwit import init_db; init_db() + + 3. now you can run the minitwit.py file with your + python interpreter and the application will + greet you on http://localhost:5000/ + + ~ Is it tested? + + You betcha. Run the `minitwit_tests.py` file to + see the tests pass. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/minitwit/minitwit.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/minitwit/minitwit.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +""" + MiniTwit + ~~~~~~~~ + + A microblogging application written with Flask and sqlite3. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +from __future__ import with_statement +import time +from sqlite3 import dbapi2 as sqlite3 +from hashlib import md5 +from datetime import datetime +from contextlib import closing +from flask import Flask, request, session, url_for, redirect, \ + render_template, abort, g, flash +from werkzeug import check_password_hash, generate_password_hash + + +# configuration +DATABASE = '/tmp/minitwit.db' +PER_PAGE = 30 +DEBUG = True +SECRET_KEY = 'development key' + +# create our little application :) +app = Flask(__name__) +app.config.from_object(__name__) +app.config.from_envvar('MINITWIT_SETTINGS', silent=True) + + +def connect_db(): + """Returns a new connection to the database.""" + return sqlite3.connect(app.config['DATABASE']) + + +def init_db(): + """Creates the database tables.""" + with closing(connect_db()) as db: + with app.open_resource('schema.sql') as f: + db.cursor().executescript(f.read()) + db.commit() + + +def query_db(query, args=(), one=False): + """Queries the database and returns a list of dictionaries.""" + cur = g.db.execute(query, args) + rv = [dict((cur.description[idx][0], value) + for idx, value in enumerate(row)) for row in cur.fetchall()] + return (rv[0] if rv else None) if one else rv + + +def get_user_id(username): + """Convenience method to look up the id for a username.""" + rv = g.db.execute('select user_id from user where username = ?', + [username]).fetchone() + return rv[0] if rv else None + + +def format_datetime(timestamp): + """Format a timestamp for display.""" + return datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d @ %H:%M') + + +def gravatar_url(email, size=80): + """Return the gravatar image for the given email address.""" + return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \ + (md5(email.strip().lower().encode('utf-8')).hexdigest(), size) + + +@app.before_request +def before_request(): + """Make sure we are connected to the database each request and look + up the current user so that we know he's there. + """ + g.db = connect_db() + g.user = None + if 'user_id' in session: + g.user = query_db('select * from user where user_id = ?', + [session['user_id']], one=True) + + +@app.after_request +def after_request(response): + """Closes the database again at the end of the request.""" + g.db.close() + return response + + +@app.route('/') +def timeline(): + """Shows a users timeline or if no user is logged in it will + redirect to the public timeline. This timeline shows the user's + messages as well as all the messages of followed users. + """ + if not g.user: + return redirect(url_for('public_timeline')) + return render_template('timeline.html', messages=query_db(''' + select message.*, user.* from message, user + where message.author_id = user.user_id and ( + user.user_id = ? or + user.user_id in (select whom_id from follower + where who_id = ?)) + order by message.pub_date desc limit ?''', + [session['user_id'], session['user_id'], PER_PAGE])) + + +@app.route('/public') +def public_timeline(): + """Displays the latest messages of all users.""" + return render_template('timeline.html', messages=query_db(''' + select message.*, user.* from message, user + where message.author_id = user.user_id + order by message.pub_date desc limit ?''', [PER_PAGE])) + + +@app.route('/') +def user_timeline(username): + """Display's a users tweets.""" + profile_user = query_db('select * from user where username = ?', + [username], one=True) + if profile_user is None: + abort(404) + followed = False + if g.user: + followed = query_db('''select 1 from follower where + follower.who_id = ? and follower.whom_id = ?''', + [session['user_id'], profile_user['user_id']], + one=True) is not None + return render_template('timeline.html', messages=query_db(''' + select message.*, user.* from message, user where + user.user_id = message.author_id and user.user_id = ? + order by message.pub_date desc limit ?''', + [profile_user['user_id'], PER_PAGE]), followed=followed, + profile_user=profile_user) + + +@app.route('//follow') +def follow_user(username): + """Adds the current user as follower of the given user.""" + if not g.user: + abort(401) + whom_id = get_user_id(username) + if whom_id is None: + abort(404) + g.db.execute('insert into follower (who_id, whom_id) values (?, ?)', + [session['user_id'], whom_id]) + g.db.commit() + flash('You are now following "%s"' % username) + return redirect(url_for('user_timeline', username=username)) + + +@app.route('//unfollow') +def unfollow_user(username): + """Removes the current user as follower of the given user.""" + if not g.user: + abort(401) + whom_id = get_user_id(username) + if whom_id is None: + abort(404) + g.db.execute('delete from follower where who_id=? and whom_id=?', + [session['user_id'], whom_id]) + g.db.commit() + flash('You are no longer following "%s"' % username) + return redirect(url_for('user_timeline', username=username)) + + +@app.route('/add_message', methods=['POST']) +def add_message(): + """Registers a new message for the user.""" + if 'user_id' not in session: + abort(401) + if request.form['text']: + g.db.execute('''insert into message (author_id, text, pub_date) + values (?, ?, ?)''', (session['user_id'], request.form['text'], + int(time.time()))) + g.db.commit() + flash('Your message was recorded') + return redirect(url_for('timeline')) + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + """Logs the user in.""" + if g.user: + return redirect(url_for('timeline')) + error = None + if request.method == 'POST': + user = query_db('''select * from user where + username = ?''', [request.form['username']], one=True) + if user is None: + error = 'Invalid username' + elif not check_password_hash(user['pw_hash'], + request.form['password']): + error = 'Invalid password' + else: + flash('You were logged in') + session['user_id'] = user['user_id'] + return redirect(url_for('timeline')) + return render_template('login.html', error=error) + + +@app.route('/register', methods=['GET', 'POST']) +def register(): + """Registers the user.""" + if g.user: + return redirect(url_for('timeline')) + error = None + if request.method == 'POST': + if not request.form['username']: + error = 'You have to enter a username' + elif not request.form['email'] or \ + '@' not in request.form['email']: + error = 'You have to enter a valid email address' + elif not request.form['password']: + error = 'You have to enter a password' + elif request.form['password'] != request.form['password2']: + error = 'The two passwords do not match' + elif get_user_id(request.form['username']) is not None: + error = 'The username is already taken' + else: + g.db.execute('''insert into user ( + username, email, pw_hash) values (?, ?, ?)''', + [request.form['username'], request.form['email'], + generate_password_hash(request.form['password'])]) + g.db.commit() + flash('You were successfully registered and can login now') + return redirect(url_for('login')) + return render_template('register.html', error=error) + + +@app.route('/logout') +def logout(): + """Logs the user out.""" + flash('You were logged out') + session.pop('user_id', None) + return redirect(url_for('public_timeline')) + + +# add some filters to jinja +app.jinja_env.filters['datetimeformat'] = format_datetime +app.jinja_env.filters['gravatar'] = gravatar_url + + +if __name__ == '__main__': + app.run() diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/minitwit/minitwit_tests.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/minitwit/minitwit_tests.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +""" + MiniTwit Tests + ~~~~~~~~~~~~~~ + + Tests the MiniTwit application. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import os +import minitwit +import unittest +import tempfile + + +class MiniTwitTestCase(unittest.TestCase): + + def setUp(self): + """Before each test, set up a blank database""" + self.db_fd, minitwit.app.config['DATABASE'] = tempfile.mkstemp() + self.app = minitwit.app.test_client() + minitwit.init_db() + + def tearDown(self): + """Get rid of the database again after each test.""" + os.close(self.db_fd) + os.unlink(minitwit.app.config['DATABASE']) + + # helper functions + + def register(self, username, password, password2=None, email=None): + """Helper function to register a user""" + if password2 is None: + password2 = password + if email is None: + email = username + '@example.com' + return self.app.post('/register', data={ + 'username': username, + 'password': password, + 'password2': password2, + 'email': email, + }, follow_redirects=True) + + def login(self, username, password): + """Helper function to login""" + return self.app.post('/login', data={ + 'username': username, + 'password': password + }, follow_redirects=True) + + def register_and_login(self, username, password): + """Registers and logs in in one go""" + self.register(username, password) + return self.login(username, password) + + def logout(self): + """Helper function to logout""" + return self.app.get('/logout', follow_redirects=True) + + def add_message(self, text): + """Records a message""" + rv = self.app.post('/add_message', data={'text': text}, + follow_redirects=True) + if text: + assert 'Your message was recorded' in rv.data + return rv + + # testing functions + + def test_register(self): + """Make sure registering works""" + rv = self.register('user1', 'default') + assert 'You were successfully registered ' \ + 'and can login now' in rv.data + rv = self.register('user1', 'default') + assert 'The username is already taken' in rv.data + rv = self.register('', 'default') + assert 'You have to enter a username' in rv.data + rv = self.register('meh', '') + assert 'You have to enter a password' in rv.data + rv = self.register('meh', 'x', 'y') + assert 'The two passwords do not match' in rv.data + rv = self.register('meh', 'foo', email='broken') + assert 'You have to enter a valid email address' in rv.data + + def test_login_logout(self): + """Make sure logging in and logging out works""" + rv = self.register_and_login('user1', 'default') + assert 'You were logged in' in rv.data + rv = self.logout() + assert 'You were logged out' in rv.data + rv = self.login('user1', 'wrongpassword') + assert 'Invalid password' in rv.data + rv = self.login('user2', 'wrongpassword') + assert 'Invalid username' in rv.data + + def test_message_recording(self): + """Check if adding messages works""" + self.register_and_login('foo', 'default') + self.add_message('test message 1') + self.add_message('') + rv = self.app.get('/') + assert 'test message 1' in rv.data + assert '<test message 2>' in rv.data + + def test_timelines(self): + """Make sure that timelines work""" + self.register_and_login('foo', 'default') + self.add_message('the message by foo') + self.logout() + self.register_and_login('bar', 'default') + self.add_message('the message by bar') + rv = self.app.get('/public') + assert 'the message by foo' in rv.data + assert 'the message by bar' in rv.data + + # bar's timeline should just show bar's message + rv = self.app.get('/') + assert 'the message by foo' not in rv.data + assert 'the message by bar' in rv.data + + # now let's follow foo + rv = self.app.get('/foo/follow', follow_redirects=True) + assert 'You are now following "foo"' in rv.data + + # we should now see foo's message + rv = self.app.get('/') + assert 'the message by foo' in rv.data + assert 'the message by bar' in rv.data + + # but on the user's page we only want the user's message + rv = self.app.get('/bar') + assert 'the message by foo' not in rv.data + assert 'the message by bar' in rv.data + rv = self.app.get('/foo') + assert 'the message by foo' in rv.data + assert 'the message by bar' not in rv.data + + # now unfollow and check if that worked + rv = self.app.get('/foo/unfollow', follow_redirects=True) + assert 'You are no longer following "foo"' in rv.data + rv = self.app.get('/') + assert 'the message by foo' not in rv.data + assert 'the message by bar' in rv.data + + +if __name__ == '__main__': + unittest.main() diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/minitwit/schema.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/minitwit/schema.sql Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,21 @@ +drop table if exists user; +create table user ( + user_id integer primary key autoincrement, + username string not null, + email string not null, + pw_hash string not null +); + +drop table if exists follower; +create table follower ( + who_id integer, + whom_id integer +); + +drop table if exists message; +create table message ( + message_id integer primary key autoincrement, + author_id integer not null, + text string not null, + pub_date integer +); diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/minitwit/static/style.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/minitwit/static/style.css Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,178 @@ +body { + background: #CAECE9; + font-family: 'Trebuchet MS', sans-serif; + font-size: 14px; +} + +a { + color: #26776F; +} + +a:hover { + color: #333; +} + +input[type="text"], +input[type="password"] { + background: white; + border: 1px solid #BFE6E2; + padding: 2px; + font-family: 'Trebuchet MS', sans-serif; + font-size: 14px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + color: #105751; +} + +input[type="submit"] { + background: #105751; + border: 1px solid #073B36; + padding: 1px 3px; + font-family: 'Trebuchet MS', sans-serif; + font-size: 14px; + font-weight: bold; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + color: white; +} + +div.page { + background: white; + border: 1px solid #6ECCC4; + width: 700px; + margin: 30px auto; +} + +div.page h1 { + background: #6ECCC4; + margin: 0; + padding: 10px 14px; + color: white; + letter-spacing: 1px; + text-shadow: 0 0 3px #24776F; + font-weight: normal; +} + +div.page div.navigation { + background: #DEE9E8; + padding: 4px 10px; + border-top: 1px solid #ccc; + border-bottom: 1px solid #eee; + color: #888; + font-size: 12px; + letter-spacing: 0.5px; +} + +div.page div.navigation a { + color: #444; + font-weight: bold; +} + +div.page h2 { + margin: 0 0 15px 0; + color: #105751; + text-shadow: 0 1px 2px #ccc; +} + +div.page div.body { + padding: 10px; +} + +div.page div.footer { + background: #eee; + color: #888; + padding: 5px 10px; + font-size: 12px; +} + +div.page div.followstatus { + border: 1px solid #ccc; + background: #E3EBEA; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + padding: 3px; + font-size: 13px; +} + +div.page ul.messages { + list-style: none; + margin: 0; + padding: 0; +} + +div.page ul.messages li { + margin: 10px 0; + padding: 5px; + background: #F0FAF9; + border: 1px solid #DBF3F1; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + min-height: 48px; +} + +div.page ul.messages p { + margin: 0; +} + +div.page ul.messages li img { + float: left; + padding: 0 10px 0 0; +} + +div.page ul.messages li small { + font-size: 0.9em; + color: #888; +} + +div.page div.twitbox { + margin: 10px 0; + padding: 5px; + background: #F0FAF9; + border: 1px solid #94E2DA; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; +} + +div.page div.twitbox h3 { + margin: 0; + font-size: 1em; + color: #2C7E76; +} + +div.page div.twitbox p { + margin: 0; +} + +div.page div.twitbox input[type="text"] { + width: 585px; +} + +div.page div.twitbox input[type="submit"] { + width: 70px; + margin-left: 5px; +} + +ul.flashes { + list-style: none; + margin: 10px 10px 0 10px; + padding: 0; +} + +ul.flashes li { + background: #B9F3ED; + border: 1px solid #81CEC6; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + padding: 4px; + font-size: 13px; +} + +div.error { + margin: 10px 0; + background: #FAE4E4; + border: 1px solid #DD6F6F; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + padding: 4px; + font-size: 13px; +} diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/minitwit/templates/layout.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/minitwit/templates/layout.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,32 @@ + +{% block title %}Welcome{% endblock %} | MiniTwit + +

+

MiniTwit

+ + {% with flashes = get_flashed_messages() %} + {% if flashes %} +
    + {% for message in flashes %} +
  • {{ message }} + {% endfor %} +
+ {% endif %} + {% endwith %} +
+ {% block body %}{% endblock %} +
+ +
diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/minitwit/templates/login.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/minitwit/templates/login.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,16 @@ +{% extends "layout.html" %} +{% block title %}Sign In{% endblock %} +{% block body %} +

Sign In

+ {% if error %}
Error: {{ error }}
{% endif %} +
+
+
Username: +
+
Password: +
+
+
+
+{% endblock %} + diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/minitwit/templates/register.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/minitwit/templates/register.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,19 @@ +{% extends "layout.html" %} +{% block title %}Sign Up{% endblock %} +{% block body %} +

Sign Up

+ {% if error %}
Error: {{ error }}
{% endif %} +
+
+
Username: +
+
E-Mail: +
+
Password: +
+
Password (repeat): +
+
+
+
+{% endblock %} diff -r 543110450d50 -r fa1f7726b08b bundled/flask/examples/minitwit/templates/timeline.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/examples/minitwit/templates/timeline.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,49 @@ +{% extends "layout.html" %} +{% block title %} + {% if request.endpoint == 'public_timeline' %} + Public Timeline + {% elif request.endpoint == 'user_timeline' %} + {{ profile_user.username }}'s Timeline + {% else %} + My Timeline + {% endif %} +{% endblock %} +{% block body %} +

{{ self.title() }}

+ {% if g.user %} + {% if request.endpoint == 'user_timeline' %} +
+ {% if g.user.user_id == profile_user.user_id %} + This is you! + {% elif followed %} + You are currently following this user. + Unfollow user. + {% else %} + You are not yet following this user. + . + {% endif %} +
+ {% elif request.endpoint == 'timeline' %} +
+

What's on your mind {{ g.user.username }}?

+
+

+

+
+ {% endif %} + {% endif %} +
    + {% for message in messages %} +
  • + {{ message.username }} + {{ message.text }} + — {{ message.pub_date|datetimeformat }} + {% else %} +

  • There's no message so far. + {% endfor %} +
+{% endblock %} diff -r 543110450d50 -r fa1f7726b08b bundled/flask/extreview/approved.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/extreview/approved.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,146 @@ +Approved Extensions +=================== + +This document contains a list of all extensions that were approved and the +date of approval as well as notes. This should make it possible to better +track the extension approval process. + + +Flask-Babel +----------- + +:First Approval: 2010-07-23 +:Last Review: 2010-07-23 +:Approved Version: 0.6 +:Approved License: BSD + +Notes: Developed by the Flask development head + +How to improve: add a better long description to the next release + + +Flask-SQLAlchemy +---------------- + +:First Approval: 2010-07-25 +:Last Review: 2010-07-25 +:Approved Version: 0.9.1 +:Approved License: BSD + +Notes: Developed by the Flask development head + +How to improve: add a better long description to the next release + + +Flask-Creole +------------ + +:First Approval: 2010-07-25 +:Last Review: 2010-07-25 +:Approved Version: 0.4.4 +:Approved License: BSD + +Notes: Flask-Markdown and this should share API, consider that when +approving Flask-Markdown + + +Flask-Genshi +------------ + +:First Approval: 2010-07-26 +:Last Review: 2010-07-26 +:Approved Version: 0.3.1 +:Approved License: BSD + +Notes: This is the first template engine extension. When others come +around it would be a good idea to decide on a common interface. + + +Flask-Script +------------ + +:First Approval: 2010-07-26 +:Last Review: 2010-07-26 +:Approved Version: 0.3 +:Approved License: BSD + +Notes: Flask-Actions has some overlap. Consider that when approving +Flask-Actions or similar packages. + + +Flask-CouchDB +------------- + +:First Approval: 2010-07-26 +:Last Review: 2010-07-26 +:Approved Version: 0.2.1 +:Approved License: MIT + +There is also Flask-CouchDBKit. Both are fine because they are doing +different things, but the latter is not yet approved. + + +Flask-Testing +------------- + +:First Approval: 2010-07-27 +:Last Review: 2010-07-27 +:Approved Version: 0.2.3 +:Approved License: BSD + +All fine. + + +Flask-WTF +--------- + +:First Approval: 2010-07-27 +:Last Review: 2010-07-27 +:Approved Version: 0.2.3 +:Approved License: BSD + +All fine. + + +Flask-Themes +------------ + +:First Approval: 2010-07-27 +:Last Review: 2010-07-27 +:Approved Version: 0.1.2 +:Approved License: MIT + +All fine. + + +Flask-Uploads +------------- + +:First Approval: 2010-07-27 +:Last Review: 2010-07-27 +:Approved Version: 0.1.2 +:Approved License: MIT + +All fine. + + +Flask-Mail +---------- + +:First Approval: 2010-07-29 +:Last Review: 2010-07-29 +:Approved Version: 0.3.4 +:Approved License: BSD + +All fine. + + +Flask-XML-RPC +------------- + +:First Approval: 2010-07-30 +:Last Review: 2010-07-30 +:Approved Version: 0.1.2 +:Approved License: MIT + +All fine. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/extreview/listed.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/extreview/listed.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,64 @@ +Listed Extensions +================= + +This list contains extensions that passed listing. This means the +extension is on the list of extensions on the website. It does not +contain extensions that are approved. + + +Flask-CouchDBKit +---------------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.2 + +Would be fine for approval, but the test suite is not part of the sdist +package (missing entry in MANIFEST.in) and the test suite does not respond +to either "make test" or "python setup.py test". + + +flask-csrf +---------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.2 + +Will not be approved because this is functionality that should be handled +in the form handling systems which is for Flask-WTF already the case. +Also, this implementation only supports one open tab with forms. + +Name is not following Flask extension naming rules. + +Considered for unlisting. + + +flask-lesscss +------------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.9.1 + +Broken package description, nonconforming package name, does not follow +standard API rules (init_lesscss instead of lesscss). + +Considered for unlisting, improved version should release as +"Flask-LessCSS" with a conforming API and fixed packages indices, as well +as a testsuite. + + +Flask-OAuth +----------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.9 + +Short long description, missing tests. + + +Flask-OpenID +------------ + +:Last-Review: 2010-07-25 +:Reviewed Version: 1.0.1 + +Short long description, missing tests. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/extreview/unlisted.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/extreview/unlisted.rst Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,55 @@ +Unlisted Extensions +=================== + +This is a list of extensions that is currently rejected from listing and +with that also not approved. If an extension ends up here it should +improved to be listed. + + +Flask-Actions +------------- + +:Last Review: 2010-07-25 +:Reviewed Version: 0.2 + +Rejected because of missing description in PyPI, formatting issues with +the documentation (missing headlines, scrollbars etc.) and a general clash +of functionality with the Flask-Script package. Latter should not be a +problem, but the documentation should improve. For listing, the extension +developer should probably discuss the extension on the mailinglist with +others. + +Futhermore it also has an egg registered with an invalid filename. + + +Flask-Jinja2Extender +-------------------- + +:Last Review: 2010-07-25 +:Reviewed Version: 0.1 + +Usecase not obvious, hacky implementation, does not solve a problem that +could not be solved with Flask itself. I suppose it is to aid other +extensions, but that should be discussed on the mailinglist. + + +Flask-Markdown +-------------- + +:Last Review: 2010-07-25 +:Reviewed Version: 0.2 + +Would be great for enlisting but it should follow the API of Flask-Creole. +Besides that, the docstrings are not valid rst (run through rst2html to +see the issue) and it is missing tests. Otherwise fine :) + + +flask-urls +---------- + +:Last Review: 2010-07-25 +:Reviewed Version: 0.9.2 + +Broken PyPI index and non-conforming extension name. Due to the small +featureset this was also delisted from the list. It was there previously +before the approval process was introduced. diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask.py --- a/bundled/flask/flask.py Mon Jul 18 13:21:39 2011 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1512 +0,0 @@ -# -*- coding: utf-8 -*- -""" - flask - ~~~~~ - - A microframework based on Werkzeug. It's extensively documented - and follows best practice patterns. - - :copyright: (c) 2010 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" -from __future__ import with_statement -import os -import sys -import mimetypes -from datetime import datetime, timedelta - -from itertools import chain -from threading import Lock -from jinja2 import Environment, PackageLoader, FileSystemLoader -from werkzeug import Request as RequestBase, Response as ResponseBase, \ - LocalStack, LocalProxy, create_environ, SharedDataMiddleware, \ - ImmutableDict, cached_property, wrap_file, Headers, \ - import_string -from werkzeug.routing import Map, Rule -from werkzeug.exceptions import HTTPException, InternalServerError -from werkzeug.contrib.securecookie import SecureCookie - -# try to load the best simplejson implementation available. If JSON -# is not installed, we add a failing class. -json_available = True -try: - import simplejson as json -except ImportError: - try: - import json - except ImportError: - json_available = False - -# utilities we import from Werkzeug and Jinja2 that are unused -# in the module but are exported as public interface. -from werkzeug import abort, redirect -from jinja2 import Markup, escape - -# use pkg_resource if that works, otherwise fall back to cwd. The -# current working directory is generally not reliable with the notable -# exception of google appengine. -try: - import pkg_resources - pkg_resources.resource_stream -except (ImportError, AttributeError): - pkg_resources = None - -# a lock used for logger initialization -_logger_lock = Lock() - - -class Request(RequestBase): - """The request object used by default in flask. Remembers the - matched endpoint and view arguments. - - It is what ends up as :class:`~flask.request`. If you want to replace - the request object used you can subclass this and set - :attr:`~flask.Flask.request_class` to your subclass. - """ - - endpoint = view_args = routing_exception = None - - @property - def module(self): - """The name of the current module""" - if self.endpoint and '.' in self.endpoint: - return self.endpoint.rsplit('.', 1)[0] - - @cached_property - def json(self): - """If the mimetype is `application/json` this will contain the - parsed JSON data. - """ - if __debug__: - _assert_have_json() - if self.mimetype == 'application/json': - return json.loads(self.data) - - -class Response(ResponseBase): - """The response object that is used by default in flask. Works like the - response object from Werkzeug but is set to have a HTML mimetype by - default. Quite often you don't have to create this object yourself because - :meth:`~flask.Flask.make_response` will take care of that for you. - - If you want to replace the response object used you can subclass this and - set :attr:`~flask.Flask.response_class` to your subclass. - """ - default_mimetype = 'text/html' - - -class _RequestGlobals(object): - pass - - -class Session(SecureCookie): - """Expands the session with support for switching between permanent - and non-permanent sessions. - """ - - def _get_permanent(self): - return self.get('_permanent', False) - - def _set_permanent(self, value): - self['_permanent'] = bool(value) - - permanent = property(_get_permanent, _set_permanent) - del _get_permanent, _set_permanent - - -class _NullSession(Session): - """Class used to generate nicer error messages if sessions are not - available. Will still allow read-only access to the empty session - but fail on setting. - """ - - def _fail(self, *args, **kwargs): - raise RuntimeError('the session is unavailable because no secret ' - 'key was set. Set the secret_key on the ' - 'application to something unique and secret') - __setitem__ = __delitem__ = clear = pop = popitem = \ - update = setdefault = _fail - del _fail - - -class _RequestContext(object): - """The request context contains all request relevant information. It is - created at the beginning of the request and pushed to the - `_request_ctx_stack` and removed at the end of it. It will create the - URL adapter and request object for the WSGI environment provided. - """ - - def __init__(self, app, environ): - self.app = app - self.url_adapter = app.url_map.bind_to_environ(environ) - self.request = app.request_class(environ) - self.session = app.open_session(self.request) - if self.session is None: - self.session = _NullSession() - self.g = _RequestGlobals() - self.flashes = None - - try: - self.request.endpoint, self.request.view_args = \ - self.url_adapter.match() - except HTTPException, e: - self.request.routing_exception = e - - def push(self): - """Binds the request context.""" - _request_ctx_stack.push(self) - - def pop(self): - """Pops the request context.""" - _request_ctx_stack.pop() - - def __enter__(self): - self.push() - return self - - def __exit__(self, exc_type, exc_value, tb): - # do not pop the request stack if we are in debug mode and an - # exception happened. This will allow the debugger to still - # access the request object in the interactive shell. Furthermore - # the context can be force kept alive for the test client. - if not self.request.environ.get('flask._preserve_context') and \ - (tb is None or not self.app.debug): - self.pop() - - -def url_for(endpoint, **values): - """Generates a URL to the given endpoint with the method provided. - The endpoint is relative to the active module if modules are in use. - - Here some examples: - - ==================== ======================= ============================= - Active Module Target Endpoint Target Function - ==================== ======================= ============================= - `None` ``'index'`` `index` of the application - `None` ``'.index'`` `index` of the application - ``'admin'`` ``'index'`` `index` of the `admin` module - any ``'.index'`` `index` of the application - any ``'admin.index'`` `index` of the `admin` module - ==================== ======================= ============================= - - Variable arguments that are unknown to the target endpoint are appended - to the generated URL as query arguments. - - For more information, head over to the :ref:`Quickstart `. - - :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. - """ - ctx = _request_ctx_stack.top - if '.' not in endpoint: - mod = ctx.request.module - if mod is not None: - endpoint = mod + '.' + endpoint - elif endpoint.startswith('.'): - endpoint = endpoint[1:] - external = values.pop('_external', False) - return ctx.url_adapter.build(endpoint, values, force_external=external) - - -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): - """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. - - Example usage: - - .. sourcecode:: html+jinja - - {% for category, msg in get_flashed_messages(with_categories=true) %} -

{{ msg }} - {% endfor %} - - .. versionchanged:: 0.3 - `with_categories` parameter added. - - :param with_categories: set to `True` to also receive categories. - """ - flashes = _request_ctx_stack.top.flashes - if flashes is None: - _request_ctx_stack.top.flashes = flashes = session.pop('_flashes', []) - if not with_categories: - return [x[1] for x in flashes] - return flashes - - -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 - """ - if __debug__: - _assert_have_json() - return current_app.response_class(json.dumps(dict(*args, **kwargs), - indent=None if request.is_xhr else 2), mimetype='application/json') - - -def send_file(filename_or_fp, mimetype=None, as_attachment=False, - attachment_filename=None): - """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 sent certain files as attachment (HTML for instance). - - 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 - - :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. - :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. - """ - if isinstance(filename_or_fp, basestring): - filename = filename_or_fp - file = None - else: - file = filename_or_fp - filename = getattr(file, 'name', None) - if filename is not None: - 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') - data = wrap_file(request.environ, file) - - return Response(data, mimetype=mimetype, headers=headers, - direct_passthrough=True) - - -def render_template(template_name, **context): - """Renders a template from the template folder with the given - context. - - :param template_name: the name of the template to be rendered - :param context: the variables that should be available in the - context of the template. - """ - current_app.update_template_context(context) - return current_app.jinja_env.get_template(template_name).render(context) - - -def render_template_string(source, **context): - """Renders a template from the given template source string - with the given context. - - :param template_name: the sourcecode of the template to be - rendered - :param context: the variables that should be available in the - context of the template. - """ - current_app.update_template_context(context) - return current_app.jinja_env.from_string(source).render(context) - - -def _default_template_ctx_processor(): - """Default template context processor. Injects `request`, - `session` and `g`. - """ - reqctx = _request_ctx_stack.top - return dict( - request=reqctx.request, - session=reqctx.session, - g=reqctx.g - ) - - -def _assert_have_json(): - """Helper function that fails if JSON is unavailable.""" - if not json_available: - raise RuntimeError('simplejson not installed') - - -def _get_package_path(name): - """Returns the path to a package or cwd if that cannot be found.""" - try: - return os.path.abspath(os.path.dirname(sys.modules[name].__file__)) - except (KeyError, AttributeError): - return os.getcwd() - - -# 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 - - -class _PackageBoundObject(object): - - def __init__(self, import_name): - #: The name of the package or module. Do not change this once - #: it was set by the constructor. - self.import_name = import_name - - #: Where is the app root located? - self.root_path = _get_package_path(self.import_name) - - def open_resource(self, resource): - """Opens a resource from the application's resource folder. To see - how this works, consider the following folder structure:: - - /myapplication.py - /schemal.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 pkg_resources is None: - return open(os.path.join(self.root_path, resource), 'rb') - return pkg_resources.resource_stream(self.import_name, resource) - - -class _ModuleSetupState(object): - - def __init__(self, app, url_prefix=None): - self.app = app - self.url_prefix = url_prefix - - -class Module(_PackageBoundObject): - """Container object that enables pluggable applications. A module can - be used to organize larger applications. They represent blueprints that, - in combination with a :class:`Flask` object are used to create a large - application. - - A module is like an application bound to an `import_name`. Multiple - modules can share the same import names, but in that case a `name` has - to be provided to keep them apart. If different import names are used, - the rightmost part of the import name is used as name. - - Here an example structure for a larger appliation:: - - /myapplication - /__init__.py - /views - /__init__.py - /admin.py - /frontend.py - - The `myapplication/__init__.py` can look like this:: - - from flask import Flask - from myapplication.views.admin import admin - from myapplication.views.frontend import frontend - - app = Flask(__name__) - app.register_module(admin, url_prefix='/admin') - app.register_module(frontend) - - And here an example view module (`myapplication/views/admin.py`):: - - from flask import Module - - admin = Module(__name__) - - @admin.route('/') - def index(): - pass - - @admin.route('/login') - def login(): - pass - - For a gentle introduction into modules, checkout the - :ref:`working-with-modules` section. - """ - - def __init__(self, import_name, name=None, url_prefix=None): - if name is None: - assert '.' in import_name, 'name required if package name ' \ - 'does not point to a submodule' - name = import_name.rsplit('.', 1)[1] - _PackageBoundObject.__init__(self, import_name) - self.name = name - self.url_prefix = url_prefix - self._register_events = [] - - def route(self, rule, **options): - """Like :meth:`Flask.route` but for a module. The endpoint for the - :func:`url_for` function is prefixed with the name of the module. - """ - def decorator(f): - self.add_url_rule(rule, f.__name__, f, **options) - return f - return decorator - - def add_url_rule(self, rule, endpoint, view_func=None, **options): - """Like :meth:`Flask.add_url_rule` but for a module. The endpoint for - the :func:`url_for` function is prefixed with the name of the module. - """ - def register_rule(state): - the_rule = rule - if state.url_prefix: - the_rule = state.url_prefix + rule - state.app.add_url_rule(the_rule, '%s.%s' % (self.name, endpoint), - view_func, **options) - self._record(register_rule) - - def before_request(self, f): - """Like :meth:`Flask.before_request` but for a module. This function - is only executed before each request that is handled by a function of - that module. - """ - self._record(lambda s: s.app.before_request_funcs - .setdefault(self.name, []).append(f)) - return f - - def before_app_request(self, f): - """Like :meth:`Flask.before_request`. Such a function is executed - before each request, even if outside of a module. - """ - self._record(lambda s: s.app.before_request_funcs - .setdefault(None, []).append(f)) - return f - - def after_request(self, f): - """Like :meth:`Flask.after_request` but for a module. This function - is only executed after each request that is handled by a function of - that module. - """ - self._record(lambda s: s.app.after_request_funcs - .setdefault(self.name, []).append(f)) - return f - - def after_app_request(self, f): - """Like :meth:`Flask.after_request` but for a module. Such a function - is executed after each request, even if outside of the module. - """ - self._record(lambda s: s.app.after_request_funcs - .setdefault(None, []).append(f)) - return f - - def context_processor(self, f): - """Like :meth:`Flask.context_processor` but for a module. This - function is only executed for requests handled by a module. - """ - self._record(lambda s: s.app.template_context_processors - .setdefault(self.name, []).append(f)) - return f - - def app_context_processor(self, f): - """Like :meth:`Flask.context_processor` but for a module. Such a - function is executed each request, even if outside of the module. - """ - self._record(lambda s: s.app.template_context_processors - .setdefault(None, []).append(f)) - return f - - def app_errorhandler(self, code): - """Like :meth:`Flask.errorhandler` but for a module. This - handler is used for all requests, even if outside of the module. - - .. versionadded:: 0.4 - """ - def decorator(f): - self._record(lambda s: s.app.errorhandler(code)(f)) - return f - return decorator - - def _record(self, func): - self._register_events.append(func) - - -class ConfigAttribute(object): - """Makes an attribute forward to the config""" - - def __init__(self, name): - self.__name__ = name - - def __get__(self, obj, type=None): - if obj is None: - return self - return obj.config[self.__name__] - - def __set__(self, obj, value): - obj.config[self.__name__] = value - - -class Config(dict): - """Works exactly like a dict but provides ways to fill it from files - or special dictionaries. There are two common patterns to populate the - config. - - Either you can fill the config from a config file:: - - app.config.from_pyfile('yourconfig.cfg') - - Or alternatively you can define the configuration options in the - module that calls :meth:`from_object` or provide an import path to - a module that should be loaded. It is also possible to tell it to - use the same module and with that provide the configuration values - just before the call:: - - DEBUG = True - SECRET_KEY = 'development key' - app.config.from_object(__name__) - - In both cases (loading from any Python file or loading from modules), - only uppercase keys are added to the config. This makes it possible to use - lowercase values in the config file for temporary values that are not added - to the config or to define the config keys in the same file that implements - the application. - - Probably the most interesting way to load configurations is from an - environment variable pointing to a file:: - - app.config.from_envvar('YOURAPPLICATION_SETTINGS') - - In this case before launching the application you have to set this - environment variable to the file you want to use. On Linux and OS X - use the export statement:: - - export YOURAPPLICATION_SETTINGS='/path/to/config/file' - - On windows use `set` instead. - - :param root_path: path to which files are read relative from. When the - config object is created by the application, this is - the application's :attr:`~flask.Flask.root_path`. - :param defaults: an optional dictionary of default values - """ - - def __init__(self, root_path, defaults=None): - dict.__init__(self, defaults or {}) - self.root_path = root_path - - def from_envvar(self, variable_name, silent=False): - """Loads a configuration from an environment variable pointing to - a configuration file. This basically is just a shortcut with nicer - error messages for this line of code:: - - app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) - - :param variable_name: name of the environment variable - :param silent: set to `True` if you want silent failing for missing - files. - :return: bool. `True` if able to load config, `False` otherwise. - """ - rv = os.environ.get(variable_name) - if not rv: - if silent: - return False - raise RuntimeError('The environment variable %r is not set ' - 'and as such configuration could not be ' - 'loaded. Set this variable and make it ' - 'point to a configuration file' % - variable_name) - self.from_pyfile(rv) - return True - - def from_pyfile(self, filename): - """Updates the values in the config from a Python file. This function - behaves as if the file was imported as module with the - :meth:`from_object` function. - - :param filename: the filename of the config. This can either be an - absolute filename or a filename relative to the - root path. - """ - filename = os.path.join(self.root_path, filename) - d = type(sys)('config') - d.__file__ = filename - execfile(filename, d.__dict__) - self.from_object(d) - - def from_object(self, obj): - """Updates the values from the given object. An object can be of one - of the following two types: - - - a string: in this case the object with that name will be imported - - an actual object reference: that object is used directly - - Objects are usually either modules or classes. - - Just the uppercase variables in that object are stored in the config - after lowercasing. Example usage:: - - app.config.from_object('yourapplication.default_config') - from yourapplication import default_config - app.config.from_object(default_config) - - You should not use this function to load the actual configuration but - rather configuration defaults. The actual config should be loaded - with :meth:`from_pyfile` and ideally from a location not within the - package because the package might be installed system wide. - - :param obj: an import name or object - """ - if isinstance(obj, basestring): - obj = import_string(obj) - for key in dir(obj): - if key.isupper(): - self[key] = getattr(obj, key) - - def __repr__(self): - return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self)) - - -class Flask(_PackageBoundObject): - """The flask object implements a WSGI application and acts as the central - object. It is passed the name of the module or package of the - application. Once it is created it will act as a central registry for - the view functions, the URL rules, template configuration and much more. - - The name of the package is used to resolve resources from inside the - package or the folder the module is contained in depending on if the - package parameter resolves to an actual python package (a folder with - an `__init__.py` file inside) or a standard module (just a `.py` file). - - For more information about resource loading, see :func:`open_resource`. - - Usually you create a :class:`Flask` instance in your main module or - in the `__init__.py` file of your package like this:: - - from flask import Flask - app = Flask(__name__) - """ - - #: The class that is used for request objects. See :class:`~flask.Request` - #: for more information. - request_class = Request - - #: The class that is used for response objects. See - #: :class:`~flask.Response` for more information. - response_class = Response - - #: Path for the static files. If you don't want to use static files - #: you can set this value to `None` in which case no URL rule is added - #: and the development server will no longer serve any static files. - static_path = '/static' - - #: The debug flag. Set this to `True` to enable debugging of the - #: application. In debug mode the debugger will kick in when an unhandled - #: exception ocurrs and the integrated server will automatically reload - #: the application if changes in the code are detected. - #: - #: This attribute can also be configured from the config with the `DEBUG` - #: configuration key. Defaults to `False`. - debug = ConfigAttribute('DEBUG') - - #: The testing flask. Set this to `True` to enable the test mode of - #: Flask extensions (and in the future probably also Flask itself). - #: For example this might activate unittest helpers that have an - #: additional runtime cost which should not be enabled by default. - #: - #: This attribute can also be configured from the config with the - #: `TESTING` configuration key. Defaults to `False`. - testing = ConfigAttribute('TESTING') - - #: If a secret key is set, cryptographic components can use this to - #: sign cookies and other things. Set this to a complex random value - #: when you want to use the secure cookie for instance. - #: - #: This attribute can also be configured from the config with the - #: `SECRET_KEY` configuration key. Defaults to `None`. - secret_key = ConfigAttribute('SECRET_KEY') - - #: The secure cookie uses this for the name of the session cookie. - #: - #: This attribute can also be configured from the config with the - #: `SESSION_COOKIE_NAME` configuration key. Defaults to ``'session'`` - session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME') - - #: A :class:`~datetime.timedelta` which is used to set the expiration - #: date of a permanent session. The default is 31 days which makes a - #: permanent session survive for roughly one month. - #: - #: This attribute can also be configured from the config with the - #: `PERMANENT_SESSION_LIFETIME` configuration key. Defaults to - #: ``timedelta(days=31)`` - permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME') - - #: Enable this if you want to use the X-Sendfile feature. Keep in - #: mind that the server has to support this. This only affects files - #: sent with the :func:`send_file` method. - #: - #: .. versionadded:: 0.2 - #: - #: This attribute can also be configured from the config with the - #: `USE_X_SENDFILE` configuration key. Defaults to `False`. - use_x_sendfile = ConfigAttribute('USE_X_SENDFILE') - - #: The name of the logger to use. By default the logger name is the - #: package name passed to the constructor. - #: - #: .. versionadded:: 0.4 - logger_name = ConfigAttribute('LOGGER_NAME') - - #: The logging format used for the debug logger. This is only used when - #: the application is in debug mode, otherwise the attached logging - #: handler does the formatting. - #: - #: .. versionadded:: 0.3 - debug_log_format = ( - '-' * 80 + '\n' + - '%(levelname)s in %(module)s, %(pathname)s:%(lineno)d]:\n' + - '%(message)s\n' + - '-' * 80 - ) - - #: Options that are passed directly to the Jinja2 environment. - jinja_options = ImmutableDict( - autoescape=True, - extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'] - ) - - #: Default configuration parameters. - default_config = ImmutableDict({ - 'DEBUG': False, - 'TESTING': False, - 'SECRET_KEY': None, - 'SESSION_COOKIE_NAME': 'session', - 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), - 'USE_X_SENDFILE': False, - 'LOGGER_NAME': None - }) - - def __init__(self, import_name): - _PackageBoundObject.__init__(self, import_name) - - #: The configuration dictionary as :class:`Config`. This behaves - #: exactly like a regular dictionary but supports additional methods - #: to load a config from files. - self.config = Config(self.root_path, self.default_config) - - #: Prepare the deferred setup of the logger. - self._logger = None - self.logger_name = self.import_name - - #: A dictionary of all view functions registered. The keys will - #: be function names which are also used to generate URLs and - #: the values are the function objects themselves. - #: to register a view function, use the :meth:`route` decorator. - self.view_functions = {} - - #: A dictionary of all registered error handlers. The key is - #: be the error code as integer, the value the function that - #: should handle that error. - #: To register a error handler, use the :meth:`errorhandler` - #: decorator. - self.error_handlers = {} - - #: A dictionary with lists of functions that should be called at the - #: beginning of the request. The key of the dictionary is the name of - #: the module this function is active for, `None` for all requests. - #: This can for example be used to open database connections or - #: getting hold of the currently logged in user. To register a - #: function here, use the :meth:`before_request` decorator. - self.before_request_funcs = {} - - #: A dictionary with lists of functions that should be called after - #: each request. The key of the dictionary is the name of the module - #: this function is active for, `None` for all requests. This can for - #: example be used to open database connections or getting hold of the - #: currently logged in user. To register a function here, use the - #: :meth:`before_request` decorator. - self.after_request_funcs = {} - - #: A dictionary with list of functions that are called without argument - #: to populate the template context. They key of the dictionary is the - #: name of the module this function is active for, `None` for all - #: requests. Each returns a dictionary that the template context is - #: updated with. To register a function here, use the - #: :meth:`context_processor` decorator. - self.template_context_processors = { - None: [_default_template_ctx_processor] - } - - #: The :class:`~werkzeug.routing.Map` for this instance. You can use - #: this to change the routing converters after the class was created - #: but before any routes are connected. Example:: - #: - #: from werkzeug import BaseConverter - #: - #: class ListConverter(BaseConverter): - #: def to_python(self, value): - #: return value.split(',') - #: def to_url(self, values): - #: return ','.join(BaseConverter.to_url(value) - #: for value in values) - #: - #: app = Flask(__name__) - #: app.url_map.converters['list'] = ListConverter - self.url_map = Map() - - if self.static_path is not None: - self.add_url_rule(self.static_path + '/', - build_only=True, endpoint='static') - if pkg_resources is not None: - target = (self.import_name, 'static') - else: - target = os.path.join(self.root_path, 'static') - self.wsgi_app = SharedDataMiddleware(self.wsgi_app, { - self.static_path: target - }) - - #: The Jinja2 environment. It is created from the - #: :attr:`jinja_options` and the loader that is returned - #: by the :meth:`create_jinja_loader` function. - self.jinja_env = Environment(loader=self.create_jinja_loader(), - **self.jinja_options) - self.jinja_env.globals.update( - url_for=url_for, - get_flashed_messages=get_flashed_messages - ) - self.jinja_env.filters['tojson'] = _tojson_filter - - @property - def logger(self): - """A :class:`logging.Logger` object for this application. The - default configuration is to log to stderr if the application is - in debug mode. This logger can be used to (surprise) log messages. - Here some examples:: - - app.logger.debug('A value for debugging') - app.logger.warning('A warning ocurred (%d apples)', 42) - app.logger.error('An error occoured') - - .. versionadded:: 0.3 - """ - if self._logger and self._logger.name == self.logger_name: - return self._logger - with _logger_lock: - if self._logger and self._logger.name == self.logger_name: - return self._logger - from logging import getLogger, StreamHandler, Formatter, DEBUG - class DebugHandler(StreamHandler): - def emit(x, record): - if self.debug: - StreamHandler.emit(x, record) - handler = DebugHandler() - handler.setLevel(DEBUG) - handler.setFormatter(Formatter(self.debug_log_format)) - logger = getLogger(self.logger_name) - logger.addHandler(handler) - self._logger = logger - return logger - - def create_jinja_loader(self): - """Creates the Jinja loader. By default just a package loader for - the configured package is returned that looks up templates in the - `templates` folder. To add other loaders it's possible to - override this method. - """ - if pkg_resources is None: - return FileSystemLoader(os.path.join(self.root_path, 'templates')) - return PackageLoader(self.import_name) - - def update_template_context(self, context): - """Update the template context with some commonly used variables. - This injects request, session and g into the template context. - - :param context: the context as a dictionary that is updated in place - to add extra variables. - """ - funcs = self.template_context_processors[None] - mod = _request_ctx_stack.top.request.module - if mod is not None and mod in self.template_context_processors: - funcs = chain(funcs, self.template_context_processors[mod]) - for func in funcs: - context.update(func()) - - def run(self, host='127.0.0.1', port=5000, **options): - """Runs the application on a local development server. If the - :attr:`debug` flag is set the server will automatically reload - for code changes and show a debugger in case an exception happened. - - :param host: the hostname to listen on. set this to ``'0.0.0.0'`` - to have the server available externally as well. - :param port: the port of the webserver - :param options: the options to be forwarded to the underlying - Werkzeug server. See :func:`werkzeug.run_simple` - for more information. - """ - from werkzeug import run_simple - if 'debug' in options: - self.debug = options.pop('debug') - options.setdefault('use_reloader', self.debug) - options.setdefault('use_debugger', self.debug) - return run_simple(host, port, self, **options) - - def test_client(self): - """Creates a test client for this application. For information - about unit testing head over to :ref:`testing`. - - The test client can be used in a `with` block to defer the closing down - of the context until the end of the `with` block. This is useful if - you want to access the context locals for testing:: - - with app.test_client() as c: - rv = c.get('/?vodka=42') - assert request.args['vodka'] == '42' - - .. versionchanged:: 0.4 - added support for `with` block usage for the client. - """ - from werkzeug import Client - class FlaskClient(Client): - preserve_context = context_preserved = False - def open(self, *args, **kwargs): - if self.context_preserved: - _request_ctx_stack.pop() - self.context_preserved = False - kwargs.setdefault('environ_overrides', {}) \ - ['flask._preserve_context'] = self.preserve_context - old = _request_ctx_stack.top - try: - return Client.open(self, *args, **kwargs) - finally: - self.context_preserved = _request_ctx_stack.top is not old - def __enter__(self): - self.preserve_context = True - return self - def __exit__(self, exc_type, exc_value, tb): - self.preserve_context = False - if self.context_preserved: - _request_ctx_stack.pop() - return FlaskClient(self, self.response_class, use_cookies=True) - - def open_session(self, request): - """Creates or opens a new session. Default implementation stores all - session data in a signed cookie. This requires that the - :attr:`secret_key` is set. - - :param request: an instance of :attr:`request_class`. - """ - key = self.secret_key - if key is not None: - return Session.load_cookie(request, self.session_cookie_name, - secret_key=key) - - def save_session(self, session, response): - """Saves the session if it needs updates. For the default - implementation, check :meth:`open_session`. - - :param session: the session to be saved (a - :class:`~werkzeug.contrib.securecookie.SecureCookie` - object) - :param response: an instance of :attr:`response_class` - """ - expires = None - if session.permanent: - expires = datetime.utcnow() + self.permanent_session_lifetime - session.save_cookie(response, self.session_cookie_name, - expires=expires, httponly=True) - - def register_module(self, module, **options): - """Registers a module with this application. The keyword argument - of this function are the same as the ones for the constructor of the - :class:`Module` class and will override the values of the module if - provided. - """ - options.setdefault('url_prefix', module.url_prefix) - state = _ModuleSetupState(self, **options) - for func in module._register_events: - func(state) - - def add_url_rule(self, rule, endpoint=None, view_func=None, **options): - """Connects a URL rule. Works exactly like the :meth:`route` - decorator. If a view_func is provided it will be registered with the - endpoint. - - Basically this example:: - - @app.route('/') - def index(): - pass - - Is equivalent to the following:: - - def index(): - pass - app.add_url_rule('/', 'index', index) - - If the view_func is not provided you will need to connect the endpoint - to a view function like so:: - - app.view_functions['index'] = index - - .. versionchanged:: 0.2 - `view_func` parameter added. - - :param rule: the URL rule as string - :param endpoint: the endpoint for the registered URL rule. Flask - itself assumes the name of the view function as - endpoint - :param view_func: the function to call when serving a request to the - provided endpoint - :param options: the options to be forwarded to the underlying - :class:`~werkzeug.routing.Rule` object - """ - if endpoint is None: - assert view_func is not None, 'expected view func if endpoint ' \ - 'is not provided.' - endpoint = view_func.__name__ - options['endpoint'] = endpoint - options.setdefault('methods', ('GET',)) - self.url_map.add(Rule(rule, **options)) - if view_func is not None: - self.view_functions[endpoint] = view_func - - def route(self, rule, **options): - """A decorator that is used to register a view function for a - given URL rule. Example:: - - @app.route('/') - def index(): - return 'Hello World' - - Variables parts in the route can be specified with angular - brackets (``/user/``). By default a variable part - in the URL accepts any string without a slash however a different - converter can be specified as well by using ````. - - Variable parts are passed to the view function as keyword - arguments. - - The following converters are possible: - - =========== =========================================== - `int` accepts integers - `float` like `int` but for floating point values - `path` like the default but also accepts slashes - =========== =========================================== - - Here some examples:: - - @app.route('/') - def index(): - pass - - @app.route('/') - def show_user(username): - pass - - @app.route('/post/') - def show_post(post_id): - pass - - An important detail to keep in mind is how Flask deals with trailing - slashes. The idea is to keep each URL unique so the following rules - apply: - - 1. If a rule ends with a slash and is requested without a slash - by the user, the user is automatically redirected to the same - page with a trailing slash attached. - 2. If a rule does not end with a trailing slash and the user request - the page with a trailing slash, a 404 not found is raised. - - This is consistent with how web servers deal with static files. This - also makes it possible to use relative link targets safely. - - The :meth:`route` decorator accepts a couple of other arguments - as well: - - :param rule: the URL rule as string - :param methods: a list of methods this rule should be limited - to (``GET``, ``POST`` etc.). By default a rule - just listens for ``GET`` (and implicitly ``HEAD``). - :param subdomain: specifies the rule for the subdoain in case - subdomain matching is in use. - :param strict_slashes: can be used to disable the strict slashes - setting for this rule. See above. - :param options: other options to be forwarded to the underlying - :class:`~werkzeug.routing.Rule` object. - """ - def decorator(f): - self.add_url_rule(rule, None, f, **options) - return f - return decorator - - def errorhandler(self, code): - """A decorator that is used to register a function give a given - error code. Example:: - - @app.errorhandler(404) - def page_not_found(error): - return 'This page does not exist', 404 - - You can also register a function as error handler without using - the :meth:`errorhandler` decorator. The following example is - equivalent to the one above:: - - def page_not_found(error): - return 'This page does not exist', 404 - app.error_handlers[404] = page_not_found - - :param code: the code as integer for the handler - """ - def decorator(f): - self.error_handlers[code] = f - return f - return decorator - - def template_filter(self, name=None): - """A decorator that is used to register custom template filter. - You can specify a name for the filter, otherwise the function - name will be used. Example:: - - @app.template_filter() - def reverse(s): - return s[::-1] - - :param name: the optional name of the filter, otherwise the - function name will be used. - """ - def decorator(f): - self.jinja_env.filters[name or f.__name__] = f - return f - return decorator - - def before_request(self, f): - """Registers a function to run before each request.""" - self.before_request_funcs.setdefault(None, []).append(f) - return f - - def after_request(self, f): - """Register a function to be run after each request.""" - self.after_request_funcs.setdefault(None, []).append(f) - return f - - def context_processor(self, f): - """Registers a template context processor function.""" - self.template_context_processors[None].append(f) - return f - - def handle_http_exception(self, e): - """Handles an HTTP exception. By default this will invoke the - registered error handlers and fall back to returning the - exception as response. - - .. versionadded: 0.3 - """ - handler = self.error_handlers.get(e.code) - if handler is None: - return e - return handler(e) - - def handle_exception(self, e): - """Default exception handling that kicks in when an exception - occours that is not catched. In debug mode the exception will - be re-raised immediately, otherwise it is logged and the handler - for a 500 internal server error is used. If no such handler - exists, a default 500 internal server error message is displayed. - - .. versionadded: 0.3 - """ - handler = self.error_handlers.get(500) - if self.debug: - raise - self.logger.exception('Exception on %s [%s]' % ( - request.path, - request.method - )) - if handler is None: - return InternalServerError() - return handler(e) - - def dispatch_request(self): - """Does the request dispatching. Matches the URL and returns the - return value of the view or error handler. This does not have to - be a response object. In order to convert the return value to a - proper response object, call :func:`make_response`. - """ - req = _request_ctx_stack.top.request - try: - if req.routing_exception is not None: - raise req.routing_exception - return self.view_functions[req.endpoint](**req.view_args) - except HTTPException, e: - return self.handle_http_exception(e) - - def make_response(self, rv): - """Converts the return value from a view function to a real - response object that is an instance of :attr:`response_class`. - - The following types are allowed for `rv`: - - .. tabularcolumns:: |p{3.5cm}|p{9.5cm}| - - ======================= =========================================== - :attr:`response_class` the object is returned unchanged - :class:`str` a response object is created with the - string as body - :class:`unicode` a response object is created with the - string encoded to utf-8 as body - :class:`tuple` the response object is created with the - contents of the tuple as arguments - a WSGI function the function is called as WSGI application - and buffered as response object - ======================= =========================================== - - :param rv: the return value from the view function - """ - if rv is None: - raise ValueError('View function did not return a response') - if isinstance(rv, self.response_class): - return rv - if isinstance(rv, basestring): - return self.response_class(rv) - if isinstance(rv, tuple): - return self.response_class(*rv) - return self.response_class.force_type(rv, request.environ) - - def preprocess_request(self): - """Called before the actual request dispatching and will - call every as :meth:`before_request` decorated function. - If any of these function returns a value it's handled as - if it was the return value from the view and further - request handling is stopped. - """ - funcs = self.before_request_funcs.get(None, ()) - mod = request.module - if mod and mod in self.before_request_funcs: - funcs = chain(funcs, self.before_request_funcs[mod]) - for func in funcs: - rv = func() - if rv is not None: - return rv - - def process_response(self, response): - """Can be overridden in order to modify the response object - before it's sent to the WSGI server. By default this will - call all the :meth:`after_request` decorated functions. - - :param response: a :attr:`response_class` object. - :return: a new response object or the same, has to be an - instance of :attr:`response_class`. - """ - ctx = _request_ctx_stack.top - mod = ctx.request.module - if not isinstance(ctx.session, _NullSession): - self.save_session(ctx.session, response) - funcs = () - if mod and mod in self.after_request_funcs: - funcs = chain(funcs, self.after_request_funcs[mod]) - if None in self.after_request_funcs: - funcs = chain(funcs, self.after_request_funcs[None]) - for handler in funcs: - response = handler(response) - return response - - def wsgi_app(self, environ, start_response): - """The actual WSGI application. This is not implemented in - `__call__` so that middlewares can be applied without losing a - reference to the class. So instead of doing this:: - - app = MyMiddleware(app) - - It's a better idea to do this instead:: - - app.wsgi_app = MyMiddleware(app.wsgi_app) - - Then you still have the original application object around and - can continue to call methods on it. - - .. versionchanged:: 0.4 - The :meth:`after_request` functions are now called even if an - error handler took over request processing. This ensures that - even if an exception happens database have the chance to - properly close the connection. - - :param environ: a WSGI environment - :param start_response: a callable accepting a status code, - a list of headers and an optional - exception context to start the response - """ - with self.request_context(environ): - try: - rv = self.preprocess_request() - if rv is None: - rv = self.dispatch_request() - response = self.make_response(rv) - except Exception, e: - response = self.make_response(self.handle_exception(e)) - try: - response = self.process_response(response) - except Exception, e: - response = self.make_response(self.handle_exception(e)) - return response(environ, start_response) - - def request_context(self, environ): - """Creates a request context from the given environment and binds - it to the current context. This must be used in combination with - the `with` statement because the request is only bound to the - current context for the duration of the `with` block. - - Example usage:: - - with app.request_context(environ): - do_something_with(request) - - The object returned can also be used without the `with` statement - which is useful for working in the shell. The example above is - doing exactly the same as this code:: - - ctx = app.request_context(environ) - ctx.push() - try: - do_something_with(request) - finally: - ctx.pop() - - The big advantage of this approach is that you can use it without - the try/finally statement in a shell for interactive testing: - - >>> ctx = app.test_request_context() - >>> ctx.bind() - >>> request.path - u'/' - >>> ctx.unbind() - - .. versionchanged:: 0.3 - Added support for non-with statement usage and `with` statement - is now passed the ctx object. - - :param environ: a WSGI environment - """ - return _RequestContext(self, environ) - - def test_request_context(self, *args, **kwargs): - """Creates a WSGI environment from the given values (see - :func:`werkzeug.create_environ` for more information, this - function accepts the same arguments). - """ - return self.request_context(create_environ(*args, **kwargs)) - - def __call__(self, environ, start_response): - """Shortcut for :attr:`wsgi_app`.""" - return self.wsgi_app(environ, start_response) - - -# context locals -_request_ctx_stack = LocalStack() -current_app = LocalProxy(lambda: _request_ctx_stack.top.app) -request = LocalProxy(lambda: _request_ctx_stack.top.request) -session = LocalProxy(lambda: _request_ctx_stack.top.session) -g = LocalProxy(lambda: _request_ctx_stack.top.g) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/__init__.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" + flask + ~~~~~ + + A microframework based on Werkzeug. It's extensively documented + and follows best practice patterns. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +# utilities we import from Werkzeug and Jinja2 that are unused +# in the module but are exported as public interface. +from werkzeug import abort, redirect +from jinja2 import Markup, escape + +from .app import Flask, Request, Response +from .config import Config +from .helpers import url_for, jsonify, json_available, flash, \ + send_file, send_from_directory, get_flashed_messages, \ + get_template_attribute, make_response +from .globals import current_app, g, request, session, _request_ctx_stack +from .module import Module +from .templating import render_template, render_template_string +from .session import Session + +# the signals +from .signals import signals_available, template_rendered, request_started, \ + request_finished, got_request_exception + +# only import json if it's available +if json_available: + from .helpers import json diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/app.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/app.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,965 @@ +# -*- coding: utf-8 -*- +""" + flask.app + ~~~~~~~~~ + + This module implements the central WSGI application object. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from __future__ import with_statement + +from threading import Lock +from datetime import timedelta, datetime +from itertools import chain + +from jinja2 import Environment + +from werkzeug import ImmutableDict +from werkzeug.routing import Map, Rule +from werkzeug.exceptions import HTTPException, InternalServerError, \ + MethodNotAllowed + +from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ + _tojson_filter, _endpoint_from_view_func +from .wrappers import Request, Response +from .config import ConfigAttribute, Config +from .ctx import _RequestContext +from .globals import _request_ctx_stack, request +from .session import Session, _NullSession +from .module import _ModuleSetupState +from .templating import _DispatchingJinjaLoader, \ + _default_template_ctx_processor +from .signals import request_started, request_finished, got_request_exception + +# a lock used for logger initialization +_logger_lock = Lock() + + +class Flask(_PackageBoundObject): + """The flask object implements a WSGI application and acts as the central + object. It is passed the name of the module or package of the + application. Once it is created it will act as a central registry for + the view functions, the URL rules, template configuration and much more. + + The name of the package is used to resolve resources from inside the + package or the folder the module is contained in depending on if the + package parameter resolves to an actual python package (a folder with + an `__init__.py` file inside) or a standard module (just a `.py` file). + + For more information about resource loading, see :func:`open_resource`. + + Usually you create a :class:`Flask` instance in your main module or + in the `__init__.py` file of your package like this:: + + from flask import Flask + app = Flask(__name__) + + .. admonition:: About the First Parameter + + The idea of the first parameter is to give Flask an idea what + belongs to your application. This name is used to find resources + on the file system, can be used by extensions to improve debugging + information and a lot more. + + So it's important what you provide there. If you are using a single + module, `__name__` is always the correct value. If you however are + using a package, it's usually recommended to hardcode the name of + your package there. + + For example if your application is defined in `yourapplication/app.py` + you should create it with one of the two versions below:: + + app = Flask('yourapplication') + app = Flask(__name__.split('.')[0]) + + Why is that? The application will work even with `__name__`, thanks + to how resources are looked up. However it will make debugging more + painful. Certain extensions can make assumptions based on the + import name of your application. For example the Flask-SQLAlchemy + extension will look for the code in your application that triggered + an SQL query in debug mode. If the import name is not properly set + up, that debugging information is lost. (For example it would only + pick up SQL queries in `yourapplicaiton.app` and not + `yourapplication.views.frontend`) + + .. versionadded:: 0.5 + The `static_path` parameter was added. + + :param import_name: the name of the application package + :param static_path: can be used to specify a different path for the + static files on the web. Defaults to ``/static``. + This does not affect the folder the files are served + *from*. + """ + + #: The class that is used for request objects. See :class:`~flask.Request` + #: for more information. + request_class = Request + + #: The class that is used for response objects. See + #: :class:`~flask.Response` for more information. + response_class = Response + + #: Path for the static files. If you don't want to use static files + #: you can set this value to `None` in which case no URL rule is added + #: and the development server will no longer serve any static files. + #: + #: This is the default used for application and modules unless a + #: different value is passed to the constructor. + static_path = '/static' + + #: The debug flag. Set this to `True` to enable debugging of the + #: application. In debug mode the debugger will kick in when an unhandled + #: exception ocurrs and the integrated server will automatically reload + #: the application if changes in the code are detected. + #: + #: This attribute can also be configured from the config with the `DEBUG` + #: configuration key. Defaults to `False`. + debug = ConfigAttribute('DEBUG') + + #: The testing flask. Set this to `True` to enable the test mode of + #: Flask extensions (and in the future probably also Flask itself). + #: For example this might activate unittest helpers that have an + #: additional runtime cost which should not be enabled by default. + #: + #: This attribute can also be configured from the config with the + #: `TESTING` configuration key. Defaults to `False`. + testing = ConfigAttribute('TESTING') + + #: If a secret key is set, cryptographic components can use this to + #: sign cookies and other things. Set this to a complex random value + #: when you want to use the secure cookie for instance. + #: + #: This attribute can also be configured from the config with the + #: `SECRET_KEY` configuration key. Defaults to `None`. + secret_key = ConfigAttribute('SECRET_KEY') + + #: The secure cookie uses this for the name of the session cookie. + #: + #: This attribute can also be configured from the config with the + #: `SESSION_COOKIE_NAME` configuration key. Defaults to ``'session'`` + session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME') + + #: A :class:`~datetime.timedelta` which is used to set the expiration + #: date of a permanent session. The default is 31 days which makes a + #: permanent session survive for roughly one month. + #: + #: This attribute can also be configured from the config with the + #: `PERMANENT_SESSION_LIFETIME` configuration key. Defaults to + #: ``timedelta(days=31)`` + permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME') + + #: Enable this if you want to use the X-Sendfile feature. Keep in + #: mind that the server has to support this. This only affects files + #: sent with the :func:`send_file` method. + #: + #: .. versionadded:: 0.2 + #: + #: This attribute can also be configured from the config with the + #: `USE_X_SENDFILE` configuration key. Defaults to `False`. + use_x_sendfile = ConfigAttribute('USE_X_SENDFILE') + + #: The name of the logger to use. By default the logger name is the + #: package name passed to the constructor. + #: + #: .. versionadded:: 0.4 + logger_name = ConfigAttribute('LOGGER_NAME') + + #: The logging format used for the debug logger. This is only used when + #: the application is in debug mode, otherwise the attached logging + #: handler does the formatting. + #: + #: .. versionadded:: 0.3 + debug_log_format = ( + '-' * 80 + '\n' + + '%(levelname)s in %(module)s [%(pathname)s:%(lineno)d]:\n' + + '%(message)s\n' + + '-' * 80 + ) + + #: Options that are passed directly to the Jinja2 environment. + jinja_options = ImmutableDict( + extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'] + ) + + #: Default configuration parameters. + default_config = ImmutableDict({ + 'DEBUG': False, + 'TESTING': False, + 'PROPAGATE_EXCEPTIONS': None, + 'SECRET_KEY': None, + 'SESSION_COOKIE_NAME': 'session', + 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), + 'USE_X_SENDFILE': False, + 'LOGGER_NAME': None, + 'SERVER_NAME': None, + 'MAX_CONTENT_LENGTH': None + }) + + #: the test client that is used with when `test_client` is used. + #: + #: .. versionadded:: 0.7 + test_client_class = None + + def __init__(self, import_name, static_path=None): + _PackageBoundObject.__init__(self, import_name) + if static_path is not None: + self.static_path = static_path + + #: The configuration dictionary as :class:`Config`. This behaves + #: exactly like a regular dictionary but supports additional methods + #: to load a config from files. + self.config = Config(self.root_path, self.default_config) + + #: Prepare the deferred setup of the logger. + self._logger = None + self.logger_name = self.import_name + + #: A dictionary of all view functions registered. The keys will + #: be function names which are also used to generate URLs and + #: the values are the function objects themselves. + #: To register a view function, use the :meth:`route` decorator. + self.view_functions = {} + + #: A dictionary of all registered error handlers. The key is + #: be the error code as integer, the value the function that + #: should handle that error. + #: To register a error handler, use the :meth:`errorhandler` + #: decorator. + self.error_handlers = {} + + #: A dictionary with lists of functions that should be called at the + #: beginning of the request. The key of the dictionary is the name of + #: the module this function is active for, `None` for all requests. + #: This can for example be used to open database connections or + #: getting hold of the currently logged in user. To register a + #: function here, use the :meth:`before_request` decorator. + self.before_request_funcs = {} + + #: A dictionary with lists of functions that should be called after + #: each request. The key of the dictionary is the name of the module + #: this function is active for, `None` for all requests. This can for + #: example be used to open database connections or getting hold of the + #: currently logged in user. To register a function here, use the + #: :meth:`after_request` decorator. + self.after_request_funcs = {} + + #: A dictionary with list of functions that are called without argument + #: to populate the template context. The key of the dictionary is the + #: name of the module this function is active for, `None` for all + #: requests. Each returns a dictionary that the template context is + #: updated with. To register a function here, use the + #: :meth:`context_processor` decorator. + self.template_context_processors = { + None: [_default_template_ctx_processor] + } + + #: all the loaded modules in a dictionary by name. + #: + #: .. versionadded:: 0.5 + self.modules = {} + + #: a place where extensions can store application specific state. For + #: example this is where an extension could store database engines and + #: similar things. For backwards compatibility extensions should register + #: themselves like this:: + #: + #: if not hasattr(app, 'extensions'): + #: app.extensions = {} + #: app.extensions['extensionname'] = SomeObject() + #: + #: The key must match the name of the `flaskext` module. For example in + #: case of a "Flask-Foo" extension in `flaskext.foo`, the key would be + #: ``'foo'``. + #: + #: .. versionadded:: 0.7 + self.extensions = {} + + #: The :class:`~werkzeug.routing.Map` for this instance. You can use + #: this to change the routing converters after the class was created + #: but before any routes are connected. Example:: + #: + #: from werkzeug.routing import BaseConverter + #: + #: class ListConverter(BaseConverter): + #: def to_python(self, value): + #: return value.split(',') + #: def to_url(self, values): + #: return ','.join(BaseConverter.to_url(value) + #: for value in values) + #: + #: app = Flask(__name__) + #: app.url_map.converters['list'] = ListConverter + self.url_map = Map() + + # register the static folder for the application. Do that even + # if the folder does not exist. First of all it might be created + # while the server is running (usually happens during development) + # but also because google appengine stores static files somewhere + # else when mapped with the .yml file. + self.add_url_rule(self.static_path + '/', + endpoint='static', + view_func=self.send_static_file) + + #: The Jinja2 environment. It is created from the + #: :attr:`jinja_options`. + self.jinja_env = self.create_jinja_environment() + self.init_jinja_globals() + + @property + def propagate_exceptions(self): + """Returns the value of the `PROPAGATE_EXCEPTIONS` configuration + value in case it's set, otherwise a sensible default is returned. + + .. versionadded:: 0.7 + """ + rv = self.config['PROPAGATE_EXCEPTIONS'] + if rv is not None: + return rv + return self.testing or self.debug + + @property + def logger(self): + """A :class:`logging.Logger` object for this application. The + default configuration is to log to stderr if the application is + in debug mode. This logger can be used to (surprise) log messages. + Here some examples:: + + app.logger.debug('A value for debugging') + app.logger.warning('A warning ocurred (%d apples)', 42) + app.logger.error('An error occoured') + + .. versionadded:: 0.3 + """ + if self._logger and self._logger.name == self.logger_name: + return self._logger + with _logger_lock: + if self._logger and self._logger.name == self.logger_name: + return self._logger + from flask.logging import create_logger + self._logger = rv = create_logger(self) + return rv + + def create_jinja_environment(self): + """Creates the Jinja2 environment based on :attr:`jinja_options` + and :meth:`select_jinja_autoescape`. + + .. versionadded:: 0.5 + """ + options = dict(self.jinja_options) + if 'autoescape' not in options: + options['autoescape'] = self.select_jinja_autoescape + return Environment(loader=_DispatchingJinjaLoader(self), **options) + + def init_jinja_globals(self): + """Called directly after the environment was created to inject + some defaults (like `url_for`, `get_flashed_messages` and the + `tojson` filter. + + .. versionadded:: 0.5 + """ + self.jinja_env.globals.update( + url_for=url_for, + get_flashed_messages=get_flashed_messages + ) + self.jinja_env.filters['tojson'] = _tojson_filter + + def select_jinja_autoescape(self, filename): + """Returns `True` if autoescaping should be active for the given + template name. + + .. versionadded:: 0.5 + """ + if filename is None: + return False + return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) + + def update_template_context(self, context): + """Update the template context with some commonly used variables. + This injects request, session, config and g into the template + context as well as everything template context processors want + to inject. Note that the as of Flask 0.6, the original values + in the context will not be overriden if a context processor + decides to return a value with the same key. + + :param context: the context as a dictionary that is updated in place + to add extra variables. + """ + funcs = self.template_context_processors[None] + mod = _request_ctx_stack.top.request.module + if mod is not None and mod in self.template_context_processors: + funcs = chain(funcs, self.template_context_processors[mod]) + orig_ctx = context.copy() + for func in funcs: + context.update(func()) + # make sure the original values win. This makes it possible to + # easier add new variables in context processors without breaking + # existing views. + context.update(orig_ctx) + + def run(self, host='127.0.0.1', port=5000, **options): + """Runs the application on a local development server. If the + :attr:`debug` flag is set the server will automatically reload + for code changes and show a debugger in case an exception happened. + + If you want to run the application in debug mode, but disable the + code execution on the interactive debugger, you can pass + ``use_evalex=False`` as parameter. This will keep the debugger's + traceback screen active, but disable code execution. + + .. admonition:: Keep in Mind + + Flask will suppress any server error with a generic error page + unless it is in debug mode. As such to enable just the + interactive debugger without the code reloading, you have to + invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``. + Setting ``use_debugger`` to `True` without being in debug mode + won't catch any exceptions because there won't be any to + catch. + + :param host: the hostname to listen on. set this to ``'0.0.0.0'`` + to have the server available externally as well. + :param port: the port of the webserver + :param options: the options to be forwarded to the underlying + Werkzeug server. See :func:`werkzeug.run_simple` + for more information. + """ + from werkzeug import run_simple + if 'debug' in options: + self.debug = options.pop('debug') + options.setdefault('use_reloader', self.debug) + options.setdefault('use_debugger', self.debug) + return run_simple(host, port, self, **options) + + def test_client(self, use_cookies=True): + """Creates a test client for this application. For information + about unit testing head over to :ref:`testing`. + + The test client can be used in a `with` block to defer the closing down + of the context until the end of the `with` block. This is useful if + you want to access the context locals for testing:: + + with app.test_client() as c: + rv = c.get('/?vodka=42') + assert request.args['vodka'] == '42' + + .. versionchanged:: 0.4 + added support for `with` block usage for the client. + + .. versionadded:: 0.7 + The `use_cookies` parameter was added as well as the ability + to override the client to be used by setting the + :attr:`test_client_class` attribute. + """ + cls = self.test_client_class + if cls is None: + from flask.testing import FlaskClient as cls + return cls(self, self.response_class, use_cookies=use_cookies) + + def open_session(self, request): + """Creates or opens a new session. Default implementation stores all + session data in a signed cookie. This requires that the + :attr:`secret_key` is set. + + :param request: an instance of :attr:`request_class`. + """ + key = self.secret_key + if key is not None: + return Session.load_cookie(request, self.session_cookie_name, + secret_key=key) + + def save_session(self, session, response): + """Saves the session if it needs updates. For the default + implementation, check :meth:`open_session`. + + :param session: the session to be saved (a + :class:`~werkzeug.contrib.securecookie.SecureCookie` + object) + :param response: an instance of :attr:`response_class` + """ + expires = domain = None + if session.permanent: + expires = datetime.utcnow() + self.permanent_session_lifetime + if self.config['SERVER_NAME'] is not None: + domain = '.' + self.config['SERVER_NAME'] + session.save_cookie(response, self.session_cookie_name, + expires=expires, httponly=True, domain=domain) + + def register_module(self, module, **options): + """Registers a module with this application. The keyword argument + of this function are the same as the ones for the constructor of the + :class:`Module` class and will override the values of the module if + provided. + """ + options.setdefault('url_prefix', module.url_prefix) + options.setdefault('subdomain', module.subdomain) + self.view_functions.update(module.view_functions) + state = _ModuleSetupState(self, **options) + for func in module._register_events: + func(state) + + def add_url_rule(self, rule, endpoint=None, view_func=None, **options): + """Connects a URL rule. Works exactly like the :meth:`route` + decorator. If a view_func is provided it will be registered with the + endpoint. + + Basically this example:: + + @app.route('/') + def index(): + pass + + Is equivalent to the following:: + + def index(): + pass + app.add_url_rule('/', 'index', index) + + If the view_func is not provided you will need to connect the endpoint + to a view function like so:: + + app.view_functions['index'] = index + + .. versionchanged:: 0.2 + `view_func` parameter added. + + .. versionchanged:: 0.6 + `OPTIONS` is added automatically as method. + + :param rule: the URL rule as string + :param endpoint: the endpoint for the registered URL rule. Flask + itself assumes the name of the view function as + endpoint + :param view_func: the function to call when serving a request to the + provided endpoint + :param options: the options to be forwarded to the underlying + :class:`~werkzeug.routing.Rule` object. A change + to Werkzeug is handling of method options. methods + is a list of methods this rule should be limited + to (`GET`, `POST` etc.). By default a rule + just listens for `GET` (and implicitly `HEAD`). + Starting with Flask 0.6, `OPTIONS` is implicitly + added and handled by the standard request handling. + """ + if endpoint is None: + endpoint = _endpoint_from_view_func(view_func) + options['endpoint'] = endpoint + methods = options.pop('methods', ('GET',)) + provide_automatic_options = False + if 'OPTIONS' not in methods: + methods = tuple(methods) + ('OPTIONS',) + provide_automatic_options = True + rule = Rule(rule, methods=methods, **options) + rule.provide_automatic_options = provide_automatic_options + self.url_map.add(rule) + if view_func is not None: + self.view_functions[endpoint] = view_func + + def route(self, rule, **options): + """A decorator that is used to register a view function for a + given URL rule. Example:: + + @app.route('/') + def index(): + return 'Hello World' + + Variables parts in the route can be specified with angular + brackets (``/user/``). By default a variable part + in the URL accepts any string without a slash however a different + converter can be specified as well by using ````. + + Variable parts are passed to the view function as keyword + arguments. + + The following converters are possible: + + =========== =========================================== + `int` accepts integers + `float` like `int` but for floating point values + `path` like the default but also accepts slashes + =========== =========================================== + + Here some examples:: + + @app.route('/') + def index(): + pass + + @app.route('/') + def show_user(username): + pass + + @app.route('/post/') + def show_post(post_id): + pass + + An important detail to keep in mind is how Flask deals with trailing + slashes. The idea is to keep each URL unique so the following rules + apply: + + 1. If a rule ends with a slash and is requested without a slash + by the user, the user is automatically redirected to the same + page with a trailing slash attached. + 2. If a rule does not end with a trailing slash and the user request + the page with a trailing slash, a 404 not found is raised. + + This is consistent with how web servers deal with static files. This + also makes it possible to use relative link targets safely. + + The :meth:`route` decorator accepts a couple of other arguments + as well: + + :param rule: the URL rule as string + :param methods: a list of methods this rule should be limited + to (`GET`, `POST` etc.). By default a rule + just listens for `GET` (and implicitly `HEAD`). + Starting with Flask 0.6, `OPTIONS` is implicitly + added and handled by the standard request handling. + :param subdomain: specifies the rule for the subdomain in case + subdomain matching is in use. + :param strict_slashes: can be used to disable the strict slashes + setting for this rule. See above. + :param options: other options to be forwarded to the underlying + :class:`~werkzeug.routing.Rule` object. + """ + def decorator(f): + self.add_url_rule(rule, None, f, **options) + return f + return decorator + + + def endpoint(self, endpoint): + """A decorator to register a function as an endpoint. + Example:: + + @app.endpoint('example.endpoint') + def example(): + return "example" + + :param endpoint: the name of the endpoint + """ + def decorator(f): + self.view_functions[endpoint] = f + return f + return decorator + + def errorhandler(self, code): + """A decorator that is used to register a function give a given + error code. Example:: + + @app.errorhandler(404) + def page_not_found(error): + return 'This page does not exist', 404 + + You can also register a function as error handler without using + the :meth:`errorhandler` decorator. The following example is + equivalent to the one above:: + + def page_not_found(error): + return 'This page does not exist', 404 + app.error_handlers[404] = page_not_found + + :param code: the code as integer for the handler + """ + def decorator(f): + self.error_handlers[code] = f + return f + return decorator + + def template_filter(self, name=None): + """A decorator that is used to register custom template filter. + You can specify a name for the filter, otherwise the function + name will be used. Example:: + + @app.template_filter() + def reverse(s): + return s[::-1] + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + def decorator(f): + self.jinja_env.filters[name or f.__name__] = f + return f + return decorator + + def before_request(self, f): + """Registers a function to run before each request.""" + self.before_request_funcs.setdefault(None, []).append(f) + return f + + def after_request(self, f): + """Register a function to be run after each request.""" + self.after_request_funcs.setdefault(None, []).append(f) + return f + + def context_processor(self, f): + """Registers a template context processor function.""" + self.template_context_processors[None].append(f) + return f + + def handle_http_exception(self, e): + """Handles an HTTP exception. By default this will invoke the + registered error handlers and fall back to returning the + exception as response. + + .. versionadded: 0.3 + """ + handler = self.error_handlers.get(e.code) + if handler is None: + return e + return handler(e) + + def handle_exception(self, e): + """Default exception handling that kicks in when an exception + occours that is not catched. In debug mode the exception will + be re-raised immediately, otherwise it is logged and the handler + for a 500 internal server error is used. If no such handler + exists, a default 500 internal server error message is displayed. + + .. versionadded: 0.3 + """ + got_request_exception.send(self, exception=e) + handler = self.error_handlers.get(500) + if self.propagate_exceptions: + raise + self.logger.exception('Exception on %s [%s]' % ( + request.path, + request.method + )) + if handler is None: + return InternalServerError() + return handler(e) + + def dispatch_request(self): + """Does the request dispatching. Matches the URL and returns the + return value of the view or error handler. This does not have to + be a response object. In order to convert the return value to a + proper response object, call :func:`make_response`. + """ + req = _request_ctx_stack.top.request + try: + if req.routing_exception is not None: + raise req.routing_exception + rule = req.url_rule + # if we provide automatic options for this URL and the + # request came with the OPTIONS method, reply automatically + if getattr(rule, 'provide_automatic_options', False) \ + and req.method == 'OPTIONS': + return self.make_default_options_response() + # otherwise dispatch to the handler for that endpoint + return self.view_functions[rule.endpoint](**req.view_args) + except HTTPException, e: + return self.handle_http_exception(e) + + def make_default_options_response(self): + """This method is called to create the default `OPTIONS` response. + This can be changed through subclassing to change the default + behaviour of `OPTIONS` responses. + + .. 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 + rv = self.response_class() + rv.allow.update(methods) + return rv + + def make_response(self, rv): + """Converts the return value from a view function to a real + response object that is an instance of :attr:`response_class`. + + The following types are allowed for `rv`: + + .. tabularcolumns:: |p{3.5cm}|p{9.5cm}| + + ======================= =========================================== + :attr:`response_class` the object is returned unchanged + :class:`str` a response object is created with the + string as body + :class:`unicode` a response object is created with the + string encoded to utf-8 as body + :class:`tuple` the response object is created with the + contents of the tuple as arguments + a WSGI function the function is called as WSGI application + and buffered as response object + ======================= =========================================== + + :param rv: the return value from the view function + """ + if rv is None: + raise ValueError('View function did not return a response') + if isinstance(rv, self.response_class): + return rv + if isinstance(rv, basestring): + return self.response_class(rv) + if isinstance(rv, tuple): + return self.response_class(*rv) + return self.response_class.force_type(rv, request.environ) + + def create_url_adapter(self, request): + """Creates a URL adapter for the given request. The URL adapter + is created at a point where the request context is not yet set up + so the request is passed explicitly. + + .. versionadded:: 0.6 + """ + return self.url_map.bind_to_environ(request.environ, + server_name=self.config['SERVER_NAME']) + + def preprocess_request(self): + """Called before the actual request dispatching and will + call every as :meth:`before_request` decorated function. + If any of these function returns a value it's handled as + if it was the return value from the view and further + request handling is stopped. + """ + funcs = self.before_request_funcs.get(None, ()) + mod = request.module + if mod and mod in self.before_request_funcs: + funcs = chain(funcs, self.before_request_funcs[mod]) + for func in funcs: + rv = func() + if rv is not None: + return rv + + def process_response(self, response): + """Can be overridden in order to modify the response object + before it's sent to the WSGI server. By default this will + call all the :meth:`after_request` decorated functions. + + .. versionchanged:: 0.5 + As of Flask 0.5 the functions registered for after request + execution are called in reverse order of registration. + + :param response: a :attr:`response_class` object. + :return: a new response object or the same, has to be an + instance of :attr:`response_class`. + """ + ctx = _request_ctx_stack.top + mod = ctx.request.module + if not isinstance(ctx.session, _NullSession): + self.save_session(ctx.session, response) + funcs = () + if mod and mod in self.after_request_funcs: + funcs = reversed(self.after_request_funcs[mod]) + if None in self.after_request_funcs: + funcs = chain(funcs, reversed(self.after_request_funcs[None])) + for handler in funcs: + response = handler(response) + return response + + def request_context(self, environ): + """Creates a request context from the given environment and binds + it to the current context. This must be used in combination with + the `with` statement because the request is only bound to the + current context for the duration of the `with` block. + + Example usage:: + + with app.request_context(environ): + do_something_with(request) + + The object returned can also be used without the `with` statement + which is useful for working in the shell. The example above is + doing exactly the same as this code:: + + ctx = app.request_context(environ) + ctx.push() + try: + do_something_with(request) + finally: + ctx.pop() + + The big advantage of this approach is that you can use it without + the try/finally statement in a shell for interactive testing: + + >>> ctx = app.test_request_context() + >>> ctx.bind() + >>> request.path + u'/' + >>> ctx.unbind() + + .. versionchanged:: 0.3 + Added support for non-with statement usage and `with` statement + is now passed the ctx object. + + :param environ: a WSGI environment + """ + return _RequestContext(self, environ) + + def test_request_context(self, *args, **kwargs): + """Creates a WSGI environment from the given values (see + :func:`werkzeug.create_environ` for more information, this + function accepts the same arguments). + """ + from werkzeug 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)) + + def wsgi_app(self, environ, start_response): + """The actual WSGI application. This is not implemented in + `__call__` so that middlewares can be applied without losing a + reference to the class. So instead of doing this:: + + app = MyMiddleware(app) + + It's a better idea to do this instead:: + + app.wsgi_app = MyMiddleware(app.wsgi_app) + + Then you still have the original application object around and + can continue to call methods on it. + + .. versionchanged:: 0.4 + The :meth:`after_request` functions are now called even if an + error handler took over request processing. This ensures that + even if an exception happens database have the chance to + properly close the connection. + + :param environ: a WSGI environment + :param start_response: a callable accepting a status code, + a list of headers and an optional + exception context to start the response + """ + with self.request_context(environ): + try: + request_started.send(self) + rv = self.preprocess_request() + if rv is None: + rv = self.dispatch_request() + response = self.make_response(rv) + except Exception, e: + response = self.make_response(self.handle_exception(e)) + try: + response = self.process_response(response) + except Exception, e: + response = self.make_response(self.handle_exception(e)) + request_finished.send(self, response=response) + return response(environ, start_response) + + def __call__(self, environ, start_response): + """Shortcut for :attr:`wsgi_app`.""" + return self.wsgi_app(environ, start_response) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/config.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/config.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- +""" + flask.config + ~~~~~~~~~~~~ + + Implements the configuration related objects. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from __future__ import with_statement + +import imp +import os +import sys + +from werkzeug import import_string + + +class ConfigAttribute(object): + """Makes an attribute forward to the config""" + + def __init__(self, name): + self.__name__ = name + + def __get__(self, obj, type=None): + if obj is None: + return self + return obj.config[self.__name__] + + def __set__(self, obj, value): + obj.config[self.__name__] = value + + +class Config(dict): + """Works exactly like a dict but provides ways to fill it from files + or special dictionaries. There are two common patterns to populate the + config. + + Either you can fill the config from a config file:: + + app.config.from_pyfile('yourconfig.cfg') + + Or alternatively you can define the configuration options in the + module that calls :meth:`from_object` or provide an import path to + a module that should be loaded. It is also possible to tell it to + use the same module and with that provide the configuration values + just before the call:: + + DEBUG = True + SECRET_KEY = 'development key' + app.config.from_object(__name__) + + In both cases (loading from any Python file or loading from modules), + only uppercase keys are added to the config. This makes it possible to use + lowercase values in the config file for temporary values that are not added + to the config or to define the config keys in the same file that implements + the application. + + Probably the most interesting way to load configurations is from an + environment variable pointing to a file:: + + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + + In this case before launching the application you have to set this + environment variable to the file you want to use. On Linux and OS X + use the export statement:: + + export YOURAPPLICATION_SETTINGS='/path/to/config/file' + + On windows use `set` instead. + + :param root_path: path to which files are read relative from. When the + config object is created by the application, this is + the application's :attr:`~flask.Flask.root_path`. + :param defaults: an optional dictionary of default values + """ + + def __init__(self, root_path, defaults=None): + dict.__init__(self, defaults or {}) + self.root_path = root_path + + def from_envvar(self, variable_name, silent=False): + """Loads a configuration from an environment variable pointing to + a configuration file. This basically is just a shortcut with nicer + error messages for this line of code:: + + app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) + + :param variable_name: name of the environment variable + :param silent: set to `True` if you want silent failing for missing + files. + :return: bool. `True` if able to load config, `False` otherwise. + """ + rv = os.environ.get(variable_name) + if not rv: + if silent: + return False + raise RuntimeError('The environment variable %r is not set ' + 'and as such configuration could not be ' + 'loaded. Set this variable and make it ' + 'point to a configuration file' % + variable_name) + self.from_pyfile(rv) + return True + + def from_pyfile(self, filename): + """Updates the values in the config from a Python file. This function + behaves as if the file was imported as module with the + :meth:`from_object` function. + + :param filename: the filename of the config. This can either be an + absolute filename or a filename relative to the + root path. + """ + filename = os.path.join(self.root_path, filename) + d = imp.new_module('config') + d.__file__ = filename + try: + execfile(filename, d.__dict__) + except IOError, e: + e.strerror = 'Unable to load configuration file (%s)' % e.strerror + raise + self.from_object(d) + + def from_object(self, obj): + """Updates the values from the given object. An object can be of one + of the following two types: + + - a string: in this case the object with that name will be imported + - an actual object reference: that object is used directly + + Objects are usually either modules or classes. + + Just the uppercase variables in that object are stored in the config + after lowercasing. Example usage:: + + app.config.from_object('yourapplication.default_config') + from yourapplication import default_config + app.config.from_object(default_config) + + You should not use this function to load the actual configuration but + rather configuration defaults. The actual config should be loaded + with :meth:`from_pyfile` and ideally from a location not within the + package because the package might be installed system wide. + + :param obj: an import name or object + """ + if isinstance(obj, basestring): + obj = import_string(obj) + for key in dir(obj): + if key.isupper(): + self[key] = getattr(obj, key) + + def __repr__(self): + return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self)) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/ctx.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/ctx.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +""" + flask.ctx + ~~~~~~~~~ + + Implements the objects required to keep the context. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from werkzeug.exceptions import HTTPException + +from .globals import _request_ctx_stack +from .session import _NullSession + + +class _RequestGlobals(object): + pass + + +class _RequestContext(object): + """The request context contains all request relevant information. It is + created at the beginning of the request and pushed to the + `_request_ctx_stack` and removed at the end of it. It will create the + URL adapter and request object for the WSGI environment provided. + """ + + def __init__(self, app, environ): + self.app = app + self.request = app.request_class(environ) + self.url_adapter = app.create_url_adapter(self.request) + self.session = app.open_session(self.request) + if self.session is None: + self.session = _NullSession() + self.g = _RequestGlobals() + self.flashes = None + + try: + url_rule, self.request.view_args = \ + self.url_adapter.match(return_rule=True) + self.request.url_rule = url_rule + except HTTPException, e: + self.request.routing_exception = e + + def push(self): + """Binds the request context.""" + _request_ctx_stack.push(self) + + def pop(self): + """Pops the request context.""" + _request_ctx_stack.pop() + + def __enter__(self): + self.push() + return self + + def __exit__(self, exc_type, exc_value, tb): + # do not pop the request stack if we are in debug mode and an + # exception happened. This will allow the debugger to still + # access the request object in the interactive shell. Furthermore + # the context can be force kept alive for the test client. + # See flask.testing for how this works. + if not self.request.environ.get('flask._preserve_context') and \ + (tb is None or not self.app.debug): + self.pop() diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/globals.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/globals.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +""" + flask.globals + ~~~~~~~~~~~~~ + + Defines all the global objects that are proxies to the current + active context. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from functools import partial +from werkzeug import LocalStack, LocalProxy + +def _lookup_object(name): + top = _request_ctx_stack.top + if top is None: + raise RuntimeError('working outside of request context') + return getattr(top, name) + +# context locals +_request_ctx_stack = LocalStack() +current_app = LocalProxy(partial(_lookup_object, 'app')) +request = LocalProxy(partial(_lookup_object, 'request')) +session = LocalProxy(partial(_lookup_object, 'session')) +g = LocalProxy(partial(_lookup_object, 'g')) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/helpers.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/helpers.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,500 @@ +# -*- coding: utf-8 -*- +""" + flask.helpers + ~~~~~~~~~~~~~ + + Implements various helpers. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +import os +import sys +import posixpath +import mimetypes +from time import time +from zlib import adler32 + +# 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 import Headers, wrap_file, cached_property +from werkzeug.exceptions import NotFound + +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 + + +# 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 + """ + if __debug__: + _assert_have_json() + 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) + + 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. + The endpoint is relative to the active module if modules are in use. + + Here are some examples: + + ==================== ======================= ============================= + Active Module Target Endpoint Target Function + ==================== ======================= ============================= + `None` ``'index'`` `index` of the application + `None` ``'.index'`` `index` of the application + ``'admin'`` ``'index'`` `index` of the `admin` module + any ``'.index'`` `index` of the application + any ``'admin.index'`` `index` of the `admin` module + ==================== ======================= ============================= + + Variable arguments that are unknown to the target endpoint are appended + to the generated URL as query arguments. + + For more information, head over to the :ref:`Quickstart `. + + :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. + """ + ctx = _request_ctx_stack.top + if '.' not in endpoint: + mod = ctx.request.module + if mod is not None: + endpoint = mod + '.' + endpoint + elif endpoint.startswith('.'): + endpoint = endpoint[1:] + external = values.pop('_external', False) + return ctx.url_adapter.build(endpoint, values, force_external=external) + + +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): + """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. + + Example usage: + + .. sourcecode:: html+jinja + + {% for category, msg in get_flashed_messages(with_categories=true) %} +

{{ msg }} + {% endfor %} + + .. versionchanged:: 0.3 + `with_categories` parameter added. + + :param with_categories: set to `True` to also receive categories. + """ + flashes = _request_ctx_stack.top.flashes + if flashes is None: + _request_ctx_stack.top.flashes = flashes = session.pop('_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. + + 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 not 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 + # current time to better support conditional requests. Werkzeug + # as of 0.6.1 will override this value however in the conditional + # response with the current time. This will be fixed in Werkzeug + # with a new release, however many WSGI servers will still emit + # a separate date header. + if mtime is not None: + rv.date = 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) & 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 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/') + 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 = posixpath.normpath(filename) + for sep in _os_alt_seps: + if sep in filename: + raise NotFound() + if os.path.isabs(filename) or filename.startswith('../'): + raise NotFound() + filename = os.path.join(directory, filename) + if not os.path.isfile(filename): + raise NotFound() + return send_file(filename, conditional=True, **options) + + +def _get_package_path(name): + """Returns the path to a package or cwd if that cannot be found.""" + try: + return os.path.abspath(os.path.dirname(sys.modules[name].__file__)) + except (KeyError, AttributeError): + return os.getcwd() + + +class _PackageBoundObject(object): + + def __init__(self, import_name): + #: The name of the package or module. Do not change this once + #: it was set by the constructor. + self.import_name = import_name + + #: Where is the app root located? + self.root_path = _get_package_path(self.import_name) + + @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 os.path.isdir(os.path.join(self.root_path, 'static')) + + @cached_property + def jinja_loader(self): + """The Jinja loader for this package bound object. + + .. versionadded:: 0.5 + """ + return FileSystemLoader(os.path.join(self.root_path, 'templates')) + + def send_static_file(self, filename): + """Function used internally to send static files from the static + folder to the browser. + + .. versionadded:: 0.5 + """ + return send_from_directory(os.path.join(self.root_path, 'static'), + filename) + + def open_resource(self, resource): + """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. + """ + return open(os.path.join(self.root_path, resource), 'rb') diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/logging.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/logging.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +""" + flask.logging + ~~~~~~~~~~~~~ + + Implements the logging support for Flask. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from __future__ import absolute_import + +from logging import getLogger, StreamHandler, Formatter, Logger, DEBUG + + +def create_logger(app): + """Creates a logger for the given application. This logger works + similar to a regular Python logger but changes the effective logging + level based on the application's debug flag. Furthermore this + function also removes all attached handlers in case there was a + logger with the log name before. + """ + + class DebugLogger(Logger): + def getEffectiveLevel(x): + return DEBUG if app.debug else Logger.getEffectiveLevel(x) + + class DebugHandler(StreamHandler): + def emit(x, record): + StreamHandler.emit(x, record) if app.debug else None + + handler = DebugHandler() + handler.setLevel(DEBUG) + handler.setFormatter(Formatter(app.debug_log_format)) + logger = getLogger(app.logger_name) + # just in case that was not a new logger, get rid of all the handlers + # already attached to it. + del logger.handlers[:] + logger.__class__ = DebugLogger + logger.addHandler(handler) + return logger diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/module.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/module.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +""" + flask.module + ~~~~~~~~~~~~ + + Implements a class that represents module blueprints. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from .helpers import _PackageBoundObject, _endpoint_from_view_func + + +def _register_module(module, static_path): + """Internal helper function that returns a function for recording + that registers the `send_static_file` function for the module on + the application if necessary. It also registers the module on + the application. + """ + def _register(state): + state.app.modules[module.name] = module + # do not register the rule if the static folder of the + # module is the same as the one from the application. + if state.app.root_path == module.root_path: + return + path = static_path + if path is None: + path = state.app.static_path + if state.url_prefix: + path = state.url_prefix + path + state.app.add_url_rule(path + '/', + endpoint='%s.static' % module.name, + view_func=module.send_static_file, + subdomain=state.subdomain) + return _register + + +class _ModuleSetupState(object): + + def __init__(self, app, url_prefix=None, subdomain=None): + self.app = app + self.url_prefix = url_prefix + self.subdomain = subdomain + + +class Module(_PackageBoundObject): + """Container object that enables pluggable applications. A module can + be used to organize larger applications. They represent blueprints that, + in combination with a :class:`Flask` object are used to create a large + application. + + A module is like an application bound to an `import_name`. Multiple + modules can share the same import names, but in that case a `name` has + to be provided to keep them apart. If different import names are used, + the rightmost part of the import name is used as name. + + Here's an example structure for a larger application:: + + /myapplication + /__init__.py + /views + /__init__.py + /admin.py + /frontend.py + + The `myapplication/__init__.py` can look like this:: + + from flask import Flask + from myapplication.views.admin import admin + from myapplication.views.frontend import frontend + + app = Flask(__name__) + app.register_module(admin, url_prefix='/admin') + app.register_module(frontend) + + And here's an example view module (`myapplication/views/admin.py`):: + + from flask import Module + + admin = Module(__name__) + + @admin.route('/') + def index(): + pass + + @admin.route('/login') + def login(): + pass + + For a gentle introduction into modules, checkout the + :ref:`working-with-modules` section. + + .. versionadded:: 0.5 + The `static_path` parameter was added and it's now possible for + modules to refer to their own templates and static files. See + :ref:`modules-and-resources` for more information. + + .. versionadded:: 0.6 + The `subdomain` parameter was added. + + :param import_name: the name of the Python package or module + implementing this :class:`Module`. + :param name: the internal short name for the module. Unless specified + the rightmost part of the import name + :param url_prefix: an optional string that is used to prefix all the + URL rules of this module. This can also be specified + when registering the module with the application. + :param subdomain: used to set the subdomain setting for URL rules that + do not have a subdomain setting set. + :param static_path: can be used to specify a different path for the + static files on the web. Defaults to ``/static``. + This does not affect the folder the files are served + *from*. + """ + + def __init__(self, import_name, name=None, url_prefix=None, + static_path=None, subdomain=None): + if name is None: + assert '.' in import_name, 'name required if package name ' \ + 'does not point to a submodule' + name = import_name.rsplit('.', 1)[1] + _PackageBoundObject.__init__(self, import_name) + self.name = name + self.url_prefix = url_prefix + self.subdomain = subdomain + self.view_functions = {} + self._register_events = [_register_module(self, static_path)] + + def route(self, rule, **options): + """Like :meth:`Flask.route` but for a module. The endpoint for the + :func:`url_for` function is prefixed with the name of the module. + """ + def decorator(f): + self.add_url_rule(rule, f.__name__, f, **options) + return f + return decorator + + def add_url_rule(self, rule, endpoint=None, view_func=None, **options): + """Like :meth:`Flask.add_url_rule` but for a module. The endpoint for + the :func:`url_for` function is prefixed with the name of the module. + + .. versionchanged:: 0.6 + The `endpoint` argument is now optional and will default to the + function name to consistent with the function of the same name + on the application object. + """ + def register_rule(state): + the_rule = rule + if state.url_prefix: + the_rule = state.url_prefix + rule + options.setdefault('subdomain', state.subdomain) + the_endpoint = endpoint + if the_endpoint is None: + the_endpoint = _endpoint_from_view_func(view_func) + state.app.add_url_rule(the_rule, '%s.%s' % (self.name, + the_endpoint), + view_func, **options) + self._record(register_rule) + + def endpoint(self, endpoint): + """Like :meth:`Flask.endpoint` but for a module.""" + def decorator(f): + self.view_functions[endpoint] = f + return f + return decorator + + def before_request(self, f): + """Like :meth:`Flask.before_request` but for a module. This function + is only executed before each request that is handled by a function of + that module. + """ + self._record(lambda s: s.app.before_request_funcs + .setdefault(self.name, []).append(f)) + return f + + def before_app_request(self, f): + """Like :meth:`Flask.before_request`. Such a function is executed + before each request, even if outside of a module. + """ + self._record(lambda s: s.app.before_request_funcs + .setdefault(None, []).append(f)) + return f + + def after_request(self, f): + """Like :meth:`Flask.after_request` but for a module. This function + is only executed after each request that is handled by a function of + that module. + """ + self._record(lambda s: s.app.after_request_funcs + .setdefault(self.name, []).append(f)) + return f + + def after_app_request(self, f): + """Like :meth:`Flask.after_request` but for a module. Such a function + is executed after each request, even if outside of the module. + """ + self._record(lambda s: s.app.after_request_funcs + .setdefault(None, []).append(f)) + return f + + def context_processor(self, f): + """Like :meth:`Flask.context_processor` but for a module. This + function is only executed for requests handled by a module. + """ + self._record(lambda s: s.app.template_context_processors + .setdefault(self.name, []).append(f)) + return f + + def app_context_processor(self, f): + """Like :meth:`Flask.context_processor` but for a module. Such a + function is executed each request, even if outside of the module. + """ + self._record(lambda s: s.app.template_context_processors + .setdefault(None, []).append(f)) + return f + + def app_errorhandler(self, code): + """Like :meth:`Flask.errorhandler` but for a module. This + handler is used for all requests, even if outside of the module. + + .. versionadded:: 0.4 + """ + def decorator(f): + self._record(lambda s: s.app.errorhandler(code)(f)) + return f + return decorator + + def _record(self, func): + self._register_events.append(func) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/session.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/session.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +""" + flask.session + ~~~~~~~~~~~~~ + + Implements cookie based sessions based on Werkzeug's secure cookie + system. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from werkzeug.contrib.securecookie import SecureCookie + + +class Session(SecureCookie): + """Expands the session with support for switching between permanent + and non-permanent sessions. + """ + + def _get_permanent(self): + return self.get('_permanent', False) + + def _set_permanent(self, value): + self['_permanent'] = bool(value) + + permanent = property(_get_permanent, _set_permanent) + del _get_permanent, _set_permanent + + +class _NullSession(Session): + """Class used to generate nicer error messages if sessions are not + available. Will still allow read-only access to the empty session + but fail on setting. + """ + + def _fail(self, *args, **kwargs): + raise RuntimeError('the session is unavailable because no secret ' + 'key was set. Set the secret_key on the ' + 'application to something unique and secret.') + __setitem__ = __delitem__ = clear = pop = popitem = \ + update = setdefault = _fail + del _fail diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/signals.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/signals.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +""" + flask.signals + ~~~~~~~~~~~~~ + + Implements signals based on blinker if available, otherwise + falls silently back to a noop + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +signals_available = False +try: + from blinker import Namespace + signals_available = True +except ImportError: + class Namespace(object): + def signal(self, name, doc=None): + return _FakeSignal(name, doc) + + class _FakeSignal(object): + """If blinker is unavailable, create a fake class with the same + interface that allows sending of signals but will fail with an + error on anything else. Instead of doing anything on send, it + will just ignore the arguments and do nothing instead. + """ + + def __init__(self, name, doc=None): + self.name = name + self.__doc__ = doc + def _fail(self, *args, **kwargs): + raise RuntimeError('signalling support is unavailable ' + 'because the blinker library is ' + 'not installed.') + send = lambda *a, **kw: None + connect = disconnect = has_receivers_for = receivers_for = \ + temporarily_connected_to = _fail + del _fail + +# the namespace for code signals. If you are not flask code, do +# not put signals in here. Create your own namespace instead. +_signals = Namespace() + + +# core signals. For usage examples grep the sourcecode or consult +# the API documentation in docs/api.rst as well as docs/signals.rst +template_rendered = _signals.signal('template-rendered') +request_started = _signals.signal('request-started') +request_finished = _signals.signal('request-finished') +got_request_exception = _signals.signal('got-request-exception') diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/templating.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/templating.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +""" + flask.templating + ~~~~~~~~~~~~~~~~ + + Implements the bridge to Jinja2. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import posixpath +from jinja2 import BaseLoader, TemplateNotFound + +from .globals import _request_ctx_stack +from .signals import template_rendered + + +def _default_template_ctx_processor(): + """Default template context processor. Injects `request`, + `session` and `g`. + """ + reqctx = _request_ctx_stack.top + return dict( + config=reqctx.app.config, + request=reqctx.request, + session=reqctx.session, + g=reqctx.g + ) + + +class _DispatchingJinjaLoader(BaseLoader): + """A loader that looks for templates in the application and all + the module folders. + """ + + def __init__(self, app): + self.app = app + + def get_source(self, environment, template): + template = posixpath.normpath(template) + if template.startswith('../'): + raise TemplateNotFound(template) + loader = None + try: + module, name = template.split('/', 1) + loader = self.app.modules[module].jinja_loader + except (ValueError, KeyError): + pass + # if there was a module and it has a loader, try this first + if loader is not None: + try: + return loader.get_source(environment, name) + except TemplateNotFound: + pass + # fall back to application loader if module failed + return self.app.jinja_loader.get_source(environment, template) + + def list_templates(self): + result = self.app.jinja_loader.list_templates() + for name, module in self.app.modules.iteritems(): + if module.jinja_loader is not None: + for template in module.jinja_loader.list_templates(): + result.append('%s/%s' % (name, template)) + return result + + +def _render(template, context, app): + """Renders the template and fires the signal""" + rv = template.render(context) + template_rendered.send(app, template=template, context=context) + return rv + + +def render_template(template_name, **context): + """Renders a template from the template folder with the given + context. + + :param template_name: the name of the template to be rendered + :param context: the variables that should be available in the + context of the template. + """ + ctx = _request_ctx_stack.top + ctx.app.update_template_context(context) + return _render(ctx.app.jinja_env.get_template(template_name), + context, ctx.app) + + +def render_template_string(source, **context): + """Renders a template from the given template source string + with the given context. + + :param template_name: the sourcecode of the template to be + rendered + :param context: the variables that should be available in the + context of the template. + """ + ctx = _request_ctx_stack.top + ctx.app.update_template_context(context) + return _render(ctx.app.jinja_env.from_string(source), + context, ctx.app) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/testing.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/testing.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +""" + flask.testing + ~~~~~~~~~~~~~ + + Implements test support helpers. This module is lazily imported + and usually not used in production environments. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from werkzeug import Client, EnvironBuilder +from flask import _request_ctx_stack + + +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. + """ + + preserve_context = context_preserved = False + + def open(self, *args, **kwargs): + if self.context_preserved: + _request_ctx_stack.pop() + self.context_preserved = False + 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 = 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, + as_tuple=as_tuple, + buffered=buffered, + follow_redirects=follow_redirects) + finally: + self.context_preserved = _request_ctx_stack.top is not old + + def __enter__(self): + self.preserve_context = True + return self + + def __exit__(self, exc_type, exc_value, tb): + self.preserve_context = False + if self.context_preserved: + _request_ctx_stack.pop() diff -r 543110450d50 -r fa1f7726b08b bundled/flask/flask/wrappers.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/flask/wrappers.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +""" + flask.wrappers + ~~~~~~~~~~~~~~ + + Implements the WSGI wrappers (request and response). + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from werkzeug import Request as RequestBase, Response as ResponseBase, \ + cached_property + +from .helpers import json, _assert_have_json +from .globals import _request_ctx_stack + + +class Request(RequestBase): + """The request object used by default in Flask. Remembers the + matched endpoint and view arguments. + + It is what ends up as :class:`~flask.request`. If you want to replace + the request object used you can subclass this and set + :attr:`~flask.Flask.request_class` to your subclass. + """ + + #: the internal URL rule that matched the request. This can be + #: useful to inspect which methods are allowed for the URL from + #: a before/after handler (``request.url_rule.methods``) etc. + #: + #: .. versionadded:: 0.6 + url_rule = None + + #: a dict of view arguments that matched the request. If an exception + #: happened when matching, this will be `None`. + view_args = None + + #: if matching the URL failed, this is the exception that will be + #: raised / was raised as part of the request handling. This is + #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or + #: something similar. + routing_exception = None + + @property + def max_content_length(self): + """Read-only view of the `MAX_CONTENT_LENGTH` config key.""" + ctx = _request_ctx_stack.top + if ctx is not None: + return ctx.app.config['MAX_CONTENT_LENGTH'] + + @property + def endpoint(self): + """The endpoint that matched the request. This in combination with + :attr:`view_args` can be used to reconstruct the same or a + modified URL. If an exception happened when matching, this will + be `None`. + """ + if self.url_rule is not None: + return self.url_rule.endpoint + + @property + def module(self): + """The name of the current module""" + if self.url_rule and '.' in self.url_rule.endpoint: + return self.url_rule.endpoint.rsplit('.', 1)[0] + + @cached_property + def json(self): + """If the mimetype is `application/json` this will contain the + parsed JSON data. + """ + if __debug__: + _assert_have_json() + if self.mimetype == 'application/json': + return json.loads(self.data) + + +class Response(ResponseBase): + """The response object that is used by default in Flask. Works like the + response object from Werkzeug but is set to have an HTML mimetype by + default. Quite often you don't have to create this object yourself because + :meth:`~flask.Flask.make_response` will take care of that for you. + + If you want to replace the response object used you can subclass this and + set :attr:`~flask.Flask.response_class` to your subclass. + """ + default_mimetype = 'text/html' diff -r 543110450d50 -r fa1f7726b08b bundled/flask/setup.py --- a/bundled/flask/setup.py Mon Jul 18 13:21:39 2011 -0400 +++ b/bundled/flask/setup.py Mon Jul 18 13:22:12 2011 -0400 @@ -38,8 +38,44 @@ `_ """ -from setuptools import setup +from setuptools import Command, setup + +class run_audit(Command): + """Audits source code using PyFlakes for following issues: + - Names which are used but not defined or used before they are defined. + - Names which are redefined without having been used. + """ + description = "Audit source code with PyFlakes" + user_options = [] + + def initialize_options(self): + all = None + + def finalize_options(self): + pass + def run(self): + import os, sys + try: + import pyflakes.scripts.pyflakes as flakes + except ImportError: + print "Audit requires PyFlakes installed in your system.""" + sys.exit(-1) + + dirs = ['flask', 'tests'] + # Add example directories + for dir in ['flaskr', 'jqueryexample', 'minitwit']: + dirs.append(os.path.join('examples', dir)) + # TODO: Add test subdirectories + warns = 0 + for dir in dirs: + for filename in os.listdir(dir): + if filename.endswith('.py') and filename != '__init__.py': + warns += flakes.checkPath(os.path.join(dir, filename)) + if warns > 0: + print ("Audit finished with total %d warnings." % warns) + else: + print ("No problems found in sourcecode.") def run_tests(): import os, sys @@ -50,7 +86,7 @@ setup( name='Flask', - version='0.4', + version='0.7', url='http://github.com/mitsuhiko/flask/', license='BSD', author='Armin Ronacher', @@ -58,7 +94,7 @@ description='A microframework based on Werkzeug, Jinja2 ' 'and good intentions', long_description=__doc__, - py_modules=['flask'], + packages=['flask'], zip_safe=False, platforms='any', install_requires=[ @@ -75,5 +111,6 @@ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ], + cmdclass={'audit': run_audit}, test_suite='__main__.run_tests' ) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/flask_tests.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/flask_tests.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1487 @@ +# -*- coding: utf-8 -*- +""" + Flask Tests + ~~~~~~~~~~~ + + Tests Flask itself. The majority of Flask is already tested + as part of Werkzeug. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +from __future__ import with_statement +import os +import re +import sys +import flask +import unittest +from threading import Thread +from logging import StreamHandler +from contextlib import contextmanager +from datetime import datetime +from werkzeug import parse_date, parse_options_header +from werkzeug.exceptions import NotFound +from jinja2 import TemplateNotFound +from cStringIO import StringIO + +example_path = os.path.join(os.path.dirname(__file__), '..', 'examples') +sys.path.append(os.path.join(example_path, 'flaskr')) +sys.path.append(os.path.join(example_path, 'minitwit')) + + +def has_encoding(name): + try: + import codecs + codecs.lookup(name) + return True + except LookupError: + return False + + +# config keys used for the ConfigTestCase +TEST_KEY = 'foo' +SECRET_KEY = 'devkey' + + +@contextmanager +def catch_warnings(): + """Catch warnings in a with block in a list""" + import warnings + 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 + + +class ContextTestCase(unittest.TestCase): + + def test_context_binding(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return 'Hello %s!' % flask.request.args['name'] + @app.route('/meh') + def meh(): + return flask.request.url + + with app.test_request_context('/?name=World'): + assert index() == 'Hello World!' + with app.test_request_context('/meh'): + assert meh() == 'http://localhost/meh' + assert flask._request_ctx_stack.top is None + + def test_manual_context_binding(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return 'Hello %s!' % flask.request.args['name'] + + ctx = app.test_request_context('/?name=World') + ctx.push() + assert index() == 'Hello World!' + ctx.pop() + try: + index() + except RuntimeError: + pass + else: + assert 0, '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('/') + assert flask.g.value == 42 + assert resp.data == 'Hello World!' + assert resp.status_code == 200 + + resp = c.get('/other') + assert not hasattr(flask.g, 'value') + assert 'Internal Server Error' in resp.data + assert resp.status_code == 500 + flask.g.value = 23 + + try: + flask.g.value + except (AttributeError, RuntimeError): + pass + else: + raise AssertionError('some kind of exception expected') + + +class BasicFunctionalityTestCase(unittest.TestCase): + + def test_options_work(self): + app = flask.Flask(__name__) + @app.route('/', methods=['GET', 'POST']) + def index(): + return 'Hello World' + rv = app.test_client().open('/', method='OPTIONS') + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + assert rv.data == '' + + def test_options_on_multiple_rules(self): + app = flask.Flask(__name__) + @app.route('/', methods=['GET', 'POST']) + def index(): + return 'Hello World' + @app.route('/', methods=['PUT']) + def index_put(): + return 'Aha!' + rv = app.test_client().open('/', method='OPTIONS') + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + + def test_request_dispatching(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return flask.request.method + @app.route('/more', methods=['GET', 'POST']) + def more(): + return flask.request.method + + c = app.test_client() + assert c.get('/').data == 'GET' + rv = c.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + rv = c.head('/') + assert rv.status_code == 200 + assert not rv.data # head truncates + assert c.post('/more').data == 'POST' + assert c.get('/more').data == 'GET' + rv = c.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + + def test_url_mapping(self): + app = flask.Flask(__name__) + def index(): + return flask.request.method + def more(): + return flask.request.method + + app.add_url_rule('/', 'index', index) + app.add_url_rule('/more', 'more', more, methods=['GET', 'POST']) + + c = app.test_client() + assert c.get('/').data == 'GET' + rv = c.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + rv = c.head('/') + assert rv.status_code == 200 + assert not rv.data # head truncates + assert c.post('/more').data == 'POST' + assert c.get('/more').data == 'GET' + rv = c.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + + def test_werkzeug_routing(self): + from werkzeug.routing import Submount, Rule + app = flask.Flask(__name__) + app.url_map.add(Submount('/foo', [ + Rule('/bar', endpoint='bar'), + Rule('/', endpoint='index') + ])) + def bar(): + return 'bar' + def index(): + return 'index' + app.view_functions['bar'] = bar + app.view_functions['index'] = index + + c = app.test_client() + assert c.get('/foo/').data == 'index' + assert c.get('/foo/bar').data == 'bar' + + def test_endpoint_decorator(self): + from werkzeug.routing import Submount, Rule + app = flask.Flask(__name__) + app.url_map.add(Submount('/foo', [ + Rule('/bar', endpoint='bar'), + Rule('/', endpoint='index') + ])) + + @app.endpoint('bar') + def bar(): + return 'bar' + + @app.endpoint('index') + def index(): + return 'index' + + c = app.test_client() + assert c.get('/foo/').data == 'index' + assert c.get('/foo/bar').data == 'bar' + + def test_session(self): + app = flask.Flask(__name__) + app.secret_key = 'testkey' + @app.route('/set', methods=['POST']) + def set(): + flask.session['value'] = flask.request.form['value'] + return 'value set' + @app.route('/get') + def get(): + return flask.session['value'] + + c = app.test_client() + assert c.post('/set', data={'value': '42'}).data == 'value set' + assert c.get('/get').data == '42' + + def test_session_using_server_name(self): + app = flask.Flask(__name__) + app.config.update( + SECRET_KEY='foo', + SERVER_NAME='example.com' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + rv = app.test_client().get('/', 'http://example.com/') + assert 'domain=.example.com' in rv.headers['set-cookie'].lower() + assert 'httponly' in rv.headers['set-cookie'].lower() + + def test_missing_session(self): + app = flask.Flask(__name__) + def expect_exception(f, *args, **kwargs): + try: + f(*args, **kwargs) + except RuntimeError, e: + assert e.args and 'session is unavailable' in e.args[0] + else: + assert False, 'expected exception' + with app.test_request_context(): + assert flask.session.get('missing_key') is None + expect_exception(flask.session.__setitem__, 'foo', 42) + expect_exception(flask.session.pop, 'foo') + + def test_session_expiration(self): + permanent = True + app = flask.Flask(__name__) + app.secret_key = 'testkey' + @app.route('/') + def index(): + flask.session['test'] = 42 + flask.session.permanent = permanent + return '' + + @app.route('/test') + def test(): + return unicode(flask.session.permanent) + + client = app.test_client() + rv = client.get('/') + assert 'set-cookie' in rv.headers + match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) + expires = parse_date(match.group()) + expected = datetime.utcnow() + app.permanent_session_lifetime + assert expires.year == expected.year + assert expires.month == expected.month + assert expires.day == expected.day + + rv = client.get('/test') + assert rv.data == 'True' + + permanent = False + rv = app.test_client().get('/') + assert 'set-cookie' in rv.headers + match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) + assert match is None + + def test_flashes(self): + app = flask.Flask(__name__) + app.secret_key = 'testkey' + + with app.test_request_context(): + assert not flask.session.modified + flask.flash('Zap') + flask.session.modified = False + flask.flash('Zip') + assert flask.session.modified + assert list(flask.get_flashed_messages()) == ['Zap', 'Zip'] + + def test_extended_flashing(self): + app = flask.Flask(__name__) + app.secret_key = 'testkey' + + @app.route('/') + def index(): + flask.flash(u'Hello World') + flask.flash(u'Hello World', 'error') + flask.flash(flask.Markup(u'Testing'), 'warning') + return '' + + @app.route('/test') + def test(): + messages = flask.get_flashed_messages(with_categories=True) + assert len(messages) == 3 + assert messages[0] == ('message', u'Hello World') + assert messages[1] == ('error', u'Hello World') + assert messages[2] == ('warning', flask.Markup(u'Testing')) + return '' + messages = flask.get_flashed_messages() + assert len(messages) == 3 + assert messages[0] == u'Hello World' + assert messages[1] == u'Hello World' + assert messages[2] == flask.Markup(u'Testing') + + c = app.test_client() + c.get('/') + c.get('/test') + + def test_request_processing(self): + app = flask.Flask(__name__) + evts = [] + @app.before_request + def before_request(): + evts.append('before') + @app.after_request + def after_request(response): + response.data += '|after' + evts.append('after') + return response + @app.route('/') + def index(): + assert 'before' in evts + assert 'after' not in evts + return 'request' + assert 'after' not in evts + rv = app.test_client().get('/').data + assert 'after' in evts + assert rv == 'request|after' + + def test_after_request_errors(self): + app = flask.Flask(__name__) + called = [] + @app.after_request + def after_request(response): + called.append(True) + return response + @app.route('/') + def fails(): + 1/0 + rv = app.test_client().get('/') + assert rv.status_code == 500 + assert 'Internal Server Error' in rv.data + assert len(called) == 1 + + def test_after_request_handler_error(self): + called = [] + app = flask.Flask(__name__) + @app.after_request + def after_request(response): + called.append(True) + 1/0 + return response + @app.route('/') + def fails(): + 1/0 + rv = app.test_client().get('/') + assert rv.status_code == 500 + assert 'Internal Server Error' in rv.data + assert len(called) == 1 + + def test_before_after_request_order(self): + called = [] + app = flask.Flask(__name__) + @app.before_request + def before1(): + called.append(1) + @app.before_request + def before2(): + called.append(2) + @app.after_request + def after1(response): + called.append(4) + return response + @app.after_request + def after2(response): + called.append(3) + return response + @app.route('/') + def index(): + return '42' + rv = app.test_client().get('/') + assert rv.data == '42' + assert called == [1, 2, 3, 4] + + def test_error_handling(self): + app = flask.Flask(__name__) + @app.errorhandler(404) + def not_found(e): + return 'not found', 404 + @app.errorhandler(500) + def internal_server_error(e): + return 'internal server error', 500 + @app.route('/') + def index(): + flask.abort(404) + @app.route('/error') + def error(): + 1 // 0 + c = app.test_client() + rv = c.get('/') + assert rv.status_code == 404 + assert rv.data == 'not found' + rv = c.get('/error') + assert rv.status_code == 500 + assert 'internal server error' == rv.data + + def test_response_creation(self): + app = flask.Flask(__name__) + @app.route('/unicode') + def from_unicode(): + return u'Hällo Wörld' + @app.route('/string') + def from_string(): + return u'Hällo Wörld'.encode('utf-8') + @app.route('/args') + def from_tuple(): + return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain' + c = app.test_client() + assert c.get('/unicode').data == u'Hällo Wörld'.encode('utf-8') + assert c.get('/string').data == u'Hällo Wörld'.encode('utf-8') + rv = c.get('/args') + assert rv.data == 'Meh' + assert rv.headers['X-Foo'] == 'Testing' + assert rv.status_code == 400 + assert rv.mimetype == 'text/plain' + + def test_make_response(self): + app = flask.Flask(__name__) + with app.test_request_context(): + rv = flask.make_response() + assert rv.status_code == 200 + assert rv.data == '' + assert rv.mimetype == 'text/html' + + rv = flask.make_response('Awesome') + assert rv.status_code == 200 + assert rv.data == 'Awesome' + assert rv.mimetype == 'text/html' + + rv = flask.make_response('W00t', 404) + assert rv.status_code == 404 + assert rv.data == 'W00t' + assert rv.mimetype == 'text/html' + + def test_url_generation(self): + app = flask.Flask(__name__) + @app.route('/hello/', methods=['POST']) + def hello(): + pass + with app.test_request_context(): + assert flask.url_for('hello', name='test x') == '/hello/test%20x' + assert flask.url_for('hello', name='test x', _external=True) \ + == 'http://localhost/hello/test%20x' + + def test_custom_converters(self): + from werkzeug.routing import BaseConverter + class ListConverter(BaseConverter): + def to_python(self, value): + return value.split(',') + def to_url(self, value): + base_to_url = super(ListConverter, self).to_url + return ','.join(base_to_url(x) for x in value) + app = flask.Flask(__name__) + app.url_map.converters['list'] = ListConverter + @app.route('/') + def index(args): + return '|'.join(args) + c = app.test_client() + assert c.get('/1,2,3').data == '1|2|3' + + def test_static_files(self): + app = flask.Flask(__name__) + rv = app.test_client().get('/static/index.html') + assert rv.status_code == 200 + assert rv.data.strip() == '

Hello World!

' + with app.test_request_context(): + assert flask.url_for('static', filename='index.html') \ + == '/static/index.html' + + def test_none_response(self): + app = flask.Flask(__name__) + @app.route('/') + def test(): + return None + try: + app.test_client().get('/') + except ValueError, e: + assert str(e) == 'View function did not return a response' + pass + else: + assert "Expected ValueError" + + def test_request_locals(self): + self.assertEqual(repr(flask.g), '') + self.assertFalse(flask.g) + + def test_proper_test_request_context(self): + app = flask.Flask(__name__) + app.config.update( + SERVER_NAME='localhost.localdomain:5000' + ) + @app.route('/') + def index(): + return None + + @app.route('/', subdomain='foo') + def sub(): + return None + + with app.test_request_context('/'): + assert flask.url_for('index', _external=True) == 'http://localhost.localdomain:5000/' + + with app.test_request_context('/'): + assert flask.url_for('sub', _external=True) == 'http://foo.localhost.localdomain:5000/' + + try: + with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}): + pass + except Exception, e: + assert isinstance(e, ValueError) + assert str(e) == "the server name provided " + \ + "('localhost.localdomain:5000') does not match the " + \ + "server name from the WSGI environment ('localhost')", str(e) + + try: + app.config.update(SERVER_NAME='localhost') + with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost'}): + pass + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + app.config.update(SERVER_NAME='localhost:80') + with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}): + pass + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + def test_test_app_proper_environ(self): + app = flask.Flask(__name__) + app.config.update( + SERVER_NAME='localhost.localdomain:5000' + ) + @app.route('/') + def index(): + return 'Foo' + + @app.route('/', subdomain='foo') + def subdomain(): + return 'Foo SubDomain' + + try: + rv = app.test_client().get('/') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + rv = app.test_client().get('/', 'http://localhost.localdomain:5000') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + rv = app.test_client().get('/', 'https://localhost.localdomain:5000') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + app.config.update(SERVER_NAME='localhost.localdomain') + rv = app.test_client().get('/', 'https://localhost.localdomain') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + app.config.update(SERVER_NAME='localhost.localdomain:443') + rv = app.test_client().get('/', 'https://localhost.localdomain') + assert rv.data == 'Foo' + except ValueError, e: + assert str(e) == "the server name provided " + \ + "('localhost.localdomain:443') does not match the " + \ + "server name from the WSGI environment ('localhost.localdomain')", str(e) + + try: + app.config.update(SERVER_NAME='localhost.localdomain') + app.test_client().get('/', 'http://foo.localhost') + except ValueError, e: + assert str(e) == "the server name provided " + \ + "('localhost.localdomain') does not match the " + \ + "server name from the WSGI environment ('foo.localhost')", str(e) + + try: + rv = app.test_client().get('/', 'http://foo.localhost.localdomain') + assert rv.data == 'Foo SubDomain' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + def test_exception_propagation(self): + def apprunner(configkey): + app = flask.Flask(__name__) + @app.route('/') + def index(): + 1/0 + c = app.test_client() + if config_key is not None: + app.config[config_key] = True + try: + resp = c.get('/') + except Exception: + pass + else: + self.fail('expected exception') + else: + assert c.get('/').status_code == 500 + + # we have to run this test in an isolated thread because if the + # debug flag is set to true and an exception happens the context is + # not torn down. This causes other tests that run after this fail + # when they expect no exception on the stack. + for config_key in 'TESTING', 'PROPAGATE_EXCEPTIONS', 'DEBUG', None: + t = Thread(target=apprunner, args=(config_key,)) + t.start() + t.join() + + +class JSONTestCase(unittest.TestCase): + + 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) + assert rv.mimetype == 'application/json' + assert 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') + assert rv.data == '3' + + def test_template_escaping(self): + app = flask.Flask(__name__) + render = flask.render_template_string + with app.test_request_context(): + rv = render('{{ ""|tojson|safe }}') + assert rv == '"<\\/script>"' + rv = render('{{ "<\0/script>"|tojson|safe }}') + assert 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')) + assert rv.status_code == 200 + assert rv.data == u'정상처리'.encode('utf-8') + + if not has_encoding('euc-kr'): + test_modified_url_encoding = None + + +class TemplatingTestCase(unittest.TestCase): + + 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('/') + assert rv.data == '

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('/') + assert 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') + assert rv.data.split() == ['42', '23', 'False', 'aha'] + + def test_escaping(self): + text = '

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() + assert lines == [ + '<p>Hello World!', + '

Hello World!', + '

Hello World!', + '

Hello World!', + '<p>Hello World!', + '

Hello World!' + ] + + def test_no_escaping(self): + app = flask.Flask(__name__) + with app.test_request_context(): + assert flask.render_template_string('{{ foo }}', + foo='') == '' + assert flask.render_template('mail.txt', foo='') \ + == ' Mail' + + def test_macros(self): + app = flask.Flask(__name__) + with app.test_request_context(): + macro = flask.get_template_attribute('_macro.html', 'hello') + assert macro('World') == 'Hello World!' + + def test_template_filter(self): + app = flask.Flask(__name__) + @app.template_filter() + def my_reverse(s): + return s[::-1] + assert 'my_reverse' in app.jinja_env.filters.keys() + assert app.jinja_env.filters['my_reverse'] == my_reverse + assert 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] + assert 'strrev' in app.jinja_env.filters.keys() + assert app.jinja_env.filters['strrev'] == my_reverse + assert 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('/') + assert 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('/') + assert rv.data == 'dcba' + + +class ModuleTestCase(unittest.TestCase): + + 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() + assert c.get('/').data == 'the index' + assert c.get('/admin/').data == 'admin index' + assert c.get('/admin/login').data == 'admin login' + assert c.get('/admin/logout').data == 'admin logout' + + 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('/') + assert rv.data == 'Awesome' + with app.test_request_context(): + assert flask.url_for('frontend.index') == '/' + + 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() + + assert c.get('/').data == 'the index' + assert catched == ['before-app', 'after-app'] + del catched[:] + + assert c.get('/admin/').data == 'the admin' + assert catched == ['before-app', 'before-admin', + 'after-admin', 'after-app'] + + 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() + assert c.get('/').data == '13' + assert c.get('/admin/').data == '123' + + 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') + assert app.test_client().get('/admin/').data == '42' + + 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('/') + assert rv.status_code == 404 + assert rv.data == 'not found' + rv = c.get('/error') + assert rv.status_code == 500 + assert 'internal server error' == rv.data + + def test_templates_and_static(self): + from moduleapp import app + c = app.test_client() + + rv = c.get('/') + assert rv.data == 'Hello from the Frontend' + rv = c.get('/admin/') + assert rv.data == 'Hello from the Admin' + rv = c.get('/admin/index2') + assert rv.data == 'Hello from the Admin' + rv = c.get('/admin/static/test.txt') + assert rv.data.strip() == 'Admin File' + rv = c.get('/admin/static/css/test.css') + assert rv.data.strip() == '/* nested file */' + + with app.test_request_context(): + assert 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: + assert e.name == 'missing.html' + else: + assert 0, 'expected exception' + + with flask.Flask(__name__).test_request_context(): + assert flask.render_template('nested/nested.txt') == 'I\'m nested' + + def test_safe_access(self): + from moduleapp import app + + with app.test_request_context(): + f = app.view_functions['admin.static'] + + try: + f('/etc/passwd') + except NotFound: + pass + else: + assert 0, 'expected exception' + try: + f('../__init__.py') + except NotFound: + pass + else: + 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: + assert 0, 'expected exception' + finally: + os.path = old_path + + def test_endpoint_decorator(self): + from werkzeug.routing import Submount, Rule + from flask import Module + + app = flask.Flask(__name__) + 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() + assert c.get('/foo/').data == 'index' + assert c.get('/foo/bar').data == 'bar' + + +class SendfileTestCase(unittest.TestCase): + + def test_send_file_regular(self): + app = flask.Flask(__name__) + with app.test_request_context(): + rv = flask.send_file('static/index.html') + assert rv.direct_passthrough + assert rv.mimetype == 'text/html' + with app.open_resource('static/index.html') as f: + assert 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') + assert rv.direct_passthrough + assert 'x-sendfile' in rv.headers + assert rv.headers['x-sendfile'] == \ + os.path.join(app.root_path, 'static/index.html') + assert 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: + assert rv.data == f.read() + assert rv.mimetype == 'text/html' + # mimetypes + etag + assert 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) + assert rv.mimetype == 'text/html' + assert 'x-sendfile' in rv.headers + assert rv.headers['x-sendfile'] == \ + os.path.join(app.root_path, 'static/index.html') + # mimetypes + etag + assert 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) + assert rv.data == 'Test' + assert rv.mimetype == 'application/octet-stream' + # etags + assert len(captured) == 1 + with catch_warnings() as captured: + f = StringIO('Test') + rv = flask.send_file(f, mimetype='text/plain') + assert rv.data == 'Test' + assert rv.mimetype == 'text/plain' + # etags + assert 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) + assert 'x-sendfile' not in rv.headers + # etags + assert 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']) + assert value == 'attachment' + # mimetypes + etag + assert len(captured) == 2 + + with app.test_request_context(): + assert options['filename'] == 'index.html' + rv = flask.send_file('static/index.html', as_attachment=True) + value, options = parse_options_header(rv.headers['Content-Disposition']) + assert value == 'attachment' + assert 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) + assert rv.mimetype == 'text/plain' + value, options = parse_options_header(rv.headers['Content-Disposition']) + assert value == 'attachment' + assert options['filename'] == 'index.txt' + + +class LoggingTestCase(unittest.TestCase): + + def test_logger_cache(self): + app = flask.Flask(__name__) + logger1 = app.logger + assert app.logger is logger1 + assert logger1.name == __name__ + app.logger_name = __name__ + '/test_logger_cache' + 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 + c = app.test_client() + + with catch_stderr() as err: + c.get('/') + out = err.getvalue() + assert 'WARNING in flask_tests [' in out + assert 'flask_tests.py' in out + assert 'the standard library is dead' in out + assert 'this is a debug statement' in out + + with catch_stderr() as err: + try: + c.get('/exc') + except ZeroDivisionError: + pass + else: + 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('/') + assert rv.status_code == 500 + assert 'Internal Server Error' in rv.data + + err = out.getvalue() + assert 'Exception on / [GET]' in err + assert 'Traceback (most recent call last):' in err + assert '1/0' in err + 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('/') + assert rv.status_code == 500 + assert rv.data == 'Hello Server Error' + + +class ConfigTestCase(unittest.TestCase): + + def common_object_test(self, app): + assert app.secret_key == 'devkey' + assert app.config['TEST_KEY'] == 'foo' + assert 'ConfigTestCase' not in app.config + + def test_config_from_file(self): + app = flask.Flask(__name__) + app.config.from_pyfile('flask_tests.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): + import os + env = os.environ + try: + os.environ = {} + app = flask.Flask(__name__) + try: + app.config.from_envvar('FOO_SETTINGS') + except RuntimeError, e: + assert "'FOO_SETTINGS' is not set" in str(e) + else: + assert 0, 'expected exception' + not app.config.from_envvar('FOO_SETTINGS', silent=True) + + os.environ = {'FOO_SETTINGS': 'flask_tests.py'} + 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) + assert msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):') + assert msg.endswith("missing.cfg'") + else: + assert 0, 'expected config' + + +class SubdomainTestCase(unittest.TestCase): + + def test_basic_support(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'localhost' + @app.route('/') + def normal_index(): + return 'normal index' + @app.route('/', subdomain='test') + def test_index(): + return 'test index' + + c = app.test_client() + rv = c.get('/', 'http://localhost/') + assert rv.data == 'normal index' + + rv = c.get('/', 'http://test.localhost/') + assert rv.data == 'test index' + + def test_module_static_path_subdomain(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'example.com' + from subdomaintestmodule import mod + app.register_module(mod) + c = app.test_client() + rv = c.get('/static/hello.txt', 'http://foo.example.com/') + assert rv.data.strip() == 'Hello Subdomain' + + def test_subdomain_matching(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'localhost' + @app.route('/', subdomain='') + def index(user): + return 'index for %s' % user + + c = app.test_client() + rv = c.get('/', 'http://mitsuhiko.localhost/') + assert rv.data == 'index for mitsuhiko' + + def test_module_subdomain_support(self): + app = flask.Flask(__name__) + mod = flask.Module(__name__, 'test', subdomain='testing') + app.config['SERVER_NAME'] = 'localhost' + + @mod.route('/test') + def test(): + return 'Test' + + @mod.route('/outside', subdomain='xtesting') + def bar(): + return 'Outside' + + app.register_module(mod) + + c = app.test_client() + rv = c.get('/test', 'http://testing.localhost/') + assert rv.data == 'Test' + rv = c.get('/outside', 'http://xtesting.localhost/') + assert rv.data == 'Outside' + + +class TestSignals(unittest.TestCase): + + 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('/') + assert len(recorded) == 1 + template, context = recorded[0] + assert template.name == 'simple_template.html' + assert 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): + assert 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('/') + assert rv.data == 'stuff' + + assert 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: + assert app.test_client().get('/').status_code == 500 + assert len(recorded) == 1 + assert isinstance(recorded[0], ZeroDivisionError) + finally: + flask.got_request_exception.disconnect(record, app) + + +def suite(): + from minitwit_tests import MiniTwitTestCase + from flaskr_tests import FlaskrTestCase + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(ContextTestCase)) + suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase)) + suite.addTest(unittest.makeSuite(TemplatingTestCase)) + suite.addTest(unittest.makeSuite(ModuleTestCase)) + suite.addTest(unittest.makeSuite(SendfileTestCase)) + suite.addTest(unittest.makeSuite(LoggingTestCase)) + suite.addTest(unittest.makeSuite(ConfigTestCase)) + suite.addTest(unittest.makeSuite(SubdomainTestCase)) + if flask.json_available: + suite.addTest(unittest.makeSuite(JSONTestCase)) + if flask.signals_available: + suite.addTest(unittest.makeSuite(TestSignals)) + suite.addTest(unittest.makeSuite(MiniTwitTestCase)) + suite.addTest(unittest.makeSuite(FlaskrTestCase)) + return suite + + +if __name__ == '__main__': + unittest.main(defaultTest='suite') diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/flaskext_test.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/flaskext_test.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,311 @@ +# -*- coding: utf-8 -*- +""" + Flask Extension Tests + ~~~~~~~~~~~~~~~~~~~~~ + + Tests the Flask extensions. + + :copyright: (c) 2010 by Ali Afshar. + :license: BSD, see LICENSE for more details. +""" + +from __future__ import with_statement + +import os +import sys +import shutil +import urllib2 +import tempfile +import subprocess +import argparse + +from flask import json + +from setuptools.package_index import PackageIndex +from setuptools.archive_util import unpack_archive + +flask_svc_url = 'http://flask.pocoo.org/extensions/' + + +# OS X has awful paths when using mkstemp or gettempdir(). I don't +# care about security or clashes here, so pick something that is +# actually rememberable. +if sys.platform == 'darwin': + _tempdir = '/private/tmp' +else: + _tempdir = tempfile.gettempdir() +tdir = _tempdir + '/flaskext-test' +flaskdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + + +# virtualenv hack *cough* +os.environ['PYTHONDONTWRITEBYTECODE'] = '' + + +RESULT_TEMPATE = u'''\ + +Flask-Extension Test Results + +

Flask-Extension Test Results

+

+ This page contains the detailed test results for the test run of + all {{ 'approved' if approved }} Flask extensions. +

Summary

+ + + + + + + {%- for result in results %} + {% set outcome = 'success' if result.success else 'failed' %} + + + {%- endfor %} + +
Extension + Version + Author + License + Outcome + {%- for iptr, _ in results[0].logs|dictsort %} + {{ iptr }} + {%- endfor %} +
{{ result.name }} + {{ result.version }} + {{ result.author }} + {{ result.license }} + {{ outcome }} + {%- for iptr, _ in result.logs|dictsort %} + see log + {%- endfor %} +
+

Test Logs

+

Detailed test logs for all tests on all platforms: +{%- for result in results %} + {%- for iptr, log in result.logs|dictsort %} +

+ {{ result.name }} - {{ result.version }} [{{ iptr }}]

+
{{ log }}
+ {%- endfor %} +{%- endfor %} +''' + + +def log(msg, *args): + print '[EXTTEST]', msg % args + + +class TestResult(object): + + def __init__(self, name, folder, statuscode, interpreters): + intrptr = os.path.join(folder, '.tox/%s/bin/python' + % interpreters[0]) + self.statuscode = statuscode + self.folder = folder + self.success = statuscode == 0 + + def fetch(field): + try: + c = subprocess.Popen([intrptr, 'setup.py', + '--' + field], cwd=folder, + stdout=subprocess.PIPE) + return c.communicate()[0].strip() + except OSError: + return '?' + self.name = name + self.license = fetch('license') + self.author = fetch('author') + self.version = fetch('version') + + self.logs = {} + for interpreter in interpreters: + logfile = os.path.join(folder, '.tox/%s/log/test.log' + % interpreter) + if os.path.isfile(logfile): + self.logs[interpreter] = open(logfile).read() + else: + self.logs[interpreter] = '' + + +def create_tdir(): + try: + shutil.rmtree(tdir) + except Exception: + pass + os.mkdir(tdir) + + +def package_flask(): + distfolder = tdir + '/.flask-dist' + c = subprocess.Popen(['python', 'setup.py', 'sdist', '--formats=gztar', + '--dist', distfolder], cwd=flaskdir) + c.wait() + return os.path.join(distfolder, os.listdir(distfolder)[0]) + + +def get_test_command(checkout_dir): + if os.path.isfile(checkout_dir + '/Makefile'): + return 'make test' + return 'python setup.py test' + + +def fetch_extensions_list(): + req = urllib2.Request(flask_svc_url, headers={'accept':'application/json'}) + d = urllib2.urlopen(req).read() + data = json.loads(d) + for ext in data['extensions']: + yield ext + + +def checkout_extension(name): + log('Downloading extension %s to temporary folder', name) + root = os.path.join(tdir, name) + os.mkdir(root) + checkout_path = PackageIndex().download(name, root) + + unpack_archive(checkout_path, root) + path = None + for fn in os.listdir(root): + path = os.path.join(root, fn) + if os.path.isdir(path): + break + log('Downloaded to %s', path) + return path + + +tox_template = """[tox] +envlist=%(env)s + +[testenv] +deps= + %(deps)s + distribute + py +commands=bash flaskext-runtest.sh {envlogdir}/test.log +downloadcache=%(cache)s +""" + + +def create_tox_ini(checkout_path, interpreters, flask_dep): + tox_path = os.path.join(checkout_path, 'tox-flask-test.ini') + if not os.path.exists(tox_path): + with open(tox_path, 'w') as f: + f.write(tox_template % { + 'env': ','.join(interpreters), + 'cache': tdir, + 'deps': flask_dep + }) + return tox_path + + +def iter_extensions(only_approved=True): + for ext in fetch_extensions_list(): + if ext['approved'] or not only_approved: + yield ext['name'] + + +def test_extension(name, interpreters, flask_dep): + checkout_path = checkout_extension(name) + log('Running tests with tox in %s', checkout_path) + + # figure out the test command and write a wrapper script. We + # can't write that directly into the tox ini because tox does + # not invoke the command from the shell so we have no chance + # to pipe the output into a logfile. The /dev/null hack is + # to trick py.test (if used) into not guessing widths from the + # invoking terminal. + test_command = get_test_command(checkout_path) + log('Test command: %s', test_command) + f = open(checkout_path + '/flaskext-runtest.sh', 'w') + f.write(test_command + ' &> "$1" < /dev/null\n') + f.close() + + # if there is a tox.ini, remove it, it will cause troubles + # for us. Remove it if present, we are running tox ourselves + # afterall. + + create_tox_ini(checkout_path, interpreters, flask_dep) + rv = subprocess.call(['tox', '-c', 'tox-flask-test.ini'], cwd=checkout_path) + return TestResult(name, checkout_path, rv, interpreters) + + +def run_tests(extensions, interpreters): + results = {} + create_tdir() + log('Packaging Flask') + flask_dep = package_flask() + log('Running extension tests') + log('Temporary Environment: %s', tdir) + for name in extensions: + log('Testing %s', name) + result = test_extension(name, interpreters, flask_dep) + if result.success: + log('Extension test succeeded') + else: + log('Extension test failed') + results[name] = result + return results + + +def render_results(results, approved): + from jinja2 import Template + items = results.values() + items.sort(key=lambda x: x.name.lower()) + rv = Template(RESULT_TEMPATE, autoescape=True).render(results=items, + approved=approved) + fd, filename = tempfile.mkstemp(suffix='.html') + os.fdopen(fd, 'w').write(rv.encode('utf-8') + '\n') + return filename + + +def main(): + parser = argparse.ArgumentParser(description='Runs Flask extension tests') + parser.add_argument('--all', dest='all', action='store_true', + help='run against all extensions, not just approved') + parser.add_argument('--browse', dest='browse', action='store_true', + help='show browser with the result summary') + parser.add_argument('--env', dest='env', default='py25,py26,py27', + help='the tox environments to run against') + parser.add_argument('--extension=', dest='extension', default=None, + help='tests a single extension') + args = parser.parse_args() + + if args.extension is not None: + only_approved = False + extensions = [args.extension] + else: + only_approved = not args.all + extensions = iter_extensions(only_approved) + + results = run_tests(extensions, [x.strip() for x in args.env.split(',')]) + filename = render_results(results, only_approved) + if args.browse: + import webbrowser + webbrowser.open('file:///' + filename.lstrip('/')) + print 'Results written to', filename + + +if __name__ == '__main__': + main() diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/moduleapp/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/moduleapp/__init__.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,7 @@ +from flask import Flask + +app = Flask(__name__) +from moduleapp.apps.admin import admin +from moduleapp.apps.frontend import frontend +app.register_module(admin) +app.register_module(frontend) diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/moduleapp/apps/__init__.py diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/moduleapp/apps/admin/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/moduleapp/apps/admin/__init__.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,14 @@ +from flask import Module, render_template + + +admin = Module(__name__, url_prefix='/admin') + + +@admin.route('/') +def index(): + return render_template('admin/index.html') + + +@admin.route('/index2') +def index2(): + return render_template('./admin/index.html') diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/moduleapp/apps/admin/static/css/test.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/moduleapp/apps/admin/static/css/test.css Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +/* nested file */ diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/moduleapp/apps/admin/static/test.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/moduleapp/apps/admin/static/test.txt Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +Admin File diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/moduleapp/apps/admin/templates/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/moduleapp/apps/admin/templates/index.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +Hello from the Admin diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/moduleapp/apps/frontend/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/moduleapp/apps/frontend/__init__.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,9 @@ +from flask import Module, render_template + + +frontend = Module(__name__) + + +@frontend.route('/') +def index(): + return render_template('frontend/index.html') diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/moduleapp/apps/frontend/templates/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/moduleapp/apps/frontend/templates/index.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +Hello from the Frontend diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/static/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/static/index.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +

Hello World!

diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/subdomaintestmodule/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/subdomaintestmodule/__init__.py Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,4 @@ +from flask import Module + + +mod = Module(__name__, 'foo', subdomain='foo') diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/subdomaintestmodule/static/hello.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/subdomaintestmodule/static/hello.txt Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +Hello Subdomain diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/templates/_macro.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/templates/_macro.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +{% macro hello(name) %}Hello {{ name }}!{% endmacro %} diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/templates/context_template.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/templates/context_template.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +

{{ value }}|{{ injected_value }} diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/templates/escaping_template.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/templates/escaping_template.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,6 @@ +{{ text }} +{{ html }} +{% autoescape false %}{{ text }} +{{ html }}{% endautoescape %} +{% autoescape true %}{{ text }} +{{ html }}{% endautoescape %} diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/templates/mail.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/templates/mail.txt Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +{{ foo}} Mail diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/templates/nested/nested.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/templates/nested/nested.txt Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +I'm nested diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/templates/simple_template.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/templates/simple_template.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +

{{ whiskey }}

diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tests/templates/template_filter.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tests/templates/template_filter.html Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,1 @@ +{{ value|super_reverse }} \ No newline at end of file diff -r 543110450d50 -r fa1f7726b08b bundled/flask/tox.ini --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bundled/flask/tox.ini Mon Jul 18 13:22:12 2011 -0400 @@ -0,0 +1,5 @@ +[tox] +envlist=py25,py26,py27 + +[testenv] +commands=make test diff -r 543110450d50 -r fa1f7726b08b kick --- a/kick Mon Jul 18 13:21:39 2011 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -kicker -qs -l 0.1 -e reload_safari \ -review/**.py \ -review/**/*.html \ -review/**/*.less \ -review/**/*.js diff -r 543110450d50 -r fa1f7726b08b review/api.py --- a/review/api.py Mon Jul 18 13:21:39 2011 -0400 +++ b/review/api.py Mon Jul 18 13:22:12 2011 -0400 @@ -423,6 +423,16 @@ return ReviewSignoff(**data) + def _create_exists_entry(self): + path = os.path.join(self.repo.root, self.node) + os.mkdir(path) + with open(os.path.join(path, '.exists'), 'w') as e: + pass + + cmdutil.commit(self.ui, self.repo, _commitfunc, + [os.path.join(path, '.exists')], + { 'message': 'Initialize review data for changeset %s' % self.node, + 'addremove': True, }) def __init__(self, ui, repo, target, node): """Initialize a ReviewChangeset. @@ -455,16 +465,6 @@ self.comments = [] self.signoffs = [] - path = os.path.join(self.repo.root, self.node) - os.mkdir(path) - with open(os.path.join(path, '.exists'), 'w') as e: - pass - - cmdutil.commit(ui, self.repo, _commitfunc, - [os.path.join(path, '.exists')], - { 'message': 'Initialize review data for changeset %s' % self.node, - 'addremove': True, }) - def signoffs_for_user(self, username): return filter(lambda s: s.author == username, self.signoffs) @@ -486,6 +486,9 @@ if existing: raise SignoffExists + if not (self.comments or self.signoffs): + self._create_exists_entry() + signoff = ReviewSignoff(fromlocal(self.ui.username()), util.makedate(), self.node, opinion, message, style) signoff._commit(self.ui, self.repo) @@ -509,6 +512,9 @@ if filename and not ufilename: ufilename = fromlocal(filename) + if not (self.comments or self.signoffs): + self._create_exists_entry() + comment = ReviewComment(fromlocal(self.ui.username()), util.makedate(), self.node, ufilename, filename, map(int, lines), message, style) comment._commit(self.ui, self.repo) @@ -615,7 +621,7 @@ for n, line in enumerate(d): start = n - context if n > context else 0 end = n + context + 1 - if any(filter(lambda l: l[0] in '+-', d[start:end])): + if any(filter(lambda l: l and l[0] in '+-', d[start:end])): yield (n, line) for filename, content in ds.iteritems(): diff -r 543110450d50 -r fa1f7726b08b review/static/aal.css --- a/review/static/aal.css Mon Jul 18 13:21:39 2011 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -/* - aardvark.legs by Anatoli Papirovski - http://fecklessmind.com/ - Licensed under the MIT license. http://www.opensource.org/licenses/mit-license.php -*/ - -/* - Reset first. Modified version of Eric Meyer and Paul Chaplin reset - from http://meyerweb.com/eric/tools/css/reset/ -*/ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, font, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -header, nav, section, article, aside, footer -{border: 0; margin: 0; outline: 0; padding: 0; background: transparent; vertical-align: baseline;} - -blockquote, q {quotes: none;} -blockquote:before,blockquote:after,q:before,q:after {content: ''; content: none;} - -header, nav, section, article, aside, footer {display: block;} - -/* Basic styles */ -body {background: #fff; color: #111; font: 0.75em/1.5em "Helvetica Neue", Helvetica, Arial, "Liberation Sans", "Bitstream Vera Sans", sans-serif;} -html>body {font-size: 12px;} - -img {display: inline-block; vertical-align: bottom;} - -h1,h2,h3,h4,h5,h6,strong,b,dt,th {font-weight: 700;} -address,cite,em,i,caption,dfn,var {font-style: italic;} - -h1 {margin: 0 0 0.75em; font-size: 2em;} -h2 {margin: 0 0 1em; font-size: 1.5em;} -h3 {margin: 0 0 1.286em; font-size: 1.167em;} -h4 {margin: 0 0 1.5em; font-size: 1em;} -h5 {margin: 0 0 1.8em; font-size: .834em;} -h6 {margin: 0 0 2em; font-size: .75em;} - -p,ul,ol,dl,blockquote,pre {margin: 0 0 1.5em;} - -li ul,li ol {margin: 0;} -ul {list-style: outside disc;} -ol {list-style: outside decimal;} -li {margin: 0 0 0 2em;} -dd {padding-left: 1.5em;} -blockquote {padding: 0 1.5em;} - -a {text-decoration: underline;} -a:hover {text-decoration: none;} -abbr,acronym {border-bottom: 1px dotted; cursor: help;} -del {text-decoration: line-through;} -ins {text-decoration: overline;} -sub {font-size: .834em; line-height: 1em; vertical-align: sub;} -sup {font-size: .834em; line-height: 1em; vertical-align: super;} - -tt,code,kbd,samp,pre {font-size: 1em; font-family: Consolas, Monaco, "Courier New", Courier, monospace;} - -/* Table styles */ -table {border-collapse: collapse; border-spacing: 0; margin: 0 0 1.5em;} -caption {text-align: left;} -th, td {padding: .25em .5em;} -tbody td, tbody th {border: 1px solid #000;} -tfoot {font-style: italic;} - -/* Form styles */ -fieldset {clear: both;} -legend {padding: 0 0 1.286em; font-size: 1.167em; font-weight: 700;} -fieldset fieldset legend {padding: 0 0 1.5em; font-size: 1em;} -* html legend {margin-left: -7px;} -*+html legend {margin-left: -7px;} - -form .field, form .buttons {clear: both; margin: 0 0 1.5em;} -form .field label {display: block;} -form ul.fields li {list-style-type: none; margin: 0;} -form ul.inline li, form ul.inline label {display: inline;} -form ul.inline li {padding: 0 .75em 0 0;} - -input.radio, input.checkbox {vertical-align: top;} -label, button, input.submit, input.image {cursor: pointer;} -* html input.radio, * html input.checkbox {vertical-align: middle;} -*+html input.radio, *+html input.checkbox {vertical-align: middle;} - -textarea {overflow: auto;} -input.text, input.password, textarea, select {margin: 0; font: 1em/1.3 Helvetica, Arial, "Liberation Sans", "Bitstream Vera Sans", sans-serif; vertical-align: baseline;} -input.text, input.password, textarea {border: 1px solid #444; border-bottom-color: #666; border-right-color: #666; padding: 2px;} - -* html button {margin: 0 .34em 0 0;} -*+html button {margin: 0 .34em 0 0;} - -form.horizontal .field {padding-left: 150px;} -form.horizontal .field label {display: inline; float: left; width: 140px; margin-left: -150px;} - -/* Useful classes */ -img.left {display: inline; float: left; margin: 0 1.5em .75em 0;} -img.right {display: inline; float: right; margin: 0 0 .75em .75em;} diff -r 543110450d50 -r fa1f7726b08b review/static/bg.png Binary file review/static/bg.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/colorbox.css --- a/review/static/colorbox/colorbox.css Mon Jul 18 13:21:39 2011 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -/* - ColorBox Core Style - The following rules are the styles that are consistant between themes. - Avoid changing this area to maintain compatability with future versions of ColorBox. -*/ -#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;} -#cboxOverlay{position:fixed; width:100%; height:100%;} -#cboxMiddleLeft, #cboxBottomLeft{clear:left;} -#cboxContent{position:relative; overflow:hidden;} -#cboxLoadedContent{overflow:auto;} -#cboxLoadedContent iframe{display:block; width:100%; height:100%; border:0;} -#cboxTitle{margin:0;} -#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%;} -#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} - -/* - Example user style - The following rules are ordered and tabbed in a way that represents the - order/nesting of the generated HTML, so that the structure easier to understand. -*/ -#cboxOverlay{background:url(images/overlay.png) 0 0 repeat;} -#colorbox{} - #cboxTopLeft{width:21px; height:21px; background:url(images/controls.png) -100px 0 no-repeat;} - #cboxTopRight{width:21px; height:21px; background:url(images/controls.png) -129px 0 no-repeat;} - #cboxBottomLeft{width:21px; height:21px; background:url(images/controls.png) -100px -29px no-repeat;} - #cboxBottomRight{width:21px; height:21px; background:url(images/controls.png) -129px -29px no-repeat;} - #cboxMiddleLeft{width:21px; background:url(images/controls.png) left top repeat-y;} - #cboxMiddleRight{width:21px; background:url(images/controls.png) right top repeat-y;} - #cboxTopCenter{height:21px; background:url(images/border.png) 0 0 repeat-x;} - #cboxBottomCenter{height:21px; background:url(images/border.png) 0 -29px repeat-x;} - #cboxContent{background:#fff;} - #cboxLoadedContent{margin-bottom:28px;} - #cboxTitle{position:absolute; bottom:4px; left:0; text-align:center; width:100%; color:#949494;} - #cboxCurrent{position:absolute; bottom:4px; left:58px; color:#949494;} - #cboxSlideshow{position:absolute; bottom:4px; right:30px; color:#0092ef;} - #cboxPrevious{position:absolute; bottom:0; left:0px; background:url(images/controls.png) -75px 0px no-repeat; width:25px; height:25px; text-indent:-9999px;} - #cboxPrevious.hover{background-position:-75px -25px;} - #cboxNext{position:absolute; bottom:0; left:27px; background:url(images/controls.png) -50px 0px no-repeat; width:25px; height:25px; text-indent:-9999px;} - #cboxNext.hover{background-position:-50px -25px;} - #cboxLoadingOverlay{background:url(images/loading_background.png) center center no-repeat;} - #cboxLoadingGraphic{background:url(images/loading.gif) center center no-repeat;} - #cboxClose{position:absolute; bottom:0; right:0; background:url(images/controls.png) -25px 0px no-repeat; width:25px; height:25px; text-indent:-9999px;} - #cboxClose.hover{background-position:-25px -25px;} - -/* - The following fixes png-transparency for IE6. - It is also necessary for png-transparency in IE7 & IE8 to avoid 'black halos' with the fade transition - - Since this method does not support CSS background-positioning, it is incompatible with CSS sprites. - Colorbox preloads navigation hover classes to account for this. - - !! Important Note: AlphaImageLoader src paths are relative to the HTML document, - while regular CSS background images are relative to the CSS document. -*/ -.cboxIE #cboxTopLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderTopLeft.png, sizingMethod='scale');} -.cboxIE #cboxTopCenter{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderTopCenter.png, sizingMethod='scale');} -.cboxIE #cboxTopRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderTopRight.png, sizingMethod='scale');} -.cboxIE #cboxBottomLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderBottomLeft.png, sizingMethod='scale');} -.cboxIE #cboxBottomCenter{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderBottomCenter.png, sizingMethod='scale');} -.cboxIE #cboxBottomRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderBottomRight.png, sizingMethod='scale');} -.cboxIE #cboxMiddleLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderMiddleLeft.png, sizingMethod='scale');} -.cboxIE #cboxMiddleRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=images/internet_explorer/borderMiddleRight.png, sizingMethod='scale');} diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/border.png Binary file review/static/colorbox/images/border.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/controls.png Binary file review/static/colorbox/images/controls.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/internet_explorer/borderBottomCenter.png Binary file review/static/colorbox/images/internet_explorer/borderBottomCenter.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/internet_explorer/borderBottomLeft.png Binary file review/static/colorbox/images/internet_explorer/borderBottomLeft.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/internet_explorer/borderBottomRight.png Binary file review/static/colorbox/images/internet_explorer/borderBottomRight.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/internet_explorer/borderMiddleLeft.png Binary file review/static/colorbox/images/internet_explorer/borderMiddleLeft.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/internet_explorer/borderMiddleRight.png Binary file review/static/colorbox/images/internet_explorer/borderMiddleRight.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/internet_explorer/borderTopCenter.png Binary file review/static/colorbox/images/internet_explorer/borderTopCenter.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/internet_explorer/borderTopLeft.png Binary file review/static/colorbox/images/internet_explorer/borderTopLeft.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/internet_explorer/borderTopRight.png Binary file review/static/colorbox/images/internet_explorer/borderTopRight.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/loading.gif Binary file review/static/colorbox/images/loading.gif has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/loading_background.png Binary file review/static/colorbox/images/loading_background.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/images/overlay.png Binary file review/static/colorbox/images/overlay.png has changed diff -r 543110450d50 -r fa1f7726b08b review/static/colorbox/jquery.colorbox.js --- a/review/static/colorbox/jquery.colorbox.js Mon Jul 18 13:21:39 2011 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,772 +0,0 @@ -// ColorBox v1.3.7 - a full featured, light-weight, customizable lightbox based on jQuery 1.3 -// c) 2009 Jack Moore - www.colorpowered.com - jack@colorpowered.com -// Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php - -/*jslint browser: true */ - -(function ($) { - // Shortcuts (to increase compression) - var colorbox = 'colorbox', - hover = 'hover', - TRUE = true, - FALSE = false, - cboxPublic, - isIE = $.browser.msie && !$.support.opacity, // feature detection alone gave a false positive on at least one phone browser - isIE6 = isIE && $.browser.version < 7, - - // Event Strings (to increase compression) - cbox_open = 'cbox_open', - cbox_load = 'cbox_load', - cbox_complete = 'cbox_complete', - cbox_cleanup = 'cbox_cleanup', - cbox_closed = 'cbox_closed', - - // Cached jQuery Object Variables - $overlay, - $cbox, - $wrap, - $content, - $topBorder, - $leftBorder, - $rightBorder, - $bottomBorder, - $related, - $window, - $loaded, - $loadingBay, - $loadingOverlay, - $title, - $current, - $slideshow, - $next, - $prev, - $close, - - // Variables for cached values or use across multiple functions - interfaceHeight, - interfaceWidth, - loadedHeight, - loadedWidth, - element, - bookmark, - index, - settings, - open, - active, - - // ColorBox Default Settings. - // See http://colorpowered.com/colorbox for details. - defaults = { - transition: "elastic", - speed: 350, - width: FALSE, - height: FALSE, - innerWidth: FALSE, - innerHeight: FALSE, - initialWidth: "400", - initialHeight: "400", - maxWidth: FALSE, - maxHeight: FALSE, - scalePhotos: TRUE, - scrolling: TRUE, - inline: FALSE, - html: FALSE, - iframe: FALSE, - photo: FALSE, - href: FALSE, - title: FALSE, - rel: FALSE, - opacity: 0.9, - preloading: TRUE, - current: "{current} of {total}", - previous: "previous", - next: "next", - close: "close", - open: FALSE, - overlayClose: TRUE, - loop: TRUE, - - slideshow: FALSE, - slideshowAuto: TRUE, - slideshowSpeed: 2500, - slideshowStart: "start slideshow", - slideshowStop: "stop slideshow", - - onOpen: FALSE, - onLoad: FALSE, - onComplete: FALSE, - onCleanup: FALSE, - onClosed: FALSE, - - escKey: TRUE, - arrowKey: TRUE - }; - - // **************** - // HELPER FUNCTIONS - // **************** - - // Convert % values to pixels - function setSize(size, dimension) { - dimension = dimension === 'x' ? $window.width() : $window.height();//document.documentElement.clientWidth : document.documentElement.clientHeight; - return (typeof size === 'string') ? Math.round((size.match(/%/) ? (dimension / 100) * parseInt(size, 10) : parseInt(size, 10))) : size; - } - - // Checks an href to see if it is a photo. - // There is a force photo option (photo: true) for hrefs that cannot be matched by this regex. - function isImage(url) { - url = $.isFunction(url) ? url.call(element) : url; - return settings.photo || url.match(/\.(gif|png|jpg|jpeg|bmp)(?:\?([^#]*))?(?:#(\.*))?$/i); - } - - // Assigns functions results to their respective settings. This allows functions to be used to set ColorBox options. - function process() { - for (var i in settings) { - if ($.isFunction(settings[i]) && i.substring(0, 2) !== 'on') { // checks to make sure the function isn't one of the callbacks, they will be handled at the appropriate time. - settings[i] = settings[i].call(element); - } - } - settings.rel = settings.rel || element.rel || 'nofollow'; - settings.href = settings.href || $(element).attr('href'); - settings.title = settings.title || element.title; - } - - function launch(elem) { - - element = elem; - - settings = $.extend({}, $(element).data(colorbox)); - - process(); // Convert functions to their returned values. - - if (settings.rel !== 'nofollow') { - $related = $('.cboxElement').filter(function () { - var relRelated = $(this).data(colorbox).rel || this.rel; - return (relRelated === settings.rel); - }); - index = $related.index(element); - - // Check direct calls to ColorBox. - if (index < 0) { - $related = $related.add(element); - index = $related.length - 1; - } - } else { - $related = $(element); - index = 0; - } - - if (!open) { - open = TRUE; - - active = TRUE; // Prevents the page-change action from queuing up if the visitor holds down the left or right keys. - - bookmark = element; - - try { - bookmark.blur(); // Remove the focus from the calling element. - }catch (e) {} - - $.event.trigger(cbox_open); - if (settings.onOpen) { - settings.onOpen.call(element); - } - - $overlay.css({"opacity": parseFloat(settings.opacity), "cursor": settings.overlayClose ? "pointer" : "auto"}).show(); - - // Opens inital empty ColorBox prior to content being loaded. - settings.w = setSize(settings.initialWidth, 'x'); - settings.h = setSize(settings.initialHeight, 'y'); - cboxPublic.position(0); - - if (isIE6) { - $window.bind('resize.cboxIE6 scroll.cboxIE6', function () { - $overlay.css({width: $window.width(), height: $window.height(), top: $window.scrollTop(), left: $window.scrollLeft()}); - }).trigger("scroll.cboxIE6"); - } - } - - $current.add($prev).add($next).add($slideshow).add($title).hide(); - - $close.html(settings.close).show(); - - cboxPublic.slideshow(); - - cboxPublic.load(); - } - - // **************** - // PUBLIC FUNCTIONS - // Usage format: $.fn.colorbox.close(); - // Usage from within an iframe: parent.$.fn.colorbox.close(); - // **************** - - cboxPublic = $.fn.colorbox = $.colorbox = function (options, callback) { - var $this = this; - - if ($this.selector && !$this.length) { - return $this; - } - - options = options || {}; - - if (callback) { - options.onComplete = callback; - } - - if (!$this.length || $this.selector === undefined) { // detects $.colorbox() and $.fn.colorbox() - $this = $(''); - options.open = TRUE; // assume an immediate open - } - - $this.each(function () { - $(this).data(colorbox, $.extend({}, $(this).data(colorbox) || defaults, options)).addClass("cboxElement"); - }); - - if (options.open) { - launch($this[0]); - } - - return $this; - }; - - // Initialize ColorBox: store common calculations, preload the interface graphics, append the html. - // This preps colorbox for a speedy open when clicked, and lightens the burdon on the browser by only - // having to run once, instead of each time colorbox is opened. - cboxPublic.init = function () { - - // jQuery object generator to save a bit of space - function $div(id) { - return $('
'); - } - - // Create & Append jQuery Objects - $window = $(window); - $cbox = $('
'); - $overlay = $div("Overlay").hide(); - $wrap = $div("Wrapper"); - $content = $div("Content").append( - $loaded = $div("LoadedContent").css({width: 0, height: 0}), - $loadingOverlay = $div("LoadingOverlay").add($div("LoadingGraphic")), - $title = $div("Title"), - $current = $div("Current"), - $next = $div("Next"), - $prev = $div("Previous"), - $slideshow = $div("Slideshow"), - $close = $div("Close") - ); - $wrap.append( // The 3x3 Grid that makes up ColorBox - $('
').append( - $div("TopLeft"), - $topBorder = $div("TopCenter"), - $div("TopRight") - ), - $('
').append( - $leftBorder = $div("MiddleLeft"), - $content, - $rightBorder = $div("MiddleRight") - ), - $('
').append( - $div("BottomLeft"), - $bottomBorder = $div("BottomCenter"), - $div("BottomRight") - ) - ).children().children().css({'float': 'left'}); - - $loadingBay = $("
"); - - $('body').prepend($overlay, $cbox.append($wrap, $loadingBay)); - - if (isIE) { - $cbox.addClass('cboxIE'); - if (isIE6) { - $overlay.css('position', 'absolute'); - } - } - - $content.children() - .hover(function () { - $(this).addClass(hover); - }, function () { - $(this).removeClass(hover); - }).addClass(hover); - - // Cache values needed for size calculations - interfaceHeight = $topBorder.height() + $bottomBorder.height() + $content.outerHeight(TRUE) - $content.height();//Subtraction needed for IE6 - interfaceWidth = $leftBorder.width() + $rightBorder.width() + $content.outerWidth(TRUE) - $content.width(); - loadedHeight = $loaded.outerHeight(TRUE); - loadedWidth = $loaded.outerWidth(TRUE); - - // Setting padding to remove the need to do size conversions during the animation step. - $cbox.css({"padding-bottom": interfaceHeight, "padding-right": interfaceWidth}).hide(); - - // Setup button events. - $next.click(cboxPublic.next); - $prev.click(cboxPublic.prev); - $close.click(cboxPublic.close); - - // Adding the 'hover' class allowed the browser to load the hover-state - // background graphics. The class can now can be removed. - $content.children().removeClass(hover); - - $('.cboxElement').live('click', function (e) { - // checks to see if it was a non-left mouse-click and for clicks modified with ctrl, shift, or alt. - if ((e.button !== 0 && typeof e.button !== 'undefined') || e.ctrlKey || e.shiftKey || e.altKey) { - return TRUE; - } else { - launch(this); - return FALSE; - } - }); - - $overlay.click(function () { - if (settings.overlayClose) { - cboxPublic.close(); - } - }); - - // Set Navigation Key Bindings - $(document).bind("keydown", function (e) { - if (open && settings.escKey && e.keyCode === 27) { - e.preventDefault(); - cboxPublic.close(); - } - if (open && settings.arrowKey && !active && $related.length > 1) { - if (e.keyCode === 37 && (index > 0 || settings.loop)) { - e.preventDefault(); - $prev.click(); - } else if (e.keyCode === 39 && (index < $related.length - 1 || settings.loop)) { - e.preventDefault(); - $next.click(); - } - } - }); - }; - - cboxPublic.remove = function () { - $cbox.add($overlay).remove(); - $('.cboxElement').removeData(colorbox).removeClass('cboxElement'); - }; - - cboxPublic.position = function (speed, loadedCallback) { - var - animate_speed, - // keeps the top and left positions within the browser's viewport. - posTop = Math.max($window.height() - settings.h - loadedHeight - interfaceHeight, 0) / 2 + $window.scrollTop(), - posLeft = Math.max($window.width() - settings.w - loadedWidth - interfaceWidth, 0) / 2 + $window.scrollLeft(); - - // setting the speed to 0 to reduce the delay between same-sized content. - animate_speed = ($cbox.width() === settings.w + loadedWidth && $cbox.height() === settings.h + loadedHeight) ? 0 : speed; - - // this gives the wrapper plenty of breathing room so it's floated contents can move around smoothly, - // but it has to be shrank down around the size of div#colorbox when it's done. If not, - // it can invoke an obscure IE bug when using iframes. - $wrap[0].style.width = $wrap[0].style.height = "9999px"; - - function modalDimensions(that) { - // loading overlay height has to be explicitly set for IE6. - $topBorder[0].style.width = $bottomBorder[0].style.width = $content[0].style.width = that.style.width; - $loadingOverlay[0].style.height = $loadingOverlay[1].style.height = $content[0].style.height = $leftBorder[0].style.height = $rightBorder[0].style.height = that.style.height; - } - - $cbox.dequeue().animate({width: settings.w + loadedWidth, height: settings.h + loadedHeight, top: posTop, left: posLeft}, { - duration: animate_speed, - complete: function () { - modalDimensions(this); - - active = FALSE; - - // shrink the wrapper down to exactly the size of colorbox to avoid a bug in IE's iframe implementation. - $wrap[0].style.width = (settings.w + loadedWidth + interfaceWidth) + "px"; - $wrap[0].style.height = (settings.h + loadedHeight + interfaceHeight) + "px"; - - if (loadedCallback) { - loadedCallback(); - } - }, - step: function () { - modalDimensions(this); - } - }); - }; - - cboxPublic.resize = function (options) { - if (open) { - options = options || {}; - - if (options.width) { - settings.w = setSize(options.width, 'x') - loadedWidth - interfaceWidth; - } - if (options.innerWidth) { - settings.w = setSize(options.innerWidth, 'x'); - } - $loaded.css({width: settings.w}); - - if (options.height) { - settings.h = setSize(options.height, 'y') - loadedHeight - interfaceHeight; - } - if (options.innerHeight) { - settings.h = setSize(options.innerHeight, 'y'); - } - if (!options.innerHeight && !options.height) { - var $child = $loaded.wrapInner("
").children(); // temporary wrapper to get an accurate estimate of just how high the total content should be. - settings.h = $child.height(); - $child.replaceWith($child.children()); // ditch the temporary wrapper div used in height calculation - } - $loaded.css({height: settings.h}); - - cboxPublic.position(settings.transition === "none" ? 0 : settings.speed); - } - }; - - cboxPublic.prep = function (object) { - if (!open) { - return; - } - - var photo, - speed = settings.transition === "none" ? 0 : settings.speed; - - $window.unbind('resize.cbox'); - - $loaded.remove(); - $loaded = $('
').html(object); - - function getWidth() { - settings.w = settings.w || $loaded.width(); - settings.w = settings.mw && settings.mw < settings.w ? settings.mw : settings.w; - return settings.w; - } - function getHeight() { - settings.h = settings.h || $loaded.height(); - settings.h = settings.mh && settings.mh < settings.h ? settings.mh : settings.h; - return settings.h; - } - - $loaded.hide() - .appendTo($loadingBay)// content has to be appended to the DOM for accurate size calculations. - .css({width: getWidth(), overflow: settings.scrolling ? 'auto' : 'hidden'}) - .css({height: getHeight()})// sets the height independently from the width in case the new width influences the value of height. - .prependTo($content); - - $('#cboxPhoto').css({cssFloat: 'none'});// floating the IMG removes the bottom line-height and fixed a problem where IE miscalculates the width of the parent element as 100% of the document width. - - // Hides SELECT elements in IE6 because they would otherwise sit on top of the overlay. - if (isIE6) { - $('select:not(#colorbox select)').filter(function () { - return this.style.visibility !== 'hidden'; - }).css({'visibility': 'hidden'}).one(cbox_cleanup, function () { - this.style.visibility = 'inherit'; - }); - } - - function setPosition(s) { - var prev, prevSrc, next, nextSrc, total = $related.length, loop = settings.loop; - cboxPublic.position(s, function () { - function defilter() { - if (isIE) { - //IE adds a filter when ColorBox fades in and out that can cause problems if the loaded content contains transparent pngs. - $cbox[0].style.removeAttribute("filter"); - } - } - - if (!open) { - return; - } - - if (isIE) { - //This fadeIn helps the bicubic resampling to kick-in. - if (photo) { - $loaded.fadeIn(100); - } - } - - //Waited until the iframe is added to the DOM & it is visible before setting the src. - //This increases compatability with pages using DOM dependent JavaScript. - if (settings.iframe) { - $("