How To Write Methods For Your API

Outline

Create api.py for any application which needs an api.

Create yourt methods:

import api
from api.models import Role

@api.method:
def describe_site(rl:Role)->str:
    return 'My wonderful site'

Parameters and Return Values

These types are allowed:

  • None
  • str
  • bytes
  • bool
  • int
  • float
  • datetime sent as a float number of seconds since 1 Jan 1971
  • Enums
  • a models.Model prepared for use in your API - see here for details. A models.Model configured for API are also known as a resource.
  • @dataclass classes
  • List[]s
  • Dict[]s
  • api.Json for treating dicts & lists as json.
  • api_api.FileUpload

Defining Methods

  • Your site's methods are expected to be in api.pys in your site's applications
  • All methods must be decorated @api.method
  • All methods must have rl:Role as their first parameter
  • All your methods' arguments must be type hinted
  • All your methods' return values must be type-hinted

Arguments of @api.method

name
the name for the method in the api. If name is not given, the python function's name is used.
require_host (None)

This allows a check on request.META['HTTP_HOST']. This can be:

str
The host
      @api.method(require_host='dev.mysite.com')
      def describe_local_IP()->str
      ...
tuple, list or set of str
The hosts to require
      @api.method(require_host=['dev.mysite.com'])
      def describe_local_IP()->str
      ...
A callable require_host(host:str)->bool
Return True if the host is allowed, False otherwise
@api.method(require_host=[lambda host:host == 'dev.mysite.com'])
def describe_local_IP()->str
...
require_auth (default True)
Set to False to allow this method for all callers regardless of what authorisation, if any, has been provided. When this is False, user_check and read_only_resources are ignored. Use require_auth=False with caution!
user_check (default None).
A callable, user_check(user:UserModel)->bool, which should return True if the user is allowed to call this method. For example, some methods might only be allowed for calls between instances serving your website:
    user_check = lambda user: return user == home.site_user
read_only_resources (None, all resources are changed)

describes which resources in the parameters are read only, ie not changed. This is used for checking a caller's permission. It can be:

str of '0's and '1's
Each character corresponds to a passed-in resource, where '1' means the resource is left unchanged by this method.
@api.method(read_only_resources='01')
def modify_user_favourite(user:UserModel, repo:Repo, favourite:bool)->None
...
A list, tuple or set of classes
Each resource type listed is left unchanged by this method.
@api.method(read_only_resources=[Repo])
def modify_user_favourite(user:UserModel, repo:Repo, favourite:bool)->None
...
  • a callable read_only_resources(resources:List[resource])->str

    Return a str of '0's and '1's. Each '0' and '1' corresponds to a passed-in resource, where '1' means the resource is left unchanged by this method.

    @api.method(read_only_resources=lambda resources:'01') def modify_user_favourite(user:UserModel, repo:Repo, favourite:bool)->None ...

resource_access (None, all resources are fully accessed)

describe the access to the passed-in resources. Access is classified into public, read or full. Public is more permissive than read access, for example describe_user might require read access, as it gives information, such as the user's email address, which the user might not want public, whereas describe_user_brief might only give the username, which is generally known. It can be:

str of 'p's, 'r's or 'f's
Each character corresponds to one resource argument.
{str: access}
Gives the access for each argument. Detailed access for members of arguments can be given. Examples: if owner is a modified resource, the 'owner': 'f',; suppose argument fred is a list of resources which are only read, then 'fred[]': 'r',; suppose betty is a dataclass with resource field owner used publicly, then 'betty.owner': 'p',; suppose complex is a dictionary with resource keys publically used and resource values modified, then 'complex[key]': 'p', 'complex[]: 'f','.
{type: access}
Gives the access for each resource type.
prefer_query (())
A tuple of the names of the arguments which your site would prefer to be sent as query parameters instead of as POST fields. Query parameters have limited length and appear in server logs, so there are only exceptional circumstance when your site might need this (eg a request reverse proxy redirection needs access to an argument to find the host to redirect to).
external (True)
Whether this method should be available through the site's http api. Internal methods can only be called as python functions.
ignore_resource_policies (False)
Whether a resource's Policy's should be ignored for this method. The expected use case are the methods which adjust a resource's Policy list. If a resource has a Policy added which is irrelevant to its Policy-adjusting method then, if the Policy was followed, the method would become blocked, and so the resource's Policy list would become stuck. To prevent this, mark these methods ignore_resource_policies=True.

File Uploads

Your API can receive file uploads by using the type api_api.FileUpload. Your methods will receive a FileUpload. A FileUpload has these properties:

name
the name of the file
file
a file with the file contents
content_type
the content-type of the file

To send files, pass FileUploads to the api_api API for your site

At time of writing requests brings the file into memory before sending it, so avoid sending huge files this way.

At time of writing multi-select <input type="files" .. are not supported through api_api - this is a limitation of requests.

[note to author from author:

To fix requests's sending of files to avoid bringing the whole file into memory first:

  • change urllib3.filepost.encode_multipart_formdata to prepare the body to be a file-like object which returns a stream of bytes. This stream should be the append of a series of bytes and file-like objects - reading from the file-like objects only when the stream needs it.

  • change requests.models.RequestEncodingMixin._encode_files to set fdata = fp not fdata = fp.read()

]

Properties of Decorated Methods

A decorated method has these properties:

check_permission(role, ...)
same parameters as the function itself - permissions are checked, but the function is not called.