diff options
| -rw-r--r-- | pyramid/config/__init__.py | 88 | ||||
| -rw-r--r-- | pyramid/config/adapters.py | 11 | ||||
| -rw-r--r-- | pyramid/config/assets.py | 10 | ||||
| -rw-r--r-- | pyramid/config/factories.py | 13 | ||||
| -rw-r--r-- | pyramid/config/i18n.py | 53 | ||||
| -rw-r--r-- | pyramid/config/introspection.py | 151 | ||||
| -rw-r--r-- | pyramid/config/routes.py | 20 | ||||
| -rw-r--r-- | pyramid/config/tweens.py | 1 | ||||
| -rw-r--r-- | pyramid/config/views.py | 15 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 86 | ||||
| -rw-r--r-- | pyramid/registry.py | 18 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_init.py | 4 |
12 files changed, 411 insertions, 59 deletions
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 40c3c037b..951f9b7ab 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -8,8 +8,11 @@ import venusian from webob.exc import WSGIHTTPException as WebobWSGIHTTPException -from pyramid.interfaces import IDebugLogger -from pyramid.interfaces import IExceptionResponse +from pyramid.interfaces import ( + IDebugLogger, + IExceptionResponse, + IIntrospector, + ) from pyramid.asset import resolve_asset_spec from pyramid.authorization import ACLAuthorizationPolicy @@ -45,6 +48,7 @@ from pyramid.config.tweens import TweensConfiguratorMixin from pyramid.config.util import action_method from pyramid.config.views import ViewsConfiguratorMixin from pyramid.config.zca import ZCAConfiguratorMixin +from pyramid.config.introspection import IntrospectionConfiguratorMixin empty = text_('') @@ -63,6 +67,7 @@ class Configurator( SettingsConfiguratorMixin, FactoriesConfiguratorMixin, AdaptersConfiguratorMixin, + IntrospectionConfiguratorMixin, ): """ A Configurator is used to configure a :app:`Pyramid` @@ -415,9 +420,37 @@ class Configurator( info=info, event=event) _registry.registerSelfAdapter = registerSelfAdapter + if not hasattr(_registry, 'introspector'): + def _get_introspector(reg): + return reg.queryUtility(IIntrospector) + + def _set_introspector(reg, introspector): + reg.registerUtility(introspector, IIntrospector) + + def _del_introspector(reg): + reg.unregisterUtility(IIntrospector) + + introspector = property(_get_introspector, _set_introspector, + _del_introspector) + + _registry.__class__.introspector = introspector + + # API - def action(self, discriminator, callable=None, args=(), kw=None, order=0): + @property + def action_info(self): + info = self.info # usually a ZCML action if self.info has data + if not info: + # Try to provide more accurate info for conflict reports + if self._ainfo: + info = self._ainfo[0] + else: + info = '' + return info + + def action(self, discriminator, callable=None, args=(), kw=None, order=0, + introspectables=()): """ Register an action which will be executed when :meth:`pyramid.config.Configurator.commit` is called (or executed immediately if ``autocommit`` is ``True``). @@ -442,27 +475,24 @@ class Configurator( kw = {} autocommit = self.autocommit + action_info = self.action_info if autocommit: if callable is not None: callable(*args, **kw) + for introspectable in introspectables: + introspectable(self.introspector, action_info) else: - info = self.info # usually a ZCML action if self.info has data - if not info: - # Try to provide more accurate info for conflict reports - if self._ainfo: - info = self._ainfo[0] - else: - info = '' self.action_state.action( discriminator, callable, args, kw, order, - info=info, + info=action_info, includepath=self.includepath, + introspectables=introspectables, ) def _get_action_state(self): @@ -488,7 +518,7 @@ class Configurator( 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.""" - self.action_state.execute_actions() + self.action_state.execute_actions(introspector=self.introspector) self.action_state = ActionState() # old actions have been processed def include(self, callable, route_prefix=None): @@ -841,7 +871,7 @@ class ActionState(object): return True def action(self, discriminator, callable=None, args=(), kw=None, order=0, - includepath=(), info=''): + includepath=(), info='', introspectables=()): """Add an action with the given discriminator, callable and arguments """ # NB: note that the ordering and composition of the action tuple should @@ -850,13 +880,14 @@ class ActionState(object): # the composition and ordering is). if kw is None: kw = {} - action = (discriminator, callable, args, kw, includepath, info, order) + action = (discriminator, callable, args, kw, includepath, info, order, + introspectables) # remove trailing false items while (len(action) > 2) and not action[-1]: action = action[:-1] self.actions.append(action) - def execute_actions(self, clear=True): + def execute_actions(self, clear=True, introspector=None): """Execute the configuration actions This calls the action callables after resolving conflicts @@ -907,7 +938,8 @@ class ActionState(object): """ try: for action in resolveConflicts(self.actions): - _, callable, args, kw, _, info, _ = expand_action(*action) + (_, callable, args, kw, _, info, _, + introspectables) = expand_action(*action) if callable is None: continue try: @@ -922,6 +954,9 @@ class ActionState(object): tb) finally: del t, v, tb + for introspectable in introspectables: + introspectable(introspector, info) + finally: if clear: del self.actions[:] @@ -980,7 +1015,8 @@ def resolveConflicts(actions): unique = {} output = [] for i in range(len(actions)): - (discriminator, callable, args, kw, includepath, info, order + (discriminator, callable, args, kw, includepath, info, order, + introspectables ) = expand_action(*(actions[i])) order = order or i @@ -989,14 +1025,15 @@ def resolveConflicts(actions): # never conflict. We can add it directly to the # configuration actions. output.append( - (order, discriminator, callable, args, kw, includepath, info) + (order, discriminator, callable, args, kw, includepath, info, + introspectables) ) continue a = unique.setdefault(discriminator, []) a.append( - (includepath, order, callable, args, kw, info) + (includepath, order, callable, args, kw, info, introspectables) ) # Check for conflicts @@ -1010,11 +1047,13 @@ def resolveConflicts(actions): # callable function is in the list return stupid[0:2] + stupid[3:] dups.sort(key=allbutfunc) - (basepath, i, callable, args, kw, baseinfo) = dups[0] + (basepath, i, callable, args, kw, baseinfo, introspectables) = dups[0] output.append( - (i, discriminator, callable, args, kw, basepath, baseinfo) + (i, discriminator, callable, args, kw, basepath, baseinfo, + introspectables) ) - for includepath, i, callable, args, kw, info in dups[1:]: + for (includepath, i, callable, args, kw, info, + introspectables) in dups[1:]: # Test whether path is a prefix of opath if (includepath[:len(basepath)] != basepath # not a prefix or @@ -1041,10 +1080,11 @@ def resolveConflicts(actions): # this function is licensed under the ZPL (stolen from Zope) def expand_action(discriminator, callable=None, args=(), kw=None, - includepath=(), info='', order=0): + includepath=(), info='', order=0, introspectables=()): if kw is None: kw = {} - return (discriminator, callable, args, kw, includepath, info, order) + return (discriminator, callable, args, kw, includepath, info, order, + introspectables) global_registries = WeakOrderedSet() diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py index f022e7f08..9efe29848 100644 --- a/pyramid/config/adapters.py +++ b/pyramid/config/adapters.py @@ -27,7 +27,10 @@ class AdaptersConfiguratorMixin(object): iface = (iface,) def register(): self.registry.registerHandler(subscriber, iface) - self.action(None, register) + intr = self.introspectable('subscriber', id(subscriber)) + intr['subscriber'] = subscriber + intr['interfaces'] = iface + self.action(None, register, introspectables=(intr,)) return subscriber @action_method @@ -52,7 +55,11 @@ class AdaptersConfiguratorMixin(object): reg.registerSelfAdapter((type_or_iface,), IResponse) else: reg.registerAdapter(adapter, (type_or_iface,), IResponse) - self.action((IResponse, type_or_iface), register) + discriminator = (IResponse, type_or_iface) + intr = self.introspectable('response adapter', discriminator) + intr['adapter'] = adapter + intr['type'] = type_or_iface + self.action(discriminator, register, introspectables=(intr,)) def _register_response_adapters(self): # cope with WebOb response objects that aren't decorated with IResponse diff --git a/pyramid/config/assets.py b/pyramid/config/assets.py index 08cc6dc38..42f36fc22 100644 --- a/pyramid/config/assets.py +++ b/pyramid/config/assets.py @@ -236,7 +236,15 @@ class AssetsConfiguratorMixin(object): to_package = sys.modules[override_package] override(from_package, path, to_package, override_prefix) - self.action(None, register) + intr = self.introspectable( + 'asset override', + (package, override_package, path, override_prefix) + ) + intr['package'] = package + intr['override_package'] = package + intr['override_prefix'] = override_prefix + intr['path'] = path + self.action(None, register, introspectables=(intr,)) override_resource = override_asset # bw compat diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index 0e59f9286..1b06a6b60 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -26,7 +26,10 @@ class FactoriesConfiguratorMixin(object): def register(): self.registry.registerUtility(factory, IRootFactory) self.registry.registerUtility(factory, IDefaultRootFactory) # b/c - self.action(IRootFactory, register) + + intr = self.introspectable('root factory', None) + intr['factory'] = factory + self.action(IRootFactory, register, introspectables=(intr,)) _set_root_factory = set_root_factory # bw compat @@ -46,7 +49,9 @@ class FactoriesConfiguratorMixin(object): session_factory = self.maybe_dotted(session_factory) def register(): self.registry.registerUtility(session_factory, ISessionFactory) - self.action(ISessionFactory, register) + intr = self.introspectable('session factory', None) + intr['factory'] = session_factory + self.action(ISessionFactory, register, introspectables=(intr,)) @action_method def set_request_factory(self, factory): @@ -67,5 +72,7 @@ class FactoriesConfiguratorMixin(object): factory = self.maybe_dotted(factory) def register(): self.registry.registerUtility(factory, IRequestFactory) - self.action(IRequestFactory, register) + intr = self.introspectable('request factory', None) + intr['factory'] = factory + self.action(IRequestFactory, register, introspectables=(intr,)) diff --git a/pyramid/config/i18n.py b/pyramid/config/i18n.py index 6eed99191..d7875f56b 100644 --- a/pyramid/config/i18n.py +++ b/pyramid/config/i18n.py @@ -38,7 +38,9 @@ class I18NConfiguratorMixin(object): """ def register(): self._set_locale_negotiator(negotiator) - self.action(ILocaleNegotiator, register) + intr = self.introspectable('locale negotiator', None) + intr['negotiator'] = negotiator + self.action(ILocaleNegotiator, register, introspectables=(intr,)) def _set_locale_negotiator(self, negotiator): locale_negotiator = self.maybe_dotted(negotiator) @@ -69,8 +71,10 @@ class I18NConfiguratorMixin(object): in the order they're provided in the ``*specs`` list argument (items earlier in the list trump ones later in the list). """ - for spec in specs[::-1]: # reversed + directories = [] + introspectables = [] + for spec in specs[::-1]: # reversed package_name, filename = self._split_spec(spec) if package_name is None: # absolute filename directory = filename @@ -80,25 +84,34 @@ class I18NConfiguratorMixin(object): directory = os.path.join(package_path(package), filename) if not os.path.isdir(os.path.realpath(directory)): - raise ConfigurationError('"%s" is not a directory' % directory) - - tdirs = self.registry.queryUtility(ITranslationDirectories) - if tdirs is None: - tdirs = [] - self.registry.registerUtility(tdirs, ITranslationDirectories) - - tdirs.insert(0, directory) - # XXX no action? + raise ConfigurationError('"%s" is not a directory' % + directory) + intr = self.introspectable('translation directory', directory) + intr['directory'] = directory + introspectables.append(intr) + directories.append(directory) - if specs: - - # We actually only need an IChameleonTranslate function - # utility to be registered zero or one times. We register the - # same function once for each added translation directory, - # which does too much work, but has the same effect. - - ctranslate = ChameleonTranslate(translator) - self.registry.registerUtility(ctranslate, IChameleonTranslate) + def register(): + for directory in directories: + + tdirs = self.registry.queryUtility(ITranslationDirectories) + if tdirs is None: + tdirs = [] + self.registry.registerUtility(tdirs, + ITranslationDirectories) + + tdirs.insert(0, directory) + # XXX no action? + + if directories: + # We actually only need an IChameleonTranslate function + # utility to be registered zero or one times. We register the + # same function once for each added translation directory, + # which does too much work, but has the same effect. + ctranslate = ChameleonTranslate(translator) + self.registry.registerUtility(ctranslate, IChameleonTranslate) + + self.action(None, register, introspectables=introspectables) def translator(msg): request = get_current_request() diff --git a/pyramid/config/introspection.py b/pyramid/config/introspection.py new file mode 100644 index 000000000..b30b8c4a1 --- /dev/null +++ b/pyramid/config/introspection.py @@ -0,0 +1,151 @@ +import operator + +from zope.interface import implementer + +from pyramid.interfaces import ( + IIntrospector, + IIntrospectable + ) + +@implementer(IIntrospector) +class Introspector(object): + action_info = None + def __init__(self): + self._refs = {} + self._categories = {} + self._counter = 0 + + def add(self, category_name, discriminator): + category = self._categories.setdefault(category_name, {}) + intr = category.get(discriminator) + if intr is None: + intr = Introspectable(category_name, discriminator) + category[intr.discriminator] = intr + category[intr.discriminator_hash] = intr + intr.order = self._counter + self._counter += 1 + return intr + + # for adding custom introspectables (instead of using .add) + + def add_intr(self, intr): + category = self._categories.setdefault(intr.category_name, {}) + category[intr.discriminator] = intr + category[intr.discriminator_hash] = intr + intr.order = self._counter + self._counter += 1 + + def get(self, category_name, discriminator, default=None): + category = self._categories.setdefault(category_name, {}) + intr = category.get(discriminator, default) + return intr + + def getall(self, category_name, sort_fn=None): + if sort_fn is None: + sort_fn = operator.attrgetter('order') + category = self._categories[category_name] + values = category.values() + return sorted(set(values), key=sort_fn) + + def remove(self, category_name, discriminator): + intr = self.get(category_name, discriminator) + if intr is None: + return + L = self._refs.pop((category_name, discriminator), []) + for d in L: + L2 = self._refs[d] + L2.remove((category_name, discriminator)) + category = self._categories[intr.category_name] + del category[intr.discriminator] + del category[intr.discriminator_hash] + + def _get_intrs_by_pairs(self, pairs): + introspectables = [] + for pair in pairs: + category_name, discriminator = pair + intr = self._categories.get(category_name, {}).get(discriminator) + if intr is None: + import pdb; pdb.set_trace() + raise KeyError((category_name, discriminator)) + introspectables.append(intr) + return introspectables + + def relate(self, *pairs): + introspectables = self._get_intrs_by_pairs(pairs) + relatable = ((x,y) for x in introspectables for y in introspectables) + for x, y in relatable: + L = self._refs.setdefault(x, []) + if x is not y and y not in L: + L.append(y) + + def unrelate(self, *pairs): + introspectables = self._get_intrs_by_pairs(pairs) + relatable = ((x,y) for x in introspectables for y in introspectables) + for x, y in relatable: + L = self._refs.get(x, []) + if y in L: + L.remove(y) + + def related(self, intr): + category_name, discriminator = intr.category_name, intr.discriminator + intr = self._categories.get(category_name, {}).get(discriminator) + if intr is None: + raise KeyError((category_name, discriminator)) + return self._refs.get(intr, []) + +@implementer(IIntrospectable) +class Introspectable(dict): + + order = 0 # mutated by .add/.add_intr + action_info = '' + + def __init__(self, category_name, discriminator): + self.category_name = category_name + self.discriminator = discriminator + self.relations = [] + self.unrelations = [] + + def relate(self, category_name, discriminator): + self.relations.append((category_name, discriminator)) + + def unrelate(self, category_name, discriminator): + self.unrelations.append((category_name, discriminator)) + + def __call__(self, introspector, action_info): + self.action_info = action_info + introspector.add_intr(self) + for category_name, discriminator in self.relations: + introspector.relate((self.category_name, self.discriminator), + (category_name, discriminator)) + + for category_name, discriminator in self.unrelations: + introspector.unrelate((self.category_name, self.discriminator), + (category_name, discriminator)) + + @property + def discriminator_hash(self): + return hash(self.discriminator) + + @property + def related(self, introspector): + return introspector.related(self) + + def __hash__(self): + return hash((self.category_name,) + (self.discriminator,)) + + def __repr__(self): + return '<%s category %r, discriminator %r>' % (self.__class__.__name__, + self.category_name, + self.discriminator) + +class IntrospectionConfiguratorMixin(object): + introspectable = Introspectable + + @property + def introspector(self): + introspector = getattr(self.registry, 'introspector', None) + if introspector is None: + introspector = Introspector() + self.registry.introspector = introspector + return introspector + diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index 4008a2e08..2e5f617af 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -365,6 +365,15 @@ class RoutesConfiguratorMixin(object): mapper = self.get_routes_mapper() + intr = self.introspectable('route', name) + intr['name'] = name + intr['pattern'] = pattern + intr['factory'] = factory + intr['predicates'] = predicates + intr['pregenerator'] = pregenerator + intr['static'] = static + intr['use_global_views'] = use_global_views + def register_route_request_iface(): request_iface = self.registry.queryUtility(IRouteRequest, name=name) if request_iface is None: @@ -377,9 +386,12 @@ class RoutesConfiguratorMixin(object): request_iface, IRouteRequest, name=name) def register_connect(): - return mapper.connect(name, pattern, factory, predicates=predicates, - pregenerator=pregenerator, static=static) - + route = mapper.connect( + name, pattern, factory, predicates=predicates, + pregenerator=pregenerator, static=static + ) + intr['object'] = route + return route # We have to connect routes in the order they were provided; # we can't use a phase to do that, because when the actions are @@ -389,7 +401,7 @@ class RoutesConfiguratorMixin(object): # But IRouteRequest interfaces must be registered before we begin to # process view registrations (in phase 3) self.action(('route', name), register_route_request_iface, - order=PHASE2_CONFIG) + order=PHASE2_CONFIG, introspectables=(intr,)) # deprecated adding views from add_route; must come after # route registration for purposes of autocommit ordering diff --git a/pyramid/config/tweens.py b/pyramid/config/tweens.py index 048309451..39ac018d0 100644 --- a/pyramid/config/tweens.py +++ b/pyramid/config/tweens.py @@ -4,7 +4,6 @@ from pyramid.interfaces import ITweens from pyramid.compat import string_types from pyramid.compat import is_nonstr_iter -from pyramid.compat import string_types from pyramid.exceptions import ConfigurationError from pyramid.tweens import excview_tween_factory from pyramid.tweens import MAIN, INGRESS, EXCVIEW diff --git a/pyramid/config/views.py b/pyramid/config/views.py index cf27c3514..4f11ed1ea 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1068,7 +1068,20 @@ class ViewsConfiguratorMixin(object): xhr, accept, header, path_info, match_param] discriminator.extend(sorted([hash(x) for x in custom_predicates])) discriminator = tuple(discriminator) - self.action(discriminator, register) + introspectables = [] + view_intr = self.introspectable('view', discriminator) + introspectables.append(view_intr) + if route_name: + view_intr.relate('route', route_name) # see add_route + if renderer is not None and renderer.name and '.' in renderer.name: + tmpl_intr = self.introspectable('template', discriminator) + tmpl_intr.relate('view', discriminator) + introspectables.append(tmpl_intr) + if permission is not None: + perm_intr = self.introspectable('permission', permission) + perm_intr.relate('view', discriminator) + introspectables.append(perm_intr) + self.action(discriminator, register, introspectables=introspectables) def derive_view(self, view, attr=None, renderer=None): """ diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index fcdf72d01..3f26c4386 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -857,9 +857,95 @@ class IRendererInfo(Interface): 'to the current application') +class IIntrospector(Interface): + def add(category_name, discriminator): + """ If the introspectable of category_name with ``discriminator`` + already exists, return it; otherwise create an IIntrospectable object + and return it""" + + def add_intr(intr): + """ Add the IIntrospectable ``intr`` (use + instead of :meth:`pyramid.interfaces.IIntrospector.add` when you have + a custom IIntrospectable). """ + + def get(category_name, discriminator, default=None): + """ Get the IIntrospectable related to the category_name and the + discriminator (or discriminator hash) ``discriminator``. If it does + not exist in the introspector, return ``default`` """ + + def getall(category_name, sort_fn=None): + """ Get a sequence of IIntrospectable objects related to + ``category_name`` . If ``sort_fn`` is ``None``, the sequence of + introspectable objects will be returned in the order they were added + to the introspector. Otherwise, sort_fn should be a function that + accepts an IIntrospectable and returns a value from it (ala the + ``key`` function of Python's ``sorted`` callable).""" + + def remove(category_name, discriminator): + """ Remove the IIntrospectable related to ``category_name`` and + ``discriminator`` from the introspector. This method will not raise + an error if the intrpsectable does not exist. """ + + def relate(*pairs): + """ Given any number of of ``(category_name, discriminator)`` pairs + passed as positional arguments, relate the associated introspectables + to each other. The introspectable related to each pair must have + already been added via ``.add`` or ``.add_intr``; a :exc:`KeyError` + will result if this is not true. An error will not be raised if any + pair has already been associated with another.""" + + def unrelate(*pairs): + """ Given any number of of ``(category_name, discriminator)`` pairs + passed as positional arguments, unrelate the associated introspectables + from each other. The introspectable related to each pair must have + already been added via ``.add`` or ``.add_intr``; a :exc:`KeyError` + will result if this is not true. An error will not be raised if any + pair is not already related to another.""" + + def related(category_name, discriminator): + """ Return a sequence of IIntrospectables related to the + IIntrospectable associated with (``category_name``, + ``discriminator``). Return the empty sequence if no relations for + exist.""" + + +class IIntrospectable(Interface): + """ An introspectable object used for configuration introspection. In + addition to the methods below, objects which implement this interface + must also implement all the methods of Python's + ``collections.MutableMapping`` (the "dictionary interface").""" + + order = Attribute('order added to introspector') + category_name = Attribute('introspection category name') + discriminator = Attribute('introspectable discriminator (within category) ' + '(must be hashable)') + discriminator_hash = Attribute('an integer hash of the discriminator') + + def relate(category_name, discriminator): + """ Relate this IIntrospectable with another IIntrospectable (the one + associated with the ``category_name`` and ``discriminator``). The + introspectable you wish to relate to must have already been added via + :meth:`pyramid.interfaces.IIntrospector.add` or + :meth:`pyramid.interfaces.IIntrospector.add_intr`; a :exc:`KeyError` + will result if this is not true. + """ + + def unrelate(self, category_name, discriminator): + """ Break any relationship between this IIntrospectable and another + IIntrospectable (the one associated with the ``category_name`` and + ``discriminator``). The introspectable you wish to unrelate from must + have already been added via + :meth:`pyramid.interfaces.IIntrospector.add` or + :meth:`pyramid.interfaces.IIntrospector.add_intr`; a :exc:`KeyError` + will result if this is not true. """ + + def related(self): + """ Return a set of related IIntrospectables """ + # configuration phases: a lower phase number means the actions associated # with this phase will be executed earlier than those with later phase # numbers. The default phase number is 0, FTR. PHASE1_CONFIG = -20 PHASE2_CONFIG = -10 + diff --git a/pyramid/registry.py b/pyramid/registry.py index ac706595e..99f5ee843 100644 --- a/pyramid/registry.py +++ b/pyramid/registry.py @@ -1,7 +1,10 @@ from zope.interface.registry import Components from pyramid.compat import text_ -from pyramid.interfaces import ISettings +from pyramid.interfaces import ( + ISettings, + IIntrospector + ) empty = text_('') @@ -26,6 +29,7 @@ class Registry(Components, dict): # for optimization purposes, if no listeners are listening, don't try # to notify them has_listeners = False + _settings = None def __nonzero__(self): @@ -74,4 +78,16 @@ class Registry(Components, dict): settings = property(_get_settings, _set_settings) + def _get_introspector(self): + return self.queryUtility(IIntrospector) + + def _set_introspector(self, introspector): + self.registerUtility(introspector, IIntrospector) + + def _del_introspector(self): + self.unregisterUtility(IIntrospector) + + introspector = property(_get_introspector, _set_introspector, + _del_introspector) + global_registry = Registry('global') diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index ca1508295..a65032143 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -750,7 +750,7 @@ pyramid.tests.test_config.dummy_include2""", self.assertEqual( state.actions, [(('discrim', None, (), {'a': 1}, 0), - {'info': 'abc', 'includepath':()})] + {'info': 'abc', 'includepath':(), 'introspectables':()})] ) def test_action_branching_nonautocommit_without_config_info(self): @@ -764,7 +764,7 @@ pyramid.tests.test_config.dummy_include2""", self.assertEqual( state.actions, [(('discrim', None, (), {'a': 1}, 0), - {'info': 'z', 'includepath':()})] + {'info': 'z', 'includepath':(), 'introspectables':()})] ) def test_scan_integration(self): |
