From 13c923f6eaf56a49897af75e14c1f70d1b26c75b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 24 Nov 2009 20:49:16 +0000 Subject: Docs updates. --- TODO.txt | 17 ++- docs/api/configuration.rst | 10 +- docs/glossary.rst | 43 +++++- docs/index.rst | 1 + docs/narr/configuration.rst | 208 +++++++++++++++------------ docs/narr/events.rst | 95 +++++++++---- docs/narr/extending.rst | 31 ++-- docs/narr/hooks.rst | 252 ++++++++++++++++++++------------- docs/narr/scanning.rst | 188 ++++++++++++++++++++++++ docs/narr/views.rst | 30 +++- repoze/bfg/configuration.py | 33 ++++- repoze/bfg/includes/configure.zcml | 20 --- repoze/bfg/log.py | 4 +- repoze/bfg/paster.py | 4 +- repoze/bfg/renderers.py | 2 +- repoze/bfg/request.py | 2 + repoze/bfg/router.py | 2 +- repoze/bfg/security.py | 6 +- repoze/bfg/testing.py | 2 + repoze/bfg/tests/test_configuration.py | 61 +++++++- repoze/bfg/url.py | 2 +- repoze/bfg/view.py | 2 + 22 files changed, 732 insertions(+), 283 deletions(-) create mode 100644 docs/narr/scanning.rst diff --git a/TODO.txt b/TODO.txt index 1eabdb458..baef7280d 100644 --- a/TODO.txt +++ b/TODO.txt @@ -3,15 +3,20 @@ - Decide about creating a "real" request object during testing.setUp vs. creating a dummy request. -- Finish "configuration" documentation chapter and audit the rest of - the docs for "view configuration" and "route configuration" - references, etc. Add one more view registration (and maybe some - other registration) in the narrative. +- Audit the rest of the docs for "view configuration" and "route + configuration" references, etc. - Finish Configurator API documentation. -- notfound/forbidden as Configurator API arguments (get rid of - set_notfound_view, set_forbidden_view, set_security_policies?) +- set_security_policies as API method? + +- add set_response_factory or ctor arg and change hooks chapter. + +- add set_traverser or ctor arg and change hooks chapter. + +- add set_urlgenerator as ctor arg and change hooks chapter? - Basic WSGI documentation (pipeline / app / server). +- "Model graph" -> "object graph"? + diff --git a/docs/api/configuration.rst b/docs/api/configuration.rst index fb7c508fc..e621fbbe1 100644 --- a/docs/api/configuration.rst +++ b/docs/api/configuration.rst @@ -5,15 +5,17 @@ .. automodule:: repoze.bfg.configuration - .. autoclass:: Configurator(registry=None, package=None, settings=None, root_factory=None, zcml_file=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS) + .. autoclass:: Configurator(registry=None, package=None, settings=None, root_factory=None, zcml_file=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None) + + .. automethod:: add_renderer(name, factory) .. automethod:: add_route - .. automethod:: add_view + .. automethod:: add_static_view(name, path, cache_max_age=3600) - .. automethod:: add_renderer(name, factory) + .. automethod:: add_subscriber - .. automethod:: add_static_view(name, path, cache_max_age=3600) + .. automethod:: add_view .. automethod:: load_zcml(spec) diff --git a/docs/glossary.rst b/docs/glossary.rst index b50b77f96..79b780092 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -130,7 +130,7 @@ Glossary An alternative to graph traversal as a mechanism for locating a :term:`context` for a :term:`view`. When you use a :term:`route` in your :mod:`repoze.bfg` application via a ```` - declaration in ZCML, you are using URL dispatch. See the + :term:`ZCML declaration` in ZCML, you are using URL dispatch. See the :ref:`urldispatch_chapter` for more information. Context An object in the system that is found during :term:`traversal` or @@ -316,12 +316,16 @@ Glossary `Zope Configuration Markup Language `_, an XML dialect used by Zope and :mod:`repoze.bfg` for configuration tasks. ZCML - is capable of performing many different registrations and - declarations, but its primary purpose in :mod:`repoze.bfg` is to + is capable of performing different types of :term:`configuration + declaration`, but its primary purpose in :mod:`repoze.bfg` is to perform :term:`view configuration` and :term:`route configuration` within the ``configure.zcml`` file in a :mod:`repoze.bfg` application. ZCML in a :mod:`repoze.bfg` application represents the application's :term:`application registry`. + ZCML Directive + A ZCML "tag" such as ```` or ````. + ZCML Declaration + The concrete use of a :term:`ZCML directive` within a ZCML file. Zope Component Architecture The `Zope Component Architecture `_ (aka ZCA) is a system @@ -469,4 +473,35 @@ Glossary and a :term:`route predicate`. View predicates are attached to :term:`view configuration` and route predicates are attached to :term:`route configuration`. - + Decorator + A wrapper around a Python function or class which accepts the + function or class as its first argument and which returns an + arbitrary object. :mod:`repoze.bfg` provides several decorators, + used for configuration and return value modification purposes. See + also `PEP 318 `_. + Configuration Declaration + An individual method call made to an instance of a + :mod:`repoze.bfg` :term:`Configurator` object which performs an + arbitrary action, such as registering a :term:`view configuration` + (via the ``view`` method of the configurator) or :term:`route + configuration` (via the ``route`` method of the configurator). A + set of configuration declarations is also usually implied via the + use of a :term:`ZCML declaration` within an application, or a set + of configuration declarations might be performed by a :term:`scan` + of code in a package. + Configuration Decoration + Metadata implying one or more :term:`configuration declaration` + invocations. Often set by configuration Python :term:`decorator` + attributes, such as ``repoze.bfg.view.bfg_view``, aka ``@bfg_view``. + Scan + The term used by :mod:`repoze.bfg` to define the process of + importing and examining all code in a Python package or module for + :term:`configuration decoration`. + Configurator + An object used to do :term:`configuration declaration` within an + application. The most common configurator is an instance of the + ``repoze.bfg.configuration.Configurator`` class. + Imperative Configuration + The configuration mode in which you use Python to call methods on + a :term:`Configurator` in order to add each :term:`configuration + declaration` required by your application. diff --git a/docs/index.rst b/docs/index.rst index 9a45d91d1..97261b27a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,6 +35,7 @@ Narrative documentation in chapter form explaining how to use narr/introduction narr/install narr/configuration + narr/scanning narr/project narr/startup narr/urlmapping diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index 01fd58300..6a465db15 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -21,29 +21,29 @@ used to create all kinds of web applications. 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 authored: 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. + selectively, making the resulting application easier to understand. + When using a framework, the developer is typically required to cede + a greater portion of control to code he has not authored: 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 requirements. :mod:`repoze.bfg` is a framework + that fits a large set of assumptions in the domain of web + application construction. As a framework, the primary job of :mod:`repoze.bfg` is to make it -easier for a developer to create an arbitrary web application. From -the perspective of the authors of :mod:`repoze.bfg`, each deployment -of an application written using :mod:`repoze.bfg` implies a specific -*configuration* of the framework itself. For example, a song-serving -application might plug code into the framework that manages songs, -while the ledger- serving application might plug code 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". +easier for a developer to create an arbitrary web application. Each +deployment of an application written using :mod:`repoze.bfg` implies a +specific *configuration* of the framework itself. For example, a +song-serving application might plug code into the framework that +manages songs, while the ledger- serving application might plug in +code 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". Most people understand "configuration" as coarse knobs that inform the high-level operation of a specific application deployment. For @@ -57,13 +57,6 @@ code into the :mod:`repoze.bfg` framework, you are indeed "configuring" :mod:`repoze.bfg` for the purpose of creating a particular application deployment. -.. admonition:: Tip - - If the term "configuration" as used in this guide just doesn't seem - to "click" in your brain, it may help to mentally substitute the - term "configuration" with "wiring" or "plumbing" as you read the - chapter. - There are a number of different mechanisms you may use to configure :mod:`repoze.bfg` to create an application: *imperative* configuration and *declarative* configuration. We'll examine both modes in the @@ -173,9 +166,10 @@ the ``Response`` constructor as the *body* of the response. In the Each of these functions is known as a :term:`view callable`. View callables in a "real" :mod:`repoze.bfg` application are often -functions which accept a request and return a response. A view -callable can be represented via another type of object, like a class -or an instance, but for our purposes here, a function serves us well. +functions which accept a :term:`request` and return a +:term:`response`. A view callable can be represented via another type +of object, like a class or an instance, but for our purposes here, a +function serves us well. A view callable is called with a :term:`request` object, which is a representation of an HTTP request sent by a remote user agent. A view @@ -192,32 +186,52 @@ but return a response with the body ``Hello world!``; the Traversal ~~~~~~~~~ -If you've used the code in this tutorial already, you've actually +If you've run the code in this tutorial already, you've actually unwittingly configured :mod:`repoze.bfg` to serve an application that relies on :term:`traversal`. A full explanation of how :mod:`repoze.bfg` locates "the right" :term:`view callable` for a -given request requires some explanation of traversal. +given request requires some explanation of :term:`traversal`. + +Traversal is part of a mechanism used by :mod:`repoze.bfg` to map the +URL of some request to a particular :term:`view callable`. It is not +the only mechanism made available by :mod:`repoze.bfg` that allows the +mapping a URL to a view callable. Another distinct mode known as +:term:`URL dispatch` can alternately be used to find a view callable +based on a URL. However, our sample application uses only +:term:`traversal`. In :mod:`repoze.bfg` terms, :term:`traversal` is the act of walking -over a *directed graph* of objects from a root object using the -individual path segments of the "path info" portion of a URL (the data -following the hostname and port number, but before any query strng -elements or fragments) in order to find a :term:`context` object. - -We will use an analogy to clarify traversal. - -Let's imagine an inexperienced UNIX computer user, wishing only to -find a file and to invoke the ``cat`` command against that file. -Because he is inexperienced, the only commands he knows how to use are -``cd`` and ``cat``. And because he is inexperienced, he doesn't -understand that ``cat`` can take an absolute path specification as an -argument, so he doesn't know that you can issue a single command -command ``cat /an/absolute/path`` to get the desired result. Instead, -this user believes he must issue the ``cd`` command, starting from the -root, for each intermediate path segment, *even the path segment that -represents the file itself*. Once he gets an error (because you can't -succesfully ``cd`` into a file) , he knows he has reached the file he -wants. +over a *directed graph* of objects from a :term:`root` object using +the individual path segments of the "path info" portion of a URL (the +data following the hostname and port number, but before any query +strng elements or fragments, for example the ``/a/b/c`` portion of the +URL ``http://example.com/a/b/c?foo=1``) in order to find a +:term:`context` object and a :term:`view name`. The combination of +the :term:`context` object and the :term:`view name` (and, in more +complex configurations, other :term:`predicate` values) are used to +find "the right" :term:`view callable`, which will be invoked after +traversal. + +The object graph of our hello world application is very simple: +there's exactly one object in our graph; the default :term:`root` +object. + +We need to use an analogy to clarify how traversal works on an +arbitrary object graph. Let's imagine an inexperienced UNIX computer +user, wishing only to use the command line to find a file and to +invoke the ``cat`` command against that file. Because he is +inexperienced, the only commands he knows how to use are ``cd``, which +changes the current directory and ``cat``, which prints the contents +of a file. And because he is inexperienced, he doesn't understand +that ``cat`` can take an absolute path specification as an argument, +so he doesn't know that you can issue a single command command ``cat +/an/absolute/path`` to get the desired result. Instead, this user +believes he must issue the ``cd`` command, starting from the root, for +each intermediate path segment, *even the path segment that represents +the file itself*. Once he gets an error (because you cannot +succesfully ``cd`` into a file), he knows he has reached the file he +wants, and he will be able to execute ``cat`` against the resulting +path segment. This inexperienced user's attempt to execute ``cat`` against the file named ``/fiz/buz/myfile`` might be to issue the following set of UNIX @@ -226,6 +240,7 @@ commands: .. code-block:: bash :linenos: + cd / cd fiz cd buz cd myfile @@ -239,16 +254,18 @@ can run the ``cat`` command: cat myfile -Now he has his answer. +The contents of ``myfile`` are now printed on the user's behalf. :mod:`repoze.bfg` is very much like this inexperienced UNIX user as it uses :term:`traversal` against an object graph. In this analogy, we can map the ``cat`` program to the :mod:`repoze.bfg` concept of a -:term:`view callable`. The file being operated on in this analogy is -the :term:`context` object. The directory structure is the object -graph being traversed. The act of progressively changing directories -to find the file as well as the handling of a ``cd`` error as a stop -condition is analogous to :term:`traversal`. +:term:`view callable`: it is a program that can be run against some +:term:`context`. The file being operated on in this analogy is the +:term:`context` object; the context is the "last object found" in a +traversal. The directory structure is the object graph being +traversed. The act of progressively changing directories to find the +file as well as the handling of a ``cd`` error as a stop condition is +analogous to :term:`traversal`. Here's an image that depicts the :mod:`repoze.bfg` traversal process graphically as a flowchart: @@ -256,32 +273,35 @@ graphically as a flowchart: .. image:: modelgraphtraverser.png The object graph is traversed, beginning at a root object, represented -by ``/``; if there are further path segments in the path info, the -root object's ``__getitem__`` is called with the next path segment, -and it is presumed to return another graph object, *ad infinitum* -until all path segments are exhausted; if any node in the graph -doesn't *have* a ``__getitem__`` method, or if the ``__getitem__`` of -a node raises a ``KeyError``, traversal ends immediately. +by the root URL (``/``); if there are further path segments in the +path info of the request being processed, the root object's +``__getitem__`` is called with the next path segment, and it is +expected to return another graph object. The resulting object's +``__getitem__`` is called with the very next path segment, and it is +expected to return another graph object. This happens *ad infinitum* +until all path segments are exhausted. If at any point during +traversal any node in the graph doesn't *have* a ``__getitem__`` +method, or if the ``__getitem__`` of a node raises a ``KeyError``, +traversal ends immediately, and the node becomes the :term:`context`. The results of a :term:`traversal` include a :term:`context` and a :term:`view name`. The :term:`view name` is the *first* URL path segment in the set of path segments "left over" during -:term:`traversal`. - -Effectively, if traversal returns a non-empty :term:`view name`, it -means that traversal "ran out" of nodes in the graph before it -finished exhausting all the path segments implied by the path info of -the URL: no segments are "left over". In this case, because the -:term:`view name` is non-empty, a *non-default* view callable will be -invoked. - -The :term:`default view` of a :term:`context` is represented by a -:term:`view configuration` that has the :term:`view name` of the empty -string. The :term:`default view` is found when all path elements in -the URL *are* exhausted before :term:`traversal` returns a -:term:`context` object, causing the :term:`view name` to be ``''`` -(the empty string). When no path segements are "left over" after -traversal, the :term:`default view` for the context found is invoked. +:term:`traversal`. This will either be the empty string (``''``) or a +non-empty string (one of the path segment strings). The empty string +represents the :term:`default view` of a context object. + +The :term:`default view` is found when all path elements in the URL +are exhausted before :term:`traversal` returns a :term:`context` +object, causing the :term:`view name` to be ``''`` (the empty string). +When no path segements are "left over" after traversal, the +:term:`default view` for the context found is invoked. + +If traversal returns a non-empty :term:`view name`, it means that +traversal "ran out" of nodes in the graph before it finished +exhausting all the path segments implied by the path info of the URL: +no segments are "left over". In this case, because the :term:`view +name` is non-empty, a *non-default* view callable will be invoked. Apologies that this digression was required; on with the chapter. @@ -293,19 +313,19 @@ Apologies that this digression was required; on with the chapter. Relating Traversal to the Hello World Application ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Our application's :term:`root` object is a default root object used -when one isn't otherwise specified in application configuration. This -root object does not have a ``__getitem__`` method, thus it has no -children. Although in a more complex system there can be many +Our application's :term:`root` object is the *default* root object +used when one isn't otherwise specified in application configuration. +This root object does not have a ``__getitem__`` method, thus it has +no children. Although in a more complex system there can be many contexts which URLs resolve to in our application, effectively there is only ever one context: the root object. We have only a single default view registered (the registration for the ``hello_world`` view callable). Due to this set of circumstances, -you can consider the sole possible URL that will resolve to a default -view in this application the root URL ``'/'``. It is the only URL -that will resolve to the :term:`view name` of ``''`` (the empty -string). +you can consider the sole possible URL that will resolve to a +:term:`default view` in this application the root URL ``'/'``. It is +the only URL that will resolve to the :term:`view name` of ``''`` (the +empty string). We have only a single view registered for the :term:`view name` ``goodbye`` (the registration for the ``goodbye_world`` view @@ -454,8 +474,8 @@ view configuration registration for the ``hello_world`` view callable has no :term:`predicate` arguments, the ``hello_world`` view callable is applicable for the :term:`default view` of any :term:`context` resulting from a request. This isn't all that interesting in this -application, because we only *have* one potential context (the root -object). +application, because we always only have *one* potential context (the +root object): it is the only object in the graph. We've also registered a view configuration for another circumstance: the ``goodbye_world`` view callable has a ``name`` predicate of @@ -883,6 +903,14 @@ XML.com article `_. Conclusions ----------- +.. sidebar:: Which Configuration Mode Should I Use? + + We recommend declarative configuration (ZMCL), because it's the more + traditional form of configuration used by Zope-based systems, it can + be overridden and extended by third party deployers, and there are + more examples for it "in the wild". However, imperative mode + configuration can be simpler to understand. + :mod:`repoze.bfg` allows an application to perform configuration tasks either imperatively or declaratively. You can choose the mode that best fits your brain as necessary. diff --git a/docs/narr/events.rst b/docs/narr/events.rst index 028bacaa8..cd5a94871 100644 --- a/docs/narr/events.rst +++ b/docs/narr/events.rst @@ -27,26 +27,61 @@ when it's called. The mere existence of a subscriber function, however, is not sufficient to arrange for it to be called. To arrange for the subscriber to be called, you'll need to change your :term:`application -registry` by modifying your application's ``configure.zcml``. Here's -an example of a bit of XML you can add to the ``configure.zcml`` file -which registers the above ``mysubscriber`` function, which we assume -lives in a ``subscribers.py`` module within your application: +registry` by either of the following methods: -.. code-block:: xml - :linenos: +.. topic:: Configuring an Event Listener Imperatively - + You can imperatively configure a subscriber function to be called + for some event type via the ``add_subscriber`` method of a + :term:`Configurator`: + + .. code-block:: python + :linenos: + + from repoze.bfg.interfaces import INewRequest + + from subscribers import mysubscriber + + config.add_subscriber(mysubscriber, INewRequest) + + The first argument to ``add_subscriber`` is the subscriber + function; the second argument is the event type. See + :ref:`configuration_module` for API documentation related to the + ``add_subscriber`` method of a :term:`Configurator`. + +.. topic:: Configuring an Event Listener Through ZCML + + You can configure an event listener by modifying your application's + ``configure.zcml``. Here's an example of a bit of XML you can add + to the ``configure.zcml`` file which registers the above + ``mysubscriber`` function, which we assume lives in a + ``subscribers.py`` module within your application: -The above example means "every time the :mod:`repoze.bfg` framework -emits an event object that supplies an ``INewRequest`` interface, call -the ``mysubscriber`` function with the event object. As you can see, -a subscription is made in terms of an :term:`interface`. The event -object sent to a subscriber will always have possess an interface. -The interface itself provides documentation of what attributes of the -event are available. + .. code-block:: xml + :linenos: + + + + The *subscriber* :term:`ZCML directive` takes two attributes: + ``for``, and ``handler``. The value of ``for`` is the interface + the subscriber is registered for. Registering a subscriber for a + specific interface limits the event types that the subscriber will + receive to those specified by the interface. The value of + ``handler`` is a Python dotted-name path to the subscriber + function. + +Each of the above examples implies that every time the +:mod:`repoze.bfg` framework emits an event object that supplies an +``INewRequest`` interface, the ``mysubscriber`` function will be +called with an *event* object. + +As you can see, a subscription is made in terms of an +:term:`interface`. The event object sent to a subscriber will always +have possess an interface. The interface itself provides +documentation of what attributes of the event are available. For example, if you create event listener functions in a ``subscribers.py`` file in your application like so: @@ -77,6 +112,21 @@ file: handler=".subscribers.handle_new_response" /> +Or imperatively via the ``add_subscriber`` method of a +:term:`Configurator`: + +.. code-block:: python + :linenos: + + from repoze.bfg.interfaces import INewRequest + from repoze.bfg.interfaces import INewResponse + + from subscribers import handle_new_request + from subscribers import handle_new_response + + config.add_subscriber(handle_new_request, INewRequest) + config.add_subscriber(handle_new_response, INewResponse) + This causes the functions as to be registered as event subscribers within the :term:`application registry` . Under this configuration, when the application is run, each time a new request or response is @@ -99,14 +149,9 @@ defined at ``repoze.bfg.interfaces.INewResponse`` says it must. These particular interfaces, along with others, are documented in the :ref:`events_module` API chapter. -The *subscriber* ZCML element takes two attributes: ``for``, and -``handler``. The value of ``for`` is the interface the subscriber is -registered for. Registering a subscriber for a specific interface -limits the event types that the subscriber will receive to those -specified by the interface. The value of ``handler`` is a Python -dotted-name path to the subscriber function. - -The return value of a subscriber function is ignored. +The return value of a subscriber function is ignored. Subscribers to +the same event type are not guaranteed to be called in any particular +order relative to one another. .. _using_an_event_to_vary_the_request_type: diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst index e77f4cfbe..bfe971b55 100644 --- a/docs/narr/extending.rst +++ b/docs/narr/extending.rst @@ -28,16 +28,19 @@ declarations made using the ZCML ```` directive (or the repoze bfg using the :term:`pkg_resources` API such as static files and templates. -There's only one rule you absolutely need to obey if you want to build -a maximally extensible :mod:`repoze.bfg` application: you should not -use the ``@bfg_view`` decorator or any other decorator meant to be -detected via the ZCML ```` directive, and you mustn't configure -your :mod:`repoze.bfg` application *imperatively* by using any code -which configures the application by mutating the BFG component -registry via Python. Instead, you must use :term:`ZCML` for the -equivalent purposes. :term:`ZCML` declarations that belong to an -application can be "overridden" by integrators as necessary, but -decorators and imperative code which perform the same tasks cannot. +There's only one rule you need to obey if you want to build a +maximally extensible :mod:`repoze.bfg` application: you should not use +any :term:`configuration decoration` or :term:`imperative +configuration`. This means the application developer should avoid +relying on :term:`configuration decoration` meant to be detected via +the ZCML ```` directive, and you mustn't configure your +:mod:`repoze.bfg` application *imperatively* by using any code which +configures the application through methods of the :term:`Configurator` +except for its ``load_zcml`` method. Instead, you must use +:term:`ZCML` for the equivalent purposes. :term:`ZCML` declarations +that belong to an application can be "overridden" by integrators as +necessary, but decorators and imperative code which perform the same +tasks cannot. In general: use only :term:`ZCML` to configure your application if you'd like it to be extensible. @@ -103,10 +106,10 @@ one of two things may be true: :ref:`extending_the_application`. If the source of trouble is configuration done imperatively (perhaps - in the ``make_app`` function called during application startup), - you'll need to or copying configuration information out of decorator - arguments and code which does imperative configuration into - equivalent :term:`ZCML` declarations. + in the function called during application startup), you'll need to + or copying configuration information out of decorator arguments and + code which does imperative configuration into equivalent + :term:`ZCML` declarations. Once this is done, you should be able to extend or modify the application like any other (see :ref:`extending_the_application`). diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index f5fc4233b..7ed0172fc 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1,9 +1,9 @@ .. _hooks_chapter: -Using ZCML Hooks -================ +Using Hooks +=========== -ZCML "hooks" can be used to influence the behavior of the +"hooks" can be used to influence the behavior of the :mod:`repoze.bfg` framework in various ways. .. _changing_the_notfound_view: @@ -12,19 +12,82 @@ Changing the Not Found View --------------------------- When :mod:`repoze.bfg` can't map a URL to view code, it invokes a -notfound :term:`view`. The view it invokes can be customized by -placing something like the following ZCML in your ``configure.zcml`` -file. +notfound :term:`view`. The view it invokes can be customized through +application configuration. -.. code-block:: xml - :linenos: +.. topic:: Using ZCML + + If your application uses :term:`ZCML`, you can replace the Not Found + view by placing something like the following ZCML in your + ``configure.zcml`` file. + + .. code-block:: xml + :linenos: + + + + Replace ``helloworld.views.notfound_view`` with the Python dotted name + to the notfound view you want to use. + + Other available attributes of the ``notfound`` ZCML directive are as + follows: + + attr + + The attribute of the view callable to use if ``__call__`` is not + correct (has the same meaning as in the context of + :ref:`the_view_zcml_directive`; see the description of ``attr`` + there). + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + + renderer + + This is either a single string term (e.g. ``json``) or a string + implying a path or :term:`resource specification` + (e.g. ``templates/views.pt``) used when the view returns a + non-:term:`response` object. This attribute has the same meaning as + it would in the context of :ref:`the_view_zcml_directive`; see the + description of ``renderer`` there). + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + + wrapper + + The :term:`view name` (*not* an object dotted name) of another view + declared elsewhere in ZCML (or via the ``@bfg_view`` decorator) + which will receive the response body of this view as the + ``request.wrapped_body`` attribute of its own request, and the + response returned by this view as the ``request.wrapped_response`` + attribute of its own request. This attribute has the same meaning + as it would in the context of :ref:`the_view_zcml_directive`; see + the description of ``wrapper`` there). Note that the wrapper view + *should not* be protected by any permission; behavior is undefined + if it does. - + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. -Replace ``helloworld.views.notfound_view`` with the Python dotted name -to the notfound view you want to use. Here's some sample code that -implements a minimal NotFound view: +.. topic:: Using Imperative Configuration + + If your application uses :term:`imperative configuration`, you can + replace the Not Found view by using the ``set_notfound_view`` + method of the ``Configurator`` named ``config``: + + .. code-block:: python + :linenos: + + import helloworld.views + config.set_notfound_view(helloworld.views.notfound_view) + + Replace ``helloworld.views.notfound_view`` with a reference to the + Python :term:`view callable` you want to use to represent the Not + Found view. + + See :ref:`configuration_module` for more information about other + arguments to the ``set_notfound_view`` method. + +Here's some sample code that implements a minimal NotFound view: .. code-block:: python :linenos: @@ -41,68 +104,98 @@ implements a minimal NotFound view: This error will be different when the ``debug_notfound`` environment setting is true than it is when it is false. -Other available attributes of the ``notfound`` ZCML directive are as -follows: +.. _changing_the_forbidden_view: -attr +Changing the Forbidden View +--------------------------- - The attribute of the view callable to use if ``__call__`` is not - correct (has the same meaning as in the context of - :ref:`the_view_zcml_directive`; see the description of ``attr`` - there). +When :mod:`repoze.bfg` can't authorize execution of a view based on +the authorization policy in use, it invokes a "forbidden view". The +default forbidden response has a 401 status code and is very plain, +but it can be overridden as necessary using one of the following +mechanisms: - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. +.. topic:: Using ZCML -renderer + If your application uses :term:`ZCML`, you can replace the + Forbidden view by placing something like the following ZCML in your + ``configure.zcml`` file. - This is either a single string term (e.g. ``json``) or a string - implying a path or :term:`resource specification` - (e.g. ``templates/views.pt``) used when the view returns a - non-:term:`response` object. This attribute has the same meaning as - it would in the context of :ref:`the_view_zcml_directive`; see the - description of ``renderer`` there). + .. code-block:: xml + :linenos: - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + -wrapper - The :term:`view name` (*not* an object dotted name) of another view - declared elsewhere in ZCML (or via the ``@bfg_view`` decorator) - which will receive the response body of this view as the - ``request.wrapped_body`` attribute of its own request, and the - response returned by this view as the ``request.wrapped_response`` - attribute of its own request. This attribute has the same meaning - as it would in the context of :ref:`the_view_zcml_directive`; see - the description of ``wrapper`` there). Note that the wrapper view - *should not* be protected by any permission; behavior is undefined - if it does. + Replace ``helloworld.views.forbidden_view`` with the Python + dotted name to the forbidden view you want to use. - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + Other available attributes of the ``forbidden`` ZCML directive are as + follows: -.. _changing_the_forbidden_view: + attr -Changing the Forbidden View ---------------------------- + The attribute of the view callable to use if ``__call__`` is not + correct (has the same meaning as in the context of + :ref:`the_view_zcml_directive`; see the description of ``attr`` + there). -When :mod:`repoze.bfg` can't authorize execution of a view based on -the authorization policy in use, it invokes a "forbidden view". The -default forbidden response has a 401 status code and is very plain, -but it can be overridden as necessary by placing something like the -following ZCML in your ``configure.zcml`` file. + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. -.. code-block:: xml - :linenos: + renderer + + This is either a single string term (e.g. ``json``) or a string + implying a path or :term:`resource specification` + (e.g. ``templates/views.pt``) used when the view returns a + non-:term:`response` object. This attribute has the same meaning as + it would in the context of :ref:`the_view_zcml_directive`; see the + description of ``renderer`` there). + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + + wrapper + + The :term:`view name` (*not* an object dotted name) of another view + declared elsewhere in ZCML (or via the ``@bfg_view`` decorator) + which will receive the response body of this view as the + ``request.wrapped_body`` attribute of its own request, and the + response returned by this view as the ``request.wrapped_response`` + attribute of its own request. This attribute has the same meaning + as it would in the context of :ref:`the_view_zcml_directive`; see + the description of ``wrapper`` there). Note that the wrapper view + *should not* be protected by any permission; behavior is undefined + if it does. + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + +.. topic:: Using Imperative Configuration + + If your application uses :term:`imperative configuration`, you can + replace the Forbidden view by using the ``set_forbidden_view`` + method of the ``Configurator`` named ``config``: + + .. code-block:: python + :linenos: - + import helloworld.views + config.set_forbiddden_view(helloworld.views.forbidden_view) -Replace ``helloworld.views.forbidden_view`` with the Python -dotted name to the forbidden view you want to use. Like any other -view, the forbidden view must accept two parameters: ``context`` and -``request`` . The ``context`` is the context found by the router when -the view invocation was denied. The ``request`` is the current -:term:`request` representing the denied action. Here's some sample -code that implements a minimal forbidden view: + Replace ``helloworld.views.forbidden_view`` with a reference to the + Python :term:`view callable` you want to use to represent the + Forbidden view. + + See :ref:`configuration_module` for more information about other + arguments to the ``set_forbidden_view`` method. + +Like any other view, the forbidden view must accept at least a +``request`` parameter, or both ``context`` and ``request``. The +``context`` (available as ``request.context`` if you're using the +request-only view argument pattern) is the context found by the router +when the view invocation was denied. The ``request`` is the current +:term:`request` representing the denied action. + +Here's some sample code that implements a minimal forbidden view: .. code-block:: python :linenos: @@ -126,45 +219,6 @@ code that implements a minimal forbidden view: an alternate forbidden view. For example, it would make sense to return a response with a ``403 Forbidden`` status code. -Other available attributes of the ``forbidden`` ZCML directive are as -follows: - -attr - - The attribute of the view callable to use if ``__call__`` is not - correct (has the same meaning as in the context of - :ref:`the_view_zcml_directive`; see the description of ``attr`` - there). - - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. - -renderer - - This is either a single string term (e.g. ``json``) or a string - implying a path or :term:`resource specification` - (e.g. ``templates/views.pt``) used when the view returns a - non-:term:`response` object. This attribute has the same meaning as - it would in the context of :ref:`the_view_zcml_directive`; see the - description of ``renderer`` there). - - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. - -wrapper - - The :term:`view name` (*not* an object dotted name) of another view - declared elsewhere in ZCML (or via the ``@bfg_view`` decorator) - which will receive the response body of this view as the - ``request.wrapped_body`` attribute of its own request, and the - response returned by this view as the ``request.wrapped_response`` - attribute of its own request. This attribute has the same meaning - as it would in the context of :ref:`the_view_zcml_directive`; see - the description of ``wrapper`` there). Note that the wrapper view - *should not* be protected by any permission; behavior is undefined - if it does. - - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. - - Changing the response factory ----------------------------- diff --git a/docs/narr/scanning.rst b/docs/narr/scanning.rst new file mode 100644 index 000000000..084d550d6 --- /dev/null +++ b/docs/narr/scanning.rst @@ -0,0 +1,188 @@ +.. _scanning_chapter: + +Configuration, Decorations And Code Scanning +============================================ + +:mod:`repoze.bfg` provides a number of "modes" for performing +application configuration. These modes can be used interchangeably or +even combined, as necessary. + +For example: + +- A ```` :term:`ZCML declaration` adds a :term:`view + configuration` to the current :term:`application registry`. + +- A call to the ``add_view`` method of a :term:`Configurator` ZCML + adds a :term:`view configuration` to the current :term:`application + registry`. + +- the ``@bfg_view`` :term:`decorator` adds :term:`configuration + decoration` to the function or method it decorates. This particular + decoration can result in a :term:`view configuration` to be added to + the current :term:`application registry` if the package the code + lives in is run through a :term:`scan`. + +Decorations and Code Scanning +----------------------------- + +To lend more *locality of reference* to a :term:`configuration +declaration`, :mod:`repoze.bfg` allows you to insert +:term:`configuration decoration` statements very close to code that is +referred to by the declaration itself. + +The mere existence of configuration decoration doesn't cause any +configuration registration to be made. Before they have any effect on +the configuration of a :mod:`repoze.bfg` application, a configuration +decoration within application code must be found through a process +known as *scanning*. + +:mod:`repoze.bfg` is willing to :term:`scan` a module or a package and +its subpackages for decorations when the ``scan`` method of a +:term:`Configurator` is invoked: scanning implies searching for +configuration declarations in a package and its subpackages. +:term:`ZCML` can also invoke a :term:`scan` via its ```` +directive. + +The scanning machinery imports each module and subpackage in a package +or module recursively, looking for special attributes attached to +objects defined within a module. These special attributes are +typically attached to code via the use of a :term:`decorator`. For +example, the :class:`repoze.bfg.view.bfg_view` decorator can be +attached to a function or instance method: + +.. code-block:: python + :linenos: + + from repoze.bfg.view import bfg_view + from webob import Response + + @bfg_view(name='hello', request_method='GET') + def hello(request): + return Response('Hello') + +The ``@bfg_view`` decorator above simply adds an attribute to the +``hello`` function, making it available for a :term:`scan` to find it +later. + +Once scanning is invoked, and :term:`configuration decoration` is +found by the scanner, a set of calls are made to a :term:`Configurator` +on behalf of the developer: these calls represent the intent of the +configuration decoration. In the example above, this is best +represented as the scanner translating the arguments to ``@bfg_view`` +into a call to the ``add_view`` method of a :term:`Configurator`, +effectively: + +.. code-block:: python + :linenos: + + config.add_view(hello, name='hello', request_method='GET') + +Scanning for :term:`configuration decoration` is performed via the +``scan`` method of a :term:`Configurator` or via a ```` +:term:`ZCML declaration`. See :ref:`config_mode_equivalence` for +examples. + +.. _config_mode_equivalence: + +Configuration Mode Equivalence +------------------------------ + +A combination of imperative configuration, declarative configuration +via ZCML and scanning can be used to configure any application. Each +of the below examples produces the same application configuration. + +.. topic:: Completely Imperative Configuration + + .. code-block:: python + :linenos: + + # helloworld.py + + from repoze.bfg.view import bfg_view + from webob import Response + + def hello(request): + return Response('Hello') + + if __name__ == '__main__': + from repoze.bfg.configuration import Configurator + config = Configurator() + config.add_view(hello, name='hello', request_method='GET') + +.. topic:: Configuration via ZCML + + .. code-block:: python + :linenos: + + # helloworld.py + + from webob import Response + + def hello(request): + return Response('Hello') + + if __name__ == '__main__': + from repoze.bfg.configuration import Configurator + config = Configurator(zcml_file='configure.zcml') + + .. code-block:: xml + :linenos: + + + + + + + + + + + +.. topic:: Using Decorations (Imperatively Starting a Scan) + + .. code-block:: python + :linenos: + + from repoze.bfg.view import bfg_view + from webob import Response + + @bfg_view(name='hello', request_method='GET') + def hello(request): + return Response('Hello') + + if __name__ == '__main__': + from repoze.bfg.configuration import Configurator + config = Configurator() + config.scan() + +.. topic:: Using Decorations (Starting a Scan via ZCML) + + .. code-block:: python + :linenos: + + # helloworld.py + + from repoze.bfg.view import bfg_view + from webob import Response + + @bfg_view(name='hello', request_method='GET') + def hello(request): + return Response('Hello') + + if __name__ == '__main__': + from repoze.bfg.configuration import Configurator + config = Configurator(zcml_file='configure.zcml') + + .. code-block:: xml + :linenos: + + + + + + + + + + diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 2e574cb14..566ef765a 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -574,10 +574,34 @@ acts as a :mod:`repoze.bfg` view. All ZCML attributes (save for the the same thing. To make :mod:`repoze.bfg` process your ``@bfg_view`` declarations, you -*must* insert the following boilerplate into your application's -``configure.zcml``:: +*must* do one of the following: - +- If you are using :term:`ZCML`, insert the following boilerplate into + your application's ``configure.zcml``: + + .. code-block:: xml + :linenos: + + + +- If you are using :term:`imperative configuration`, use the ``scan`` + method of the ``Configurator`` object: + + .. code-block:: python + :linenos: + + config.scan() + +.. note:: See :ref:`configuration_module` for additional API arguments + to the ``Configurator.scan`` method. For example, the ``scan`` + method allows you to supply a ``package`` argument to better + control exactly *which* code will be scanned. This is the same + value implied by the ``package`` attribute of the ZCML ```` + directive. + +Please see :ref:`scanning_chapter` for more information about what +happens when code is scanned for configuration declarations resulting +from use of decorators like ``@bfg_view``. After you do so, you will not need to use any other ZCML to configure :mod:`repoze.bfg` view declarations. Instead, you will be able to use diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py index a3ce9b1e0..13f730d97 100644 --- a/repoze/bfg/configuration.py +++ b/repoze/bfg/configuration.py @@ -262,6 +262,22 @@ class Configurator(object): # API + def add_subscriber(self, subscriber, iface=None): + """ Add an event subscriber for the event stream implied by + the supplied ``iface`` interface. The ``subscriber`` argument + represents a callable object; it will be called with a single + object ``event`` whenever :mod:`repoze.bfg` emits an event + associated with the ``iface``. Using the default ``iface`` + value, ``None`` will cause the subscriber to be registered for + all event types. See :ref:`events_chapter` for more + information about events and subscribers.""" + if iface is None: + iface = (Interface,) + if not isinstance(iface, (tuple, list)): + iface = (iface,) + self.registry.registerHandler(subscriber, iface) + return subscriber + def make_wsgi_app(self, manager=manager, getSiteManager=getSiteManager): """ Returns a :mod:`repoze.bfg` WSGI application representing the current configuration state.""" @@ -471,11 +487,19 @@ class Configurator(object): self.registry.registerUtility(mapper, IRoutesMapper) mapper.connect(path, name, factory, predicates=predicates) - def scan(self, package, _info=u'', martian=martian): + def scan(self, package=None, _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.""" + objects marked with configuration decorators such as + ``@bfg_view``. Any decorated object found will influence the + current configuration state. See + + The ``package`` argument should be a reference to a Python + package or module object. If ``package`` is ``None``, the + package of the *caller* is used. + """ # martian overrideable only for unit tests + if package is None: + package = caller_package() multi_grokker = BFGMultiGrokker() multi_grokker.register(BFGViewGrokker()) module_grokker = martian.ModuleGrokker(grokker=multi_grokker) @@ -739,8 +763,7 @@ def rendered_response(renderer, response, view, context, request, reg = getattr(request, 'registry', None) if reg is not None: # be kind to old unit tests - response_factory = reg.queryUtility(IResponseFactory, - default=Response) + response_factory = reg.queryUtility(IResponseFactory, default=Response) response = response_factory(result) if request is not None: # in tests, it may be None attrs = request.__dict__ diff --git a/repoze/bfg/includes/configure.zcml b/repoze/bfg/includes/configure.zcml index db369dd2d..ffabca9a3 100644 --- a/repoze/bfg/includes/configure.zcml +++ b/repoze/bfg/includes/configure.zcml @@ -6,24 +6,4 @@ - - - - - - - - diff --git a/repoze/bfg/log.py b/repoze/bfg/log.py index a9957e550..b8762e6e2 100644 --- a/repoze/bfg/log.py +++ b/repoze/bfg/log.py @@ -1,7 +1,7 @@ import logging -def make_stream_logger(name, stream, levelname='DEBUG', - fmt='%(asctime)s %(message)s'): +def make_stream_logger( + name, stream, levelname='DEBUG', fmt='%(asctime)s %(message)s'): """ Return an object which implements ``repoze.bfg.interfaces.IDebugLogger`` (ie. a Python PEP 282 logger instance) with the name ``name`` using the stream (or open diff --git a/repoze/bfg/paster.py b/repoze/bfg/paster.py index e57d17c1b..d5b03cf38 100644 --- a/repoze/bfg/paster.py +++ b/repoze/bfg/paster.py @@ -10,9 +10,9 @@ from paste.util.template import paste_script_template_renderer from repoze.bfg.scripting import get_root try: - from IPython.Shell import IPShell + from IPython.Shell import IPShell # pragma: no cover except ImportError: - IPShell = None + IPShell = None # pragma: no cover class StarterProjectTemplate(Template): diff --git a/repoze/bfg/renderers.py b/repoze/bfg/renderers.py index 2a42ef901..be04e6578 100644 --- a/repoze/bfg/renderers.py +++ b/repoze/bfg/renderers.py @@ -4,8 +4,8 @@ import pkg_resources from repoze.bfg.interfaces import ITemplateRenderer from repoze.bfg.compat import json -from repoze.bfg.settings import get_settings from repoze.bfg.path import caller_package +from repoze.bfg.settings import get_settings from repoze.bfg.threadlocal import get_current_registry # concrete renderer factory implementations diff --git a/repoze/bfg/request.py b/repoze/bfg/request.py index 131c786ca..a7f4f32ba 100644 --- a/repoze/bfg/request.py +++ b/repoze/bfg/request.py @@ -87,6 +87,8 @@ def add_global_response_headers(request, headerlist): from repoze.bfg.threadlocal import get_current_request as get_request # b/c +get_request # prevent PyFlakes complaints + deprecated('get_request', 'As of repoze.bfg 1.0, any import of get_request from' '``repoze.bfg.request`` is ' diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index 872b5a134..a68ce3653 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -26,7 +26,7 @@ from repoze.bfg.traversal import ModelGraphTraverser from repoze.bfg.view import default_forbidden_view from repoze.bfg.view import default_notfound_view -make_app = make_app # prevent pyflakes from complaining +make_app # prevent pyflakes from complaining class Router(object): """ The main repoze.bfg WSGI application. """ diff --git a/repoze/bfg/security.py b/repoze/bfg/security.py index a7a5d5f35..0366107c7 100644 --- a/repoze/bfg/security.py +++ b/repoze/bfg/security.py @@ -6,11 +6,11 @@ from repoze.bfg.interfaces import IAuthenticationPolicy from repoze.bfg.interfaces import IAuthorizationPolicy from repoze.bfg.interfaces import ISecuredView -# b/c import -from repoze.bfg.exceptions import Forbidden as Unauthorized - +from repoze.bfg.exceptions import Forbidden as Unauthorized # b/c import from repoze.bfg.threadlocal import get_current_registry +Unauthorized # prevent PyFlakes from complaining + deprecated('Unauthorized', "('from repoze.bfg.security import Unauthorized' was " "deprecated as of repoze.bfg 1.1; instead use 'from " diff --git a/repoze/bfg/testing.py b/repoze/bfg/testing.py index f12f5b110..4a4acf98b 100644 --- a/repoze/bfg/testing.py +++ b/repoze/bfg/testing.py @@ -13,6 +13,8 @@ from repoze.bfg.threadlocal import manager from repoze.bfg.threadlocal import get_current_registry from repoze.bfg.zcml import zcml_configure # API +zcml_configure # prevent pyflakes from complaining + _marker = object() def registerDummySecurityPolicy(userid=None, groupids=(), permissive=True): diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py index c16d738f5..549663f2f 100644 --- a/repoze/bfg/tests/test_configuration.py +++ b/repoze/bfg/tests/test_configuration.py @@ -109,8 +109,6 @@ class ConfiguratorTests(unittest.TestCase): self.failUnless(registry.queryUtility(IFixture)) # only in c.zcml def test_ctor_noreg_zcml_file_routes_in_config(self): - from repoze.bfg.interfaces import ISettings - from repoze.bfg.interfaces import IRootFactory from repoze.bfg.interfaces import IRoutesMapper config = self._makeOne( zcml_file='repoze.bfg.tests.routesapp:configure.zcml') @@ -158,6 +156,64 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(config.registry.getUtility(IRendererFactory, 'yeah'), renderer) + def test_add_subscriber_defaults(self): + from zope.interface import implements + from zope.interface import Interface + class IEvent(Interface): + pass + class Event: + implements(IEvent) + L = [] + def subscriber(event): + L.append(event) + config = self._makeOne() + config.add_subscriber(subscriber) + event = Event() + config.registry.notify(event) + self.assertEqual(len(L), 1) + self.assertEqual(L[0], event) + config.registry.notify(object()) + self.assertEqual(len(L), 2) + + def test_add_subscriber_iface_specified(self): + from zope.interface import implements + from zope.interface import Interface + class IEvent(Interface): + pass + class Event: + implements(IEvent) + L = [] + def subscriber(event): + L.append(event) + config = self._makeOne() + config.add_subscriber(subscriber, IEvent) + event = Event() + config.registry.notify(event) + self.assertEqual(len(L), 1) + self.assertEqual(L[0], event) + config.registry.notify(object()) + self.assertEqual(len(L), 1) + + def test_add_object_event_subscriber(self): + from zope.interface import implements + from zope.interface import Interface + class IEvent(Interface): + pass + class Event: + object = 'foo' + implements(IEvent) + event = Event() + L = [] + def subscriber(object, event): + L.append(event) + config = self._makeOne() + config.add_subscriber(subscriber, (Interface, IEvent)) + config.registry.subscribers((event.object, event), None) + self.assertEqual(len(L), 1) + self.assertEqual(L[0], event) + config.registry.subscribers((event.object, IDummy), None) + self.assertEqual(len(L), 1) + def test_make_wsgi_app(self): from repoze.bfg.threadlocal import get_current_registry from repoze.bfg.router import Router @@ -220,7 +276,6 @@ class ConfiguratorTests(unittest.TestCase): registry = config.load_zcml( 'repoze.bfg.tests.fixtureapp:configure.zcml', lock=dummylock) - from repoze.bfg.tests.fixtureapp.models import IFixture self.assertEqual(dummylock.acquired, True) self.assertEqual(dummylock.released, True) diff --git a/repoze/bfg/url.py b/repoze/bfg/url.py index 51e1b109e..d94cd429c 100644 --- a/repoze/bfg/url.py +++ b/repoze/bfg/url.py @@ -267,7 +267,7 @@ def static_url(path, request, **kw): # if it's not a package:relative/name and it's not an # /absolute/path it's a relative/path; this means its relative # to the package in which the caller's module is defined. - package = caller_package(level=2) + package = caller_package() path = '%s:%s' % (package.__name__, path) try: diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py index c4542dc79..cfdf61522 100644 --- a/repoze/bfg/view.py +++ b/repoze/bfg/view.py @@ -32,6 +32,8 @@ from repoze.bfg.static import PackageURLParser # b/c imports from repoze.bfg.security import view_execution_permitted +view_execution_permitted # prevent PyFlakes from complaining + deprecated('view_execution_permitted', "('from repoze.bfg.view import view_execution_permitted' was " "deprecated as of repoze.bfg 1.0; instead use 'from " -- cgit v1.2.3