Skip to content

Commit e1b5d61

Browse files
committed
Update dependencies docs
I've also added a test for the example in here.
1 parent 1022ca3 commit e1b5d61

File tree

6 files changed

+75
-49
lines changed

6 files changed

+75
-49
lines changed

docs/source/dependencies.rst

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
Dependencies
3+
============
4+
5+
Simple actions depend only on their input parameters and the :class:`~labthings_fastapi.thing.Thing` on which they are defined. However, it's quite common to need something else, for example accessing another :class:`~labthings_fastapi.thing.Thing` instance on the same LabThings server. There are two important principles to bear in mind here:
6+
7+
* Other :class:`~labthings_fastapi.thing.Thing` instances should be accessed using a :class:`~labthings_fastapi.client.in_server.DirectThingClient` subclass if possible. This creates a wrapper object that should work like a :class:`~labthings_fastapi.client.ThingClient`, meaning your code should work either on the server or in a client script. This makes the code much easier to debug.
8+
* LabThings uses the FastAPI "dependency injection" mechanism, where you specify what's needed with type hints, and the argument is supplied automatically at run-time. You can see the `FastAPI documentation`_ for more information.
9+
10+
LabThings provides a shortcut to create the annotated type needed to declare a dependency on another :class:`~labthings_fastapi.thing.Thing`, with the function :func:`~labthings_fastapi.dependencies.thing.direct_thing_client_dependency`. This generates a type annotation that you can use when you define your actions, that will supply a client object when the action is called.
11+
12+
:func:`~labthings_fastapi.dependencies.thing.direct_thing_client_dependency` takes a :func:`~labthings_fastapi.thing.Thing` class and a path as arguments: these should match the configuration of your LabThings server. Optionally, you can specify the actions that you're going to use. The default behaviour is to make all actions available, however it is more efficient to specify only the actions you will use.
13+
14+
Dependencies are added recursively - so if you depend on another Thing, and some of its actions have their own dependencies, those dependencies are also added to your action. Using the ``actions`` argument means you only need the dependencies of the actions you are going to use, which is more efficient.
15+
16+
.. literalinclude:: example.py
17+
:language: python
18+
19+
In the example above, the :func:`increment_counter` action on :class:`TestThing` takes a :class:`MyThing` as an argument. When the action is called, the ``my_thing`` argument is supplied automatically. The argument is not the :class:`MyThing` instance, instead it is a wrapper class (a dynamically generated :class:`~labthings_fastapi.client.in_server.DirectThingClient` subclass). The wrapper should have the same signature as a :class:`~labthings_fastapi.client.ThingClient`. This means any dependencies of actions on the :class:`MyThing` are automatically supplied, so you only need to worry about the arguments that are not dependencies. The aim of this is to ensure that the code you write for your :class:`Thing` is as similar as possible to the code you'd write if you were using it through the Python client module.
20+
21+
If you need access to the actual Python object (e.g. you need to access methods that are not decorated as actions), you can use the :func:`~labthings_fastapi.dependencies.raw_thing.raw_thing_dependency` function instead. This will give you the actual Python object, but you will need to supply all the arguments of the actions, including dependencies, yourself.
22+
23+
.. _`FastAPI documentation`: https://fastapi.tiangolo.com/tutorial/dependencies/
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from labthings_fastapi.thing import Thing
2+
from labthings_fastapi.decorators import thing_action
3+
from labthings_fastapi.dependencies.thing import direct_thing_client_dependency
4+
from labthings_fastapi.example_things import MyThing
5+
from labthings_fastapi.server import ThingServer
6+
7+
MyThingDep = direct_thing_client_dependency(MyThing, "/mything/")
8+
9+
class TestThing(Thing):
10+
"""A test thing with a counter property and a couple of actions"""
11+
12+
@thing_action
13+
def increment_counter(self, my_thing: MyThingDep) -> None:
14+
"""Increment the counter on another thing"""
15+
my_thing.increment_counter()
16+
17+
server = ThingServer()
18+
server.add_thing(MyThing(), "/mything/")
19+
server.add_thing(TestThing(), "/testthing/")
20+
21+
if __name__ == "__main__":
22+
import uvicorn
23+
uvicorn.run(server.app, port=5000)

docs/source/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Welcome to labthings-fastapi's documentation!
1212

1313
core_concepts.rst
1414
quickstart/quickstart.rst
15-
dependencies.rst
15+
dependencies/dependencies.rst
1616

1717
apidocs/index
1818

tests/test_docs.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from subprocess import Popen, PIPE, STDOUT
2+
import os
3+
from pathlib import Path
4+
from runpy import run_path
5+
from test_server_cli import MonitoredProcess
6+
from fastapi.testclient import TestClient
7+
from labthings_fastapi.client import ThingClient
8+
9+
10+
this_file = Path(__file__)
11+
repo = this_file.parents[1]
12+
docs = repo / "docs" / "source"
13+
14+
def run_quickstart_counter():
15+
# A server is started in the `__name__ == "__main__" block`
16+
run_path(docs / "quickstart" / "counter.py")
17+
18+
19+
def test_quickstart_counter():
20+
"""Check we can create a server from the command line"""
21+
p = MonitoredProcess(target=run_quickstart_counter)
22+
p.run_monitored(terminate_outputs=["Application startup complete"])
23+
24+
def test_dependency_example():
25+
globals = run_path(docs / "dependencies" / "example.py", run_name="not_main")
26+
with TestClient(globals["server"].app) as client:
27+
testthing = ThingClient.from_url("/testthing/", client=client)
28+
testthing.increment_counter()

tests/test_docs_quickstart.py

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

0 commit comments

Comments
 (0)