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, andnull
, otherwise, and (2) keys corresponding to every public attribute in the corresponding artifact, mapped tonull
. 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 withartisan.get_spec_schema()
, as a JSON object.GET /schemas/spec-list
: Responds withartisan.get_spec_list_schema()
, as a JSON object.GET /schemas/spec-dict
: Responds withartisan.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.