summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-06-11 03:15:15 +0000
committerChris McDonough <chrism@agendaless.com>2009-06-11 03:15:15 +0000
commitdfc2b65c1b6d2f938f68b7868a14d8f9a4faab9e (patch)
treef3241401b7175a401e00286b11e3efe3c21f5093
parentf8b0065b6ede54424d7a7b49f9f113e87634b5ab (diff)
downloadpyramid-dfc2b65c1b6d2f938f68b7868a14d8f9a4faab9e.tar.gz
pyramid-dfc2b65c1b6d2f938f68b7868a14d8f9a4faab9e.tar.bz2
pyramid-dfc2b65c1b6d2f938f68b7868a14d8f9a4faab9e.zip
Merge unifyroutesandtraversal branch into trunk
-rw-r--r--CHANGES.txt93
-rw-r--r--docs/narr/hooks.rst73
-rw-r--r--docs/narr/project.rst4
-rw-r--r--docs/narr/traversal.rst21
-rw-r--r--docs/narr/urldispatch.rst90
-rw-r--r--docs/narr/urlmapping.rst7
-rw-r--r--docs/tutorials/bfgwiki2/authorization.rst80
-rw-r--r--docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml3
-rw-r--r--docs/tutorials/bfgwiki2/src/authorization/tutorial/models.py8
-rw-r--r--docs/tutorials/bfgwiki2/src/authorization/tutorial/run.py3
-rw-r--r--docs/tutorials/bfgwiki2/src/authorization/tutorial/utilities.py10
-rw-r--r--repoze/bfg/includes/configure.zcml14
-rw-r--r--repoze/bfg/interfaces.py25
-rw-r--r--repoze/bfg/request.py144
-rw-r--r--repoze/bfg/router.py86
-rw-r--r--repoze/bfg/tests/test_authentication.py27
-rw-r--r--repoze/bfg/tests/test_integration.py24
-rw-r--r--repoze/bfg/tests/test_request.py170
-rw-r--r--repoze/bfg/tests/test_router.py125
-rw-r--r--repoze/bfg/tests/test_traversal.py82
-rw-r--r--repoze/bfg/tests/test_urldispatch.py269
-rw-r--r--repoze/bfg/tests/test_zcml.py196
-rw-r--r--repoze/bfg/traversal.py46
-rw-r--r--repoze/bfg/urldispatch.py136
-rw-r--r--repoze/bfg/view.py12
-rw-r--r--repoze/bfg/zcml.py93
26 files changed, 848 insertions, 993 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 7d993d13b..f23908ebe 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,12 +1,105 @@
Next release
============
+Features
+--------
+
+- The concepts of traversal and URL dispatch have been unified. It is
+ now possible to use the same sort of factory as both a traversal
+ "root factory" and what used to be referred to as a urldispatch
+ "context factory".
+
+- When the root factory argument (as a first argument) passed to
+ ``repoze.bfg.router.make_app`` is ``None``, a *default* root factory
+ is used. This is in support of using routes as "root finders"; it
+ supplants the idea that there is a default
+ ``IRoutesContextFactory``.
+
+- The `view`` ZCML statement and the ``repoze.bfg.view.bfg_view``
+ decorator now accept an extra argument: ``route_name``. If a
+ ``route_name`` is specified, it must match the name of a previously
+ defined ``route`` statement. When it is specified, the view will
+ only be called when that route matches during a request.
+
+- It is now possible to perfom traversal *after* a route has matched.
+ Use the pattern ``*traverse`` in a ``<route>`` ``path`` attribute
+ within ZCML, and the path remainder which it matches will be used as
+ a traversal path.
+
+- When any route defined matches, the WSGI environment will now
+ contain a key ``bfg.routes.route`` (the Route object which matched),
+ and a key ``bfg.routes.matchdict`` (the result of calling route.match).
+
+Removals
+--------
+
+- The ``IRoutesContext``, ``IRoutesContextFactory``, and
+ ``IContextNotFound`` interfaces were removed from
+ ``repoze.bfg.interfaces``. These were never APIs.
+
+- The ``repoze.bfg.urldispatch.RoutesContextNotFound``,
+ ``repoze.bfg.urldispatch.RoutesModelTraverser`` and
+ ``repoze.bfg.urldispatch.RoutesContextURL`` classes were removed.
+ These were also never APIs.
+
+Backwards Incompatibilities
+---------------------------
+
+- Changing the default request factory via an IRequestFactory utility
+ registration (as used to be documented in the "Hooks" chapter's
+ "Changing the request factory" section) is no longer supported. The
+ dance to manufacture a request is complicated as a result of
+ unifying traversal and url dispatch, making it highly unlikely for
+ anyone to be able to override it properly. For those who just want
+ to decorate or modify a request, use a NewRequestEvent subscriber
+ (see the Events chapter in the documentation).
+
+- The ``repoze.bfg.IRequestFactory`` interface was removed. See the
+ bullet above for why.
+
+- View code that used urldispatch which expected the default routes
+ context factory to stash the routes matchdict arguments on the
+ context will not work properly without passing a default "root
+ factory" to ``repoze.bfg.router.make_app`` that performs this magic.
+ The "magic" is retrieving the matchdict from the environ and
+ constructing a root object that has this set of key/value pairs as
+ instance __dict__ values.
+
+- Routes "context factories" (spelled as the factory argument to a
+ route statement in ZCML) must now expect the WSGI environ as a
+ single argument rather than a set of keyword arguments. They can
+ obtain the match dictionary by asking for
+ environ['bfg.routes.matchdict']. This is the same set of keywords
+ that used to be passed to context factories.
+
+- Using the ``@zope.component.adapter`` decorator on a bfg view
+ function no longer works. Use the ``@repoze.bfg.view.bfg_view``
+ decorator instead to mark a function (or a class) as a view.
+
+- The name under which the matching route object is found in the
+ environ was changed from ``bfg.route`` to ``bfg.routes.route``.
+
+- Finding the root is now done *before* manufacturing a request object
+ (and sending a new request event) within the router (it used to be
+ performed afterwards).
+
Bug Fixes
---------
- The ``bfg_alchemy`` Paster template named "repoze.tm" in its
pipeline rather than "repoze.tm2", causing the startup to fail.
+Documentation
+-------------
+
+- Replaced all mentions and explanations of a routes "context factory"
+ with equivalent explanations of a "root factory" (context factories
+ have been disused).
+
+- Updated Routes bfgwiki2 tutorial to reflect the fact that context
+ factories are now no longer used.
+
+
0.9.1 (2009-06-02)
==================
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 533024352..53d6c8c77 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -7,51 +7,6 @@ ZCML "hooks" can be used to influence the behavior of the
:mod:`repoze.bfg` framework in various ways. This is an advanced
topic; not many people will want or need to do any of these things.
-Changing the request factory
-----------------------------
-
-You may change the class used as the "request factory" from within the
-:mod:`repoze.bfg` ``Router`` class (the ``Router`` class turns the
-WSGI environment into a "request" object which is used ubiquitously
-throughout :mod:`repoze.bfg`). The default "request factory" is the
-class ``webob.Request``. You may change it by placing the following
-ZCML in your ``configure.zcml`` file.
-
-.. code-block:: xml
- :linenos:
-
- <utility provides="repoze.bfg.interfaces.IRequestFactory"
- component="helloworld.factories.request_factory"/>
-
-Replace ``helloworld.factories.request_factory`` with the Python
-dotted name to the request factory you want to use. Here's some
-sample code that implements a minimal request factory:
-
-.. code-block:: python
-
- from webob import Request
- from repoze.bfg.interfaces import IRequest
-
- class MyRequest(Request):
- implements(IRequest)
-
- def request_factory():
- return MyRequest
-
-.. warning:: If you register an ``IRequestFactory`` utility in this
- way, you *must* be sure that the factory returns an object that
- implements *at least* the ``repoze.bfg.interfaces.IRequest``
- interface. Otherwise all application view lookups will fail (they
- will all return a 404 response code). Likewise, if you want to be
- able to use method-related interfaces such as ``IGETRequest``,
- ``IPOSTRequest``, etc. in your view declarations, the callable
- returned by the factory must also do the same introspection of the
- environ that the default request factory does and decorate the
- returned object to implement one of these interfaces based on the
- ``HTTP_METHOD`` present in the environ. Note that the above
- example does not do this, so lookups for method-related interfaces
- will fail.
-
Changing the response factory
-----------------------------
@@ -164,31 +119,3 @@ code that implements a minimal forbidden view:
an alterate forbidden view. For example, it would make sense to
return a response with a ``403 Forbidden`` status code.
-.. _changing_routes_context_factory:
-
-Changing the Default Routes Context Factory
--------------------------------------------
-
-The default Routes "context factory" (the object used to create
-context objects when you use ``<route..>`` statements in your ZCML) is
-``repoze.bfg.urldispatch.DefaultRoutesContext``. You may change the
-class used as the Routes "context factory" by placing the following
-ZCML in your ``configure.zcml`` file.
-
-.. code-block:: xml
- :linenos:
-
- <utility provides="repoze.bfg.interfaces.IRoutesContextFactory"
- component="helloworld.factories.routes_context_factory"/>
-
-Replace ``helloworld.factories.routes_context_factory`` with the
-Python dotted name to the context factory you want to use. Here's
-some sample code that implements a minimal context factory:
-
-.. code-block:: python
- :linenos:
-
- class RoutesContextFactory(object):
- def __init__(self, **kw):
- self.__dict__.update(kw)
-
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 7fb3604de..3de146e8e 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -92,10 +92,10 @@ application's Python code and templates.
<http://www.sqlalchemy.org/>`_ also exist. Use ``paster create -t
bfg_zodb`` to create a project that depends on ZODB. Use ``paster
create -t bfg_routesalchemy`` to create a project that depends on
- SQLAlchemy and Routes (uses :term:`URL dispatch` instead of
+ SQLAlchemy and Routes (uses only :term:`URL dispatch` and no
:term:`traversal`). Use ``paster create -t bfg_alchemy`` to create
a project that depends on SQLAlchemy but *not* Routes (uses
- :term:`traversal` instead of :term:`URL dispatch`).
+ only :term:`traversal` and no :term:`URL dispatch`).
Installing your Newly Created Project for Development
-----------------------------------------------------
diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst
index 7177432de..4c032952b 100644
--- a/docs/narr/traversal.rst
+++ b/docs/narr/traversal.rst
@@ -29,15 +29,18 @@ root object is usually a *mapping* object (such as a Python
dictionary).
.. note:: If a :term:`root factory` is passed to the :mod:`repoze.bfg`
- "make_app" function as the value ``None``, no traversal is
- performed. Instead, it's assumed that all URLs will be mapped to
- code via :term:`URL dispatch`. No root factory, no traversal. It
- is also possible to mix-and-match traversal with URL dispatch.
- When both a root factory (and therefore traversal) *and* "routes"
- declarations (and therefore url dispatch) are used, the url
- dispatch routes are checked first, and if none match,
- :mod:`repoze.bfg` will fall back to using traversal to attempt to
- map the request to a view.
+ "make_app" function as the value ``None``, a default root factory
+ is used. This is most useful when you're using :term:`URL
+ dispatch` and you don't care very much about traversing any
+ particular graph to resolve URLs to code. It is also possible to
+ use traversal and URL dispatch together. When both a root factory
+ (and therefore traversal) *and* "routes" declarations (and
+ therefore url dispatch) are used, the url dispatch routes are
+ checked first, and if none match, :mod:`repoze.bfg` will fall back
+ to using traversal to attempt to map the request to a view. If the
+ name ``*traverse`` is in a route's ``path`` pattern, when it is
+ matched, it is also possible to do traversal *after* a route has
+ been matched. See :ref:`urldispatch_chapter` for more information.
Items contained within the object graph are analogous to the concept
of :term:`model` objects used by many other frameworks (and
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index c7d1e2a38..91063fc26 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -16,8 +16,8 @@ which allows you to declaratively map URLs to code.
neither concept (controller nor action) exists within
:mod:`repoze.bfg`. Instead, when you map a URL pattern to
code in bfg, you will map the URL patterm to a :term:`view`.
- Once the context and view name are found, the view will be
- called with a :term:`context` and a :term:`request`.
+ Once the context and view are found, the view will be called
+ with a :term:`context` and a :term:`request`.
It often makes a lot of sense to use :term:`URL dispatch` instead of
:term:`traversal` in an application that has no natural hierarchy.
@@ -54,17 +54,15 @@ acts as a root factory, it is willing to check the requested URL
against a *routes map* to find a :term:`context` and a :term:`view`
before traversal has a chance to find it first. If a route matches, a
:term:`context` is generated and :mod:`repoze.bfg` will call the
-:term:`view` specified with the context and the request.
-
-If no route matches, :mod:`repoze.bfg` will fail over to calling the
-root factory callable passed to the application in it's ``make_app``
-function (usually a traversal function). By configuring your ZCML
-``route`` statements appropriately, you can mix and match URL dispatch
-and traversal in this way.
+:term:`view` specified with the context and the request. If no route
+matches, :mod:`repoze.bfg` will fail over to calling the :term:`root
+factory` callable passed to the application in it's ``make_app``
+function (usually a traversal function).
A root factory is not required for purely URL-dispatch-based apps: if
-the root factory callable is ``None``, :mod:`repoze.bfg` will return a
-NotFound error to the user's browser when no routes match.
+the root factory callable is passed as ``None`` to the ``make_app``
+function, :mod:`repoze.bfg` will return a NotFound error to the user's
+browser when no routes match.
.. note:: See :ref:`modelspy_project_section` for an example of a
simple root factory callable that will use traversal.
@@ -105,9 +103,8 @@ factory
The Python dotted-path name to a function that will generate a
:mod:`repoze.bfg` context object when this route matches.
- e.g. ``mypackage.models.MyFactoryClass``. By default, a
- ``repoze.bfg.urldispatch.DefaultRoutesContext`` object will be
- constructed if a factory is not provided.
+ e.g. ``mypackage.models.MyFactoryClass``. If this argument is not
+ specified, a default root factory will be used.
encoding
@@ -267,12 +264,12 @@ Example 3
---------
The context object passed to a view found as the result of URL
-dispatch will by default be an instance of the
-``repoze.bfg.urldispatch.DefaultRoutesContext`` object. You can
-override this behavior by passing in a ``factory`` argument to the
-ZCML directive for a particular route. The ``factory`` should be a
-callable that accepts arbitrary keyword arguments and returns an
-instance of a class that will be the context used by the view.
+dispatch will by default be an instance of the object returned by the
+default :term:`root factory`. You can override this behavior by
+passing in a ``factory`` argument to the ZCML directive for a
+particular route. The ``factory`` should be a callable that accepts a
+WSGI environment and returns an instance of a class that will be the
+context used by the view.
An example of using a route with a factory:
@@ -288,13 +285,13 @@ An example of using a route with a factory:
The above route will manufacture an ``Idea`` model as a context,
assuming that ``mypackage.models.Idea`` resolves to a class that
-accepts arbitrary key/value pair arguments.
+accepts a WSGI environment in its ``__init__``.
.. note:: Values prefixed with a period (``.``) for the ``factory``
- and ``provides`` attributes of a ``route`` (such as
- ``.models.Idea`` and ``.views.idea_view``) above) mean "relative to
- the Python package directory in which this :term:`ZCML` file is
- stored". So if the above ``route`` declaration was made inside a
+ and ``view`` attributes of a ``route`` (such as ``.models.Idea``
+ and ``.views.idea_view``) above) mean "relative to the Python
+ package directory in which this :term:`ZCML` file is stored". So
+ if the above ``route`` declaration was made inside a
``configure.zcml`` file that lived in the ``hello`` package, you
could replace the relative ``.models.Idea`` with the absolute
``hello.models.Idea`` Either the relative or absolute form is
@@ -302,14 +299,10 @@ accepts arbitrary key/value pair arguments.
form, in case your package's name changes. It's also shorter to
type.
-All context objects manufactured via URL dispatch will be decorated by
-default with the ``repoze.bfg.interfaces.IRoutesContext``
-:term:`interface`.
-
If no route matches in the above configuration, :mod:`repoze.bfg` will
-call the "fallback" ``get_root`` callable provided to it during
-``make_app`. If the "fallback" ``get_root`` is None, a ``NotFound``
-error will be raised when no route matches.
+call the "fallback" :term:`root factory` callable provided to it
+during ``make_app`. If the "fallback" root factory is None, a
+``NotFound`` error will be raised when no route matches.
.. note:: See :ref:`using_model_interfaces` for more information about
how views are found when interfaces are attached to a
@@ -338,7 +331,10 @@ The ``.models`` module referred to above might look like so:
.. code-block:: python
:linenos:
- class Article(dict):
+ class Article(object):
+ def __init__(self, environ):
+ self.__dict__.update(environ['repoze.bfg.matchdict'])
+
def is_root(self):
return self['article'] == 'root'
@@ -393,8 +389,8 @@ request when a database connection is involved. When
of the traversal :term:`root factory`. Often the root factory will
insert an object into the WSGI environment that performs some cleanup
when its ``__del__`` method is called. When URL dispatch is used,
-however, no root factory is required, so sometimes that option is not
-open to you.
+however, no special root factory is required, so sometimes that option
+is not open to you.
Instead of putting this cleanup logic in the root factory, however,
you can cause a subscriber to be fired when a new request is detected;
@@ -439,32 +435,28 @@ Lists, see :ref:`security_chapter` for more information about the
:mod:`repoze.bfg` authorization subsystem). A common thing to want to
do is to attach an ``__acl__`` to the context object dynamically for
declarative security purposes. You can use the ``factory`` argument
-that points at a context factory which attaches a custom ``__acl__``
-to an object at its creation time.
+that points at a factory which attaches a custom ``__acl__`` to an
+object at its creation time.
Such a ``factory`` might look like so:
.. code-block:: python
:linenos:
- class Article(dict):
- pass
-
- def article_factory(**kw):
- model = Article(**kw)
- article = kw.get('article', None)
- if article == '1':
- model.__acl__ = [ (Allow, 'editor', 'view') ]
- return model
+ class Article(object):
+ def __init__(self, environ):
+ matchdict = environ['bfg.routes.matchdict']
+ article = matchdict.get('article', None)
+ if article == '1':
+ self.__acl__ = [ (Allow, 'editor', 'view') ]
If the route ``archives/:article`` is matched, and the article number
is ``1``, :mod:`repoze.bfg` will generate an ``Article``
:term:`context` with an ACL on it that allows the ``editor`` principal
the ``view`` permission. Obviously you can do more generic things
that inspect the routes match dict to see if the ``article`` argument
-matches a particular string; our sample ``article_factory`` function
-is not very ambitious. Its job could have just as well been done in
-the ``Article`` class' constructor, too.
+matches a particular string; our sample ``Article`` factory class is
+not very ambitious.
.. note:: See :ref:`security_chapter` for more information about
:mod:`repoze.bfg` security and ACLs.
diff --git a/docs/narr/urlmapping.rst b/docs/narr/urlmapping.rst
index 3c74d04d9..21f6235f8 100644
--- a/docs/narr/urlmapping.rst
+++ b/docs/narr/urlmapping.rst
@@ -90,10 +90,3 @@ URL-based dispatch.
:mod:`repoze.bfg` provides support for both approaches. You can use
either as you see fit.
-.. note::
-
- Most existng :mod:`repoze.bfg` applications use :term:`traversal` to
- map URLs to code. This is mostly due to the :term:`Zope` heritage
- of :mod:`repoze.bfg` and because it aids applications that require
- highly granular declarative security assertions.
-
diff --git a/docs/tutorials/bfgwiki2/authorization.rst b/docs/tutorials/bfgwiki2/authorization.rst
index 53d4cfb63..402e42f8d 100644
--- a/docs/tutorials/bfgwiki2/authorization.rst
+++ b/docs/tutorials/bfgwiki2/authorization.rst
@@ -11,54 +11,39 @@ allowing anyone with access to the server to view pages.
*authentication*. We'll make use of both features to provide security
to our application.
-Adding A Context Factory
-------------------------
+Adding A Root Factory
+---------------------
-We're going to start to use a custom *context factory* within our
-``configure.zcml`` file in order to be able to attach security
-declarations to our :term:`context` object. When we do this, we can
-begin to make use of the declarative security features of
-:mod:`repoze.bfg`.
+We're going to start to use a custom *root factory* within our
+``run.py`` file in order to be able to attach security declarations to
+our :term:`context` object. When we do this, we can begin to make use
+of the declarative security features of :mod:`repoze.bfg`.
-Let's modify our ``configure.zcml``, following the instructions in the
-BFG documentation section named
-:ref:`changing_routes_context_factory`. We'll point it at a function
-in a new module we create named ``utilities.py``.
+Let's modify our ``run.py``, passing in a :term:`root factory` as the
+first argument to ``repoze.bfg.router.make_app``. We'll point it at a
+new class we create inside our ``models.py`` file. Add the following
+statements to your ``models.py`` file:
-Add the following section to your application's
-``configure.zcml`` file:
-
-.. code-block:: xml
- :linenos:
-
- <utility provides="repoze.bfg.interfaces.IRoutesContextFactory"
- component=".utilities.RoutesContextFactory"/>
-
-As a result, our ``configure.zcml`` file will now look like so:
-
-.. literalinclude:: src/authorization/tutorial/configure.zcml
- :linenos:
- :language: xml
-
-Once ``configure.zcml`` has been modified, create a file named
-``utilities.py`` and give it the following contents:
-
-.. literalinclude:: src/authorization/tutorial/utilities.py
- :linenos:
- :language: python
+.. code-block:: python
-The result of our changing of the default routes context factory in
-``configure.zcml`` and our addition of a new ``RoutesContextFactory``
-class to ``utilities.py`` allows us to use declarative security
-features of :mod:`repoze.bfg`. The ``RoutesContextFactory`` class we
-added will be used to construct each of the ``context`` objects passed
-to our views. All of our ``context`` objects will possess an
-``__acl__`` attribute that allows "Everyone" (a special principal) to
-view all request, while allowing only a user named ``editor`` to edit
-and add pages. The ``__acl__`` attribute attached to a context is
-interpreted specially by :mod:`repoze.bfg` as an access control list
-during view execution. See :ref:`assigning_acls` for more information
-about what an :term:`ACL` represents.
+ from repoze.bfg.security import Allow
+ from repoze.bfg.security import Everyone
+
+ class RootFactory(object):
+ __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'editor', 'edit') ]
+ def __init__(self, environ):
+ self.__dict__.update(environ['bfg.routes.matchdict'])
+
+Defining a root factory allows us to use declarative security features
+of :mod:`repoze.bfg`. The ``RootFactory`` class we added will be used
+to construct each of the ``context`` objects passed to our views. All
+of our ``context`` objects will possess an ``__acl__`` attribute that
+allows "Everyone" (a special principal) to view all request, while
+allowing only a user named ``editor`` to edit and add pages. The
+``__acl__`` attribute attached to a context is interpreted specially
+by :mod:`repoze.bfg` as an access control list during view execution.
+See :ref:`assigning_acls` for more information about what an
+:term:`ACL` represents.
.. note: Although we don't use the functionality here, the ``factory``
used to create route contexts may differ per-route instead of
@@ -87,8 +72,11 @@ accepts a userid. If the userid exists in the system, the callback
should return a sequence of group identifiers (or an empty sequence if
the user isn't a member of any groups). If the userid *does not*
exist in the system, the callback should return ``None``. We'll use
-"dummy" data to represent user and groups sources. When we're done,
-your application's ``run.py`` will look like this.
+"dummy" data to represent user and groups sources.
+
+We'll also use the opportunity to pass our ``RootFactory`` in as the
+first argument to ``make_app``. When we're done, your application's
+``run.py`` will look like this.
.. literalinclude:: src/authorization/tutorial/run.py
:linenos:
diff --git a/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml b/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml
index 8fd6140ab..ff0125f83 100644
--- a/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml
+++ b/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml
@@ -49,9 +49,6 @@
permission="edit"
/>
- <utility provides="repoze.bfg.interfaces.IRoutesContextFactory"
- component=".utilities.RoutesContextFactory"/>
-
<utility provides="repoze.bfg.interfaces.IForbiddenView"
component=".login.login"/>
diff --git a/docs/tutorials/bfgwiki2/src/authorization/tutorial/models.py b/docs/tutorials/bfgwiki2/src/authorization/tutorial/models.py
index 3e63c3734..283ddea74 100644
--- a/docs/tutorials/bfgwiki2/src/authorization/tutorial/models.py
+++ b/docs/tutorials/bfgwiki2/src/authorization/tutorial/models.py
@@ -14,6 +14,9 @@ from sqlalchemy.ext.declarative import declarative_base
from zope.sqlalchemy import ZopeTransactionExtension
+from repoze.bfg.security import Allow
+from repoze.bfg.security import Everyone
+
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()
@@ -28,6 +31,11 @@ class Page(Base):
self.name = name
self.data = data
+class RootFactory(object):
+ __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'editor', 'edit') ]
+ def __init__(self, environ):
+ self.__dict__.update(environ['bfg.routes.matchdict'])
+
def initialize_sql(db, echo=False):
engine = create_engine(db, echo=echo)
DBSession.configure(bind=engine)
diff --git a/docs/tutorials/bfgwiki2/src/authorization/tutorial/run.py b/docs/tutorials/bfgwiki2/src/authorization/tutorial/run.py
index 0f2068bba..698ba96b9 100644
--- a/docs/tutorials/bfgwiki2/src/authorization/tutorial/run.py
+++ b/docs/tutorials/bfgwiki2/src/authorization/tutorial/run.py
@@ -4,6 +4,7 @@ from repoze.bfg.authentication import AuthTktAuthenticationPolicy
import tutorial
from tutorial.models import DBSession
from tutorial.models import initialize_sql
+from tutorial.models import RootFactory
class Cleanup:
def __init__(self, cleaner):
@@ -27,7 +28,7 @@ def app(global_config, **kw):
authpolicy = AuthTktAuthenticationPolicy('seekr!t', callback=groupfinder)
- return make_app(None, tutorial, authentication_policy=authpolicy,
+ return make_app(RootFactory, tutorial, authentication_policy=authpolicy,
options=kw)
USERS = {'editor':'editor',
diff --git a/docs/tutorials/bfgwiki2/src/authorization/tutorial/utilities.py b/docs/tutorials/bfgwiki2/src/authorization/tutorial/utilities.py
deleted file mode 100644
index cc1e0d515..000000000
--- a/docs/tutorials/bfgwiki2/src/authorization/tutorial/utilities.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from repoze.bfg.security import Allow
-from repoze.bfg.security import Everyone
-
-class RoutesContextFactory(object):
- __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'editor', 'edit') ]
- def __init__(self, **kw):
- self.__dict__.update(kw)
-
-
-
diff --git a/repoze/bfg/includes/configure.zcml b/repoze/bfg/includes/configure.zcml
index 048a0cbc8..7e2036476 100644
--- a/repoze/bfg/includes/configure.zcml
+++ b/repoze/bfg/includes/configure.zcml
@@ -16,20 +16,6 @@
for="* repoze.bfg.interfaces.IRequest"
/>
- <!-- URL dispatch adapters -->
-
- <adapter
- factory="repoze.bfg.urldispatch.RoutesModelTraverser"
- provides="repoze.bfg.interfaces.ITraverserFactory"
- for="repoze.bfg.interfaces.IRoutesContext"
- />
-
- <adapter
- factory="repoze.bfg.urldispatch.RoutesContextURL"
- provides="repoze.bfg.interfaces.IContextURL"
- for="repoze.bfg.interfaces.IRoutesContext repoze.bfg.interfaces.IRequest"
- />
-
<include file="meta.zcml" />
</configure>
diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py
index 79ef25f09..b8f62c05f 100644
--- a/repoze/bfg/interfaces.py
+++ b/repoze/bfg/interfaces.py
@@ -3,13 +3,6 @@ from zope.interface import Interface
from zope.component.interfaces import IObjectEvent
-class IRequestFactory(Interface):
- """ A utility which generates a request object """
- def __call__():
- """ Return a request factory (a callable that accepts an
- environ and returns an object implementing IRequest,
- e.g. ``webob.Request``)"""
-
class IRequest(Interface):
""" Request type interface attached to all request objects """
@@ -47,7 +40,11 @@ class IView(Interface):
class IRootFactory(Interface):
def __call__(environ):
- """ Return a root object """
+ """ Return a root object based on the WSGI environ """
+
+class IDefaultRootFactory(Interface):
+ def __call__(environ):
+ """ Return the *default* root object for an application """
class ITraverser(Interface):
def __call__(environ):
@@ -126,10 +123,6 @@ class IRouter(Interface):
registry = Attribute(
"""Component architecture registry local to this application.""")
-class IRoutesContext(Interface):
- """ A context (model instance) that is created as a result of URL
- dispatching"""
-
class INewRequest(Interface):
""" An event type that is emitted whenever repoze.bfg begins to
process a new request """
@@ -165,10 +158,6 @@ class ILogger(Interface):
class IRoutesMapper(Interface):
""" Interface representing a Routes ``Mapper`` object """
-class IContextNotFound(Interface):
- """ Interface implemented by contexts generated by code which
- cannot find a context during root finding or traversal """
-
class IForbiddenView(Interface):
""" A utility which returns an IResponse as the result of the
denial of a view invocation by a security policy."""
@@ -210,10 +199,6 @@ class IContextURL(Interface):
def __call__():
""" Return a URL that points to the context """
-class IRoutesContextFactory(Interface):
- """ A marker interface used to look up the default routes context factory
- """
-
class IAuthenticationPolicy(Interface):
""" An object representing a BFG authentication policy. """
def authenticated_userid(request):
diff --git a/repoze/bfg/request.py b/repoze/bfg/request.py
index 71f639626..e5482848b 100644
--- a/repoze/bfg/request.py
+++ b/repoze/bfg/request.py
@@ -1,7 +1,14 @@
from zope.interface import implements
from webob import Request as WebobRequest
-import repoze.bfg.interfaces
+from zope.interface.interface import InterfaceClass
+
+from repoze.bfg.interfaces import IRequest
+from repoze.bfg.interfaces import IGETRequest
+from repoze.bfg.interfaces import IPOSTRequest
+from repoze.bfg.interfaces import IPUTRequest
+from repoze.bfg.interfaces import IDELETERequest
+from repoze.bfg.interfaces import IHEADRequest
from repoze.bfg.threadlocal import manager
@@ -73,6 +80,25 @@ def current_request():
"""
return manager.get()['request']
+def request_factory(environ):
+ try:
+ method = environ['REQUEST_METHOD']
+ except KeyError:
+ method = None
+
+ if 'bfg.routes.route' in environ:
+ route = environ['bfg.routes.route']
+ request_factories = route.request_factories
+ else:
+ request_factories = DEFAULT_REQUEST_FACTORIES
+
+ try:
+ request_factory = request_factories[method]['factory']
+ except KeyError:
+ request_factory = request_factories[None]['factory']
+
+ return request_factory(environ)
+
def make_request_ascii(event):
""" An event handler that causes the request charset to be ASCII;
used as an INewRequest subscriber so code written before 0.7.0 can
@@ -80,53 +106,71 @@ def make_request_ascii(event):
request = event.request
request.charset = None
-class Request(WebobRequest):
- implements(repoze.bfg.interfaces.IRequest)
- charset = 'utf-8'
-
-# We use 'precooked' Request subclasses that correspond to HTTP
-# request methods within ``router.py`` when constructing a request
-# object rather than using ``alsoProvides`` to attach the proper
-# interface to an unsubclassed webob.Request. This pattern is purely
-# an optimization (e.g. preventing calls to ``alsoProvides`` means the
-# difference between 590 r/s and 690 r/s on a MacBook 2GHz). These
-# classes are *not* APIs. None of these classes, nor the
-# ``HTTP_METHOD_FACTORIES`` or ``HTTP_METHOD_INTERFACES`` lookup dicts
-# should be imported directly by user code.
-
-class GETRequest(WebobRequest):
- implements(repoze.bfg.interfaces.IGETRequest)
- charset = 'utf-8'
-
-class POSTRequest(WebobRequest):
- implements(repoze.bfg.interfaces.IPOSTRequest)
- charset = 'utf-8'
-
-class PUTRequest(WebobRequest):
- implements(repoze.bfg.interfaces.IPUTRequest)
- charset = 'utf-8'
-
-class DELETERequest(WebobRequest):
- implements(repoze.bfg.interfaces.IDELETERequest)
- charset = 'utf-8'
-
-class HEADRequest(WebobRequest):
- implements(repoze.bfg.interfaces.IHEADRequest)
- charset = 'utf-8'
-
-HTTP_METHOD_FACTORIES = {
- 'GET':GETRequest,
- 'POST':POSTRequest,
- 'PUT':PUTRequest,
- 'DELETE':DELETERequest,
- 'HEAD':HEADRequest,
- }
-
-HTTP_METHOD_INTERFACES = {
- 'GET':repoze.bfg.interfaces.IGETRequest,
- 'POST':repoze.bfg.interfaces.IPOSTRequest,
- 'PUT':repoze.bfg.interfaces.IPUTRequest,
- 'DELETE':repoze.bfg.interfaces.IDELETERequest,
- 'HEAD':repoze.bfg.interfaces.IHEADRequest,
- }
+def named_request_factories(name=None):
+ # We use 'precooked' Request subclasses that correspond to HTTP
+ # request methods when returning a request object from
+ # ``request_factory`` rather than using ``alsoProvides`` to attach
+ # the proper interface to an unsubclassed webob.Request. This
+ # pattern is purely an optimization (e.g. preventing calls to
+ # ``alsoProvides`` means the difference between 590 r/s and 690
+ # r/s on a MacBook 2GHz). This method should be never imported
+ # directly by user code; it is *not* an API.
+ if name is None:
+ default_iface = IRequest
+ get_iface = IGETRequest
+ post_iface = IPOSTRequest
+ put_iface = IPUTRequest
+ delete_iface = IDELETERequest
+ head_iface = IHEADRequest
+ else:
+ default_iface = InterfaceClass('%s_IRequest' % name)
+ get_iface = InterfaceClass('%s_IGETRequest' % name, (default_iface,))
+ post_iface = InterfaceClass('%s_IPOSTRequest' % name, (default_iface,))
+ put_iface = InterfaceClass('%s_IPUTRequest' % name, (default_iface,))
+ delete_iface = InterfaceClass('%s_IDELETERequest' % name,
+ (default_iface,))
+ head_iface = InterfaceClass('%s_IHEADRequest' % name, (default_iface))
+
+ class Request(WebobRequest):
+ implements(default_iface)
+ charset = 'utf-8'
+
+ class GETRequest(WebobRequest):
+ implements(get_iface)
+ charset = 'utf-8'
+
+ class POSTRequest(WebobRequest):
+ implements(post_iface)
+ charset = 'utf-8'
+ class PUTRequest(WebobRequest):
+ implements(put_iface)
+ charset = 'utf-8'
+
+ class DELETERequest(WebobRequest):
+ implements(delete_iface)
+ charset = 'utf-8'
+
+ class HEADRequest(WebobRequest):
+ implements(head_iface)
+ charset = 'utf-8'
+
+ factories = {
+ IRequest:{'interface':default_iface, 'factory':Request},
+ IGETRequest:{'interface':get_iface, 'factory':GETRequest},
+ IPOSTRequest:{'interface':post_iface, 'factory':POSTRequest},
+ IPUTRequest:{'interface':put_iface, 'factory':PUTRequest},
+ IDELETERequest:{'interface':delete_iface, 'factory':DELETERequest},
+ IHEADRequest:{'interface':head_iface, 'factory':HEADRequest},
+ None:{'interface':default_iface, 'factory':Request},
+ 'GET':{'interface':get_iface, 'factory':GETRequest},
+ 'POST':{'interface':post_iface, 'factory':POSTRequest},
+ 'PUT':{'interface':put_iface, 'factory':PUTRequest},
+ 'DELETE':{'interface':delete_iface, 'factory':DELETERequest},
+ 'HEAD':{'interface':head_iface, 'factory':HEADRequest},
+ }
+
+ return factories
+
+DEFAULT_REQUEST_FACTORIES = named_request_factories()
+
diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py
index 0c00b239c..d8a55fe7b 100644
--- a/repoze/bfg/router.py
+++ b/repoze/bfg/router.py
@@ -16,7 +16,6 @@ from repoze.bfg.events import WSGIApplicationCreatedEvent
from repoze.bfg.interfaces import ILogger
from repoze.bfg.interfaces import ISecurityPolicy
-from repoze.bfg.interfaces import IRequestFactory
from repoze.bfg.interfaces import IResponseFactory
from repoze.bfg.interfaces import IRootFactory
from repoze.bfg.interfaces import IRouter
@@ -30,16 +29,14 @@ from repoze.bfg.interfaces import IView
from repoze.bfg.interfaces import IViewPermission
from repoze.bfg.interfaces import IAuthorizationPolicy
from repoze.bfg.interfaces import IAuthenticationPolicy
-from repoze.bfg.interfaces import IRoutesContext
-from repoze.bfg.interfaces import IRoutesContextFactory
+from repoze.bfg.interfaces import IDefaultRootFactory
from repoze.bfg.log import make_stream_logger
from repoze.bfg.registry import Registry
from repoze.bfg.registry import populateRegistry
-from repoze.bfg.request import HTTP_METHOD_FACTORIES
-from repoze.bfg.request import Request
+from repoze.bfg.request import request_factory
from repoze.bfg.secpols import registerBBBAuthn
@@ -68,8 +65,6 @@ class Router(object):
self.registry = registry
self.logger = registry.queryUtility(ILogger, 'repoze.bfg.debug')
- self.request_factory = registry.queryUtility(IRequestFactory)
-
forbidden = None
unauthorized_app_factory = registry.queryUtility(
@@ -129,7 +124,8 @@ class Router(object):
self.secured = not not registry.queryUtility(IAuthenticationPolicy)
- self.root_factory = registry.getUtility(IRootFactory)
+ self.root_factory = registry.queryUtility(IRootFactory,
+ default=DefaultRootFactory)
self.root_policy = self.root_factory # b/w compat
self.traverser_warned = {}
@@ -145,33 +141,13 @@ class Router(object):
threadlocals = {'registry':registry, 'request':None}
self.threadlocal_manager.push(threadlocals)
-
- def respond(response, view_name):
- registry.has_listeners and registry.notify(NewResponse(response))
- try:
- start_response(response.status, response.headerlist)
- return response.app_iter
- except AttributeError:
- raise ValueError(
- 'Non-response object returned from view %s: %r' %
- (view_name, response))
-
+
try:
- if self.request_factory is None:
- method = environ['REQUEST_METHOD']
- try:
- # for speed we disuse HTTP_METHOD_FACTORIES.get
- request_factory = HTTP_METHOD_FACTORIES[method]
- except KeyError:
- request_factory = Request
- else:
- request_factory = self.request_factory
-
+ root = self.root_factory(environ)
request = request_factory(environ)
threadlocals['request'] = request
-
registry.has_listeners and registry.notify(NewRequest(request))
- root = self.root_factory(environ)
+
tdict = _traverse(root, environ)
if '_deprecation_warning' in tdict:
warning = tdict.pop('_deprecation_warning')
@@ -198,6 +174,17 @@ class Router(object):
request.virtual_root = vroot
request.virtual_root_path = vroot_path
+ def respond(response, view_name):
+ registry.has_listeners and registry.notify(
+ NewResponse(response))
+ try:
+ start_response(response.status, response.headerlist)
+ return response.app_iter
+ except AttributeError:
+ raise ValueError(
+ 'Non-response object returned from view %s: %r' %
+ (view_name, response))
+
if self.secured:
permitted = registry.queryMultiAdapter((context, request),
@@ -299,10 +286,11 @@ def make_app(root_factory, package=None, filename='configure.zcml',
``repoze.bfg`` WSGI application.
``root_factory`` must be a callable that accepts a WSGI
- environment and returns a traversal root object. It may be
- ``None``, in which case traversal is not performed at all.
- Instead, all URL-to-code mapping is done via URL dispatch (aka
- Routes).
+ environment and returns a traversal root object. The traversal
+ root returned by the root factory is the *default* traversal root;
+ it can be overridden on a per-view basis. ``root_factory`` may be
+ ``None``, in which case a 'default default' traversal root is
+ used.
``package`` is a Python module representing the application's
package. It is optional, defaulting to ``None``. If ``package``
@@ -360,19 +348,17 @@ def make_app(root_factory, package=None, filename='configure.zcml',
authorization_policy = ACLAuthorizationPolicy()
registry.registerUtility(authorization_policy, IAuthorizationPolicy)
+ if root_factory is None:
+ root_factory = DefaultRootFactory
+
+ # register the *default* root factory so we can find it later
+ registry.registerUtility(root_factory, IDefaultRootFactory)
+
mapper = RoutesRootFactory(root_factory)
registry.registerUtility(mapper, IRoutesMapper)
populateRegistry(registry, filename, package)
- context_factory = registry.queryUtility(
- IRoutesContextFactory,
- default=mapper.default_context_factory)
-
- if IRoutesContext.implementedBy(context_factory):
- mapper.decorate_context = False
- mapper.default_context_factory = context_factory
-
if not authentication_policy:
# deal with bw compat of <= 0.8 security policies (deprecated)
secpol = registry.queryUtility(ISecurityPolicy)
@@ -390,13 +376,10 @@ def make_app(root_factory, package=None, filename='configure.zcml',
if mapper.has_routes():
# if the user had any <route/> statements in his configuration,
- # use the RoutesRootFactory as the root factory
+ # use the RoutesRootFactory as the IRootFactory; otherwise use the
+ # default root factory (optimization; we don't want to go through
+ # the Routes logic if we know there are no routes to match)
root_factory = mapper
- else:
- # otherwise, use only the supplied root_factory (unless it's None)
- if root_factory is None:
- raise ValueError(
- 'root_factory (aka get_root) was None and no routes connected')
registry.registerUtility(root_factory, IRootFactory)
@@ -415,3 +398,8 @@ def make_app(root_factory, package=None, filename='configure.zcml',
return app
+class DefaultRootFactory:
+ __parent__ = None
+ __name__ = None
+ def __init__(self, environ):
+ pass
diff --git a/repoze/bfg/tests/test_authentication.py b/repoze/bfg/tests/test_authentication.py
index 09782cd3c..258fadfd2 100644
--- a/repoze/bfg/tests/test_authentication.py
+++ b/repoze/bfg/tests/test_authentication.py
@@ -361,20 +361,23 @@ class TestAuthTktCookieHelper(unittest.TestCase):
plugin = self._makeOne('secret')
old_val = self._makeTicket(userid='userid')
request = self._makeRequest({'HTTP_COOKIE':'auth_tkt=%s' % old_val})
- new_val = self._makeTicket(userid='other', userdata='userdata')
result = plugin.remember(request, 'other', userdata='userdata')
self.assertEqual(len(result), 3)
- self.assertEqual(result[0],
- ('Set-Cookie',
- 'auth_tkt="%s"; Path=/' % new_val))
- self.assertEqual(result[1],
- ('Set-Cookie',
- 'auth_tkt="%s"; Path=/; Domain=localhost'
- % new_val))
- self.assertEqual(result[2],
- ('Set-Cookie',
- 'auth_tkt="%s"; Path=/; Domain=.localhost'
- % new_val))
+
+ self.assertEqual(result[0][0], 'Set-Cookie')
+ self.failUnless(result[0][1].endswith('; Path=/'))
+ self.failUnless(result[0][1].startswith('auth_tkt='))
+ self.failIf(result[0][1].startswith('auth_tkt="%s"' % old_val))
+
+ self.assertEqual(result[1][0], 'Set-Cookie')
+ self.failUnless(result[1][1].endswith('; Path=/; Domain=localhost'))
+ self.failUnless(result[1][1].startswith('auth_tkt='))
+ self.failIf(result[1][1].startswith('auth_tkt="%s"' % old_val))
+
+ self.assertEqual(result[2][0], 'Set-Cookie')
+ self.failUnless(result[2][1].endswith('; Path=/; Domain=.localhost'))
+ self.failUnless(result[2][1].startswith('auth_tkt='))
+ self.failIf(result[2][1].startswith('auth_tkt="%s"' % old_val))
def test_remember_creds_different_include_ip(self):
plugin = self._makeOne('secret', include_ip=True)
diff --git a/repoze/bfg/tests/test_integration.py b/repoze/bfg/tests/test_integration.py
index 9c2d87135..107c8ae89 100644
--- a/repoze/bfg/tests/test_integration.py
+++ b/repoze/bfg/tests/test_integration.py
@@ -19,6 +19,10 @@ def wsgiapptest(environ, start_response):
""" """
return '123'
+def _getRequestInterface(name_or_iface=None):
+ from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES
+ return DEFAULT_REQUEST_FACTORIES[name_or_iface]['interface']
+
class WGSIAppPlusBFGViewTests(unittest.TestCase):
def setUp(self):
cleanUp()
@@ -36,7 +40,7 @@ class WGSIAppPlusBFGViewTests(unittest.TestCase):
self.assertEqual(result, '123')
def test_scanned(self):
- from repoze.bfg.interfaces import IRequest
+ IRequest = _getRequestInterface()
from repoze.bfg.interfaces import IView
from repoze.bfg.zcml import scan
context = DummyContext()
@@ -76,7 +80,6 @@ class PushPagePlusBFGViewTests(unittest.TestCase):
self.assertEqual(result.status, '200 OK')
def test_scanned(self):
- from repoze.bfg.interfaces import IRequest
from repoze.bfg.interfaces import IView
from repoze.bfg.zcml import scan
context = DummyContext()
@@ -85,6 +88,7 @@ class PushPagePlusBFGViewTests(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 2)
action = actions[0]
+ IRequest = _getRequestInterface()
self.assertEqual(action['args'],
('registerAdapter',
pushtest, (INothing, IRequest), IView, '', None))
@@ -139,7 +143,6 @@ class TestGrokkedApp(unittest.TestCase):
def test_it(self):
import inspect
from repoze.bfg.interfaces import IPOSTRequest
- from repoze.bfg.interfaces import IRequest
from repoze.bfg.interfaces import IView
import repoze.bfg.tests.grokkedapp as package
from zope.configuration import config
@@ -150,22 +153,25 @@ class TestGrokkedApp(unittest.TestCase):
xmlconfig.include(context, 'configure.zcml', package)
actions = context.actions
+ post_iface = _getRequestInterface(IPOSTRequest)
+ request_iface = _getRequestInterface()
+
postview = actions[-1]
self.assertEqual(postview[0][1], None)
self.assertEqual(postview[0][2], '')
- self.assertEqual(postview[0][3], IPOSTRequest)
+ self.assertEqual(postview[0][3], post_iface)
self.assertEqual(postview[0][4], IView)
self.assertEqual(postview[2][1], package.grokked_post)
- self.assertEqual(postview[2][2], (None, IPOSTRequest))
+ self.assertEqual(postview[2][2], (None, post_iface))
self.assertEqual(postview[2][3], IView)
klassview = actions[-2]
self.assertEqual(klassview[0][1], None)
self.assertEqual(klassview[0][2], 'grokked_klass')
- self.assertEqual(klassview[0][3], IRequest)
+ self.assertEqual(klassview[0][3], request_iface)
self.assertEqual(klassview[0][4], IView)
self.assertEqual(klassview[2][1], package.grokked_klass)
- self.assertEqual(klassview[2][2], (None, IRequest))
+ self.assertEqual(klassview[2][2], (None, request_iface))
self.assertEqual(klassview[2][3], IView)
self.failUnless(inspect.isfunction(package.grokked_klass))
self.assertEqual(package.grokked_klass(None, None), None)
@@ -173,10 +179,10 @@ class TestGrokkedApp(unittest.TestCase):
funcview = actions[-3]
self.assertEqual(funcview[0][1], None)
self.assertEqual(funcview[0][2], '')
- self.assertEqual(funcview[0][3], IRequest)
+ self.assertEqual(funcview[0][3], request_iface)
self.assertEqual(funcview[0][4], IView)
self.assertEqual(funcview[2][1], package.grokked)
- self.assertEqual(funcview[2][2], (None, IRequest))
+ self.assertEqual(funcview[2][2], (None, request_iface))
self.assertEqual(funcview[2][3], IView)
class DummyContext:
diff --git a/repoze/bfg/tests/test_request.py b/repoze/bfg/tests/test_request.py
index 4771c3e1c..9cae6643a 100644
--- a/repoze/bfg/tests/test_request.py
+++ b/repoze/bfg/tests/test_request.py
@@ -13,8 +13,8 @@ class TestMakeRequestASCII(unittest.TestCase):
class TestSubclassedRequest(unittest.TestCase):
def _getTargetClass(self):
- from repoze.bfg.request import Request
- return Request
+ from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES
+ return DEFAULT_REQUEST_FACTORIES[None]['factory']
def _makeOne(self, environ):
request = self._getTargetClass()(environ)
@@ -56,6 +56,172 @@ class TestCurrentRequest(unittest.TestCase):
manager.pop()
self.assertEqual(self._callFUT(), None)
+class TestRequestFactory(unittest.TestCase):
+ def _callFUT(self, environ):
+ from repoze.bfg.request import request_factory
+ return request_factory(environ)
+
+ def _getRequestFactory(self, name_or_iface=None):
+ from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES
+ return DEFAULT_REQUEST_FACTORIES[name_or_iface]['factory']
+
+ def _makeRoute(self):
+ route = DummyRoute()
+ factories = {}
+ def factory(environ):
+ return environ
+ for name in (None, 'GET', 'POST', 'PUT', 'DELETE', 'HEAD'):
+ factories[name] = {'factory':factory}
+ route.request_factories = factories
+ return route
+
+ def test_no_route_no_request_method(self):
+ from repoze.bfg.interfaces import IRequest
+ result = self._callFUT({})
+ self.assertEqual(result.__class__, self._getRequestFactory())
+ self.failUnless(IRequest.providedBy(result))
+
+ def test_no_route_unknown(self):
+ from repoze.bfg.interfaces import IRequest
+ result = self._callFUT({'REQUEST_METHOD':'UNKNOWN'})
+ self.assertEqual(result.__class__, self._getRequestFactory())
+ self.failUnless(IRequest.providedBy(result))
+
+ def test_no_route_get(self):
+ from repoze.bfg.interfaces import IGETRequest
+ result = self._callFUT({'REQUEST_METHOD':'GET'})
+ self.assertEqual(result.__class__, self._getRequestFactory('GET'))
+ self.failUnless(IGETRequest.providedBy(result))
+
+ def test_no_route_post(self):
+ from repoze.bfg.interfaces import IPOSTRequest
+ result = self._callFUT({'REQUEST_METHOD':'POST'})
+ self.assertEqual(result.__class__, self._getRequestFactory('POST'))
+ self.failUnless(IPOSTRequest.providedBy(result))
+
+ def test_no_route_put(self):
+ from repoze.bfg.interfaces import IPUTRequest
+ result = self._callFUT({'REQUEST_METHOD':'PUT'})
+ self.assertEqual(result.__class__, self._getRequestFactory('PUT'))
+ self.failUnless(IPUTRequest.providedBy(result))
+
+ def test_no_route_delete(self):
+ from repoze.bfg.interfaces import IDELETERequest
+ result = self._callFUT({'REQUEST_METHOD':'DELETE'})
+ self.assertEqual(result.__class__, self._getRequestFactory('DELETE'))
+ self.failUnless(IDELETERequest.providedBy(result))
+
+ def test_no_route_head(self):
+ from repoze.bfg.interfaces import IHEADRequest
+ result = self._callFUT({'REQUEST_METHOD':'HEAD'})
+ self.assertEqual(result.__class__, self._getRequestFactory('HEAD'))
+ self.failUnless(IHEADRequest.providedBy(result))
+
+ def test_route_no_request_method(self):
+ route = self._makeRoute()
+ environ = {'bfg.routes.route':route}
+ result = self._callFUT(environ)
+ self.assertEqual(result, environ)
+
+ def test_route_unknown(self):
+ route = self._makeRoute()
+ environ = {'bfg.routes.route':route, 'REQUEST_METHOD':'UNKNOWN'}
+ result = self._callFUT(environ)
+ self.assertEqual(result, environ)
+
+ def test_route_known(self):
+ route = self._makeRoute()
+ environ = {'bfg.routes.route':route, 'REQUEST_METHOD':'GET'}
+ result = self._callFUT(environ)
+ self.assertEqual(result, environ)
+
+class TestNamedRequestFactories(unittest.TestCase):
+ def _callFUT(self, name):
+ from repoze.bfg.request import named_request_factories
+ return named_request_factories(name)
+
+ def test_it_unnamed(self):
+ factories = self._callFUT(None)
+ from repoze.bfg.interfaces import IRequest
+ from repoze.bfg.interfaces import IGETRequest
+ from repoze.bfg.interfaces import IPOSTRequest
+ from repoze.bfg.interfaces import IPUTRequest
+ from repoze.bfg.interfaces import IDELETERequest
+ from repoze.bfg.interfaces import IHEADRequest
+ for alias, iface in (
+ (None, IRequest),
+ ('GET', IGETRequest),
+ ('POST', IPOSTRequest),
+ ('PUT', IPUTRequest),
+ ('DELETE', IDELETERequest),
+ ('HEAD', IHEADRequest),
+ ):
+ self.failUnless(alias in factories)
+ self.failUnless(iface in factories)
+ self.assertEqual(factories[alias], factories[iface])
+ named_iface = factories[alias]['interface']
+ named_factory = factories[alias]['factory']
+ self.failUnless(named_iface.implementedBy(named_factory))
+ self.assertEqual(factories[alias]['interface'], iface)
+ self.assertEqual(factories[iface]['interface'], iface)
+ self.assertEqual(factories[alias]['factory'].charset, 'utf-8')
+
+ def test_it_named(self):
+ factories = self._callFUT('name')
+ from zope.interface.interface import InterfaceClass
+ from repoze.bfg.interfaces import IRequest
+ from repoze.bfg.interfaces import IGETRequest
+ from repoze.bfg.interfaces import IPOSTRequest
+ from repoze.bfg.interfaces import IPUTRequest
+ from repoze.bfg.interfaces import IDELETERequest
+ from repoze.bfg.interfaces import IHEADRequest
+ for alias, iface in (
+ (None, IRequest),
+ ('GET', IGETRequest),
+ ('POST', IPOSTRequest),
+ ('PUT', IPUTRequest),
+ ('DELETE', IDELETERequest),
+ ('HEAD', IHEADRequest),
+ ):
+ self.failUnless(alias in factories)
+ self.failUnless(iface in factories)
+ self.assertEqual(factories[alias], factories[iface])
+ self.assertEqual(factories[alias]['factory'].charset, 'utf-8')
+ named_iface = factories[alias]['interface']
+ named_factory = factories[alias]['factory']
+ self.failUnless(named_iface.implementedBy(named_factory))
+
+class TestDefaultRequestFactories(unittest.TestCase):
+ def test_it(self):
+ from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES as factories
+ from repoze.bfg.interfaces import IRequest
+ from repoze.bfg.interfaces import IGETRequest
+ from repoze.bfg.interfaces import IPOSTRequest
+ from repoze.bfg.interfaces import IPUTRequest
+ from repoze.bfg.interfaces import IDELETERequest
+ from repoze.bfg.interfaces import IHEADRequest
+ for alias, iface in (
+ (None, IRequest),
+ ('GET', IGETRequest),
+ ('POST', IPOSTRequest),
+ ('PUT', IPUTRequest),
+ ('DELETE', IDELETERequest),
+ ('HEAD', IHEADRequest),
+ ):
+ self.failUnless(alias in factories)
+ self.failUnless(iface in factories)
+ self.assertEqual(factories[alias], factories[iface])
+ named_iface = factories[alias]['interface']
+ named_factory = factories[alias]['factory']
+ self.failUnless(named_iface.implementedBy(named_factory))
+ self.assertEqual(factories[alias]['interface'], iface)
+ self.assertEqual(factories[iface]['interface'], iface)
+ self.assertEqual(factories[alias]['factory'].charset, 'utf-8')
+
+
+class DummyRoute:
+ pass
+
class DummyRequest:
pass
diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py
index 6e18f6089..589843b0f 100644
--- a/repoze/bfg/tests/test_router.py
+++ b/repoze/bfg/tests/test_router.py
@@ -123,7 +123,7 @@ class RouterTests(unittest.TestCase):
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
- rootfactory = self._registerRootFactory(None)
+ rootfactory = self._registerRootFactory('abc')
router = self._makeOne()
self.assertEqual(router.root_policy, rootfactory)
@@ -132,13 +132,11 @@ class RouterTests(unittest.TestCase):
def app():
""" """
self.registry.registerUtility(app, IForbiddenView)
- self._registerRootFactory(None)
router = self._makeOne()
self.assertEqual(router.forbidden_view, app)
def test_iforbiddenview_nooverride(self):
context = DummyContext()
- self._registerRootFactory(None)
router = self._makeOne()
from repoze.bfg.router import default_forbidden_view
self.assertEqual(router.forbidden_view, default_forbidden_view)
@@ -148,13 +146,11 @@ class RouterTests(unittest.TestCase):
def app():
""" """
self.registry.registerUtility(app, INotFoundView)
- self._registerRootFactory(None)
router = self._makeOne()
self.assertEqual(router.notfound_view, app)
def test_inotfoundview_nooverride(self):
context = DummyContext()
- self._registerRootFactory(None)
router = self._makeOne()
from repoze.bfg.router import default_notfound_view
self.assertEqual(router.notfound_view, default_notfound_view)
@@ -164,7 +160,6 @@ class RouterTests(unittest.TestCase):
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
- rootfactory = self._registerRootFactory(None)
logger = self._registerLogger()
def factory():
return 'yo'
@@ -183,7 +178,6 @@ class RouterTests(unittest.TestCase):
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
- rootfactory = self._registerRootFactory(None)
logger = self._registerLogger()
def factory():
return 'yo'
@@ -202,7 +196,6 @@ class RouterTests(unittest.TestCase):
context = DummyContext()
self._registerTraverserFactory(context)
logger = self._registerLogger()
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -214,29 +207,12 @@ class RouterTests(unittest.TestCase):
self.failIf('debug_notfound' in result[0])
self.assertEqual(len(logger.messages), 0)
- def test_call_root_is_icontextnotfound(self):
- from zope.interface import implements
- from repoze.bfg.interfaces import IContextNotFound
- class NotFound(object):
- implements(IContextNotFound)
- context = NotFound()
- self._registerTraverserFactory(context)
- environ = self._makeEnviron()
- start_response = DummyStartResponse()
- self._registerRootFactory(NotFound())
- router = self._makeOne()
- result = router(environ, start_response)
- status = start_response.status
- self.assertEqual(status, '404 Not Found')
- self.failUnless('http://localhost:8080' in result[0], result)
-
def test_call_no_view_registered_debug_notfound_false(self):
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
logger = self._registerLogger()
self._registerSettings(debug_notfound=False)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -254,7 +230,6 @@ class RouterTests(unittest.TestCase):
self._registerTraverserFactory(context)
self._registerSettings(debug_notfound=True)
logger = self._registerLogger()
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -282,7 +257,6 @@ class RouterTests(unittest.TestCase):
environ = self._makeEnviron()
view = make_view('abc')
self._registerView(view, '', None, None)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
self.assertRaises(ValueError, router, environ, start_response)
@@ -292,7 +266,6 @@ class RouterTests(unittest.TestCase):
context = DummyContext()
environ = self._makeEnviron()
self._registerTraverserFactory(context)
- self._registerRootFactory(None)
def app(context, request):
""" """
self.registry.registerUtility(app, INotFoundView)
@@ -318,7 +291,6 @@ class RouterTests(unittest.TestCase):
environ = self._makeEnviron()
self._registerView(view, '', IContext, IRequest)
checker = self._registerViewPermission('', denied)
- self._registerRootFactory(None)
def app(context, request):
""" """
self.registry.registerUtility(app, IForbiddenView)
@@ -334,7 +306,7 @@ class RouterTests(unittest.TestCase):
view = make_view(response)
environ = self._makeEnviron()
self._registerView(view, '', None, None)
- self._registerRootFactory(None)
+ rootfactory = self._registerRootFactory(context)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -344,7 +316,7 @@ class RouterTests(unittest.TestCase):
self.assertEqual(environ['webob.adhoc_attrs']['view_name'], '')
self.assertEqual(environ['webob.adhoc_attrs']['subpath'], [])
self.assertEqual(environ['webob.adhoc_attrs']['context'], context)
- self.assertEqual(environ['webob.adhoc_attrs']['root'], None)
+ self.assertEqual(environ['webob.adhoc_attrs']['root'], context)
def test_call_deprecation_warning(self):
context = DummyContext()
@@ -354,7 +326,6 @@ class RouterTests(unittest.TestCase):
view = make_view(response)
environ = self._makeEnviron()
self._registerView(view, '', None, None)
- self._registerRootFactory(None)
router = self._makeOne()
logger = self._registerLogger()
router.logger = logger
@@ -368,12 +339,12 @@ class RouterTests(unittest.TestCase):
self._registerTraverserFactory(context, view_name='foo',
subpath=['bar'],
traversed=['context'])
+ rootfactory = self._registerRootFactory(context)
response = DummyResponse()
response.app_iter = ['Hello world']
view = make_view(response)
environ = self._makeEnviron()
self._registerView(view, 'foo', None, None)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -383,7 +354,7 @@ class RouterTests(unittest.TestCase):
self.assertEqual(environ['webob.adhoc_attrs']['view_name'], 'foo')
self.assertEqual(environ['webob.adhoc_attrs']['subpath'], ['bar'])
self.assertEqual(environ['webob.adhoc_attrs']['context'], context)
- self.assertEqual(environ['webob.adhoc_attrs']['root'], None)
+ self.assertEqual(environ['webob.adhoc_attrs']['root'], context)
def test_call_view_registered_specific_success(self):
from zope.interface import Interface
@@ -394,12 +365,12 @@ class RouterTests(unittest.TestCase):
context = DummyContext()
directlyProvides(context, IContext)
self._registerTraverserFactory(context)
+ rootfactory = self._registerRootFactory(context)
response = DummyResponse()
response.app_iter = ['Hello world']
view = make_view(response)
environ = self._makeEnviron()
self._registerView(view, '', IContext, IRequest)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -409,7 +380,7 @@ class RouterTests(unittest.TestCase):
self.assertEqual(environ['webob.adhoc_attrs']['view_name'], '')
self.assertEqual(environ['webob.adhoc_attrs']['subpath'], [])
self.assertEqual(environ['webob.adhoc_attrs']['context'], context)
- self.assertEqual(environ['webob.adhoc_attrs']['root'], None)
+ self.assertEqual(environ['webob.adhoc_attrs']['root'], context)
def test_call_view_registered_specific_fail(self):
from zope.interface import Interface
@@ -426,7 +397,6 @@ class RouterTests(unittest.TestCase):
view = make_view(response)
environ = self._makeEnviron()
self._registerView(view, '', IContext, IRequest)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -446,7 +416,6 @@ class RouterTests(unittest.TestCase):
view = make_view(response)
environ = self._makeEnviron()
self._registerView(view, '', IContext, IRequest)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -466,7 +435,6 @@ class RouterTests(unittest.TestCase):
view = make_view(response)
environ = self._makeEnviron()
self._registerView(view, '', IContext, IRequest)
- self._registerRootFactory(None)
router = self._makeOne()
router.debug_authorization = True
start_response = DummyStartResponse()
@@ -490,7 +458,6 @@ class RouterTests(unittest.TestCase):
view = make_view(response)
environ = self._makeEnviron()
self._registerView(view, '', IContext, IRequest)
- self._registerRootFactory(None)
router = self._makeOne()
router.debug_authorization = True
start_response = DummyStartResponse()
@@ -514,7 +481,6 @@ class RouterTests(unittest.TestCase):
view = make_view(response)
environ = self._makeEnviron()
self._registerView(view, '', IContext, IRequest)
- self._registerRootFactory(None)
router = self._makeOne()
router.debug_authorization = False
start_response = DummyStartResponse()
@@ -537,7 +503,6 @@ class RouterTests(unittest.TestCase):
environ = self._makeEnviron()
self._registerView(view, '', IContext, IRequest)
checker = self._registerViewPermission('', True)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -561,7 +526,6 @@ class RouterTests(unittest.TestCase):
environ = self._makeEnviron()
self._registerView(view, '', IContext, IRequest)
checker = self._registerViewPermission('', denied)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -588,7 +552,6 @@ class RouterTests(unittest.TestCase):
self._registerView(view, '', IContext, IRequest)
checker = self._registerViewPermission('', denied)
self._registerSettings(debug_authorization=False)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -616,7 +579,6 @@ class RouterTests(unittest.TestCase):
checker = self._registerViewPermission('', allowed)
self._registerSettings(debug_authorization=True)
logger = self._registerLogger()
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -650,7 +612,6 @@ class RouterTests(unittest.TestCase):
from repoze.bfg.interfaces import INewResponse
request_events = self._registerEventListener(INewRequest)
response_events = self._registerEventListener(INewResponse)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -667,7 +628,6 @@ class RouterTests(unittest.TestCase):
view = make_view(response)
environ = self._makeEnviron()
self._registerView(view, '', None, None)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
router.threadlocal_manager = DummyThreadLocalManager()
@@ -687,7 +647,6 @@ class RouterTests(unittest.TestCase):
view = make_view(response)
environ = self._makeEnviron(REQUEST_METHOD='POST')
self._registerView(view, '', None, None)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
request_events = self._registerEventListener(INewRequest)
@@ -709,7 +668,6 @@ class RouterTests(unittest.TestCase):
view = make_view(response)
environ = self._makeEnviron(REQUEST_METHOD='PUT')
self._registerView(view, '', None, None)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
request_events = self._registerEventListener(INewRequest)
@@ -729,7 +687,6 @@ class RouterTests(unittest.TestCase):
view = make_view(response)
environ = self._makeEnviron(REQUEST_METHOD='UNKNOWN')
self._registerView(view, '', None, None)
- self._registerRootFactory(None)
router = self._makeOne()
start_response = DummyStartResponse()
request_events = self._registerEventListener(INewRequest)
@@ -737,30 +694,6 @@ class RouterTests(unittest.TestCase):
request = request_events[0].request
self.failUnless(IRequest.providedBy(request))
- def test_call_irequestfactory_override(self):
- from repoze.bfg.interfaces import INewRequest
- from repoze.bfg.interfaces import IRequestFactory
- from repoze.bfg.testing import DummyRequest
- self.registry.registerUtility(DummyRequest, IRequestFactory)
- context = DummyContext()
- self._registerTraverserFactory(context)
- response = DummyResponse()
- response.app_iter = ['Hello world']
- view = make_view(response)
- environ = self._makeEnviron()
- self._registerView(view, '', None, None)
- self._registerRootFactory(None)
- router = self._makeOne()
- start_response = DummyStartResponse()
- request_events = self._registerEventListener(INewRequest)
- result = router(environ, start_response)
- request = request_events[0].request
- self.failUnless(isinstance(request, DummyRequest))
- self.assertEqual(request.root, None)
- self.assertEqual(request.context, context)
- self.assertEqual(request.view_name, '')
- self.assertEqual(request.subpath, [])
-
class MakeAppTests(unittest.TestCase):
def setUp(self):
cleanUp()
@@ -844,13 +777,14 @@ class MakeAppTests(unittest.TestCase):
self.assertEqual(settings.reload_templates, True)
self.assertEqual(settings.debug_authorization, True)
self.failUnless(isinstance(rootfactory, RoutesRootFactory))
- self.assertEqual(rootfactory.get_root, rootpolicy)
+ self.assertEqual(rootfactory.default_root_factory, rootpolicy)
self.failUnless(self.regmgr.pushed and self.regmgr.popped)
def test_routes_in_config_no_rootpolicy(self):
options= {'reload_templates':True,
'debug_authorization':True}
from repoze.bfg.urldispatch import RoutesRootFactory
+ from repoze.bfg.router import DefaultRootFactory
from repoze.bfg.tests import routesapp
app = self._callFUT(None, routesapp, options=options)
from repoze.bfg.interfaces import ISettings
@@ -863,15 +797,18 @@ class MakeAppTests(unittest.TestCase):
self.assertEqual(settings.reload_templates, True)
self.assertEqual(settings.debug_authorization, True)
self.failUnless(isinstance(rootfactory, RoutesRootFactory))
- self.assertEqual(rootfactory.get_root, None)
+ self.assertEqual(rootfactory.default_root_factory, DefaultRootFactory)
self.failUnless(self.regmgr.pushed and self.regmgr.popped)
def test_no_routes_in_config_no_rootpolicy(self):
+ from repoze.bfg.router import DefaultRootFactory
+ from repoze.bfg.interfaces import IRootFactory
options= {'reload_templates':True,
'debug_authorization':True}
from repoze.bfg.tests import fixtureapp
- self.assertRaises(ValueError, self._callFUT, None, fixtureapp,
- options=options)
+ app = self._callFUT(None, fixtureapp, options=options)
+ rootfactory = app.registry.getUtility(IRootFactory)
+ self.assertEqual(rootfactory, DefaultRootFactory)
def test_authorization_policy_no_authentication_policy(self):
from repoze.bfg.interfaces import IAuthorizationPolicy
@@ -922,38 +859,6 @@ class MakeAppTests(unittest.TestCase):
self.assertEqual(len(logger.messages), 1)
self.failUnless('ISecurityPolicy' in logger.messages[0])
- def test_custom_default_context_factory_nodecorate(self):
- from repoze.bfg.tests import routesapp
- from zope.component import getGlobalSiteManager
- from repoze.bfg.interfaces import IRoutesContextFactory
- from repoze.bfg.interfaces import IRoutesMapper
- class Dummy(object):
- pass
- gsm = getGlobalSiteManager()
- gsm.registerUtility(Dummy, IRoutesContextFactory)
- app = self._callFUT(None, routesapp, registry=gsm)
- mapper = gsm.getUtility(IRoutesMapper)
- self.assertEqual(mapper.default_context_factory,
- Dummy)
- self.assertEqual(mapper.decorate_context, True)
-
- def test_custom_default_context_factory_decorate(self):
- from repoze.bfg.tests import routesapp
- from zope.component import getGlobalSiteManager
- from repoze.bfg.interfaces import IRoutesContextFactory
- from repoze.bfg.interfaces import IRoutesMapper
- from repoze.bfg.interfaces import IRoutesContext
- from zope.interface import implements
- class Dummy(object):
- implements(IRoutesContext)
- gsm = getGlobalSiteManager()
- gsm.registerUtility(Dummy, IRoutesContextFactory)
- app = self._callFUT(None, routesapp, registry=gsm)
- mapper = gsm.getUtility(IRoutesMapper)
- self.assertEqual(mapper.default_context_factory,
- Dummy)
- self.assertEqual(mapper.decorate_context, False)
-
class TestDefaultForbiddenView(unittest.TestCase):
def _callFUT(self, context, request):
from repoze.bfg.router import default_forbidden_view
diff --git a/repoze/bfg/tests/test_traversal.py b/repoze/bfg/tests/test_traversal.py
index 41cf667b9..506a69d17 100644
--- a/repoze/bfg/tests/test_traversal.py
+++ b/repoze/bfg/tests/test_traversal.py
@@ -189,6 +189,67 @@ class ModelGraphTraverserTests(unittest.TestCase):
environ = self._getEnviron(PATH_INFO='/%s' % segment)
self.assertRaises(TypeError, policy, environ)
+ def test_withroute_nothingfancy(self):
+ model = DummyContext()
+ traverser = self._makeOne(model)
+ routing_args = ((), {})
+ environ = {'bfg.routes.matchdict': {}}
+ result = traverser(environ)
+ self.assertEqual(result['context'], model)
+ self.assertEqual(result['view_name'], '')
+ self.assertEqual(result['subpath'], [])
+ self.assertEqual(result['traversed'], [])
+ self.assertEqual(result['virtual_root'], model)
+ self.assertEqual(result['virtual_root_path'], [])
+
+ def test_withroute_with_subpath(self):
+ model = DummyContext()
+ traverser = self._makeOne(model)
+ environ = {'bfg.routes.matchdict': {'subpath':'/a/b/c'}}
+ result = traverser(environ)
+ self.assertEqual(result['context'], model)
+ self.assertEqual(result['view_name'], '')
+ self.assertEqual(result['subpath'], ['a', 'b','c'])
+ self.assertEqual(result['traversed'], [])
+ self.assertEqual(result['virtual_root'], model)
+ self.assertEqual(result['virtual_root_path'], [])
+
+ def test_withroute_with_path_info(self):
+ model = DummyContext()
+ traverser = self._makeOne(model)
+ environ = {'bfg.routes.matchdict': {'path_info':'foo/bar'},
+ 'PATH_INFO':'/a/b/foo/bar', 'SCRIPT_NAME':''}
+ result = traverser(environ)
+ self.assertEqual(result['context'], model)
+ self.assertEqual(result['view_name'], '')
+ self.assertEqual(result['subpath'], [])
+ self.assertEqual(result['traversed'], [])
+ self.assertEqual(result['virtual_root'], model)
+ self.assertEqual(result['virtual_root_path'], [])
+ self.assertEqual(environ['PATH_INFO'], '/foo/bar')
+ self.assertEqual(environ['SCRIPT_NAME'], '/a/b')
+
+ def test_withroute_with_path_info_PATH_INFO_w_extra_slash(self):
+ model = DummyContext()
+ traverser = self._makeOne(model)
+ environ = {'bfg.routes.matchdict':{'path_info':'foo/bar'},
+ 'PATH_INFO':'/a/b//foo/bar', 'SCRIPT_NAME':''}
+ traverser(environ)
+ self.assertEqual(environ['PATH_INFO'], '/foo/bar')
+ self.assertEqual(environ['SCRIPT_NAME'], '/a/b')
+
+ def test_withroute_and_traverse(self):
+ model = DummyContext()
+ traverser = self._makeOne(model)
+ environ = {'bfg.routes.matchdict': {'traverse':'foo/bar'}}
+ result = traverser(environ)
+ self.assertEqual(result['context'], model)
+ self.assertEqual(result['view_name'], 'foo')
+ self.assertEqual(result['subpath'], ['bar'])
+ self.assertEqual(result['traversed'], [])
+ self.assertEqual(result['virtual_root'], model)
+ self.assertEqual(result['virtual_root_path'], [])
+
class FindInterfaceTests(unittest.TestCase):
def _callFUT(self, context, iface):
from repoze.bfg.traversal import find_interface
@@ -637,7 +698,21 @@ class TraversalContextURLTests(unittest.TestCase):
context_url = self._makeOne(bar, request)
result = context_url()
self.assertEqual(result, 'http://example.com:5432//bar/')
-
+
+ def test_with_route(self):
+ root = DummyContext()
+ root.__name__ = None
+ root.__parent__ = None
+ one = DummyContext()
+ one.__name__ = 'one'
+ one.__parent__ = root
+ route = DummyRoute()
+ request = DummyRequest({'bfg.routes.route':route,
+ 'bfg.routes.matchdict':{'a':1}})
+ context_url = self._makeOne(one, request)
+ result = context_url()
+ self.assertEqual(result, 'http://example.com/one/')
+ self.assertEqual(route.generate_kw, {'a':1, 'traverse':'/one/'})
class TestVirtualRoot(unittest.TestCase):
def setUp(self):
@@ -839,3 +914,8 @@ class DummyContextURL:
def virtual_root(self):
return '123'
+
+class DummyRoute:
+ def generate(self, **kw):
+ self.generate_kw = kw
+ return 'http://example.com'
diff --git a/repoze/bfg/tests/test_urldispatch.py b/repoze/bfg/tests/test_urldispatch.py
index 2b4578f94..68fda032d 100644
--- a/repoze/bfg/tests/test_urldispatch.py
+++ b/repoze/bfg/tests/test_urldispatch.py
@@ -22,139 +22,78 @@ class RoutesRootFactoryTests(unittest.TestCase):
klass = self._getTargetClass()
return klass(get_root)
- def test_init_default_context_factory(self):
- from zope.component import getGlobalSiteManager
- from repoze.bfg.interfaces import IRoutesContextFactory
- from repoze.bfg.urldispatch import DefaultRoutesContext
- class Dummy(object):
- pass
- gsm = getGlobalSiteManager()
- gsm.registerUtility(Dummy, IRoutesContextFactory)
+ def test_init_default_root_factory(self):
mapper = self._makeOne(None)
- self.assertEqual(mapper.default_context_factory, DefaultRoutesContext)
- self.assertEqual(mapper.decorate_context, True)
+ self.assertEqual(mapper.default_root_factory, None)
def test_no_route_matches(self):
- marker = ()
- get_root = make_get_root(marker)
+ get_root = make_get_root(123)
mapper = self._makeOne(get_root)
environ = self._getEnviron(PATH_INFO='/')
result = mapper(environ)
- self.assertEqual(result, marker)
+ self.assertEqual(result, 123)
self.assertEqual(mapper.environ, environ)
def test_route_matches(self):
- marker = ()
- get_root = make_get_root(marker)
+ get_root = make_get_root(123)
mapper = self._makeOne(get_root)
mapper.connect('foo', 'archives/:action/:article', foo='foo')
environ = self._getEnviron(PATH_INFO='/archives/action1/article1')
result = mapper(environ)
- from repoze.bfg.interfaces import IRoutesContext
- self.failUnless(IRoutesContext.providedBy(result))
- self.assertEqual(result.foo, 'foo')
- self.assertEqual(result.action, 'action1')
- self.assertEqual(result.article, 'article1')
+ self.assertEqual(result, 123)
routing_args = environ['wsgiorg.routing_args'][1]
self.assertEqual(routing_args['foo'], 'foo')
self.assertEqual(routing_args['action'], 'action1')
self.assertEqual(routing_args['article'], 'article1')
- self.assertEqual(environ['bfg.route'].name, 'foo')
+ self.assertEqual(environ['bfg.routes.matchdict'], routing_args)
+ self.assertEqual(environ['bfg.routes.route'].name, 'foo')
def test_unnamed_root_route_matches(self):
- mapper = self._makeOne(None)
+ root_factory = make_get_root(123)
+ mapper = self._makeOne(root_factory)
mapper.connect('')
environ = self._getEnviron(PATH_INFO='/')
result = mapper(environ)
- from repoze.bfg.interfaces import IRoutesContext
- self.failUnless(IRoutesContext.providedBy(result))
- self.assertEqual(environ['bfg.route'].name, None)
+ self.assertEqual(result, 123)
+ self.assertEqual(environ['bfg.routes.route'].name, None)
+ self.assertEqual(environ['bfg.routes.matchdict'], {})
+ self.assertEqual(environ['wsgiorg.routing_args'], ((), {}))
def test_named_root_route_matches(self):
- mapper = self._makeOne(None)
+ root_factory = make_get_root(123)
+ mapper = self._makeOne(root_factory)
mapper.connect('root', '')
environ = self._getEnviron(PATH_INFO='/')
result = mapper(environ)
- from repoze.bfg.interfaces import IRoutesContext
- self.failUnless(IRoutesContext.providedBy(result))
- self.assertEqual(environ['bfg.route'].name, 'root')
+ self.assertEqual(result, 123)
+ self.assertEqual(environ['bfg.routes.route'].name, 'root')
+ self.assertEqual(environ['bfg.routes.matchdict'], {})
+ self.assertEqual(environ['wsgiorg.routing_args'], ((), {}))
def test_unicode_in_route_default(self):
- marker = ()
- get_root = make_get_root(marker)
- mapper = self._makeOne(get_root)
- class DummyRoute2:
+ root_factory = make_get_root(123)
+ mapper = self._makeOne(root_factory)
+ class DummyRoute:
routepath = ':id'
_factory = None
- _provides = ()
la = unicode('\xc3\xb1a', 'utf-8')
- mapper.routematch = lambda *arg: ({la:'id'}, DummyRoute2)
+ mapper.routematch = lambda *arg: ({la:'id'}, DummyRoute)
mapper.connect('whatever', ':la')
environ = self._getEnviron(PATH_INFO='/foo')
result = mapper(environ)
- from repoze.bfg.interfaces import IRoutesContext
- self.failUnless(IRoutesContext.providedBy(result))
- self.assertEqual(getattr(result, la.encode('utf-8')), 'id')
+ self.assertEqual(result, 123)
+ self.assertEqual(environ['bfg.routes.route'], DummyRoute)
+ self.assertEqual(environ['bfg.routes.matchdict'], {u'\xf1a': 'id'})
routing_args = environ['wsgiorg.routing_args'][1]
- self.assertEqual(routing_args[la.encode('utf-8')], 'id')
+ self.assertEqual(routing_args[la], 'id')
- def test_no_fallback_get_root(self):
- from repoze.bfg.urldispatch import RoutesContextNotFound
- marker = ()
- mapper = self._makeOne(None)
+ def test_fallback_to_default_root_factory(self):
+ root_factory = make_get_root(123)
+ mapper = self._makeOne(root_factory)
mapper.connect('wont', 'wont/:be/:found')
environ = self._getEnviron(PATH_INFO='/archives/action1/article1')
result = mapper(environ)
- self.failUnless(isinstance(result, RoutesContextNotFound))
-
- def test_custom_factory(self):
- marker = ()
- get_root = make_get_root(marker)
- mapper = self._makeOne(get_root)
- from zope.interface import implements, Interface
- class IDummy(Interface):
- pass
- class Dummy(object):
- implements(IDummy)
- def __init__(self, **kw):
- self.__dict__.update(kw)
- mapper.connect('article', 'archives/:action/:article',
- _factory=Dummy)
- environ = self._getEnviron(PATH_INFO='/archives/action1/article1')
- result = mapper(environ)
- self.assertEqual(result.action, 'action1')
- self.assertEqual(result.article, 'article1')
- from repoze.bfg.interfaces import IRoutesContext
- self.failUnless(IRoutesContext.providedBy(result))
- self.failUnless(isinstance(result, Dummy))
- self.failUnless(IDummy.providedBy(result))
- self.failIf(hasattr(result, '_factory'))
-
- def test_decorate_context_false(self):
- from repoze.bfg.interfaces import IRoutesContext
- class Dummy:
- def __init__(self, **kw):
- pass
- mapper = self._makeOne(None)
- mapper.connect('root', '')
- environ = self._getEnviron(PATH_INFO='/')
- mapper.decorate_context = False
- mapper.default_context_factory = Dummy
- result = mapper(environ)
- self.failIf(IRoutesContext.providedBy(result))
-
- def test_decorate_context_true(self):
- from repoze.bfg.interfaces import IRoutesContext
- class Dummy:
- def __init__(self, **kw):
- pass
- mapper = self._makeOne(None)
- mapper.connect('root', '')
- environ = self._getEnviron(PATH_INFO='/')
- mapper.decorate_context = True
- mapper.default_context_factory = Dummy
- result = mapper(environ)
- self.failUnless(IRoutesContext.providedBy(result))
+ self.assertEqual(result, 123)
def test_has_routes(self):
mapper = self._makeOne(None)
@@ -163,163 +102,23 @@ class RoutesRootFactoryTests(unittest.TestCase):
self.assertEqual(mapper.has_routes(), True)
def test_url_for(self):
- marker = ()
- get_root = make_get_root(marker)
- mapper = self._makeOne(get_root)
+ root_factory = make_get_root(None)
+ mapper = self._makeOne(root_factory)
mapper.connect('whatever', 'archives/:action/:article')
environ = self._getEnviron(PATH_INFO='/archives/action1/article1')
- route = DummyRoute('yo')
- environ['bfg.route'] = route
result = mapper(environ)
from routes import url_for
result = url_for(action='action2', article='article2')
self.assertEqual(result, '/archives/action2/article2')
-class TestRoutesContextNotFound(unittest.TestCase):
- def _getTargetClass(self):
- from repoze.bfg.urldispatch import RoutesContextNotFound
- return RoutesContextNotFound
-
- def _makeOne(self, msg):
- return self._getTargetClass()(msg)
-
- def test_it(self):
- inst = self._makeOne('hi')
- self.assertEqual(inst.msg, 'hi')
-
def make_get_root(result):
def dummy_get_root(environ):
return result
return dummy_get_root
-class RoutesModelTraverserTests(unittest.TestCase):
- def _getTargetClass(self):
- from repoze.bfg.urldispatch import RoutesModelTraverser
- return RoutesModelTraverser
-
- def _makeOne(self, model):
- klass = self._getTargetClass()
- return klass(model)
-
- def test_class_conforms_to_ITraverser(self):
- from zope.interface.verify import verifyClass
- from repoze.bfg.interfaces import ITraverser
- verifyClass(ITraverser, self._getTargetClass())
-
- def test_instance_conforms_to_ITraverser(self):
- from zope.interface.verify import verifyObject
- from repoze.bfg.interfaces import ITraverser
- verifyObject(ITraverser, self._makeOne(None))
-
- def test_it_nothingfancy(self):
- model = DummyContext()
- traverser = self._makeOne(model)
- routing_args = ((), {})
- route = DummyRoute('yo')
- environ = {'wsgiorg.routing_args': routing_args, 'bfg.route': route}
- result = traverser(environ)
- self.assertEqual(result['context'], model)
- self.assertEqual(result['view_name'], 'yo')
- self.assertEqual(result['subpath'], [])
- self.assertEqual(result['traversed'], None)
- self.assertEqual(result['virtual_root'], model)
- self.assertEqual(result['virtual_root_path'], None)
-
- def test_call_with_subpath(self):
- model = DummyContext()
- traverser = self._makeOne(model)
- routing_args = ((), {'subpath':'/a/b/c'})
- route = DummyRoute('yo')
- environ = {'wsgiorg.routing_args':routing_args, 'bfg.route': route}
- result = traverser(environ)
- self.assertEqual(result['context'], model)
- self.assertEqual(result['view_name'], 'yo')
- self.assertEqual(result['subpath'], ['a', 'b','c'])
- self.assertEqual(result['traversed'], None)
- self.assertEqual(result['virtual_root'], model)
- self.assertEqual(result['virtual_root_path'], None)
-
- def test_with_path_info(self):
- model = DummyContext()
- traverser = self._makeOne(model)
- routing_args = ((), {'path_info':'foo/bar'})
- route = DummyRoute('yo')
- environ = {'wsgiorg.routing_args': routing_args, 'bfg.route': route,
- 'PATH_INFO':'/a/b/foo/bar', 'SCRIPT_NAME':''}
- result = traverser(environ)
- self.assertEqual(result['context'], model)
- self.assertEqual(result['view_name'], 'yo')
- self.assertEqual(result['subpath'], [])
- self.assertEqual(result['traversed'], None)
- self.assertEqual(result['virtual_root'], model)
- self.assertEqual(result['virtual_root_path'], None)
- self.assertEqual(environ['PATH_INFO'], '/foo/bar')
- self.assertEqual(environ['SCRIPT_NAME'], '/a/b')
-
- def test_with_path_info_PATH_INFO_w_extra_slash(self):
- model = DummyContext()
- traverser = self._makeOne(model)
- routing_args = ((), {'path_info':'foo/bar'})
- route = DummyRoute('yo')
- environ = {'wsgiorg.routing_args': routing_args, 'bfg.route':route,
- 'PATH_INFO':'/a/b//foo/bar', 'SCRIPT_NAME':''}
- traverser(environ)
- self.assertEqual(environ['PATH_INFO'], '/foo/bar')
- self.assertEqual(environ['SCRIPT_NAME'], '/a/b')
-
-class RoutesContextURLTests(unittest.TestCase):
- def _getTargetClass(self):
- from repoze.bfg.urldispatch import RoutesContextURL
- return RoutesContextURL
-
- def _makeOne(self, context, request):
- return self._getTargetClass()(context, request)
-
- def test_class_conforms_to_IContextURL(self):
- from zope.interface.verify import verifyClass
- from repoze.bfg.interfaces import IContextURL
- verifyClass(IContextURL, self._getTargetClass())
-
- def test_instance_conforms_to_IContextURL(self):
- from zope.interface.verify import verifyObject
- from repoze.bfg.interfaces import IContextURL
- verifyObject(IContextURL, self._makeOne(None, None))
-
- def test_get_virtual_root(self):
- context_url = self._makeOne(1,2)
- self.assertEqual(context_url.virtual_root(), 1)
-
- def test_call(self):
- from routes import Mapper
- mapper = Mapper(controller_scan=None, directory=None,
- explicit=True, always_scan=False)
- args = {'a':'1', 'b':'2', 'c':'3'}
- mapper.connect(':a/:b/:c')
- mapper.create_regs([])
- environ = {'SERVER_NAME':'example.com', 'wsgi.url_scheme':'http',
- 'SERVER_PORT':'80', 'wsgiorg.routing_args':((), args)}
- mapper.environ = environ
- from routes import request_config
- config = request_config()
- config.environ = environ
- config.mapper = mapper
- config.mapper_dict = args
- config.host = 'www.example.com'
- config.protocol = 'https'
- config.redirect = None
- request = DummyRequest()
- request.environ = environ
- context_url = self._makeOne(None, request)
- result = context_url()
- self.assertEqual(result, '/1/2/3')
-
class DummyContext(object):
""" """
class DummyRequest(object):
""" """
-class DummyRoute(object):
- def __init__(self, name):
- self.name = name
-
diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py
index e35944add..2fad91150 100644
--- a/repoze/bfg/tests/test_zcml.py
+++ b/repoze/bfg/tests/test_zcml.py
@@ -184,111 +184,54 @@ class TestViewDirective(unittest.TestCase):
regadapt_discriminator = ('view', IFoo, '', IDummy, IView)
self.assertEqual(regadapt['args'][2], (IFoo, IDummy))
- def test_adapted_class(self):
- from zope.interface import Interface
- import zope.component
-
- class IFoo(Interface):
+ def test_with_route_name(self):
+ class IFoo:
pass
- class IBar(Interface):
+ class IDummyRequest:
pass
-
- class AView(object):
- zope.component.adapts(IFoo, IBar)
-
- aview = AView()
-
context = DummyContext()
- self._callFUT(context, view=aview)
-
+ context.request_factories = {'foo':{None:{'interface':IDummyRequest}}}
+ view = lambda *arg: None
+ self._callFUT(context, 'repoze.view', IFoo, view=view, route_name='foo')
actions = context.actions
from repoze.bfg.interfaces import IView
+ from repoze.bfg.interfaces import IViewPermission
+ from repoze.bfg.security import ViewPermissionFactory
from repoze.bfg.zcml import handler
- self.assertEqual(len(actions), 1)
-
- regadapt = actions[0]
- regadapt_discriminator = ('view', IFoo, '', IBar, IView)
+ self.assertEqual(len(actions), 2)
+ permission = actions[0]
+ permission_discriminator = ('permission', IFoo, '', IDummyRequest,
+ IViewPermission)
+ self.assertEqual(permission['discriminator'], permission_discriminator)
+ self.assertEqual(permission['callable'], handler)
+ self.assertEqual(permission['args'][0], 'registerAdapter')
+ self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory))
+ self.assertEqual(permission['args'][1].permission_name, 'repoze.view')
+ self.assertEqual(permission['args'][2], (IFoo, IDummyRequest))
+ self.assertEqual(permission['args'][3], IViewPermission)
+ self.assertEqual(permission['args'][4], '')
+ self.assertEqual(permission['args'][5], None)
+
+ regadapt = actions[1]
+ regadapt_discriminator = ('view', IFoo, '', IDummyRequest, IView)
self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
self.assertEqual(regadapt['callable'], handler)
self.assertEqual(regadapt['args'][0], 'registerAdapter')
- self.assertEqual(regadapt['args'][1], aview)
- self.assertEqual(regadapt['args'][2], (IFoo, IBar))
+ self.assertEqual(regadapt['args'][1], view)
+ self.assertEqual(regadapt['args'][2], (IFoo, IDummyRequest))
self.assertEqual(regadapt['args'][3], IView)
self.assertEqual(regadapt['args'][4], '')
self.assertEqual(regadapt['args'][5], None)
- def test_adapted_function(self):
- from zope.interface import Interface
- import zope.component
-
- class IFoo(Interface):
- pass
- class IBar(Interface):
- pass
-
- @zope.component.adapter(IFoo, IBar)
- def aview(context, request):
- pass
- aview(None, None) # dead chicken for test coverage
-
+ def test_with_route_name_bad_order(self):
context = DummyContext()
- self._callFUT(context, view=aview)
-
- actions = context.actions
- from repoze.bfg.interfaces import IView
- from repoze.bfg.zcml import handler
-
- self.assertEqual(len(actions), 1)
-
- regadapt = actions[0]
- regadapt_discriminator = ('view', IFoo, '', IBar, IView)
-
- self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
- self.assertEqual(regadapt['callable'], handler)
- self.assertEqual(regadapt['args'][0], 'registerAdapter')
- self.assertEqual(regadapt['args'][1], aview)
- self.assertEqual(regadapt['args'][2], (IFoo, IBar))
- self.assertEqual(regadapt['args'][3], IView)
- self.assertEqual(regadapt['args'][4], '')
- self.assertEqual(regadapt['args'][5], None)
-
- def test_adapted_nonsense(self):
- from repoze.bfg.interfaces import IRequest
- from zope.interface import Interface
- import zope.component
-
- class IFoo(Interface):
- pass
- class IBar(Interface):
- pass
-
- @zope.component.adapter(IFoo) # too few arguments
- def aview(context, request):
- pass
- aview(None, None) # dead chicken for test coverage
-
- context = DummyContext()
- self._callFUT(context, view=aview)
-
- actions = context.actions
- from repoze.bfg.interfaces import IView
- from repoze.bfg.zcml import handler
-
- self.assertEqual(len(actions), 1)
-
- regadapt = actions[0]
- regadapt_discriminator = ('view', None, '', IRequest, IView)
-
- self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
- self.assertEqual(regadapt['callable'], handler)
- self.assertEqual(regadapt['args'][0], 'registerAdapter')
- self.assertEqual(regadapt['args'][1], aview)
- self.assertEqual(regadapt['args'][2], (None, IRequest))
- self.assertEqual(regadapt['args'][3], IView)
- self.assertEqual(regadapt['args'][4], '')
- self.assertEqual(regadapt['args'][5], None)
+ context.request_factories = {}
+ view = lambda *arg: None
+ from zope.configuration.exceptions import ConfigurationError
+ self.assertRaises(ConfigurationError, self._callFUT, context,
+ 'repoze.view', None, view, '', None, 'foo')
class TestRouteRequirementFunction(unittest.TestCase):
def _callFUT(self, context, attr, expr):
@@ -372,7 +315,11 @@ class TestConnectRouteFunction(unittest.TestCase):
self.assertEqual(D['_collection_name'], 'c')
self.assertEqual(D['_parent_resource'], pr)
self.assertEqual(D['conditions'], c)
- self.assertEqual(D['_factory'], foo)
+ route = mapper.matchlist[-1]
+ self.assertEqual(route._factory, foo)
+ self.assertEqual(route.request_factories,
+ directive.context.request_factories['thename'])
+
def test_condition_subdomain_true(self):
mapper = self._registerRoutesMapper()
@@ -469,49 +416,45 @@ class TestRoute(unittest.TestCase):
from repoze.bfg.zcml import Route
return Route
- def _makeOne(self, context, path, name, view, **kw):
- return self._getTargetClass()(context, path, name, view, **kw)
+ def _makeOne(self, context, path, name, **kw):
+ return self._getTargetClass()(context, path, name, **kw)
def test_defaults(self):
context = DummyContext()
- view = Dummy()
- route = self._makeOne(context, 'path', 'name', view)
+ route = self._makeOne(context, 'path', 'name')
self.assertEqual(route.path, 'path')
self.assertEqual(route.name, 'name')
- self.assertEqual(route.view, view)
self.assertEqual(route.requirements, {})
def test_parent_collection_name_missing(self):
context = DummyContext()
- view = Dummy()
from zope.configuration.exceptions import ConfigurationError
self.assertRaises(ConfigurationError, self._makeOne, context,
- 'path', 'name', view,
- parent_member_name='a')
+ 'path', 'name', parent_member_name='a')
def test_parent_collection_name_present(self):
context = DummyContext()
- view = Dummy()
- route = self._makeOne(context, 'path', 'name', view,
+ route = self._makeOne(context, 'path', 'name',
parent_member_name='a',
parent_collection_name='p')
self.assertEqual(route.parent_member_name, 'a')
self.assertEqual(route.parent_collection_name, 'p')
- def test_after(self):
+ def test_after_with_view(self):
from repoze.bfg.zcml import handler
from repoze.bfg.zcml import connect_route
- from repoze.bfg.interfaces import IRoutesContext
- from repoze.bfg.interfaces import IRequest
from repoze.bfg.interfaces import IView
context = DummyContext()
view = Dummy()
- route = self._makeOne(context, 'path', 'name', view)
+ route = self._makeOne(context, 'path', 'name', view=view)
route.after()
actions = context.actions
self.assertEqual(len(actions), 2)
+ factories = context.request_factories
+ request_iface = factories['name'][None]['interface']
+
view_action = actions[0]
view_callable = view_action['callable']
view_discriminator = view_action['discriminator']
@@ -519,13 +462,13 @@ class TestRoute(unittest.TestCase):
self.assertEqual(view_callable, handler)
self.assertEqual(len(view_discriminator), 5)
self.assertEqual(view_discriminator[0], 'view')
- self.assertEqual(view_discriminator[1], IRoutesContext)
- self.assertEqual(view_discriminator[2],'name')
- self.assertEqual(view_discriminator[3], IRequest)
+ self.assertEqual(view_discriminator[1], None)
+ self.assertEqual(view_discriminator[2],'')
+ self.assertEqual(view_discriminator[3], request_iface)
self.assertEqual(view_discriminator[4], IView)
self.assertEqual(view_args, ('registerAdapter', view,
- (IRoutesContext, IRequest), IView,
- 'name', None))
+ (None, request_iface), IView,
+ '', None))
route_action = actions[1]
route_callable = route_action['callable']
@@ -534,7 +477,32 @@ class TestRoute(unittest.TestCase):
self.assertEqual(route_callable, connect_route)
self.assertEqual(len(route_discriminator), 7)
self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'path')
+ self.assertEqual(route_discriminator[1], 'name')
+ self.assertEqual(route_discriminator[2],'{}')
+ self.assertEqual(route_discriminator[3], None)
+ self.assertEqual(route_discriminator[4], None)
+ self.assertEqual(route_discriminator[5], None)
+ self.assertEqual(route_discriminator[6], None)
+ self.assertEqual(route_args, (route,))
+
+ def test_after_without_view(self):
+ from repoze.bfg.zcml import connect_route
+
+ context = DummyContext()
+ view = Dummy()
+ route = self._makeOne(context, 'path', 'name')
+ route.after()
+ actions = context.actions
+ self.assertEqual(len(actions), 1)
+
+ route_action = actions[0]
+ route_callable = route_action['callable']
+ route_discriminator = route_action['discriminator']
+ route_args = route_action['args']
+ self.assertEqual(route_callable, connect_route)
+ self.assertEqual(len(route_discriminator), 7)
+ self.assertEqual(route_discriminator[0], 'route')
+ self.assertEqual(route_discriminator[1], 'name')
self.assertEqual(route_discriminator[2],'{}')
self.assertEqual(route_discriminator[3], None)
self.assertEqual(route_discriminator[4], None)
@@ -611,6 +579,7 @@ class TestBFGViewFunctionGrokker(unittest.TestCase):
obj.__for__ = Interface
obj.__view_name__ = 'foo.html'
obj.__request_type__ = IRequest
+ obj.__route_name__ = None
context = DummyContext()
result = grokker.grok('name', obj, context=context)
self.assertEqual(result, True)
@@ -729,13 +698,20 @@ class DummyRouteDirective:
if not 'requirements' in kw:
kw['requirements'] = {}
self.__dict__.update(kw)
+ self.context = DummyContext()
+ self.context.request_factories = {self.name:{}}
class DummyMapper:
def __init__(self):
self.connections = []
+ self.matchlist = []
def connect(self, *arg, **kw):
self.connections.append((arg, kw))
+ self.matchlist.append(DummyRoute())
+
+class DummyRoute:
+ pass
from zope.interface import Interface
class IDummy(Interface):
diff --git a/repoze/bfg/traversal.py b/repoze/bfg/traversal.py
index b8ba68faa..a0fdc5c71 100644
--- a/repoze/bfg/traversal.py
+++ b/repoze/bfg/traversal.py
@@ -489,10 +489,31 @@ class ModelGraphTraverser(object):
self.root = root
def __call__(self, environ, _marker=_marker):
- try:
- path = environ['PATH_INFO']
- except KeyError:
- path = '/'
+ if 'bfg.routes.matchdict' in environ:
+ # this request matched a Routes route
+ matchdict = environ['bfg.routes.matchdict']
+ if 'path_info' in matchdict:
+ # this is stolen from routes.middleware; if the route map
+ # has a *path_info capture, use it to influence the path
+ # info and script_name of the generated environment
+ oldpath = environ['PATH_INFO']
+ newpath = matchdict['path_info'] or ''
+ environ['PATH_INFO'] = newpath
+ if not environ['PATH_INFO'].startswith('/'):
+ environ['PATH_INFO'] = '/' + environ['PATH_INFO']
+ pattern = r'^(.*?)/' + re.escape(newpath) + '$'
+ environ['SCRIPT_NAME'] += re.sub(pattern, r'\1', oldpath)
+ if environ['SCRIPT_NAME'].endswith('/'):
+ environ['SCRIPT_NAME'] = environ['SCRIPT_NAME'][:-1]
+ path = matchdict.get('traverse', '/')
+ subpath = filter(None, matchdict.get('subpath', '').split('/'))
+ else:
+ # this request did not match a Routes route
+ subpath = []
+ try:
+ path = environ['PATH_INFO']
+ except KeyError:
+ path = '/'
try:
vroot_path_string = environ[VH_ROOT_KEY]
except KeyError:
@@ -535,8 +556,9 @@ class ModelGraphTraverser(object):
ob = next
i += 1
- return dict(context=ob, view_name=u'', subpath=[], traversed=traversed,
- virtual_root=vroot, virtual_root_path=vroot_path,
+ return dict(context=ob, view_name=u'', subpath=subpath,
+ traversed=traversed, virtual_root=vroot,
+ virtual_root_path=vroot_path,
root=self.root)
class TraversalContextURL(object):
@@ -584,8 +606,16 @@ class TraversalContextURL(object):
else:
if path.startswith(vroot_path):
path = path[len(vroot_path):]
-
- app_url = request.application_url # never ends in a slash
+
+ environ = request.environ
+ if 'bfg.routes.route' in environ:
+ matchdict = environ['bfg.routes.matchdict'].copy()
+ matchdict['traverse'] = path
+ route = environ['bfg.routes.route']
+ app_url = route.generate(**matchdict)
+ else:
+ app_url = request.application_url # never ends in a slash
+
return app_url + path
always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py
index 612843cfa..bfeae3333 100644
--- a/repoze/bfg/urldispatch.py
+++ b/repoze/bfg/urldispatch.py
@@ -1,72 +1,25 @@
-import re
-
-from zope.component import queryUtility
-
-from zope.interface import implements
-from zope.interface import alsoProvides
-from zope.interface import classProvides
-
from routes import Mapper
from routes import request_config
-from routes import url_for
-
-from repoze.bfg.interfaces import IContextNotFound
-from repoze.bfg.interfaces import IContextURL
-from repoze.bfg.interfaces import IRoutesContext
-from repoze.bfg.interfaces import IRoutesContextFactory
-from repoze.bfg.interfaces import ITraverser
-from repoze.bfg.interfaces import ITraverserFactory
-_marker = ()
-
-class DefaultRoutesContext(object):
- implements(IRoutesContext)
- def __init__(self, **kw):
- self.__dict__.update(kw)
-
-class RoutesContextNotFound(object):
- implements(IContextNotFound)
- def __init__(self, msg):
- self.msg = msg
-
-_notfound = RoutesContextNotFound(
- 'Routes context cannot be found and no fallback "get_root"')
+_marker = object()
class RoutesRootFactory(Mapper):
- """ The ``RoutesRootFactory`` is a wrapper for the root factory
- callable passed in to the repoze.bfg ``Router`` at initialization
- time. When it is instantiated, it wraps the root factory of an
- application in such a way that the `Routes
- <http://routes.groovie.org/index.html>`_ engine has the 'first
- crack' at resolving the current request URL to a repoze.bfg view.
- Any view that claims it is 'for' the interface
- ``repoze.bfg.interfaces.IRoutesContext`` will be called if its
- name matches the Routes route ``name`` name for the match. It
- will be passed a context object that has attributes that are
- present as Routes match arguments dictionary keys. If no Routes
- route matches the current request, the 'fallback' get_root is
- called."""
- decorate_context = True
- def __init__(self, get_root=None, **kw):
- self.get_root = get_root
+ def __init__(self, default_root_factory, **kw):
+ self.default_root_factory = default_root_factory
kw['controller_scan'] = None
kw['always_scan'] = False
kw['directory'] = None
kw['explicit'] = True
Mapper.__init__(self, **kw)
self._regs_created = False
- self.default_context_factory = DefaultRoutesContext
def has_routes(self):
return bool(self.matchlist)
def connect(self, *arg, **kw):
- # we need to deal with our custom attributes specially :-(
- factory = None
- if '_factory' in kw:
- factory = kw.pop('_factory')
result = Mapper.connect(self, *arg, **kw)
- self.matchlist[-1]._factory = factory
+ route = self.matchlist[-1]
+ route._factory = None # overridden by ZCML
return result
def __call__(self, environ):
@@ -89,78 +42,11 @@ class RoutesRootFactory(Mapper):
config.host = environ.get('HTTP_HOST', environ['SERVER_NAME'])
config.protocol = environ['wsgi.url_scheme']
config.redirect = None
- kw = {}
- for k, v in args.items():
- # Routes "helpfully" converts default parameter names
- # into Unicode; these can't be used as attr names
- if k.__class__ is unicode:
- k = k.encode('utf-8')
- kw[k] = v
- factory = route._factory
- if factory is None:
- context = self.default_context_factory(**kw)
- if self.decorate_context:
- alsoProvides(context, IRoutesContext)
- else:
- context = factory(**kw)
- alsoProvides(context, IRoutesContext)
- environ['wsgiorg.routing_args'] = ((), kw)
- environ['bfg.route'] = route
- return context
-
- if self.get_root is None:
- return _notfound
-
- return self.get_root(environ)
-
-class RoutesModelTraverser(object):
- classProvides(ITraverserFactory)
- implements(ITraverser)
- def __init__(self, context):
- self.context = context
-
- def __call__(self, environ):
- route = environ['bfg.route']
- match = environ['wsgiorg.routing_args'][1]
-
- subpath = match.get('subpath', [])
- if subpath:
- subpath = filter(None, subpath.split('/'))
-
- if 'path_info' in match:
- # this is stolen from routes.middleware; if the route map
- # has a *path_info capture, use it to influence the path
- # info and script_name of the generated environment
- oldpath = environ['PATH_INFO']
- newpath = match['path_info'] or ''
- environ['PATH_INFO'] = newpath
- if not environ['PATH_INFO'].startswith('/'):
- environ['PATH_INFO'] = '/' + environ['PATH_INFO']
- pattern = r'^(.*?)/' + re.escape(newpath) + '$'
- environ['SCRIPT_NAME'] += re.sub(pattern, r'\1', oldpath)
- if environ['SCRIPT_NAME'].endswith('/'):
- environ['SCRIPT_NAME'] = environ['SCRIPT_NAME'][:-1]
-
- return dict(context=self.context, view_name=route.name,
- subpath=subpath, traversed=None, virtual_root=self.context,
- virtual_root_path=None, root=self.context)
-
-class RoutesContextURL(object):
- """ The IContextURL adapter used to generate URLs for a context
- object obtained via Routes URL dispatch. This implementation
- juses the ``url_for`` Routes API to generate a URL based on
- ``environ['wsgiorg.routing_args']``. Routes context objects,
- unlike traversal-based context objects, cannot have a virtual root
- that differs from its physical root; furthermore, the physical
- root of a Routes context is always itself, so the ``virtual_root``
- function returns the context of this adapter unconditionally."""
- implements(IContextURL)
- def __init__(self, context, request):
- self.context = context
- self.request = request
+ environ['wsgiorg.routing_args'] = ((), args)
+ environ['bfg.routes.route'] = route
+ environ['bfg.routes.matchdict'] = args
+ factory = route._factory or self.default_root_factory
+ return factory(environ)
- def virtual_root(self):
- return self.context
+ return self.default_root_factory(environ)
- def __call__(self):
- return url_for(**self.request.environ['wsgiorg.routing_args'][1])
diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py
index e6cd11939..7eb996a8d 100644
--- a/repoze/bfg/view.py
+++ b/repoze/bfg/view.py
@@ -146,7 +146,7 @@ class bfg_view(object):
from repoze.bfg.interfaces import IRequest
@bfg_view(name='my_view', request_type=IRequest, for_=IMyModel,
- permission='read'))
+ permission='read', route_name='site1'))
def my_view(context, request):
return render_template_to_response('templates/my.pt')
@@ -157,6 +157,7 @@ class bfg_view(object):
view='.views.my_view'
name='my_view'
permission='read'
+ route_name='site1'
/>
If ``name`` is not supplied, the empty string is used (implying
@@ -171,6 +172,10 @@ class bfg_view(object):
If ``permission`` is not supplied, no permission is registered for
this view (it's accessible by any caller).
+ If ``route_name`` is not supplied, the view declaration is considered
+ to be made against the 'default' route (the route which matches when
+ no ZCML-defined route matches the request).
+
Any individual or all parameters can be omitted. The simplest
bfg_view declaration then becomes::
@@ -217,11 +222,13 @@ class bfg_view(object):
<scan package="."/>
"""
- def __init__(self, name='', request_type=None, for_=None, permission=None):
+ def __init__(self, name='', request_type=None, for_=None, permission=None,
+ route_name=None):
self.name = name
self.request_type = request_type
self.for_ = for_
self.permission = permission
+ self.route_name = route_name
def __call__(self, wrapped):
_bfg_view = wrapped
@@ -244,5 +251,6 @@ class bfg_view(object):
_bfg_view.__for__ = self.for_
_bfg_view.__view_name__ = self.name
_bfg_view.__request_type__ = self.request_type
+ _bfg_view.__route_name__ = self.route_name
return _bfg_view
diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py
index bd12e926c..eeb8dfc32 100644
--- a/repoze/bfg/zcml.py
+++ b/repoze/bfg/zcml.py
@@ -3,7 +3,6 @@ import types
from zope.configuration import xmlconfig
-from zope.component import adaptedBy
from zope.component import getSiteManager
from zope.component import queryUtility
@@ -19,13 +18,12 @@ from zope.interface import implements
from zope.schema import Bool
from zope.schema import TextLine
-from repoze.bfg.interfaces import IRequest
from repoze.bfg.interfaces import IRoutesMapper
-from repoze.bfg.interfaces import IRoutesContext
from repoze.bfg.interfaces import IViewPermission
from repoze.bfg.interfaces import IView
-from repoze.bfg.request import HTTP_METHOD_INTERFACES
+from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES
+from repoze.bfg.request import named_request_factories
from repoze.bfg.security import ViewPermissionFactory
@@ -35,10 +33,6 @@ def handler(methodName, *args, **kwargs):
method = getattr(getSiteManager(), methodName)
method(*args, **kwargs)
-class Uncacheable(object):
- """ Include in discriminators of actions which are not cacheable;
- this class only exists for backwards compatibility (<0.8.1)"""
-
def view(
_context,
permission=None,
@@ -46,38 +40,29 @@ def view(
view=None,
name="",
request_type=None,
+ route_name=None,
cacheable=True, # not used, here for b/w compat < 0.8
):
if not view:
raise ConfigurationError('"view" attribute was not specified')
- # adapts() decorations may be used against either functions or
- # class instances
- if inspect.isfunction(view):
- adapted_by = adaptedBy(view)
+ if route_name is None:
+ request_factories = DEFAULT_REQUEST_FACTORIES
else:
- adapted_by = adaptedBy(type(view))
-
- if adapted_by is not None:
try:
- if for_ is None:
- for_, _ = adapted_by
- if request_type is None:
- _, request_type = adapted_by
- except ValueError:
- # the component adaptation annotation does not conform to
- # the view specification; we ignore it.
- pass
-
- if request_type is None:
- request_type = IRequest
-
- elif isinstance(request_type, basestring):
- if request_type in HTTP_METHOD_INTERFACES:
- request_type = HTTP_METHOD_INTERFACES[request_type]
- else:
- request_type = _context.resolve(request_type)
+ request_factories = _context.request_factories[route_name]
+ except KeyError:
+ raise ConfigurationError(
+ 'Unknown route_name "%s". <route> definitions must be ordered '
+ 'before the view definition which mentions the route\'s name '
+ 'within ZCML (or before the "scan" directive is invoked '
+ 'within a bfg_view decorator).' % route_name)
+
+ if request_type in request_factories:
+ request_type = request_factories[request_type]['interface']
+ else:
+ request_type = _context.resolve(request_type)
if inspect.isclass(view):
# If the object we've located is a class, turn it into a
@@ -110,8 +95,7 @@ def view(
discriminator = ('view', for_, name, request_type, IView),
callable = handler,
args = ('registerAdapter',
- view, (for_, request_type), IView, name,
- _context.info),
+ view, (for_, request_type), IView, name, _context.info),
)
class IViewDirective(Interface):
@@ -148,7 +132,10 @@ class IViewDirective(Interface):
required=False
)
-PVERSION = 1
+ route_name = TextLine(
+ title = u'The route that must match for this view to be used',
+ required = False)
+
def zcml_configure(name, package):
context = zope.configuration.config.ConfigurationMachine()
@@ -187,9 +174,11 @@ class BFGViewFunctionGrokker(martian.InstanceGrokker):
for_ = obj.__for__
name = obj.__view_name__
request_type = obj.__request_type__
+ route_name = obj.__route_name__
context = kw['context']
view(context, permission=permission, for_=for_,
- view=obj, name=name, request_type=request_type)
+ view=obj, name=name, request_type=request_type,
+ route_name=route_name)
return True
return False
@@ -209,7 +198,7 @@ class IRouteDirective(Interface):
"""
name = TextLine(title=u'name', required=True)
path = TextLine(title=u'path', required=True)
- view = GlobalObject(title=u'view', required=True)
+ view = GlobalObject(title=u'view', required=False)
permission = TextLine(title=u'permission', required=False)
factory = GlobalObject(title=u'context factory', required=False)
minimize = Bool(title=u'minimize', required=False)
@@ -275,10 +264,12 @@ def connect_route(directive):
if conditions:
kw['conditions'] = conditions
- if directive.factory:
- kw['_factory'] = directive.factory
-
- return mapper.connect(*args, **kw)
+ result = mapper.connect(*args, **kw)
+ route = mapper.matchlist[-1]
+ route._factory = directive.factory
+ context = directive.context
+ route.request_factories = context.request_factories[directive.name]
+ return result
class Route(zope.configuration.config.GroupingContextDecorator):
""" Handle ``route`` ZCML directives
@@ -305,13 +296,12 @@ class Route(zope.configuration.config.GroupingContextDecorator):
implements(zope.configuration.config.IConfigurationContext,
IRouteDirective)
- def __init__(self, context, path, name, view, **kw):
+ def __init__(self, context, path, name, **kw):
self.validate(**kw)
self.requirements = {} # mutated by subdirectives
self.context = context
self.path = path
self.name = name
- self.view = view
self.__dict__.update(**kw)
def validate(self, **kw):
@@ -324,16 +314,27 @@ class Route(zope.configuration.config.GroupingContextDecorator):
'specified together')
def after(self):
- view(self.context, self.permission, IRoutesContext, self.view,
- self.name, None)
+ context = self.context
+ name = self.name
+ if not hasattr(context, 'request_factories'):
+ context.request_factories = {}
+ context.request_factories[name] = named_request_factories(name)
+
+ if self.view:
+ view(context, self.permission, None, self.view, '',
+ self.request_type, name)
method = self.condition_method or self.request_type
self.context.action(
- discriminator = ('route', self.path, repr(self.requirements),
+ discriminator = ('route', self.name, repr(self.requirements),
method, self.condition_subdomain,
self.condition_function, self.subdomains),
callable = connect_route,
args = (self,),
)
+class Uncacheable(object):
+ """ Include in discriminators of actions which are not cacheable;
+ this class only exists for backwards compatibility (<0.8.1)"""
+