From cad4e3c917017d43c382df213a1c045523e247b8 Mon Sep 17 00:00:00 2001 From: Gael Pasgrimaud Date: Wed, 12 Jan 2011 19:07:14 +0100 Subject: add Configurator.extend feature --- pyramid/config.py | 27 ++++++++++++++++++++ pyramid/tests/__init__.py | 4 ++- pyramid/tests/test_config.py | 59 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 9604833f3..60197a4c6 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -558,6 +558,33 @@ class Configurator(object): config = self.__class__.with_context(context) c(config) + def extend(self, *callables): + _context = self._ctx + if _context is None: + _context = self._ctx = self._make_context(self.autocommit) + + klass = self.__class__ + + for c in callables: + c = self.maybe_dotted(c) + name = c.__name__ + sourcefile = inspect.getsourcefile(c) + module = inspect.getmodule(c) + spec = module.__name__ + ':' + name + if _context.processSpec(spec): + if hasattr(klass, name): + raise ConfigurationError( + "Configurator already have a method named %s" % name) + context = GroupingContextDecorator(_context) + context.basepath = os.path.dirname(sourcefile) + context.includepath = _context.includepath + (spec,) + context.package = package_of(module) + config = klass.with_context(context) + def extend_wrapper(*args, **kwargs): + c(config, *args, **kwargs) + extend_wrapper.__name__ = name + setattr(klass, name, staticmethod(extend_wrapper)) + @classmethod def with_context(cls, context): """A classmethod used by ZCML directives, diff --git a/pyramid/tests/__init__.py b/pyramid/tests/__init__.py index 5bb534f79..a62c29f47 100644 --- a/pyramid/tests/__init__.py +++ b/pyramid/tests/__init__.py @@ -1 +1,3 @@ -# package + +def dummy_extend(*args): + """used to test Configurator.extend""" diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index ac459d7e3..a16323313 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -3216,6 +3216,60 @@ class ConfiguratorTests(unittest.TestCase): for confinst in conflict: yield confinst[2] +class TestConfiguratorExtender(unittest.TestCase): + + def setUp(self): + from pyramid.config import Configurator + class Config(Configurator): pass + self.config = Config() + + def test_extend_with_dotted_name(self): + from pyramid import tests + config = self.config + context_before = config._make_context() + config._ctx = context_before + config.extend('pyramid.tests.test_config.dummy_extend') + self.assert_(hasattr(config, 'dummy_extend')) + config.dummy_extend('discrim') + context_after = config._ctx + actions = context_after.actions + self.assertEqual(len(actions), 1) + self.assertEqual( + context_after.actions[0][:3], + ('discrim', None, tests), + ) + self.assertEqual(context_after.basepath, None) + self.assertEqual(context_after.includepath, ()) + self.failUnless(context_after is context_before) + + def test_extend_with_python_callable(self): + from pyramid import tests + config = self.config + context_before = config._make_context() + config._ctx = context_before + config.extend(dummy_extend) + self.assert_(hasattr(config, 'dummy_extend')) + config.dummy_extend('discrim') + context_after = config._ctx + actions = context_after.actions + self.assertEqual(len(actions), 1) + self.assertEqual( + context_after.actions[0][:3], + ('discrim', None, tests), + ) + self.assertEqual(context_after.basepath, None) + self.assertEqual(context_after.includepath, ()) + self.failUnless(context_after is context_before) + + def test_extend_conflict(self): + from pyramid import tests + from pyramid.exceptions import ConfigurationError + config = self.config + context_before = config._make_context() + config._ctx = context_before + config.extend(dummy_extend) + self.assertRaises(ConfigurationError, config.extend, 'pyramid.tests.dummy_extend') + class TestViewDeriver(unittest.TestCase): def setUp(self): self.config = testing.setUp() @@ -4943,4 +4997,7 @@ class DummyHandler(object): # pragma: no cover def dummy_include(config): config.action('discrim', None, config.package) - + +def dummy_extend(config, discrim): + config.action(discrim, None, config.package) + -- cgit v1.2.3 From 2422ceae0750c65cc1e4ea097143f4c71fbba348 Mon Sep 17 00:00:00 2001 From: Gael Pasgrimaud Date: Wed, 12 Jan 2011 21:38:06 +0100 Subject: store extends in context and add attributes to instance --- pyramid/config.py | 19 +++++++++++++------ pyramid/tests/test_config.py | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 60197a4c6..8e709b607 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -275,6 +275,7 @@ class Configurator(object): default_permission=None, session_factory=None, default_view_mapper=None, + extends = None, autocommit=False, ): if package is None: @@ -302,6 +303,8 @@ class Configurator(object): session_factory=session_factory, default_view_mapper=default_view_mapper, ) + if extends: + self.extend(*extends) def _set_settings(self, mapping): settings = Settings(mapping or {}) @@ -425,6 +428,7 @@ class Configurator(object): context = PyramidConfigurationMachine() registerCommonDirectives(context) context.registry = self.registry + context.extends = [] context.autocommit = autocommit return context @@ -572,7 +576,7 @@ class Configurator(object): module = inspect.getmodule(c) spec = module.__name__ + ':' + name if _context.processSpec(spec): - if hasattr(klass, name): + if hasattr(self, name): raise ConfigurationError( "Configurator already have a method named %s" % name) context = GroupingContextDecorator(_context) @@ -580,10 +584,13 @@ class Configurator(object): context.includepath = _context.includepath + (spec,) context.package = package_of(module) config = klass.with_context(context) - def extend_wrapper(*args, **kwargs): - c(config, *args, **kwargs) - extend_wrapper.__name__ = name - setattr(klass, name, staticmethod(extend_wrapper)) + wrapped = action_method(c) + def wrapper(*args, **kwargs): + return wrapped(config, *args, **kwargs) + wrapper.__name__ = name + wrapper.__doc__ = c.__doc__ + self.__dict__[name] = wrapper + context.extends.append(c) @classmethod def with_context(cls, context): @@ -592,7 +599,7 @@ class Configurator(object): :meth:`pyramid.config.Configurator.include` to obtain a configurator with 'the right' context. Returns a new Configurator instance.""" configurator = cls(registry=context.registry, package=context.package, - autocommit=context.autocommit) + extends=context.extends, autocommit=context.autocommit) configurator._ctx = context return configurator diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index a16323313..6b5bfbf94 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -3220,8 +3220,7 @@ class TestConfiguratorExtender(unittest.TestCase): def setUp(self): from pyramid.config import Configurator - class Config(Configurator): pass - self.config = Config() + self.config = Configurator() def test_extend_with_dotted_name(self): from pyramid import tests @@ -3241,6 +3240,8 @@ class TestConfiguratorExtender(unittest.TestCase): self.assertEqual(context_after.basepath, None) self.assertEqual(context_after.includepath, ()) self.failUnless(context_after is context_before) + self.assertEqual(len(context_before.extends), 1) + self.assertEqual(context_before.extends, context_after.extends) def test_extend_with_python_callable(self): from pyramid import tests @@ -3260,9 +3261,10 @@ class TestConfiguratorExtender(unittest.TestCase): self.assertEqual(context_after.basepath, None) self.assertEqual(context_after.includepath, ()) self.failUnless(context_after is context_before) + self.assertEqual(len(context_before.extends), 1) + self.assertEqual(context_before.extends, context_after.extends) def test_extend_conflict(self): - from pyramid import tests from pyramid.exceptions import ConfigurationError config = self.config context_before = config._make_context() @@ -3270,6 +3272,14 @@ class TestConfiguratorExtender(unittest.TestCase): config.extend(dummy_extend) self.assertRaises(ConfigurationError, config.extend, 'pyramid.tests.dummy_extend') + def test_extend_no_conflict_with_two_instance(self): + from pyramid.config import Configurator + config = self.config + config.extend(dummy_extend) + config2 = Configurator() + config2.extend('pyramid.tests.dummy_extend') + self.failUnless(config._ctx.extends != config2._ctx.extends) + class TestViewDeriver(unittest.TestCase): def setUp(self): self.config = testing.setUp() -- cgit v1.2.3 From 035d033169f48d8250afafedd0a078d3e4f62922 Mon Sep 17 00:00:00 2001 From: Gael Pasgrimaud Date: Wed, 12 Jan 2011 22:01:16 +0100 Subject: improve code lines --- pyramid/config.py | 4 +--- pyramid/tests/test_config.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 8e709b607..468618c92 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -567,8 +567,6 @@ class Configurator(object): if _context is None: _context = self._ctx = self._make_context(self.autocommit) - klass = self.__class__ - for c in callables: c = self.maybe_dotted(c) name = c.__name__ @@ -583,7 +581,7 @@ class Configurator(object): context.basepath = os.path.dirname(sourcefile) context.includepath = _context.includepath + (spec,) context.package = package_of(module) - config = klass.with_context(context) + config = self.__class__.with_context(context) wrapped = action_method(c) def wrapper(*args, **kwargs): return wrapped(config, *args, **kwargs) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 6b5bfbf94..3fa9b954c 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -3277,8 +3277,19 @@ class TestConfiguratorExtender(unittest.TestCase): config = self.config config.extend(dummy_extend) config2 = Configurator() - config2.extend('pyramid.tests.dummy_extend') - self.failUnless(config._ctx.extends != config2._ctx.extends) + config2.extend(dummy_extend) + self.assertEqual(config._ctx.extends, config2._ctx.extends) + + def test_extend_after_with_package(self): + from pyramid import tests + config = self.config + context_before = config._make_context() + config._ctx = context_before + config.extend(dummy_extend) + config2 = config.with_package('pyramid.tests') + self.assert_(hasattr(config2, 'dummy_extend')) + self.assertEqual(len(config._ctx.extends), 1) + self.assertEqual(config._ctx.extends, config2._ctx.extends) class TestViewDeriver(unittest.TestCase): def setUp(self): -- cgit v1.2.3 From da358e42a1dda347f04d9331bb1e43f065546133 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 16 Jan 2011 01:32:29 -0500 Subject: simplify slightly --- docs/api/config.rst | 2 ++ pyramid/config.py | 60 +++++++++++++++----------------- pyramid/tests/test_config.py | 81 +++++++++++++++++++++++--------------------- 3 files changed, 73 insertions(+), 70 deletions(-) diff --git a/docs/api/config.rst b/docs/api/config.rst index 3f37e739c..51666f53f 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -76,6 +76,8 @@ .. automethod:: set_renderer_globals_factory + .. automethod:: add_directive + .. automethod:: testing_securitypolicy .. automethod:: testing_resources diff --git a/pyramid/config.py b/pyramid/config.py index 468618c92..b144b8fe3 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -2,6 +2,7 @@ import inspect import os import re import sys +import types import threading import traceback @@ -275,7 +276,6 @@ class Configurator(object): default_permission=None, session_factory=None, default_view_mapper=None, - extends = None, autocommit=False, ): if package is None: @@ -303,8 +303,9 @@ class Configurator(object): session_factory=session_factory, default_view_mapper=default_view_mapper, ) - if extends: - self.extend(*extends) + if hasattr(registry, '_directives'): + for name, directive in registry._directives.items(): + self.add_directive(name, directive) def _set_settings(self, mapping): settings = Settings(mapping or {}) @@ -428,7 +429,6 @@ class Configurator(object): context = PyramidConfigurationMachine() registerCommonDirectives(context) context.registry = self.registry - context.extends = [] context.autocommit = autocommit return context @@ -562,33 +562,29 @@ class Configurator(object): config = self.__class__.with_context(context) c(config) - def extend(self, *callables): - _context = self._ctx - if _context is None: - _context = self._ctx = self._make_context(self.autocommit) - - for c in callables: - c = self.maybe_dotted(c) - name = c.__name__ - sourcefile = inspect.getsourcefile(c) - module = inspect.getmodule(c) - spec = module.__name__ + ':' + name - if _context.processSpec(spec): - if hasattr(self, name): - raise ConfigurationError( - "Configurator already have a method named %s" % name) - context = GroupingContextDecorator(_context) - context.basepath = os.path.dirname(sourcefile) - context.includepath = _context.includepath + (spec,) - context.package = package_of(module) - config = self.__class__.with_context(context) - wrapped = action_method(c) - def wrapper(*args, **kwargs): - return wrapped(config, *args, **kwargs) - wrapper.__name__ = name - wrapper.__doc__ = c.__doc__ - self.__dict__[name] = wrapper - context.extends.append(c) + def add_directive(self, name, directive): + """ + Add a directive method to the configurator. + + Framework extenders can add directive methods to a configurator by + instructing their users to call ``config.add_directive('somename', + 'some.callable')``. This will make ``some.callable`` accessible as + ``config.somename``. ``some.callable`` should be a function which + accepts ``config`` as a first argument, and arbitrary positional and + keyword arguments following. It should use config.action as + necessary to perform actions. Directive methods can then be invoked + like 'built-in' directives such as ``add_view``, ``add_route``, etc. + + ``add_directive`` does not participate in conflict detection, and + later calls to ``add_directive`` will override earlier calls. + """ + c = self.maybe_dotted(directive) + if not hasattr(self.registry, '_directives'): + self.registry._directives = {} + self.registry._directives[name] = c + c = action_method(c) + m = types.MethodType(c, self, self.__class__) + setattr(self, name, m) @classmethod def with_context(cls, context): @@ -597,7 +593,7 @@ class Configurator(object): :meth:`pyramid.config.Configurator.include` to obtain a configurator with 'the right' context. Returns a new Configurator instance.""" configurator = cls(registry=context.registry, package=context.package, - extends=context.extends, autocommit=context.autocommit) + autocommit=context.autocommit) configurator._ctx = context return configurator diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 3fa9b954c..61a665f5a 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -3216,7 +3216,7 @@ class ConfiguratorTests(unittest.TestCase): for confinst in conflict: yield confinst[2] -class TestConfiguratorExtender(unittest.TestCase): +class TestConfigurator_add_directive(unittest.TestCase): def setUp(self): from pyramid.config import Configurator @@ -3225,9 +3225,8 @@ class TestConfiguratorExtender(unittest.TestCase): def test_extend_with_dotted_name(self): from pyramid import tests config = self.config - context_before = config._make_context() - config._ctx = context_before - config.extend('pyramid.tests.test_config.dummy_extend') + config.add_directive( + 'dummy_extend', 'pyramid.tests.test_config.dummy_extend') self.assert_(hasattr(config, 'dummy_extend')) config.dummy_extend('discrim') context_after = config._ctx @@ -3237,18 +3236,12 @@ class TestConfiguratorExtender(unittest.TestCase): context_after.actions[0][:3], ('discrim', None, tests), ) - self.assertEqual(context_after.basepath, None) - self.assertEqual(context_after.includepath, ()) - self.failUnless(context_after is context_before) - self.assertEqual(len(context_before.extends), 1) - self.assertEqual(context_before.extends, context_after.extends) def test_extend_with_python_callable(self): from pyramid import tests config = self.config - context_before = config._make_context() - config._ctx = context_before - config.extend(dummy_extend) + config.add_directive( + 'dummy_extend', dummy_extend) self.assert_(hasattr(config, 'dummy_extend')) config.dummy_extend('discrim') context_after = config._ctx @@ -3258,38 +3251,47 @@ class TestConfiguratorExtender(unittest.TestCase): context_after.actions[0][:3], ('discrim', None, tests), ) - self.assertEqual(context_after.basepath, None) - self.assertEqual(context_after.includepath, ()) - self.failUnless(context_after is context_before) - self.assertEqual(len(context_before.extends), 1) - self.assertEqual(context_before.extends, context_after.extends) - def test_extend_conflict(self): - from pyramid.exceptions import ConfigurationError + def test_extend_same_name_doesnt_conflict(self): config = self.config - context_before = config._make_context() - config._ctx = context_before - config.extend(dummy_extend) - self.assertRaises(ConfigurationError, config.extend, 'pyramid.tests.dummy_extend') + config.add_directive( + 'dummy_extend', dummy_extend) + config.add_directive( + 'dummy_extend', dummy_extend2) + self.assert_(hasattr(config, 'dummy_extend')) + config.dummy_extend('discrim') + context_after = config._ctx + actions = context_after.actions + self.assertEqual(len(actions), 1) + self.assertEqual( + context_after.actions[0][:3], + ('discrim', None, config.registry), + ) - def test_extend_no_conflict_with_two_instance(self): - from pyramid.config import Configurator + def test_extend_action_method_successful(self): + from zope.configuration.config import ConfigurationConflictError config = self.config - config.extend(dummy_extend) - config2 = Configurator() - config2.extend(dummy_extend) - self.assertEqual(config._ctx.extends, config2._ctx.extends) + config.add_directive( + 'dummy_extend', dummy_extend) + config.dummy_extend('discrim') + config.dummy_extend('discrim') + self.assertRaises(ConfigurationConflictError, config.commit) - def test_extend_after_with_package(self): - from pyramid import tests + def test_directive_persists_across_configurator_creations(self): + from zope.configuration.config import GroupingContextDecorator config = self.config - context_before = config._make_context() - config._ctx = context_before - config.extend(dummy_extend) - config2 = config.with_package('pyramid.tests') - self.assert_(hasattr(config2, 'dummy_extend')) - self.assertEqual(len(config._ctx.extends), 1) - self.assertEqual(config._ctx.extends, config2._ctx.extends) + config.add_directive('dummy_extend', dummy_extend) + context = config._make_context(autocommit=False) + context = GroupingContextDecorator(context) + config2 = config.with_context(context) + config2.dummy_extend('discrim') + context_after = config2._ctx + actions = context_after.actions + self.assertEqual(len(actions), 1) + self.assertEqual( + context_after.actions[0][:3], + ('discrim', None, config2.package), + ) class TestViewDeriver(unittest.TestCase): def setUp(self): @@ -5022,3 +5024,6 @@ def dummy_include(config): def dummy_extend(config, discrim): config.action(discrim, None, config.package) +def dummy_extend2(config, discrim): + config.action(discrim, None, config.registry) + -- cgit v1.2.3