Fully documented Python component example
from restx.components.api import * # Imports all aspects of the API
#
# If you want to handle or throw RESTx specific exceptions, you might also want
# to import this:
#
# from org.mulesource.restx.exception import *
#
# -------------------------------------------------------------------
# Components
#
# In order to define a new component, you merely have to derive a new
# class from BaseComponent. Then you use certain class attributes with
# well known names to tell RESTx about your component.
#
# BaseComponent has a parameterless, default __init__() method. It is
# recommended that you do not defined your own.
#
class SampleComponent(BaseComponent):
# -----------------------------------------------
# Tell RESTx some information about this component.
#
# The following are well-known names that RESTx
# is looking for when dealing with a component.
#
# Name, description and doc string of the component as it should appear to the user.
# Note that NAME does not have to be the same as the class name.
NAME = "SomeSampleComponent"
DESCRIPTION = "One line description of the component"
DOCUMENTATION = "Longer description text possibly multiple lines."
# ---------------------------------------------------------------
# Resource creation time parameters
#
# Those parameters are required in order to create a resource for
# this component. As component author, you provide a dictionary
# in PARAM_DEFINITION, which contains the names and definitions
# of each parameter.
#
# When a resource is accessed and an instance of your component is
# invoked then these fields are initialized by RESTx to the
# values that were provided for them during resource creation time.
# That way you get automatic and easy access to resource creation
# time parameters from within your service methods.
#
# Each parameter is defined through a ParameterDef object, which
# encapsulates the definition of the parameter. The PARAM_* argument
# determines the type of the parameter. Currently, we know PARAM_STRING,
# PARAM_NUMBER and PARAM_BOOL.
PARAM_DEFINITION = {
# With 'required' flag set, this parameter is mandatory. No default
# value needs to be specified.
"some_parameter" : ParameterDef(PARAM_STRING, "Short description of this parameter",
required=True),
# With 'required' flag not set, the parameter is optional. A default value
# must then be provided.
"another_parameter" : ParameterDef(PARAM_NUMBER, "Short description of this parameter",
required=False, default=123.4)
}
# ---------------------------------------------------------------
# Service method definitions.
#
# Each service method represents a sub-resource of whichever resource
# was created for this component.
#
# For example, if the resource URI is /resource/foo then the name of
# each service method appears as a possible sub-resource. If you have
# service methods 'bar1' and 'bar2' then the URLs of those sub-resources
# are /resource/foo/bar1 and /resource/foo/bar2.
#
# Service methods (and sub-resources) are needed in order to do something
# with a resource, since access to the base URL (/resource/foo) only
# gives you information about the resource.
#
# A full service method definition consists of an entry in the SERVICES
# dictionary, where the name has to match the name of the actual function.
# This service dictionary then has to have at least a "desc" entry, which
# contains a short human readable description of the service method.
#
# The service definition may also contain a "param" entry, if the service
# accepts any parameters. Parameters are then defined as entries in the
# "param" dictionary (also of types String, Boolean, Number), using the
# same type of ParameterDef objects, which are used for resource-creation
# time parameters. These parameters can then be passed on the URI command
# line: /resource/foo/bar1?p1=foo
#
# In addition, service methods may define the accepted input and output
# mime types. Please see the comments below for more details.
#
# Finally, a service definition may also contain a field called
# "positional_params". This allows the component author to list those
# service method parameters, which are allowed to appear in the URI
# path component.
#
# For example, if your service method 'bar1' declares three additional
# parameters (p1, p2 and p3) and p1 and p3 are listed in "positional_params"
# then you can have a URI like this:
# /resource/foo/bar1/p1value/p3value?p2=p2value
# But even positional parameters may still be set ordinarily in the
# query portion of the URI, just like 'p2' is in our example.
#
#
SERVICES = {
# Key into the dictionary is the service name. Has to be an
# exact function name.
"some_subresource" : {
# A human readable, brief description of the service.
"desc" : "This is the XYZ sub resource",
# Definition of all parameters that this service accepts and which
# therefore are exposed on the URI command line. Can be skipped if
# the service does not take parameters.
"params" : {
# Names of these parameters need to match the names of the
# service method.
"text" : ParameterDef(PARAM_STRING, "This is a text parameter",
required=True), # is required, so no default needed
"num" : ParameterDef(PARAM_NUMBER, "A numeric parameter",
required=False, # not required, so a default is needed
default=10)
},
# Define the list of positional parameters in their desired order
# Note that positional parameters may either be set with query-type
# arguments on the URI command line, or as path elements in the URI.
"positional_params": [ "num" ],
# The service definition may contain attributes for defining the supported
# input and output MIME types. They are presented as a list of the
# official content-type names. In their absence, RESTx uses all its supported
# types as a default. Based on the specified output types, RESTx performs
# content negotiation with the client. It automatically attempts to format
# the result of a service method according to the agreed upon format.
# Likewise, input (the message body) sent by the client is interpreted
# according to the specified input type and converted into an actual object -
# such as a map or dictionary - before it is being passed to the service method.
# If the service method should not accept input or produce output, just set
# 'None' as the value instead of a list.
input_types = None, # In this example, don't accept any input
output_types = [ "application/json", "text/html" ]
}
}
# ------------------------------------------------------------------
# Here now the actual service methods.
#
# Service methods always take at least two parameters: The HTTP request
# method and an input. The input is merely the body of the HTTP request
# (in case of PUT or POST) interpreted according to the specified input
# types. It is null when another method is used.
#
# Any additional parameters that a service method expects need to be
# defined in SERVICES["<service method name>"]["params"].
#
# Service methods always return a Result object, which allows you to
# set a specific HTTP status code, attach custom response headers and
# arbitrary items of return data. More example of that below.
#
def some_subresoure(self,
method, input, # These two parameters always need to be present
text, num): # These are the additional parameters for this service, specified above
"""
The method that provides the XYZ sub resource.
@param method: The HTTP request method. Always passed to service methods.
@type method: string
@param input: Any data that came in the body of the request. Always passed
to service methods.
@type input: an object representing the input after transformation according to
the indicated content type, or None
@param text: A text parameter. Specific to this particular service method.
@type text: string
@param num: A numerical parameter. Specific to this particular service method.
@type num: number
@return: The output data of this service.
@rtype: Result
"""
# -------------------------------------------
# Any kind of processing may take place here.
# -------------------------------------------
# -----------------------------------------------
# BaseComponent provides a few facilities to make
# life easier for the component author.
# -----------------------------------------------
# HTTP access to external resources
# ---------------------------------
#
# Need to access an external web-site? You can use any kind of client library
# you like, but for your convenience, BaseComponent provides some helper methods.
#
# Use the httpGet() method, like so:
#
# (status, data) = self.httpGet("http://example.com")
# or
# (status, data) = self.httpGet("http://example.com", headers)
#
# where 'headers' is a dictionary of additional HTTP request headers.
#
# Use httpPost() to send data, like so:
#
# (status, data) = self.httpPost("http://example.com", "data to send")
#
# Just like with httpGet(), you may specify additional headers.
#
# If your request require HTTP basic authentication, you can just use the
# setCredentials() function once:
#
# self.setCredentials("username", "password")
#
# Subsequent httpGet() and httpPost() requests will use those credentials.
# File storage
# ------------
#
# No restrictions are placed on the component code. Therefore, you can access
# files and other storage any way you see fit.
#
# However, you often need means to store some simple data, which should persist
# between accesses of the same resource. You should be able to create, list,
# access and delete those stored items. An example of this is a queue or 'bag'
# resource, which provides service methods to store, list and access items
#
# This is a common pattern, so BaseComponent provides some simple facilities
# to help with that.
#
# Use getFileStorage() to retrieve handle on your very own storage space. This
# is per-resource. So, other components (for other resources), or even the same
# component (if used behind a different resource) cannot see any of the items
# you have stored for this resource.
#
# fs = getFileStorage()
# or
# fs = getFileStorage("namespace")
#
# The latter form allows you to sub-divide your resource's name space further.
# You could maintain different name spaces for different items or service
# methods, for example.
#
# Once you have a handle on the file storage, you can use these very simple
# methods:
#
# buf = fs.loadFile("name") # returns a string buffer
# fs.storeFile("name", "content of file as string")
# fs.deleteFile("name")
# names = fs.listFiles() # returns a list of names
#
# Accessing other resources
# -------------------------
#
# It is possible to easily access other resources that live on the same server.
# For this, use the accessResource() method. Note that this is part of the
# 'api' module and thus does not need to be called with a 'self.' prefix:
#
# (status, data) = accessResource("/resource/blah/foo")
# or
# (status, data) = accessResource("/resource/blah/foo", "some data")
#
# In the latter case, the second parameter is the data that should be POSTed.
#
# If you want to use a different HTTP method, you can do:
#
# (status, data) = accessResource("/resource/blah/foo", "some data", HTTP.PUT)
#
# Note: The HTTP method is specified via the HTTP class, which is part of the
# component API. Other methods are 'GET', 'POST' and 'DELETE'.
#
# Parameters can be passed to the resource either in the URI:
#
# (status, data) = accessResource("/resource/blah/foo?p1=v1")
# or as a dictionary:
# (status, data) = accessResource("/resource/blah/foo", params={ .... })
#
# The 'data' element of the returned tuple can contain the same type of
# data you prepare for a service methods's return value. This is described
# in detail below...
# -------------------------
# Preparing the return data
# -------------------------
#
# A service method has to return a Result object. A Result object carries
# information about the desired HTTP status code, custom headers as well
# as any return data we might want.
#
# You can create a result object directly, like so:
#
# res = Result(200, "No problem")
#
# Or you can use one of several convenient factory methods, which return
# a Result object pre-initialized with the correct status code and possibly
# additional response headers. For example:
#
# Result res = Result.ok("No problem") # same as above
# or
# res = Result.created("/resource/foo/blah")
# res = Result.notFound("couldn't find the item")
# res = Result.badRequest("your request was bad")
# res = Result.noContent()
# res = Result.temporaryRedirect("http://example.com")
# res = Result.internalServerError("some big issues")
#
# Once you have created a Result object, you can also add your own return
# headers to it:
#
# res.addHeader("header-name", "value")
#
# Use the getStatus() and setStatus() methods of the Result object to get
# or set the status manually.
#
# Use getEntity() or setEntity() to get or manually set the return data.
#
# Return data can be an object of the following types: string, boolean,
# number, dict or list. Dictionaries or lists may contain elements
# of any of these types, including further dictionaries or lists. Thus,
# it is possible to assemble lists or complex, hiearchical data structures
# as return values.
#
# In this example we return a dictionary that contains another dictionary
# RESTx renders this automatically, according to the negotiated output
# content type.
data = dict()
data["foo"] = "This is a test"
sub = dict()
data["bar"] = sub
sub["number value"] = 1
return Result.ok(data)