To validate the parameters we can use forms, it's their job after all:

classListColoursForm(TableForm):fields=[# One field per parametertwf.TextField("filter",help_text="Please enter the string to use as a filter"),twf.TextField("productID",help_text="Please enter the product ID"),twf.TextField("maxResults",validator=twfv.Int(min=0),default=200,size=5,help_text="Please enter the maximum number of results"),]list_colours_form=ListColoursForm()#...@expose("json")@validate(list_colours_form,error_handler=list_colours_validation_error)deflist_colours(self,filter=None,productID=None,maxResults=100,**kw):# Parameter validation is done by the form# Call APIres=self.engine.list_colours(filter,productID,maxResults)# Return resultreturnres

All straightforward so far. However, this means that we need two exposed
methods for every API call: one for the API call and one error handler.
For every API call, we have to type the name several times, which is error
prone and risks to get things mixed up.

We can however have a single error handler for all methonds:

defget_method():''' The method name is the first url component after the controller name that does not start with 'test' '''found_controller=Falsefornameinpylons.c.url.split("/"):ifnotfound_controllerandname=="controllername":found_controller=Truecontinueifname.startswith("test"):continueiffound_controller:returnnamereturnNoneclassValidatorDispatcher:''' Validate using the right form according to the value of the "method" field '''defvalidate(self,args,state):method=args.get("method",None)# Extract the method from the URL if it is missingifmethodisNone:method=get_method()args["method"]=methodreturnforms[method].validate(args,state)validator_dispatcher=ValidatorDispatcher()

This validator will try to find the method name, either as a form field
or by parsing the URL. It will then use the method name to find the form to use
for validation, and pass control to the validate method of that form.

We then need to add an extra "method" field to our forms, and arrange the forms
inside a dictionary:

classListColoursForm(TableForm):fields=[# One hidden field to have a place for the method nametwf.HiddenField("method")# One field per parametertwf.TextField("filter",help_text="Please enter the string to use as a filter"),#...forms["list_colours"]=ListColoursForm()

api_validation_error is interesting: it returns a proper HTTP error status,
and a JSON body with the details of the error, taken straight from the form
validators. It took me a while to find out that the form errors are in
pylons.c.form_errors (and for reference, the form values are in
pylons.c.form_values). pylons.response is a WebOb Response that we can play with.

So now our client side is able to call the API methods, and get a proper error
if it calls them wrong.

But now that we have the forms ready, it doesn't take much to display them in
web pages as well:

def_describe(self,method):"Return a dict describing an API method"ldesc=getattr(self.engine,method).__doc__.strip()sdesc=ldesc.split("\n")[0]returndict(name=method,sdesc=sdesc,ldesc=ldesc)@expose("myappserver.templates.myappapi")defindex(self):''' Show an index of exported API methods '''methods=dict()forminforms.keys():methods[m]=self._describe(m)returndict(methods=methods)@expose('myappserver.templates.testform')deftestform(self,method,**kw):''' Show a form with the parameters of an API method '''kw["method"]=methodreturndict(method=method,action="/myapp/test/"+method,value=kw,info=self._describe(method),form=forms[method])@expose(content_type="text/plain")@validate(validator_dispatcher,error_handler=testform)deftest(self,method,**kw):''' Run an API method and show its prettyprinted result '''res=getattr(self,str(method))(**kw)returnpprint.pformat(res)

In a few lines, we have all we need: an index of the API methods (including
their documentation taken from the docstrings!), and for each method a form to
invoke it and a page to see the results.

Make the forms children of AjaxForm, and you can even see the results together
with the form.