From f20a018167a19d17527d40c027e6f9045749f065 Mon Sep 17 00:00:00 2001 From: cewing Date: Tue, 23 May 2017 20:21:07 -0700 Subject: fixes per code review, Thanks @stevepiercy. --- docs/narr/advanced-features.rst | 418 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 418 insertions(+) create mode 100644 docs/narr/advanced-features.rst (limited to 'docs/narr/advanced-features.rst') 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 -- cgit v1.2.3 From 44c621a5b8320848933024280dc491dec844c184 Mon Sep 17 00:00:00 2001 From: cewing Date: Tue, 23 May 2017 21:07:42 -0700 Subject: finish polishing the advanced configuration doc per code review --- docs/narr/advanced-features.rst | 44 +++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 21 deletions(-) (limited to 'docs/narr/advanced-features.rst') diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst index a97d4f3b1..35841631f 100644 --- a/docs/narr/advanced-features.rst +++ b/docs/narr/advanced-features.rst @@ -245,6 +245,8 @@ But if you're a developer who likes the aesthetics of simplicity, A new response adapter is registered in configuration: +.. code-block:: python + if __name__ == '__main__': config = Configurator() config.add_response_adapter(string_response_adapter, basestring) @@ -325,15 +327,14 @@ use it directly, if you prefer: 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? +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: +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: @@ -349,8 +350,8 @@ where there is no such shortcut: 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: +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: @@ -368,8 +369,8 @@ Pretty tedious right? You can add a directive to the :app:`Pyramid` 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: +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: @@ -379,10 +380,11 @@ the :term:`configurator` object: 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. +You can share your configuration code with others, too. +Add your code to a Python package. +Put the call to :meth:`~pyramid.config.Configurator.add_directive` in a function. +When other programmers install your package, +they'll be able to use your configuration by passing your function to a call to :meth:`~pyramid.config.Configurator.include`. .. seealso:: @@ -391,14 +393,14 @@ called when another user uses the 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. +If you're building a large, pluggable 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: +Here's an example of using :app:`Pyramid`\ 's :term:`introspector` from within a view: .. code-block:: python :linenos: -- cgit v1.2.3 From e20ed7d9271d4e824a08d23be1ed942db0756a86 Mon Sep 17 00:00:00 2001 From: cewing Date: Thu, 25 May 2017 09:05:35 -0700 Subject: fix code indentation and unify style for all code blocks, per CR --- docs/narr/advanced-features.rst | 165 ++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 81 deletions(-) (limited to 'docs/narr/advanced-features.rst') diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst index 35841631f..ae28ba41b 100644 --- a/docs/narr/advanced-features.rst +++ b/docs/narr/advanced-features.rst @@ -30,6 +30,7 @@ How many times have you found yourself beginning the logic of your view code with something like this: .. code-block:: python + :linenos: if request.user.is_authenticated: # do one thing @@ -51,6 +52,7 @@ 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 + :linenos: @view_config(route_name="items", effective_principals=pyramid.security.Authenticated) def auth_view(request): @@ -135,15 +137,15 @@ bunch of routes, you can just use the ``include`` statement with a as you requested: .. code-block:: python - :linenos: + :linenos: - from pyramid.config import Configurator + 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') + if __name__ == '__main__': + config = Configurator() + config.include('pyramid_jinja2') + config.include('pyramid_exclog') + config.include('some.other.package', route_prefix='/somethingelse') .. seealso:: @@ -207,20 +209,20 @@ 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: + :linenos: - from pyramid.response import Response + from pyramid.response import Response - def aview(request): - return Response("Hello world!") + def aview(request): + return Response("Hello world!") To this: .. code-block:: python - :linenos: + :linenos: - def aview(request): - return "Hello world!" + def aview(request): + return "Hello world!" Nicer to look at, right? @@ -233,71 +235,72 @@ But if you're a developer who likes the aesthetics of simplicity, :term:`response adapter`\ : .. code-block:: python - :linenos: + :linenos: - from pyramid.config import Configurator - from pyramid.response import Response + 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 + def string_response_adapter(s): + response = Response(s) + response.content_type = 'text/html' + return response A new response adapter is registered in configuration: .. code-block:: python + :linenos: - if __name__ == '__main__': - config = Configurator() - config.add_response_adapter(string_response_adapter, basestring) + 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: + :linenos: - def helloview(request): - return "Hello world!" + def helloview(request): + return "Hello world!" - def goodbyeview(request): - return "Goodbye 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: + :linenos: - from pyramid.config import Configurator + 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 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 + 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) + 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: + :linenos: - def aview(request): - return "Hello world!" + def aview(request): + return "Hello world!" - def anotherview(request): - return (403, 'text/plain', "Forbidden") + def anotherview(request): + return (403, 'text/plain', "Forbidden") .. seealso:: @@ -312,13 +315,13 @@ much work. :app:`Pyramid` provides a global response object as well. You can use it directly, if you prefer: .. code-block:: python - :linenos: + :linenos: - def aview(request): - response = request.response - response.body = 'Hello world!' - response.content_type = 'text/plain' - return response + def aview(request): + response = request.response + response.body = 'Hello world!' + response.content_type = 'text/plain' + return response .. seealso:: @@ -337,46 +340,46 @@ 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: + :linenos: - from pyramid.config import Configurator + 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') + 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: + :linenos: - from pyramid.config import Configurator + 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) + 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) + 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: + :linenos: - config.add_route('xhr_route', '/xhr/{id}') - config.add_protected_xhr_views('my.package') + config.add_route('xhr_route', '/xhr/{id}') + config.add_protected_xhr_views('my.package') Much better! -- cgit v1.2.3 From f42ab136cd5d2c98c34b101d458750f638380d08 Mon Sep 17 00:00:00 2001 From: cewing Date: Sat, 3 Jun 2017 16:06:38 -0700 Subject: use str in deference to Py3 style over Py2 --- docs/narr/advanced-features.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs/narr/advanced-features.rst') diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst index ae28ba41b..9ed0cc712 100644 --- a/docs/narr/advanced-features.rst +++ b/docs/narr/advanced-features.rst @@ -252,7 +252,7 @@ A new response adapter is registered in configuration: if __name__ == '__main__': config = Configurator() - config.add_response_adapter(string_response_adapter, basestring) + config.add_response_adapter(string_response_adapter, str) With that, you may return strings from any of your view callables, e.g.: @@ -288,7 +288,7 @@ and return codes: if __name__ == '__main__': config = Configurator() - config.add_response_adapter(string_response_adapter, basestring) + config.add_response_adapter(string_response_adapter, str) config.add_response_adapter(tuple_response_adapter, tuple) With this, both of these views will work as expected: -- cgit v1.2.3 From 794fd355156224b9ce93651837a311dbf6ac7040 Mon Sep 17 00:00:00 2001 From: cewing Date: Sat, 3 Jun 2017 16:29:01 -0700 Subject: finish all app references for Pyramid and refold line lengths --- docs/narr/advanced-features.rst | 96 +++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 46 deletions(-) (limited to 'docs/narr/advanced-features.rst') diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst index 9ed0cc712..63bc5d3e7 100644 --- a/docs/narr/advanced-features.rst +++ b/docs/narr/advanced-features.rst @@ -1,9 +1,8 @@ Advanced :app:`Pyramid` Design Features ======================================= -Pyramid has been built from the ground up to avoid the problems that other -frameworks can suffer. - +:app:`Pyramid` has been built from the ground up to avoid the problems +that other frameworks can suffer. You Don't Need Singletons ------------------------- @@ -37,8 +36,8 @@ with something like this: 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 +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. @@ -72,10 +71,11 @@ understandable, and more directly testable. 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. +: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 @@ -86,10 +86,10 @@ 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. +:app:`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:: @@ -103,10 +103,10 @@ 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 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 @@ -123,9 +123,9 @@ 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. +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. @@ -155,9 +155,10 @@ as you requested: 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. +: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 @@ -202,11 +203,11 @@ transaction manager. 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: +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: @@ -334,10 +335,11 @@ 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: +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: @@ -353,8 +355,8 @@ but let's say that this is a case where there is no such shortcut: 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: +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: @@ -372,8 +374,8 @@ You can add a directive to the :app:`Pyramid` :term:`configurator` to automate s 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: +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: @@ -383,11 +385,12 @@ you can call the directive you've just added as a method of the :term:`configura Much better! -You can share your configuration code with others, too. -Add your code to a Python package. -Put the call to :meth:`~pyramid.config.Configurator.add_directive` in a function. -When other programmers install your package, -they'll be able to use your configuration by passing your function to a call to :meth:`~pyramid.config.Configurator.include`. +You can share your configuration code with others, too. Add your code to a +Python package. Put the call to +:meth:`~pyramid.config.Configurator.add_directive` in a function. When other +programmers install your package, they'll be able to use your configuration by +passing your function to a call to +:meth:`~pyramid.config.Configurator.include`. .. seealso:: @@ -396,14 +399,15 @@ they'll be able to use your configuration by passing your function to a call to Introspect Your Application --------------------------- -If you're building a large, pluggable 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. +If you're building a large, pluggable 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 :app:`Pyramid`\ 's :term:`introspector` from within a view: +Here's an example of using :app:`Pyramid`\ 's :term:`introspector` from within +a view: .. code-block:: python :linenos: -- cgit v1.2.3 From be4b64f19c1dfde34d95e492c0554f13d8b8f797 Mon Sep 17 00:00:00 2001 From: cewing Date: Mon, 5 Jun 2017 08:49:30 -0700 Subject: fix more comments and refold lines, one per paragraph as specified in stylesheet. --- docs/narr/advanced-features.rst | 173 +++++++++------------------------------- 1 file changed, 38 insertions(+), 135 deletions(-) (limited to 'docs/narr/advanced-features.rst') diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst index 63bc5d3e7..82e20963d 100644 --- a/docs/narr/advanced-features.rst +++ b/docs/narr/advanced-features.rst @@ -1,32 +1,21 @@ Advanced :app:`Pyramid` Design Features ======================================= -:app:`Pyramid` has been built from the ground up to avoid the problems -that other frameworks can suffer. +:app:`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? +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. +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. +: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: +How many times have you found yourself beginning the logic of your view code with something like this: .. code-block:: python :linenos: @@ -36,17 +25,9 @@ with something like this: 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. +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. +: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: @@ -61,8 +42,7 @@ For our example above, you can do this instead: 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. +This approach allows you to develop view code that is simpler, more easily understandable, and more directly testable. .. seealso:: @@ -71,47 +51,26 @@ understandable, and more directly testable. 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. +: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. +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. +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. -:app:`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. +:app:`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). + 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. +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 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. +: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:: @@ -120,21 +79,12 @@ local ones. So you can intelligently factor large systems into smaller ones. 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. +Speaking of the :app:`Pyramid` structured :meth:`~pyramid.config.Configurator.include` mechanism, 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. +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: +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: @@ -149,21 +99,14 @@ as you requested: .. seealso:: - See also :ref:`including_configuration` and - :ref:`building_an_extensible_app`. + 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. +: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. +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:: @@ -172,29 +115,18 @@ configuration to make the change. 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. +: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`. + 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. +: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. +The :app:`Pyramid` debug toolbar is a :term:`tween`, as is the ``pyramid_tm`` transaction manager. .. seealso:: @@ -203,11 +135,7 @@ transaction manager. 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: +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: @@ -227,13 +155,9 @@ To this: 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". +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`\ : +But if you're a developer who likes the aesthetics of simplicity, :app:`Pyramid` provides a way to support this sort of thing, the :term:`response adapter`\ : .. code-block:: python :linenos: @@ -266,8 +190,7 @@ With that, you may return strings from any of your view callables, e.g.: def goodbyeview(request): return "Goodbye world!" -You can even use a :term:`response adapter` to allow for custom content types -and return codes: +You can even use a :term:`response adapter` to allow for custom content types and return codes: .. code-block:: python :linenos: @@ -310,10 +233,7 @@ With this, both of these views will work as expected: 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: +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: @@ -331,15 +251,9 @@ use it directly, if you prefer: 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? +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: +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: @@ -355,8 +269,7 @@ where there is no such shortcut: 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: +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: @@ -374,8 +287,7 @@ Pretty tedious right? You can add a directive to the :app:`Pyramid` 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: +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: @@ -385,12 +297,7 @@ the :term:`configurator` object: Much better! -You can share your configuration code with others, too. Add your code to a -Python package. Put the call to -:meth:`~pyramid.config.Configurator.add_directive` in a function. When other -programmers install your package, they'll be able to use your configuration by -passing your function to a call to -:meth:`~pyramid.config.Configurator.include`. +You can share your configuration code with others, too. Add your code to a Python package. Put the call to :meth:`~pyramid.config.Configurator.add_directive` in a function. When other programmers install your package, they'll be able to use your configuration by passing your function to a call to :meth:`~pyramid.config.Configurator.include`. .. seealso:: @@ -399,15 +306,11 @@ passing your function to a call to Introspect Your Application --------------------------- -If you're building a large, pluggable 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. +If you're building a large, pluggable 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 :app:`Pyramid`\ 's :term:`introspector` from within -a view: +Here's an example of using :app:`Pyramid`\ 's :term:`introspector` from within a view: .. code-block:: python :linenos: -- cgit v1.2.3