Skip to content

Commit 7c99d92

Browse files
committed
Improvements to documentation from review
1 parent 1d37e80 commit 7c99d92

3 files changed

Lines changed: 77 additions & 8 deletions

File tree

docs/source/documentation.rst

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,28 @@ Generated documentation
55

66
LabThings describes its HTTP API in two ways: with a :ref:`wot_td` and with an OpenAPI_ document.
77

8+
.. _openapi:
9+
10+
OpenAPI
11+
-------
12+
813
OpenAPI_ is a standard way to describe an HTTP interface. It lists all of the possible HTTP requests that may be made, along with a description of each one, and a description of the possible responses.
914

10-
Each :ref:`wot_thing` is documented by a Thing Description, which is a JSON document describing all of the ways to interact with that Thing. The WoT_ standard defines the `Thing Description`_ and includes a JSON Schema against which it may be validated.
15+
.. _gen_td:
16+
17+
Thing Description
18+
-----------------
19+
20+
Each :ref:`wot_thing` is documented by a Thing Description, which is a JSON document describing all of the ways to interact with that Thing (:ref:`wot_affordances`\ ). The WoT_ standard defines the `Thing Description`_ and includes a JSON Schema against which it may be validated.
21+
22+
Thing Description documents are higher-level than OpenAPI_ and focus on the capabilities of the Thing. For example, they include a list of properties, where each action is described only once. LabThings treats the Thing Description as your public API, and as a general rule anything not described in the Thing Description is not available over HTTP or to a `.DirectThingClient`\ .
23+
24+
Comparison of Thing Description and OpenAPI
25+
-------------------------------------------
26+
27+
Thing Description aims to be a neat way to describe the capabilities of a Thing, while OpenAPI focuses on detailed documentation of every possible interaction with the server. Thing Description is a newer and less well adopted standard that's specific to the Web of Things, while OpenAPI has been around for a while and is widely used and understood as a general-purpose API description.
1128

12-
Thing Description documents are higher-level than OpenAPI_ and focus on the capabilities of the Thing, rather than the HTTP endpoints. In general you would expect more HTTP endpoints than there are interaction affordances, so in principle client code based on a Thing Description should be more meaningful. However, OpenAPI is a much more widely adopted standard and so both forms of documentation are generated by LabThings-FastAPI.
29+
OpenAPI describes each HTTP endpoint individually. There are usually more HTTP endpoints than there are :ref:`wot_affordances` because a Property may have two endpoints, one to read its value and one to write it. Actions usually correspond to several endpoints; one to invoke the action, one to check the action's status, one to cancel it, and another to retrieve its output once it has finished. In principle, client code based on a Thing Description should be more meaningful because related endpoints can be grouped together into properties or actions that correspond to structures in the programming language (like methods and properties in Python). However, OpenAPI is a much more widely adopted standard and so both forms of documentation are generated by LabThings-FastAPI.
1330

1431
.. _WoT: https://www.w3.org/WoT/
1532
.. _Thing Description: https://www.w3.org/TR/wot-thing-description/

docs/source/see_also.rst

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,41 @@ LabThings-FastAPI makes quite heavy use of a few key concepts from external libr
88
Descriptors
99
-----------
1010

11-
Descriptors are a way to itercept attribute access on an object. By default, attributes of an object are just variables - so an object called ``foo`` might have an attribute called ``bar``, and you may read its value with ``foo.bar``, write its value with ``foo.bar = "baz"``, and delete the attribute with ``del foo.bar``. If ``foo`` is a descriptor, Python will call the ``__get__`` method of that descriptor when it's read and the ``__set__`` method when it's written to. The descriptor protocol is described with plenty of examples in the `Descriptor Guide`_ in the Python documentation.
11+
Descriptors are a way to itercept attribute access on an object. By default, attributes of an object are just variables - so an object called ``foo`` might have an attribute called ``bar``, and you may read its value with ``foo.bar``, write its value with ``foo.bar = "baz"``, and delete the attribute with ``del foo.bar``. If ``foo`` is a descriptor, Python will call the ``__get__`` method of that descriptor when it's read and the ``__set__`` method when it's written to. You have quite probably used a descriptor already, because the built-in `~builtins.property` creates a descriptor object: that's what runs your getter method when the property is accessed. The descriptor protocol is described with plenty of examples in the `Descriptor Guide`_ in the Python documentation.
1212

1313
In LabThings-FastAPI, descriptors are used to implement :ref:`wot_actions` and :ref:`wot_properties` on `.Thing` subclasses. The intention is that these will function like standard Python methods and properties, but will also be available over HTTP, along with automatic documentation in the :ref:`wot_td` and OpenAPI documents.
1414

1515
There are a few useful notes that relate to many of the descriptors in LabThings-FastAPI:
1616

17-
* Descriptor objects **may have more than one owner**. As a rule, a descriptor object (e.g. an instance of `.DataProperty`) is assigned to an attribute of one `.Thing` subclass. There may, however, be multiple *instances* of that class, so it is not safe to assume that the descriptor object corresponds to only one `.Thing`. This is why the `.Thing` is passed to the ``__get__`` method: we should ensure that any values being remembered are keyed to the owning `.Thing` and are not simply stored in the descriptor. Usually, this is done using `.WeakKeyDictionary` objects, which allow us to look up values based on the `.Thing`, without interfering with garbage collection.
17+
* Descriptor objects **may have more than one owner**. As a rule, a descriptor object
18+
(e.g. an instance of `.DataProperty`) is assigned to an attribute of one `.Thing` subclass. There may, however, be multiple *instances* of that class, so it is not safe to assume that the descriptor object corresponds to only one `.Thing`. This is why the `.Thing` is passed to the ``__get__`` method: we should ensure that any values being remembered are keyed to the owning `.Thing` and are not simply stored in the descriptor. Usually, this is done using `.WeakKeyDictionary` objects, which allow us to look up values based on the `.Thing`, without interfering with garbage collection.
19+
20+
The example below shows how this can go wrong.
21+
22+
.. code-block:: python
23+
24+
class BadProperty:
25+
"An example of a descriptor that has unwanted behaviour."
26+
def __init__(self):
27+
self._value = None
28+
29+
def __get__(self, obj):
30+
return self._value
31+
32+
def __set__(self, obj, val):
33+
self._value = val
34+
35+
class BrokenExample:
36+
myprop = BadProperty()
37+
38+
a = BrokenExample()
39+
b = BrokenExample()
40+
41+
assert a.myprop is None
42+
b.myprop = True
43+
assert a.myprop is None # FAILS because `myprop` shares values between a and b
44+
1845
* Descriptor objects **may know their name**. Python calls ``__set_name__`` on a descriptor if it is available. This allows the descriptor to know the name of the attribute to which it is assigned. LabThings-FastAPI uses the name in the URL and in the Thing Description. When ``__set_name__`` is called, the descriptor **is also passed the class that owns it**. This allows us to check for type hints and docstrings that are part of the class, rather than part of the descriptor.
19-
* There is a convention that descriptors return their value when accessed as an instance attribute, but return themselves when accessed as a class attribute. We try to adhere to that convention.
46+
* There is a convention that descriptors return their value when accessed as an instance attribute, but return themselves when accessed as a class attribute (as done by `builtins.property`). LabThings adheres to that convention.
2047

2148
.. _`Descriptor Guide`: https://docs.python.org/3/howto/descriptor.html

docs/source/tutorial/properties.rst

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@
33
Properties
44
=========================
55

6-
:ref:`wot_properties` are values that can be read and written on a Thing. They are used to represent the state of the Thing, such as its current temperature, brightness, or status. Properties can be read using a ``GET`` request and written using a ``PUT`` or ``POST`` request. You can add properties to a `.Thing` by using `.property` (usually imported as ``lt.property``).
6+
7+
8+
Properties are values that can be read from and written to a Thing. They are used to represent the state of the Thing, such as its current temperature, brightness, or status. :ref:`wot_properties` are a key concept in the Web of Things standard.
9+
10+
LabThings implements properties in a very similar way to the built-in Python `~builtins.property`. The key difference is that defining an attribute as a `.property` means that the property will be listed in the :ref:`gen_td` and exposed over HTTP. This is important for two reasons:
11+
12+
* Only properties declared using `.property` (usually imported as ``lt.property``) can be accessed over HTTP. Regular attributes or properties using `builtins.property` are only available to your `.Thing` internally, except in some special cases.
13+
* Communication between `.Thing`\ s within a LabThings server should be done using a `.DirectThingClient` classs. The purpose of `.DirectThingClient` is to provide the same interface as a `.ThingClient` over HTTP, so it will also only expose functionality described in the Thing Description.
14+
15+
You can add properties to a `.Thing` by using `.property` (usually imported as ``lt.property``).
716

817
Data properties
918
-------------------------
@@ -78,7 +87,9 @@ Functional properties may also have a "setter" method, which is called when the
7887
"""Set the value of twice_my_property."""
7988
self.my_property = value // 2
8089
81-
Adding a setter makes the property read-write (if only a getter is present, it must be read-only). It is possible to make a property read-only for clients by setting its ``readonly`` attribute: this has the same behaviour as for data properties, i.e. it prevents the property from being written to via HTTP requests or `.DirectThingClient` instances, but it can still be modified by the Thing's code.
90+
Adding a setter makes the property read-write (if only a getter is present, it must be read-only).
91+
92+
It is possible to make a property read-only for clients by setting its ``readonly`` attribute: this has the same behaviour as for data properties.
8293

8394
.. code-block:: python
8495
@@ -101,7 +112,21 @@ Adding a setter makes the property read-write (if only a getter is present, it m
101112
# Make the property read-only for clients
102113
twice_my_property.readonly = True
103114
104-
Functional properties may not be observed, as they are not backed by a simple value. If you need to notify clients when the value changes, you can use a data property that is updated by the functional property.
115+
In the example above, ``twice_my_property`` may be set by code within ``MyThing`` but cannot be written to via HTTP requests or `.DirectThingClient` instances.
116+
117+
Functional properties may not be observed, as they are not backed by a simple value. If you need to notify clients when the value changes, you can use a data property that is updated by the functional property. In the example above, ``my_property`` may be observed, while ``twice_my_property`` cannot be observed. It would be possible to observe changes in ``my_property`` and then query ``twice_my_property`` for its new value.
118+
119+
HTTP interface
120+
--------------
121+
122+
LabThings is primarily controlled using HTTP. Mozilla have a good `Overview of HTTP`_ that is worth a read if you are unfamiliar with the concept of requests, or what ``GET`` and ``PUT`` mean.
123+
124+
Each property in LabThings will be assigned a URL, which allows it to be read and (optionally) written to. The easiest way to explore this is in the interactive OpenAPI documentation, served by your LabThings server at ``/docs``\ . Properties can be read using a ``GET`` request and written using a ``PUT`` request.
125+
126+
LabThings follows the `HTTP Protocol Binding`_ from the Web of Things standard. That's quite a detailed document: for a gentle introduction to HTTP and what a request means, see
127+
128+
.. _`Overview of HTTP`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Overview
129+
.. _`HTTP Protocol Binding`: https://w3c.github.io/wot-binding-templates/bindings/protocols/http/index.html
105130

106131
Observable properties
107132
-------------------------

0 commit comments

Comments
 (0)