You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/source/concurrency.rst
+6-6Lines changed: 6 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,28 +5,28 @@ Concurrency in LabThings-FastAPI
5
5
6
6
One of the major challenges when controlling hardware, particularly from web frameworks, is concurrency. Most web frameworks assume resources (database connections, object storage, etc.) may be instantiated multiple times, and often initialise or destroy objects as required. In contrast, hardware can usually only be controlled from one process, and usually is initialised and shut down only once.
7
7
8
-
LabThings-FastAPI instantiates each :class:`.Thing` only once, and runs all code in a thread. More specifically, each time an action is invoked via HTTP, a new thread is created to run the action. Similarly, each time a property is read or written, a new thread is created to run the property method. This means that :class:`.Thing` code should protect important variables or resources using locks from the `threading` module, and need not worry about writing asynchronous code.
8
+
LabThings-FastAPI instantiates each :class:`~lt.Thing` only once, and runs all code in a thread. More specifically, each time an action is invoked via HTTP, a new thread is created to run the action. Similarly, each time a property is read or written, a new thread is created to run the property method. This means that :class:`~lt.Thing` code should protect important variables or resources using locks from the `threading` module, and need not worry about writing asynchronous code.
9
9
10
-
In the case of properties, the HTTP response is only returned once the `.Thing` code is complete. Actions currently return a response immediately, and must be polled to determine when they have completed. This behaviour may change in the future, most likely with the introduction of a timeout to allow the client to choose between waiting for a response or polling.
10
+
In the case of properties, the HTTP response is only returned once the `~lt.Thing` code is complete. Actions currently return a response immediately, and must be polled to determine when they have completed. This behaviour may change in the future, most likely with the introduction of a timeout to allow the client to choose between waiting for a response or polling.
11
11
12
12
Many of the functions that handle HTTP requests are asynchronous, running in an :mod:`anyio` event loop. This enables many HTTP connections to be handled at once with good efficiency. The `anyio documentation`_ describes the functions that link between async and threaded code. When the LabThings server is started, we create an :class:`anyio.from_thread.BlockingPortal`, which allows threaded code to run code asynchronously in the event loop.
13
13
14
-
An action can run async code using its server interface. See `.ThingServerInterface.start_async_task_soon` for details.
14
+
An action can run async code using its server interface. See `~lt.ThingServerInterface.start_async_task_soon` for details.
15
15
16
-
There are relatively few occasions when `.Thing` code will need to consider this explicitly: more usually the blocking portal will be obtained by a LabThings function, for example the `.MJPEGStream` class.
16
+
There are relatively few occasions when `~lt.Thing` code will need to consider this explicitly: more usually the blocking portal will be obtained by a LabThings function, for example the `.MJPEGStream` class.
When one `Thing` calls the actions or properties of another `.Thing`, either directly or via a `.DirectThingClient`, no new threads are spawned: the action or property is run in the same thread as the caller. This mirrors the behaviour of the `.ThingClient`, which blocks until the action or property is complete. See :doc:`using_things` for more details on how to call actions and properties of other Things.
23
+
When one `Thing` calls the actions or properties of another `~lt.Thing`, either directly or via a `.DirectThingClient`, no new threads are spawned: the action or property is run in the same thread as the caller. This mirrors the behaviour of the `~lt.ThingClient`, which blocks until the action or property is complete. See :doc:`using_things` for more details on how to call actions and properties of other Things.
24
24
25
25
Invocations and concurrency
26
26
---------------------------
27
27
28
28
Each time an action is run ("invoked" in :ref:`wot_cc`), we create a new thread to run it. This thread has a context variable set, such that ``lt.cancellable_sleep`` and ``lt.get_invocation_logger`` are aware of which invocation is currently running. If an action spawns a new thread (e.g. using `threading.Thread`\ ), this new thread will not have an invocation ID, and consequently the two invocation-specific functions mentioned will not work.
29
29
30
-
Usually, the best solution to this problem is to generate a new invocation ID for the thread. This means only the original action thread will receive cancellation events, and only the original action thread will log to the invocation logger. If the action is cancelled, you must cancel the background thread. This is the behaviour of `.ThreadWithInvocationID`\ .
30
+
Usually, the best solution to this problem is to generate a new invocation ID for the thread. This means only the original action thread will receive cancellation events, and only the original action thread will log to the invocation logger. If the action is cancelled, you must cancel the background thread. This is the behaviour of `~lt.ThreadWithInvocationID`\ .
31
31
32
32
It is also possible to copy the current invocation ID to a new thread. This is often a bad idea, as it's ill-defined whether the exception will arise in the original thread or the new one if the invocation is cancelled. Logs from the two threads will also be interleaved. If it's desirable to log from the background thread, the invocation logger may safely be passed as an argument, rather than accessed via ``lt.get_invocation_logger``\ .
Copy file name to clipboardExpand all lines: docs/source/developer_notes/descriptors.rst
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,11 +3,11 @@
3
3
Descriptors
4
4
===========
5
5
6
-
Descriptors are a way to intercept attribute access on an object, and they are used extensively by LabThings to add functionality to `.Thing` instances, while continuing to look like normal Python objects.
6
+
Descriptors are a way to intercept attribute access on an object, and they are used extensively by LabThings to add functionality to `~lt.Thing` instances, while continuing to look like normal Python objects.
7
7
8
8
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.
9
9
10
-
In LabThings-FastAPI, descriptors are used to implement :ref:`actions` and :ref:`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 :ref:`gen_docs`.
10
+
In LabThings-FastAPI, descriptors are used to implement :ref:`actions` and :ref:`properties` on `~lt.Thing` subclasses. The intention is that these will function like standard Python methods and properties, but will also be available over HTTP, along with :ref:`gen_docs`.
11
11
12
12
.. _field_typing:
13
13
@@ -22,7 +22,7 @@ Field typing
22
22
my_property: int= lt.property(default=0)
23
23
"""An integer property."""
24
24
25
-
This makes it clear to anyone using ``MyThing`` that ``my_property`` is an integer, and should be picked up by most type checking/autocompletion tools. However, because the annotation is attached to the *class* and not passed to the underlying `.DataProperty` descriptor, we need to use the descriptor protocol to figure it out.
25
+
This makes it clear to anyone using ``MyThing`` that ``my_property`` is an integer, and should be picked up by most type checking/autocompletion tools. However, because the annotation is attached to the *class* and not passed to the underlying `~lt.DataProperty` descriptor, we need to use the descriptor protocol to figure it out.
26
26
27
27
Field typing in LabThings is implemented by `.FieldTypedBaseDescriptor` and there are docstrings on all of the relevant "magic" methods explaining what each one does. Below, there is a brief overview of how these fit together.
28
28
@@ -39,7 +39,7 @@ Descriptor implementation
39
39
There are a few useful notes that relate to many of the descriptors in LabThings-FastAPI:
40
40
41
41
* Descriptor objects **may have more than one owner**. As a rule, a descriptor object
42
-
(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.
42
+
(e.g. an instance of `~lt.DataProperty`) is assigned to an attribute of one `~lt.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 `~lt.Thing`. This is why the `~lt.Thing` is passed to the ``__get__`` method: we should ensure that any values being remembered are keyed to the owning `~lt.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 `~lt.Thing`, without interfering with garbage collection.
For a simple example `.Thing` and instructions on how to serve it, see the :ref:`tutorial`\ .
4
+
For a simple example `~lt.Thing` and instructions on how to serve it, see the :ref:`tutorial`\ .
5
5
6
-
For more complex examples, there is a useful collection of `.Thing` subclasses implemented as part of the `OpenFlexure Microscope`_ in the `things`_ submodule. This includes control of a camera and a translation stage, as well as various software `.Thing`\ s that integrate the two.
6
+
For more complex examples, there is a useful collection of `~lt.Thing` subclasses implemented as part of the `OpenFlexure Microscope`_ in the `things`_ submodule. This includes control of a camera and a translation stage, as well as various software `~lt.Thing`\ s that integrate the two.
Copy file name to clipboardExpand all lines: docs/source/index.rst
+4-3Lines changed: 4 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -20,15 +20,16 @@ Documentation for LabThings-FastAPI
20
20
wot_core_concepts.rst
21
21
removed_features.rst
22
22
23
+
quick_reference.rst
23
24
autoapi/index
24
25
developer_notes/index.rst
25
26
26
27
`labthings-fastapi` is a Python library to simplify the process of making laboratory instruments available via a HTTP. It aims to create an API that is usable from any modern programming language, with API documentation in both :ref:`openapi` and :ref:`gen_td` formats. It is the underlying framework for v3 of the `OpenFlexure Microscope software <https://gitlab.com/openflexure/openflexure-microscope-server/>`_. Key features and design aims are:
27
28
28
-
* The functionality of a unit of hardware or software is described using `.Thing` subclasses.
29
-
* Methods and properties of `.Thing` subclasses may be added to the HTTP API and associated documentation using decorators.
29
+
* The functionality of a unit of hardware or software is described using `~lt.Thing` subclasses.
30
+
* Methods and properties of `~lt.Thing` subclasses may be added to the HTTP API and associated documentation using decorators.
30
31
* Datatypes of action input/outputs and properties are defined with Python type hints.
31
-
* Actions are decorated methods of a `.Thing` class. There is no need for separate schemas or endpoint definitions.
32
+
* Actions are decorated methods of a `~lt.Thing` class. There is no need for separate schemas or endpoint definitions.
32
33
* Properties are defined either as typed attributes (similar to `pydantic` or `dataclasses`) or with a `property`\ -like decorator.
33
34
* Lifecycle and concurrency are appropriate for hardware: `Thing` code is always run in a thread, and each `Thing` is instantiated, started up, and shut down only once.
34
35
* Vocabulary and concepts are aligned with the `W3C Web of Things <https://www.w3.org/WoT/>`_ standard (see :ref:`wot_cc`)
0 commit comments