Calls to api apis are made with html POSTs:
curl -X POST ... https://mywonderfulsite.com/api/home/describe_site
The arguments are:
* your POST fields
* query parameters
* file uploads (such as from <input type="files"...
)
We recommend you avoid query parameters except for special circumstances. Query parameters have a limited length, and can appear in server logs - logs can be a route for information to escape.
Pass arguments in as you would form fields.
@api.method
def describe_user(rl:Role, user:User)->UserDescription:
...
In your sites web pages:
<form method="POST" action="{% url 'api:home/describe_user' %}>
{% csrf_token %}
<input type="hidden" name="user" value="{{user.api_id}}">
</form>
In curl
curl -X POST -d 'user=u-12346789&authorisation=k-12345679:0123....def' https://mywonderfulsite.com/api/home/describe_user
The result, when successful, is returned as json.
These kinds of single value are supported:
Pass in the value.
...
<input type="hidden" name="username" value="joebloggs">
...
or
...-d 'filter.username=joebloggs&autho...'...
Most single values must be sent only once.
False: 0, False, false, No, no, Off, off
True: 1, True, true, Yes, yes, On, on
To help with checkboxes, you are allowed to send a bool parameter once or twice - the last value is the one used:
...
<input type="hidden" name="active" value="off">
<input type="checkbox" name="active">
...
Pass the enum's name.
Send as a timestamp - seconds since start of 1970 as a float.
Send the api_id of the model - see the section on models for details on how to prepare for this.
These are file uploads sent from your web pages, or api.FileUploads
sent through api_api
.
...
<input type="file" name="upload" />
...
@dataclass
class UserFilter:
name_gt:str
name_lt:str
@api.method
def list_users(rl:Role, filter:UserFilter)->List[User]:
...
In your sites web pages:
...
<input type="text" name="filter.name_gt" value="">
<input type="text" name="filter.name_lt" value="">
...
In curl
...-d 'filter.name_gt=a&filter.name_lt=p&autho...'...
Sometimes, you may need to pass in a structured argiment which has no fields. To do this, send a field with a blank name:
...
<input type="text" name="filter." value="">
...
or
...-d 'filter.=&autho...'...
This marker can be used with fields too:
...
<input type="text" name="filter." value="">
<input type="text" name="filter.name_gt" value="">
<input type="text" name="filter.name_lt" value="">
...
or
...-d 'filter.=filter.name_gt=a&filter.name_lt=p&&autho...'...
@api.method
def list_users(rl:Role, usernames:List[str])->List[User]:
...
In your sites web pages:
...
<input type="text" name="usernames.0" value="joebloggs">
<input type="text" name="usernames.1" value="johndoe">
...
In curl
-d 'usernames.0=joebloggs&usernames.1=johndoe&autho...'...
You may leave out the list indices:
...
<input type="text" name="usernames" value="joebloggs">
<input type="text" name="usernames" value="johndoe">
...
or
...-d 'usernames=joebloggs&usernames=johndoe&autho...' ...
This is intended, when combined with the empty-list indicator, to ease list editing on your web page:
...
<!-- show the usernames list is present -->
<input type="text" name="usernames." value="">
<!-- what's in the usernames list -->
<input type="text" name="usernames" value="joebloggs">
<input type="text" name="usernames" value="johndoe">
...
Your editing code only needs to change which inputs are sent, not the names of them to build the list.
The name is the key's value. The key can only be a single value, such as a string or int.
With dataclasses, lists and dictionaries you may, optionally pass in an an extra field which is nameless with a blank value:
...
<input type="hidden" name="usernames." value="">
...
...-d 'usernames.=&autho...'...
The first such field in your dataclass, list or dictionary will be consumed. It can be used to send a dataclass which has no fields, an empty list or en empty dictionary.
{% csrf_token %}
in Your Web Page Form{% csrf_token %}
to your form) - the Role
will be the user's default Role
.authorisation
ArgumentRole
, and add a key to it, then pass an argument (ie a form field) called authorisation
with the value keyid:keysecret:curl -X POST -d 'authorisation=k-0123456789abcdef:abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST' https://mywonderfulsite.com/api/home/describe_site
NEVER use a query parameter for your authoirsation. It works, but exposes your authorisation in server logs, increasing the chance of escape
Create a Key
for the Role
. Use keyid:keysecret for the user's password:
curl -X POST -u joebloggs:k-0123456789abcdef:abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST -d ... https://mywonderfulsite.com/api/home/describe_site
Arguments to the call are passed as POST fields:
curl -X POST htt...api/home/find_user ... -d firstname=joe&lastname=bloggs
This is not recommended, but you can use the user's username and password. It gives full access, so use it as little as possible to avoid it leaking unexpectedly.
curl -X POST -u joebloggs:password -d ... https://mywonderfulsite.com/api/home/describe_site
In almost all circumstances POST fields are the way you should send arguments - they have no length limit, and won't appear in server logs. However, to illustrate why it might be necessary for your site, here is an example from ours:
An API call to upload a file needs the file to arrive at a particular machine to be stored, and that machine depends on the upload arguments.
The call could have arrived at a front line machine and that could have called the storage machine, and that would have worked, but...
Django stores uploaded files before passing the request to the site's code, so the file would get stored on the front-line machine then forwarded to the destination machine. This would have meant the sites' users waiting lots of time at 100% as the file was forwarded within the site to its final destination. So, ideally the calls should be forwarded by the web server to the destination machine instead of to the local Django instance. To work out the destination machine enough arguments need to be visible to the forwarding code, and query parameters are the only option - the body is not accessible for this decision.
Store your credentials:
in ~/.mywonderfulsite/config
:
[default]
access_key_id = k-0123456789abcdef
access_key_secret = abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST
access_key_id
and access_key_secret
are from the Key
you created - see here. key.api_id
is access_key_id
and from secret = key.create_secret()
the key secret, secret.decode()
, is access_key_secret
.
Call your api methods:
from api_api import API
api = API('http://mywonderfulsite.com/api', ['~/.mywonderfulsite/config'])
site_description = api.home.describe_site()
The return value is turned into python objects:
print(site_description.name)
print(site_description.users.count)
api_api
defines some properties on the methods:
'home.describe_site'
Thread
. This is useful when you don't want to wait for the result.api.home.describe_site.callasync()
The return value is discarded and exceptions are reported on logging.getLogger('api_api')
.
api_roots
. Each call is executed on its own thread.To use a different profile add a profile to your credentials file...
[default]
access_key_id = k-0123456789abcdef
access_key_secret = abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST
[day to day working]
access_key_id = k-fedcba9876543210
access_key_secret = asdfghjklmASDFGHJKLMasdfghjklmASDFGHJKLM
...
...then created a session using the new profile:
session = api(profile='day to day working')
site_description = session.home.describe_site()
api.home.describe_site()
is a shortcut for:
session = api(profile='default')
site_description = session.home.describe_site()
You can give a list of credentaisl files, so you can store your credentials in multiple places.
If you don't want to use config files, you can give the credential directly:
session = api(key_id='k-0123456789abcdef', key_secret='abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST')
site_description = session.home.describe_site()
It is then your choice where you store them.
If you want to add headers to your calls, this can be done in the API
constructor:
api = API(..., headers={'host':'myhost.com'})
and additional headers can be set creating the session:
session = api(..., headers={'host':'mybetterhost.com'})