summaryrefslogtreecommitdiff
path: root/docs/narr/extconfig.rst
blob: 6587aef928dca415575e7fe57e9c39333015308a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
.. index::
   single: extending configuration

.. _extconfig_narr:

Extending Pyramid Configuration
===============================

Pyramid allows you to extend its Configurator with custom directives.  Custom
directives can use other directives, they can add a custom :term:`action`,
they can participate in :term:`conflict resolution`, and they can provide
some number of :term:`introspectable` objects.

.. index::
   single: add_directive
   pair: configurator; adding directives

.. _add_directive:

Adding Methods to the Configurator via ``add_directive``
--------------------------------------------------------

Framework extension writers can add arbitrary methods to a
:term:`Configurator` by using the
:meth:`pyramid.config.Configurator.add_directive` method of the configurator.
Using :meth:`~pyramid.config.Configurator.add_directive` makes it possible to
extend a Pyramid configurator in arbitrary ways, and allows it to perform
application-specific tasks more succinctly.

The :meth:`~pyramid.config.Configurator.add_directive` method accepts two
positional arguments: a method name and a callable object.  The callable
object is usually a function that takes the configurator instance as its
first argument and accepts other arbitrary positional and keyword arguments.
For example:

.. code-block:: python
   :linenos:

   from pyramid.events import NewRequest
   from pyramid.config import Configurator

   def add_newrequest_subscriber(config, subscriber):
       config.add_subscriber(subscriber, NewRequest)

   if __name__ == '__main__':
       config = Configurator()
       config.add_directive('add_newrequest_subscriber',
                            add_newrequest_subscriber)

Once :meth:`~pyramid.config.Configurator.add_directive` is called, a user can
then call the added directive by its given name as if it were a built-in
method of the Configurator:

.. code-block:: python
   :linenos:

   def mysubscriber(event):
       print(event.request)

   config.add_newrequest_subscriber(mysubscriber)

A call to :meth:`~pyramid.config.Configurator.add_directive` is often
"hidden" within an ``includeme`` function within a "frameworky" package meant
to be included as per :ref:`including_configuration` via
:meth:`~pyramid.config.Configurator.include`.  For example, if you put this
code in a package named ``pyramid_subscriberhelpers``:

.. code-block:: python
   :linenos:

   def includeme(config):
       config.add_directive('add_newrequest_subscriber',
                            add_newrequest_subscriber)

The user of the add-on package ``pyramid_subscriberhelpers`` would then be
able to install it and subsequently do:

.. code-block:: python
   :linenos:

   def mysubscriber(event):
       print(event.request)

   from pyramid.config import Configurator
   config = Configurator()
   config.include('pyramid_subscriberhelpers')
   config.add_newrequest_subscriber(mysubscriber)

Using ``config.action`` in a Directive
--------------------------------------

If a custom directive can't do its work exclusively in terms of existing
configurator methods (such as
:meth:`pyramid.config.Configurator.add_subscriber`, as above), the directive
may need to make use of the :meth:`pyramid.config.Configurator.action`
method.  This method adds an entry to the list of "actions" that Pyramid will
attempt to process when :meth:`pyramid.config.Configurator.commit` is called.
An action is simply a dictionary that includes a :term:`discriminator`,
possibly a callback function, and possibly other metadata used by Pyramid's
action system.

Here's an example directive which uses the "action" method:

.. code-block:: python
   :linenos:

   def add_jammyjam(config, jammyjam):
       def register():
           config.registry.jammyjam = jammyjam
       config.action('jammyjam', register)

   if __name__ == '__main__':
       config = Configurator()
       config.add_directive('add_jammyjam', add_jammyjam)

Fancy, but what does it do?  The action method accepts a number of arguments.
In the above directive named ``add_jammyjam``, we call
:meth:`~pyramid.config.Configurator.action` with two arguments: the string
``jammyjam`` is passed as the first argument named ``discriminator``, and the
closure function named ``register`` is passed as the second argument named
``callable``.

When the :meth:`~pyramid.config.Configurator.action` method is called, it
appends an action to the list of pending configuration actions.  All pending
actions with the same discriminator value are potentially in conflict with
one another (see :ref:`conflict_detection`).  When the
:meth:`~pyramid.config.Configurator.commit` method of the Configurator is
called (either explicitly or as the result of calling
:meth:`~pyramid.config.Configurator.make_wsgi_app`), conflicting actions are
potentially automatically resolved as per
:ref:`automatic_conflict_resolution`.  If a conflict cannot be automatically
resolved, a :exc:`pyramid.exceptions.ConfigurationConflictError` is raised
and application startup is prevented.

In our above example, therefore, if a consumer of our ``add_jammyjam``
directive did this:

.. code-block:: python

   config.add_jammyjam('first')
   config.add_jammyjam('second')

When the action list was committed resulting from the set of calls above, our
user's application would not start, because the discriminators of the actions
generated by the two calls are in direct conflict.  Automatic conflict
resolution cannot resolve the conflict (because no ``config.include`` is
involved), and the user provided no intermediate
:meth:`pyramid.config.Configurator.commit` call between the calls to
``add_jammyjam`` to ensure that the successive calls did not conflict with
each other.

This demonstrates the purpose of the discriminator argument to the action
method: it's used to indicate a uniqueness constraint for an action.  Two
actions with the same discriminator will conflict unless the conflict is
automatically or manually resolved. A discriminator can be any hashable
object, but it is generally a string or a tuple.  *You use a discriminator to
declaratively ensure that the user doesn't provide ambiguous configuration
statements.*

But let's imagine that a consumer of ``add_jammyjam`` used it in such a way
that no configuration conflicts are generated.

.. code-block:: python

   config.add_jammyjam('first')

What happens now?  When the ``add_jammyjam`` method is called, an action is
appended to the pending actions list.  When the pending configuration actions
are processed during :meth:`~pyramid.config.Configurator.commit`, and no
conflicts occur, the *callable* provided as the second argument to the
:meth:`~pyramid.config.Configurator.action` method within ``add_jammyjam`` is
called with no arguments.  The callable in ``add_jammyjam`` is the
``register`` closure function.  It simply sets the value
``config.registry.jammyjam`` to whatever the user passed in as the
``jammyjam`` argument to the ``add_jammyjam`` function.  Therefore, the
result of the user's call to our directive will set the ``jammyjam``
attribute of the registry to the string ``first``.  *A callable is used by a
directive to defer the result of a user's call to the directive until
conflict detection has had a chance to do its job*.

Other arguments exist to the :meth:`~pyramid.config.Configurator.action`
method, including ``args``, ``kw``, ``order``, and ``introspectables``.  

``args`` and ``kw`` exist as values, which, if passed, will be used as
arguments to the ``callable`` function when it is called back.  For example
our directive might use them like so:

.. code-block:: python
   :linenos:

   def add_jammyjam(config, jammyjam):
       def register(*arg, **kw):
           config.registry.jammyjam_args = arg
           config.registry.jammyjam_kw = kw
           config.registry.jammyjam = jammyjam
       config.action('jammyjam', register, args=('one',), kw={'two':'two'})

In the above example, when this directive is used to generate an action, and
that action is committed, ``config.registry.jammyjam_args`` will be set to
``('one',)`` and ``config.registry.jammyjam_kw`` will be set to
``{'two':'two'}``.  ``args`` and ``kw`` are honestly not very useful when
your ``callable`` is a closure function, because you already usually have
access to every local in the directive without needing them to be passed
back.  They can be useful, however, if you don't use a closure as a callable.

``order`` is a crude order control mechanism.  ``order`` defaults to the
integer ``0``; it can be set to any other integer.  All actions that share an
order will be called before other actions that share a higher order.  This
makes it possible to write a directive with callable logic that relies on the
execution of the callable of another directive being done first.  For
example, Pyramid's :meth:`pyramid.config.Configurator.add_view` directive
registers an action with a higher order than the
:meth:`pyramid.config.Configurator.add_route` method.  Due to this, the
``add_view`` method's callable can assume that, if a ``route_name`` was
passed to it, that a route by this name was already registered by
``add_route``, and if such a route has not already been registered, it's a
configuration error (a view that names a nonexistent route via its
``route_name`` parameter will never be called).

``introspectables`` is a sequence of :term:`introspectable` objects.  You can
pass a sequence of introspectables to the
:meth:`~pyramid.config.Configurator.action` method, which allows you to
augment Pyramid's configuration introspection system.

.. _introspection:

Adding Configuration Introspection
----------------------------------

.. versionadded:: 1.3

Pyramid provides a configuration introspection system that can be used by
debugging tools to provide visibility into the configuration of a running
application.

All built-in Pyramid directives (such as
:meth:`pyramid.config.Configurator.add_view` and
:meth:`pyramid.config.Configurator.add_route`) register a set of
introspectables when called.  For example, when you register a view via
``add_view``, the directive registers at least one introspectable: an
introspectable about the view registration itself, providing human-consumable
values for the arguments it was passed.  You can later use the introspection
query system to determine whether a particular view uses a renderer, or
whether a particular view is limited to a particular request method, or which
routes a particular view is registered against.  The Pyramid "debug toolbar"
makes use of the introspection system in various ways to display information
to Pyramid developers.

Introspection values are set when a sequence of :term:`introspectable`
objects is passed to the :meth:`~pyramid.config.Configurator.action` method.
Here's an example of a directive which uses introspectables:

.. code-block:: python
   :linenos:

   def add_jammyjam(config, value):
       def register():
           config.registry.jammyjam = value
       intr = config.introspectable(category_name='jammyjams', 
                                    discriminator='jammyjam',
                                    title='a jammyjam',
                                    type_name=None)
       intr['value'] = value
       config.action('jammyjam', register, introspectables=(intr,))

   if __name__ == '__main__':
       config = Configurator()
       config.add_directive('add_jammyjam', add_jammyjam)

If you notice, the above directive uses the ``introspectable`` attribute of a
Configurator (:attr:`pyramid.config.Configurator.introspectable`) to create
an introspectable object.  The introspectable object's constructor requires
at least four arguments: the ``category_name``, the ``discriminator``, the
``title``, and the ``type_name``.

The ``category_name`` is a string representing the logical category for this
introspectable.  Usually the category_name is a pluralization of the type of
object being added via the action.

The ``discriminator`` is a value unique **within the category** (unlike the
action discriminator, which must be unique within the entire set of actions).
It is typically a string or tuple representing the values unique to this
introspectable within the category.  It is used to generate links and as part
of a relationship-forming target for other introspectables.

The ``title`` is a human-consumable string that can be used by introspection
system frontends to show a friendly summary of this introspectable.

The ``type_name`` is a value that can be used to subtype this introspectable
within its category for sorting and presentation purposes.  It can be any
value.

An introspectable is also dictionary-like.  It can contain any set of
key/value pairs, typically related to the arguments passed to its related
directive.  While the category_name, discriminator, title and type_name are
*metadata* about the introspectable, the values provided as key/value pairs
are the actual data provided by the introspectable.  In the above example, we
set the ``value`` key to the value of the ``value`` argument passed to the
directive.

Our directive above mutates the introspectable, and passes it in to the
``action`` method as the first element of a tuple as the value of the
``introspectable`` keyword argument.  This associates this introspectable
with the action.  Introspection tools will then display this introspectable
in their index.

Introspectable Relationships
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Two introspectables may have relationships between each other.

.. code-block:: python
   :linenos:

   def add_jammyjam(config, value, template):
       def register():
           config.registry.jammyjam = (value, template)
       intr = config.introspectable(category_name='jammyjams', 
                                    discriminator='jammyjam',
                                    title='a jammyjam',
                                    type_name=None)
       intr['value'] = value
       tmpl_intr = config.introspectable(category_name='jammyjam templates',
                                         discriminator=template,
                                         title=template,
                                         type_name=None)
       tmpl_intr['value'] = template
       intr.relate('jammyjam templates', template)
       config.action('jammyjam', register, introspectables=(intr, tmpl_intr))

   if __name__ == '__main__':
       config = Configurator()
       config.add_directive('add_jammyjam', add_jammyjam)

In the above example, the ``add_jammyjam`` directive registers two
introspectables.  The first is related to the ``value`` passed to the
directive; the second is related to the ``template`` passed to the directive.
If you believe a concept within a directive is important enough to have its
own introspectable, you can cause the same directive to register more than
one introspectable, registering one introspectable for the "main idea" and
another for a related concept.

The call to ``intr.relate`` above
(:meth:`pyramid.interfaces.IIntrospectable.relate`) is passed two arguments:
a category name and a directive.  The example above effectively indicates
that the directive wishes to form a relationship between the ``intr``
introspectable and the ``tmpl_intr`` introspectable; the arguments passed to
``relate`` are the category name and discriminator of the ``tmpl_intr``
introspectable.

Relationships need not be made between two introspectables created by the
same directive.  Instead, a relationship can be formed between an
introspectable created in one directive and another introspectable created in
another by calling ``relate`` on either side with the other directive's
category name and discriminator.  An error will be raised at configuration
commit time if you attempt to relate an introspectable with another
nonexistent introspectable, however.

Introspectable relationships will show up in frontend system renderings of
introspection values.  For example, if a view registration names a route
name, the introspectable related to the view callable will show a reference
to the route to which it relates to and vice versa.