summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-01-16 01:32:43 -0500
committerChris McDonough <chrism@plope.com>2011-01-16 01:32:43 -0500
commit659d5f6b1f7605daaa0666fab7061bba9848d241 (patch)
tree4de47bbc52737bc717cd515806c48a57b5bb2bcb
parentfe847fad6768c9a7f2cb1b61d88164a370f6b2db (diff)
parentda358e42a1dda347f04d9331bb1e43f065546133 (diff)
downloadpyramid-659d5f6b1f7605daaa0666fab7061bba9848d241.tar.gz
pyramid-659d5f6b1f7605daaa0666fab7061bba9848d241.tar.bz2
pyramid-659d5f6b1f7605daaa0666fab7061bba9848d241.zip
Merge branch 'gawel-config_extend'
-rw-r--r--docs/api/config.rst2
-rw-r--r--pyramid/config.py28
-rw-r--r--pyramid/tests/__init__.py4
-rw-r--r--pyramid/tests/test_config.py83
4 files changed, 116 insertions, 1 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 9604833f3..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
@@ -302,6 +303,9 @@ class Configurator(object):
session_factory=session_factory,
default_view_mapper=default_view_mapper,
)
+ 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 {})
@@ -558,6 +562,30 @@ class Configurator(object):
config = self.__class__.with_context(context)
c(config)
+ 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):
"""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..61a665f5a 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -3216,6 +3216,83 @@ class ConfiguratorTests(unittest.TestCase):
for confinst in conflict:
yield confinst[2]
+class TestConfigurator_add_directive(unittest.TestCase):
+
+ def setUp(self):
+ from pyramid.config import Configurator
+ self.config = Configurator()
+
+ def test_extend_with_dotted_name(self):
+ from pyramid import tests
+ config = self.config
+ 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
+ actions = context_after.actions
+ self.assertEqual(len(actions), 1)
+ self.assertEqual(
+ context_after.actions[0][:3],
+ ('discrim', None, tests),
+ )
+
+ def test_extend_with_python_callable(self):
+ from pyramid import tests
+ config = self.config
+ config.add_directive(
+ 'dummy_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),
+ )
+
+ def test_extend_same_name_doesnt_conflict(self):
+ config = self.config
+ 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_action_method_successful(self):
+ from zope.configuration.config import ConfigurationConflictError
+ config = self.config
+ config.add_directive(
+ 'dummy_extend', dummy_extend)
+ config.dummy_extend('discrim')
+ config.dummy_extend('discrim')
+ self.assertRaises(ConfigurationConflictError, config.commit)
+
+ def test_directive_persists_across_configurator_creations(self):
+ from zope.configuration.config import GroupingContextDecorator
+ config = self.config
+ 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):
self.config = testing.setUp()
@@ -4943,4 +5020,10 @@ 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)
+
+def dummy_extend2(config, discrim):
+ config.action(discrim, None, config.registry)