Generating a REST API

The API class can be used to expose an Artisan context via HTTP:

#-- rest.py --#
from artisan import API

api = API()

if __name__ == '__main__':
    api.serve(port=8000)

APIs can also be used with third-party WSGI servers like Gunicorn, which supports useful features like worker pools and hot-reloading:

> gunicorn rest:api --workers=8 --reload

By default, an API exposes the context in which it is constructed, but context attributes can be overridden by passing options to the API constructor:

api = API(root = 'path/to/artifacts', # overrides `<active_context>.root`
          scope = {'DataBlob': my_lib.DataBlob}, # overrides `<active_context>.scope`
          builder = my_lib.bespoke_artifact_builder) # overrides `<active_context>.builder`

Supported request types

  • GET /artifacts{/path-to-file*}: Responds with the file at the specified path, relative to the context’s root directory. The path’s extension is inferred. A “Last-Modified” header is provided and “If-Modified-Since” request headers are supported. Contiguous range requests are also supported.

  • GET /artifacts{/path-to-directory*}: Responds with a shallow, CBOR-encoded description of the artifact at the specified path, relative to the context’s root directory. The path’s extension is inferred. The response body is a mapping with (1) a “_meta_” key mapped to the contents of the artifact’s _meta_.json file, if it exists, and null, otherwise, and (2) keys corresponding to every public attribute in the corresponding artifact, mapped to null. A “Last-Modified” header is provided and “If-Modified-Since” request headers are supported.

  • POST /artifacts: Creates a new artifact from a CBOR-encoded specification (the request body), if it does not already exist.

  • DELETE /artifacts{/path*}: Deletes the artifact or artifact entry at the specified path, relative to the context’s root directory. The path’s extension is inferred.

  • GET /schemas/spec: Responds with artisan.get_spec_schema(), as a JSON object.

  • GET /schemas/spec-list: Responds with artisan.get_spec_list_schema(), as a JSON object.

  • GET /schemas/spec-dict: Responds with artisan.get_spec_dict_schema(), as a JSON object.

  • GET /ui{/path*}: Responds with a user-interface resource.

Analogous HEAD and OPTIONS requests are also supported.

Authentication

APIs can be constructed with a permissions argument to add password protection:

api = API(permissions={
    '🗝🔑🗝🔑🗝': {'read'}, # OPTIONS, HEAD, and GET access
    '0pen_5esame': {'read', 'write'}, # OPTIONS, HEAD, GET, and POST access
    'correct-cheval-batterie-agrafe': {'read', 'write', 'delete'}}) # Full access

permissions is a mapping from passwords to permission sets. Every entry in permissions grants the specified permissions to clients that provide the specified password. Passwords must be provided as authentication headers using the “Basic” authentication scheme (IETF RFC 7617) with an empty username. Permissions sets can contain “read”, “write”, and/or “delete”. The default permission policy is {'': ('read', 'write', 'delete')}, meaning even users who have not provided a password have “read”, “write”, and “delete” permissions.

permissions can only provide a reasonable level of security if the API is exposed via HTTPS. Let’s Encrypt provides free digital certificates that can be used to serve HTTPS applications.