/
configuration.py
2477 lines (2121 loc) · 107 KB
/
configuration.py
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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import os
import re
import sys
import threading
import inspect
import venusian
from translationstring import ChameleonTranslate
from zope.configuration import xmlconfig
from zope.interface import Interface
from zope.interface import implementedBy
from zope.interface.interfaces import IInterface
from zope.interface import implements
from repoze.bfg.interfaces import IAuthenticationPolicy
from repoze.bfg.interfaces import IAuthorizationPolicy
from repoze.bfg.interfaces import IChameleonTranslate
from repoze.bfg.interfaces import IDebugLogger
from repoze.bfg.interfaces import IDefaultRootFactory
from repoze.bfg.interfaces import IExceptionViewClassifier
from repoze.bfg.interfaces import ILocaleNegotiator
from repoze.bfg.interfaces import IMultiView
from repoze.bfg.interfaces import IPackageOverrides
from repoze.bfg.interfaces import IRendererFactory
from repoze.bfg.interfaces import IRendererGlobalsFactory
from repoze.bfg.interfaces import IRequest
from repoze.bfg.interfaces import IRequestFactory
from repoze.bfg.interfaces import IRootFactory
from repoze.bfg.interfaces import IRouteRequest
from repoze.bfg.interfaces import IRoutesMapper
from repoze.bfg.interfaces import ISecuredView
from repoze.bfg.interfaces import ISettings
from repoze.bfg.interfaces import IStaticURLInfo
from repoze.bfg.interfaces import ITranslationDirectories
from repoze.bfg.interfaces import ITraverser
from repoze.bfg.interfaces import IView
from repoze.bfg.interfaces import IViewClassifier
from repoze.bfg.interfaces import IExceptionResponse
from repoze.bfg.interfaces import IException
from repoze.bfg import chameleon_text
from repoze.bfg import chameleon_zpt
from repoze.bfg import renderers
from repoze.bfg.renderers import _render_to_response
from repoze.bfg.authorization import ACLAuthorizationPolicy
from repoze.bfg.compat import all
from repoze.bfg.compat import md5
from repoze.bfg.events import WSGIApplicationCreatedEvent
from repoze.bfg.exceptions import Forbidden
from repoze.bfg.exceptions import NotFound
from repoze.bfg.exceptions import PredicateMismatch
from repoze.bfg.exceptions import ConfigurationError
from repoze.bfg.i18n import get_localizer
from repoze.bfg.log import make_stream_logger
from repoze.bfg.path import caller_package
from repoze.bfg.path import package_path
from repoze.bfg.registry import Registry
from repoze.bfg.request import route_request_iface
from repoze.bfg.resource import PackageOverrides
from repoze.bfg.resource import resolve_resource_spec
from repoze.bfg.settings import Settings
from repoze.bfg.static import StaticURLInfo
from repoze.bfg.threadlocal import get_current_registry
from repoze.bfg.threadlocal import get_current_request
from repoze.bfg.threadlocal import manager
from repoze.bfg.traversal import traversal_path
from repoze.bfg.traversal import DefaultRootFactory
from repoze.bfg.traversal import find_interface
from repoze.bfg.urldispatch import RoutesMapper
from repoze.bfg.view import render_view_to_response
from repoze.bfg.view import default_exceptionresponse_view
MAX_ORDER = 1 << 30
DEFAULT_PHASH = md5().hexdigest()
DEFAULT_RENDERERS = (
('.pt', chameleon_zpt.renderer_factory),
('.txt', chameleon_text.renderer_factory),
('json', renderers.json_renderer_factory),
('string', renderers.string_renderer_factory),
)
class Configurator(object):
"""
A Configurator is used to configure a :mod:`repoze.bfg`
:term:`application registry`.
The Configurator accepts a number of arguments: ``registry``,
``package``, ``settings``, ``root_factory``,
``authentication_policy``, ``authorization_policy``, ``renderers``
``debug_logger``, ``locale_negotiator``, ``request_factory``, and
``renderer_globals_factory``.
If the ``registry`` argument is passed as a non-``None`` value, it
must be an instance of the :class:`repoze.bfg.registry.Registry`
class representing the registry to configure. If ``registry`` is
``None``, the configurator will create a
:class:`repoze.bfg.registry.Registry` instance itself; it will
also perform some default configuration that would not otherwise
be done. After construction, the configurator may be used to add
configuration to the registry. The overall state of a registry is
called the 'configuration state'.
.. warning:: If a ``registry`` is passed to the Configurator
constructor, all other constructor arguments except ``package``
are ignored.
If the ``package`` argument is passed, it must be a reference to a
Python :term:`package` (e.g. ``sys.modules['thepackage']``). This
value is used as a basis to convert relative paths passed to
various configuration methods, such as methods which accept a
``renderer`` argument, into absolute paths. If ``None`` is passed
(the default), the package is assumed to be the Python package in
which the *caller* of the ``Configurator`` constructor lives.
If the ``settings`` argument is passed, it should be a Python
dictionary representing the deployment settings for this
application. These are later retrievable using the
:meth:`repoze.bfg.configuration.Configurator.get_settings` and
:func:`repoze.bfg.settings.get_settings` APIs.
If the ``root_factory`` argument is passed, it should be an object
representing the default :term:`root factory` for your
application. If it is ``None``, a default root factory will be
used.
If ``authentication_policy`` is passed, it should be an instance
of an :term:`authentication policy`.
If ``authorization_policy`` is passed, it should be an instance
of an :term:`authorization policy`.
.. note:: A ``ConfigurationError`` will be raised when an
authorization policy is supplied without also supplying an
authentication policy (authorization requires authentication).
If ``renderers`` is passed, it should be a list of tuples
representing a set of :term:`renderer` factories which should be
configured into this application. If it is not passed, a default
set of renderer factories is used.
If ``debug_logger`` is not passed, a default debug logger that
logs to stderr will be used. If it is passed, it should be an
instance of the :class:`logging.Logger` (PEP 282) standard library
class. The debug logger is used by :mod:`repoze.bfg` itself to
log warnings and authorization debugging information.
If ``locale_negotiator`` is passed, it should be a
:term:`locale negotiator` implementation. See
:ref:`custom_locale_negotiator`.
"""
manager = manager # for testing injection
venusian = venusian # for testing injection
def __init__(self,
registry=None,
package=None,
settings=None,
root_factory=None,
authentication_policy=None,
authorization_policy=None,
renderers=DEFAULT_RENDERERS,
debug_logger=None,
locale_negotiator=None,
request_factory=None,
renderer_globals_factory=None):
self.package = package or caller_package()
self.registry = registry
if registry is None:
registry = Registry(self.package.__name__)
self.registry = registry
self.setup_registry(
settings=settings,
root_factory=root_factory,
authentication_policy=authentication_policy,
authorization_policy=authorization_policy,
renderers=renderers,
debug_logger=debug_logger,
locale_negotiator=locale_negotiator,
request_factory=request_factory,
renderer_globals_factory=renderer_globals_factory
)
def _set_settings(self, mapping):
settings = Settings(mapping or {})
self.registry.registerUtility(settings, ISettings)
return settings
def _set_root_factory(self, factory):
""" Add a :term:`root factory` to the current configuration
state. If the ``factory`` argument is ``None`` a default root
factory will be registered."""
if factory is None:
factory = DefaultRootFactory
self.registry.registerUtility(factory, IRootFactory)
self.registry.registerUtility(factory, IDefaultRootFactory) # b/c
def _renderer_factory_from_name(self, path_or_spec):
if '.' in path_or_spec:
name = os.path.splitext(path_or_spec)[1]
spec = self._make_spec(path_or_spec)
else:
name = path_or_spec
spec = path_or_spec
factory = self.registry.queryUtility(IRendererFactory, name=name)
return name, spec, factory
def _renderer_from_name(self, path_or_spec):
if path_or_spec is None:
# check for global default renderer
factory = self.registry.queryUtility(IRendererFactory)
if factory is not None:
return factory(path_or_spec)
return None
name, spec, factory = self._renderer_factory_from_name(path_or_spec)
if factory is None:
raise ValueError(
'No factory for renderer named %r when looking up %s' %
(name, spec))
return factory(spec)
def _set_authentication_policy(self, policy, _info=u''):
""" Add a :mod:`repoze.bfg` :term:`authentication policy` to
the current configuration."""
self.registry.registerUtility(policy, IAuthenticationPolicy, info=_info)
def _set_authorization_policy(self, policy, _info=u''):
""" Add a :mod:`repoze.bfg` :term:`authorization policy` to
the current configuration state."""
self.registry.registerUtility(policy, IAuthorizationPolicy, info=_info)
def _make_spec(self, path_or_spec):
package, filename = resolve_resource_spec(path_or_spec,
self.package.__name__)
if package is None:
return filename # absolute filename
return '%s:%s' % (package, filename)
def _split_spec(self, path_or_spec):
return resolve_resource_spec(path_or_spec, self.package.__name__)
def derive_view(self, view, attr=None, renderer=None):
"""
Create a :term:`view callable` using the function, instance,
or class provided as ``view`` object.
This is API is useful to framework extenders who create
pluggable systems which need to register 'proxy' view
callables for functions, instances, or classes which meet the
requirements of being a :mod:`repoze.bfg` view callable. For
example, a ``some_other_framework`` function in another
framework may want to allow a user to supply a view callable,
but he may want to wrap the view callable in his own before
registering the wrapper as a :mod:`repoze.bfg` view callable.
Because a :mod:`repoze.bfg` view callable can be any of a
number of valid objects, the framework extender will not know
how to call the user-supplied object. Running it through
``derive_view`` normalizes it to a callable which accepts two
arguments: ``context`` and ``request``.
For example:
.. code-block:: python
def some_other_framework(user_supplied_view):
config = Configurator(reg)
proxy_view = config.derive_view(user_supplied_view)
def my_wrapper(context, request):
do_something_that_mutates(request)
return proxy_view(context, request)
config.add_view(my_wrapper)
The ``view`` object provided should be one of the following:
- A function or another non-class callable object that accepts
a :term:`request` as a single positional argument and which
returns a :term:`response` object.
- A function or other non-class callable object that accepts
two positional arguments, ``context, request`` and which
returns a :term:`response` object.
- A class which accepts a single positional argument in its
constructor named ``request``, and which has a ``__call__``
method that accepts no arguments that returns a
:term:`response` object.
- A class which accepts two positional arguments named
``context, request``, and which has a ``__call__`` method
that accepts no arguments that returns a :term:`response`
object.
This API returns a callable which accepts the arguments
``context, request`` and which returns the result of calling
the provided ``view`` object.
The ``attr`` keyword argument is most useful when the view
object is a class. It names the method that should be used as
the callable. If ``attr`` is not provided, the attribute
effectively defaults to ``__call__``. See
:ref:`class_as_view` for more information.
The ``renderer`` keyword argument, if supplies, causes the
returned callable to use a :term:`renderer` to convert the
user-supplied view result to a :term:`response` object. If a
``renderer`` argument is not supplied, the user-supplied view
must itself return a :term:`response` object.
"""
return self._derive_view(view, attr=attr, renderer_name=renderer)
def _derive_view(self, view, permission=None, predicates=(),
attr=None, renderer_name=None, wrapper_viewname=None,
viewname=None, accept=None, order=MAX_ORDER,
phash=DEFAULT_PHASH):
renderer = self._renderer_from_name(renderer_name)
authn_policy = self.registry.queryUtility(IAuthenticationPolicy)
authz_policy = self.registry.queryUtility(IAuthorizationPolicy)
settings = self.registry.queryUtility(ISettings)
logger = self.registry.queryUtility(IDebugLogger)
mapped_view = _map_view(view, attr, renderer, renderer_name)
owrapped_view = _owrap_view(mapped_view, viewname, wrapper_viewname)
secured_view = _secure_view(owrapped_view, permission,
authn_policy, authz_policy)
debug_view = _authdebug_view(secured_view, permission,
authn_policy, authz_policy, settings,
logger)
predicated_view = _predicate_wrap(debug_view, predicates)
derived_view = _attr_wrap(predicated_view, accept, order, phash)
return derived_view
def _override(self, package, path, override_package, override_prefix,
_info=u'', PackageOverrides=PackageOverrides):
pkg_name = package.__name__
override_pkg_name = override_package.__name__
override = self.registry.queryUtility(
IPackageOverrides, name=pkg_name)
if override is None:
override = PackageOverrides(package)
self.registry.registerUtility(override, IPackageOverrides,
name=pkg_name, info=_info)
override.insert(path, override_pkg_name, override_prefix)
def _set_security_policies(self, authentication, authorization=None):
if authorization is None:
authorization = ACLAuthorizationPolicy() # default
if authorization and not authentication:
raise ConfigurationError(
'If the "authorization" is passed a value, '
'the "authentication" argument must also be '
'passed a value; authorization requires authentication.')
self._set_authentication_policy(authentication)
self._set_authorization_policy(authorization)
def _fix_registry(self):
""" Fix up a ZCA component registry that is not a
repoze.bfg.registry.Registry by adding analogues of
``has_listeners`` and ``notify`` through monkey-patching."""
if not hasattr(self.registry, 'notify'):
def notify(*events):
[ _ for _ in self.registry.subscribers(events, None) ]
self.registry.notify = notify
if not hasattr(self.registry, 'has_listeners'):
self.registry.has_listeners = True
# API
def setup_registry(self, settings=None, root_factory=None,
authentication_policy=None, authorization_policy=None,
renderers=DEFAULT_RENDERERS, debug_logger=None,
locale_negotiator=None, request_factory=None,
renderer_globals_factory=None):
""" When you pass a non-``None`` ``registry`` argument to the
:term:`Configurator` constructor, no initial 'setup' is
performed against the registry. This is because the registry
you pass in may have already been initialized for use under
:mod:`repoze.bfg` via a different configurator. However, in
some circumstances, such as when you want to use the Zope
'global` registry instead of a registry created as a result of
the Configurator constructor, or when you want to reset the
initial setup of a registry, you *do* want to explicitly
initialize the registry associated with a Configurator for use
under :mod:`repoze.bfg`. Use ``setup_registry`` to do this
initialization.
``setup_registry`` configures settings, a root factory,
security policies, renderers, a debug logger, a locale
negotiator, and various other settings using the
configurator's current registry, as per the descriptions in
the Configurator constructor."""
self._fix_registry()
self._set_settings(settings)
self._set_root_factory(root_factory)
if debug_logger is None:
debug_logger = make_stream_logger('repoze.bfg.debug', sys.stderr)
registry = self.registry
registry.registerUtility(debug_logger, IDebugLogger)
registry.registerUtility(debug_logger, IDebugLogger,
'repoze.bfg.debug') # b /c
if authentication_policy or authorization_policy:
self._set_security_policies(authentication_policy,
authorization_policy)
for name, renderer in renderers:
self.add_renderer(name, renderer)
self.add_view(default_exceptionresponse_view,
context=IExceptionResponse)
if locale_negotiator:
registry.registerUtility(locale_negotiator, ILocaleNegotiator)
if request_factory:
self.set_request_factory(request_factory)
if renderer_globals_factory:
self.set_renderer_globals_factory(renderer_globals_factory)
# getSiteManager is a unit testing dep injection
def hook_zca(self, getSiteManager=None):
""" Call :func:`zope.component.getSiteManager.sethook` with
the argument
:data:`repoze.bfg.threadlocal.get_current_registry`, causing
the :term:`Zope Component Architecture` 'global' APIs such as
:func:`zope.component.getSiteManager`,
:func:`zope.component.getAdapter` and others to use the
:mod:`repoze.bfg` :term:`application registry` rather than the
Zope 'global' registry. If :mod:`zope.component` cannot be
imported, this method will raise an :exc:`ImportError`."""
if getSiteManager is None:
from zope.component import getSiteManager
getSiteManager.sethook(get_current_registry)
# getSiteManager is a unit testing dep injection
def unhook_zca(self, getSiteManager=None):
""" Call :func:`zope.component.getSiteManager.reset` to undo
the action of
:meth:`repoze.bfg.configuration.Configurator.hook_zca`. If
:mod:`zope.component` cannot be imported, this method will
raise an :exc:`ImportError`."""
if getSiteManager is None: # pragma: no cover
from zope.component import getSiteManager
getSiteManager.reset()
def begin(self, request=None):
""" Indicate that application or test configuration has begun.
This pushes a dictionary containing the :term:`application
registry` implied by ``registry`` attribute of this
configurator and the :term:`request` implied by the
``request`` argument on to the :term:`thread local` stack
consulted by various :mod:`repoze.bfg.threadlocal` API
functions."""
self.manager.push({'registry':self.registry, 'request':request})
def end(self):
""" Indicate that application or test configuration has ended.
This pops the last value pushed on to the :term:`thread local`
stack (usually by the ``begin`` method) and returns that
value.
"""
return self.manager.pop()
def add_subscriber(self, subscriber, iface=None, info=u''):
"""Add an event :term:`subscriber` for the event stream
implied by the supplied ``iface`` interface. The
``subscriber`` argument represents a callable object; it will
be called with a single object ``event`` whenever
:mod:`repoze.bfg` emits an :term:`event` associated with the
``iface``. Using the default ``iface`` value, ``None`` will
cause the subscriber to be registered for all event types. See
:ref:`events_chapter` for more information about events and
subscribers."""
if iface is None:
iface = (Interface,)
if not isinstance(iface, (tuple, list)):
iface = (iface,)
self.registry.registerHandler(subscriber, iface, info=info)
return subscriber
def add_settings(self, settings=None, **kw):
"""Augment the ``settings`` argument passed in to the
Configurator constructor with one or more 'setting' key/value
pairs. A setting is a single key/value pair in the
dictionary-ish object returned from the API
:func:`repoze.bfg.settings.get_settings` and
:meth:`repoze.bfg.configuration.Configurator.get_settings`.
You may pass a dictionary::
config.add_settings({'external_uri':'http://example.com'})
Or a set of key/value pairs::
config.add_settings(external_uri='http://example.com')
This function is useful when you need to test code that calls
the :func:`repoze.bfg.settings.get_settings` API (or the
:meth:`repoze.bfg.configuration.Configurator.get_settings`
API) and which uses return values from that API.
.. note:: This method is new as of :mod:`repoze.bfg` 1.2.
"""
if settings is None:
settings = {}
utility = self.registry.queryUtility(ISettings)
if utility is None:
utility = self._set_settings(settings)
utility.update(settings)
utility.update(kw)
def get_settings(self):
"""
Return a 'settings' object for the current application. A
'settings' object is a dictionary-like object that contains
key/value pairs based on the dictionary passed as the ``settings``
argument to the :class:`repoze.bfg.configuration.Configurator`
constructor or the :func:`repoze.bfg.router.make_app` API.
.. note:: For backwards compatibility, dictionary keys can also be
looked up as attributes of the settings object.
.. note:: the :class:`repoze.bfg.settings.get_settings` function
performs the same duty."""
return self.registry.queryUtility(ISettings)
def make_wsgi_app(self):
""" Returns a :mod:`repoze.bfg` WSGI application representing
the current configuration state and sends a
:class:`repoze.bfg.interfaces.IWSGIApplicationCreatedEvent`
event to all listeners."""
from repoze.bfg.router import Router # avoid circdep
app = Router(self.registry)
# We push the registry on to the stack here in case any code
# that depends on the registry threadlocal APIs used in
# listeners subscribed to the WSGIApplicationCreatedEvent.
self.manager.push({'registry':self.registry, 'request':None})
try:
self.registry.notify(WSGIApplicationCreatedEvent(app))
finally:
self.manager.pop()
return app
def load_zcml(self, spec='configure.zcml', lock=threading.Lock()):
""" Load configuration from a :term:`ZCML` file into the
current configuration state. The ``spec`` argument is an
absolute filename, a relative filename, or a :term:`resource
specification`, defaulting to ``configure.zcml`` (relative to
the package of the configurator's caller)."""
package_name, filename = self._split_spec(spec)
if package_name is None: # absolute filename
package = self.package
else:
__import__(package_name)
package = sys.modules[package_name]
lock.acquire()
self.manager.push({'registry':self.registry, 'request':None})
try:
xmlconfig.file(filename, package, execute=True)
finally:
lock.release()
self.manager.pop()
return self.registry
def add_view(self, view=None, name="", for_=None, permission=None,
request_type=None, route_name=None, request_method=None,
request_param=None, containment=None, attr=None,
renderer=None, wrapper=None, xhr=False, accept=None,
header=None, path_info=None, custom_predicates=(),
context=None, _info=u''):
""" Add a :term:`view configuration` to the current
configuration state. Arguments to ``add_view`` are broken
down below into *predicate* arguments and *non-predicate*
arguments. Predicate arguments narrow the circumstances in
which the view callable will be invoked when a request is
presented to :mod:`repoze.bfg`; non-predicate arguments are
informational.
Non-Predicate Arguments
view
A reference to a :term:`view callable`. This argument is
required unless a ``renderer`` argument also exists. If a
``renderer`` argument is passed, and a ``view`` argument is
not provided, the view callable defaults to a callable that
returns an empty dictionary (see
:ref:`views_which_use_a_renderer`).
permission
The name of a :term:`permission` that the user must possess
in order to invoke the :term:`view callable`. See
:ref:`view_security_section` for more information about view
security and permissions.
attr
The view machinery defaults to using the ``__call__`` method
of the :term:`view callable` (or the function itself, if the
view callable is a function) to obtain a response. The
``attr`` value allows you to vary the method attribute used
to obtain the response. For example, if your view was a
class, and the class has a method named ``index`` and you
wanted to use this method instead of the class' ``__call__``
method to return the response, you'd say ``attr="index"`` in the
view configuration for the view. This is
most useful when the view definition is a class.
renderer
This is either a single string term (e.g. ``json``) or a
string implying a path or :term:`resource specification`
(e.g. ``templates/views.pt``) naming a :term:`renderer`
implementation. If the ``renderer`` value does not contain
a dot ``.``, the specified string will be used to look up a
renderer implementation, and that renderer implementation
will be used to construct a response from the view return
value. If the ``renderer`` value contains a dot (``.``),
the specified term will be treated as a path, and the
filename extension of the last element in the path will be
used to look up the renderer implementation, which will be
passed the full path. The renderer implementation will be
used to construct a :term:`response` from the view return
value.
Note that if the view itself returns a :term:`response` (see
:ref:`the_response`), the specified renderer implementation
is never called.
When the renderer is a path, although a path is usually just
a simple relative pathname (e.g. ``templates/foo.pt``,
implying that a template named "foo.pt" is in the
"templates" directory relative to the directory of the
current :term:`package` of the Configurator), a path can be
absolute, starting with a slash on UNIX or a drive letter
prefix on Windows. The path can alternately be a
:term:`resource specification` in the form
``some.dotted.package_name:relative/path``, making it
possible to address template resources which live in a
separate package.
The ``renderer`` attribute is optional. If it is not
defined, the "null" renderer is assumed (no rendering is
performed and the value is passed back to the upstream
:mod:`repoze.bfg` machinery unmolested).
wrapper
The :term:`view name` of a different :term:`view
configuration` which will receive the response body of this
view as the ``request.wrapped_body`` attribute of its own
:term:`request`, and the :term:`response` returned by this
view as the ``request.wrapped_response`` attribute of its
own request. Using a wrapper makes it possible to "chain"
views together to form a composite response. The response
of the outermost wrapper view will be returned to the user.
The wrapper view will be found as any view is found: see
:ref:`view_lookup`. The "best" wrapper view will be found
based on the lookup ordering: "under the hood" this wrapper
view is looked up via
``repoze.bfg.view.render_view_to_response(context, request,
'wrapper_viewname')``. The context and request of a wrapper
view is the same context and request of the inner view. If
this attribute is unspecified, no view wrapping is done.
Predicate Arguments
name
The :term:`view name`. Read :ref:`traversal_chapter` to
understand the concept of a view name.
context
An object representing Python class that the :term:`context`
must be an instance of, *or* the :term:`interface` that the
:term:`context` must provide in order for this view to be
found and called. This predicate is true when the
:term:`context` is an instance of the represented class or
if the :term:`context` provides the represented interface;
it is otherwise false. This argument may also be provided
to ``add_view`` as ``for_`` (an older, still-supported
spelling).
route_name
This value must match the ``name`` of a :term:`route
configuration` declaration (see :ref:`urldispatch_chapter`)
that must match before this view will be called. Note that
the ``route`` configuration referred to by ``route_name``
usually has a ``*traverse`` token in the value of its
``path``, representing a part of the path that will be used
by :term:`traversal` against the result of the route's
:term:`root factory`.
.. warning:: Using this argument services an advanced
feature that isn't often used unless you want to perform
traversal *after* a route has matched. See
:ref:`hybrid_chapter` for more information on using this
advanced feature.
request_type
This value should be an :term:`interface` that the
:term:`request` must provide in order for this view to be
found and called. This value exists only for backwards
compatibility purposes.
request_method
This value can either be one of the strings ``GET``,
``POST``, ``PUT``, ``DELETE``, or ``HEAD`` representing an
HTTP ``REQUEST_METHOD``. A view declaration with this
argument ensures that the view will only be called when the
request's ``method`` attribute (aka the ``REQUEST_METHOD`` of
the WSGI environment) string matches the supplied value.
request_param
This value can be any string. A view declaration with this
argument ensures that the view will only be called when the
:term:`request` has a key in the ``request.params``
dictionary (an HTTP ``GET`` or ``POST`` variable) that has a
name which matches the supplied value. If the value
supplied has a ``=`` sign in it,
e.g. ``request_params="foo=123"``, then the key (``foo``)
must both exist in the ``request.params`` dictionary, *and*
the value must match the right hand side of the expression
(``123``) for the view to "match" the current request.
containment
This value should be a reference to a Python class or
:term:`interface` that a parent object in the
:term:`lineage` must provide in order for this view to be
found and called. The nodes in your object graph must be
"location-aware" to use this feature. See
:ref:`location_aware` for more information about
location-awareness.
xhr
This value should be either ``True`` or ``False``. If this
value is specified and is ``True``, the :term:`request`
must possess an ``HTTP_X_REQUESTED_WITH`` (aka
``X-Requested-With``) header that has the value
``XMLHttpRequest`` for this view to be found and called.
This is useful for detecting AJAX requests issued from
jQuery, Prototype and other Javascript libraries.
accept
The value of this argument represents a match query for one
or more mimetypes in the ``Accept`` HTTP request header. If
this value is specified, it must be in one of the following
forms: a mimetype match token in the form ``text/plain``, a
wildcard mimetype match token in the form ``text/*`` or a
match-all wildcard mimetype match token in the form ``*/*``.
If any of the forms matches the ``Accept`` header of the
request, this predicate will be true.
header
This value represents an HTTP header name or a header
name/value pair. If the value contains a ``:`` (colon), it
will be considered a name/value pair
(e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). The
value portion should be a regular expression. If the value
does not contain a colon, the entire value will be
considered to be the header name
(e.g. ``If-Modified-Since``). If the value evaluates to a
header name only without a value, the header specified by
the name must be present in the request for this predicate
to be true. If the value evaluates to a header name/value
pair, the header specified by the name must be present in
the request *and* the regular expression specified as the
value must match the header value. Whether or not the value
represents a header name or a header name/value pair, the
case of the header name is not significant.
path_info
This value represents a regular expression pattern that will
be tested against the ``PATH_INFO`` WSGI environment
variable. If the regex matches, this predicate will be
``True``.
custom_predicates
This value should be a sequence of references to custom
predicate callables. Use custom predicates when no set of
predefined predicates do what you need. Custom predicates
can be combined with predefined predicates as necessary.
Each custom predicate callable should accept two arguments:
``context`` and ``request`` and should return either
``True`` or ``False`` after doing arbitrary evaluation of
the context and/or the request. If all callables return
``True``, the associated view callable will be considered
viable for a given request.
.. note:: This feature is new as of :mod:`repoze.bfg` 1.2.
"""
if not view:
if renderer:
def view(context, request):
return {}
else:
raise ConfigurationError('"view" was not specified and '
'no "renderer" specified')
if request_type in ('GET', 'HEAD', 'PUT', 'POST', 'DELETE'):
# b/w compat for 1.0
request_method = request_type
request_type = None
if request_type is not None:
if not IInterface.providedBy(request_type):
raise ConfigurationError(
'request_type must be an interface, not %s' % request_type)
request_iface = IRequest
if route_name is not None:
request_iface = self.registry.queryUtility(IRouteRequest,
name=route_name)
if request_iface is None:
deferred_views = getattr(self.registry,
'deferred_route_views', None)
if deferred_views is None:
deferred_views = self.registry.deferred_route_views = {}
info = dict(
view=view, name=name, for_=for_, permission=permission,
request_type=request_type, route_name=route_name,
request_method=request_method, request_param=request_param,
containment=containment, attr=attr,
renderer=renderer, wrapper=wrapper, xhr=xhr, accept=accept,
header=header, path_info=path_info,
custom_predicates=custom_predicates, context=context,
_info=u''
)
view_info = deferred_views.setdefault(route_name, [])
view_info.append(info)
return
order, predicates, phash = _make_predicates(xhr=xhr,
request_method=request_method, path_info=path_info,
request_param=request_param, header=header, accept=accept,
containment=containment, request_type=request_type,
custom=custom_predicates)
derived_view = self._derive_view(view, permission, predicates, attr,
renderer, wrapper, name, accept, order,
phash)
if context is None:
context = for_
r_context = context
if r_context is None:
r_context = Interface
if not IInterface.providedBy(r_context):
r_context = implementedBy(r_context)
registered = self.registry.adapters.registered
# A multiviews is a set of views which are registered for
# exactly the same context type/request type/name triad. Each
# consituent view in a multiview differs only by the
# predicates which it possesses.
# To find a previously registered view for a context
# type/request type/name triad, we need to use the
# ``registered`` method of the adapter registry rather than
# ``lookup``. ``registered`` ignores interface inheritance
# for the required and provided arguments, returning only a
# view registered previously with the *exact* triad we pass
# in.
# We need to do this three times, because we use three
# different interfaces as the ``provided`` interface while
# doing registrations, and ``registered`` performs exact
# matches on all the arguments it receives.
old_view = None
for view_type in (IView, ISecuredView, IMultiView):
old_view = registered((IViewClassifier, request_iface, r_context),
view_type, name)
if old_view is not None:
break
is_exception_view = isexception(context)
def regclosure():
if hasattr(view, '__call_permissive__'):
view_iface = ISecuredView
else:
view_iface = IView
self.registry.registerAdapter(
derived_view, (IViewClassifier, request_iface, context),
view_iface, name, info=_info)
if is_exception_view:
self.registry.registerAdapter(
derived_view,
(IExceptionViewClassifier, request_iface, context),
view_iface, name, info=_info)
is_multiview = IMultiView.providedBy(old_view)
old_phash = getattr(old_view, '__phash__', DEFAULT_PHASH)
if old_view is None:
# - No component was yet registered for any of our I*View
# interfaces exactly; this is the first view for this
# triad.
regclosure()
elif (not is_multiview) and (old_phash == phash):
# - A single view component was previously registered with
# the same predicate hash as this view; this registration
# is therefore an override.
regclosure()
else:
# - A view or multiview was already registered for this
# triad, and the new view is not an override.
# XXX we could try to be more efficient here and register
# a non-secured view for a multiview if none of the
# multiview's consituent views have a permission
# associated with them, but this code is getting pretty
# rough already
if is_multiview:
multiview = old_view
else:
multiview = MultiView(name)
old_accept = getattr(old_view, '__accept__', None)
old_order = getattr(old_view, '__order__', MAX_ORDER)
multiview.add(old_view, old_order, old_accept, old_phash)
multiview.add(derived_view, order, accept, phash)
for view_type in (IView, ISecuredView):
# unregister any existing views
self.registry.adapters.unregister(
(IViewClassifier, request_iface, r_context),
view_type, name=name)
if is_exception_view:
self.registry.adapters.unregister(
(IExceptionViewClassifier, request_iface, r_context),
view_type, name=name)
self.registry.registerAdapter(
multiview, (IViewClassifier, request_iface, context),
IMultiView, name, info=_info)
if is_exception_view:
self.registry.registerAdapter(
multiview,
(IExceptionViewClassifier, request_iface, context),
IMultiView, name, info=_info)
def add_route(self,
name,
path,
view=None,
view_for=None,
permission=None,
factory=None,
for_=None,
header=None,
xhr=False,
accept=None,
path_info=None,
request_method=None,
request_param=None,
traverse=None,
custom_predicates=(),
view_permission=None,
renderer=None,
view_renderer=None,
view_context=None,
view_attr=None,
use_global_views=False,
_info=u''):
""" Add a :term:`route configuration` to the current
configuration state, as well as possibly a :term:`view
configuration` to be used to specify a :term:`view callable`
that will be invoked when this route matches. The arguments
to this method are divided into *predicate*, *non-predicate*,
and *view-related* types. :term:`Route predicate` arguments
narrow the circumstances in which a route will be match a
request; non-predicate arguments are informational.
Non-Predicate Arguments
name
The name of the route, e.g. ``myroute``. This attribute is
required. It must be unique among all defined routes in a given