Michael Merickel
2018-10-15 bda1306749c62ef4f11cfe567ed7d56c8ad94240
commit | author | age
5bf23f 1 import inspect
9c8ec5 2 import itertools
5bf23f 3 import logging
412b4a 4 import operator
5bf23f 5 import os
79ef3d 6 import sys
c15cbc 7 import threading
5bf23f 8 import venusian
ea824f 9
CM 10 from webob.exc import WSGIHTTPException as WebobWSGIHTTPException
11
3b5ccb 12 from pyramid.interfaces import (
CM 13     IDebugLogger,
14     IExceptionResponse,
95f766 15     IPredicateList,
568a02 16     PHASE0_CONFIG,
95f766 17     PHASE1_CONFIG,
568a02 18     PHASE2_CONFIG,
MM 19     PHASE3_CONFIG,
0c29cf 20 )
5bf23f 21
cfbbd6 22 from pyramid.asset import resolve_asset_spec
ee117e 23
66da9b 24 from pyramid.authorization import ACLAuthorizationPolicy
ee117e 25
0c29cf 26 from pyramid.compat import text_, reraise, string_types
ee117e 27
5bf23f 28 from pyramid.events import ApplicationCreated
ee117e 29
CM 30 from pyramid.exceptions import (
31     ConfigurationConflictError,
32     ConfigurationError,
33     ConfigurationExecutionError,
0c29cf 34 )
ee117e 35
5bf23f 36 from pyramid.httpexceptions import default_exceptionresponse_view
ee117e 37
0c29cf 38 from pyramid.path import caller_package, package_of
ee117e 39
0c29cf 40 from pyramid.registry import Introspectable, Introspector, Registry, undefer
ee117e 41
cfbbd6 42 from pyramid.router import Router
ee117e 43
5bf23f 44 from pyramid.settings import aslist
ee117e 45
5bf23f 46 from pyramid.threadlocal import manager
ee117e 47
0c29cf 48 from pyramid.util import WeakOrderedSet, object_description
52fde9 49
0c29cf 50 from pyramid.config.util import ActionInfo, PredicateList, action_method, not_
5bf23f 51
ea046b 52 from pyramid.config.adapters import AdaptersConfiguratorMixin
CM 53 from pyramid.config.assets import AssetsConfiguratorMixin
54 from pyramid.config.factories import FactoriesConfiguratorMixin
55 from pyramid.config.i18n import I18NConfiguratorMixin
56 from pyramid.config.rendering import RenderingConfiguratorMixin
57 from pyramid.config.routes import RoutesConfiguratorMixin
58 from pyramid.config.security import SecurityConfiguratorMixin
59 from pyramid.config.settings import SettingsConfiguratorMixin
5bf23f 60 from pyramid.config.testing import TestingConfiguratorMixin
CM 61 from pyramid.config.tweens import TweensConfiguratorMixin
62 from pyramid.config.views import ViewsConfiguratorMixin
63 from pyramid.config.zca import ZCAConfiguratorMixin
e6c2d2 64
56df90 65 from pyramid.path import DottedNameResolver
CM 66
e6c2d2 67 empty = text_('')
e49638 68 _marker = object()
5bf23f 69
52fde9 70 not_ = not_  # api
32333e 71
568a02 72 PHASE0_CONFIG = PHASE0_CONFIG  # api
MM 73 PHASE1_CONFIG = PHASE1_CONFIG  # api
74 PHASE2_CONFIG = PHASE2_CONFIG  # api
75 PHASE3_CONFIG = PHASE3_CONFIG  # api
32333e 76
0c29cf 77
5bf23f 78 class Configurator(
CM 79     TestingConfiguratorMixin,
80     TweensConfiguratorMixin,
81     SecurityConfiguratorMixin,
82     ViewsConfiguratorMixin,
83     RoutesConfiguratorMixin,
84     ZCAConfiguratorMixin,
85     I18NConfiguratorMixin,
86     RenderingConfiguratorMixin,
87     AssetsConfiguratorMixin,
88     SettingsConfiguratorMixin,
89     FactoriesConfiguratorMixin,
90     AdaptersConfiguratorMixin,
0c29cf 91 ):
5bf23f 92     """
CM 93     A Configurator is used to configure a :app:`Pyramid`
94     :term:`application registry`.
95
134ef7 96     The Configurator lifecycle can be managed by using a context manager to
MM 97     automatically handle calling :meth:`pyramid.config.Configurator.begin` and
98     :meth:`pyramid.config.Configurator.end` as well as
99     :meth:`pyramid.config.Configurator.commit`.
100
101     .. code-block:: python
102
103         with Configurator(settings=settings) as config:
104             config.add_route('home', '/')
105             app = config.make_wsgi_app()
106
0bf091 107     If the ``registry`` argument is not ``None``, it must
adfc23 108     be an instance of the :class:`pyramid.registry.Registry` class
CM 109     representing the registry to configure.  If ``registry`` is ``None``, the
110     configurator will create a :class:`pyramid.registry.Registry` instance
111     itself; it will also perform some default configuration that would not
112     otherwise be done.  After its construction, the configurator may be used
113     to add further configuration to the registry.
5bf23f 114
60270f 115     .. warning:: If ``registry`` is assigned the above-mentioned class
TL 116        instance, all other constructor arguments are ignored,
117        with the exception of ``package``.
5bf23f 118
adfc23 119     If the ``package`` argument is passed, it must be a reference to a Python
CM 120     :term:`package` (e.g. ``sys.modules['thepackage']``) or a :term:`dotted
121     Python name` to the same.  This value is used as a basis to convert
122     relative paths passed to various configuration methods, such as methods
123     which accept a ``renderer`` argument, into absolute paths.  If ``None``
124     is passed (the default), the package is assumed to be the Python package
125     in which the *caller* of the ``Configurator`` constructor lives.
5bf23f 126
ae6c88 127     If the ``root_package`` is passed, it will propagate through the
MM 128     configuration hierarchy as a way for included packages to locate
129     resources relative to the package in which the main ``Configurator`` was
130     created. If ``None`` is passed (the default), the ``root_package`` will
131     be derived from the ``package`` argument. The ``package`` attribute is
132     always pointing at the package being included when using :meth:`.include`,
133     whereas the ``root_package`` does not change.
134
5bf23f 135     If the ``settings`` argument is passed, it should be a Python dictionary
adfc23 136     representing the :term:`deployment settings` for this application.  These
CM 137     are later retrievable using the
138     :attr:`pyramid.registry.Registry.settings` attribute (aka
139     ``request.registry.settings``).
5bf23f 140
CM 141     If the ``root_factory`` argument is passed, it should be an object
adfc23 142     representing the default :term:`root factory` for your application or a
CM 143     :term:`dotted Python name` to the same.  If it is ``None``, a default
144     root factory will be used.
5bf23f 145
CM 146     If ``authentication_policy`` is passed, it should be an instance
147     of an :term:`authentication policy` or a :term:`dotted Python
adfc23 148     name` to the same.
5bf23f 149
CM 150     If ``authorization_policy`` is passed, it should be an instance of
151     an :term:`authorization policy` or a :term:`dotted Python name` to
adfc23 152     the same.
5bf23f 153
CM 154     .. note:: A ``ConfigurationError`` will be raised when an
155        authorization policy is supplied without also supplying an
156        authentication policy (authorization requires authentication).
157
3778e8 158     If ``renderers`` is ``None`` (the default), a default set of
TL 159     :term:`renderer` factories is used. Else, it should be a list of
160     tuples representing a set of renderer factories which should be
161     configured into this application, and each tuple representing a set of
5bf23f 162     positional values that should be passed to
3778e8 163     :meth:`pyramid.config.Configurator.add_renderer`.
5bf23f 164
CM 165     If ``debug_logger`` is not passed, a default debug logger that logs to a
166     logger will be used (the logger name will be the package name of the
167     *caller* of this configurator).  If it is passed, it should be an
168     instance of the :class:`logging.Logger` (PEP 282) standard library class
169     or a Python logger name.  The debug logger is used by :app:`Pyramid`
170     itself to log warnings and authorization debugging information.
171
172     If ``locale_negotiator`` is passed, it should be a :term:`locale
173     negotiator` implementation or a :term:`dotted Python name` to
174     same.  See :ref:`custom_locale_negotiator`.
175
176     If ``request_factory`` is passed, it should be a :term:`request
adfc23 177     factory` implementation or a :term:`dotted Python name` to the same.
5bf23f 178     See :ref:`changing_the_request_factory`.  By default it is ``None``,
CM 179     which means use the default request factory.
180
1236de 181     If ``response_factory`` is passed, it should be a :term:`response
JA 182     factory` implementation or a :term:`dotted Python name` to the same.
183     See :ref:`changing_the_response_factory`.  By default it is ``None``,
184     which means use the default response factory.
185
5bf23f 186     If ``default_permission`` is passed, it should be a
CM 187     :term:`permission` string to be used as the default permission for
188     all view configuration registrations performed against this
189     Configurator.  An example of a permission string:``'view'``.
190     Adding a default permission makes it unnecessary to protect each
191     view configuration with an explicit permission, unless your
192     application policy requires some exception for a particular view.
193     By default, ``default_permission`` is ``None``, meaning that view
194     configurations which do not explicitly declare a permission will
195     always be executable by entirely anonymous users (any
2033ee 196     authorization policy in effect is ignored).
1236de 197
2033ee 198     .. seealso::
SP 199
200         See also :ref:`setting_a_default_permission`.
5bf23f 201
CM 202     If ``session_factory`` is passed, it should be an object which
203     implements the :term:`session factory` interface.  If a nondefault
204     value is passed, the ``session_factory`` will be used to create a
205     session object when ``request.session`` is accessed.  Note that
206     the same outcome can be achieved by calling
207     :meth:`pyramid.config.Configurator.set_session_factory`.  By
208     default, this argument is ``None``, indicating that no session
209     factory will be configured (and thus accessing ``request.session``
210     will throw an error) unless ``set_session_factory`` is called later
211     during configuration.
212
213     If ``autocommit`` is ``True``, every method called on the configurator
214     will cause an immediate action, and no configuration conflict detection
215     will be used. If ``autocommit`` is ``False``, most methods of the
216     configurator will defer their action until
217     :meth:`pyramid.config.Configurator.commit` is called.  When
218     :meth:`pyramid.config.Configurator.commit` is called, the actions implied
219     by the called methods will be checked for configuration conflicts unless
5c351a 220     ``autocommit`` is ``True``.  If a conflict is detected, a
5bf23f 221     ``ConfigurationConflictError`` will be raised.  Calling
CM 222     :meth:`pyramid.config.Configurator.make_wsgi_app` always implies a final
223     commit.
224
225     If ``default_view_mapper`` is passed, it will be used as the default
226     :term:`view mapper` factory for view configurations that don't otherwise
a3f67c 227     specify one (see :class:`pyramid.interfaces.IViewMapperFactory`).  If
d0e652 228     ``default_view_mapper`` is not passed, a superdefault view mapper will be
5bf23f 229     used.
CM 230
231     If ``exceptionresponse_view`` is passed, it must be a :term:`view
232     callable` or ``None``.  If it is a view callable, it will be used as an
233     exception view callable when an :term:`exception response` is raised. If
234     ``exceptionresponse_view`` is ``None``, no exception response view will
235     be registered, and all raised exception responses will be bubbled up to
236     Pyramid's caller.  By
237     default, the ``pyramid.httpexceptions.default_exceptionresponse_view``
0b23b3 238     function is used as the ``exceptionresponse_view``.
5bf23f 239
CM 240     If ``route_prefix`` is passed, all routes added with
241     :meth:`pyramid.config.Configurator.add_route` will have the specified path
0b23b3 242     prepended to their pattern.
5bf23f 243
844ed9 244     If ``introspection`` is passed, it must be a boolean value.  If it's
043ccd 245     ``True``, introspection values during actions will be kept for use
844ed9 246     for tools like the debug toolbar.  If it's ``False``, introspection
CM 247     values provided by registrations will be ignored.  By default, it is
0b23b3 248     ``True``.
TL 249
250     .. versionadded:: 1.1
251        The ``exceptionresponse_view`` argument.
252
253     .. versionadded:: 1.2
254        The ``route_prefix`` argument.
255
256     .. versionadded:: 1.3
257        The ``introspection`` argument.
ae6c88 258
MM 259     .. versionadded:: 1.6
260        The ``root_package`` argument.
1236de 261        The ``response_factory`` argument.
134ef7 262
MM 263     .. versionadded:: 1.9
264        The ability to use the configurator as a context manager with the
265        ``with``-statement to make threadlocal configuration available for
266        further configuration with an implicit commit.
e49638 267     """
0c29cf 268
MM 269     manager = manager  # for testing injection
270     venusian = venusian  # for testing injection
5bf23f 271     _ainfo = None
b86fa9 272     basepath = None
CM 273     includepath = ()
274     info = ''
8b6f09 275     object_description = staticmethod(object_description)
e49638 276     introspectable = Introspectable
5ad401 277     inspect = inspect
5bf23f 278
0c29cf 279     def __init__(
MM 280         self,
281         registry=None,
282         package=None,
283         settings=None,
284         root_factory=None,
285         authentication_policy=None,
286         authorization_policy=None,
287         renderers=None,
288         debug_logger=None,
289         locale_negotiator=None,
290         request_factory=None,
291         response_factory=None,
292         default_permission=None,
293         session_factory=None,
294         default_view_mapper=None,
295         autocommit=False,
296         exceptionresponse_view=default_exceptionresponse_view,
297         route_prefix=None,
298         introspection=True,
299         root_package=None,
300     ):
5bf23f 301         if package is None:
CM 302             package = caller_package()
ae6c88 303         if root_package is None:
MM 304             root_package = package
5bf23f 305         name_resolver = DottedNameResolver(package)
CM 306         self.name_resolver = name_resolver
078859 307         self.package_name = name_resolver.get_package_name()
CM 308         self.package = name_resolver.get_package()
ae6c88 309         self.root_package = root_package
5bf23f 310         self.registry = registry
CM 311         self.autocommit = autocommit
312         self.route_prefix = route_prefix
844ed9 313         self.introspection = introspection
5bf23f 314         if registry is None:
CM 315             registry = Registry(self.package_name)
316             self.registry = registry
317             self.setup_registry(
318                 settings=settings,
319                 root_factory=root_factory,
320                 authentication_policy=authentication_policy,
321                 authorization_policy=authorization_policy,
322                 renderers=renderers,
323                 debug_logger=debug_logger,
324                 locale_negotiator=locale_negotiator,
325                 request_factory=request_factory,
1236de 326                 response_factory=response_factory,
5bf23f 327                 default_permission=default_permission,
CM 328                 session_factory=session_factory,
329                 default_view_mapper=default_view_mapper,
330                 exceptionresponse_view=exceptionresponse_view,
0c29cf 331             )
5bf23f 332
0c29cf 333     def setup_registry(
MM 334         self,
335         settings=None,
336         root_factory=None,
337         authentication_policy=None,
338         authorization_policy=None,
339         renderers=None,
340         debug_logger=None,
341         locale_negotiator=None,
342         request_factory=None,
343         response_factory=None,
344         default_permission=None,
345         session_factory=None,
346         default_view_mapper=None,
347         exceptionresponse_view=default_exceptionresponse_view,
348     ):
5bf23f 349         """ When you pass a non-``None`` ``registry`` argument to the
adfc23 350         :term:`Configurator` constructor, no initial setup is performed
5bf23f 351         against the registry.  This is because the registry you pass in may
CM 352         have already been initialized for use under :app:`Pyramid` via a
353         different configurator.  However, in some circumstances (such as when
adfc23 354         you want to use a global registry instead of a registry created as a
CM 355         result of the Configurator constructor), or when you want to reset
356         the initial setup of a registry, you *do* want to explicitly
357         initialize the registry associated with a Configurator for use under
358         :app:`Pyramid`.  Use ``setup_registry`` to do this initialization.
5bf23f 359
CM 360         ``setup_registry`` configures settings, a root factory, security
361         policies, renderers, a debug logger, a locale negotiator, and various
362         other settings using the configurator's current registry, as per the
363         descriptions in the Configurator constructor."""
c1624c 364
5bf23f 365         registry = self.registry
c1624c 366
5bf23f 367         self._fix_registry()
e49638 368
5bf23f 369         self._set_settings(settings)
CM 370
e6c2d2 371         if isinstance(debug_logger, string_types):
c1624c 372             debug_logger = logging.getLogger(debug_logger)
CM 373
5bf23f 374         if debug_logger is None:
CM 375             debug_logger = logging.getLogger(self.package_name)
012b97 376
5bf23f 377         registry.registerUtility(debug_logger, IDebugLogger)
CM 378
27190e 379         self.add_default_response_adapters()
b7e92d 380         self.add_default_renderers()
121f45 381         self.add_default_accept_view_order()
a00621 382         self.add_default_view_predicates()
ceb1f2 383         self.add_default_view_derivers()
9c8ec5 384         self.add_default_route_predicates()
35b0e3 385         self.add_default_tweens()
7c0f09 386         self.add_default_security()
a00621 387
5bf23f 388         if exceptionresponse_view is not None:
CM 389             exceptionresponse_view = self.maybe_dotted(exceptionresponse_view)
390             self.add_view(exceptionresponse_view, context=IExceptionResponse)
0c29cf 391             self.add_view(
MM 392                 exceptionresponse_view, context=WebobWSGIHTTPException
393             )
5bf23f 394
0ded4e 395         # commit below because:
CM 396         #
397         # - the default exceptionresponse_view requires the superdefault view
a54bc1 398         #   mapper, so we need to configure it before adding
MM 399         #   default_view_mapper
0ded4e 400         #
257f2c 401         # - superdefault renderers should be overrideable without requiring
0ded4e 402         #   the user to commit before calling config.add_renderer
ea824f 403
CM 404         self.commit()
405
27190e 406         # self.commit() should not be called within this method after this
CM 407         # point because the following registrations should be treated as
408         # analogues of methods called by the user after configurator
409         # construction.  Rationale: user-supplied implementations should be
410         # preferred rather than add-on author implementations with the help of
411         # automatic conflict resolution.
ea824f 412
66da9b 413         if authentication_policy and not authorization_policy:
0c29cf 414             authorization_policy = ACLAuthorizationPolicy()  # default
66da9b 415
ea824f 416         if authorization_policy:
CM 417             self.set_authorization_policy(authorization_policy)
637bda 418
eb2fee 419         if authentication_policy:
CM 420             self.set_authentication_policy(authentication_policy)
421
422         if default_view_mapper is not None:
423             self.set_view_mapper(default_view_mapper)
f67ba4 424
637bda 425         if renderers:
CM 426             for name, renderer in renderers:
427                 self.add_renderer(name, renderer)
cfbbd6 428
2a818a 429         if root_factory is not None:
CM 430             self.set_root_factory(root_factory)
ea824f 431
5bf23f 432         if locale_negotiator:
ea824f 433             self.set_locale_negotiator(locale_negotiator)
5bf23f 434
CM 435         if request_factory:
436             self.set_request_factory(request_factory)
437
1236de 438         if response_factory:
JA 439             self.set_response_factory(response_factory)
440
5bf23f 441         if default_permission:
CM 442             self.set_default_permission(default_permission)
443
444         if session_factory is not None:
445             self.set_session_factory(session_factory)
446
25c64c 447         tweens = aslist(registry.settings.get('pyramid.tweens', []))
5bf23f 448         for factory in tweens:
CM 449             self._add_tween(factory, explicit=True)
012b97 450
ea824f 451         includes = aslist(registry.settings.get('pyramid.includes', []))
CM 452         for inc in includes:
453             self.include(inc)
454
5bf23f 455     def _make_spec(self, path_or_spec):
79ef3d 456         package, filename = resolve_asset_spec(path_or_spec, self.package_name)
5bf23f 457         if package is None:
0c29cf 458             return filename  # absolute filename
5bf23f 459         return '%s:%s' % (package, filename)
CM 460
461     def _fix_registry(self):
462         """ Fix up a ZCA component registry that is not a
463         pyramid.registry.Registry by adding analogues of ``has_listeners``,
464         ``notify``, ``queryAdapterOrSelf``, and ``registerSelfAdapter``
465         through monkey-patching."""
466
467         _registry = self.registry
468
469         if not hasattr(_registry, 'notify'):
0c29cf 470
5bf23f 471             def notify(*events):
0c29cf 472                 [_ for _ in _registry.subscribers(events, None)]
MM 473
5bf23f 474             _registry.notify = notify
CM 475
476         if not hasattr(_registry, 'has_listeners'):
477             _registry.has_listeners = True
478
479         if not hasattr(_registry, 'queryAdapterOrSelf'):
0c29cf 480
5bf23f 481             def queryAdapterOrSelf(object, interface, default=None):
CM 482                 if not interface.providedBy(object):
0c29cf 483                     return _registry.queryAdapter(
MM 484                         object, interface, default=default
485                     )
5bf23f 486                 return object
0c29cf 487
5bf23f 488             _registry.queryAdapterOrSelf = queryAdapterOrSelf
CM 489
490         if not hasattr(_registry, 'registerSelfAdapter'):
0c29cf 491
MM 492             def registerSelfAdapter(
493                 required=None,
494                 provided=None,
495                 name=empty,
496                 info=empty,
497                 event=True,
498             ):
499                 return _registry.registerAdapter(
500                     lambda x: x,
501                     required=required,
502                     provided=provided,
503                     name=name,
504                     info=info,
505                     event=event,
506                 )
507
5bf23f 508             _registry.registerSelfAdapter = registerSelfAdapter
CM 509
c15cbc 510         if not hasattr(_registry, '_lock'):
CM 511             _registry._lock = threading.Lock()
512
513         if not hasattr(_registry, '_clear_view_lookup_cache'):
0c29cf 514
c15cbc 515             def _clear_view_lookup_cache():
CM 516                 _registry._view_lookup_cache = {}
517
0c29cf 518             _registry._clear_view_lookup_cache = _clear_view_lookup_cache
c15cbc 519
5bf23f 520     # API
1236de 521
e49638 522     def _get_introspector(self):
CM 523         introspector = getattr(self.registry, 'introspector', _marker)
524         if introspector is _marker:
525             introspector = Introspector()
526             self._set_introspector(introspector)
527         return introspector
528
529     def _set_introspector(self, introspector):
530         self.registry.introspector = introspector
531
532     def _del_introspector(self):
533         del self.registry.introspector
534
078859 535     introspector = property(
CM 536         _get_introspector, _set_introspector, _del_introspector
0c29cf 537     )
5bf23f 538
405213 539     def get_predlist(self, name):
95f766 540         predlist = self.registry.queryUtility(IPredicateList, name=name)
CM 541         if predlist is None:
542             predlist = PredicateList()
543             self.registry.registerUtility(predlist, IPredicateList, name=name)
544         return predlist
545
0c29cf 546     def _add_predicate(
MM 547         self, type, name, factory, weighs_more_than=None, weighs_less_than=None
548     ):
06b839 549         factory = self.maybe_dotted(factory)
c2c589 550         discriminator = ('%s option' % type, name)
95f766 551         intr = self.introspectable(
CM 552             '%s predicates' % type,
553             discriminator,
554             '%s predicate named %s' % (type, name),
0c29cf 555             '%s predicate' % type,
MM 556         )
95f766 557         intr['name'] = name
06b839 558         intr['factory'] = factory
95f766 559         intr['weighs_more_than'] = weighs_more_than
CM 560         intr['weighs_less_than'] = weighs_less_than
0c29cf 561
95f766 562         def register():
405213 563             predlist = self.get_predlist(type)
0c29cf 564             predlist.add(
MM 565                 name,
566                 factory,
567                 weighs_more_than=weighs_more_than,
568                 weighs_less_than=weighs_less_than,
569             )
570
571         self.action(
572             discriminator,
573             register,
574             introspectables=(intr,),
575             order=PHASE1_CONFIG,
576         )  # must be registered early
95f766 577
3b5ccb 578     @property
CM 579     def action_info(self):
0c29cf 580         info = self.info  # usually a ZCML action (ParserInfo) if self.info
3b5ccb 581         if not info:
CM 582             # Try to provide more accurate info for conflict reports
583             if self._ainfo:
584                 info = self._ainfo[0]
585             else:
549cf7 586                 info = ActionInfo(None, 0, '', '')
3b5ccb 587         return info
CM 588
0c29cf 589     def action(
MM 590         self,
591         discriminator,
592         callable=None,
593         args=(),
594         kw=None,
595         order=0,
596         introspectables=(),
597         **extra
598     ):
5bf23f 599         """ Register an action which will be executed when
adfc23 600         :meth:`pyramid.config.Configurator.commit` is called (or executed
5bf23f 601         immediately if ``autocommit`` is ``True``).
CM 602
603         .. warning:: This method is typically only used by :app:`Pyramid`
604            framework extension authors, not by :app:`Pyramid` application
605            developers.
606
607         The ``discriminator`` uniquely identifies the action.  It must be
608         given, but it can be ``None``, to indicate that the action never
609         conflicts.  It must be a hashable value.
610
7d109d 611         The ``callable`` is a callable object which performs the task
CM 612         associated with the action when the action is executed.  It is
613         optional.
5bf23f 614
7d109d 615         ``args`` and ``kw`` are tuple and dict objects respectively, which
CM 616         are passed to ``callable`` when this action is executed.  Both are
617         optional.
618
619         ``order`` is a grouping mechanism; an action with a lower order will
620         be executed before an action with a higher order (has no effect when
621         autocommit is ``True``).
622
623         ``introspectables`` is a sequence of :term:`introspectable` objects
624         (or the empty sequence if no introspectable objects are associated
844ed9 625         with this action).  If this configurator's ``introspection``
CM 626         attribute is ``False``, these introspectables will be ignored.
7d109d 627
CM 628         ``extra`` provides a facility for inserting extra keys and values
629         into an action dictionary.
5bf23f 630         """
22e0aa 631         # catch nonhashable discriminators here; most unit tests use
CM 632         # autocommit=False, which won't catch unhashable discriminators
0c29cf 633         assert hash(discriminator)
22e0aa 634
5bf23f 635         if kw is None:
CM 636             kw = {}
637
b86fa9 638         autocommit = self.autocommit
3b5ccb 639         action_info = self.action_info
844ed9 640
CM 641         if not self.introspection:
642             # if we're not introspecting, ignore any introspectables passed
643             # to us
644             introspectables = ()
5bf23f 645
CM 646         if autocommit:
7401b8 647             # callables can depend on the side effects of resolving a
CM 648             # deferred discriminator
804eb0 649             self.begin()
MM 650             try:
651                 undefer(discriminator)
652                 if callable is not None:
653                     callable(*args, **kw)
654                 for introspectable in introspectables:
655                     introspectable.register(self.introspector, action_info)
656             finally:
657                 self.end()
b86fa9 658
5bf23f 659         else:
7d109d 660             action = extra
CM 661             action.update(
662                 dict(
663                     discriminator=discriminator,
664                     callable=callable,
665                     args=args,
666                     kw=kw,
667                     order=order,
668                     info=action_info,
669                     includepath=self.includepath,
670                     introspectables=introspectables,
b86fa9 671                 )
0c29cf 672             )
7d109d 673             self.action_state.action(**action)
b86fa9 674
CM 675     def _get_action_state(self):
676         registry = self.registry
677         try:
678             state = registry.action_state
679         except AttributeError:
680             state = ActionState()
681             registry.action_state = state
682         return state
683
684     def _set_action_state(self, state):
685         self.registry.action_state = state
686
687     action_state = property(_get_action_state, _set_action_state)
5bf23f 688
0c29cf 689     _ctx = action_state  # bw compat
3e4f69 690
5bf23f 691     def commit(self):
134ef7 692         """
MM 693         Commit any pending configuration actions. If a configuration
adfc23 694         conflict is detected in the pending configuration actions, this method
5bf23f 695         will raise a :exc:`ConfigurationConflictError`; within the traceback
CM 696         of this error will be information about the source of the conflict,
697         usually including file names and line numbers of the cause of the
134ef7 698         configuration conflicts.
MM 699
700         .. warning::
701            You should think very carefully before manually invoking
702            ``commit()``. Especially not as part of any reusable configuration
703            methods. Normally it should only be done by an application author at
704            the end of configuration in order to override certain aspects of an
705            addon.
706
707         """
0c1c58 708         self.begin()
MM 709         try:
710             self.action_state.execute_actions(introspector=self.introspector)
711         finally:
712             self.end()
0c29cf 713         self.action_state = ActionState()  # old actions have been processed
5bf23f 714
CM 715     def include(self, callable, route_prefix=None):
f7c173 716         """Include a configuration callable, to support imperative
5bf23f 717         application extensibility.
CM 718
719         .. warning:: In versions of :app:`Pyramid` prior to 1.2, this
720             function accepted ``*callables``, but this has been changed
721             to support only a single callable.
722
723         A configuration callable should be a callable that accepts a single
724         argument named ``config``, which will be an instance of a
2e95ac 725         :term:`Configurator`.  However, be warned that it will not be the same
TL 726         configurator instance on which you call this method.  The
e09e08 727         code which runs as a result of calling the callable should invoke
5bf23f 728         methods on the configurator passed to it which add configuration
CM 729         state.  The return value of a callable will be ignored.
730
731         Values allowed to be presented via the ``callable`` argument to
732         this method: any callable Python object or any :term:`dotted Python
733         name` which resolves to a callable Python object.  It may also be a
734         Python :term:`module`, in which case, the module will be searched for
735         a callable named ``includeme``, which will be treated as the
736         configuration callable.
b86fa9 737
5bf23f 738         For example, if the ``includeme`` function below lives in a module
CM 739         named ``myapp.myconfig``:
740
741         .. code-block:: python
742            :linenos:
743
744            # myapp.myconfig module
745
746            def my_view(request):
747                from pyramid.response import Response
748                return Response('OK')
749
750            def includeme(config):
751                config.add_view(my_view)
752
026ac8 753         You might cause it to be included within your Pyramid application like
5bf23f 754         so:
CM 755
756         .. code-block:: python
757            :linenos:
758
759            from pyramid.config import Configurator
760
761            def main(global_config, **settings):
762                config = Configurator()
763                config.include('myapp.myconfig.includeme')
764
765         Because the function is named ``includeme``, the function name can
766         also be omitted from the dotted name reference:
767
768         .. code-block:: python
769            :linenos:
770
771            from pyramid.config import Configurator
772
773            def main(global_config, **settings):
774                config = Configurator()
775                config.include('myapp.myconfig')
776
777         Included configuration statements will be overridden by local
778         configuration statements if an included callable causes a
779         configuration conflict by registering something with the same
780         configuration parameters.
781
782         If the ``route_prefix`` is supplied, it must be a string.  Any calls
783         to :meth:`pyramid.config.Configurator.add_route` within the included
784         callable will have their pattern prefixed with the value of
785         ``route_prefix``. This can be used to help mount a set of routes at a
2e95ac 786         different location than the included callable's author intended, while
5bf23f 787         still maintaining the same route names.  For example:
012b97 788
5bf23f 789         .. code-block:: python
CM 790            :linenos:
791
792            from pyramid.config import Configurator
793
794            def included(config):
795                config.add_route('show_users', '/show')
012b97 796
5bf23f 797            def main(global_config, **settings):
CM 798                config = Configurator()
799                config.include(included, route_prefix='/users')
800
801         In the above configuration, the ``show_users`` route will have an
802         effective route pattern of ``/users/show``, instead of ``/show``
803         because the ``route_prefix`` argument will be prepended to the
804         pattern.
805
0b23b3 806         .. versionadded:: 1.2
TL 807            The ``route_prefix`` parameter.
808
f5ff7e 809         .. versionchanged:: 1.9
MM 810            The included function is wrapped with a call to
811            :meth:`pyramid.config.Configurator.begin` and
812            :meth:`pyramid.config.Configurator.end` while it is executed.
813
5bf23f 814         """
7a7c8c 815         # """ <-- emacs
5bf23f 816
b86fa9 817         action_state = self.action_state
5bf23f 818
CM 819         c = self.maybe_dotted(callable)
5ad401 820         module = self.inspect.getmodule(c)
5bf23f 821         if module is c:
f8bfc6 822             try:
CM 823                 c = getattr(module, 'includeme')
824             except AttributeError:
825                 raise ConfigurationError(
0c29cf 826                     "module %r has no attribute 'includeme'"
MM 827                     % (module.__name__)
828                 )
829
5bf23f 830         spec = module.__name__ + ':' + c.__name__
5ad401 831         sourcefile = self.inspect.getsourcefile(c)
CM 832
833         if sourcefile is None:
834             raise ConfigurationError(
835                 'No source file for module %r (.py file must exist, '
0c29cf 836                 'refusing to use orphan .pyc or .pyo file).' % module.__name__
MM 837             )
5bf23f 838
b86fa9 839         if action_state.processSpec(spec):
efd61e 840             with self.route_prefix_context(route_prefix):
HS 841                 configurator = self.__class__(
842                     registry=self.registry,
843                     package=package_of(module),
844                     root_package=self.root_package,
845                     autocommit=self.autocommit,
846                     route_prefix=self.route_prefix,
0c29cf 847                 )
efd61e 848                 configurator.basepath = os.path.dirname(sourcefile)
HS 849                 configurator.includepath = self.includepath + (spec,)
850
851                 self.begin()
852                 try:
853                     c(configurator)
854                 finally:
855                     self.end()
012b97 856
5bf23f 857     def add_directive(self, name, directive, action_wrap=True):
CM 858         """
859         Add a directive method to the configurator.
860
861         .. warning:: This method is typically only used by :app:`Pyramid`
862            framework extension authors, not by :app:`Pyramid` application
863            developers.
864
865         Framework extenders can add directive methods to a configurator by
866         instructing their users to call ``config.add_directive('somename',
867         'some.callable')``.  This will make ``some.callable`` accessible as
868         ``config.somename``.  ``some.callable`` should be a function which
869         accepts ``config`` as a first argument, and arbitrary positional and
870         keyword arguments following.  It should use config.action as
871         necessary to perform actions.  Directive methods can then be invoked
872         like 'built-in' directives such as ``add_view``, ``add_route``, etc.
873
874         The ``action_wrap`` argument should be ``True`` for directives which
875         perform ``config.action`` with potentially conflicting
876         discriminators.  ``action_wrap`` will cause the directive to be
877         wrapped in a decorator which provides more accurate conflict
878         cause information.
012b97 879
5bf23f 880         ``add_directive`` does not participate in conflict detection, and
CM 881         later calls to ``add_directive`` will override earlier calls.
882         """
883         c = self.maybe_dotted(directive)
884         if not hasattr(self.registry, '_directives'):
885             self.registry._directives = {}
886         self.registry._directives[name] = (c, action_wrap)
887
888     def __getattr__(self, name):
889         # allow directive extension names to work
890         directives = getattr(self.registry, '_directives', {})
891         c = directives.get(name)
892         if c is None:
893             raise AttributeError(name)
894         c, action_wrap = c
895         if action_wrap:
896             c = action_method(c)
c7cc88 897         # Create a bound method (works on both Py2 and Py3)
CM 898         # http://stackoverflow.com/a/1015405/209039
899         m = c.__get__(self, self.__class__)
5bf23f 900         return m
CM 901
902     def with_package(self, package):
903         """ Return a new Configurator instance with the same registry
80281d 904         as this configurator. ``package`` may be an actual Python package
TL 905         object or a :term:`dotted Python name` representing a package."""
b86fa9 906         configurator = self.__class__(
CM 907             registry=self.registry,
908             package=package,
ae6c88 909             root_package=self.root_package,
b86fa9 910             autocommit=self.autocommit,
CM 911             route_prefix=self.route_prefix,
844ed9 912             introspection=self.introspection,
0c29cf 913         )
b86fa9 914         configurator.basepath = self.basepath
CM 915         configurator.includepath = self.includepath
916         configurator.info = self.info
917         return configurator
5bf23f 918
CM 919     def maybe_dotted(self, dotted):
920         """ Resolve the :term:`dotted Python name` ``dotted`` to a
921         global Python object.  If ``dotted`` is not a string, return
922         it without attempting to do any name resolution.  If
923         ``dotted`` is a relative dotted name (e.g. ``.foo.bar``,
924         consider it relative to the ``package`` argument supplied to
925         this Configurator's constructor."""
926         return self.name_resolver.maybe_resolve(dotted)
927
928     def absolute_asset_spec(self, relative_spec):
929         """ Resolve the potentially relative :term:`asset
930         specification` string passed as ``relative_spec`` into an
931         absolute asset specification string and return the string.
932         Use the ``package`` of this configurator as the package to
933         which the asset specification will be considered relative
934         when generating an absolute asset specification.  If the
935         provided ``relative_spec`` argument is already absolute, or if
936         the ``relative_spec`` is not a string, it is simply returned."""
e6c2d2 937         if not isinstance(relative_spec, string_types):
5bf23f 938             return relative_spec
CM 939         return self._make_spec(relative_spec)
940
0c29cf 941     absolute_resource_spec = absolute_asset_spec  # b/w compat forever
5bf23f 942
804eb0 943     def begin(self, request=_marker):
5bf23f 944         """ Indicate that application or test configuration has begun.
CM 945         This pushes a dictionary containing the :term:`application
946         registry` implied by ``registry`` attribute of this
947         configurator and the :term:`request` implied by the
f271a6 948         ``request`` argument onto the :term:`thread local` stack
5bf23f 949         consulted by various :mod:`pyramid.threadlocal` API
804eb0 950         functions.
MM 951
952         If ``request`` is not specified and the registry owned by the
953         configurator is already pushed as the current threadlocal registry
954         then this method will keep the current threadlocal request unchanged.
955
956         .. versionchanged:: 1.8
957            The current threadlocal request is propagated if the current
958            threadlocal registry remains unchanged.
959
960         """
961         if request is _marker:
962             current = self.manager.get()
963             if current['registry'] == self.registry:
964                 request = current['request']
965             else:
966                 request = None
0c29cf 967         self.manager.push({'registry': self.registry, 'request': request})
5bf23f 968
CM 969     def end(self):
970         """ Indicate that application or test configuration has ended.
f271a6 971         This pops the last value pushed onto the :term:`thread local`
5bf23f 972         stack (usually by the ``begin`` method) and returns that
CM 973         value.
974         """
975         return self.manager.pop()
976
134ef7 977     def __enter__(self):
MM 978         self.begin()
979         return self
980
981     def __exit__(self, exc_type, exc_value, exc_traceback):
982         self.end()
983
984         if exc_value is None:
985             self.commit()
986
5bf23f 987     # this is *not* an action method (uses caller_package)
0c29cf 988     def scan(
MM 989         self, package=None, categories=None, onerror=None, ignore=None, **kw
990     ):
5bf23f 991         """Scan a Python package and any of its subpackages for objects
CM 992         marked with :term:`configuration decoration` such as
993         :class:`pyramid.view.view_config`.  Any decorated object found will
994         influence the current configuration state.
995
996         The ``package`` argument should be a Python :term:`package` or module
997         object (or a :term:`dotted Python name` which refers to such a
998         package or module).  If ``package`` is ``None``, the package of the
999         *caller* is used.
1000
1001         The ``categories`` argument, if provided, should be the
1002         :term:`Venusian` 'scan categories' to use during scanning.  Providing
1003         this argument is not often necessary; specifying scan categories is
1004         an extremely advanced usage.  By default, ``categories`` is ``None``
1005         which will execute *all* Venusian decorator callbacks including
1006         :app:`Pyramid`-related decorators such as
1007         :class:`pyramid.view.view_config`.  See the :term:`Venusian`
1008         documentation for more information about limiting a scan by using an
1009         explicit set of categories.
1010
1aeae3 1011         The ``onerror`` argument, if provided, should be a Venusian
CM 1012         ``onerror`` callback function.  The onerror function is passed to
1013         :meth:`venusian.Scanner.scan` to influence error behavior when an
1014         exception is raised during the scanning process.  See the
1015         :term:`Venusian` documentation for more information about ``onerror``
1016         callbacks.
1017
e4b8fa 1018         The ``ignore`` argument, if provided, should be a Venusian ``ignore``
CM 1019         value.  Providing an ``ignore`` argument allows the scan to ignore
1020         particular modules, packages, or global objects during a scan.
1021         ``ignore`` can be a string or a callable, or a list containing
1022         strings or callables.  The simplest usage of ``ignore`` is to provide
1023         a module or package by providing a full path to its dotted name.  For
1024         example: ``config.scan(ignore='my.module.subpackage')`` would ignore
1025         the ``my.module.subpackage`` package during a scan, which would
1026         prevent the subpackage and any of its submodules from being imported
1027         and scanned.  See the :term:`Venusian` documentation for more
1028         information about the ``ignore`` argument.
1029
5bf23f 1030         To perform a ``scan``, Pyramid creates a Venusian ``Scanner`` object.
CM 1031         The ``kw`` argument represents a set of keyword arguments to pass to
1032         the Venusian ``Scanner`` object's constructor.  See the
1033         :term:`venusian` documentation (its ``Scanner`` class) for more
1034         information about the constructor.  By default, the only keyword
1035         arguments passed to the Scanner constructor are ``{'config':self}``
1036         where ``self`` is this configurator object.  This services the
1037         requirement of all built-in Pyramid decorators, but extension systems
1038         may require additional arguments.  Providing this argument is not
1039         often necessary; it's an advanced usage.
1040
0b23b3 1041         .. versionadded:: 1.1
TL 1042            The ``**kw`` argument.
1043
1044         .. versionadded:: 1.3
1045            The ``ignore`` argument.
1046
5bf23f 1047         """
CM 1048         package = self.maybe_dotted(package)
0c29cf 1049         if package is None:  # pragma: no cover
5bf23f 1050             package = caller_package()
CM 1051
25c64c 1052         ctorkw = {'config': self}
1aeae3 1053         ctorkw.update(kw)
5bf23f 1054
1aeae3 1055         scanner = self.venusian.Scanner(**ctorkw)
25c64c 1056
0c29cf 1057         scanner.scan(
MM 1058             package, categories=categories, onerror=onerror, ignore=ignore
1059         )
5bf23f 1060
CM 1061     def make_wsgi_app(self):
1062         """ Commits any pending configuration statements, sends a
1063         :class:`pyramid.events.ApplicationCreated` event to all listeners,
1064         adds this configuration's registry to
1065         :attr:`pyramid.config.global_registries`, and returns a
1066         :app:`Pyramid` WSGI application representing the committed
1067         configuration state."""
1068         self.commit()
1069         app = Router(self.registry)
cfbbd6 1070
cfb2b5 1071         # Allow tools like "pshell development.ini" to find the 'last'
cfbbd6 1072         # registry configured.
5bf23f 1073         global_registries.add(self.registry)
cfbbd6 1074
f271a6 1075         # Push the registry onto the stack in case any code that depends on
cfbbd6 1076         # the registry threadlocal APIs used in listeners subscribed to the
CM 1077         # IApplicationCreated event.
0c1c58 1078         self.begin()
5bf23f 1079         try:
CM 1080             self.registry.notify(ApplicationCreated(app))
1081         finally:
0c1c58 1082             self.end()
5bf23f 1083
CM 1084         return app
1085
25c64c 1086
d94e4d 1087 # this class is licensed under the ZPL (stolen from Zope)
b86fa9 1088 class ActionState(object):
79ef3d 1089     def __init__(self):
da8e2a 1090         # NB "actions" is an API, dep'd upon by pyramid_zcml's load_zcml func
25c64c 1091         self.actions = []
79ef3d 1092         self._seen_files = set()
5bf23f 1093
CM 1094     def processSpec(self, spec):
1095         """Check whether a callable needs to be processed.  The ``spec``
1096         refers to a unique identifier for the callable.
1097
1098         Return True if processing is needed and False otherwise. If
1099         the callable needs to be processed, it will be marked as
1100         processed, assuming that the caller will procces the callable if
1101         it needs to be processed.
1102         """
1103         if spec in self._seen_files:
1104             return False
1105         self._seen_files.add(spec)
1106         return True
1107
0c29cf 1108     def action(
MM 1109         self,
1110         discriminator,
1111         callable=None,
1112         args=(),
1113         kw=None,
1114         order=0,
1115         includepath=(),
1116         info=None,
1117         introspectables=(),
1118         **extra
1119     ):
79ef3d 1120         """Add an action with the given discriminator, callable and arguments
CM 1121         """
b86fa9 1122         if kw is None:
CM 1123             kw = {}
7d109d 1124         action = extra
CM 1125         action.update(
1126             dict(
1127                 discriminator=discriminator,
1128                 callable=callable,
1129                 args=args,
1130                 kw=kw,
1131                 includepath=includepath,
1132                 info=info,
1133                 order=order,
1134                 introspectables=introspectables,
412b4a 1135             )
0c29cf 1136         )
79ef3d 1137         self.actions.append(action)
CM 1138
3b5ccb 1139     def execute_actions(self, clear=True, introspector=None):
79ef3d 1140         """Execute the configuration actions
CM 1141
1142         This calls the action callables after resolving conflicts
1143
1144         For example:
1145
1146         >>> output = []
1147         >>> def f(*a, **k):
1148         ...    output.append(('f', a, k))
d94e4d 1149         >>> context = ActionState()
79ef3d 1150         >>> context.actions = [
CM 1151         ...   (1, f, (1,)),
1152         ...   (1, f, (11,), {}, ('x', )),
1153         ...   (2, f, (2,)),
1154         ...   ]
1155         >>> context.execute_actions()
1156         >>> output
1157         [('f', (1,), {}), ('f', (2,), {})]
1158
1159         If the action raises an error, we convert it to a
1160         ConfigurationExecutionError.
1161
1162         >>> output = []
1163         >>> def bad():
1164         ...    bad.xxx
1165         >>> context.actions = [
1166         ...   (1, f, (1,)),
1167         ...   (1, f, (11,), {}, ('x', )),
1168         ...   (2, f, (2,)),
1169         ...   (3, bad, (), {}, (), 'oops')
1170         ...   ]
1171         >>> try:
1172         ...    v = context.execute_actions()
1173         ... except ConfigurationExecutionError, v:
1174         ...    pass
edfc4f 1175         >>> print(v)
79ef3d 1176         exceptions.AttributeError: 'function' object has no attribute 'xxx'
CM 1177           in:
1178           oops
1179
1180         Note that actions executed before the error still have an effect:
1181
1182         >>> output
1183         [('f', (1,), {}), ('f', (2,), {})]
1184
225364 1185         The execution is re-entrant such that actions may be added by other
MM 1186         actions with the one caveat that the order of any added actions must
1187         be equal to or larger than the current action.
412b4a 1188
225364 1189         >>> output = []
MM 1190         >>> def f(*a, **k):
1191         ...   output.append(('f', a, k))
1192         ...   context.actions.append((3, g, (8,), {}))
1193         >>> def g(*a, **k):
1194         ...    output.append(('g', a, k))
1195         >>> context.actions = [
1196         ...   (1, f, (1,)),
1197         ...   ]
1198         >>> context.execute_actions()
1199         >>> output
c56957 1200         [('f', (1,), {}), ('g', (8,), {})]
225364 1201
MM 1202         """
79ef3d 1203         try:
a52326 1204             all_actions = []
225364 1205             executed_actions = []
0f9a56 1206             action_iter = iter([])
MM 1207             conflict_state = ConflictResolverState()
225364 1208
a52326 1209             while True:
MM 1210                 # We clear the actions list prior to execution so if there
1211                 # are some new actions then we add them to the mix and resolve
1212                 # conflicts again. This orders the new actions as well as
1213                 # ensures that the previously executed actions have no new
1214                 # conflicts.
1215                 if self.actions:
1216                     all_actions.extend(self.actions)
0f9a56 1217                     action_iter = resolveConflicts(
0c29cf 1218                         self.actions, state=conflict_state
0f9a56 1219                     )
a52326 1220                     self.actions = []
MM 1221
0f9a56 1222                 action = next(action_iter, None)
a52326 1223                 if action is None:
MM 1224                     # we are done!
1225                     break
1226
412b4a 1227                 callable = action['callable']
CM 1228                 args = action['args']
1229                 kw = action['kw']
1230                 info = action['info']
73b206 1231                 # we use "get" below in case an action was added via a ZCML
CM 1232                 # directive that did not know about introspectables
1233                 introspectables = action.get('introspectables', ())
ae0ff2 1234
79ef3d 1235                 try:
2e651e 1236                     if callable is not None:
CM 1237                         callable(*args, **kw)
0f9a56 1238                 except Exception:
79ef3d 1239                     t, v, tb = sys.exc_info()
b86fa9 1240                     try:
0c29cf 1241                         reraise(
MM 1242                             ConfigurationExecutionError,
1243                             ConfigurationExecutionError(t, v, info),
1244                             tb,
1245                         )
b86fa9 1246                     finally:
25c64c 1247                         del t, v, tb
ae0ff2 1248
5e92f3 1249                 if introspector is not None:
CM 1250                     for introspectable in introspectables:
35ad08 1251                         introspectable.register(introspector, info)
225364 1252
MM 1253                 executed_actions.append(action)
1254
0f9a56 1255             self.actions = all_actions
MM 1256             return executed_actions
1257
79ef3d 1258         finally:
CM 1259             if clear:
0f9a56 1260                 self.actions = []
MM 1261
1262
1263 class ConflictResolverState(object):
1264     def __init__(self):
1265         # keep a set of resolved discriminators to test against to ensure
1266         # that a new action does not conflict with something already executed
1267         self.resolved_ainfos = {}
1268
1269         # actions left over from a previous iteration
1270         self.remaining_actions = []
1271
1272         # after executing an action we memoize its order to avoid any new
1273         # actions sending us backward
1274         self.min_order = None
1275
1276         # unique tracks the index of the action so we need it to increase
1277         # monotonically across invocations to resolveConflicts
1278         self.start = 0
1279
9c8ec5 1280
d94e4d 1281 # this function is licensed under the ZPL (stolen from Zope)
0f9a56 1282 def resolveConflicts(actions, state=None):
79ef3d 1283     """Resolve conflicting actions
CM 1284
1285     Given an actions list, identify and try to resolve conflicting actions.
412b4a 1286     Actions conflict if they have the same non-None discriminator.
0f9a56 1287
79ef3d 1288     Conflicting actions can be resolved if the include path of one of
CM 1289     the actions is a prefix of the includepaths of the other
1290     conflicting actions and is unequal to the include paths in the
1291     other conflicting actions.
0f9a56 1292
MM 1293     Actions are resolved on a per-order basis because some discriminators
1294     cannot be computed until earlier actions have executed. An action in an
1295     earlier order may execute successfully only to find out later that it was
1296     overridden by another action with a smaller include path. This will result
1297     in a conflict as there is no way to revert the original action.
1298
1299     ``state`` may be an instance of ``ConflictResolverState`` that
1300     can be used to resume execution and resolve the new actions against the
1301     list of executed actions from a previous call.
1302
79ef3d 1303     """
0f9a56 1304     if state is None:
MM 1305         state = ConflictResolverState()
1306
1307     # pick up where we left off last time, but track the new actions as well
1308     state.remaining_actions.extend(normalize_actions(actions))
1309     actions = state.remaining_actions
79ef3d 1310
859e94 1311     def orderandpos(v):
CM 1312         n, v = v
9c8ec5 1313         return (v['order'] or 0, n)
CM 1314
859e94 1315     def orderonly(v):
CM 1316         n, v = v
9c8ec5 1317         return v['order'] or 0
CM 1318
0f9a56 1319     sactions = sorted(enumerate(actions, start=state.start), key=orderandpos)
9c8ec5 1320     for order, actiongroup in itertools.groupby(sactions, orderonly):
82f677 1321         # "order" is an integer grouping. Actions in a lower order will be
9c8ec5 1322         # executed before actions in a higher order.  All of the actions in
CM 1323         # one grouping will be executed (its callable, if any will be called)
1324         # before any of the actions in the next.
1325         output = []
0f9a56 1326         unique = {}
MM 1327
1328         # error out if we went backward in order
1329         if state.min_order is not None and order < state.min_order:
0c29cf 1330             r = [
MM 1331                 'Actions were added to order={0} after execution had moved '
1332                 'on to order={1}. Conflicting actions: '.format(
1333                     order, state.min_order
1334                 )
1335             ]
0f9a56 1336             for i, action in actiongroup:
MM 1337                 for line in str(action['info']).rstrip().split('\n'):
1338                     r.append("  " + line)
1339             raise ConfigurationError('\n'.join(r))
82f677 1340
9c8ec5 1341         for i, action in actiongroup:
CM 1342             # Within an order, actions are executed sequentially based on
1343             # original action ordering ("i").
82f677 1344
0f9a56 1345             # "ainfo" is a tuple of (i, action) where "i" is an integer
MM 1346             # expressing the relative position of this action in the action
1347             # list being resolved, and "action" is an action dictionary.  The
1348             # purpose of an ainfo is to associate an "i" with a particular
1349             # action; "i" exists for sorting after conflict resolution.
1350             ainfo = (i, action)
9c8ec5 1351
0f9a56 1352             # wait to defer discriminators until we are on their order because
MM 1353             # the discriminator may depend on state from a previous order
9c8ec5 1354             discriminator = undefer(action['discriminator'])
CM 1355             action['discriminator'] = discriminator
1356
1357             if discriminator is None:
1358                 # The discriminator is None, so this action can never conflict.
1359                 # We can add it directly to the result.
1360                 output.append(ainfo)
1361                 continue
1362
1363             L = unique.setdefault(discriminator, [])
1364             L.append(ainfo)
1365
1366         # Check for conflicts
1367         conflicts = {}
1368         for discriminator, ainfos in unique.items():
0f9a56 1369             # We use (includepath, i) as a sort key because we need to
9c8ec5 1370             # sort the actions by the paths so that the shortest path with a
CM 1371             # given prefix comes first.  The "first" action is the one with the
0f9a56 1372             # shortest include path.  We break sorting ties using "i".
9c8ec5 1373             def bypath(ainfo):
0f9a56 1374                 path, i = ainfo[1]['includepath'], ainfo[0]
9c8ec5 1375                 return path, order, i
CM 1376
1377             ainfos.sort(key=bypath)
1378             ainfo, rest = ainfos[0], ainfos[1:]
0f9a56 1379             _, action = ainfo
735df7 1380
0f9a56 1381             # ensure this new action does not conflict with a previously
MM 1382             # resolved action from an earlier order / invocation
1383             prev_ainfo = state.resolved_ainfos.get(discriminator)
1384             if prev_ainfo is not None:
1385                 _, paction = prev_ainfo
1386                 basepath, baseinfo = paction['includepath'], paction['info']
1387                 includepath = action['includepath']
1388                 # if the new action conflicts with the resolved action then
1389                 # note the conflict, otherwise drop the action as it's
1390                 # effectively overriden by the previous action
0c29cf 1391                 if (
MM 1392                     includepath[: len(basepath)] != basepath
1393                     or includepath == basepath
1394                 ):
0f9a56 1395                     L = conflicts.setdefault(discriminator, [baseinfo])
MM 1396                     L.append(action['info'])
1397
1398             else:
1399                 output.append(ainfo)
1400
1401             basepath, baseinfo = action['includepath'], action['info']
1402             for _, action in rest:
9c8ec5 1403                 includepath = action['includepath']
CM 1404                 # Test whether path is a prefix of opath
0c29cf 1405                 if (
MM 1406                     includepath[: len(basepath)] != basepath
1407                     or includepath == basepath  # not a prefix
1408                 ):
9c8ec5 1409                     L = conflicts.setdefault(discriminator, [baseinfo])
CM 1410                     L.append(action['info'])
79ef3d 1411
9c8ec5 1412         if conflicts:
CM 1413             raise ConfigurationConflictError(conflicts)
735df7 1414
0f9a56 1415         # sort resolved actions by "i" and yield them one by one
MM 1416         for i, action in sorted(output, key=operator.itemgetter(0)):
1417             # do not memoize the order until we resolve an action inside it
1418             state.min_order = action['order']
1419             state.start = i + 1
1420             state.remaining_actions.remove(action)
1421             state.resolved_ainfos[action['discriminator']] = (i, action)
1422             yield action
50a8a0 1423
JA 1424
0f9a56 1425 def normalize_actions(actions):
MM 1426     """Convert old-style tuple actions to new-style dicts."""
1427     result = []
1428     for v in actions:
1429         if not isinstance(v, dict):
1430             v = expand_action_tuple(*v)
1431         result.append(v)
1432     return result
1433
1434
1435 def expand_action_tuple(
0c29cf 1436     discriminator,
MM 1437     callable=None,
1438     args=(),
1439     kw=None,
1440     includepath=(),
1441     info=None,
1442     order=0,
1443     introspectables=(),
0f9a56 1444 ):
b86fa9 1445     if kw is None:
CM 1446         kw = {}
412b4a 1447     return dict(
CM 1448         discriminator=discriminator,
1449         callable=callable,
1450         args=args,
1451         kw=kw,
1452         includepath=includepath,
1453         info=info,
1454         order=order,
1455         introspectables=introspectables,
0c29cf 1456     )
79ef3d 1457
0f9a56 1458
5bf23f 1459 global_registries = WeakOrderedSet()