Writing transmute-compatible functions

Note

this section is broadly applicable to all transmute frameworks.

Functions are converted to APIs by using an intermediary TransmuteFunction object.

A transmute function is identical to a standard Python function, with the addition of a few details:

Add function annotations for input type validation / documentation

if type validation and complete swagger documentation is desired, arguments should be annotated with types. For Python 3, function annotations are used.

For Python 2, transmute provides an annotate decorator:

import transmute_core

# the python 3 way
def add(left: int, right: int) -> int:
     return left + right

# the python 2 way
@transmute_core.annotate({"left": int, "right": int, "return": int})
def add(left, right):
    return left + right

By default, primitive types and schematics models are accepted. See serialization for more information.

Use transmute_core.describe to customize behaviour

Not every aspect of an api can be extracted from the function signature: often additional metadata is required. Transmute provides the “describe” decorator to specify those attributes.

import transmute_core  # these are usually also imparted into the
# top level module of the transmute

@transmute_core.describe(
    methods=["PUT", "POST"],  # the methods that the function is for
    # the source of the arguments are usually inferred from the method type, but can
    # be specified explicitly
    query_parameters=["blockRequest"],
    body_parameters=["name"]
    header_parameters=["authtoken"]
    path_parameters=["username"]
)
def create_record(name: str, blockRequest: bool, authtoken: str, username: str) -> bool:
    if block_request:
        db.insert_record(name)
    else:
        db.async_insert_record(name)
    return True

Exceptions

By default, transmute functions only catch exceptions which extend transmute_core.APIException, which results in an http response with a non-200 status code. (400 by default):

from transmute_core import APIException

def my_api() -> int:
    if not retrieve_from_database():
        raise APIException(code=404)

However, many transmute frameworks allow the catching of additional exceptions, and converting them to an error response.

Query parameter arguments vs post parameter arguments

The convention in transmute is to have the method dictate the source of the argument:

  • GET uses query parameters
  • all other methods extract parameters from the body

This behaviour can be overridden with transmute_core.decorators.describe.

Additional Examples

Optional Values

transmute libraries support optional values by providing them as keyword arguments:

# count and page will be optional with default values,
# but query will be required.
def add(count: int=100, page: int=0, query: str) -> [str]:
    return db.query(query=query, page=page, count=count)

Custom Response Code

In the case where it desirable to override the default response code, the response_code parameter can be used:

@describe(success_code=201)
def create() -> bool:
    return True

Use a single schema for the body parameter

It’s often desired to represent the body parameter as a single argument. That can be done using a string for body_parameters describe:

@describe(body_parameters="body", methods="POST"):
def submit_data(body: int) -> bool:
    return True

Multiple Response Types

To allow multiple response types, there is a combination of types that can be used:

from transmute_core import Response

@describe(paths="/api/v1/create_if_authorized/",
          response_types={
              401: {"type": str, "description": "unauthorized"},
              201: {"type": bool}
          })
@annotate({"username": str})
def create_if_authorized(username):
    if username != "im the boss":
        return Response("this is unauthorized!", 401)
    else:
        return Response(True, 201)

note that adding these will remove the documentation and type honoring for the default success result: it is assumed you will document all non-400 responses in the response_types dict yourself.

Headers in a Response

Headers within a response also require defining a custom response type:

from transmute_core import Response

@describe(paths="/api/v1/create_if_authorized/",
          response_types={
              200: {"type": str, "description": "success",
                    "headers": {
                        "location": {
                            "description": "url to the location",
                            "type": str
                        }
                    }
              },
          })
def return_url():
    return Response("success!", headers={
        "location": "http://foo"
    })