Tuning your API

Sometimes the defaults piston_mini_client provides won’t be what you need, or you just need to customize the requests you send out for one part or another of your API. In those cases, piston_mini_client will strive to stay out of your way.

Handling Failure

A common issue is what to do if the webservice returns an error. One possible solution (and the default for piston-mini-client) is to raise an exception.

This might not be the best solution for everybody, so piston-mini-client allows you to customize the way in which such failures are handled.

You can do this by defining a FailHandler class. This class needs to provide a single method (handle), that receives the response headers and body, and decides what to do with it all. It can raise an exception, or modify the body in any way.

If it returns a string this will be assumed to be the (possibly fixed) body of the response and will be deserialized by any decorators the method has.

To use a different fail handler in your PistonAPI set the fail_handler class attribute. For example, to use the NoneFailHandler instead of the default ExceptionFailHandler, you can use:

class MyAPI(PistonAPI):
    fail_handler = NoneFailHandler
    # ... rest of the client definition...

piston-mini-client provides four fail handlers out of the box:

  • ExceptionFailHandler: The default fail handler, raises APIError if anything goes wrong
  • NoneFailHandler: Returns None if anything goes wrong. This will provide no information about what went wrong, so only use it if you don’t really care.
  • DictFailHandler: If anything goes wrong it returns a dict with all the request and response headers and body, requested url and method, for you to debug.
  • MultiExceptionFailHandler: Raises a different exception according to what went wrong.

Talking to dual http/https apis

Often your API provides a set of public calls, and some other calls that are authenticated.

Public calls sometimes are heavily used, so we’d like to serve them over http. They’re public anyway.

Authenticated calls involve some sensitive information passing with the user’s credentials, so we like serving those over https.

Once you’ve got all this set up on the server, you can ask piston_mini_client to make each call using the appropriate scheme by using the scheme optional argument when you call _get, _post, _put or _delete:

class DualAPI(PistonAPI):
    default_service_root = 'http://myhostname.com/api/1.0'

    def public_method(self):
        return self._get('public_method/', scheme='http')

    def private_method(self):
        return self._post('private_method/', scheme='https')

    def either(self):
        return self._get('either/')

In this case, no matter what scheme the service root uses, calls to public_method() will result in an http request, and calls to private_method() will result in an https request. Calls to either() will leave the scheme alone, so it will follow the scheme used to instantiate the api, or fall back to default_service_root‘s scheme.

Customizing request headers

If you need to send custom headers to the server in your requests you can specify these both in the PistonAPI instance as an instance or class variable, or when you make calls to _get, _post, _put or _delete. Specifying headers as a class variable will add the same custom headers to all requests made by all instances of the class:

class MyAPI(PistonAPI):
    extra_headers = {'X-Requested-With': 'XMLHttpRequest'}
    # ... etc

Here these extra_headers will be added to any and all requests made by MyAPI instances. You could also specify an extra header for a single instance of MyAPI:

api = MyAPI()
api.extra_headers = {'X-Somethingelse': 'dont forget to buy milk'}

In this case you’ll get this extra header in all requests made by this instance of MyAPI.

Finally, you can also pass in an optional extra_headers argument into each call to _get, _post, _put or _delete, if only specific api calls need to be provided additional headers:

class MyAPI(PistonAPI):
    def crumble(self):
        return self._get('crumble')
    def icecream(self):
        return self._get('icecream',
            extra_headers={'X-secret-sauce': 'chocolate'})

Here calls to icecream will use the extra special header, but other calls (like crumble) won’t.

Customizing the serializer for each content type

piston_mini_client provides a set of default serializers, but sometimes you have your own serialization convention, set by the server, and the client just needs to comply. In that case, you can implement your own serializer and add an instance of it to the serializers class attribute.

To define a serializer all you need to provide is a serialize method, that should take a single obj argument and return it serialized into a string:

class ReprSerializer(object):
    def serialize(self, obj):
        return repr(obj)

class MyAPI(PistonAPI):
    serializers = {'application/json': ReprSerializer()}

In this case, any POST/PUT request that goes out with a content type of application/json will use your ReprSerializer for serializing its data into the request body.

If you need to serialize only arguments of a certain specific API call with this special serializer, you can serialize data before calling _post/_put:

class GroceryAPI(PistonAPI):
    def order(self, shopping_list):
        serializer = ReprSerializer()
        self._post('somecall', data=serializer.serialize(shopping_list))

Passing a string into the data argument skips serialization altogether, so you can apply whichever serialization you want before calling _post or _put, and piston_mini_client will avoid double-serializing your request body.

Logging to a file

If you need to debug the actual requests and responses on the wire, you can initialize a PistonAPI passing in a log_filename argument. piston_mini_client will append all requests and responses, including headers, status code and all, to this file.

Also, if you’re debugging an application that uses piston_mini_client but don’t want to (or can’t) start hacking at the code, you can set PISTON_MINI_CLIENT_LOG_FILENAME in the environment to point a file, and all PistonAPI instances will use this location by default. That is, unless they’re explicitly being instantiated to log elsewhere.

Handling timeouts

When you instantiate a PistonAPI you can provide an optional timeout argument that will be used as a socket timeout for the requests that instance makes. To explicitly set no timeout, pass in timeout=0. If you leave the default timeout=None, the instance will first check for an environment variable PISTON_MINI_CLIENT_DEFAULT_TIMEOUT, and if that is undefined or invalid, then the class’s default timeout will be used; this can be defined by setting a default_timeout class attribute when writing the API class. Finally, if the class’s default timeout is also None, Python’s system-wide socket default timeout will be used.

You can’t currently define timeouts on a per-request basis. If you need to change the timeout used for certain requests, you’ll need to use a new PistonAPI instance.