summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-11-23 20:45:10 -0500
committerChris McDonough <chrism@plope.com>2011-11-23 20:45:10 -0500
commit3b5ccb38eb98e05604a5c19d89e8c77fb6104429 (patch)
treeb8f261d07283f19d2a373fbfbacedd89493ef130
parent6f4242bed8e7418984642da4e0ff861d39195410 (diff)
downloadpyramid-3b5ccb38eb98e05604a5c19d89e8c77fb6104429.tar.gz
pyramid-3b5ccb38eb98e05604a5c19d89e8c77fb6104429.tar.bz2
pyramid-3b5ccb38eb98e05604a5c19d89e8c77fb6104429.zip
first cut at introspection feature
-rw-r--r--pyramid/config/__init__.py88
-rw-r--r--pyramid/config/adapters.py11
-rw-r--r--pyramid/config/assets.py10
-rw-r--r--pyramid/config/factories.py13
-rw-r--r--pyramid/config/i18n.py53
-rw-r--r--pyramid/config/introspection.py151
-rw-r--r--pyramid/config/routes.py20
-rw-r--r--pyramid/config/tweens.py1
-rw-r--r--pyramid/config/views.py15
-rw-r--r--pyramid/interfaces.py86
-rw-r--r--pyramid/registry.py18
-rw-r--r--pyramid/tests/test_config/test_init.py4
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):