APIMAS supports the creation of client-side applications to interact with the REST API described by your specification. There is a client-adapter which is responsible for the conversion of specification into implementation. The logic behind this conversion is similar with that of server-side applications which use the corresponding adapter.
The ApimasClientAdapter
is the bridge between specification and
python objects which represent the client of each collection. In
other words, these clients enable you to interact with a REST API
programmatically.
Therefore, given the specification below, you use this class to
construct client objects. This class is initialized with the root url
of the server we want to interact. In the end of the construction
process, the client objects have been constructed and you can extract
a client object for a particular collection via
adapter.get_client()
. This object provides you the following
methods to interact with the API:
list()
retrieve()
create()
update()
partial_update()
delete()
from apimas.clients import ApimasClientAdapter
API_SPEC = {
'api': {
'.endpoint': {},
'foo': {
'.collection': {},
'*': {
'text': {
'.string': {}
},
'number': {
'.integer': {},
},
},
'actions': {
'.list': {},
'.retrieve': {},
'.create': {},
'.update': {},
'.delete': {},
}
}
}
}
adapter = ApimasClientAdapter('http://localhost:8000')
adapter.construct(API_SPEC)
clients = adapters.clients
foo_client = adapter.get_client('foo')
data = {'text': 'bar', 'integer': 1}
# Create a new foo resource object.
# It performs a POST http://localhost:8000/foo/ request.
response = foo_client.create(data=data)
print response.data, response.status_code
# List resources of foo collection.
# It performs a GET http://localgost:8000foo/ request.
response = foo_client.list()
print response.data, response.status_code
ApimasClientAdapter
uses python
requests to make
the necessary HTTP calls and
cerberus for
validating data.
Before you interact with the API, you may want to authenticate your
party. For this reason, client objects generated by the
client-adapter provide method set_credentials
with the following
signature:
def set_credentials(self, auth_type, **credentials):
...
You have to provide the type of the authentication, e.g. basic, token, etc. and your credentials.
Example:
client = adapter.get_client('foo')
client.set_credentials('basic', username='foo',
password='passoword')
client.retrieve('1')
Before retrieving a single resource, we had to set our credentials according to the specified authentication mode. Each authentication mode supports different credentials schema. For instance, if you use basic authentication, you must provide a username and a password.
Supported authentication modes:
Authentication Mode | Credentials Schema |
---|---|
basic |
|
token |
|
In case you wish to create a command line interface (CLI) for your
client-side application, APIMAS offers a built-in adapter which
creates the CLI for you based on your specification. This is
ApimasCliAdapter
class which introduces two new predicates
a) .cli_commands
, b) .cli_option
.
But first, you have to create a configuration file, say .apimas
on a directory of your choice, written in yaml syntax.
For example, in myloc/.apimas
:
root: http:localhost:8000
spec:
api:
.endpoint: {}
foo:
.collection: {}
.cli_commands: {}
'*':
text:
.cli_option: {}
.string: {}
number:
.cli_option: {}
.integer: {}
actions:
.list: {}
.retrieve: {}
.create: {}
.update: {}
.delete: {}
The CLI-adapter constructs a set of commands for every collection based on that file. For example, for the collection foo, we have the following commands corresponding to every action as specified on specification:
apimas --config myloc/.apimas api foo-list
apimas --config myloc/.apimas api foo-retrieve
apimas --config myloc/.apimas api foo-create
apimas --config myloc/.apimas api foo-update
apimas --config myloc/.apimas api foo-delete
Apparently, these five commands use the same client object internally,
that is, the client object which is responsible for interacting with
the collection foo
. Option --config
tells apimas where to find
the configuration file. Note that sub-command api
stands for the
endpoint (i.e. api
) in which collection is located.
Also note that if one action is not specified on specification, the
corresponding command is not created. For instance, if we remove the
.list
predicate, there will not be the apimas foo-list
command.
Generally, the generated command has the following format:
apimas <endpoint> <collection>-<action> --<option1> --<option2>
For write-actions, i.e. create and update, you have to pass some data
according to the data description of your collection (i.e. fields).
For this purpose, you have to create some command options by enriching
your specification using .cli_option
predicate. This tells adapter
to create an option for the command, keeping all the other properties
of the node. For instance, the presence of .required
predicate
will make the option required, etc.
Example:
apimas api foo-create --text foo --number 1
In the above example, we use the foo-create
command to create a
new resource of collection foo, setting text as foo and number as 1.
Also note that it is not necessary for the names of command-line
options and fields to be verbatim equal.
Example:
root: http:localhost:8000
spec:
api:
.endpoint: {}
foo:
.collection: {}
.cli_commands: {}
'*':
text:
.cli_option:
option_name: text-option
.string: {}
number:
.cli_option:
option_name: number-option
.integer: {}
actions:
.list: {}
.retrieve: {}
.create: {}
.update: {}
.delete: {}
In the above example, we specified the parameter option_name
in
.cli_option
predicate which defines the name of the command
option and it creates a mapping with the name of the API field.
apimas api foo-create --text-option foo --number-option 1
However, the HTTP request which is going to be made by the client, has still the structure as defined by the specification.
Imagine we have two more fields which describe the collection foo.
One is a .struct
(i.e. field “foo”) and the other is
.structarray
(i.e. field “bar”).
root: http:localhost:8000
spec:
api:
.endpoint: {}
foo:
.collection: {}
.cli_commands: {}
'*':
text:
.cli_option: {}
.string: {}
number:
.cli_option: {}
.integer: {}
foo:
.cli_option: {}
.struct:
age:
.cli_option: {}
.integer: {}
name:
.cli_option: {}
.string: {}
bar:
.cli_option: {}
.structarray:
age:
.cli_option: {}
.integer: {}
name:
.cli_option: {}
.string: {}
actions:
.list: {}
.retrieve: {}
.create: {}
.update: {}
.delete: {}
The command options are created as follows:
.struct
, a command option for every nested field
prefixed by the name of parent node is created..structarray
, a single command option is created
which takes a JSON as input.Example:
apimas api foo-create --foo-age 1 --foo-name myname --bar '[{"age": 1, "name": "myname"}]'
Commands performed on single resources, have a required command argument which is the identifier of the resource to the set of the collection.
Example:
apimas api foo-update bar --data foo --number 1
apimas api foo-retrieve bar
apimas api foo-delete bar
We performed update, retrieve and delete actions on a resource of collection foo, identified by the name “bar”.
If you want to provide your credentials in order to be authenticated
before interacting with your collection, you have to enrich your
specification, using .cli_auth
predicate. The .cli_auth
predicate creates a new required option named --credentials
for every command of your collection. This command options takes a
file path as input. This points to a file where your credentials are
provided. The format of your file is indicated by the parameter
format
inside .cli_auth
. The supported formats are a) yaml,
b) json. In addition, this file must provide your credentials
based on the credentials schema which you have specified on your
specification.
Example:
root: http:localhost:8000
spec:
api:
.endpoint: {}
foo:
.collection: {}
.cli_commands: {}
.cli_auth:
format: yaml
schema:
basic:
-username
-password
'*':
text:
.cli_option: {}
.string: {}
number:
.cli_option: {}
.integer: {}
actions:
.list: {}
.retrieve: {}
.create: {}
.update: {}
.delete: {}
Then, your file where your credentials are stored should be as follows:
mycredentials.yaml
basic:
username: myusername
password: mypassword
Now you are ready to execute all commands:
apimas api foo-list --credentials ~/mycredentials.yaml
apimas api foo-retrieve bar --credentials ~/mycredentials.yaml
apimas api foo-create --text foo --number 1 --credentials ~/mycredentials.yaml
apimas api foo-update bar --text foo --number 1 --credentials ~/mycredentials.yaml
apimas api foo-delete bar --credentials ~/credentials.yaml
If you need multiple authentication modes, then you should specify all
of them on your specification. Then, you should add the .cli_auth
predicate to your specification. In the following example, a client can
be authenticated with two possible authentication modes, i.e. basic
and token
.
.cli_auth:
format: yaml
schema:
basic:
-username
-password
token:
-token
In this case, you can provide credentials for both authentication
modes on your credentials file. However, only one authentication
mode is used each time. You can select which one you want to use by
specifying default
. If default is not specified, then the first
authentication mode is used.
For example:
credentials.yaml
default: token
basic:
username: myusername
password: mypassword
token:
token: mytoken