summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-11-22 03:33:45 +0000
committerChris McDonough <chrism@agendaless.com>2009-11-22 03:33:45 +0000
commitff0eb79c0137976f69d261f5e6ad7471c1c53dc9 (patch)
tree4d1f872e573e3509223c942948c9beb71c1e576c
parentc947545b2a86b5fd07fe5ad6a33556a5ce1f2f7b (diff)
downloadpyramid-ff0eb79c0137976f69d261f5e6ad7471c1c53dc9.tar.gz
pyramid-ff0eb79c0137976f69d261f5e6ad7471c1c53dc9.tar.bz2
pyramid-ff0eb79c0137976f69d261f5e6ad7471c1c53dc9.zip
Test load_zcml.
-rw-r--r--docs/narr/configuration.rst85
-rw-r--r--repoze/bfg/configuration.py89
-rw-r--r--repoze/bfg/tests/test_configuration.py45
3 files changed, 157 insertions, 62 deletions
diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst
index 767005e23..59fa70e86 100644
--- a/docs/narr/configuration.rst
+++ b/docs/narr/configuration.rst
@@ -18,19 +18,25 @@ application.
.. sidebar:: Frameworks vs. Libraries
- A framework differs from a *library*: library code is always
- *called* by code that you write, while a framework always *calls*
- code that you write. Using the average library is typically easier
- than using the average framework. During typical library usage,
- the developer can more granularly avoid ceding any control to code
- he does not himself author. During typical framework usage,
- however, the developer must cede a greater portion of control to
- the framework. In practice, using a framework to create a web
- application is often more practical than using a set of libraries
- if the framework provides a set of facilities and assumptions that
- fit a large portion of your application requirements.
- :mod:`repoze.bfg` is a framework that fits a large set of
- assumptions in the domain of web application creation.
+ A *framework* differs from a *library* in one very important way:
+ library code is always *called* by code that you write, while a
+ framework always *calls* code that you write. Using a set of
+ libraries to create an application is often initially easier than
+ using a framework to create an application, because the developer
+ can choose to cede control to library code he has not authored
+ selectively, making the resulting application easier to understand
+ for the original developer. When using a framework, the developer
+ is typically required to cede a greater portion of control to code
+ he has not authoried: code that resides in the framework itself.
+ You needn't use a framework at all to create a web application
+ using Python. A rich set of libraries exists for the platform
+ which you can snap together to effectively create your own
+ framework. In practice, however, using an existing framework to
+ create an application is often more practical than rolling your own
+ via a set of libraries if the framework provides a set of
+ facilities and assumptions that fit your application domain's
+ requirements. :mod:`repoze.bfg` is a framework that fits a large
+ set of assumptions in the domain of web application creation.
Because :mod:`repoze.bfg` is a framework, from the perspective of the
people who have written :mod:`repoze.bfg` itself, each deployment of
@@ -42,21 +48,21 @@ the :mod:`repoze.bfg` framework that manages accounting information.
:mod:`repoze.bfg` refers to the way in which code is plugged in to it
for a specific deployment as "configuration".
-It can be a bit strange to think of the act of plugging code which you
+It may be a bit strange to think of the act of plugging code which you
write into :mod:`repoze.bfg` as "configuration". Many people think of
-"configuration" as knobs that control operation of only a specific
-application deployment; for instance, it's easy to think of the values
-implied by a ``.ini.`` configuration file that is read at application
-startup time as configuration. However, because :mod:`repoze.bfg` is
-itself a framework, from the perspective of the authors of
-:mod:`repoze.bfg`, when you plug code into it, you **are** indeed
-"configuring" the :mod:`repoze.bfg` framework *itself* for the purpose
-of creating an application deployment. From the perspective of an
-developer creating an application using :mod:`repoze.bfg`, performing
-the tasks that :mod:`repoze.bfg` calls "configuration" might
-alternately be referred to as "wiring" or
-"plumbing". :mod:`repoze.bfg` refers to it as "configuration", for
-lack of a more elegant term.
+"configuration" as coarse knobs that inform the high-level operation
+of a specific application deployment; for instance, it's easy to think
+of the values implied by a ``.ini`` file that is read at application
+startup time as "configuration". We can draw an analogy to this type
+of configuration here: because :mod:`repoze.bfg` is itself a
+framework, from the perspective of the authors of :mod:`repoze.bfg`
+itself, when you plug code into it in various ways, you are indeed
+"configuring" :mod:`repoze.bfg` for the purpose of creating an
+application deployment. From the perspective of an developer creating
+an application using :mod:`repoze.bfg`, performing the tasks that
+:mod:`repoze.bfg` calls "configuration" might alternately be referred
+to as "wiring" or "plumbing". :mod:`repoze.bfg` refers to it as
+"configuration", for lack of a more fitting term.
There are a number of different mechanisms you may use to configure
:mod:`repoze.bfg` to create an application: *imperative* configuration
@@ -69,8 +75,8 @@ Hello World, Configured Imperatively
The mechanism simplest for existing Python programmers is "imperative"
configuration. This is the configuration mode in which developers
cede the least amount of control to the framework itself. Because
-application developers cede the least amount of control to the
-framework, it is also the easiest configuration mode to understand.
+application developers cede less control to the framework, it is also
+the easiest configuration mode to understand.
Here's the simplest :mod:`repoze.bfg` application, configured
imperatively:
@@ -196,7 +202,7 @@ Let's break this down this line-by-line:
if __name__ == '__main__':
config = Configurator()
-The ``if __name__ == '__main__':`` line above qrepresents a Python
+The ``if __name__ == '__main__':`` line above represents a Python
idiom: the code inside this if clause is not invoked unless the script
is run directly from the command line via, for example, ``python
helloworld.py`` where the file named ``helloworld.py`` contains the
@@ -298,19 +304,19 @@ Conclusion
~~~~~~~~~~
Our hello world application is the simplest possible :mod:`repoze.bfg`
-application, configured "imperatively". We can see more or less
-what's going on "under the hood" when we configure things
-imperatively. However, another mode of configuration exists named
-*declarative* configuration.
+application, configured "imperatively". We can see a good deal of
+what's going on "under the hood" when we configure a :mod:`repoze.bfg`
+application imperatively. However, another mode of configuration
+exists named *declarative* configuration.
Hello World, Configured Declaratively
-------------------------------------
-:mod:`repoze.bfg` can be configured for the same application
-"declaratively", if so desired. Declarative configuration relies on
-*declarations* made external to the code in a configuration file
-format named :term:`ZCML` (Zope Configuration Markup Language), an XML
-dialect.
+:mod:`repoze.bfg` can be configured for the same "hello world"
+application "declaratively", if so desired. Declarative configuration
+relies on *declarations* made external to the code in a configuration
+file format named :term:`ZCML` (Zope Configuration Markup Language),
+an XML dialect.
Declarative configuration mode is the configuration mode in which
developers cede the most amount of control to the framework itself.
@@ -364,3 +370,4 @@ previously created ``helloworld.py``:
/>
</configure>
+
diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py
index 9a48fa2d7..bf3284d00 100644
--- a/repoze/bfg/configuration.py
+++ b/repoze/bfg/configuration.py
@@ -64,9 +64,21 @@ from repoze.bfg.view import static
import martian
class Configurator(object):
- """ A wrapper around the registry that performs configuration tasks """
- def __init__(self, registry=None):
- self.package = caller_package()
+ """
+ A wrapper around the registry that performs configuration tasks.
+ A Configurator is used to configure a :mod:`repoze.bfg`
+ :term:`application registry`. The Configurator accepts a single
+ argument: ``registry``. If the ``registry`` argument is passed as
+ a non-``None`` value, it must be an instance of the
+ :mod:`repoze.bfg.registry.Registry` class representing the
+ registry to configure. If ``registry`` is ``None``, the
+ configurator will create a Registry instance itself and perform
+ some default configuration on the registry it creates. After
+ construction, the configurator may be used to add configuration to
+ the registry. The overall state of a registry is called the
+ 'configuration state'."""
+ def __init__(self, registry=None, package=None):
+ self.package = package or caller_package()
self.reg = registry
if registry is None:
registry = Registry(self.package.__name__)
@@ -78,11 +90,13 @@ class Configurator(object):
self.renderer(chameleon_text.renderer_factory, '.txt')
self.renderer(renderers.json_renderer_factory, 'json')
self.renderer(renderers.string_renderer_factory, 'string')
- self.settings(Settings({}))
+ self.settings({})
self.root_factory(DefaultRootFactory)
self.debug_logger(None)
def make_wsgi_app(self, manager=manager, getSiteManager=getSiteManager):
+ """ Returns a :mod:`repoze.bfg` WSGI application representing
+ the current configuration state."""
# manager and getSiteManager in arglist for testing dep injection only
from repoze.bfg.router import Router # avoid circdep
app = Router(self.reg)
@@ -102,7 +116,6 @@ class Configurator(object):
def declarative(self, root_factory, spec='configure.zcml',
settings=None, debug_logger=None, os=os,
lock=threading.Lock()):
-
self._default_configuration()
# debug_logger, os and lock *only* for unittests
@@ -130,23 +143,30 @@ class Configurator(object):
def split_spec(self, path_or_spec):
return resolve_resource_spec(path_or_spec, self.package.__name__)
- def load_zcml(self, spec, lock=threading.Lock()):
+ def load_zcml(self, spec='configure.zcml', lock=threading.Lock()):
+ """ Load configuration from a :term:`ZCML` file into the
+ current configuration state. The ``spec`` argument is an
+ absolute filename, a relative filename, or a :term:`resource
+ specification`, defaulting to ``configure.zcml``."""
+
# We push our ZCML-defined configuration into an app-local
- # component registry in order to allow more than one bfg app to live
- # in the same process space without one unnecessarily stomping on
- # the other's component registrations (although I suspect directives
- # that have side effects are going to fail). The only way to do
- # that currently is to override zope.component.getGlobalSiteManager
- # for the duration of the ZCML includes. We acquire a lock in case
- # another make_app runs in a different thread simultaneously, in a
- # vain attempt to prevent mixing of registrations. There's not much
- # we can do about non-makeRegistry code that tries to use the global
- # site manager API directly in a different thread while we hold the
- # lock. Those registrations will end up in our application's
- # registry.
+ # component registry in order to allow more than one bfg app
+ # to live in the same process space without one unnecessarily
+ # stomping on the other's component registrations (although I
+ # suspect directives that have side effects are going to
+ # fail). The only way to do that currently is to override
+ # zope.component.getGlobalSiteManager for the duration of the
+ # ZCML includes. We acquire a lock in case another load_zcml
+ # runs in a different thread simultaneously, in a vain attempt
+ # to prevent mixing of registrations. There's not much we can
+ # do about non-load_zcml code that tries to use the global
+ # site manager API directly in a different thread while we
+ # hold the lock. Those registrations will end up in our
+ # application's registry.
+
package_name, filename = self.split_spec(spec)
if package_name is None: # absolute filename
- package = None
+ package = self.package
else:
__import__(package_name)
package = sys.modules[package_name]
@@ -162,12 +182,15 @@ class Configurator(object):
lock.release()
manager.pop()
getSiteManager.reset()
+ return self.reg
def view(self, view=None, name="", for_=None, permission=None,
request_type=None, route_name=None, request_method=None,
request_param=None, containment=None, attr=None,
renderer=None, wrapper=None, xhr=False, accept=None,
header=None, path_info=None, _info=u''):
+ """ Add a :term:`view configuration` to the current
+ configuration state."""
if not view:
if renderer:
@@ -285,8 +308,8 @@ class Configurator(object):
renderer=None, view_renderer=None, view_header=None,
view_accept=None, view_xhr=False,
view_path_info=None, _info=u''):
- # the strange ordering of the request kw args above is for b/w
- # compatibility purposes.
+ """ Add a :term:`route configuration` to the current
+ configuration state."""
# these are route predicates; if they do not match, the next route
# in the routelist will be tried
_, predicates = _make_predicates(xhr=xhr,
@@ -330,6 +353,9 @@ class Configurator(object):
mapper.connect(path, name, factory, predicates=predicates)
def scan(self, package, _info=u'', martian=martian):
+ """ Scan a Python package and any of its subpackages for
+ configuration decorators such as ``@bfg_view``. Any decorator
+ found will influence the current configuration state."""
# martian overrideable only for unit tests
multi_grokker = BFGMultiGrokker()
multi_grokker.register(BFGViewGrokker())
@@ -340,18 +366,27 @@ class Configurator(object):
exclude_filter=lambda name: name.startswith('.'))
def authentication_policy(self, policy, _info=u''):
+ """ Add a :mod:`repoze.bfg` :term:`authentication policy` to
+ the current configuration."""
self.reg.registerUtility(policy, IAuthenticationPolicy, info=_info)
def authorization_policy(self, policy, _info=u''):
+ """ Add a :mod:`repoze.bfg` :term:`authorization policy` to
+ the current configuration state."""
self.reg.registerUtility(policy, IAuthorizationPolicy, info=_info)
def renderer(self, factory, name, _info=u''):
+ """ Add a :mod:`repoze.bfg` :term:`renderer` to the current
+ configuration state."""
iface = IRendererFactory
if name.startswith('.'):
iface = ITemplateRendererFactory
self.reg.registerUtility(factory, iface, name=name, info=_info)
def resource(self, to_override, override_with, _info=u'', _override=None,):
+ """ Add a :mod:`repoze.bfg` resource override to the current
+ configuration state. See :ref:`resources_chapter` for more
+ information about resource overrides."""
if to_override == override_with:
raise ConfigurationError('You cannot override a resource with '
'itself')
@@ -399,9 +434,14 @@ class Configurator(object):
override.insert(path, override_pkg_name, override_prefix)
def forbidden(self, *arg, **kw):
+ """ Add a default forbidden view to the current configuration
+ state."""
+
return self.system_view(IForbiddenView, *arg, **kw)
def notfound(self, *arg, **kw):
+ """ Add a default not found view to the current configuration
+ state."""
return self.system_view(INotFoundView, *arg, **kw)
def system_view(self, iface, view=None, attr=None, renderer=None,
@@ -421,6 +461,7 @@ class Configurator(object):
self.reg.registerUtility(derived_view, iface, '', info=_info)
def static(self, name, path, cache_max_age=3600, _info=u''):
+ """ Add a static view to the current configuration state."""
spec = self.make_spec(path)
view = static(spec, cache_max_age=cache_max_age)
self.route(name, "%s*subpath" % name, view=view,
@@ -428,6 +469,10 @@ class Configurator(object):
_info=_info)
def settings(self, settings):
+ """ Register the value passed in as ``settings`` as the basis
+ of the value returned by the
+ ``repoze.bfg.settings.get_settings`` API."""
+ settings = Settings(settings)
self.reg.registerUtility(settings, ISettings)
def debug_logger(self, logger):
@@ -436,6 +481,8 @@ class Configurator(object):
self.reg.registerUtility(logger, ILogger, 'repoze.bfg.debug')
def root_factory(self, factory):
+ """ Add a :term:`root factory` to the current configuration
+ state."""
self.reg.registerUtility(factory, IRootFactory)
self.reg.registerUtility(factory, IDefaultRootFactory) # b/c
diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py
index 8053256c4..477525431 100644
--- a/repoze/bfg/tests/test_configuration.py
+++ b/repoze/bfg/tests/test_configuration.py
@@ -3,12 +3,12 @@ import unittest
from repoze.bfg.testing import cleanUp
class ConfiguratorTests(unittest.TestCase):
- def _makeOne(self, registry=None):
+ def _makeOne(self, registry=None, package=None):
from repoze.bfg.registry import Registry
from repoze.bfg.configuration import Configurator
if registry is None:
registry = Registry()
- return Configurator(registry)
+ return Configurator(registry, package)
def _registerRenderer(self, config, name='.txt'):
from repoze.bfg.interfaces import IRendererFactory
@@ -91,10 +91,20 @@ class ConfiguratorTests(unittest.TestCase):
config.reg.registerUtility(settings, ISettings)
def test_ctor_no_registry(self):
+ import sys
from repoze.bfg.interfaces import ISettings
from repoze.bfg.configuration import Configurator
config = Configurator()
+ this_pkg = sys.modules['repoze.bfg.tests']
self.failUnless(config.reg.getUtility(ISettings))
+ self.assertEqual(config.package, this_pkg)
+
+ def test_ctor_with_package_registry(self):
+ import sys
+ from repoze.bfg.configuration import Configurator
+ bfg_pkg = sys.modules['repoze.bfg']
+ config = Configurator(package=bfg_pkg)
+ self.assertEqual(config.package, bfg_pkg)
def test__default_configuration(self):
from repoze.bfg.interfaces import ISettings
@@ -130,6 +140,37 @@ class ConfiguratorTests(unittest.TestCase):
self.failUnless(manager.popped)
self.assertEqual(len(subscriber), 1)
+ def test_load_zcml_default(self):
+ import repoze.bfg.tests.fixtureapp
+ config = self._makeOne(package=repoze.bfg.tests.fixtureapp)
+ registry = config.load_zcml()
+ from repoze.bfg.tests.fixtureapp.models import IFixture
+ self.failUnless(registry.queryUtility(IFixture)) # only in c.zcml
+
+ def test_load_zcml_as_resource_spec(self):
+ config = self._makeOne()
+ registry = config.load_zcml(
+ 'repoze.bfg.tests.fixtureapp:configure.zcml')
+ from repoze.bfg.tests.fixtureapp.models import IFixture
+ self.failUnless(registry.queryUtility(IFixture)) # only in c.zcml
+
+ def test_load_zcml_as_relative_filename(self):
+ import repoze.bfg.tests.fixtureapp
+ config = self._makeOne(package=repoze.bfg.tests.fixtureapp)
+ registry = config.load_zcml('configure.zcml')
+ from repoze.bfg.tests.fixtureapp.models import IFixture
+ self.failUnless(registry.queryUtility(IFixture)) # only in c.zcml
+
+ def test_load_zcml_as_absolute_filename(self):
+ import os
+ import repoze.bfg.tests.fixtureapp
+ config = self._makeOne(package=repoze.bfg.tests.fixtureapp)
+ dn = os.path.dirname(repoze.bfg.tests.fixtureapp.__file__)
+ c_z = os.path.join(dn, 'configure.zcml')
+ registry = config.load_zcml(c_z)
+ from repoze.bfg.tests.fixtureapp.models import IFixture
+ self.failUnless(registry.queryUtility(IFixture)) # only in c.zcml
+
def test_declarative_fixtureapp_default_filename_withpackage(self):
rootfactory = DummyRootFactory(None)
registry = self._callDeclarative(