From 52ff50f75d265a4c7d04db538c26b61ab05677c6 Mon Sep 17 00:00:00 2001 From: cewing Date: Mon, 22 May 2017 16:38:53 -0700 Subject: Updating wording in the advanced features doc to make it more accessible --- docs/narr/advfeatures.rst | 352 +++++++++++++++++++++++----------------------- 1 file changed, 179 insertions(+), 173 deletions(-) diff --git a/docs/narr/advfeatures.rst b/docs/narr/advfeatures.rst index 4365b1855..5c45cf61a 100644 --- a/docs/narr/advfeatures.rst +++ b/docs/narr/advfeatures.rst @@ -5,104 +5,123 @@ Pyramid has been built from the ground up to avoid the problems that other frameworks can suffer. -No singletons -~~~~~~~~~~~~~ - -Pyramid is written in such a way that it requires your application to have -exactly zero "singleton" data structures. Or put another way, Pyramid doesn't -require you to construct any "mutable globals". Or put even another different -way, an import of a Pyramid application needn't have any "import-time side -effects". This is esoteric-sounding, but if you've ever tried to cope with -parameterizing a Django ``settings.py`` file for multiple installations of the -same application, or if you've ever needed to monkey-patch some framework -fixture so that it behaves properly for your use case, or if you've ever wanted -to deploy your system using an asynchronous server, you'll end up appreciating -this feature. It just won't be a problem. You can even run multiple copies of -a similar but not identically configured Pyramid application within the same -Python process. This is good for shared hosting environments, where RAM is at -a premium. - -View predicates and many views per route -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +You Don't Need Singletons +------------------------- + +Have you ever struggled with parametrizing 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 "mutable global state", also known as +"import-time side effects" and arise from the use of "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 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:: + + if request.user.is_authenticated: + # do one thing + else: + # do something else Unlike many other systems, Pyramid allows you to associate more than one view -per route. For example, you can create a route with the pattern ``/items`` and -when the route is matched, you can shuffle off the request to one view if the -request method is GET, another view if the request method is POST, etc. A -system known as "view predicates" allows for this. Request method matching is -the most basic thing you can do with a view predicate. You can also associate -views with other request parameters, such as the elements in the query string, -the Accept header, whether the request is an XHR request or not, and lots of -other things. This feature allows you to keep your individual views clean. -They won't need much conditional logic, so they'll be easier to test. +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 "view predicates" to allow this. Matching the +request method is one basic thing you can do with a 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 XHR request or not, +and lots of other things. + +For our example above, you can do this instead:: + + @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. Example: :ref:`view_configuration_parameters`. -Transaction management -~~~~~~~~~~~~~~~~~~~~~~ +Stop Worrying About Transactions +-------------------------------- Pyramid's :term:`scaffold` system renders projects that include a *transaction -management* system, stolen from Zope. When you use this transaction management -system, you cease being responsible for committing your data anymore. Instead -Pyramid takes care of committing: it commits at the end of a request or aborts -if there's an exception. Why is that a good thing? Having a centralized place -for transaction management is a great thing. If, instead of managing your -transactions in a centralized place, you sprinkle ``session.commit`` calls in -your application logic itself, you can wind up in a bad place. Wherever you -manually commit data to your database, it's likely that some of your other code -is going to run *after* your commit. If that code goes on to do other important -things after that commit, and an error happens in the later code, you can -easily wind up with inconsistent data if you're not extremely careful. Some -data will have been written to the database that probably should not have. -Having a centralized commit point saves you from needing to think about this; -it's great for lazy people who also care about data integrity. Either the -request completes successfully, and all changes are committed, or it does not, -and all changes are aborted. - -Pyramid's transaction management system allows you to synchronize commits -between multiple databases. It also allows you to do things like conditionally -send email if a transaction commits, but otherwise keep quiet. +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 aborts 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 commits, but +otherwise keep quiet. Example: :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements anywhere in application code). -Configuration conflict detection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Stop Worrying About Configuration +--------------------------------- When a system is small, it's reasonably easy to keep it all in your head. But -when systems grow large, you may have hundreds or thousands of configuration -statements which add a view, add a route, and so forth. +as systems grow large, configuration grows more complex. Your app may grow to +have hundreds or even thousands of configuration statements. -Pyramid's configuration system keeps track of your configuration statements. If -you accidentally add two that are identical, or Pyramid can't make sense out of +Pyramid's configuration system keeps track of your configuration. If you +accidentally add two that are identical, or 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. It's not dumb though. It will automatically -resolve conflicting configuration statements on its own if you use the -configuration :meth:`~pyramid.config.Configurator.include` system. "More local" -statements are preferred over "less local" ones. This allows you to -intelligently factor large systems into smaller ones. +complain loudly at startup time. + +Pyramid's configuration system is not dumb though. If you use the confugration +: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. Example: :ref:`conflict_detection`. -Configuration extensibility -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Unlike other systems, Pyramid provides a structured "include" mechanism (see -:meth:`~pyramid.config.Configurator.include`) that allows you to combine -applications from multiple Python packages. All the configuration statements -that can be performed in your "main" Pyramid application can also be performed -by included packages, including the addition of views, routes, subscribers, and -even authentication and authorization policies. You can even extend or override -an existing application by including another application's configuration in -your own, overriding or adding new views and routes to it. This has the -potential to allow you to create a big application out of many other smaller -ones. 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``. The new application will live within your application at an -URL prefix. It's not a big deal, and requires little up-front engineering -effort. - -For example: +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" 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: @@ -120,91 +139,79 @@ For example: See also :ref:`including_configuration` and :ref:`building_an_extensible_app`. -Flexible authentication and authorization -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +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. -Pyramid includes a flexible, pluggable authentication and authorization system. -No matter where your user data is stored, or what scheme you'd like to use to -permit your users to access your data, you can use a predefined Pyramid -plugpoint to plug in your custom authentication and authorization code. If you -want to change these schemes later, you can just change it in one place rather -than everywhere in your code. It also ships with prebuilt well-tested -authentication and authorization schemes out of the box. But what if you don't -want to use Pyramid's built-in system? You don't have to. You can just write -your own bespoke security code as you would in any other system. +In addition, the system that handles authentication an 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. Example: :ref:`enabling_authorization_policy`. -Traversal -~~~~~~~~~ - -:term:`Traversal` is a concept stolen from :term:`Zope`. It allows you to -create a tree of resources, each of which can be addressed by one or more URLs. -Each of those resources can have one or more *views* associated with it. If -your data isn't naturally treelike, or you're unwilling to create a treelike -representation of your data, you aren't going to find traversal very useful. -However, traversal is absolutely fantastic for sites that need to be -arbitrarily extensible. It's a lot easier to add a node to a tree than it is to -shoehorn a route into an ordered list of other routes, or to create another -entire instance of an application to service a department and glue code to -allow disparate apps to share data. It's a great fit for sites that naturally -lend themselves to changing departmental hierarchies, such as content -management systems and document management systems. Traversal also lends -itself well to systems that require very granular security ("Bob can edit -*this* document" as opposed to "Bob can edit documents"). +Build Trees of Resources +------------------------ + +:app:`Pyramid` supports :term:`Traversal`, a way of mapping URLs to a concrete +tree of resources. 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. Examples: :ref:`hello_traversal_chapter` and :ref:`much_ado_about_traversal_chapter`. -Tweens -~~~~~~ +Take Action on Each Request with Tweens +--------------------------------------- + +Pyramid has a system for applying arbitrary actions to each request or response +called *tweens*. The system is similar in concept to WSGI :term:`middleware`, +but can be more useful since they run in the Pyramid context, and have access +to templates, request objects, and other niceties. -Pyramid has a sort of internal WSGI-middleware-ish pipeline that can be hooked -by arbitrary add-ons named "tweens". The debug toolbar is a "tween", and the -``pyramid_tm`` transaction manager is also. Tweens are more useful than WSGI -:term:`middleware` in some circumstances because they run in the context of -Pyramid itself, meaning you have access to templates and other renderers, a -"real" request object, and other niceties. +The Pyramid debug toolbar is a "tween", as is the ``pyramid_tm`` transaction +manager. Example: :ref:`registering_tweens`. -View response adapters -~~~~~~~~~~~~~~~~~~~~~~ +Return What You Want From Your Views +------------------------------------ -A lot is made of the aesthetics of what *kinds* of objects you're allowed to -return from view callables in various frameworks. In a previous section in -this document, we showed you that, if you use a :term:`renderer`, you can -usually return a dictionary from a view callable instead of a full-on -:term:`Response` object. But some frameworks allow you to return strings or -tuples from view callables. When frameworks allow for this, code looks -slightly prettier, because fewer imports need to be done, and there is less -code. For example, compare this: +We have shown before (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 "Hello world!" + return Response("Hello world!") To this: .. code-block:: python :linenos: - from pyramid.response import Response - def aview(request): - return Response("Hello world!") + return "Hello world!" -The former is "prettier", right? +Nicer to look at, right? -Out of the box, if you define the former view callable (the one that simply -returns a string) in Pyramid, when it is executed, Pyramid will raise an -exception. This is because "explicit is better than implicit", in most cases, -and by default Pyramid wants you to return a :term:`Response` object from a -view callable. This is because there's usually a heck of a lot more to a -response object than just its body. But if you're the kind of person who -values such aesthetics, we have an easy way to allow for this sort of thing: +Out of the box, 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, Pyramid +provides an way to support this sort of thing, the *response adapter*: .. code-block:: python :linenos: @@ -217,12 +224,13 @@ values such aesthetics, we have an easy way to allow for this sort of thing: 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) -Do that once in your Pyramid application at startup. Now you can return -strings from any of your view callables, e.g.: +With that, you may return strings from any of your view callables, e.g.: .. code-block:: python :linenos: @@ -233,8 +241,8 @@ strings from any of your view callables, e.g.: def goodbyeview(request): return "Goodbye world!" -Oh noes! What if you want to indicate a custom content type? And a custom -status code? No fear: +You can even use a response adapter to allow for custom content types and +return codes: .. code-block:: python :linenos: @@ -259,7 +267,7 @@ status code? No fear: config.add_response_adapter(string_response_adapter, basestring) config.add_response_adapter(tuple_response_adapter, tuple) -Once this is done, both of these view callables will work: +With this, both of these views will work as expected: .. code-block:: python :linenos: @@ -270,20 +278,17 @@ Once this is done, both of these view callables will work: def anotherview(request): return (403, 'text/plain', "Forbidden") -Pyramid defaults to explicit behavior, because it's the most generally useful, -but provides hooks that allow you to adapt the framework to localized aesthetic -desires. - .. seealso:: See also :ref:`using_iresponse`. -"Global" response object -~~~~~~~~~~~~~~~~~~~~~~~~ +Use Global Response Objects +--------------------------- -"Constructing these response objects in my view callables is such a chore! And -I'm way too lazy to register a response adapter, as per the prior section," you -say. Fine. Be that way: +Views have to return responses. But constructing them in view code is a chore. +And perhaps registering a response adapter as shown above is just too much +work. :app:`Pyramid` provides a global response object as well. You can just +use it directly, if you prefer: .. code-block:: python :linenos: @@ -298,17 +303,18 @@ say. Fine. Be that way: See also :ref:`request_response_attr`. -Automating repetitive configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +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 Pyramid itself? -Does Pyramid's configurator allow you to do something, but you're a little -adventurous and just want it a little less verbose? Or you'd like to offer up -some handy configuration feature to other Pyramid users without requiring that -we change Pyramid? You can extend 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 -take the boring away by using existing shortcuts, but let's say that this is a -case where there is no such shortcut: +You can extend 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: @@ -352,26 +358,26 @@ the Configurator object: config.add_route('xhr_route', '/xhr/{id}') config.add_protected_xhr_views('my.package') -Your previously repetitive configuration lines have now morphed into one line. +Much better! -You can share your configuration code with others this way, too, by packaging -it up and calling :meth:`~pyramid.config.Configurator.add_directive` from -within a function called when another user uses the +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`. -Programmatic introspection -~~~~~~~~~~~~~~~~~~~~~~~~~~ +Introspect Your Application +--------------------------- -If you're building a large system that other users may plug code into, it's -useful to be able to get an enumeration of what code they plugged in *at -application runtime*. For example, you might want to show them a set of tabs -at the top of the screen based on an enumeration of views they registered. +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. -This is possible using Pyramid's :term:`introspector`. +:app:`Pyramid` provides an :term:`introspector` for just this purpose. Here's an example of using Pyramid's introspector from within a view callable: -- cgit v1.2.3