Tangled Web¶
A resource oriented, Python 3 only Web framework.
No controllers. No views. Just resources and representations.
Also, there are no magic globals. There is an
Application object that contains your
app’s configuration. It is passed to resources along with the current request.
Contents¶
Hello, World¶
Here’s a really simple Tangled Web app:
from wsgiref.simple_server import make_server
from tangled.web import Application, Resource
class Hello(Resource):
def GET(self):
if 'name' in self.urlvars:
content = 'Hello, {name}'.format(**self.urlvars)
else:
content = 'Hello'
return content
if __name__ == '__main__':
settings = {
'debug': True,
}
app = Application(settings)
app.mount_resource('hello', Hello, '/')
app.mount_resource('hello_name', Hello, '/<name>')
server = make_server('0.0.0.0', 6666, app)
server.serve_forever()
Note
This is a copy of examples/hello_world.py. If you’re in the top level
of tangled.web checkout, you can run it with
python examples/hello_world.py (assuming tangled.web is already
installed).
Quick Start¶
This is a short guide that will show you the basics of creating a Web
application based on tangled.web.
Install Python 3.3+¶
First, install Python 3.3. Older versions of Python 3 will not work. Mainly, this is because of the use of built-in namespace package support that was added in Python 3.3.
You can download Python 3.3 from here. If you’re using Mac OS, Homebrew is an easy way to install Python:
brew install python3
Note
Python 2.x is not supported, and there are no plans to support it.
Virtual Env¶
Next, set up an isolated virtual environment. Since we’re using Python 3, this is built in. The command for creating a virtual env looks like this:
python3 -m venv helloworld.venv
Change into the helloworld.venv directory and download the following file
there:
https://raw.github.com/pypa/pip/master/contrib/get-pip.py
Then run the following command:
./bin/python get-pip.py
Install Dependencies¶
A couple of Tangled dependencies need to be installed so that the
tangled scaffold command and basic scaffold are available:
./bin/pip install tangled.web==VERSION
Replace VERSION with the version you want to install. The current version is 1.0a13.dev0.
If you want to use the latest code, you can do this instead (requires git to be installed):
./bin/pip install -e git+git://github.com/TangledWeb/tangled#egg=tangled
./bin/pip install -e git+git://github.com/TangledWeb/tangled.web#egg=tangled.web
Create a Basic Tangled Web App¶
Now that the virtual environment is set up and the Tangled dependencies have
been installed, a project can be created. Run the following commands in the
helloworld.venv directory:
./bin/tangled scaffold basic helloworld
./bin/pip install -e helloworld
Serve it Up¶
Now that everything’s installed, it’s time to run the app:
./bin/tangled serve -f helloworld/development.ini
Now you can visit http://localhost:6666/ and http://localhost:6666/name.
Next Steps¶
Take a look at the app configuration in helloworld/helloworld/__init__.py
and the Hello resource in helloworld/helloworld/resources.py.
The Application API documentation currently has the most comprehensive info on creating and configuring Tangled Web apps.
Note
This is all still very much a work in progress. Please feel free to make suggestions or report issues on GitHub.
Installation¶
Note
Python 3.3+ is required. Older versions of Python 3 will not work. No version of Python 2 will work.
All tangled.* packages are standard setuptools distributions that can
be installed via easy_install or pip.
Contributing¶
Patches¶
To contribute patches, go to the TangledWeb project on GitHub, fork a package, and send a pull request. All new code must be 100% covered by tests and be PEP8 compliant.
Creating an Extension Package¶
To create your own extension package, you can use the tangled.contrib
namespace. If you install the tangled.contrib package, you will be able to
create a contrib package easily using the tangled scaffold command:
tangled scaffold contrib tangled.contrib.{name}
Main Documentation¶
Displaying Errors¶
By default, errors will be displayed using the plain error templates provided by WebOb. To customize the display of errors, an error resource needs to be created. The simplest error resource looks like this:
from tangled.web import Resource, config
class Error(Resource):
@config('text/html', template='/error.html')
def GET(self):
return {}
error.html would contain contents like this:
<%inherit file="/layout.html"/>
<h1>Error</h1>
<div class="error">
The request failed with status code ${request.status_code}
</div>
To activate the error resource, point the tangled.app.error_resource
setting at it:
[app]
tangled.app.error_resource = my.pkg.resources.error:Error
Application API¶
This documents the API that’s typically used by application developers.
Application¶
-
class
tangled.web.app.Application(settings, **extra_settings)[source]¶ Application container.
The application container handles configuration and provides the WSGI interface. It is passed to components such as handlers, requests, and resources so they can inspect settings, retrieve items from the registry, etc…
Registry:
Speaking of which, the application instance acts as a registry (it’s a subclass of
tangled.registry.Registry). This provides a means for extensions and application code to set application level globals.Settings:
settingscan be passed as either a file name pointing to a settings file or as a dict.File names can be specified as absolute, relative, or asset paths:
- development.ini
- /some/where/production.ini
- some.package:some.ini
A plain dict can be used when no special handling of settings is required. For more control of how settings are parsed (or to disable parsing), pass a
AAppSettingsinstance instead (typically, but not necessarily, created by callingtangled.web.settings.make_app_settings()).Extra settings can be passed as keyword args. These settings will override all other settings. They will be parsed along with other settings.
NOTE: If
settingsis anAppSettingsinstance, extra settings passed here will be ignored; pass them to theAppSettingsinstead.Logging:
If settings are loaded from a file and that file (or one of the files it extends) contains logging config sections (
formatters,handlers,loggers), that logging configuration will automatically be loaded vialogging.config.fileConfig.-
add_helper(helper, name=None, static=False, package=None, replace=False)[source]¶ Add a “helper” function.
helpercan be a string pointing to the helper or the helper itself. If it’s a string,helperandpackagewill be passed toload_object().Helper functions can be methods that take a
Helpersinstance as their first arg or they can be static methods. The latter is useful for adding third party functions as helpers.Helper functions can be accessed via
request.helpers. The advantage of this is that helpers added as method have access to the application and the current request.
-
add_subscriber(event_type, func, priority=None, once=False, **args)[source]¶ Add a subscriber for the specified event type.
argswill be passed tofuncas keyword args. (Note: this functionality is somewhat esoteric and should perhaps be removed.)You can also use the
subscriberdecorator to register subscribers.
-
get_setting(key, default=NOT_SET)[source]¶ Get a setting; return
defaultif one is passed.If
keyisn’t in settings, try prepending'tangled.app.'.If the
keyisn’t present, return thedefaultif one was passed; if adefaultwasn’t passed, a KeyError will be raised.
-
get_settings(settings=None, prefix='tangled.app.', **kwargs)[source]¶ Get settings with names that start with
prefix.This is a front end for
tangled.util.get_items_with_key_prefix()that sets defaults forsettingsandprefix.By default, this will get the settings from
self.settingsthat have a'tangled.app.'prefix.Alternate
settingsand/orprefixcan be specified.
-
on_created(func, priority=None, once=True, **args)[source]¶ Add an
ApplicationCreatedsubscriber.Sets
oncetoTrueby default sinceApplicationCreatedis only emitted once per application.This can be used as a decorator in the simple case where no args other than
funcneed to be passed along toadd_subscriber().
Settings¶
-
tangled.web.settings.make_app_settings(settings, conversion_map={}, defaults={}, required=(), prefix=None, strip_prefix=True, parse=True, section='app', **extra_settings)[source]¶ Create a properly initialized application settings dict.
In simple cases, you don’t need to call this directly–you can just pass a settings file name or a plain dict to
tangled.web.app.Application, and this will be called for you.If you need to do custom parsing (e.g., if your app has custom settings), you can call this function with a conversion map, defaults, &c. It’s a wrapper around
parse_settings()that adds a bit of extra functionality:- A file name can be passed instead of a settings dict, in
which case the settings will be extracted from the specified
sectionof that file. - Core tangled.web defaults are always added because
tangled.web.app.Applicationassumes they are always set. - Settings parsing can be disabled by passing
parse=False. This only applies to your settings, including defaults and extra settings (core defaults are always parsed). - Extra settings can be passed as keyword args; they will override all other settings, and they will be parsed (or not) along with other settings.
- Required settings are checked for after all the settings are merged.
In really special cases you can create a subclass of
AAppSettingsand then construct your settings dict by hand (eschewing the use of this function).- A file name can be passed instead of a settings dict, in
which case the settings will be extracted from the specified
Events¶
Events are registered in the context of an application via
tangled.web.app.Application.add_subscriber().
Subscribers typically have the signature subscriber(event). If subscriber
keyword args were passed to add_subscriber, then the signature for the
subscriber would be subscriber(event, **kwargs).
Every event object will have an app attribute. Other attributes are
event dependent.
-
class
tangled.web.events.ApplicationCreated(app)[source]¶ Emitted when an application is fully configured.
These events can be registered in the usual way by calling
tangled.web.app.Application.add_subscriber(). There’s also a convenience method for this:tangled.web.app.Application.on_created().Attributes:
app.
-
class
tangled.web.events.NewRequest(app, request)[source]¶ Emitted when an application receives a new request.
This is not emitted for static file requests.
Attributes:
app,request.
-
class
tangled.web.events.NewResponse(app, request, response)[source]¶ Emitted when the response for a request is created.
This is not emitted for static file requests.
If there’s in exception during request handling, this will not be emitted.
Attributes:
app,request,response.
-
class
tangled.web.events.ResourceFound(app, request, resource)[source]¶ Emitted when the resource is found for a request.
Attributes:
app,request,resource.
-
class
tangled.web.events.TemplateContextCreated(app, request, context)[source]¶ Emitted when the context for a template is created.
The template
contextis whatever data will passed to the template. E.g., for Mako, it’s a dict.This is emitted just before the template is rendered. Its purpose is to allow additional data to be injected into the template context.
Attributes:
app,request,context
-
tangled.web.events.subscriber(event_type, *args, **kw)[source]¶ Decorator for adding event subscribers.
Subscribers registered this way won’t be activated until
tangled.web.app.Application.load_config()is called.Example:
@subscriber('tangled.web.events:ResourceFound') def on_resource_found(event): log.debug(event.resource.name)
Request factory¶
-
class
tangled.web.request.Request(environ, app, *args, **kwargs)[source]¶ Default request factory.
Every request has a reference to its application context (i.e.,
request.app).-
abort(status_code, *args, **kwargs)[source]¶ Abort the request by raising a WSGIHTTPException.
This is a convenience so resource modules don’t need to import exceptions from
webob.exc.
-
get_setting(*args, **kwargs)[source]¶ Get an app setting.
Simply delegates to
tangled.web.app.Application.get_setting().
-
helpers¶ Get helpers for this request.
Returns a
Helpersinstance; all the helpers added viatangled.web.app.Application.add_helper()will be accessible as methods of this instance.
-
make_url(path, query=None, fragment=None, *, _fully_qualified=True)[source]¶ Generate a URL.
pathshould be application-relative (that is, it should not include SCRIPT_NAME).querycan be a string, a dict, or a sequence. Seemake_query_string()for details.If
fragmentis passed it will be quoted usingurllib.parse.quote()with no “safe” characters (i.e., all special characters will be quoted).
-
on_finished(callback, *args, **kwargs)[source]¶ Add a finished callback.
Callbacks must have the signature
(app, response). They can also take additional positional and keyword args–*argsand**kwargswill be passed along to thecallback.Finished callbacks are always called regardless of whether an error occurred while processing the request. They are called just before the Tangled application returns to its caller.
All finished callbacks will be called. If any of them raises an exception, a
RequestFinishedExceptionwill be raised and a “500 Internal Server Error” response will be returned in place of the original response.Raising instances of
webob.exc.WSGIHTTPExceptionin finished callbacks is an error.The
responseobject can be inspected to see if an error occurred while processing the request. If theresponseisNone, the request failed hard (i.e., there was an uncaught exception before the response could be created).This can be used as a decorator in the simple case where the
callbackdoesn’t take any additional args.
-
resource_config¶ Get info for the resource associated with this request.
Note
This can’t be safely accessed until after the resource has been found and set for this request.
-
resource_path(resource, urlvars=None, **kwargs)[source]¶ Generate a URL path (with SCRIPT_NAME) for a resource.
-
response¶ Create the default response object for this request.
The response is initialized with attributes set via
@config:status,location, andresponse_attrs.If no status code was set via
@config, we try our best to set it to something sane here based on content type and method.If
locationis set butstatusisn’t, the response’s status is set toDEFAULT_REDIRECT_STATUS.The location can also be set to one of the special values ‘REFERER’ or ‘CAME_FROM’. The former redirects back to the refering page. The latter redirects to whatever is set in the
came_fromrequest parameter.TODO: Check origin of referer and came from.
Note
See note in
resource_config().
-
response_content_type¶ Get the content type to use for the response.
This retrieves the content types the resource is configured to handle then selects the best match for the requested content type. If the resource isn’t explicitly configured to handle any types or of there’s no best match, the default content type will be used.
Note
This can’t be safely accessed until after the resource has been found and set for this request.
-
static_url(path, query=None, **kwargs)[source]¶ Generate a static URL from
path.pathshould always be an application-relative path like ‘/static/images/logo.png’. SCRIPT_NAME will be prepended bymake_url().
-
Resources¶
Creating resources¶
-
class
tangled.web.resource.resource.Resource(app, request, name=None, urlvars=None)[source]¶ Base resource class.
Usually, you will want to subclass
Resourcewhen creating your own resources. Doing so will ensure your resources are properly initialized.Subclasses will automatically return a
405 Method Not Allowedresponse for unimplemented methods.Subclasses also have
url()andpath()methods that generate URLs and paths to the “current resource”. E.g., in a template, you can doresource.path()to generate the application-relative path to the current resource. You can also pass in query parameters and alternate URL vars to generate URLs and paths based on the current resource.-
DELETE()¶ Delete resource.
Return
- 204 if no body
- 200 if body
- 202 if accepted but not yet deleted
-
GET()¶ Get resource.
Return:
- 200 body
-
HEAD()¶ Get resource metadata.
Return:
- 204 no body (same headers as GET)
-
OPTIONS()[source]¶ Get resource options.
By default, this will add an
Allowheader to the response that lists the methods implemented by the resource.
-
PATCH()¶ Update resource.
Return:
- 200 (body)
- 204 (no body)
- 303 (instead of 204)
-
POST()¶ Create a new child resource.
Return:
- If resource created and identifiable w/ URL:
- 201 w/ body and Location header (for XHR?)
- 303 w/ Location header (for browser?)
- If resource not identifiable:
- 200 if body
- 204 if no body
-
PUT()¶ Update resource or create if it doesn’t exist.
Return:
- If new resource created, same as
POST() - If updated:
- 200 (body)
- 204 (no body)
- 303 (instead of 204)
- If new resource created, same as
-
Configuring resources¶
-
class
tangled.web.resource.config.config[source]¶ Decorator for configuring resources methods.
When used on a resource class, the class level configuration will be applied to all methods.
Example:
class MyResource: @config('text/html', template='my_resource.mako') def GET(self): pass
Example of defaults and overrides:
@config('*/*', status=303, response_attrs={'location': '/'}) class MyResource: @config('*/*', status=302) @config('text/html', status=None, response_attrs={}) def GET(self): pass
Mounting Resources¶
-
Application.mount_resource(name, factory, path, methods=(), method_name=None, add_slash=False, _level=3)[source]¶ Mount a resource at the specified path.
Basic example:
app.mount_resource('home', 'mypackage.resources:Home', '/')
Specifying URL vars:
app.mount_resource( 'user', 'mypackage.resources:User', '/user/<id>')
A unique
namefor the mounted resource must be specified. This can be any string. It’s used when generating resource URLs viarequest.Request.resource_url().A
factorymust also be specified. This can be any class or function that produces objects that implement the resource interface (typically a subclass ofresource.resource.Resource). The factory may be passed as a string with the following format:package.module:factory.The
pathis an application relative path that may or may not include URL vars.A list of HTTP
methodscan be passed to constrain which methods the resource will respond to. By default, it’s assumed that a resource will respond to all methods. Note however that when subclassingresource.resource.Resource, unimplemented methods will return a405 Method Not Allowedresponse, so it’s often unnecessary to specify the list of allowed methods here; this is mainly useful if you want to mount different resources at the same path for different methods.If
pathends with a slash oradd_slashis True, requests topathwithout a trailing slash will be redirected to thepathwith a slash appended.About URL vars:
The format of a URL var is
<(converter)identifier:regex>. Angle brackets delimit URL vars. Only theidentifieris required; it can be any valid Python identifier.If a
converteris specified, it can be a built-in name, the name of a converter intangled.util.converters, or apackage.module:callablepath that points to a callable that accepts a single argument. URL vars found in a request path will be converted automatically.The
regexcan be almost any regular expression. The exception is that<and>can’t be used. In practice, this means that named groups ((?P<name>regex)) can’t be used (which would be pointless anyway), nor can “look behinds”.Mounting Subresources
Subresources can be mounted like this:
parent = app.mount_resource('parent', factory, '/parent') parent.mount('child', 'child')
or like this:
with app.mount_resource('parent', factory, '/parent') as parent: parent.mount('child', 'child')
In either case, the subresource’s
namewill be prepended with its parent’s name plus a slash, and itspathwill be prepended with its parent’s path plus a slash. If nofactoryis specified, the parent’s factory will be used.methodswill be propagated as well.method_nameandadd_slashare not propagated.In the examples above, the child’s name would be
parent/childand its path would be/parent/child.
Static Files¶
-
Application.mount_static_directory(prefix, directory, remote=False, index_page=None)[source]¶ Mount a local or remote static directory.
prefixis an alias referring todirectory.If
directoryis just a path, it should be a local directory. Requests to/{prefix}/{path}will look in this directory for the file indicated bypath.If
directoryrefers to a remote location (i.e., it starts withhttp://orhttps://), URLs generated viareqeust.static_urlandrequest.static_pathwill point to the remote directory.remotecan also be specified explicitly. In this context, “remote” means not served by the application itself. E.g., you might be mapping an alias in Nginx to a local directory.Note
It’s best to always use
tangled.web.request.Request.static_url()tangled.web.request.Request.static_path()to generate static URLs.
Extension API¶
This documents the API that is typically used by extension developers.
Adding @config Args¶
Fields¶
-
Application.add_config_field(content_type, name, *args, **kwargs)[source]¶ Add a config field that can be passed via
@config.This allows extensions to add additional keyword args for
@config. These args will be accessible as attributes of theresource.config.Configobject returned byrequest.resource_config.These fields can serve any purpose. For example, a
permissionfield could be added, which would be accessible asrequest.resource_config.permission. This could be checked in an auth handler to verify the user has the specified permission.See
_add_config_arg()for more detail.
Representation Args¶
-
Application.add_representation_arg(*args, **kwargs)[source]¶ Add a representation arg that can be specified via @config.
This allows extensions to add additional keyword args for
@config. These args will be passed as keyword args to the representation type that is used for the request.These args are accessible via the
representation_argsdict of theresource.config.Configobject returned byrequest.resource_config(but generally would not be accessed directly).See
_add_config_arg()for more detail.
Request Handlers¶
Adding request handlers¶
Handlers are callables with the signature (app, request, next_handler).
-
Application.add_handler(handler)[source]¶ Add a handler to the handler chain.
Handlers added via this method are inserted into the system handler chain above the main handler. They will be called in the order they are added (the last handler added will be called directly before the main handler).
Handlers are typically functions but can be any callable that accepts
app,request, andnext_handlerargs.Each handler should either call its
next_handler, return a response object, or raise an exception.TODO: Allow ordering?
System handler chain¶
System handlers.
Requests are processed through a chain of handlers. This module contains the “system” handlers. These are handlers that always run in a specific order.
Most of the system handlers always run. They can’t be turned off, but
you can swap in different implementations via settings. Take a look at
tangled/web/defaults.ini to see how you would do this.
Some handlers are only enabled when certain settings are enabled or when
certain configuration takes place. For example, to enable CSRF
protection, the tangled.app.csrf.enabled setting needs to be set to
True. Another example: the static files handlers is only enabled
when at least one static directory has been mounted.
If an auth handler is enabled, it will run directly before any (other) handlers added by the application developer.
All added handlers are called in the order they were added. The last
handler to run is always the main() handler; it calls into
application code (i.e., it calls a resource method to get data or
a response).
-
tangled.web.handlers.error_handler(app, request, main_handler, original_response)[source]¶ Handle error response.
If an error resource is configured, its
GETmethod will be called to get the final response. This is accomplished by setting the error resource as the resource for the request and then passing the request back into the main handler.If CORS is enabled, the main handler will be wrapped in the CORS handler so that error responses will have the appropriate headers.
If no error resource is configured, the original error response will be returned as is.
-
tangled.web.handlers.request_finished_handler(app, request, _)[source]¶ Call request finished callbacks in exc handling context.
This calls the request finished callbacks in the same exception handling context as the request. This way, if exceptions occur in finished callbacks, they can be logged and displayed as usual.
Note
Finished callbacks are not called for static requests.
-
tangled.web.handlers.tweaker(app, request, next_handler)[source]¶ Tweak the request based on special request parameters.
-
tangled.web.handlers.resource_finder(app, request, next_handler)[source]¶ Find resource for request.
Sets
request.resourceand notifiesResourceFoundsubscribers.If a resource isn’t found, a 404 response is immediatley returned. If a resource is found but doesn’t respond to the request’s method, a
405 Method Not Allowedresponse is returned.
-
tangled.web.handlers.main(app, request, _)[source]¶ Get data from resource method and return response.
If the resource method returns a response object (an instance of
Response), that response will be returned without further processing.If the status of
request.responsehas been set to 3xx (either via @config or in the body of the resource method) AND the resource method returns no data, the response will will be returned as is without further processing.Otherwise, a representation will be generated based on the request’s Accept header (unless a representation type has been set via @config, in which case that type will be used instead of doing a best match guess).
If the representation returns a response object as its content, that response will be returned without further processing.
Otherwise, request.response will be updated according to the representation type (the response’s content_type, charset, and body are set from the representation).
Request¶
Adding request methods¶
-
Application.add_request_attribute(attr, name=None, decorator=None, reify=False)[source]¶ Add dynamic attribute to requests.
This is mainly intended so that extensions can easily add request methods and properties.
Functions can already be decorated, or a
decoratorcan be specified. IfreifyisTrue, the function will be decorated withtangled.decorators.cached_property(). If adecoratoris passed andreifyisTrue,cached_propertywill be applied as the outermost decorator.
Request factories¶
These two methods make it easy to create properly configured requests. In
particular, they set the request’s app attribute, and they create
request instances with the attributes added via
tangled.web.app.Application.add_request_attribute().