From e496386fd5e9e98c0479f39d67c092d61720c29c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 28 Nov 2011 19:06:07 -0500 Subject: rejigger how registry is assigned an introspector; add tests for object_description --- pyramid/config/__init__.py | 79 +++++++++++++-------- pyramid/config/introspection.py | 152 ---------------------------------------- pyramid/registry.py | 146 +++++++++++++++++++++++++++++++++++--- pyramid/tests/test_events.py | 5 +- pyramid/tests/test_util.py | 70 ++++++++++++++++++ pyramid/util.py | 44 ++++++------ 6 files changed, 279 insertions(+), 217 deletions(-) delete mode 100644 pyramid/config/introspection.py diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index b0c86f0cd..3039abedd 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -12,7 +12,6 @@ from webob.exc import WSGIHTTPException as WebobWSGIHTTPException from pyramid.interfaces import ( IDebugLogger, IExceptionResponse, - IIntrospector, ) from pyramid.asset import resolve_asset_spec @@ -41,7 +40,11 @@ from pyramid.path import ( package_of, ) -from pyramid.registry import Registry +from pyramid.registry import ( + Introspectable, + Introspector, + Registry, + ) from pyramid.router import Router @@ -69,9 +72,9 @@ 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_('') +_marker = object() ConfigurationError = ConfigurationError # pyflakes @@ -88,7 +91,6 @@ class Configurator( SettingsConfiguratorMixin, FactoriesConfiguratorMixin, AdaptersConfiguratorMixin, - IntrospectionConfiguratorMixin, ): """ A Configurator is used to configure a :app:`Pyramid` @@ -231,8 +233,13 @@ class Configurator( If ``route_prefix`` is passed, all routes added with :meth:`pyramid.config.Configurator.add_route` will have the specified path - prepended to their pattern. This parameter is new in Pyramid 1.2.""" + prepended to their pattern. This parameter is new in Pyramid 1.2. + If ``introspector`` is passed, it must be an instance implementing the + :class:`pyramid.interfaces.IIntrospector` interface. If no + ``introspector`` is passed, the default IIntrospector implementation will + be used. + """ manager = manager # for testing injection venusian = venusian # for testing injection _ainfo = None @@ -240,6 +247,7 @@ class Configurator( includepath = () info = '' object_description = staticmethod(object_description) + introspectable = Introspectable def __init__(self, registry=None, @@ -259,6 +267,7 @@ class Configurator( autocommit=False, exceptionresponse_view=default_exceptionresponse_view, route_prefix=None, + introspector=None, ): if package is None: package = caller_package() @@ -286,15 +295,24 @@ class Configurator( session_factory=session_factory, default_view_mapper=default_view_mapper, exceptionresponse_view=exceptionresponse_view, + introspector=introspector, ) - def setup_registry(self, settings=None, root_factory=None, - authentication_policy=None, authorization_policy=None, - renderers=None, debug_logger=None, - locale_negotiator=None, request_factory=None, - renderer_globals_factory=None, default_permission=None, - session_factory=None, default_view_mapper=None, - exceptionresponse_view=default_exceptionresponse_view): + def setup_registry(self, + settings=None, + root_factory=None, + authentication_policy=None, + authorization_policy=None, + renderers=None, + debug_logger=None, + locale_negotiator=None, + request_factory=None, + renderer_globals_factory=None, + default_permission=None, + session_factory=None, + default_view_mapper=None, + exceptionresponse_view=default_exceptionresponse_view, + introspector=None): """ When you pass a non-``None`` ``registry`` argument to the :term:`Configurator` constructor, no initial setup is performed against the registry. This is because the registry you pass in may @@ -314,6 +332,10 @@ class Configurator( registry = self.registry self._fix_registry() + + if introspector is not None: + self.introspector = introspector + self._set_settings(settings) self._register_response_adapters() @@ -442,23 +464,24 @@ 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 _get_introspector(self): + introspector = getattr(self.registry, 'introspector', _marker) + if introspector is _marker: + introspector = Introspector() + self._set_introspector(introspector) + return introspector + + def _set_introspector(self, introspector): + self.registry.introspector = introspector + + def _del_introspector(self): + del self.registry.introspector + + introspector = property(_get_introspector, + _set_introspector, + _del_introspector) @property def action_info(self): diff --git a/pyramid/config/introspection.py b/pyramid/config/introspection.py deleted file mode 100644 index 666728fc5..000000000 --- a/pyramid/config/introspection.py +++ /dev/null @@ -1,152 +0,0 @@ -import operator - -from zope.interface import implementer - -from pyramid.interfaces import ( - IIntrospectable, - IIntrospector - ) - -@implementer(IIntrospector) -class Introspector(object): - def __init__(self): - self._refs = {} - self._categories = {} - self._counter = 0 - - def add(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 get_category(self, category_name, sort_fn=None): - if sort_fn is None: - sort_fn = operator.attrgetter('order') - category = self._categories[category_name] - values = category.values() - values = sorted(set(values), key=sort_fn) - return [{'introspectable':intr, 'related':self.related(intr)} for - intr in values] - - def categories(self): - return sorted(self._categories.keys()) - - def categorized(self, sort_fn=None): - L = [] - for category_name in self.categories(): - L.append((category_name, self.get_category(category_name, sort_fn))) - return L - - 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: - 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, []) - - def register(self, introspectable, action_info=''): - introspectable.action_info = action_info - self.add(introspectable) - for category_name, discriminator in introspectable.relations: - self.relate(( - introspectable.category_name, introspectable.discriminator), - (category_name, discriminator)) - - for category_name, discriminator in introspectable.unrelations: - self.unrelate(( - introspectable.category_name, introspectable.discriminator), - (category_name, discriminator)) - -@implementer(IIntrospectable) -class Introspectable(dict): - - order = 0 # mutated by introspector.add/introspector.add_intr - action_info = '' # mutated by introspector.register - - def __init__(self, category_name, discriminator, title, type_name): - self.category_name = category_name - self.discriminator = discriminator - self.title = title - self.type_name = type_name - 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)) - - @property - def discriminator_hash(self): - return hash(self.discriminator) - - 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) - - def __nonzero__(self): - return True - - __bool__ = __nonzero__ # py3 - -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/registry.py b/pyramid/registry.py index 99f5ee843..947643252 100644 --- a/pyramid/registry.py +++ b/pyramid/registry.py @@ -1,9 +1,15 @@ +import operator + +from zope.interface import implementer + from zope.interface.registry import Components from pyramid.compat import text_ + from pyramid.interfaces import ( ISettings, - IIntrospector + IIntrospector, + IIntrospectable, ) empty = text_('') @@ -78,16 +84,136 @@ class Registry(Components, dict): settings = property(_get_settings, _set_settings) - def _get_introspector(self): - return self.queryUtility(IIntrospector) +@implementer(IIntrospector) +class Introspector(object): + def __init__(self): + self._refs = {} + self._categories = {} + self._counter = 0 + + def add(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 get_category(self, category_name, sort_fn=None): + if sort_fn is None: + sort_fn = operator.attrgetter('order') + category = self._categories[category_name] + values = category.values() + values = sorted(set(values), key=sort_fn) + return [{'introspectable':intr, 'related':self.related(intr)} for + intr in values] + + def categories(self): + return sorted(self._categories.keys()) + + def categorized(self, sort_fn=None): + L = [] + for category_name in self.categories(): + L.append((category_name, self.get_category(category_name, sort_fn))) + return L + + 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: + 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, []) + + def register(self, introspectable, action_info=''): + introspectable.action_info = action_info + self.add(introspectable) + for category_name, discriminator in introspectable.relations: + self.relate(( + introspectable.category_name, introspectable.discriminator), + (category_name, discriminator)) + + for category_name, discriminator in introspectable.unrelations: + self.unrelate(( + introspectable.category_name, introspectable.discriminator), + (category_name, discriminator)) + +@implementer(IIntrospectable) +class Introspectable(dict): + + order = 0 # mutated by introspector.add/introspector.add_intr + action_info = '' # mutated by introspector.register + + def __init__(self, category_name, discriminator, title, type_name): + self.category_name = category_name + self.discriminator = discriminator + self.title = title + self.type_name = type_name + 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)) + + @property + def discriminator_hash(self): + return hash(self.discriminator) + + 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) - def _set_introspector(self, introspector): - self.registerUtility(introspector, IIntrospector) - - def _del_introspector(self): - self.unregisterUtility(IIntrospector) + def __nonzero__(self): + return True - introspector = property(_get_introspector, _set_introspector, - _del_introspector) + __bool__ = __nonzero__ # py3 global_registry = Registry('global') diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py index 108a5d2d9..4b58a129c 100644 --- a/pyramid/tests/test_events.py +++ b/pyramid/tests/test_events.py @@ -122,11 +122,10 @@ class ContextFoundEventTests(unittest.TestCase): class TestSubscriber(unittest.TestCase): def setUp(self): - registry = DummyRegistry() - self.config = testing.setUp(registry=registry) + self.config = testing.setUp() def tearDown(self): - self.config.end() + testing.tearDown() def _makeOne(self, *ifaces): from pyramid.events import subscriber diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 57bcd08d7..f45c75535 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -255,5 +255,75 @@ class Test_WeakOrderedSet(unittest.TestCase): self.assertEqual(list(wos), []) self.assertEqual(wos.last, None) +class Test_object_description(unittest.TestCase): + def _callFUT(self, object): + from pyramid.util import object_description + return object_description(object) + + def test_string(self): + self.assertEqual(self._callFUT('abc'), 'abc') + + def test_int(self): + self.assertEqual(self._callFUT(1), '1') + + def test_bool(self): + self.assertEqual(self._callFUT(True), 'True') + + def test_None(self): + self.assertEqual(self._callFUT(None), 'None') + + def test_float(self): + self.assertEqual(self._callFUT(1.2), '1.2') + + def test_tuple(self): + self.assertEqual(self._callFUT(('a', 'b')), "('a', 'b')") + + def test_set(self): + self.assertEqual(self._callFUT(set(['a'])), "set(['a'])") + + def test_list(self): + self.assertEqual(self._callFUT(['a']), "['a']") + + def test_dict(self): + self.assertEqual(self._callFUT({'a':1}), "{'a': 1}") + + def test_nomodule(self): + o = object() + self.assertEqual(self._callFUT(o), 'object %s' % str(o)) + + def test_module(self): + import pyramid + self.assertEqual(self._callFUT(pyramid), 'module pyramid') + + def test_method(self): + self.assertEqual( + self._callFUT(self.test_method), + 'method test_method of class pyramid.tests.test_util.' + 'Test_object_description') + + def test_class(self): + self.assertEqual( + self._callFUT(self.__class__), + 'class pyramid.tests.test_util.Test_object_description') + + def test_function(self): + self.assertEqual( + self._callFUT(dummyfunc), + 'function pyramid.tests.test_util.dummyfunc') + + def test_instance(self): + inst = Dummy() + self.assertEqual( + self._callFUT(inst), + "object %s" % str(inst)) + + def test_shortened_repr(self): + inst = ['1'] * 1000 + self.assertEqual( + self._callFUT(inst), + str(inst)[:100] + ' ... ]') + +def dummyfunc(): pass + class Dummy(object): pass diff --git a/pyramid/util.py b/pyramid/util.py index c439456b6..1fd612d09 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -1,12 +1,12 @@ import inspect import pkg_resources import sys -import types import weakref from pyramid.compat import ( integer_types, string_types, + text_, ) from pyramid.exceptions import ConfigurationError @@ -235,20 +235,20 @@ def strings_differ(string1, string2): return invalid_bits != 0 def object_description(object): - """ Produce a human-consumable string description of ``object``, usually - involving a Python dotted name. For example: + """ Produce a human-consumable text description of ``object``, + usually involving a Python dotted name. For example: .. code-block:: python >>> object_description(None) - 'None' + u'None' >>> from xml.dom import minidom >>> object_description(minidom) - 'module xml.dom.minidom' + u'module xml.dom.minidom' >>> object_description(minidom.Attr) - 'class xml.dom.minidom.Attr' + u'class xml.dom.minidom.Attr' >>> object_description(minidom.Attr.appendChild) - 'method appendChild of class xml.dom.minidom.Attr' + u'method appendChild of class xml.dom.minidom.Attr' >>> If this method cannot identify the type of the object, a generic @@ -259,11 +259,11 @@ def object_description(object): (possibly shortened) string representation is returned. """ if isinstance(object, string_types): - return object + return text_(object) if isinstance(object, integer_types): - return object + return text_(str(object)) if isinstance(object, (bool, float, type(None))): - return str(object) + return text_(str(object)) if isinstance(object, (tuple, set)): return shortrepr(object, ')') if isinstance(object, list): @@ -272,32 +272,28 @@ def object_description(object): return shortrepr(object, '}') module = inspect.getmodule(object) if module is None: - return 'object %s' % str(object) + return text_('object %s' % str(object)) modulename = module.__name__ if inspect.ismodule(object): - return 'module %s' % modulename + return text_('module %s' % modulename) if inspect.ismethod(object): oself = getattr(object, '__self__', None) - if oself is None: + if oself is None: # pragma: no cover oself = getattr(object, 'im_self', None) - return 'method %s of class %s.%s' % (object.__name__, modulename, - oself.__class__.__name__) + return text_('method %s of class %s.%s' % + (object.__name__, modulename, + oself.__class__.__name__)) if inspect.isclass(object): dottedname = '%s.%s' % (modulename, object.__name__) - return 'class %s' % dottedname + return text_('class %s' % dottedname) if inspect.isfunction(object): dottedname = '%s.%s' % (modulename, object.__name__) - return 'function %s' % dottedname - if inspect.isbuiltin(object): - dottedname = '%s.%s' % (modulename, object.__name__) - return 'builtin %s' % dottedname - if hasattr(object, '__name__'): - return 'object %s' % object.__name__ - return 'object %s' % str(object) + return text_('function %s' % dottedname) + return text_('object %s' % str(object)) def shortrepr(object, closer): r = str(object) if len(r) > 100: - r = r[:100] + '... %s' % closer + r = r[:100] + ' ... %s' % closer return r -- cgit v1.2.3