Creating an API
Overview
The core of the API being created for the network clients is provided
through the APIBase
class. This reference describes
the core APIBase
class, together with example
implementations (sub-classes) from the urest.example
package.
The APIBase
class delegates all network requests to
the RESTServer
class (described separately). During the marshalling of client requests
by the RESTServer
class, the path
portion of the URI is extracted from the client. This path is expected to take the
form of
/< noun >
with the ‘verb’ denoted by the HTTP methods. Each ‘noun’ is implemented by a class
which inherits from APIBase
. The ‘noun’ determines the
resource of the request (i.e. derived class), and the ‘verb’ the action to be
taken on that resource. Also important is the intent of the client as inferred
from the underlying HTTP request, e.g. GET
or PUT
. Taken together the URI and
the client intent are mapped to methods of APIBase
as
follows
Verb | HTTP Method | APIBase method |
---|---|---|
Get | GET |
APIBase.get_state() |
Set | PUT |
APIBase.set_state() |
Update | POST |
APIBase.update_state() |
Delete | DELETE |
APIBase.delete_state() |
Routing Requests
All nouns are registered with RESTServer
in lowercase. Likewise all routing of API requests will also assume that the name of the noun to be used will be in lowercase. Thus, for instance, calling
app.register_noun('leD', SimpleLED(28))
or
app.register_noun('LED', SimpleLED(28))
will all route to the name noun with the canonical name led
. Likewise
the request
GET /lEd HTTP 1.1
or
GET /LED HTTP 1.1
will also route to the same noun with the canonical name led
.
Note
All nouns are checked by RESTServer
, and
nouns will not be properly routed unless APIBase
is an ancestor of the derived class. Thus, for example, the SimpleLED
class above must also be a sub-class
of APIBase
for RESTServer
to route the request properly.
Package and Class Structure
The APIBase
class is an abstract base class and provides minimal services beyond defining the core interface (or protocol) for RESTServer
. As outlined above, this interface is provided though the core class methods as shown below
The API services for the network clients are then expected to be built from the abstract base class of APIBase
. The urest
library provides a number of examples in the urest.examples
package, which illustrate some way of using the library. Many of these are also used by the code in the examples
folder of the source repository. The classes of the urest.examples
package are described later, but can be conceptually defined as follows
Example Usage
Getting the Noun State
This example is based on the SimpleLED
class as a minimal implementation of a noun controlling a GPIO pin, using the
MicroPython Pin
library. The full documentation for the class is detailed below,
and the SimpleLED
is available through the
urest.examples
package.
For the SimpleLED
class, the exact pin
being controlled is set during the object instantiation, via the class
constructor. Assuming the RESTServer
has been
created as
app = RESTServer()
then a ‘noun’ led
can be registered via the RESTServer.register_noun()
method as
app.register_noun('led', SimpleLED(28))
This will also create an instance of the SimpleLED
class, bound to the controlled GPIO
pin Pin 28
. Documentation for the micro-controller and platform being used will
be need to determine suitable values for the GPIO pin numbering.
Once the ‘noun’ has been registered, then the state of GPIO Pin 28
can be
found by the HTTP request
GET /led HTTP 1.1
On receiving the above request from the client, the RESTServer
instance will call the relevant methods of the SimpleLED
(as a sub-class of APIBase
) as shown in Figure 1. Note that the ‘<< resource state>>
’ returned by SimpleLED.get_state()
is as a Python dict[str, Union[str, int]]
. The full return of data to the client, including relevant HTTP headers, conversion to JSON, etc. is handeled by the RESTServer
instance.
Figure 1: A Sequence Diagram for the Noun Get Request of the Example
Setting the Noun State
Setting the state of the noun is a little more involved, as this will require the desired state of the noun to be sent to the server. A useful tool for testing purposes is the curl utility; available on most platforms.
Continuing the minimal example above, the command line
curl -X PUT -d '{"led":"0"}' -H "Content-Type: application/json" http://10.0.30.225/LED
will transmit something like the following HTTP request to
RESTServer
PUT /LED HTTP/1.1
Host: 10.0.30.225
User-Agent: curl/7.81.0
Accept: */*
Content-Type: application/json
Content-Length: 11
{"led":"0"}
JSON is Required
The only format accepted by the RESTServer
for the state of the nouns is JSON. The RESTServer
will also only accept a sub-set of the JSON standard: notably assuming a single object, and a collection of key/value pairs.
Figure 2: A Sequence Diagram for the Noun Set Request of the Example
The exact interpretation of the JSON << resource_state >>
from the Client
in Figure 2 is left to the implementation of the noun. The only internal guarantee provided by the library is the JSON will be parsed as far as possible and sent to SimpleLED.set_state()
, in this case, as a Python dict[str, Union[str, int]]
. For instance in the above example the key led
is interpreted as referring to the noun, and the value 0
as the state value. In this case setting the GPIO Pin 28
to the value 0
(or off
). A close correspondence between the name of the noun as referred to the API, and the name of the noun in the state list is strongly advised: but is not strictly required.
Tested Implementations
This version is written for MicroPython 3.4, and has been tested on:
- Raspberry Pi Pico W
Classes
urest.api.base.APIBase
Define the Abstract Base Class for the nouns, used to structure the response from the server to the client.
This base class defines the minimum interface used in marshalling
requests from the clients by the
RESTServer
class. The API defined
by the server consists of ‘nouns’ representing the resources defined
by the sub-classes of this base class, and the methods which act
upon those resources (which map to the ‘verbs’ of the HTTP requests
made to the RESTServer
class).
Where data is returned to the client by the, e.g by the get_state
method, JSON will ultimately be used as the encoding format.
Sub-classes do not need to implement the saving of the object state to
the client: however to assist they should be aware of the type of
the each value
returned as part of a key/value
pair in the
dictionary. Specifically the data representing the resource state is
expected to be (coerced to) a dict[str, Union[str, int]]
for all
methods. This type implies that the only acceptable ‘key value’ for the
dic
is a Python string, and the ‘value’ itself is either a string
or an integer.
When returning data to the client, the value
of each entry in the
dictionary will attempt to be inferred using the normal Python type
library. If the type can be identified, then an appropriate JSON
type of string
or integer
will be used as appropriate. If the
type for the value
of that dictionary entry cannot be determined,
or cannot be coerced to an integer, then the value will be returned
as a string. Note that string
and integer
are the only types
returned to (and seen from) the client by sub-classes of APIBase
.
Client Data is Handled ‘as is’
No further checking on the validity (or otherwise)
of the content of the dictionary will be undertaken past this
point. Anything that appear to be in a valid dictionary (of type
dict[str, Union[str, int]]
) will be returned to the client. It
is the module consumers responsibility to ensure the returned data
follows the form expected by those clients.
Similar data sent from the client will be passed to sub-classes
of APIBase
as a dictionary of type dict[str, Union[str, int]]
.
If the RESTServer
class cannot
coerce client data into this format it will be dropped and will
not be passed onto sub-classes of APIBase
. In this case an error
will be returned to the client, and the methods of APIBase
will
not be called with the partial data.
Source code in urest/api/base.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
|
Functions
delete_state
delete_state() -> None
Remove the internal state of the resource, essentially ‘resetting’ or re-initialising the object.
The exact mechanism for returning the state to the defaults are left to the implementation. However it is expected that once this call completes the internal state will be identical to that of the default constructor.
Source code in urest/api/base.py
188 189 190 191 192 193 194 195 196 197 198 |
|
get_state
get_state() -> dict[str, Union[str, int]]
Return the state of the resource, as defined by the sub-classes. By
default this method will return the contents of the private
state_attributes
Dictionary
to the client; assuming that
Dictionary
has been appropriately completed in the processing of the
resource.
Returns: |
|
---|
Source code in urest/api/base.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
|
set_state
set_state(
state_attributes: dict[str, Union[str, int]]
) -> None
Set the full state of the resource.
The exact mechanism for updating the internal state of the resource represented
by sub-classes is implementation defined. By default this method expects the
full state to be represented by the state_attributes
parameters; and by
default the new state will be exactly as stated by the Dictionary
passed in
through state_attributes
.
Parameters: |
|
---|
Source code in urest/api/base.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
|
update_state
update_state(
state_attributes: dict[str, Union[str, int]]
) -> None
Update the state of the resource, using the ‘key/value’ pairs of the
Dictionary
in state_attributes
.
The exact mechanism for updating the internal state of the resource represented
by sub-classes is implementation defined. By default this expects to update
only part of the state through the use of the partial state defined in
state_attributes
. This defines a Dictionary
of (key, value) pairs which
can be interpreted by sub-classes to update the true internal state as needed.
Parameters: |
|
---|
Source code in urest/api/base.py
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
|
urest.examples.echo.EchoServer
Bases: APIBase
An example of a ‘noun’ class which echo’s responses to the client. The main use of this library is as a test: it should work on both the Pico (W) boards and any ‘normal’ installation of Python. As such it provides very little functionality, but could in principle also be used as a the basis for a more useful test harness or stub.
For an example of the client part of the ‘echo server’, see the example
echo_server.py
.
A full example of this class is also described in the
Simple Echo Server How-To.
API
The ‘noun’ exposes the following internal state
JSON Key | JSON Type | Description |
---|---|---|
echo |
Integer |
Holds the current state of the resource, determined by the client |
This state can be altered by the client sending either a 0
or 1
to the
noun
: which will record the last value seen from the client. This last
value can then be returned to the client on request. Values greater than 1
will be treated as 1
for the purposes of setting the state.
Source code in urest/examples/echo.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
|
urest.examples.pwmled.PWMLED
Bases: APIBase
Controls an LED by PWM modulation, and gives a reasonably minimal
example of a ‘noun’ class, which inherits from urest.api.base.APIBase
. It
also requires the Pin
library from MicroPython: but should be able to be
adapted to other GPIO libraries which provide a similar interface.
In contrast to the urest.examples.simpleled.SimpleLED
class, the
urest.examples.pwmled.PWMLED
class shows the use of the ayncio
create_tasks()
hook within a urest.api.base.APIBase
‘noun’ to set off slow
running tasks. This allows the state update to be returned to the network
client via the API ‘immediately’ (at least subject to the other tasks
outstanding and network conditions); without waiting for the actual internal
state to complete. This is a much more realistic scenario for use in the
control of external devices: especially devices such as motors which may take
seconds (or longer) to obtain the correct state.
API
The ‘noun’ exposes the following internal state, with allowed values as 0
or 1
for both keys.
JSON Key | JSON Type | Description |
---|---|---|
actual |
Integer |
The current state of the controlled output |
desired |
Integer |
The next state, if any, that the output is currently transitioning to |
Since the class will not immediately set the desired
state, but only once
the full transition is complete, the client may not see any immediate changes
in the get_state
requests. Instead the full state table for the response,
and interpretation, is as follows
Actual State | Desired State | Description |
---|---|---|
0 | 0 | Output fully off |
0 | 1 | Output commanded on ; currently turning from off to on |
1 | 0 | Output commanded off ; currently turning from on to off |
1 | 1 | Output fully on |
Source code in urest/examples/pwmled.py
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
|
urest.examples.simpleled.SimpleLED
Bases: APIBase
A minimal example of a ‘noun’ class, which inherits from
urest.api.base.APIBase
. It also requires the Pin
library from
MicroPython: but should be able to be adapted to other GPIO libraries which
provide a similar interface.
For an example of an application which uses this library, see the example
led_control
.
This class is also referred to the
Creating a Network Server How-To.
API
The ‘noun’ exposes the following internal state, with allowed values as 0
or 1
for both keys.
JSON Key | JSON Type | Description |
---|---|---|
led |
Integer |
The current state of the controlled output |
The class will attempt to set the GPIO output (assumed to be an LED, but any
digital output should work) to the commanded value immediately. Values
equivalent to 0
for led
will result in the output being set off
(‘low
’): any other integer value will be interpreted as 1
and set the
input on
(‘high
’).
Source code in urest/examples/simpleled.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
|