weblayer.auth provides TrivialAuthenticationManager, an implementation of IAuthenticationManager.
The implementation is deliberately trivial as it’s envisaged that a bespoke application that requires authentication will:
A very simple IAuthenticationManager implementation that uses the webob.Request request.remote_user attribute which, under the WebOb hood, is derived from request.environ['REMOTE_USER'], which is the standard place for authentication middleware to put a user id.
TrivialAuthenticationManager is thus perfectly usable in many cases with is_authenticated returning True or False appropriately and current_user returning a user id if present.
Returns request.remote_user:
>>> from mock import Mock
>>> request = Mock()
>>> request.remote_user = 'joe'
>>> am = TrivialAuthenticationManager(request)
>>> am.current_user
‘joe’
Is there a remote_user in the request?
>>> from mock import Mock
>>> request = Mock()
If remote_user is None, returns False:
>>> request.remote_user = None
>>> am = TrivialAuthenticationManager(request)
>>> am.is_authenticated
False
Otherwise returns True:
>>> request.remote_user = 'foo'
>>> am = TrivialAuthenticationManager(request)
>>> am.is_authenticated
True
weblayer.base provides default IRequest and IResponse implementations, based purely on webob.Request and webob.Response.
The two implementations, Request and Response, add no functionality to their WebOb superclasses beyond declaring that they implement the IRequest and IResponse interfaces.
IRequest implementation using webob.Request.
Like .str_GET, but may decode values and keys
Like .str_POST, but may decode values and keys
Gets and sets the ‘HTTP_ACCEPT’ key in the environment. For more information on Accept see section 14.1. Converts it as a MIME Accept.
Gets and sets the ‘HTTP_ACCEPT_CHARSET’ key in the environment. For more information on Accept-Charset see section 14.2. Converts it as a accept header.
Gets and sets the ‘HTTP_ACCEPT_ENCODING’ key in the environment. For more information on Accept-Encoding see section 14.3. Converts it as a accept header.
Gets and sets the ‘HTTP_ACCEPT_LANGUAGE’ key in the environment. For more information on Accept-Language see section 14.4. Converts it as a accept header.
The URL including SCRIPT_NAME (no PATH_INFO or query string)
Gets and sets the ‘HTTP_AUTHORIZATION’ key in the environment. For more information on Authorization see section 14.8. Converts it as a <function parse_auth at 0x27e95f0> and <function serialize_auth at 0x27e9630>.
Create a blank request environ (and Request wrapper) with the given path (path should be urlencoded), and any keys from environ.
The path will become path_info, with any query string split off and used.
All necessary keys will be added to the environ, but the values you pass in will take precedence. If you pass in base_url then wsgi.url_scheme, HTTP_HOST, and SCRIPT_NAME will be filled in from that value.
Any extra keyword will be passed to __init__ (e.g., decode_param_names).
Return the content of the request body.
Access the body of the request (wsgi.input) as a file-like object.
If you set this value, CONTENT_LENGTH will also be updated (either set to -1, 0 if you delete the attribute, or if you set the attribute to a string then the length of the string).
Call the given WSGI application, returning (status_string, headerlist, app_iter)
Be sure to call app_iter.close() if it’s there.
If catch_exc_info is true, then returns (status_string, headerlist, app_iter, exc_info), where the fourth item may be None, but won’t be if there was an exception. If you don’t do this and there was an exception, the exception will be raised directly.
Get the charset of the request.
If the request was sent with a charset parameter on the Content-Type, that will be used. Otherwise if there is a default charset (set during construction, or as a class attribute) that will be returned. Otherwise None.
Setting this property after request instantiation will always update Content-Type. Deleting the property updates the Content-Type to remove any charset parameter (if none exists, then deleting the property will do nothing, and there will be no error).
Gets and sets the ‘CONTENT_LENGTH’ key in the environment. For more information on CONTENT_LENGTH see section 14.13. Converts it as a int.
Return the content type, but leaving off any parameters (like charset, but also things like the type in application/atom+xml; type=entry)
If you set this property, you can include parameters, or if you don’t include any parameters in the value then existing parameters will be preserved.
Like .str_cookies, but may decode values and keys
Copy the request and environment object.
This only does a shallow copy, except of wsgi.input
Copies the body, in cases where it might be shared with another request object and that is not desired.
This copies the body in-place, either into a StringIO object or a temporary file.
Copies the request and environment object, but turning this request into a GET along the way. If this was a POST request (or any other verb) then it becomes GET, and the request body is thrown away.
Gets and sets the ‘HTTP_DATE’ key in the environment. For more information on Date see section 14.8. Converts it as a HTTP date.
Reads a request from a file-like object (it must implement .read(size) and .readline()).
It will read up to the end of the request, not the end of the file.
This reads the request as represented by str(req); it may not read every valid HTTP request properly.
Like .call_application(application), except returns a response object with .status, .headers, and .body attributes.
This will use self.ResponseClass to figure out the class of the response object to return.
All the request headers as a case-insensitive dictionary-like object.
Host name provided in HTTP_HOST, with fall-back to SERVER_NAME
The URL through the host (no path)
Gets and sets the ‘HTTP_IF_MATCH’ key in the environment. For more information on If-Match see section 14.24. Converts it as a Etag.
Gets and sets the ‘HTTP_IF_MODIFIED_SINCE’ key in the environment. For more information on If-Modified-Since see section 14.25. Converts it as a HTTP date.
Gets and sets the ‘HTTP_IF_NONE_MATCH’ key in the environment. For more information on If-None-Match see section 14.26. Converts it as a Etag.
Gets and sets the ‘HTTP_IF_RANGE’ key in the environment. For more information on If-Range see section 14.27. Converts it as a IfRange object.
Gets and sets the ‘HTTP_IF_UNMODIFIED_SINCE’ key in the environment. For more information on If-Unmodified-Since see section 14.28. Converts it as a HTTP date.
Returns a boolean if X-Requested-With is present and XMLHttpRequest
Note: this isn’t set by every XMLHttpRequest request, it is only set if you are using a Javascript library that sets it (or you set the header yourself manually). Currently Prototype and jQuery are known to set this header.
This forces environ['wsgi.input'] to be seekable. That is, if it doesn’t have a seek method already, the content is copied into a StringIO or temporary file.
The choice to copy to StringIO is made from self.request_body_tempfile_limit
Gets and sets the ‘HTTP_MAX_FORWARDS’ key in the environment. For more information on Max-Forwards see section 14.31. Converts it as a int.
Gets and sets the ‘REQUEST_METHOD’ key in the environment.
Like .str_params, but may decode values and keys
The path of the request, without host or query string
Gets and sets the ‘PATH_INFO’ key in the environment.
Returns the next segment on PATH_INFO, or None if there is no next segment. Doesn’t modify the environment.
‘Pops’ off the next segment of PATH_INFO, pushing it onto SCRIPT_NAME, and returning the popped segment. Returns None if there is nothing left on PATH_INFO.
Does not return '' when there’s an empty segment (like /path//path); these segments are just ignored.
Optional pattern argument is a regexp to match the return value before returning. If there is no match, no changes are made to the request and None is returned.
The path of the request, without host but with query string
The URL including SCRIPT_NAME and PATH_INFO, but not QUERY_STRING
Wraps a descriptor, with a deprecation warning or error
Gets and sets the ‘HTTP_PRAGMA’ key in the environment. For more information on Pragma see section 14.32.
Gets and sets the ‘QUERY_STRING’ key in the environment.
Wraps a descriptor, with a deprecation warning or error
Gets and sets the ‘HTTP_RANGE’ key in the environment. For more information on Range see section 14.35. Converts it as a Range object.
Gets and sets the ‘HTTP_REFERER’ key in the environment. For more information on Referer see section 14.36.
Gets and sets the ‘HTTP_REFERER’ key in the environment. For more information on Referer see section 14.36.
Resolve other_url relative to the request URL.
If to_application is True, then resolve it relative to the URL with only SCRIPT_NAME
Gets and sets the ‘REMOTE_ADDR’ key in the environment.
Gets and sets the ‘REMOTE_USER’ key in the environment.
Remove headers that make the request conditional.
These headers can cause the response to be 304 Not Modified, which in some cases you may not want to be possible.
This does not remove headers like If-Match, which are used for conflict detection.
Gets and sets the ‘wsgi.url_scheme’ key in the environment.
Gets and sets the ‘SCRIPT_NAME’ key in the environment.
Gets and sets the ‘SERVER_NAME’ key in the environment.
Gets and sets the ‘SERVER_PORT’ key in the environment. Converts it as a int.
Return a MultiDict containing all the variables from the QUERY_STRING.
Return a MultiDict containing all the variables from a form request. Returns an empty dict-like object for non-form requests.
Form requests are typically POST requests, however PUT requests with an appropriate Content-Type are also supported.
Return a plain dictionary of cookies as found in the request.
A dictionary-like object containing both the parameters from the query string and request body.
Wraps a descriptor, with a deprecation warning or error
Wraps a descriptor, with a deprecation warning or error
upath_property(‘PATH_INFO’)
The full request URL, including QUERY_STRING
Return any positional variables matched in the URL.
Takes values from environ['wsgiorg.routing_args']. Systems like routes set this value.
Return any named variables matched in the URL.
Takes values from environ['wsgiorg.routing_args']. Systems like routes set this value.
upath_property(‘SCRIPT_NAME’)
Gets and sets the ‘HTTP_USER_AGENT’ key in the environment. For more information on User-Agent see section 14.43.
IResponse implementation using webob.Response.
Gets and sets and deletes the Accept-Ranges header. For more information on Accept-Ranges see section 14.5.
Gets and sets and deletes the Age header. For more information on Age see section 14.6. Converts it as a int.
Gets and sets and deletes the Allow header. For more information on Allow see section 14.7. Converts it as a list.
Returns the app_iter of the response.
If body was set, this will create an app_iter from that body (a single-item list)
Return a new app_iter built from the response app_iter, that serves up only the given start:stop range.
The body of the response, as a str. This will read in the entire app_iter if necessary.
A file-like object that can be used to write to the body. If you passed in a list app_iter, that app_iter will be modified by writes.
Get/set the charset (in the Content-Type)
Like the normal __call__ interface, but checks conditional headers:
Gets and sets and deletes the Content-Disposition header. For more information on Content-Disposition see section 19.5.1.
Gets and sets and deletes the Content-Encoding header. For more information on Content-Encoding see section 14.11.
Gets and sets and deletes the Content-Language header. For more information on Content-Language see section 14.12. Converts it as a list.
Gets and sets and deletes the Content-Length header. For more information on Content-Length see section 14.17. Converts it as a int.
Gets and sets and deletes the Content-Location header. For more information on Content-Location see section 14.14.
Gets and sets and deletes the Content-MD5 header. For more information on Content-MD5 see section 14.14.
Gets and sets and deletes the Content-Range header. For more information on Content-Range see section 14.16. Converts it as a ContentRange object.
Get/set the Content-Type header (or None), without the charset or any parameters.
If you include parameters (or ; at all) when setting the content_type, any existing parameters will be deleted; otherwise they will be preserved.
A dictionary of all the parameters in the content type.
(This is not a view, set to change, modifications of the dict would not be applied otherwise)
Makes a copy of the response
Gets and sets and deletes the Date header. For more information on Date see section 14.18. Converts it as a HTTP date.
Delete a cookie from the client. Note that path and domain must match how the cookie was originally set.
This sets the cookie to the empty string, and max_age=0 so that it should expire immediately.
Encode the content with the given encoding (only gzip and identity are supported).
Get/set the request environ associated with this response, if any.
Gets and sets and deletes the ETag header. For more information on ETag see section 14.19. Converts it as a Entity tag.
Gets and sets and deletes the Expires header. For more information on Expires see section 14.21. Converts it as a HTTP date.
Reads a response from a file-like object (it must implement .read(size) and .readline()).
It will read up to the end of the response, not the end of the file.
This reads the response as represented by str(resp); it may not read every valid HTTP response properly. Responses must have a Content-Length
The list of response headers
The headers in a dictionary-like object
Gets and sets and deletes the Last-Modified header. For more information on Last-Modified see section 14.29. Converts it as a HTTP date.
Gets and sets and deletes the Location header. For more information on Location see section 14.30.
Generate an etag for the response object using an MD5 hash of the body (the body parameter, or self.body if not given)
Sets self.etag If set_content_md5 is True sets self.content_md5 as well
Merge the cookies that were set on this response with the given resp object (which can be any WSGI application).
If the resp is a webob.Response object, then the other object will be modified in-place.
Gets and sets and deletes the Pragma header. For more information on Pragma see section 14.32.
Return the request associated with this response if any.
Gets and sets and deletes the Retry-After header. For more information on Retry-After see section 14.37. Converts it as a HTTP date or delta seconds.
Gets and sets and deletes the Server header. For more information on Server see section 14.38.
Set (add) a cookie for the response
The status string
Wraps a descriptor, with a deprecation warning or error
The status as an integer
Alias for unicode_body
Get/set the unicode value of the body (using the charset of the Content-Type)
Unset a cookie with the given name (remove it from the response).
Gets and sets and deletes the Vary header. For more information on Vary see section 14.44. Converts it as a list.
Gets and sets and deletes the WWW-Authenticate header. For more information on WWW-Authenticate see section 14.47. Converts it as a <function parse_auth at 0x27e95f0> and <function serialize_auth at 0x27e9630>.
weblayer.bootstrap provides Bootstrapper, a helper class that simplifies setting up and registering weblayer components. To bootstrap a default configuration, pass a dictionary of settings and list of url mappings to the Bootstrapper constructor:
>>> bootstrapper = Bootstrapper(settings={}, url_mapping=[])
Then call the bootstrapper instance to register components and get ISettings and IPathRouter utilities. By default, the bootstrapper uses RequirableSettings as its ISettings implementation and performs a venusian scan of the weblayer package to require settings declared explicitly with require_setting(). This means that you must pass the required settings into the Bootstrapper constructor when instantiating the bootstrapper or get a KeyError:
>>> settings, path_router = bootstrapper()
Traceback (most recent call last):
...
KeyError: u'Required setting `template_directories` () is missing,
Required setting `static_files_path` () is missing,
Required setting `cookie_secret` (a long, random sequence
of bytes) is missing'
Whereas if the required settings are provided, all is well:
>>> config = {
... 'cookie_secret': '...',
... 'static_files_path': '/var/www/static',
... 'template_directories': ['templates']
... }
>>> bootstrapper = Bootstrapper(settings=config, url_mapping=[])
>>> settings, path_router = bootstrapper()
If you require your own settings (see the settings module for more information), pass in the dotted names of the modules or packages they are required in:
>>> bootstrapper = Bootstrapper(settings=config, url_mapping=[])
>>> settings, path_router = bootstrapper(packages=['foo', 'baz.bar'])
Traceback (most recent call last):
...
ImportError: No module named foo
To override specific components, either pass in False to skip registering them, e.g.:
>>> bootstrapper = Bootstrapper(settings=config, url_mapping=[])
>>> settings, path_router = bootstrapper(TemplateRenderer=False)
Or pass in your own implementation, e.g.:
>>> from mock import Mock
>>> mock_router = Mock()
>>> bootstrapper = Bootstrapper(settings=config, url_mapping=[])
>>> settings, path_router = bootstrapper(path_router=mock_router)
>>> path_router == mock_router
True
Simplifies setting up and registering weblayer components.
Init and return a RequirableSettings instance, scanning packages for required settings.
Setup component registrations. Pass in alternative implementations here to override, or pass in False to avoid registering a component.
If require_settings is True and settings isn’t provided as a keyword argument, call require_settings(), register_components() and return settings, path_router.
weblayer.component provides registry, an instance of a zope.component.registry.Components component management registry.
registry provides methods to register and lookup utilities and adapters against interfaces. For example:
>>> from mock import Mock
>>> from weblayer.interfaces import *
>>> mock_path_router = Mock()
>>> MockTemplateRenderer = Mock()
>>> MockTemplateRenderer.return_value = 'mock_template_renderer'
To register a utility:
>>> registry.registerUtility(mock_path_router, IPathRouter)
To register an adapter:
>>> registry.registerAdapter(
... MockTemplateRenderer,
... required=[ISettings],
... provided=ITemplateRenderer
... )
To get a registered utility:
>>> path_router = registry.getUtility(IPathRouter)
>>> path_router == mock_path_router
True
To get a registered adapter:
>>> from weblayer.settings import RequirableSettings
>>> settings = RequirableSettings()
>>> template_renderer = registry.getAdapter(
... settings,
... ITemplateRenderer
... )
Note how you register an adapter class and get an instance:
>>> MockTemplateRenderer.assert_called_with(settings)
>>> template_renderer = 'mock_template_renderer'
Tear down:
>>> registry.unregisterUtility(provided=IPathRouter)
True
>>> registry.unregisterAdapter(
... required=[ISettings],
... provided=ITemplateRenderer
... )
True
weblayer.cookie provides SignedSecureCookieWrapper, an implementation of ISecureCookieWrapper.
SignedSecureCookieWrapper provides two key methods, set() and get() to set and get cookies whose value is signed using the required settings['cookie_secret'].
The resulting cookies are secure, in the sense that they can’t be forged (without the forger knowing the settings['cookie_secret']). However, it’s important to note that they are just as vulnerable to sidejacking as normal cookies. The only way to secure cookies against sidejacking is to serve your application over HTTPS and that is a matter for web server configuration, outside the scope of weblayer.
weblayer.interfaces provides Interface definitions that show the contracts that weblayer‘s Components implement and are registered against and looked up by through the weblayer.component registry.
Authentication manager. Default implementation is TrivialAuthenticationManager.
Boolean – is there an authenticated user?
The authenticated user, or None
Selects request handler methods by name. Default implementation is ExposedMethodSelector.
Maps incoming requests to request handlers using the request path. Default implementation is RegExpPathRouter.
A Request object, based on webob.Request. Default implementation is Request.
This interface details only the attributes of webob.Request that weblayer uses by default, not the full interface webob.Request actually provides.
Content of the request body.
URL w. SCRIPT_NAME no PATH_INFO or QUERY_STRING
Full request URL, including QUERY_STRING
Dictionary of cookies found in the request
Path without HOST but with QUERY_STRING
Headers as case-insensitive dictionary-like object
HOST provided in HTTP_HOST w. fall-back to SERVER_NAME
Dictionary-like obj of params from POST and QUERY_STRING
The URL through the HOST (no path)
Path of the request, without HOST or QUERY_STRING
URL w. SCRIPT_NAME & PATH_INFO no QUERY_STRING
A request handler. Default implementation is RequestHandler.
Settings instance
Request instance
Cookie wrapper
Authentication manager
Response instance
Static url generator
<input/> element to be included in forms.
XSRF prevention token
A Response object, based on webob.Response. Default implementation is Response.
This interface details only the attributes of webob.Response that weblayer uses by default, not the full interface webob.Response actually provides.
The body of the response, as a str.
The status string
The list of response headers
Set value for cookie called key.
The body of the response, as a unicode.
The headers in a dictionary-like object
The Content-Type header
Normalise the response provided by a request handler method. Default implementation is DefaultToJSONResponseNormaliser.
Get and set cookies that can’t be forged. Default implementation is SignedSecureCookieWrapper.
Provides dictionary-like access to global application settings. Default implementation is RequirableSettings.
Static url generator. Default implementation is MemoryCachedStaticURLGenerator.
A template renderer. Default implementation is MakoTemplateRenderer.
A callable WSGI application. Default implementation is WSGIApplication.
weblayer.method provides ExposedMethodSelector, an implementation of IMethodSelector.
ExposedMethodSelector works in tandem with RequestHandler.__all__ to select only explicitly exposed request handler methods to handle incoming requests:
>>> class MockHandler(object):
... implements(IRequestHandler)
...
... __all__ = ('get')
...
... def get(self):
... pass
...
...
... def post(self):
... pass
...
...
...
>>> handler = MockHandler()
>>> selector = ExposedMethodSelector(handler)
>>> callable(selector.select_method('GET'))
True
>>> callable(selector.select_method('POST'))
False
Method selector adapter that works in tandem with the RequestHandler.__all__ attribute.
Returns getattr(self, method_name) iff the method exists and is exposed. Otherwise returns None.
Special cases HEAD requests to use GET, iff 'head' is exposed, def get() exists and def head() doesn’t. This allows applications to respond to HEAD requests without writing seperate head methods and takes advantage of the special case in webob.Response.__call__:
def __call__(self, environ, start_response):
# ... code removed for brevity
if environ['REQUEST_METHOD'] == 'HEAD':
# Special case here...
return EmptyResponse(self.app_iter)
return self.app_iter
weblayer.normalise provides DefaultToJSONResponseNormaliser, an implementation of IResponseNormaliser.
DefaultToJSONResponseNormaliser adapts a response object:
>>> from mock import Mock
>>> response = Mock()
>>> normaliser = DefaultToJSONResponseNormaliser(response)
>>> normaliser.response == response
True
Provides a normalise() method that takes a single argument and uses it to update and or replace the original response object before returning it:
>>> class MockResponse(Mock):
... implements(IResponse)
...
>>> mock_response = MockResponse()
>>> r = normaliser.normalise(mock_response)
>>> r == mock_response
True
>>> r = normaliser.normalise('a')
>>> r.body == 'a'
True
>>> r = normaliser.normalise(u'a')
>>> r.unicode_body == u'a'
True
>>> r = normaliser.normalise(None)
>>> r == normaliser.response
True
If the argument provided isn’t callable(), a basestring or None, the default implementation tries to JSON encode it:
>>> r = normaliser.normalise({'a': u'b'})
>>> r.content_type
'application/json; charset=UTF-8'
>>> r.unicode_body
u'{"a": "b"}'
Adapter to normalise a response.
Update and return self.response appropriately.
>>> from mock import Mock
>>> response = Mock()
>>> normaliser = DefaultToJSONResponseNormaliser(
... response,
... json_encode=None
... )
If handler_response is callable() then just use that. The intention being that the callable() is a WSGI application:
>>> def app(environ, start_response):
... headers = [('Content-type', 'text/plain')]
... start_response('200 OK', headers)
... return ['']
...
>>> r = normaliser.normalise(app)
>>> r == app
True
But, any old callable() will sneak through:
>>> def foo():
... pass
...
>>> r = normaliser.normalise(foo)
>>> r == foo
True
Otherwise if it’s a str or a unicode use that as the response body:
>>> r = normaliser.normalise('a')
>>> r.body == 'a'
True
>>> r = normaliser.normalise(u'a')
>>> r.unicode_body == u'a'
True
If it’s None then just return the origin response:
>>> normaliser = DefaultToJSONResponseNormaliser(
... 42,
... json_encode=None
... )
>>> normaliser.normalise(None)
42
Otherwise (with this particular implementation) assume we want to encode handler_response as a JSON string as use that as the response body:
>>> response = Mock()
>>> json_encode = Mock()
>>> normaliser = DefaultToJSONResponseNormaliser(
... response,
... json_encode=json_encode
... )
>>> json_encode.return_value = '{"a": "b"}'
>>> r = normaliser.normalise({'a': 'b'})
>>> r.content_type == normaliser._json_content_type
True
>>> json_encode.call_args[0][0] == {'a': 'b'}
True
>>> r.body
'{"a": "b"}'
>>> json_encode.return_value = u'{"a": "b"}'
>>> r = normaliser.normalise({'a': u'b'})
>>> r.unicode_body
u'{"a": "b"}'
weblayer.request provides RequestHandler, an implementation of IRequestHandler.
RequestHandler is designed to be used as a base class in which you can write code that handles incoming requests to a web application, as per:
class Hello(RequestHandler):
def get(self, world):
return u'hello %s' % world
The main point being to take advantage of the Request Handler API that the class provides.
A request handler (aka view class) implementation.
Accepts GET and HEAD requests by default, when used in tandem with an ExposedMethodSelector (or any method selector implementation that checks to see if the request method name is listed in RequestHandler.__all__).
Return a response corresponding to either exception or (if exception is None) status.
kwargs are passed to the appropriate webob_exception_ class constructor.
Note
Override this method to generate error messages that are more user friendly.
Log a warning and return “405 Method Not Allowed”.
Log the error and return “500 Internal Server Error”.
Log a warning and return “403 Forbidden”.
Redirect to location. The response status defaults to 302 unless permanent is True.
kwargs (with kwargs['location'] set to location are passed to the appropriate webob_exception class constructor.
Render the template called tmpl_name, passing through the params and kwargs.
An HTML <input /> element to be included with all POST forms.
Raise an XSRFError if the _xsrf argument isn’t present or if it doesn’t match self.xsrf_token.
weblayer.route provides RegExpPathRouter, an implementation of IPathRouter that uses regular expression patterns. Say, for example, you have some request handlers:
>>> class DummyIndex(object):
... implements(IRequestHandler)
...
>>> class Dummy404(object):
... implements(IRequestHandler)
...
You can then map request paths to them using a list of two item tuples:
>>> mapping = [(
... # string or compiled regexp pattern to match
... # against the request path
... r'/',
... # class the request should be handled by
... DummyIndex
... ), (
... r'/(.*)',
... Dummy404
... )
... ]
>>> path_router = RegExpPathRouter(mapping)
And use the path router to get handlers for request paths:
>>> path_router.match('/') == (DummyIndex, (), {})
True
Returning the handler and the match groups if any:
>>> path_router.match('/foobar') == (Dummy404, ('foobar',), {})
True
The mapping items are looked up in order:
>>> mapping.reverse()
>>> path_router = RegExpPathRouter(mapping)
>>> path_router.match('/') == (Dummy404, ('',), {})
True
If the path doesn’t match, returns (None, None, None):
>>> path_router = RegExpPathRouter([])
>>> path_router.match('/')
(None, None, None)
Routes paths to request handlers using regexp patterns.
If the path matches, return the handler class, the regular expression match object’s groups (as args to pass to the handler) and an empty dict (as kwargs to pass to the handler), as per:
>>> path_router = RegExpPathRouter([])
>>> handler_class, args, kwargs = path_router.match('/foo')
Otherwise return (None, None, None).
weblayer.settings provides RequirableSettings, an implementation of ISettings that allows you to (optionally) declare the settings that your application requires.
For example, say your application code needs to know the value of a particular webservice api key. You can require it by calling the require_setting() method (by convention at the top of a module, so the requirement is clear):
>>> require_setting('api_key')
Or by decorating a function or method with require():
>>> class Foo(object):
... @require('api_key')
... def foo(self):
... pass
...
...
>>> @require('api_key')
... def foo(self): pass
...
Then, once we’ve executed a venusian scan (which we fake here: see ./tests/test_settings.py for real integration tests):
>>> settings = RequirableSettings()
>>> def mock_require_setting(*args, **kwargs):
... settings._require(*args, **kwargs) # never call this directly!
...
>>> def mock_override_setting(*args, **kwargs):
... settings._override(*args, **kwargs) # never call this directly!
...
>>> mock_require_setting('api_key')
You can call the RequirableSettings instance with a dictionary of settings provided by the user / your application. If you pass in a value for api_key, great, otherwise, you’ll get a KeyError:
>>> settings({'api_key': '123'})
>>> settings['api_key']
'123'
>>> settings({})
Traceback (most recent call last):
...
KeyError: u'Required setting `api_key` () is missing'
You can specify default values and help strings ala:
>>> mock_require_setting('baz', default='blah', help=u'what is this?')
>>> settings({'api_key': '123'})
>>> settings['baz']
'blah'
You can’t require the same setting twice with different values:
>>> mock_require_setting('baz', default='something else')
Traceback (most recent call last):
...
KeyError: u'baz is already defined'
Unless you explicitly use override_setting() (also available as the override() decorator):
>>> mock_override_setting('baz', default='something else')
>>> settings({'api_key': '123'})
>>> settings['baz']
'something else'
Utility that provides dictionary-like access to application settings.
Do not use the _require and _override methods directly. Instead, use the require_setting() and override_setting() functions or the require() and override() decorators.
Note that the
Require a setting.
Decorator to require a setting.
weblayer.static provides MemoryCachedStaticURLGenerator, an implementation of IStaticURLGenerator.
The purpose of an IStaticURLGenerator is to generate static urls for use in templates. It does not serve static files.
MemoryCachedStaticURLGenerator adapts an IRequest and requires two settings, settings['static_files_path'] and settings['static_url_prefix'] (which defaults to u'/static/').
>>> from mock import Mock
>>> from os.path import normpath, join as join_path
>>> static_files_path = normpath('/var/www/static')
>>> request = Mock()
>>> request.host_url = 'http://foo.com'
>>> settings = {}
>>> settings['static_files_path'] = static_files_path
>>> settings['static_url_prefix'] = u'/static/'
>>> MemoryCachedStaticURLGenerator._cache = {}
>>> static = MemoryCachedStaticURLGenerator(request, settings)
>>> static._cache_path = Mock()
When get_url() is called, it looks for a file at the path passed in, relative to settings['static_files_path']. If the file exists, it hashes it and appends the first few characters of the hash digest to the returned url.
For example, imagine we’ve hashed and cached /var/www/static/foo.js:
>>> static._cache[join_path(static_files_path, 'foo.js')] = 'abcdefghij'
The static URL returned is:
>>> static.get_url('foo.js')
u'http://foo.com/static/foo.js?v=abcdefg'
Use settings['static_host_url'] to specify the url static files should be requested on (if this is different from the request url):
>>> settings['static_host_url'] = 'http://static.foo.com'
>>> static = MemoryCachedStaticURLGenerator(request, settings)
>>> static.get_url('foo.js')
u'http://static.foo.com/static/foo.js?v=abcdefg'
As the MemoryCachedStaticURLGenerator name suggests, the hash digests are cached in memory using a static class attribute. This means that:
The last two points mean that applications serving static files may incur CPU and memory overhead that could be avoided using a dedicated cache (like memcached or redis). Production systems thus may want to provide their own IStaticURLGenerator implementation, (potentially by subclassing MemoryCachedStaticURLGenerator and overriding the _cache_path() method).
Note
Alternative implementations must consider invalidating the hash digests when files change. One benefit of the default MemoryCachedStaticURLGenerator implementation is that, as hash digests are invalidated when the application restarts, deployment setups that watch for changes to the underlying source code and restart when files change cause the cache to be invalidated.
For example, one way to integrate with paste.reloader so it reloaded your application every time a cached file changed would be to use:
paster serve --reload
With:
def watch_cached_static_files():
return MemoryCachedStaticURLGenerator._cache.keys()
paste.reloader.add_file_callback(watch_cached_static_files)
Adapter to generate static URLs from a request.
Get a fully expanded url for the given static resource path:
>>> from mock import Mock
>>> request = Mock()
>>> request.host_url = 'http://static.foo.com'
>>> settings = {}
>>> settings['static_files_path'] = '/var/www/static'
>>> settings['static_url_prefix'] = u'/static/'
>>> join_path = Mock()
>>> join_path.return_value = '/var/www/static/foo.js'
>>> MemoryCachedStaticURLGenerator._cache = {}
>>> static = MemoryCachedStaticURLGenerator(
... request,
... settings,
... join_path_=join_path
... )
>>> static._cache_path = Mock()
path is expanded into file_path:
>>> url = static.get_url('foo.js')
>>> join_path.assert_called_with('/var/www/static', 'foo.js')
If path isn’t in self._cache, calls self._cache_path():
>>> static._cache_path.assert_called_with('/var/www/static/foo.js')
If the digest is None, just joins the host url, prefix and path:
>>> static._cache['/var/www/static/foo.js'] = None
>>> static.get_url('foo.js')
u'http://static.foo.com/static/foo.js'
Else also appends '?v=' plus upto the first snip_digest_at chars of the digest, which defaults to 7:
>>> static._cache['/var/www/static/foo.js'] = 'abcdefghijkl'
>>> static.get_url('foo.js')
u'http://static.foo.com/static/foo.js?v=abcdefg'
>>> static.get_url('foo.js', snip_digest_at=4)
u'http://static.foo.com/static/foo.js?v=abcd'
Cleanup:
>>> MemoryCachedStaticURLGenerator._cache = {}
weblayer.template provides MakoTemplateRenderer, an implementation of ITemplateRenderer that uses Mako templates.
>>> import tempfile, os
>>> from os.path import basename, dirname
>>> fd, abs_path = tempfile.mkstemp()
>>> sock = os.fdopen(fd, 'w')
>>> tmpl_dir = dirname(abs_path)
>>> tmpl_name = basename(abs_path)
MakoTemplateRenderer requires settings['template_directories']:
>>> settings = {'template_directories': [tmpl_dir]}
>>> template_renderer = MakoTemplateRenderer(settings)
And provides a render() method that accepts a tmpl_name which is resolved relative to settings['template_directories'] and passes through a set of built in functions and the keyword arguments provided to render() to the template’s global namespace:
>>> tmpl = u'<h1>${escape(foo)}</h1>'
>>> sock.write(tmpl)
>>> sock.close()
>>> template_renderer.render(tmpl_name, foo='&')
'<h1>&</h1>'
The built ins available by default are:
DEFAULT_BUILT_INS = {
"escape": utils.xhtml_escape,
"url_escape": utils.url_escape,
"json_encode": utils.json_encode,
"datetime": datetime
}
Cleanup:
>>> os.unlink(abs_path)
weblayer.utils provides a set of utility functions for converting and encoding.
Converts a unicode to a utf-8 encoded str:
>>> a = u'foo'
>>> a
u'foo'
>>> encode_to_utf8(a)
'foo'
>>> b = u'\u817e\u8baf\u9996\u9875'
>>> c = '腾讯首页'
>>> assert encode_to_utf8(b) == c
Regular strings get left alone:
>>> d = 'foo'
>>> encode_to_utf8(d)
'foo'
Other types raise a ValueError:
>>> e = None
>>> encode_to_utf8(e)
Traceback (most recent call last):
...
ValueError: None must be a `basestring`
Converts a (hopefully) utf-8 encoded str to a unicode:
>>> a = 'foo'
>>> decode_to_unicode(a)
u'foo'
>>> b = '腾讯首页'
>>> decode_to_unicode(b)
u'\u817e\u8baf\u9996\u9875'
Unicode values get left alone:
>>> c = u'foo'
>>> decode_to_unicode(c)
u'foo'
Other types raise a ValueError:
>>> d = None
>>> decode_to_unicode(d)
Traceback (most recent call last):
...
ValueError: None must be a `basestring`
Escapes a string so it is valid within XML or XHTML:
>>> xhtml_escape('a')
'a'
>>> xhtml_escape('<')
'<'
>>> xhtml_escape('&')
'&'
Including double quotes:
>>> xhtml_escape('"')
'"'
Encoding the result to utf-8:
>>> xhtml_escape(u'a')
'a'
Returns a URL-encoded version of value.
Runs the value through quote_plus():
>>> url_escape('a')
'a'
>>> url_escape(' ')
'+'
Encoding it first to utf-8:
>>> url_escape(u'a')
'a'
>>> url_escape(u'http://foo.com?bar=baz')
'http%3A%2F%2Ffoo.com%3Fbar%3Dbaz'
Which means the value must be a basestring:
>>> url_escape(None)
Traceback (most recent call last):
...
ValueError: None must be a `basestring`
Ensures all items are encoded to utf-8 and passed to urlencode().
Pass it a dict, comes out like a query string:
>>> r1 = unicode_urlencode({'a': 'b'})
>>> r1
'a=b'
Ditto a list of two item tuples:
>>> r2 = unicode_urlencode([('a', 'b')])
>>> r2 == r1
True
Converting any unicode values to utf8:
>>> unicode_urlencode({'a': u'b'})
'a=b'
>>> r3 = unicode_urlencode({'a': u'\u817e\u8baf\u9996\u9875'})
>>> r3
'a=%E8%85%BE%E8%AE%AF%E9%A6%96%E9%A1%B5'
Before running them through urlencode():
>>> from urllib import urlencode
>>> r4 = urlencode({'a': encode_to_utf8(u'\u817e\u8baf\u9996\u9875')})
>>> r4 == r3
True
All values must be instances of basestring:
>>> unicode_urlencode({'a': object()})
Traceback (most recent call last):
...
ValueError: <object object ... must be a `basestring`
Lists must contain at least two values to unpack:
>>> unicode_urlencode(['a', 'b'])
Traceback (most recent call last):
...
ValueError: need more than 1 value to unpack
And not more than two values to unpack:
>>> unicode_urlencode([('a', 'b', 'c')])
Traceback (most recent call last):
...
ValueError: too many values to unpack
JSON encodes the given value:
>>> json_encode({'a': 'b'}) == json.dumps({'a': 'b'})
True
>>> json_encode({'a': 'b'})
'{"a": "b"}'
>>> json_encode({'a': None})
'{"a": null}'
>>> json_encode([])
'[]'
With ensure_ascii False by default:
>>> json_encode({'a': u'\u817e\u8baf\u9996\u9875'})
u'{"a": "\u817e\u8baf\u9996\u9875"}'
>>> result = json_encode({'a': u'\u817e\u8baf'}, ensure_ascii=True)
>>> result == '{"a": "\u817e\u8baf"}'
True
Raises a TypeError if the value isn’t serializable:
>>> json_encode([object()])
Traceback (most recent call last):
...
TypeError: <object object ... is not JSON serializable
If value is valid JSON, parses it into a Python object:
>>> json_decode('{}') == json.loads('{}')
True
>>> json_decode('{}')
{}
>>> json_decode('[null]')
[None]
Passing the value through decode_to_unicode() to start with:
>>> json_decode('{"a": "b"}')
{u'a': u'b'}
>>> json_decode('{"a": "\u817e\u8baf\u9996\u9875"}')
{u'a': u'\u817e\u8baf\u9996\u9875'}
Raises a ValueError if the decoded value can’t be parsed:
>>> json_decode('{"a": object()}')
Traceback (most recent call last):
...
ValueError: No JSON object could be decoded
Generates a hexdigest() string, either randomly or from a string or file like object (like an open file or a buffer).
By default, the hash is randomly generated and uses the sha512 algorithm:
>>> s1 = generate_hash()
>>> isinstance(s1, str)
True
>>> len(s1) == 128
True
>>> s2 = generate_hash()
>>> s1 == s2
False
>>> s3 = generate_hash(algorithm='sha512')
>>> len(s1) == len(s3)
True
The hash can be generated from a seed:
>>> generate_hash(s='a')
'1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75'
Using None as the seed (which is the default) will, as we’ve seen, generate a random value:
>>> s6 = generate_hash(s=None)
>>> s7 = generate_hash(s=None)
>>> s6 == s7
False
Using a file like object (anything with a read() method) will use the contents of the file like object:
>>> from StringIO import StringIO
>>> sock = StringIO()
>>> sock.write('abc')
>>> sock.seek(0)
>>> s8 = generate_hash(s=sock)
>>> s9 = generate_hash(s='abc')
>>> s8 == s9
True
Reading the contents into memory in blocks of block_size, which defaults to 512:
>>> from mock import Mock
>>> sock = Mock()
>>> sock.read.return_value = None
>>> s10 = generate_hash(s=sock)
>>> sock.read.assert_called_with(512)
>>> s10 = generate_hash(s=sock, block_size=1024)
>>> sock.read.assert_called_with(1024)
Using other types as a seed (anything that hashlib doesn’t like) will raise a TypeError:
>>> generate_hash(s=[])
Traceback (most recent call last):
...
TypeError: ...
The algorithm name can also be passed in:
>>> s4 = generate_hash(algorithm='md5')
>>> s5 = generate_hash(algorithm='sha224')
>>> len(s4) == 32 and len(s5) == 56
True
As long as it’s available in hashlib:
>>> generate_hash(algorithm='foo')
Traceback (most recent call last):
...
AttributeError: 'module' object has no attribute 'foo'
weblayer.wsgi provides WSGIApplication, an implementation of IWSGIApplication that adapts ISettings and an IPathRouter:
>>> settings = {}
>>> path_router = object()
To provide a callable WSGI application:
>>> application = WSGIApplication(settings, path_router)
Checks self._path_router for a match() against the incoming path:
handler_class, args, kwargs = self._path_router.match(request.path)
If handler_class is not None, instantiates the IRequestHandler:
handler = handler_class(request, response, self._settings)
And calls it with environ['REQUEST_METHOD'] and the args and kwargs from match():
response = handler(environ['REQUEST_METHOD'], *args, **kwargs)
Note
If calling the handler errors (which is shouldn’t normally do, as the handler should catch the error), returns a minimalist 500 response.
Note
If no match is found, returns a minimalist 404 response. To handle 404 responses more elegantly, define a catch all URL handler.