Skip to content

Commit 21c6f76

Browse files
author
Joel Collins
committed
Updated docs
1 parent 3dc2c05 commit 21c6f76

File tree

4 files changed

+135
-48
lines changed

4 files changed

+135
-48
lines changed

docs/advanced_usage/view_class.rst

Lines changed: 0 additions & 13 deletions
This file was deleted.

docs/basic_usage/app_thing_server.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,16 @@ The LabThing object is our main entrypoint, and handles creating API views, mana
3333
.. autoclass:: labthings.LabThing
3434
:noindex:
3535

36-
Two key methods are :meth:`labthings.LabThing.build_property` and :meth:`labthings.LabThing.build_action`. These methods allow the automation creation of Property and Action API views from Python object attributes and methods. By passing schemas to these methods, argument and response marshalling is automatically performed. Offloading actions to background threads is also handled automatically.
3736

38-
.. automethod:: labthings.LabThing.build_property
37+
Views
38+
-----
39+
40+
Thing interaction affordances are created using Views. Two main View types correspond to properties and actions.
41+
42+
.. autoclass:: labthings.PropertyView
3943
:noindex:
4044

41-
.. automethod:: labthings.LabThing.build_action
45+
.. autoclass:: labthings.ActionView
4246
:noindex:
4347

4448

docs/quickstart.rst

Lines changed: 117 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,54 +14,139 @@ An example Lab Thing built from our ``PretendSpectrometer`` class, complete with
1414

1515
.. code-block:: python
1616
17-
from labthings import fields, create_app
17+
import time
18+
19+
from labthings import ActionView, PropertyView, create_app, fields, find_component, op
1820
from labthings.example_components import PretendSpectrometer
21+
from labthings.json import encode_json
22+
23+
"""
24+
Class for our lab component functionality. This could include serial communication,
25+
equipment API calls, network requests, or a "virtual" device as seen here.
26+
"""
27+
28+
29+
"""
30+
Create a view to view and change our integration_time value,
31+
and register is as a Thing property
32+
"""
33+
34+
35+
# Wrap in a semantic annotation to autmatically set schema and args
36+
class DenoiseProperty(PropertyView):
37+
"""Value of integration_time"""
38+
39+
schema = fields.Int(required=True, minimum=100, maximum=500)
40+
semtype = "LevelProperty"
41+
42+
@op.readproperty
43+
def get(self):
44+
# When a GET request is made, we'll find our attached component
45+
my_component = find_component("org.labthings.example.mycomponent")
46+
return my_component.integration_time
47+
48+
@op.writeproperty
49+
def put(self, new_property_value):
50+
# Find our attached component
51+
my_component = find_component("org.labthings.example.mycomponent")
52+
53+
# Apply the new value
54+
my_component.integration_time = new_property_value
55+
56+
return my_component.integration_time
57+
58+
@op.observeproperty
59+
def websocket(self, ws):
60+
# Find our attached component
61+
my_component = find_component("org.labthings.example.mycomponent")
62+
initial_value = None
63+
while not ws.closed:
64+
time.sleep(1)
65+
if my_component.integration_time != initial_value:
66+
ws.send(encode_json(my_component.integration_time))
67+
initial_value = my_component.integration_time
68+
69+
70+
"""
71+
Create a view to quickly get some noisy data, and register is as a Thing property
72+
"""
73+
74+
75+
class QuickDataProperty(PropertyView):
76+
"""Show the current data value"""
77+
78+
# Marshal the response as a list of floats
79+
schema = fields.List(fields.Float())
80+
81+
@op.readproperty
82+
def get(self):
83+
# Find our attached component
84+
my_component = find_component("org.labthings.example.mycomponent")
85+
return my_component.data
86+
87+
@op.observeproperty
88+
def websocket(self, ws):
89+
# Find our attached component
90+
my_component = find_component("org.labthings.example.mycomponent")
91+
while not ws.closed:
92+
ws.send(encode_json(my_component.data))
93+
94+
95+
"""
96+
Create a view to start an averaged measurement, and register is as a Thing action
97+
"""
98+
99+
100+
class MeasurementAction(ActionView):
101+
# Expect JSON parameters in the request body.
102+
# Pass to post function as dictionary argument.
103+
args = {
104+
"averages": fields.Integer(
105+
missing=20, example=20, description="Number of data sets to average over",
106+
)
107+
}
108+
# Marshal the response as a list of numbers
109+
schema = fields.List(fields.Number)
110+
111+
# Main function to handle POST requests
112+
@op.invokeaction
113+
def post(self, args):
114+
"""Start an averaged measurement"""
115+
116+
# Find our attached component
117+
my_component = find_component("org.labthings.example.mycomponent")
118+
119+
# Get arguments and start a background task
120+
n_averages = args.get("averages")
121+
122+
# Return the task information
123+
return my_component.average_data(n_averages)
19124
20125
21126
# Create LabThings Flask app
22127
app, labthing = create_app(
23128
__name__,
24-
title="My PretendSpectrometer API",
25-
description="LabThing API for PretendSpectrometer",
26-
version="0.1.0"
129+
title="My Lab Device API",
130+
description="Test LabThing-based API",
131+
version="0.1.0",
27132
)
28133
29-
30-
# Make some properties and actions out of our component
134+
# Attach an instance of our component
135+
# Usually a Python object controlling some piece of hardware
31136
my_spectrometer = PretendSpectrometer()
137+
labthing.add_component(my_spectrometer, "org.labthings.example.mycomponent")
32138
33-
# Single-shot data property
34-
labthing.build_property(
35-
my_spectrometer, # Python object
36-
"data", # Objects attribute name
37-
description="A single-shot measurement",
38-
readonly=True,
39-
schema=fields.List(fields.Number())
40-
)
41139
42-
# Integration time property
43-
labthing.build_property(
44-
my_spectrometer, # Python object
45-
"integration_time", # Objects attribute name
46-
description="Single-shot integration time",
47-
schema=fields.Int(min=100, max=500, example=200, unit="microsecond")
48-
)
49-
50-
# Averaged measurement action
51-
labthing.build_action(
52-
my_spectrometer, # Python object
53-
"average_data", # Objects method name
54-
description="Take an averaged measurement",
55-
schema=fields.List(fields.Number()),
56-
args={ # How do we convert from the request input to function arguments?
57-
"n": fields.Int(description="Number of averages to take", example=5, default=5)
58-
},
59-
)
140+
# Add routes for the API views we created
141+
labthing.add_view(DenoiseProperty, "/integration_time")
142+
labthing.add_view(QuickDataProperty, "/quick-data")
143+
labthing.add_view(MeasurementAction, "/actions/measure")
60144
61145
62146
# Start the app
63147
if __name__ == "__main__":
64148
from labthings import Server
149+
65150
Server(app).run()
66151
67152

src/labthings/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# Main LabThing class
22
# Submodules
33
from . import extensions, fields, json, marshalling, views
4+
45
# Action threads
56
from .actions import (
67
ActionKilledException,
78
current_action,
89
update_action_data,
910
update_action_progress,
1011
)
12+
1113
# Functions to speed up finding global objects
1214
from .find import (
1315
current_labthing,
@@ -17,12 +19,19 @@
1719
registered_extensions,
1820
)
1921
from .labthing import LabThing
22+
2023
# Quick-create app+LabThing function
2124
from .quick import create_app
25+
2226
# Schema and field
2327
from .schema import Schema
28+
2429
# Synchronisation classes
2530
from .sync import ClientEvent, CompositeLock, StrictLock
31+
32+
# Views
33+
from .views import ActionView, PropertyView
34+
2635
# Suggested WSGI+WebSocket server class
2736
from .wsgi import Server
2837

@@ -48,4 +57,6 @@
4857
"Schema",
4958
"semantics",
5059
"json",
60+
"PropertyView",
61+
"ActionView",
5162
]

0 commit comments

Comments
 (0)