Documented Python component example

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)