From ef0dc1800f7558abbefe070f361b97b9161b2452 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 24 May 2010 18:37:48 +0200 Subject: [PATCH] Added interactive Python docs, fixed part style. --- CHANGES | 4 ++ docs/contents.rst.inc | 43 +++++++++++++++++ docs/flaskstyle.sty | 8 +-- docs/index.rst | 43 +---------------- docs/latexindex.rst | 34 +------------ docs/shell.rst | 110 ++++++++++++++++++++++++++++++++++++++++++ flask.py | 37 +++++++++++++- tests/flask_tests.py | 17 +++++++ 8 files changed, 215 insertions(+), 81 deletions(-) create mode 100644 docs/contents.rst.inc create mode 100644 docs/shell.rst diff --git a/CHANGES b/CHANGES index 84aa4633..47dd08b6 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,10 @@ Release date to be announced log request handling exceptions to that logger when not in debug mode. This makes it possible to receive mails on server errors for example. +- added support for context binding that does not require the use of + the with statement for playing in the console. +- the request context is now available within the with statement making + it possible to further push the request context or pop it. Version 0.2 ----------- diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc new file mode 100644 index 00000000..66dc0ead --- /dev/null +++ b/docs/contents.rst.inc @@ -0,0 +1,43 @@ +User's Guide +------------ + +This part of the documentation is written text and should give you an idea +how to work with Flask. It's a series of step-by-step instructions for +web development. + +.. toctree:: + :maxdepth: 2 + + foreword + installation + quickstart + tutorial/index + testing + errorhandling + 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 + license + changelog diff --git a/docs/flaskstyle.sty b/docs/flaskstyle.sty index a86c6682..b2d073d0 100644 --- a/docs/flaskstyle.sty +++ b/docs/flaskstyle.sty @@ -1,4 +1,3 @@ -\pagenumbering{arabic} \definecolor{TitleColor}{rgb}{0,0,0} \definecolor{InnerLinkColor}{rgb}{0,0,0} @@ -72,9 +71,10 @@ \renewcommand\thepart{\@Roman\c@part} \renewcommand\part{% + \pagestyle{empty} \if@noskipsec \leavevmode \fi \cleardoublepage - \vspace*{8cm}% + \vspace*{6cm}% \@afterindentfalse \secdef\@part\@spart} @@ -85,7 +85,7 @@ \else \addcontentsline{toc}{part}{#1}% \fi - {\parindent \z@ \center + {\parindent \z@ %\center \interlinepenalty \@M \normalfont \ifnum \c@secnumdepth >\m@ne @@ -98,7 +98,7 @@ \vskip 8ex \@afterheading} \def\@spart#1{% - {\parindent \z@ \center + {\parindent \z@ %\center \interlinepenalty \@M \normalfont \huge \bfseries #1\par}% diff --git a/docs/index.rst b/docs/index.rst index f2270422..e8ea5c33 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,45 +27,4 @@ following links: .. _Jinja2: http://jinja.pocoo.org/2/ .. _Werkzeug: http://werkzeug.pocoo.org/ -User's Guide ------------- - -This part of the documentation is written text and should give you an idea -how to work with Flask. It's a series of step-by-step instructions for -web development. - -.. toctree:: - :maxdepth: 2 - - foreword - installation - quickstart - tutorial/index - testing - errorhandling - 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 - license - changelog +.. include:: contents.rst.inc diff --git a/docs/latexindex.rst b/docs/latexindex.rst index a4aa0b4a..288197c3 100644 --- a/docs/latexindex.rst +++ b/docs/latexindex.rst @@ -3,36 +3,4 @@ Flask Documentation =================== -User's Guide ------------- - -.. toctree:: - :maxdepth: 3 - - foreword - installation - quickstart - tutorial/index - testing - errorhandling - patterns/index - deploying/index - becomingbig - -API Reference -------------- - -.. toctree:: - :maxdepth: 3 - - api - -Additional Notes ----------------- - -.. toctree:: - :maxdepth: 3 - - design - license - changelog +.. include:: contents.rst.inc diff --git a/docs/shell.rst b/docs/shell.rst new file mode 100644 index 00000000..65e44ce7 --- /dev/null +++ b/docs/shell.rst @@ -0,0 +1,110 @@ +Working with the Shell +====================== + +.. versionadded:: 0.5 + +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 --git a/flask.py b/flask.py index ea3fcaa0..d3391a43 100644 --- a/flask.py +++ b/flask.py @@ -147,15 +147,24 @@ class _RequestContext(object): except HTTPException, e: self.request.routing_exception = e - def __enter__(self): + 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. if tb is None or not self.app.debug: - _request_ctx_stack.pop() + self.pop() def url_for(endpoint, **values): @@ -1202,6 +1211,30 @@ class Flask(_PackageBoundObject): 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.5 + 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) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 14e3e384..17f73ad7 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -53,6 +53,23 @@ class ContextTestCase(unittest.TestCase): with app.test_request_context('/meh'): assert meh() == 'http://localhost/meh' + 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 AttributeError: + pass + else: + assert 0, 'expected runtime error' + class BasicFunctionalityTestCase(unittest.TestCase):