summaryrefslogtreecommitdiff
path: root/docs/narr/advanced-features.rst
diff options
context:
space:
mode:
authorcewing <cris@crisewing.com>2017-05-23 20:21:07 -0700
committercewing <cris@crisewing.com>2017-05-23 20:21:07 -0700
commitf20a018167a19d17527d40c027e6f9045749f065 (patch)
treec965d5dc2050111d4c90386c15b6b5ad5816b736 /docs/narr/advanced-features.rst
parent52ff50f75d265a4c7d04db538c26b61ab05677c6 (diff)
downloadpyramid-f20a018167a19d17527d40c027e6f9045749f065.tar.gz
pyramid-f20a018167a19d17527d40c027e6f9045749f065.tar.bz2
pyramid-f20a018167a19d17527d40c027e6f9045749f065.zip
fixes per code review, Thanks @stevepiercy.
Diffstat (limited to 'docs/narr/advanced-features.rst')
-rw-r--r--docs/narr/advanced-features.rst418
1 files changed, 418 insertions, 0 deletions
diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst
new file mode 100644
index 000000000..a97d4f3b1
--- /dev/null
+++ b/docs/narr/advanced-features.rst
@@ -0,0 +1,418 @@
+Advanced :app:`Pyramid` Design Features
+=======================================
+
+Pyramid has been built from the ground up to avoid the problems that other
+frameworks can suffer.
+
+
+You Don't Need Singletons
+-------------------------
+
+Have you ever struggled with parameterizing Django's ``settings.py`` file for
+multiple installations of the same Django application? Have you ever needed to
+monkey-patch a framework fixture to get it to behave properly for your
+use case? Have you ever tried to deploy your application using an asynchronous
+server and failed?
+
+All these problems are symptoms of :term:`mutable` :term:`global state`, also
+known as :term:`import time` :term:`side effect`\ s and arise from the use of
+:term:`singleton` data structures.
+
+:app:`Pyramid` is written so that you don't run into these types of problems.
+It is even possible to run multiple copies of the *same* :app:`Pyramid`
+application configured differently within a single Python process. This makes
+running :app:`Pyramid` in shared hosting environments a snap.
+
+Simplify your View Code with Predicates
+---------------------------------------
+
+How many times have you found yourself beginning the logic of your view code
+with something like this:
+
+.. code-block:: python
+
+ if request.user.is_authenticated:
+ # do one thing
+ else:
+ # do something else
+
+Unlike many other systems, :app:`Pyramid` allows you to associate more than one view
+with a single route. For example, you can create a route with the pattern
+``/items`` and when the route is matched, you can send the request to one view
+if the request method is GET, another view if the request method is POST, and
+so on.
+
+:app:`Pyramid` uses a system of :term:`view predicate`\ s to allow this.
+Matching the request method is one basic thing you can do with a
+:term:`view predicate`. You can also associate views with other request
+parameters, such as elements in the query string, the Accept header, whether
+the request is an AJAX (XHR) request or not, and lots of other things.
+
+For our example above, you can do this instead:
+
+.. code-block:: python
+
+ @view_config(route_name="items", effective_principals=pyramid.security.Authenticated)
+ def auth_view(request):
+ # do one thing
+
+ @view_config(route_name="items")
+ def anon_view(request):
+ # do something else
+
+This approach allows you to develop view code that is simpler, more easily
+understandable, and more directly testable.
+
+.. seealso::
+
+ See also :ref:`view_configuration_parameters`.
+
+Stop Worrying About Transactions
+--------------------------------
+
+:app:`Pyramid`\ 's :term:`cookiecutter`\ s render projects that include a *transaction
+management* system. When you use this system, you can stop worrying about when
+to commit your changes, :app:`Pyramid` handles it for you. The system will
+commit at the end of a request or abort if there was an exception.
+
+Why is that a good thing? Imagine a situation where you manually commit a
+change to your persistence layer. It's very likely that other framework code
+will run *after* your changes are done. If an error happens in that other code,
+you can easily wind up with inconsistent data if you're not extremely careful.
+
+Using transaction management saves you from needing to think about this. Either
+a request completes successfully and all changes are committed, or it does
+not and all changes are aborted.
+
+Pyramid's transaction management is extendable, so you can synchronize commits
+between multiple databases or databases of different kinds. It also allows you
+to do things like conditionally send email if a transaction is committed, but
+otherwise keep quiet.
+
+.. seealso::
+
+ See also :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements
+ anywhere in application code).
+
+Stop Worrying About Configuration
+---------------------------------
+
+When a system is small, it's reasonably easy to keep it all in your head. But
+as systems grow large, configuration grows more complex. Your app may grow to
+have hundreds or even thousands of configuration statements.
+
+:app:`Pyramid`\ 's configuration system keeps track of each of your statements. If you
+accidentally add two that are identical, or :app:`Pyramid` can't make sense out of
+what it would mean to have both statements active at the same time, it will
+complain loudly at startup time.
+
+:app:`Pyramid`\ 's configuration system is not dumb though. If you use the
+:meth:`~pyramid.config.Configurator.include` system, it can automatically
+resolve conflicts on its own. More local statements are preferred over less
+local ones. So you can intelligently factor large systems into smaller ones.
+
+.. seealso::
+
+ See also :ref:`conflict_detection`.
+
+Compose Powerful Apps From Simple Parts
+----------------------------------------
+
+Speaking of the :app:`Pyramid` structured "include" mechanism (see
+:meth:`~pyramid.config.Configurator.include`), it allows you to compose complex
+applications from multiple, simple Python packages. All the configuration
+statements that can be performed in your main :app:`Pyramid` application can also be
+used in included packages. You can add views, routes, and subscribers, and even
+set authentication and authorization policies.
+
+If you need, you can extend or override the configuration of an existing
+application by including its configuration in your own and then modifying it.
+
+
+For example, if you want to reuse an existing application that already has a
+bunch of routes, you can just use the ``include`` statement with a
+``route_prefix``. All the routes of that application will be availabe, prefixed
+as you requested:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.include('pyramid_jinja2')
+ config.include('pyramid_exclog')
+ config.include('some.other.package', route_prefix='/somethingelse')
+
+.. seealso::
+
+ See also :ref:`including_configuration` and
+ :ref:`building_an_extensible_app`.
+
+Authenticate Users Your Way
+---------------------------
+
+:app:`Pyramid` ships with prebuilt, well-tested authentication and authorization
+schemes out of the box. Using a scheme is a matter of configuration. So if you
+need to change approaches later, you need only update your configuration.
+
+In addition, the system that handles authentication and authorization is
+flexible and pluggable. If you want to use another security add-on, or define
+your own, you can. And again, you need only update your application
+configuration to make the change.
+
+.. seealso::
+
+ See also :ref:`enabling_authorization_policy`.
+
+Build Trees of Resources
+------------------------
+
+:app:`Pyramid` supports :term:`traversal`, a way of mapping URLs to a concrete
+:term:`resource tree`. If your application naturally consists of an arbitrary
+heirarchy of different types of content (like a CMS or a Document Management
+System), traversal is for you. If you have a requirement for a highly granular
+security model ("Jane can edit documents in *this* folder, but not *that*
+one"), traversal can be a powerful approach.
+
+.. seealso::
+
+ See also :ref:`hello_traversal_chapter` and
+ :ref:`much_ado_about_traversal_chapter`.
+
+Take Action on Each Request with Tweens
+---------------------------------------
+
+:app:`Pyramid` has a system for applying an arbitrary action to each request or
+response called a :term:`tween`. The system is similar in concept to WSGI
+:term:`middleware`, but can be more useful since :term:`tween`\ s run in the
+:app:`Pyramid` context, and have access to templates, request objects, and
+other niceties.
+
+The :app:`Pyramid` debug toolbar is a :term:`tween`, as is the ``pyramid_tm``
+transaction manager.
+
+.. seealso::
+
+ See also :ref:`registering_tweens`.
+
+Return What You Want From Your Views
+------------------------------------
+
+We have shown elsewhere (in the :doc:`introduction`) how using a :term:`renderer`
+allows you to return simple Python dictionaries from your view code. But some
+frameworks allow you to return strings or tuples from view callables.
+When frameworks allow for this, code looks slightly prettier because there are
+fewer imports and less code. For example, compare this:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.response import Response
+
+ def aview(request):
+ return Response("Hello world!")
+
+To this:
+
+.. code-block:: python
+ :linenos:
+
+ def aview(request):
+ return "Hello world!"
+
+Nicer to look at, right?
+
+Out of the box, :app:`Pyramid` will raise an exception if you try to run the
+second example above. After all, a view should return a response, and "explicit
+is better than implicit".
+
+But if you're a developer who likes the aesthetics of simplicity,
+:app:`Pyramid` provides an way to support this sort of thing, the
+:term:`response adapter`\ :
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+ from pyramid.response import Response
+
+ def string_response_adapter(s):
+ response = Response(s)
+ response.content_type = 'text/html'
+ return response
+
+A new response adapter is registered in configuration:
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_response_adapter(string_response_adapter, basestring)
+
+With that, you may return strings from any of your view callables, e.g.:
+
+.. code-block:: python
+ :linenos:
+
+ def helloview(request):
+ return "Hello world!"
+
+ def goodbyeview(request):
+ return "Goodbye world!"
+
+You can even use a :term:`response adapter` to allow for custom content types
+and return codes:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+
+ def tuple_response_adapter(val):
+ status_int, content_type, body = val
+ response = Response(body)
+ response.content_type = content_type
+ response.status_int = status_int
+ return response
+
+ def string_response_adapter(body):
+ response = Response(body)
+ response.content_type = 'text/html'
+ response.status_int = 200
+ return response
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_response_adapter(string_response_adapter, basestring)
+ config.add_response_adapter(tuple_response_adapter, tuple)
+
+With this, both of these views will work as expected:
+
+.. code-block:: python
+ :linenos:
+
+ def aview(request):
+ return "Hello world!"
+
+ def anotherview(request):
+ return (403, 'text/plain', "Forbidden")
+
+.. seealso::
+
+ See also :ref:`using_iresponse`.
+
+Use Global Response Objects
+---------------------------
+
+Views have to return responses. But constructing them in view code is a chore.
+And perhaps registering a :term:`response adapter` as shown above is just too
+much work. :app:`Pyramid` provides a global response object as well. You can
+use it directly, if you prefer:
+
+.. code-block:: python
+ :linenos:
+
+ def aview(request):
+ response = request.response
+ response.body = 'Hello world!'
+ response.content_type = 'text/plain'
+ return response
+
+.. seealso::
+
+ See also :ref:`request_response_attr`.
+
+Extend Configuration
+--------------------
+
+Perhaps the :app:`Pyramid` configurator's syntax feels a bit verbose to you. Or
+possibly you would like to add a feature to configuration without asking the
+core developers to change :app:`Pyramid` itself?
+
+You can extend :app:`Pyramid`\ 's :term:`configurator` with your own
+directives. For example, let's say you find yourself calling
+:meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can get
+rid of the boring with existing shortcuts, but let's say that this is a case
+where there is no such shortcut:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+
+ config = Configurator()
+ config.add_route('xhr_route', '/xhr/{id}')
+ config.add_view('my.package.GET_view', route_name='xhr_route',
+ xhr=True, permission='view', request_method='GET')
+ config.add_view('my.package.POST_view', route_name='xhr_route',
+ xhr=True, permission='view', request_method='POST')
+ config.add_view('my.package.HEAD_view', route_name='xhr_route',
+ xhr=True, permission='view', request_method='HEAD')
+
+Pretty tedious right? You can add a directive to the :app:`Pyramid`
+:term:`configurator` to automate some of the tedium away:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+
+ def add_protected_xhr_views(config, module):
+ module = config.maybe_dotted(module)
+ for method in ('GET', 'POST', 'HEAD'):
+ view = getattr(module, 'xhr_%s_view' % method, None)
+ if view is not None:
+ config.add_view(view, route_name='xhr_route', xhr=True,
+ permission='view', request_method=method)
+
+ config = Configurator()
+ config.add_directive('add_protected_xhr_views', add_protected_xhr_views)
+
+Once that's done, you can call the directive you've just added as a method of
+the :term:`configurator` object:
+
+.. code-block:: python
+ :linenos:
+
+ config.add_route('xhr_route', '/xhr/{id}')
+ config.add_protected_xhr_views('my.package')
+
+Much better!
+
+You can share your configuration code with others, too. Package it up and call
+:meth:`~pyramid.config.Configurator.add_directive` from within a function
+called when another user uses the
+:meth:`~pyramid.config.Configurator.include` method against your code.
+
+.. seealso::
+
+ See also :ref:`add_directive`.
+
+Introspect Your Application
+---------------------------
+
+If you're building a large, pluggalbe system, it's useful to be able to get a
+list of what has been plugged in *at application runtime*. For example, you
+might want to show users a set of tabs at the top of the screen based on a list
+of the views they registered.
+
+:app:`Pyramid` provides an :term:`introspector` for just this purpose.
+
+Here's an example of using Pyramid's introspector from within a view callable:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+ from pyramid.response import Response
+
+ @view_config(route_name='bar')
+ def show_current_route_pattern(request):
+ introspector = request.registry.introspector
+ route_name = request.matched_route.name
+ route_intr = introspector.get('routes', route_name)
+ return Response(str(route_intr['pattern']))
+
+.. seealso::
+
+ See also :ref:`using_introspection`. \ No newline at end of file