labthings-fastapi 0.0.16__tar.gz → 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/PKG-INFO +3 -3
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/pyproject.toml +3 -3
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/__init__.py +0 -2
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/actions.py +1 -7
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/base_descriptor.py +48 -33
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/logs.py +10 -4
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/outputs/blob.py +1 -36
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/server/__init__.py +10 -5
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/server/cli.py +7 -1
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/utilities/__init__.py +4 -9
- labthings_fastapi-0.0.16/src/labthings_fastapi/client/in_server.py +0 -344
- labthings_fastapi-0.0.16/src/labthings_fastapi/dependencies/__init__.py +0 -15
- labthings_fastapi-0.0.16/src/labthings_fastapi/dependencies/blocking_portal.py +0 -77
- labthings_fastapi-0.0.16/src/labthings_fastapi/dependencies/invocation.py +0 -160
- labthings_fastapi-0.0.16/src/labthings_fastapi/dependencies/metadata.py +0 -92
- labthings_fastapi-0.0.16/src/labthings_fastapi/dependencies/raw_thing.py +0 -130
- labthings_fastapi-0.0.16/src/labthings_fastapi/dependencies/thing.py +0 -58
- labthings_fastapi-0.0.16/src/labthings_fastapi/dependencies/thing_server.py +0 -96
- labthings_fastapi-0.0.16/src/labthings_fastapi/deps.py +0 -30
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/.gitignore +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/LICENSE +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/README.md +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/client/__init__.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/endpoints.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/example_things/__init__.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/exceptions.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/invocation_contexts.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/invocations.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/middleware/__init__.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/middleware/url_for.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/notifications.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/outputs/__init__.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/outputs/mjpeg_stream.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/properties.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/py.typed +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/server/config_model.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/server/fallback.html.jinja +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/server/fallback.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/testing.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/thing.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/thing_description/__init__.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/thing_description/_model.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/thing_description/td-json-schema-validation.json +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/thing_description/validation.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/thing_server_interface.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/thing_slots.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/types/__init__.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/types/numpy.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/utilities/introspection.py +0 -0
- {labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/websockets.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labthings-fastapi
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: An implementation of LabThings using FastAPI
|
|
5
5
|
Project-URL: Homepage, https://github.com/labthings/labthings-fastapi
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/labthings/labthings-fastapi/issues
|
|
@@ -11,11 +11,11 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.10
|
|
13
13
|
Requires-Dist: anyio~=4.0
|
|
14
|
-
Requires-Dist: fastapi[all]
|
|
14
|
+
Requires-Dist: fastapi[all]~=0.135.0
|
|
15
15
|
Requires-Dist: httpx
|
|
16
16
|
Requires-Dist: jsonschema
|
|
17
17
|
Requires-Dist: numpy>=1.20
|
|
18
|
-
Requires-Dist: pydantic~=2.
|
|
18
|
+
Requires-Dist: pydantic~=2.12
|
|
19
19
|
Requires-Dist: typing-extensions
|
|
20
20
|
Requires-Dist: zeroconf>=0.28.0
|
|
21
21
|
Provides-Extra: dev
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "labthings-fastapi"
|
|
3
|
-
version = "0.0
|
|
3
|
+
version = "0.1.0"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Richard Bowman", email="richard.bowman@cantab.net" },
|
|
6
6
|
]
|
|
@@ -13,13 +13,13 @@ classifiers = [
|
|
|
13
13
|
"Operating System :: OS Independent",
|
|
14
14
|
]
|
|
15
15
|
dependencies = [
|
|
16
|
-
"pydantic ~= 2.
|
|
16
|
+
"pydantic ~= 2.12",
|
|
17
17
|
"numpy>=1.20",
|
|
18
18
|
"jsonschema",
|
|
19
19
|
"typing_extensions",
|
|
20
20
|
"anyio ~=4.0",
|
|
21
21
|
"httpx",
|
|
22
|
-
"fastapi[all]
|
|
22
|
+
"fastapi[all]~=0.135.0",
|
|
23
23
|
"zeroconf >=0.28.0",
|
|
24
24
|
]
|
|
25
25
|
|
|
@@ -25,7 +25,6 @@ from .thing_server_interface import ThingServerInterface
|
|
|
25
25
|
from .properties import property, setting, DataProperty, DataSetting
|
|
26
26
|
from .actions import action
|
|
27
27
|
from .endpoints import endpoint
|
|
28
|
-
from . import deps
|
|
29
28
|
from . import outputs
|
|
30
29
|
from .outputs import blob
|
|
31
30
|
from .server import ThingServer, cli
|
|
@@ -53,7 +52,6 @@ __all__ = [
|
|
|
53
52
|
"action",
|
|
54
53
|
"thing_slot",
|
|
55
54
|
"endpoint",
|
|
56
|
-
"deps",
|
|
57
55
|
"outputs",
|
|
58
56
|
"blob",
|
|
59
57
|
"ThingServer",
|
|
@@ -48,7 +48,6 @@ from .base_descriptor import (
|
|
|
48
48
|
from .logs import add_thing_log_destination
|
|
49
49
|
from .utilities import model_to_dict, wrap_plain_types_in_rootmodel
|
|
50
50
|
from .invocations import InvocationModel, InvocationStatus
|
|
51
|
-
from .dependencies.invocation import NonWarningInvocationID
|
|
52
51
|
from .exceptions import (
|
|
53
52
|
InvocationCancelledError,
|
|
54
53
|
InvocationError,
|
|
@@ -352,7 +351,6 @@ class ActionManager:
|
|
|
352
351
|
self,
|
|
353
352
|
action: ActionDescriptor,
|
|
354
353
|
thing: Thing,
|
|
355
|
-
id: uuid.UUID,
|
|
356
354
|
input: Any,
|
|
357
355
|
dependencies: dict[str, Any],
|
|
358
356
|
) -> Invocation:
|
|
@@ -366,8 +364,6 @@ class ActionManager:
|
|
|
366
364
|
:param thing: is the object on which we are running the ``action``, i.e.
|
|
367
365
|
it is supplied to the function wrapped by ``action`` as the ``self``
|
|
368
366
|
argument.
|
|
369
|
-
:param id: is a `uuid.UUID` used to identify the invocation, for example
|
|
370
|
-
when polling its status via HTTP.
|
|
371
367
|
:param input: is a `pydantic.BaseModel` representing the body of the HTTP
|
|
372
368
|
request that invoked the action. It is supplied to the function as
|
|
373
369
|
keyword arguments.
|
|
@@ -381,7 +377,7 @@ class ActionManager:
|
|
|
381
377
|
thing=thing,
|
|
382
378
|
input=input,
|
|
383
379
|
dependencies=dependencies,
|
|
384
|
-
id=
|
|
380
|
+
id=uuid.uuid4(),
|
|
385
381
|
)
|
|
386
382
|
self.append_invocation(thread)
|
|
387
383
|
thread.start()
|
|
@@ -821,7 +817,6 @@ class ActionDescriptor(
|
|
|
821
817
|
# the function to the decorator.
|
|
822
818
|
def start_action(
|
|
823
819
|
body: Any, # This annotation will be overwritten below.
|
|
824
|
-
id: NonWarningInvocationID,
|
|
825
820
|
background_tasks: BackgroundTasks,
|
|
826
821
|
**dependencies: Any,
|
|
827
822
|
) -> InvocationModel:
|
|
@@ -831,7 +826,6 @@ class ActionDescriptor(
|
|
|
831
826
|
thing=thing,
|
|
832
827
|
input=body,
|
|
833
828
|
dependencies=dependencies,
|
|
834
|
-
id=id,
|
|
835
829
|
)
|
|
836
830
|
background_tasks.add_task(action_manager.expire_invocations)
|
|
837
831
|
return action.response()
|
{labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/base_descriptor.py
RENAMED
|
@@ -247,7 +247,7 @@ class BaseDescriptorInfo(
|
|
|
247
247
|
encountered directly by someone using LabThings, except as a base class for
|
|
248
248
|
`.Action`\ , `.Property` and others.
|
|
249
249
|
|
|
250
|
-
LabThings uses descriptors to represent the :ref:`
|
|
250
|
+
LabThings uses descriptors to represent the :ref:`wot_affordances` of a `.Thing`\ .
|
|
251
251
|
However, passing descriptors around isn't very elegant for two reasons:
|
|
252
252
|
|
|
253
253
|
* Holding references to Descriptor objects can confuse static type checkers.
|
|
@@ -335,22 +335,6 @@ class BaseDescriptorInfo(
|
|
|
335
335
|
descriptor = self.get_descriptor()
|
|
336
336
|
return descriptor.__get__(self.owning_object_or_error())
|
|
337
337
|
|
|
338
|
-
def set(self, value: Value) -> None:
|
|
339
|
-
"""Set the value of the descriptor.
|
|
340
|
-
|
|
341
|
-
This method may only be called if the DescriptorInfo object is bound to a
|
|
342
|
-
`.Thing` instance. It will raise an error if called on a class.
|
|
343
|
-
|
|
344
|
-
:param value: the new value.
|
|
345
|
-
|
|
346
|
-
:raises NotBoundToInstanceError: if called on an unbound info object.
|
|
347
|
-
"""
|
|
348
|
-
if not self.is_bound:
|
|
349
|
-
msg = f"We can't set the value of {self.name} when called on a class."
|
|
350
|
-
raise NotBoundToInstanceError(msg)
|
|
351
|
-
descriptor = self.get_descriptor()
|
|
352
|
-
descriptor.__set__(self.owning_object_or_error(), value)
|
|
353
|
-
|
|
354
338
|
def __eq__(self, other: Any) -> bool:
|
|
355
339
|
"""Determine if this object is equal to another one.
|
|
356
340
|
|
|
@@ -404,6 +388,11 @@ class BaseDescriptor(Generic[Owner, Value]):
|
|
|
404
388
|
assert p.name == "my_prop"
|
|
405
389
|
assert p.title == "My Property."
|
|
406
390
|
assert p.description.startswith("This is")
|
|
391
|
+
|
|
392
|
+
`.BaseDescriptor` is a "non-data descriptor" (meaning it doesn't implement
|
|
393
|
+
``__set__``). This allows it to be overwritten by assigning to an object's
|
|
394
|
+
attribute, which can be useful in test code. This can easily be changed in
|
|
395
|
+
subclasses by implementing ``__set__``\ .
|
|
407
396
|
"""
|
|
408
397
|
|
|
409
398
|
def __init__(self) -> None:
|
|
@@ -593,21 +582,6 @@ class BaseDescriptor(Generic[Owner, Value]):
|
|
|
593
582
|
"See BaseDescriptor.__instance_get__ for details."
|
|
594
583
|
)
|
|
595
584
|
|
|
596
|
-
def __set__(self, obj: Owner, value: Value) -> None:
|
|
597
|
-
"""Mark the `BaseDescriptor` as a data descriptor.
|
|
598
|
-
|
|
599
|
-
Even for read-only descriptors, it's important to define a ``__set__`` method.
|
|
600
|
-
The presence of this method prevents Python overwriting the descriptor when
|
|
601
|
-
a value is assigned. This base implementation returns an `AttributeError` to
|
|
602
|
-
signal that the descriptor is read-only. Overriding it with a method that
|
|
603
|
-
does not raise an exception will allow the descriptor to be written to.
|
|
604
|
-
|
|
605
|
-
:param obj: The object on which to set the value.
|
|
606
|
-
:param value: The value to set the descriptor to.
|
|
607
|
-
:raises AttributeError: always, as this is read-only by default.
|
|
608
|
-
"""
|
|
609
|
-
raise AttributeError("This attribute is read-only.")
|
|
610
|
-
|
|
611
585
|
def _descriptor_info(
|
|
612
586
|
self, info_class: type[DescriptorInfoT], obj: Owner | None = None
|
|
613
587
|
) -> DescriptorInfoT:
|
|
@@ -669,9 +643,35 @@ class FieldTypedBaseDescriptorInfo(
|
|
|
669
643
|
"""The type of the descriptor's value."""
|
|
670
644
|
return self.get_descriptor().value_type
|
|
671
645
|
|
|
646
|
+
def set(self, value: Value) -> None:
|
|
647
|
+
"""Set the value of the descriptor.
|
|
648
|
+
|
|
649
|
+
This method may only be called if the DescriptorInfo object is bound to a
|
|
650
|
+
`.Thing` instance. It will raise an error if called on a class.
|
|
651
|
+
|
|
652
|
+
:param value: the new value.
|
|
653
|
+
|
|
654
|
+
:raises NotBoundToInstanceError: if called on an unbound info object.
|
|
655
|
+
"""
|
|
656
|
+
if not self.is_bound:
|
|
657
|
+
msg = f"We can't set the value of {self.name} when called on a class."
|
|
658
|
+
raise NotBoundToInstanceError(msg)
|
|
659
|
+
descriptor = self.get_descriptor()
|
|
660
|
+
descriptor.__set__(self.owning_object_or_error(), value)
|
|
661
|
+
|
|
672
662
|
|
|
673
663
|
class FieldTypedBaseDescriptor(Generic[Owner, Value], BaseDescriptor[Owner, Value]):
|
|
674
|
-
"""A BaseDescriptor that determines its type like a dataclass field.
|
|
664
|
+
r"""A `.BaseDescriptor` that determines its type like a dataclass field.
|
|
665
|
+
|
|
666
|
+
This adds two things to `.BaseDescriptor`\ :
|
|
667
|
+
|
|
668
|
+
1. Descriptors inheriting from this class will inspect the type annotations of
|
|
669
|
+
their owning class when determining ``value_type``\ .
|
|
670
|
+
2. This class and its children will be "data descriptors" because there is a
|
|
671
|
+
stub implementation of ``__set__``\ . This means that the attribute may not
|
|
672
|
+
be assigned to (unless ``__set__`` is overridden). This is the behaviour
|
|
673
|
+
that `builtins.property` has.
|
|
674
|
+
"""
|
|
675
675
|
|
|
676
676
|
def __init__(self) -> None:
|
|
677
677
|
"""Initialise the FieldTypedBaseDescriptor.
|
|
@@ -852,6 +852,21 @@ class FieldTypedBaseDescriptor(Generic[Owner, Value], BaseDescriptor[Owner, Valu
|
|
|
852
852
|
"""
|
|
853
853
|
return self._descriptor_info(FieldTypedBaseDescriptorInfo, owner)
|
|
854
854
|
|
|
855
|
+
def __set__(self, obj: Owner, value: Value) -> None:
|
|
856
|
+
"""Mark the `BaseDescriptor` as a data descriptor.
|
|
857
|
+
|
|
858
|
+
Even for read-only descriptors, it's important to define a ``__set__`` method.
|
|
859
|
+
The presence of this method prevents Python overwriting the descriptor when
|
|
860
|
+
a value is assigned. This base implementation returns an `AttributeError` to
|
|
861
|
+
signal that the descriptor is read-only. Overriding it with a method that
|
|
862
|
+
does not raise an exception will allow the descriptor to be written to.
|
|
863
|
+
|
|
864
|
+
:param obj: The object on which to set the value.
|
|
865
|
+
:param value: The value to set the descriptor to.
|
|
866
|
+
:raises AttributeError: always, as this is read-only by default.
|
|
867
|
+
"""
|
|
868
|
+
raise AttributeError("This attribute is read-only.")
|
|
869
|
+
|
|
855
870
|
|
|
856
871
|
class DescriptorInfoCollection(
|
|
857
872
|
Mapping[str, DescriptorInfoT],
|
|
@@ -79,11 +79,11 @@ class DequeByInvocationIDHandler(logging.Handler):
|
|
|
79
79
|
pass # If there's no destination for a particular log, ignore it.
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
def configure_thing_logger() -> None:
|
|
82
|
+
def configure_thing_logger(level: int | None = None) -> None:
|
|
83
83
|
"""Set up the logger for thing instances.
|
|
84
84
|
|
|
85
|
-
We always set the logger for thing instances to level INFO
|
|
86
|
-
is currently used to relay progress to the client.
|
|
85
|
+
We always set the logger for thing instances to level INFO by default,
|
|
86
|
+
as this is currently used to relay progress to the client.
|
|
87
87
|
|
|
88
88
|
This function will collect logs on a per-invocation
|
|
89
89
|
basis by adding a `.DequeByInvocationIDHandler` to the log. Only one
|
|
@@ -93,8 +93,14 @@ def configure_thing_logger() -> None:
|
|
|
93
93
|
a filter to add invocation ID is not possible. Instead, we attach a filter to
|
|
94
94
|
the handler, which filters all the records that propagate to it (i.e. anything
|
|
95
95
|
that starts with ``labthings_fastapi.things``).
|
|
96
|
+
|
|
97
|
+
:param level: the logging level to use. If not specified, we use INFO.
|
|
96
98
|
"""
|
|
97
|
-
|
|
99
|
+
if level is not None:
|
|
100
|
+
THING_LOGGER.setLevel(level)
|
|
101
|
+
else:
|
|
102
|
+
THING_LOGGER.setLevel(logging.INFO)
|
|
103
|
+
|
|
98
104
|
if not any(
|
|
99
105
|
isinstance(h, DequeByInvocationIDHandler) for h in THING_LOGGER.handlers
|
|
100
106
|
):
|
|
@@ -93,7 +93,6 @@ from typing import (
|
|
|
93
93
|
Literal,
|
|
94
94
|
Mapping,
|
|
95
95
|
)
|
|
96
|
-
from warnings import warn
|
|
97
96
|
from weakref import WeakValueDictionary
|
|
98
97
|
from tempfile import TemporaryDirectory
|
|
99
98
|
import uuid
|
|
@@ -615,7 +614,7 @@ class Blob:
|
|
|
615
614
|
"""
|
|
616
615
|
return core_schema.no_info_wrap_validator_function(
|
|
617
616
|
cls._validate,
|
|
618
|
-
BlobModel.
|
|
617
|
+
BlobModel.__pydantic_core_schema__,
|
|
619
618
|
serialization=core_schema.wrap_serializer_function_ser_schema(
|
|
620
619
|
cls._serialize,
|
|
621
620
|
is_field_serializer=False,
|
|
@@ -881,40 +880,6 @@ class Blob:
|
|
|
881
880
|
)
|
|
882
881
|
|
|
883
882
|
|
|
884
|
-
def blob_type(media_type: str) -> type[Blob]:
|
|
885
|
-
r"""Create a `.Blob` subclass for a given media type.
|
|
886
|
-
|
|
887
|
-
This convenience function may confuse static type checkers, so it is usually
|
|
888
|
-
clearer to make a subclass instead, e.g.:
|
|
889
|
-
|
|
890
|
-
.. code-block:: python
|
|
891
|
-
|
|
892
|
-
class MyImageBlob(Blob):
|
|
893
|
-
media_type = "image/png"
|
|
894
|
-
|
|
895
|
-
:param media_type: the media type that the new `.Blob` subclass will use.
|
|
896
|
-
|
|
897
|
-
:return: a subclass of `.Blob` with the specified media type.
|
|
898
|
-
|
|
899
|
-
:raise ValueError: if the media type contains ``'`` or ``\``.
|
|
900
|
-
"""
|
|
901
|
-
warn(
|
|
902
|
-
"`blob_type` is deprecated and will be removed in v0.1.0. "
|
|
903
|
-
"Create a subclass of `Blob` instead.",
|
|
904
|
-
DeprecationWarning,
|
|
905
|
-
stacklevel=2,
|
|
906
|
-
)
|
|
907
|
-
if "'" in media_type or "\\" in media_type:
|
|
908
|
-
raise ValueError("media_type must not contain single quotes or backslashes")
|
|
909
|
-
return type(
|
|
910
|
-
f"{media_type.replace('/', '_')}_blob",
|
|
911
|
-
(Blob,),
|
|
912
|
-
{
|
|
913
|
-
"media_type": media_type,
|
|
914
|
-
},
|
|
915
|
-
)
|
|
916
|
-
|
|
917
|
-
|
|
918
883
|
router = APIRouter()
|
|
919
884
|
"""A FastAPI router for BlobData download endpoints."""
|
|
920
885
|
|
{labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/server/__init__.py
RENAMED
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
from typing import Any, AsyncGenerator, Optional, TypeVar
|
|
11
11
|
from typing_extensions import Self
|
|
12
12
|
import os
|
|
13
|
+
import logging
|
|
13
14
|
|
|
14
15
|
from fastapi import FastAPI, Request
|
|
15
16
|
from fastapi.middleware.cors import CORSMiddleware
|
|
@@ -27,7 +28,6 @@ from ..logs import configure_thing_logger
|
|
|
27
28
|
from ..thing import Thing
|
|
28
29
|
from ..thing_server_interface import ThingServerInterface
|
|
29
30
|
from ..thing_description._model import ThingDescription
|
|
30
|
-
from ..dependencies.thing_server import _thing_servers # noqa: F401
|
|
31
31
|
from .config_model import (
|
|
32
32
|
ThingsConfig,
|
|
33
33
|
ThingServerConfig,
|
|
@@ -66,6 +66,7 @@ class ThingServer:
|
|
|
66
66
|
things: ThingsConfig,
|
|
67
67
|
settings_folder: Optional[str] = None,
|
|
68
68
|
application_config: Optional[Mapping[str, Any]] = None,
|
|
69
|
+
debug: bool = False,
|
|
69
70
|
) -> None:
|
|
70
71
|
r"""Initialise a LabThings server.
|
|
71
72
|
|
|
@@ -86,9 +87,12 @@ class ThingServer:
|
|
|
86
87
|
application. This is not processed by LabThings. Each `.Thing` can access
|
|
87
88
|
application. This is not processed by LabThings. Each `.Thing` can access
|
|
88
89
|
this via the Thing-Server interface.
|
|
90
|
+
:param debug: If ``True``, set the log level for `.Thing` instances to
|
|
91
|
+
DEBUG.
|
|
89
92
|
"""
|
|
90
93
|
self.startup_failure: dict | None = None
|
|
91
|
-
|
|
94
|
+
# Note: this is safe to call multiple times.
|
|
95
|
+
configure_thing_logger(logging.DEBUG if debug else None)
|
|
92
96
|
self._config = ThingServerConfig(
|
|
93
97
|
things=things,
|
|
94
98
|
settings_folder=settings_folder,
|
|
@@ -105,22 +109,23 @@ class ThingServer:
|
|
|
105
109
|
self.blocking_portal: Optional[BlockingPortal] = None
|
|
106
110
|
self.startup_status: dict[str, str | dict] = {"things": {}}
|
|
107
111
|
global _thing_servers # noqa: F824
|
|
108
|
-
_thing_servers.add(self)
|
|
109
112
|
# The function calls below create and set up the Things.
|
|
110
113
|
self._things = self._create_things()
|
|
111
114
|
self._connect_things()
|
|
112
115
|
self._attach_things_to_server()
|
|
113
116
|
|
|
114
117
|
@classmethod
|
|
115
|
-
def from_config(cls, config: ThingServerConfig) -> Self:
|
|
118
|
+
def from_config(cls, config: ThingServerConfig, debug: bool = False) -> Self:
|
|
116
119
|
r"""Create a ThingServer from a configuration model.
|
|
117
120
|
|
|
118
121
|
This is equivalent to ``ThingServer(**dict(config))``\ .
|
|
119
122
|
|
|
120
123
|
:param config: The configuration parameters for the server.
|
|
124
|
+
:param debug: If ``True``, set the log level for `.Thing` instances to
|
|
125
|
+
DEBUG.
|
|
121
126
|
:return: A `.ThingServer` configured as per the model.
|
|
122
127
|
"""
|
|
123
|
-
return cls(**dict(config))
|
|
128
|
+
return cls(**dict(config), debug=debug)
|
|
124
129
|
|
|
125
130
|
def _set_cors_middleware(self) -> None:
|
|
126
131
|
"""Configure the server to allow requests from other origins.
|
|
@@ -56,6 +56,11 @@ def get_default_parser() -> ArgumentParser:
|
|
|
56
56
|
default=5000,
|
|
57
57
|
help="Bind socket to this port. If 0, an available port will be picked.",
|
|
58
58
|
)
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
"--debug",
|
|
61
|
+
action="store_true",
|
|
62
|
+
help="Enable debug logging.",
|
|
63
|
+
)
|
|
59
64
|
return parser
|
|
60
65
|
|
|
61
66
|
|
|
@@ -149,10 +154,11 @@ def serve_from_cli(
|
|
|
149
154
|
option is not specified.
|
|
150
155
|
"""
|
|
151
156
|
args = parse_args(argv)
|
|
157
|
+
|
|
152
158
|
try:
|
|
153
159
|
config, server = None, None
|
|
154
160
|
config = config_from_args(args)
|
|
155
|
-
server = ThingServer.from_config(config)
|
|
161
|
+
server = ThingServer.from_config(config, True if args.debug else False)
|
|
156
162
|
if dry_run:
|
|
157
163
|
return server
|
|
158
164
|
uvicorn.run(server.app, host=args.host, port=args.port)
|
{labthings_fastapi-0.0.16 → labthings_fastapi-0.1.0}/src/labthings_fastapi/utilities/__init__.py
RENAMED
|
@@ -6,7 +6,6 @@ from typing import Any, Dict, Generic, Iterable, TYPE_CHECKING, Optional, TypeVa
|
|
|
6
6
|
from weakref import WeakSet
|
|
7
7
|
from pydantic import BaseModel, ConfigDict, Field, RootModel, create_model
|
|
8
8
|
from pydantic.dataclasses import dataclass
|
|
9
|
-
from pydantic_core import SchemaError
|
|
10
9
|
|
|
11
10
|
from labthings_fastapi.exceptions import UnsupportedConstraintError
|
|
12
11
|
from .introspection import EmptyObject
|
|
@@ -129,7 +128,7 @@ def wrap_plain_types_in_rootmodel(
|
|
|
129
128
|
:raises UnsupportedConstraintError: if constraints are provided for an
|
|
130
129
|
unsuitable type, for example `allow_inf_nan` for an `int` property, or
|
|
131
130
|
any constraints for a `BaseModel` subclass.
|
|
132
|
-
:raises
|
|
131
|
+
:raises RuntimeError: if other errors prevent Pydantic from creating a schema
|
|
133
132
|
for the generated model.
|
|
134
133
|
"""
|
|
135
134
|
try: # This needs to be a `try` as basic types are not classes
|
|
@@ -148,13 +147,9 @@ def wrap_plain_types_in_rootmodel(
|
|
|
148
147
|
root=(model, Field(**constraints)),
|
|
149
148
|
__base__=LabThingsRootModelWrapper,
|
|
150
149
|
)
|
|
151
|
-
except
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
key = error["loc"][-1]
|
|
155
|
-
raise UnsupportedConstraintError(
|
|
156
|
-
f"Constraint {key} is not supported for type {model!r}."
|
|
157
|
-
) from e
|
|
150
|
+
except RuntimeError as e:
|
|
151
|
+
if "Unable to apply constraint" in str(e):
|
|
152
|
+
raise UnsupportedConstraintError(str(e)) from e
|
|
158
153
|
raise e
|
|
159
154
|
|
|
160
155
|
|