spaceapi-server/README.md

315 lines
7.8 KiB
Markdown
Raw Normal View History

2019-11-25 04:10:20 +01:00
# SpaceAPI Server
2021-12-07 03:34:15 +01:00
[![pipeline status](https://gitlab.com/s3lph/spaceapi-server/badges/main/pipeline.svg)][main]
[![coverage report](https://gitlab.com/s3lph/spaceapi-server/badges/main/coverage.svg)][main]
2019-11-25 04:10:20 +01:00
2019-11-30 03:24:29 +01:00
A lightweight server for [SpaceAPI][spaceapi] endpoints. Includes
support for pluggable templating, so dynamic content, like sensor
values, can be added.
2019-11-25 04:10:20 +01:00
## Dependencies
- Python 3 (>=3.6)
- [Bottle][pypi-bottle]
2021-05-30 17:52:25 +02:00
- [PyYAML][pypi-yaml]
2019-11-25 04:10:20 +01:00
## License
[MIT License][mit]
2019-11-30 03:24:29 +01:00
## Introduction
This project is an attempt to implement a lightweight, yet versatile
SpaceAPI endpoint server. In its simplest configuration, it just
serves a plain, static, boring JSON document.
In order to provide dynamic content (e.g. whether your space is
2021-05-30 17:52:25 +02:00
currently open), you can replace parts of the YAML document (anything
except object keys) with custom plugin invocations. These plugins
look up and return your dynamic content.
2019-11-30 03:24:29 +01:00
2019-12-02 22:27:30 +01:00
<table>
<thead>
<tr>
<td>Input</td>
<td>Output</td>
</tr>
</thead>
<tbody>
<tr>
<td>
2021-05-30 17:52:25 +02:00
```yaml
---
api: "0.13"
space: My Hackerspace
# This is a plugin invocation
# with no arguments
state: !space_state {}
sensors:
# This is a plugin invocation with
# arguments. They are passed to the
# plugin function as kwargs.
network_connections: !network_connections
networks: [ "2.4 GHz", "5 GHz" ]
2019-12-02 22:27:30 +01:00
```
</td>
<td>
```json
{
"api": "0.13",
"state": {
"open": true,
"lastchange": 1575160777,
"message": "Visitors Welcome!"
},
"sensors": {
"network_connections": [
{
"value": 4,
"type": "wifi",
"name": "2.4 GHz"
},
{
"value": 7,
"type": "wifi",
"name": "5 GHz"
}
]
}
}
```
</td>
</tr>
</tbody>
</table>
2019-11-25 04:10:20 +01:00
## Usage
2019-11-30 03:24:29 +01:00
### 0. Download
Head over to the [Releases][releases], download and install the
package that suits your needs. Alternatively, clone the repo and get
2020-06-20 04:39:50 +02:00
started. There also is a Container Image available through the
[Gitlab registry][registry] tagged as
`registry.gitlab.com/s3lph/spaceapi-server`.
2019-11-30 03:24:29 +01:00
2019-11-30 03:43:31 +01:00
The remainder of this document assumes that you installed the
2019-11-30 03:24:29 +01:00
server as an OS distribution package.
2019-11-25 04:10:20 +01:00
2019-11-30 03:24:29 +01:00
### 1. Overview
The configuration of this server consists of three parts:
- The **main configuration** file, usually located at
2021-05-30 17:52:25 +02:00
`/etc/spaceapi-server/config.yaml`. This file controls all the
2019-11-30 03:24:29 +01:00
internal settings of the server.
- The **response template** file, located at
2021-05-30 17:52:25 +02:00
`/etc/spaceapi-server/template.yaml`. This file defines the content
2019-11-30 03:24:29 +01:00
served by your sever.
- The **plugins** directory, located at
`/etc/spaceapi-server/plugins/`. Here you can put your plugins for
rendering dynamic content.
### 2. Configure the Server
2021-05-30 17:52:25 +02:00
Open the file `/etc/spaceapi-server/config.yaml`.
2019-11-30 03:24:29 +01:00
The following options are currently available:
2019-11-25 04:10:20 +01:00
2021-05-30 17:52:25 +02:00
```yaml
---
# The address to listen on.
address: "::1"
# The TCP port to listen on.
port: 8000
# The Bottle backend server to use.
server: wsgiref
# Path to the SpaceAPI response template file.
template: template.yaml
# Path to the directory containing your plugins.
plugins_dir: plugins
plugins:
# Plugin-specific configuration should go in here, separated by plugin
my_plugin:
my_option: "Hello, World!"
my_other_option: [ 42, 1337 ]
2019-11-25 04:10:20 +01:00
```
2019-11-30 03:24:29 +01:00
### 3. Configure a Static SpaceAPI Endpoint
2021-05-30 17:52:25 +02:00
Open the file `/etc/spaceapi-server/template.yaml`. By default it
2019-11-30 03:24:29 +01:00
contains a minimal example response. If you only want to serve static
content, your `template.json` should simply contain the SpaceAPI JSON
response you want to serve.
2019-11-25 04:10:20 +01:00
2021-05-30 17:52:25 +02:00
The content is served "almost-as-is" (apart from the conversion from
YAML to JSON).
2019-11-25 04:10:20 +01:00
2019-11-30 03:24:29 +01:00
To learn about how a SpaceAPI response should look like, have a look
at [SpaceAPI: Getting Started][spaceapi-getting-started].
2019-11-25 04:10:20 +01:00
2019-11-30 03:24:29 +01:00
### 4. Add Dynamic Content
2019-11-25 04:10:20 +01:00
2019-11-30 03:24:29 +01:00
This example guides you through adding a dynamic `state` property to
your SpaceAPI endpoint. We'll use the following (rather simple, and
probably not too useful) data source: Check a certain file, and mark
the space as open depending on its existence.
1. Create a plugin to fetch the data. Let's name it `filestate.py`
and put in in our plugins directory:
2019-11-25 04:10:20 +01:00
```python
import os
from spaceapi_server import config, plugins
@plugins.template_function
2021-05-30 17:52:25 +02:00
# The plugin can be invoked by using the !space_state YAML tag
2019-11-25 04:10:20 +01:00
def space_state():
# Get the plugin config dict
2019-11-30 04:39:33 +01:00
conf = config.get_plugin_config('filestate')
2019-11-25 04:10:20 +01:00
# Get the filename
filename = conf.get('filename', '/var/space_state')
try:
# Get the file's properties
stat = os.stat(filename)
except FileNotFoundError:
# File doesn't exist, aka. space is closed
return {
'open': False
}
# File exists, aka. space is open. Also report the mtime as "last changed" timestamp
return {
'open': True,
'lastchange': int(stat.st_mtime)
}
```
2021-05-30 17:52:25 +02:00
The `@template_function` decorator registers a constructor in
PyYAML's parser with the function's name as tag
(e.g. `!space_state).
2019-11-25 04:10:20 +01:00
2. Call the template function in your template:
2021-05-30 17:52:25 +02:00
```yaml
# ...
state: !space_state
# ...
2019-11-25 04:10:20 +01:00
```
3. Configure the server:
2021-05-30 17:52:25 +02:00
```yaml
2019-11-25 04:10:20 +01:00
# ...
2021-05-30 17:52:25 +02:00
template: template.yaml
plugins_dir: plugins
plugins:
filestate:
filename: /var/space_state
2019-11-25 04:10:20 +01:00
# ...
```
2019-11-30 03:24:29 +01:00
### 5. Start the Server
Start the server with e.g.:
```bash
systemctl start spaceapi-server.service
```
To reload the configuration, template and plugins, send a SIGHUP,
e.g. through `systemctl reload`.
If you need to run the server ad-hoc, you can start it with:
```bash
2021-05-30 17:52:25 +02:00
python3 -m spaceapi_server /path/to/config.yaml
2019-11-30 03:24:29 +01:00
```
### 6. Test the Server
```bash
curl http://localhost:8000/
```
You should be greeted with the SpaceAPI endpoint response.
2019-11-25 04:10:20 +01:00
2019-11-30 04:39:33 +01:00
## Plugin API Reference
### Configuration
The following functions provide access to values defined in the
configuration file.
#### `spaceapi_server.config.get_plugin_config(name: str)`
This function returns a plugin's configuration.
The function takes one argument, the name of the plugin. This name is
used to look up the plugin configuration.
The function returns the content present at the key `.plugins.<name>`
of the global configuration file, or an empty object if absent.
Usage:
```python
from spaceapi_server import plugins
2019-11-30 04:39:33 +01:00
print(plugins.get_plugin_config('my_plugin'))
2019-11-30 04:39:33 +01:00
```
### Templating
2021-05-30 17:52:25 +02:00
The following decorators register a function for use as a PyYAML
constructor, so they become usable from within templates. They are
2019-11-30 04:39:33 +01:00
invoked when the template is rendered, which happens for each HTTP
request.
If performance is an issue, consider applying caching, either in your
plugins, or by using a caching HTTP reverse proxy.
#### `spaceapi_server.plugins.template_function`
2021-05-30 17:52:25 +02:00
This decorator registers the function's name as a YAML tag in the parser.
2019-11-30 04:39:33 +01:00
2021-05-30 17:52:25 +02:00
The decorated function may take arguments (always passed as
`**kwargs`, so `*args`, or arguments before `*` won't work) that can
be represented in a YAML file.
2019-11-30 04:39:33 +01:00
The decorated function may return any value that can be serialized
into JSON. This includes objects and arrays.
Usage:
```python
from spaceapi_server import plugins
@plugins.template_function
def lookup_sensor(query, default=None):
# Do something with the query
result = ...
# If the loo
if not result:
return default or []
```
2021-05-30 17:52:25 +02:00
```yaml
# ...
state: !lookup_sensor
query: SELECT timestamp, value FROM people_now_present LIMIT 1
# ...
2019-11-30 04:39:33 +01:00
```
2021-12-07 03:34:15 +01:00
[main]: https://gitlab.com/s3lph/spaceapi-server/commits/main
2019-11-30 03:24:29 +01:00
[releases]: https://gitlab.com/s3lph/spaceapi-server/-/releases
2019-11-25 04:10:20 +01:00
[spaceapi]: https://spaceapi.io/
[pypi-bottle]: https://pypi.org/project/bottle/
2021-05-30 17:52:25 +02:00
[pypi-yaml]: https://pypi.org/project/PyYAML/
2021-12-07 03:34:15 +01:00
[mit]: https://gitlab.com/s3lph/spaceapi-server/blob/main/LICENSE
2019-11-25 04:10:20 +01:00
[spaceapi-getting-started]: https://spaceapi.io/getting-started/
2019-11-30 03:24:29 +01:00
[jinja]: https://jinja.palletsprojects.com/
2021-05-30 17:52:25 +02:00
[registry]: https://gitlab.com/s3lph/spaceapi-server/container_registry