wiki:S3REST

Version 57 (modified by Fran Boon, 14 years ago) ( diff )

--

S3REST

S3RESTController

The S3RESTController class implements a generic RESTful interface for database resources in S3.

This class is instantiated as the global callable s3rest object, which will be used by the shn_rest_controller() wrapper function.

s3rest can be configured dynamically, and can easily be used to build RESTful controllers throughout the application.

Important: an s3rest instance is created per request and must not be stored or referenced by static objects to avoid memory leaks!

The default method handlers of s3rest (provided by the shn_rest_controller() wrapper) implement CRUD (create, read, update, delete) in all major representations, including the capabilities of S3XRC to use in-line XSLT for export and import of XML and JSON formats.

Instead of the web2py request object, s3rest uses the S3RESTRequest class (described below) to represent the current REST request. Unlike web2py's request, the S3RESTRequest of the current call is not a global object, but gets passed as argument when calling REST method handlers.

REST Methods

It is critical to understand how RESTful requests work.

A RESTful request has two components:

  • the URL addresses the resource
  • the HTTP method determines the action to be taken with that resource, which is either:
    • GET, PUT, POST or DELETE

A resource in this terminology is either:

  • a database table or one or more records in that table
  • a combination of tables or records (so called "compound" or "joined" resources)
  • a function in the application related to one of the above resource types

Functions as resources in RESTful requests are to be understood and addressed as "sub"-resources of their respective primary resource.

The finally executed response action depends on the HTTP/method and on the type of resource addressed by the URL, e.g.

  • if the method is "GET" and the resource addressed is a database table, then the response will be a list of the records in that table
  • if the method is "PUT" and the resource addressed is a record in a table, then the response will be an update of that record with the data from the request body

CRUD Method Handlers

The default method handlers for each of CRUD+S are implemented in models/01_crud.py:

  • shn_read(jr, attr)
  • shn_list(jr, attr)
  • shn_create(jr, attr)
  • shn_update(jr, attr)
  • shn_delete(jr, attr)
  • shn_search(jr, attr)
  • shn_options(jr, attr)
  • import_xml(jr, attr)
  • import_json(jr, attr)

where jr is the current S3RESTRequest, and attr contains all further arguments passed to the REST controller.

You can override these defaults at any time by

s3rest.set_handler(action, handler)

where:

  • action is the name of the action, i.e. one of 'read', 'list', 'create', 'update', 'delete', 'search', 'options', 'import_xml' or 'import_json'
  • handler is the handler function/lambda

Custom Methods

You can also add resource-specific custom methods by:

  s3xrc.model.set_method(module, resource, method, action)

where:

  • module is the module prefix of a resource
  • resource is the name of the resource (without prefix)
  • method is a string representing the name of the method (e.g. "search_simple")
  • action is the method handler function/lambda

The action method has to take the same arguments as the default handlers: jr (S3RESTRequest) and attr.

Pre- and Post-Hooks

You can hook in a preprocessing function into the REST controller (as response.s3.prep) which will be called after the controller has parsed the request, but before it gets actually executed - with the current S3RESTRequest as argument (which includes the primary resource record, if any).

This allows you to easily make changes to resource settings (e.g. access control, list fields etc.), or even to the REST controller configuration (e.g. custom methods) depending on the request type, its parameters or the addressed resource, without having to parse the web2py request manually. You can even bypass the execution of the request and thus hook in your own REST controller - with the advantage that you don't need to parse the request anymore.

NB: Hooks should be added at the top of your controller function, especially pre-processing hooks.

A simple example:

Original code fragment:

    if len(request.args) == 0:
        # List View - reduce fields to declutter
        table.message.readable = False
        table.categories.readable = False
        table.verified_details.readable = False
        table.actioned_details.readable = False

    response.s3.pagination = True #enable SSPag here!

    return shn_rest_controller(module, resource, listadd=False)

Using the pre-processor hook instead:

    def log_prep(jr):
        if jr.representation == "html" and \
           jr.method is None and \
           jr.component is None:
            # List View - reduce fields to declutter
            table.message.readable = False
            table.categories.readable = False
            table.verified_details.readable = False
            table.actioned_details.readable = False
        return True

    response.s3.prep = log_prep
    response.s3.pagination = True #enable SSPag here!

    return shn_rest_controller(module, resource, listadd=False)

The return value of the preprocessor function can simply be True, in which case the REST request will be executed as usual. Returning False would lead to a HTTP400 "Invalid Request" exception being raised.

The return value of the preprocessor function can also be a dict for more granular control - containing the following elements (all optional):

  • success: boolean (default: True)
  • output: dict (default: None)
  • bypass: boolean (default: False)

If bypass is True, then the REST controller does not execute the request (the post-hook is executed, though). output must not be None in this case - it will be returned from the REST controller.

If success is False, and output is not None, then the REST controller does not execute the request, but just returns "output" (post-hook will not be executed in this case).

If success is False and output is None, a HTTP400 "Invalid Request" will be raised instead.

Examples:

In most cases, you will just return "True" - in some cases you might want to raise an error, e.g.:

    response.error = "This request cannot be executed"
    return dict(
        success=False,
        output=dict(title="My Pagetitle", item="Sorry, no data..."))

There is also a post-processing hook (response.s3.postp) that allows you to execute something directly after the REST request has been executed, but before the shn_rest_controller returns. The post-hook function will be called with the current S3RESTRequest and the output dict of its execution as arguments.

PostP Examples:

def user_postp(jr, output):
    # Replace the ID column in List views with 'Action Buttons'
    shn_action_buttons(jr)
    return output
response.s3.postp = user_postp
def postp(r, output):
   # Redirect to read/edit view after create rather than to list view
   if r.representation == "html" and r.method == "create":
       r.next = r.other(method="", record_id=s3xrc.get_session(session,
module, resource))
   return output
response.s3.postp = postp

Passing information between main Controller & Prep

Scope normally means that these 2 sections can only talk to each other via globals or the Request object.

If you need to pass data between them, you can use this trick:

vars = {} # the surrounding dict
def prep(r, vars):
    vars.update(x=y) # the actual variable to pass is x
    return True

response.s3.prep = lambda r, vars=vars: prep(r, vars)

output = shn_rest_controller(module, resource)

x = vars.get(x, None)

An example usage is in controllers/gis.py for location()

S3RESTRequest

Important: S3RESTRequest instances are generated per request and must not be stored or referenced by static or global objects to avoid memory leaks!

Attributes of the S3RESTRequest

The following attributes are set during initialisation of an S3RESTRequest object, no further call is required.

Controller Attributes

rcthe resource controller object (S3XRC)
requestthe original web2py request (Storage)
sessionthe current session (Storage)

Request Attributes

representationthe current representation of this request (string, lowercase)
httpthe HTTP method of this request (string, always uppercase!)
extensionthe extension found in the original request (string, lowercase)
methodthe method of the request if not HTTP (string, always lowercase)
custom_actionthe custom method handler for the request (function or lambda)

Primary Resource Attributes

prefixthe prefix (=module name) of the requested resource (string)
namethe name of the requested resource, without prefix (string)
tablenamethe name of the primary table (string)
tablethe primary table (Table)
idthe ID of the primary record (int)
recordthe primary record (Row)

Component Resource Attributes

componentthe requested component, if any (ObjectComponent)
pkeythe primary key of the Resource/Component join (string)
fkeythe foreign key of the Resource/Component join (string)
component_namethe name of the component without prefix (string)
component_idthe ID of the component record as of the request, if any (int)
multipleFlag indicating that multiple component records are allowed (boolean)

Error Indicators

errorthe last error message (string)
invalidFlag indicating this request as invalid (boolean)
badmethodFlag indicating a bad method error (boolean)
badrecordFlag indicating a invalid record ID error (boolean)
badrequestFlag indicating an unqualified request error (boolean)

ObjectComponent contains:

  • prefix, name, tablename, table and attr of the component
  • attr contains:
    • multiple Multiple-flag
    • editable, deletable and listadd
    • list_fields, rss, main and extra
    • onaccept, onvalidation, delete_onaccept and delete_onvalidation
  • methods: set_attr() and get_attr()

Methods of the S3RESTRequest

Magic URLs

here(representation=None)

  • returns the URL of the current request

there(representation=None)

  • returns the URL of a HTTP GET request for the same resource

same(representation=None)

  • returns the URL of the current request with the primary record ID replaced by the string literal '[id]'

other(method=None, record_id=None, representation=None)

  • returns the URL of a request with another method and record ID, but for the same resource

Other functions

target()

  • returns the target table of the current request as tuple of (prefix, name, table, tablename)

export_xml(permit=None, audit=None, template=None, filterby=None, pretty_print=False)

  • exports the requested resources as XML

export_json(permit=None, audit=None, template=None, filterby=None, pretty_print=False)

  • exports the requested resources as JSON

import_xml(tree, permit=None, audit=None, onvalidation=None, onaccept=None)

  • import the requested resources from the given ElementTree

options_xml(pretty_print=False)

  • exports the options of a field (or all fields) in the resource as XML

options_json(pretty_print=False)

  • exports the options of a field (or all fields) in the resource as JSON

DeveloperGuidelines

Attachments (1)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.