Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
11d705a
Change `add_thing` to use name and instantiate Things.
rwb27 Sep 11, 2025
5f59b04
Improve validation of arguments.
rwb27 Sep 11, 2025
f670f22
Update DirectThingClient to use name not path.
rwb27 Sep 11, 2025
cbe8791
Return the created Thing from add_thing
rwb27 Sep 11, 2025
5c60e0d
Update test suite to use new add_thing syntax.
rwb27 Sep 11, 2025
fcb4ca8
Update code examples in documentation
rwb27 Sep 11, 2025
ed073b9
Update docstrings that mention `add_thing`
rwb27 Sep 11, 2025
ac8ec8d
Fix docstring for add_thing.
rwb27 Sep 11, 2025
00bce70
Remove vestigial #noqa statements
rwb27 Sep 11, 2025
66810fc
Implement ThingServerInterface
rwb27 Sep 11, 2025
7a8d9e5
Provide, and test, thing state metadata via ThingServerInterface
rwb27 Sep 11, 2025
86cf44d
Create invocation loggers before starting actions.
rwb27 Sep 11, 2025
025cd83
Update concurrency docs.
rwb27 Sep 11, 2025
8f7b5c7
Replace BlockingPortal with ThingServerInterface.
rwb27 Sep 11, 2025
1c18dbc
Mark slow tests to speed up repeated testing.
rwb27 Sep 11, 2025
eeddee9
Update test for MJPEGStream
rwb27 Sep 11, 2025
0acea11
Update test suite to mock the server interface.
rwb27 Sep 11, 2025
4f5e429
Tests for ThingServerInterface.
rwb27 Sep 11, 2025
8f06fcc
Typing fixes.
rwb27 Sep 11, 2025
0fb1e37
Docstring fixes.
rwb27 Sep 11, 2025
9394f4d
Use ThingServerInterface in settings code
rwb27 Sep 11, 2025
e774862
Deduplicate `path_for_thing`.
rwb27 Sep 11, 2025
2e13f2c
Refactor field-like typing logic into base_descriptor.
rwb27 Sep 12, 2025
78a9720
First implementation of ThingConnection.
rwb27 Sep 12, 2025
5e333f6
Completed implementation of thing_connection
rwb27 Sep 15, 2025
405ac09
Test code for thing_connection
rwb27 Sep 15, 2025
e688dd7
Improve handling of field-typed descriptors.
rwb27 Sep 15, 2025
28b2709
Docstring fixes
rwb27 Sep 15, 2025
f747fc6
Revert to using `typing.get_type_hints` but evaluate lazily.
rwb27 Sep 24, 2025
9816338
Support multiple things in a connection
rwb27 Sep 24, 2025
8cb258c
Move more error checking into `ThingConnection`
rwb27 Sep 24, 2025
24998bf
Got tests passing
rwb27 Sep 24, 2025
869bd73
Tidy up some imports
rwb27 Sep 24, 2025
31350b4
Testing and improvements
rwb27 Sep 25, 2025
739372c
Removed unnecessary override.
rwb27 Sep 25, 2025
4464624
Typing fixes.
rwb27 Sep 25, 2025
7a266e7
Update tests to take account of different type evaluation.
rwb27 Sep 25, 2025
dbcbe72
Check ReferenceError is raised if a Thing gets deleted.
rwb27 Sep 25, 2025
2260d64
Add comment to #type: ignore
rwb27 Sep 25, 2025
3451344
Explicitly don't support forward references in type subscripts.
rwb27 Oct 6, 2025
0e6908c
Add full testing for FieldTypedBaseDescriptor.
rwb27 Oct 6, 2025
79c08fb
Mark dependencies as deprecated.
rwb27 Oct 6, 2025
997f1b6
Fix broken test
rwb27 Oct 7, 2025
a5d61d2
Fix type ignore comments
rwb27 Oct 7, 2025
b0c12c6
Fix docstring
rwb27 Oct 7, 2025
e9a0e29
Pass Thing Connection config through `add_thing`.
rwb27 Oct 7, 2025
602ccf1
Allow thing connections to be specified in config.
rwb27 Oct 7, 2025
be6fc96
Spelling fix
rwb27 Oct 7, 2025
04499c1
Fix a typo in test code.
rwb27 Oct 7, 2025
cc3cb27
Add a documentation page on thing connections.
rwb27 Oct 7, 2025
369faaa
Fix tests for Python < 3.12
rwb27 Oct 7, 2025
421ac90
Add invocation context module
rwb27 Oct 9, 2025
4f9efd1
Migrate actions and add to __init__
rwb27 Oct 9, 2025
d489910
Tests and documentation.
rwb27 Oct 9, 2025
f2ef644
Docstring fixes
rwb27 Oct 9, 2025
fd4b610
Migrate test code
rwb27 Oct 9, 2025
a5fa721
More docstring for ThreadWithInvocationID
rwb27 Oct 9, 2025
b791072
Split cancellable_sleep
rwb27 Oct 22, 2025
bea820d
Don't make loggers per-invocation
rwb27 Oct 22, 2025
f784f50
Fix old test and dependency
rwb27 Oct 22, 2025
056f61f
Fix an edge case in ThreadWithInvocationID
rwb27 Oct 22, 2025
3478e65
Test the logs module
rwb27 Oct 26, 2025
e282676
Initialise all Things during ThingServer.__init__
rwb27 Oct 27, 2025
15dce7b
Tidy up config models and make them available at module level.
rwb27 Oct 27, 2025
5399569
Tidy up Thing lifecycle and leave validation to the Pydantic models
rwb27 Oct 27, 2025
d9689a0
Don't print stack traces for validation errors.
rwb27 Oct 27, 2025
f3aedd8
Update test code to pass Thing classes to ThingServer.__init__
rwb27 Oct 27, 2025
869d7ac
Update example/documentation code examples.
rwb27 Oct 27, 2025
3a5aef1
Fix up some docstrings.
rwb27 Oct 27, 2025
c7786df
Rename thing_connection to thing_slot
rwb27 Oct 27, 2025
728baa5
Remove the defunct object_reference_to_object
rwb27 Oct 27, 2025
655bf9d
Remove an unused import
rwb27 Oct 28, 2025
1c23151
Typing fixes for the fallback server
rwb27 Oct 28, 2025
b1b98e2
Update index and core concepts
rwb27 Oct 28, 2025
c496f96
Allow specifying folder in create_thing_without_server
rwb27 Oct 28, 2025
aa04aff
Spelling fixes in docs.
rwb27 Oct 28, 2025
6aaf7b2
silence a spurious type warning
rwb27 Oct 28, 2025
71e2a38
Update src/labthings_fastapi/server/__init__.py
rwb27 Oct 29, 2025
28e905a
Reformat example to give `things` a name.
rwb27 Oct 29, 2025
c6766aa
Fix some broken references in the docs.
rwb27 Oct 29, 2025
d2b03b5
Use sphinx-toolbox to provide decorator links.
rwb27 Oct 30, 2025
b79b62f
Fix some ambiguous links in the docs
rwb27 Oct 30, 2025
1fcbf9c
Fix links and RST formatting
rwb27 Oct 30, 2025
6782063
Update some broken links and don't link to WoT Core Concepts so much.
rwb27 Oct 30, 2025
cfb90b3
Properly import ThingServerInterface in `thing`
rwb27 Oct 30, 2025
a077e7f
Shorten the explicit reference `_structure`.
rwb27 Oct 30, 2025
88dcbf8
Single-source the version number.
rwb27 Oct 30, 2025
a79423f
Fix indentation in subclassing notes.
rwb27 Oct 30, 2025
c9bfd2e
De-emphasise WoT core concepts
rwb27 Oct 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 81 additions & 2 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,31 @@ anyio==4.9.0
# httpx
# starlette
# watchfiles
apeye==1.4.1
# via sphinx-toolbox
apeye-core==1.1.5
# via apeye
astroid==3.3.11
# via sphinx-autoapi
attrs==25.3.0
# via
# jsonschema
# referencing
autodocsumm==0.2.14
# via sphinx-toolbox
babel==2.17.0
# via sphinx
beautifulsoup4==4.14.2
# via sphinx-toolbox
cachecontrol==0.14.3
# via sphinx-toolbox
certifi==2025.7.14
# via
# httpcore
# httpx
# requests
# sentry-sdk
# sphinx-prompt
charset-normalizer==3.4.2
# via requests
click==8.2.1
Expand All @@ -42,6 +53,10 @@ colorama==0.4.6
# uvicorn
coverage==7.9.2
# via pytest-cov
cssutils==2.11.1
# via dict2css
dict2css==0.3.0.post1
# via sphinx-toolbox
dnspython==2.7.0
# via email-validator
docstring-parser-fork==0.0.12
Expand All @@ -50,7 +65,16 @@ docutils==0.21.2
# via
# restructuredtext-lint
# sphinx
# sphinx-prompt
# sphinx-rtd-theme
# sphinx-tabs
# sphinx-toolbox
domdf-python-tools==3.10.0
# via
# apeye
# apeye-core
# dict2css
# sphinx-toolbox
email-validator==2.2.0
# via
# fastapi
Expand All @@ -65,6 +89,10 @@ fastapi-cli==0.0.8
# via fastapi
fastapi-cloud-cli==0.1.4
# via fastapi-cli
filelock==3.20.0
# via
# cachecontrol
# sphinx-toolbox
flake8==7.3.0
# via
# labthings-fastapi (pyproject.toml)
Expand All @@ -82,6 +110,8 @@ h11==0.16.0
# via
# httpcore
# uvicorn
html5lib==1.1
# via sphinx-toolbox
httpcore==1.0.9
# via httpx
httptools==0.6.4
Expand All @@ -94,9 +124,11 @@ httpx==0.28.1
idna==3.10
# via
# anyio
# apeye-core
# email-validator
# httpx
# requests
# sphinx-prompt
ifaddr==0.2.0
# via zeroconf
imagesize==1.4.1
Expand All @@ -110,22 +142,31 @@ jinja2==3.1.6
# fastapi
# sphinx
# sphinx-autoapi
# sphinx-jinja2-compat
jsonschema==4.24.1
# via labthings-fastapi (pyproject.toml)
jsonschema-specifications==2025.4.1
# via jsonschema
markdown-it-py==3.0.0
# via rich
markupsafe==3.0.2
# via jinja2
# via
# jinja2
# sphinx-jinja2-compat
mccabe==0.7.0
# via flake8
mdurl==0.1.2
# via markdown-it-py
more-itertools==10.8.0
# via cssutils
msgpack==1.1.2
# via cachecontrol
mypy==1.17.0
# via labthings-fastapi (pyproject.toml)
mypy-extensions==1.1.0
# via mypy
natsort==8.4.0
# via domdf-python-tools
numpy==2.2.6
# via labthings-fastapi (pyproject.toml)
orjson==3.11.0
Expand All @@ -138,6 +179,8 @@ pathspec==0.12.1
# via mypy
pillow==11.3.0
# via labthings-fastapi (pyproject.toml)
platformdirs==4.5.0
# via apeye
pluggy==1.6.0
# via
# pytest
Expand Down Expand Up @@ -167,6 +210,8 @@ pygments==2.19.2
# pytest
# rich
# sphinx
# sphinx-prompt
# sphinx-tabs
pytest==8.4.1
# via
# labthings-fastapi (pyproject.toml)
Expand All @@ -193,7 +238,10 @@ referencing==0.36.2
# jsonschema-specifications
# types-jsonschema
requests==2.32.4
# via sphinx
# via
# apeye
# cachecontrol
# sphinx
restructuredtext-lint==1.4.0
# via flake8-rst-docstrings
rich==14.0.0
Expand All @@ -210,26 +258,49 @@ rpds-py==0.26.0
# via
# jsonschema
# referencing
ruamel-yaml==0.18.16
# via sphinx-toolbox
ruamel-yaml-clib==0.2.14
# via ruamel-yaml
ruff==0.12.3
# via labthings-fastapi (pyproject.toml)
sentry-sdk==2.33.0
# via fastapi-cloud-cli
shellingham==1.5.4
# via typer
six==1.17.0
# via html5lib
sniffio==1.3.1
# via anyio
snowballstemmer==3.0.1
# via sphinx
soupsieve==2.8
# via beautifulsoup4
sphinx==8.1.3
# via
# labthings-fastapi (pyproject.toml)
# autodocsumm
# sphinx-autoapi
# sphinx-autodoc-typehints
# sphinx-prompt
# sphinx-rtd-theme
# sphinx-tabs
# sphinx-toolbox
# sphinxcontrib-jquery
sphinx-autoapi==3.6.0
# via labthings-fastapi (pyproject.toml)
sphinx-autodoc-typehints==3.0.1
# via sphinx-toolbox
sphinx-jinja2-compat==0.4.1
# via sphinx-toolbox
sphinx-prompt==1.9.0
# via sphinx-toolbox
sphinx-rtd-theme==3.0.2
# via labthings-fastapi (pyproject.toml)
sphinx-tabs==3.4.5
# via sphinx-toolbox
sphinx-toolbox==4.0.0
# via labthings-fastapi (pyproject.toml)
sphinxcontrib-applehelp==2.0.0
# via sphinx
sphinxcontrib-devhelp==2.0.0
Expand All @@ -246,6 +317,8 @@ sphinxcontrib-serializinghtml==2.0.0
# via sphinx
starlette==0.47.1
# via fastapi
tabulate==0.9.0
# via sphinx-toolbox
tomli==2.2.1
# via
# coverage
Expand All @@ -265,6 +338,8 @@ typing-extensions==4.14.1
# labthings-fastapi (pyproject.toml)
# anyio
# astroid
# beautifulsoup4
# domdf-python-tools
# exceptiongroup
# fastapi
# mypy
Expand All @@ -274,6 +349,7 @@ typing-extensions==4.14.1
# referencing
# rich
# rich-toolkit
# sphinx-toolbox
# starlette
# typer
# typing-inspection
Expand All @@ -286,13 +362,16 @@ urllib3==2.5.0
# via
# requests
# sentry-sdk
# sphinx-prompt
uvicorn==0.35.0
# via
# fastapi
# fastapi-cli
# fastapi-cloud-cli
watchfiles==1.1.0
# via uvicorn
webencodings==0.5.1
# via html5lib
websockets==15.0.1
# via uvicorn
zeroconf==0.147.0
Expand Down
52 changes: 50 additions & 2 deletions docs/source/actions.rst
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file I want to comment on doesn't exist, but this is the same directory.

running_labthings.rst has a / in the path of mything on both lines 12 and 31

Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ Actions

Actions are the way `.Thing` objects are instructed to do things. In Python
terms, any method of a `.Thing` that we want to be able to call over HTTP
should be decorated as an Action, using :deco:`.thing_action`.
should be decorated as an Action, using `.thing_action`.

This page gives an overview of how actions are implemented in LabThings-FastAPI.
:ref:`wot_cc` includes a section on :ref:`wot_actions` that introduces the general concept.
Our implementation should align with :ref:`wot_actions` as defined by the Web of Things standard.

Running actions via HTTP
------------------------
Expand Down Expand Up @@ -58,6 +58,54 @@ The first is ``self`` (the first positional argument), which is always the
supply resources needed by the action. Most often, this is a way of accessing
other `.Things` on the same server.

.. _action_logging:

Logging from actions
--------------------
Action code should use `.Thing.logger` to log messages. This will be configured
to handle messages on a per-invocation basis and make them available when the action
is queried over HTTP.

This may be used to display status updates to the user when an action takes
a long time to run, or it may simply be a helpful debugging aid.

See :mod:`.logs` for details of how this is implemented.

.. _action_cancellation:

Cancelling actions
------------------
If an action could run for a long time, it is useful to be able to cancel it
cleanly. LabThings makes provision for this by allowing actions to be cancelled
using a ``DELETE`` HTTP request. In order to allow an action to be cancelled,
you must give LabThings opportunities to interrupt it. This is most often done
by replacing a `time.sleep()` statement with `.cancellable_sleep()` which
is equivalent, but will raise an exception if the action is cancelled.

For more advanced options, see `.invocation_contexts` for detail.

.. _invocation_context:

Invocation contexts
-------------------
Cancelling actions and capturing their logs requires action code to use a
specific logger and check for cancel events. This is done using `contextvars`
such that the action code can use module-level symbols rather than needing
to explicitly pass the logger and cancel hook as arguments to the action
method.

Usually, you don't need to consider this mechanism: simply use `.Thing.logger`
or `.cancellable_sleep` as explained above. However, if you want to run actions
outside of the server (for example, for testing purposes) or if you want to
call one action from another action, but not share the cancellation signal
or log, functions are provided in `.invocation_contexts` to manage this.

If you start a new thread from an action, code running in that thread will
not have an invocation ID set in a context variable. A subclass of
`threading.Thread` is provided to do this, `.ThreadWithInvocationID`\ .
This may be useful for test code, or if you wish to run actions in the
background, with the option of cancelling them.

Raising exceptions
------------------
If an action raises an unhandled exception, the action will terminate with an Error
Expand Down
10 changes: 9 additions & 1 deletion docs/source/concurrency.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ In the case of properties, the HTTP response is only returned once the `.Thing`

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.

An action can obtain the blocking portal using the `~labthings_fastapi.dependencies.blocking_portal.BlockingPortal` dependency, i.e. by declaring an argument of that type. This avoids referring to the blocking portal through a global variable, which could lead to confusion if there are multiple event loops, e.g. during testing.
An action can run async code using its server interface. See `.ThingServerInterface.start_async_task_soon` for details.

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.

Expand All @@ -22,3 +22,11 @@ Calling Things from other Things

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.

Invocations and concurrency
---------------------------

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.

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`\ .

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``\ .
4 changes: 3 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import labthings_fastapi
import importlib.metadata

# Configuration file for the Sphinx documentation builder.
#
Expand All @@ -12,7 +13,7 @@
project = "labthings-fastapi"
copyright = "2024, Richard Bowman"
author = "Richard Bowman"
release = "0.0.10"
release = importlib.metadata.version("labthings-fastapi")

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand All @@ -23,6 +24,7 @@
# "autodoc2",
"autoapi.extension",
"sphinx_rtd_theme",
"sphinx_toolbox.decorators",
]

templates_path = ["_templates"]
Expand Down
8 changes: 8 additions & 0 deletions docs/source/dependencies/dependencies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
Dependencies
============

.. warning::

The use of dependencies is now deprecated. See :ref:`thing_slots` and `.ThingServerInterface` for a more intuitive way to access that functionality.

LabThings makes use of the powerful "dependency injection" mechanism in FastAPI. You can see the `FastAPI documentation`_ for more information. In brief, FastAPI dependencies are annotated types that instruct FastAPI to supply certain function arguments automatically. This removes the need to set up resources at the start of a function, and ensures everything the function needs is declared and typed clearly. The most common use for dependencies in LabThings is where an action needs to make use of another `.Thing` on the same `.ThingServer`.

Inter-Thing dependencies
------------------------

.. warning::

These dependencies are deprecated - see :ref:`thing_slots` instead.

Simple actions depend only on their input parameters and the `.Thing` on which they are defined. However, it's quite common to need something else, for example accessing another `.Thing` instance on the same LabThings server. There are two important principles to bear in mind here:

* Other `.Thing` instances should be accessed using a `.DirectThingClient` subclass if possible. This creates a wrapper object that should work like a `.ThingClient`, meaning your code should work either on the server or in a client script. This makes the code much easier to debug.
Expand Down
Loading