| | |
| | | import inspect |
| | | import itertools |
| | | import logging |
| | | import operator |
| | | import os |
| | | import sys |
| | | import threading |
| | | import venusian |
| | | |
| | |
| | | |
| | | from pyramid.authorization import ACLAuthorizationPolicy |
| | | |
| | | from pyramid.compat import text_, reraise, string_types |
| | | from pyramid.compat import text_, string_types |
| | | |
| | | from pyramid.events import ApplicationCreated |
| | | |
| | | from pyramid.exceptions import ( |
| | | ConfigurationConflictError, |
| | | ConfigurationError, |
| | | ConfigurationExecutionError, |
| | | ) |
| | | from pyramid.exceptions import ConfigurationError |
| | | |
| | | from pyramid.httpexceptions import default_exceptionresponse_view |
| | | |
| | | from pyramid.path import caller_package, package_of |
| | | |
| | | from pyramid.registry import Introspectable, Introspector, Registry, undefer |
| | | from pyramid.registry import Introspectable, Introspector, Registry |
| | | |
| | | from pyramid.router import Router |
| | | |
| | |
| | | |
| | | from pyramid.util import WeakOrderedSet, object_description |
| | | |
| | | from pyramid.config.util import ActionInfo, PredicateList, action_method, not_ |
| | | from pyramid.config.util import PredicateList, action_method, not_ |
| | | |
| | | from pyramid.config.actions import ActionConfiguratorMixin |
| | | from pyramid.config.adapters import AdaptersConfiguratorMixin |
| | | from pyramid.config.assets import AssetsConfiguratorMixin |
| | | from pyramid.config.factories import FactoriesConfiguratorMixin |
| | |
| | | |
| | | |
| | | class Configurator( |
| | | ActionConfiguratorMixin, |
| | | TestingConfiguratorMixin, |
| | | TweensConfiguratorMixin, |
| | | SecurityConfiguratorMixin, |
| | |
| | | order=PHASE1_CONFIG, |
| | | ) # must be registered early |
| | | |
| | | @property |
| | | def action_info(self): |
| | | info = self.info # usually a ZCML action (ParserInfo) if self.info |
| | | if not info: |
| | | # Try to provide more accurate info for conflict reports |
| | | if self._ainfo: |
| | | info = self._ainfo[0] |
| | | else: |
| | | info = ActionInfo(None, 0, '', '') |
| | | return info |
| | | |
| | | def action( |
| | | self, |
| | | discriminator, |
| | | callable=None, |
| | | args=(), |
| | | kw=None, |
| | | order=0, |
| | | introspectables=(), |
| | | **extra |
| | | ): |
| | | """ Register an action which will be executed when |
| | | :meth:`pyramid.config.Configurator.commit` is called (or executed |
| | | immediately if ``autocommit`` is ``True``). |
| | | |
| | | .. warning:: This method is typically only used by :app:`Pyramid` |
| | | framework extension authors, not by :app:`Pyramid` application |
| | | developers. |
| | | |
| | | The ``discriminator`` uniquely identifies the action. It must be |
| | | given, but it can be ``None``, to indicate that the action never |
| | | conflicts. It must be a hashable value. |
| | | |
| | | The ``callable`` is a callable object which performs the task |
| | | associated with the action when the action is executed. It is |
| | | optional. |
| | | |
| | | ``args`` and ``kw`` are tuple and dict objects respectively, which |
| | | are passed to ``callable`` when this action is executed. Both are |
| | | optional. |
| | | |
| | | ``order`` is a grouping mechanism; an action with a lower order will |
| | | be executed before an action with a higher order (has no effect when |
| | | autocommit is ``True``). |
| | | |
| | | ``introspectables`` is a sequence of :term:`introspectable` objects |
| | | (or the empty sequence if no introspectable objects are associated |
| | | with this action). If this configurator's ``introspection`` |
| | | attribute is ``False``, these introspectables will be ignored. |
| | | |
| | | ``extra`` provides a facility for inserting extra keys and values |
| | | into an action dictionary. |
| | | """ |
| | | # catch nonhashable discriminators here; most unit tests use |
| | | # autocommit=False, which won't catch unhashable discriminators |
| | | assert hash(discriminator) |
| | | |
| | | if kw is None: |
| | | kw = {} |
| | | |
| | | autocommit = self.autocommit |
| | | action_info = self.action_info |
| | | |
| | | if not self.introspection: |
| | | # if we're not introspecting, ignore any introspectables passed |
| | | # to us |
| | | introspectables = () |
| | | |
| | | if autocommit: |
| | | # callables can depend on the side effects of resolving a |
| | | # deferred discriminator |
| | | self.begin() |
| | | try: |
| | | undefer(discriminator) |
| | | if callable is not None: |
| | | callable(*args, **kw) |
| | | for introspectable in introspectables: |
| | | introspectable.register(self.introspector, action_info) |
| | | finally: |
| | | self.end() |
| | | |
| | | else: |
| | | action = extra |
| | | action.update( |
| | | dict( |
| | | discriminator=discriminator, |
| | | callable=callable, |
| | | args=args, |
| | | kw=kw, |
| | | order=order, |
| | | info=action_info, |
| | | includepath=self.includepath, |
| | | introspectables=introspectables, |
| | | ) |
| | | ) |
| | | self.action_state.action(**action) |
| | | |
| | | def _get_action_state(self): |
| | | registry = self.registry |
| | | try: |
| | | state = registry.action_state |
| | | except AttributeError: |
| | | state = ActionState() |
| | | registry.action_state = state |
| | | return state |
| | | |
| | | def _set_action_state(self, state): |
| | | self.registry.action_state = state |
| | | |
| | | action_state = property(_get_action_state, _set_action_state) |
| | | |
| | | _ctx = action_state # bw compat |
| | | |
| | | def commit(self): |
| | | """ |
| | | Commit any pending configuration actions. If a configuration |
| | | conflict is detected in the pending configuration actions, this method |
| | | will raise a :exc:`ConfigurationConflictError`; within the traceback |
| | | of this error will be information about the source of the conflict, |
| | | usually including file names and line numbers of the cause of the |
| | | configuration conflicts. |
| | | |
| | | .. warning:: |
| | | You should think very carefully before manually invoking |
| | | ``commit()``. Especially not as part of any reusable configuration |
| | | methods. Normally it should only be done by an application author at |
| | | the end of configuration in order to override certain aspects of an |
| | | addon. |
| | | |
| | | """ |
| | | self.begin() |
| | | try: |
| | | self.action_state.execute_actions(introspector=self.introspector) |
| | | finally: |
| | | self.end() |
| | | self.action_state = ActionState() # old actions have been processed |
| | | |
| | | def include(self, callable, route_prefix=None): |
| | | """Include a configuration callable, to support imperative |
| | | application extensibility. |
| | |
| | | self.end() |
| | | |
| | | return app |
| | | |
| | | |
| | | # this class is licensed under the ZPL (stolen from Zope) |
| | | class ActionState(object): |
| | | def __init__(self): |
| | | # NB "actions" is an API, dep'd upon by pyramid_zcml's load_zcml func |
| | | self.actions = [] |
| | | self._seen_files = set() |
| | | |
| | | def processSpec(self, spec): |
| | | """Check whether a callable needs to be processed. The ``spec`` |
| | | refers to a unique identifier for the callable. |
| | | |
| | | Return True if processing is needed and False otherwise. If |
| | | the callable needs to be processed, it will be marked as |
| | | processed, assuming that the caller will procces the callable if |
| | | it needs to be processed. |
| | | """ |
| | | if spec in self._seen_files: |
| | | return False |
| | | self._seen_files.add(spec) |
| | | return True |
| | | |
| | | def action( |
| | | self, |
| | | discriminator, |
| | | callable=None, |
| | | args=(), |
| | | kw=None, |
| | | order=0, |
| | | includepath=(), |
| | | info=None, |
| | | introspectables=(), |
| | | **extra |
| | | ): |
| | | """Add an action with the given discriminator, callable and arguments |
| | | """ |
| | | if kw is None: |
| | | kw = {} |
| | | action = extra |
| | | action.update( |
| | | dict( |
| | | discriminator=discriminator, |
| | | callable=callable, |
| | | args=args, |
| | | kw=kw, |
| | | includepath=includepath, |
| | | info=info, |
| | | order=order, |
| | | introspectables=introspectables, |
| | | ) |
| | | ) |
| | | self.actions.append(action) |
| | | |
| | | def execute_actions(self, clear=True, introspector=None): |
| | | """Execute the configuration actions |
| | | |
| | | This calls the action callables after resolving conflicts |
| | | |
| | | For example: |
| | | |
| | | >>> output = [] |
| | | >>> def f(*a, **k): |
| | | ... output.append(('f', a, k)) |
| | | >>> context = ActionState() |
| | | >>> context.actions = [ |
| | | ... (1, f, (1,)), |
| | | ... (1, f, (11,), {}, ('x', )), |
| | | ... (2, f, (2,)), |
| | | ... ] |
| | | >>> context.execute_actions() |
| | | >>> output |
| | | [('f', (1,), {}), ('f', (2,), {})] |
| | | |
| | | If the action raises an error, we convert it to a |
| | | ConfigurationExecutionError. |
| | | |
| | | >>> output = [] |
| | | >>> def bad(): |
| | | ... bad.xxx |
| | | >>> context.actions = [ |
| | | ... (1, f, (1,)), |
| | | ... (1, f, (11,), {}, ('x', )), |
| | | ... (2, f, (2,)), |
| | | ... (3, bad, (), {}, (), 'oops') |
| | | ... ] |
| | | >>> try: |
| | | ... v = context.execute_actions() |
| | | ... except ConfigurationExecutionError, v: |
| | | ... pass |
| | | >>> print(v) |
| | | exceptions.AttributeError: 'function' object has no attribute 'xxx' |
| | | in: |
| | | oops |
| | | |
| | | Note that actions executed before the error still have an effect: |
| | | |
| | | >>> output |
| | | [('f', (1,), {}), ('f', (2,), {})] |
| | | |
| | | The execution is re-entrant such that actions may be added by other |
| | | actions with the one caveat that the order of any added actions must |
| | | be equal to or larger than the current action. |
| | | |
| | | >>> output = [] |
| | | >>> def f(*a, **k): |
| | | ... output.append(('f', a, k)) |
| | | ... context.actions.append((3, g, (8,), {})) |
| | | >>> def g(*a, **k): |
| | | ... output.append(('g', a, k)) |
| | | >>> context.actions = [ |
| | | ... (1, f, (1,)), |
| | | ... ] |
| | | >>> context.execute_actions() |
| | | >>> output |
| | | [('f', (1,), {}), ('g', (8,), {})] |
| | | |
| | | """ |
| | | try: |
| | | all_actions = [] |
| | | executed_actions = [] |
| | | action_iter = iter([]) |
| | | conflict_state = ConflictResolverState() |
| | | |
| | | while True: |
| | | # We clear the actions list prior to execution so if there |
| | | # are some new actions then we add them to the mix and resolve |
| | | # conflicts again. This orders the new actions as well as |
| | | # ensures that the previously executed actions have no new |
| | | # conflicts. |
| | | if self.actions: |
| | | all_actions.extend(self.actions) |
| | | action_iter = resolveConflicts( |
| | | self.actions, state=conflict_state |
| | | ) |
| | | self.actions = [] |
| | | |
| | | action = next(action_iter, None) |
| | | if action is None: |
| | | # we are done! |
| | | break |
| | | |
| | | callable = action['callable'] |
| | | args = action['args'] |
| | | kw = action['kw'] |
| | | info = action['info'] |
| | | # we use "get" below in case an action was added via a ZCML |
| | | # directive that did not know about introspectables |
| | | introspectables = action.get('introspectables', ()) |
| | | |
| | | try: |
| | | if callable is not None: |
| | | callable(*args, **kw) |
| | | except Exception: |
| | | t, v, tb = sys.exc_info() |
| | | try: |
| | | reraise( |
| | | ConfigurationExecutionError, |
| | | ConfigurationExecutionError(t, v, info), |
| | | tb, |
| | | ) |
| | | finally: |
| | | del t, v, tb |
| | | |
| | | if introspector is not None: |
| | | for introspectable in introspectables: |
| | | introspectable.register(introspector, info) |
| | | |
| | | executed_actions.append(action) |
| | | |
| | | self.actions = all_actions |
| | | return executed_actions |
| | | |
| | | finally: |
| | | if clear: |
| | | self.actions = [] |
| | | |
| | | |
| | | class ConflictResolverState(object): |
| | | def __init__(self): |
| | | # keep a set of resolved discriminators to test against to ensure |
| | | # that a new action does not conflict with something already executed |
| | | self.resolved_ainfos = {} |
| | | |
| | | # actions left over from a previous iteration |
| | | self.remaining_actions = [] |
| | | |
| | | # after executing an action we memoize its order to avoid any new |
| | | # actions sending us backward |
| | | self.min_order = None |
| | | |
| | | # unique tracks the index of the action so we need it to increase |
| | | # monotonically across invocations to resolveConflicts |
| | | self.start = 0 |
| | | |
| | | |
| | | # this function is licensed under the ZPL (stolen from Zope) |
| | | def resolveConflicts(actions, state=None): |
| | | """Resolve conflicting actions |
| | | |
| | | Given an actions list, identify and try to resolve conflicting actions. |
| | | Actions conflict if they have the same non-None discriminator. |
| | | |
| | | Conflicting actions can be resolved if the include path of one of |
| | | the actions is a prefix of the includepaths of the other |
| | | conflicting actions and is unequal to the include paths in the |
| | | other conflicting actions. |
| | | |
| | | Actions are resolved on a per-order basis because some discriminators |
| | | cannot be computed until earlier actions have executed. An action in an |
| | | earlier order may execute successfully only to find out later that it was |
| | | overridden by another action with a smaller include path. This will result |
| | | in a conflict as there is no way to revert the original action. |
| | | |
| | | ``state`` may be an instance of ``ConflictResolverState`` that |
| | | can be used to resume execution and resolve the new actions against the |
| | | list of executed actions from a previous call. |
| | | |
| | | """ |
| | | if state is None: |
| | | state = ConflictResolverState() |
| | | |
| | | # pick up where we left off last time, but track the new actions as well |
| | | state.remaining_actions.extend(normalize_actions(actions)) |
| | | actions = state.remaining_actions |
| | | |
| | | def orderandpos(v): |
| | | n, v = v |
| | | return (v['order'] or 0, n) |
| | | |
| | | def orderonly(v): |
| | | n, v = v |
| | | return v['order'] or 0 |
| | | |
| | | sactions = sorted(enumerate(actions, start=state.start), key=orderandpos) |
| | | for order, actiongroup in itertools.groupby(sactions, orderonly): |
| | | # "order" is an integer grouping. Actions in a lower order will be |
| | | # executed before actions in a higher order. All of the actions in |
| | | # one grouping will be executed (its callable, if any will be called) |
| | | # before any of the actions in the next. |
| | | output = [] |
| | | unique = {} |
| | | |
| | | # error out if we went backward in order |
| | | if state.min_order is not None and order < state.min_order: |
| | | r = [ |
| | | 'Actions were added to order={0} after execution had moved ' |
| | | 'on to order={1}. Conflicting actions: '.format( |
| | | order, state.min_order |
| | | ) |
| | | ] |
| | | for i, action in actiongroup: |
| | | for line in str(action['info']).rstrip().split('\n'): |
| | | r.append(" " + line) |
| | | raise ConfigurationError('\n'.join(r)) |
| | | |
| | | for i, action in actiongroup: |
| | | # Within an order, actions are executed sequentially based on |
| | | # original action ordering ("i"). |
| | | |
| | | # "ainfo" is a tuple of (i, action) where "i" is an integer |
| | | # expressing the relative position of this action in the action |
| | | # list being resolved, and "action" is an action dictionary. The |
| | | # purpose of an ainfo is to associate an "i" with a particular |
| | | # action; "i" exists for sorting after conflict resolution. |
| | | ainfo = (i, action) |
| | | |
| | | # wait to defer discriminators until we are on their order because |
| | | # the discriminator may depend on state from a previous order |
| | | discriminator = undefer(action['discriminator']) |
| | | action['discriminator'] = discriminator |
| | | |
| | | if discriminator is None: |
| | | # The discriminator is None, so this action can never conflict. |
| | | # We can add it directly to the result. |
| | | output.append(ainfo) |
| | | continue |
| | | |
| | | L = unique.setdefault(discriminator, []) |
| | | L.append(ainfo) |
| | | |
| | | # Check for conflicts |
| | | conflicts = {} |
| | | for discriminator, ainfos in unique.items(): |
| | | # We use (includepath, i) as a sort key because we need to |
| | | # sort the actions by the paths so that the shortest path with a |
| | | # given prefix comes first. The "first" action is the one with the |
| | | # shortest include path. We break sorting ties using "i". |
| | | def bypath(ainfo): |
| | | path, i = ainfo[1]['includepath'], ainfo[0] |
| | | return path, order, i |
| | | |
| | | ainfos.sort(key=bypath) |
| | | ainfo, rest = ainfos[0], ainfos[1:] |
| | | _, action = ainfo |
| | | |
| | | # ensure this new action does not conflict with a previously |
| | | # resolved action from an earlier order / invocation |
| | | prev_ainfo = state.resolved_ainfos.get(discriminator) |
| | | if prev_ainfo is not None: |
| | | _, paction = prev_ainfo |
| | | basepath, baseinfo = paction['includepath'], paction['info'] |
| | | includepath = action['includepath'] |
| | | # if the new action conflicts with the resolved action then |
| | | # note the conflict, otherwise drop the action as it's |
| | | # effectively overriden by the previous action |
| | | if ( |
| | | includepath[: len(basepath)] != basepath |
| | | or includepath == basepath |
| | | ): |
| | | L = conflicts.setdefault(discriminator, [baseinfo]) |
| | | L.append(action['info']) |
| | | |
| | | else: |
| | | output.append(ainfo) |
| | | |
| | | basepath, baseinfo = action['includepath'], action['info'] |
| | | for _, action in rest: |
| | | includepath = action['includepath'] |
| | | # Test whether path is a prefix of opath |
| | | if ( |
| | | includepath[: len(basepath)] != basepath |
| | | or includepath == basepath # not a prefix |
| | | ): |
| | | L = conflicts.setdefault(discriminator, [baseinfo]) |
| | | L.append(action['info']) |
| | | |
| | | if conflicts: |
| | | raise ConfigurationConflictError(conflicts) |
| | | |
| | | # sort resolved actions by "i" and yield them one by one |
| | | for i, action in sorted(output, key=operator.itemgetter(0)): |
| | | # do not memoize the order until we resolve an action inside it |
| | | state.min_order = action['order'] |
| | | state.start = i + 1 |
| | | state.remaining_actions.remove(action) |
| | | state.resolved_ainfos[action['discriminator']] = (i, action) |
| | | yield action |
| | | |
| | | |
| | | def normalize_actions(actions): |
| | | """Convert old-style tuple actions to new-style dicts.""" |
| | | result = [] |
| | | for v in actions: |
| | | if not isinstance(v, dict): |
| | | v = expand_action_tuple(*v) |
| | | result.append(v) |
| | | return result |
| | | |
| | | |
| | | def expand_action_tuple( |
| | | discriminator, |
| | | callable=None, |
| | | args=(), |
| | | kw=None, |
| | | includepath=(), |
| | | info=None, |
| | | order=0, |
| | | introspectables=(), |
| | | ): |
| | | if kw is None: |
| | | kw = {} |
| | | return dict( |
| | | discriminator=discriminator, |
| | | callable=callable, |
| | | args=args, |
| | | kw=kw, |
| | | includepath=includepath, |
| | | info=info, |
| | | order=order, |
| | | introspectables=introspectables, |
| | | ) |
| | | |
| | | |
| | | global_registries = WeakOrderedSet() |