Skip to content
This repository has been archived by the owner on Feb 10, 2023. It is now read-only.

Commit

Permalink
- The bobo.Application constructor now accepts objects as well as
Browse files Browse the repository at this point in the history
  strings for the bobo options. This makes application definition from
  Python a bit cleaner.

- A new ``bobo_handle_exceptions`` options makes it easy to tell bobo
  not to catch application exceptions.  This is helpful is you're
  using WSGI middleware to handle exceptions.

- The object provided to ``bobo_errors`` option can now provide a
  subset of error handlers.
  • Loading branch information
Jim Fulton committed Apr 6, 2014
1 parent 88f1c38 commit f01f704
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 27 deletions.
14 changes: 14 additions & 0 deletions bobo/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ To learn more. visit: http://bobo.digicool.com
Change History
==============

2.1.0 2014-04-06
----------------

- The ``bobo.Application`` constructor now accepts objects as well as
strings for the bobo options. This makes application definition from
Python a bit cleaner.

- A new ``bobo_handle_exceptions`` options makes it easy to tell bobo
not to catch application exceptions. This is helpful is you're
using WSGI middleware to handle exceptions.

- The object provided to ``bobo_errors`` option can now provide a
subset of error handlers.

2.0.0 2014-02-09
----------------

Expand Down
78 changes: 54 additions & 24 deletions bobo/src/bobo.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class Application:
Bobo also used the following options:
bobo_configuration
bobo_configure
Specify one or more (whitespace-delimited) callables to be
called with the configuration data passed to the application.
Expand Down Expand Up @@ -155,6 +155,15 @@ class Application:
exc_info. This method is optional. Bobo's default behavior
is to simply re-raise the exception.
bobo_handle_exceptions
Boolean indicating whether bobo should catch application exceptions.
This defaults to True. It should be set to false if WSGI
middleware should handle exceptions.
If provides as a string (through configuration), it should be
either 'true' or 'false'.
"""

def __init__(self, DEFAULT=None, **config):
Expand All @@ -164,28 +173,44 @@ def __init__(self, DEFAULT=None, **config):
config = DEFAULT

self.config = config
for name in filter(None, _uncomment(config, 'bobo_configure').split()):
_get_global(name)(config)

bobo_errors = _uncomment(config, 'bobo_errors')
if bobo_errors:
if ':' in bobo_errors:
bobo_errors = _get_global(bobo_errors)
bobo_configure = config.get('bobo_configure', '')
if isinstance(bobo_configure, six.string_types):
bobo_configure = (
_get_global(name)
for name in filter(None, _uncomment(bobo_configure).split())
)
for configure in bobo_configure:
configure(config)

bobo_errors = config.get('bobo_errors')
if bobo_errors is not None:
if isinstance(bobo_errors, six.string_types):
bobo_errors = _uncomment(bobo_errors)
if ':' in bobo_errors:
bobo_errors = _get_global(bobo_errors)
else:
bobo_errors = _import(bobo_errors)

_maybe_copy(bobo_errors, 'not_found', self)
_maybe_copy(bobo_errors, 'method_not_allowed', self)
_maybe_copy(bobo_errors, 'missing_form_variable', self)
_maybe_copy(bobo_errors, 'exception', self)

bobo_resources = config.get('bobo_resources', '')
if isinstance(bobo_resources, six.string_types):
bobo_resources = _uncomment(bobo_resources, True)
if bobo_resources:
self.handlers = _route_config(bobo_resources)
else:
bobo_errors = _import(bobo_errors)
self.not_found = bobo_errors.not_found
self.method_not_allowed = bobo_errors.method_not_allowed
self.missing_form_variable = bobo_errors.missing_form_variable
try:
self.exception = bobo_errors.exception
except AttributeError:
pass

bobo_resources = _uncomment(config, 'bobo_resources', True)
if bobo_resources:
self.handlers = _route_config(bobo_resources)
raise ValueError("Missing bobo_resources option.")
else:
raise ValueError("Missing bobo_resources option.")
self.handlers = [r.bobo_response for r in bobo_resources]

handle_exceptions = config.get('bobo_handle_exceptions', True)
if isinstance(handle_exceptions, six.string_types):
handle_exceptions = handle_exceptions.lower() == 'true'
self.reraise_exceptions = not handle_exceptions

def bobo_response(self, request, path, method):
try:
Expand All @@ -210,7 +235,9 @@ def bobo_response(self, request, path, method):
except bbbbad_errors:
raise
except Exception as exc:
if request.environ.get("x-wsgiorg.throw_errors"):
if (self.reraise_exceptions or
request.environ.get("x-wsgiorg.throw_errors")
):
raise
return self.exception(request, method, sys.exc_info())

Expand Down Expand Up @@ -388,16 +415,19 @@ def bobo_response(request, path, method):

return bobo_response

def _uncomment(config, name, split=False):
str = config.get(name, '')
def _uncomment(text, split=False):
result = list(filter(None, (
line.split('#', 1)[0].strip()
for line in str.strip().split('\n')
for line in text.strip().split('\n')
)))
if split:
return result
return '\n'.join(result)

def _maybe_copy(ob1, name, ob2):
if hasattr(ob1, name):
setattr(ob2, name, getattr(ob1, name))

class _MultiResource(list):
def bobo_response(self, request, path, method):
for resource in self:
Expand Down
95 changes: 92 additions & 3 deletions bobodoctestumentation/src/bobodoctestumentation/more.txt
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,79 @@ This example also illustrates that, rather than passing strings to the
``resources``, ``reroute`` and ``preroute`` functions, we can pass
objects directly.

Creating bobo-based WSGI applications from Python
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Usually, bobo applications are created using the bobo development
server or through a PasteDeployment configuration. You can also
create applications in Python using the ``bobo.Application``
constructor. You call the constructor with keyword arguments:

bobo_resources
The bobo resources to be used in the application

This is either a string defining resources, or an iterable of
modules or resource objects.

bobo_configuration
A list of configuration functions.

This is either a string consistning whitespace-delimited list of
configuration callable names, or an iterable of callables. The
callables will be called with the keyword arguments passed to
``bobo.Application``. This allows you to pass configuration options
when defining an application.

bobo_errors
A custom error-handler object. This is either a string name, of the
form ``'modulename:expression'``, or a Python object defining one or
more of the error handling functions.

bobo_handle_exceptions
A boolean flag indicating whether bobo should handle uncaught
application exceptions. If set to ``False`` or ``'false'``, then
bobo won't catch exceptions. This is useful if you want middleware
to handle exceptions.

Here's a somewhat contrived example that illustrates creating an
application object from Python, passing objects rather than strings::

import bobo, webob

def config(config):
global configured_name
configured_name = config['name']

@bobo.get('/hi')
def hi():
return configured_name

class Errors:
@classmethod
def not_found(self, request, method):
return webob.Response("missing", 404)

app = bobo.Application(
bobo_resources=[hi],
bobo_configure=[config],
bobo_errors=Errors,
name="welcome",
)

.. -> src

>>> exec(src)
>>> app = webtest.TestApp(app)
>>> print(app.get('/hi'))
Response: 200 OK
Content-Type: text/html; charset=UTF-8
welcome

>>> print(app.get('/ho', status=404))
Response: 404 Not Found
Content-Type: text/html; charset=UTF-8
missing

Error response generation
-------------------------

Expand Down Expand Up @@ -742,9 +815,11 @@ use an expression to specify the errors object.
Uncaught exceptions
~~~~~~~~~~~~~~~~~~~

Normally, bobo does not let uncaught exceptions propagate; however,
if the `x-wsgiorg.throw_errors` key is present in the environment,
any uncaught exceptions will be raised.
Normally, bobo does not let uncaught exceptions propagate; however, if
the ``bobo_handle_exceptions`` option is set to ``False`` (or
``'false'``) or if a request environment has the key
`x-wsgiorg.throw_errors`, any uncaught exceptions will be raised.
This is useful if you want WSGI middleware to handle exceptions.

If you want to provide custom handling of uncaught exceptions,
you can include an ``exception`` method in the object you
Expand Down Expand Up @@ -806,6 +881,20 @@ give to ``bobo_errors``.
...
TypeError: ...

>>> app = webtest.TestApp(bobo.Application(
... bobo_resources='badapp', bobo_handle_exceptions=False))
>>> app.get("/bad.html")
Traceback (most recent call last):
...
TypeError: ...

>>> app = webtest.TestApp(bobo.Application(
... bobo_resources='badapp', bobo_handle_exceptions='false'))
>>> app.get("/bad.html")
Traceback (most recent call last):
...
TypeError: ...

>>> app = webtest.TestApp(bobo.Application(
... bobo_resources='badapp', bobo_errors='errorsample2:Errors()'))
>>> print(app.get('/bad.html', status=500).text)
Expand Down

0 comments on commit f01f704

Please sign in to comment.