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
@@ -13,17 +13,16 @@ Only methods decorated with `action()` are exposed to clients.
13
13
14
14
## Payload Validation
15
15
16
-
Arguments are loosely typed and may need to be constrained with a schema based
17
-
on the robustness the developer is expecting in their application:
16
+
If arguments are loosely typed, the action will be invoked with given payload without any validation. One may validate them manually inside the method. However, one can also specify the expected argument schema using either `JSON Schema` or `pydantic` models:
18
17
19
18
<aid="actions-argument-schema"></a>
20
19
=== "JSON Schema"
21
20
22
21
=== "Single Argument"
23
22
24
-
Just specify the expected type of the argument (with or without name)
23
+
Specify the expected type of the argument (with or without name)
@@ -47,10 +46,10 @@ on the robustness the developer is expecting in their application:
47
46
48
47
=== "Multiple Arguments"
49
48
50
-
You need to specify the action argument names under the `properties` field with `type` as `object`.
51
-
Names not found in the `properties` field can be subsumed under python spread operator `**kwargs` if necessary.
49
+
Specify the argument names under the `properties` field with `type` as `object`.
50
+
Names not found in the `properties` field can be subsumed under python spread operator `**kwargs` if necessary (dont set `additionalProperties` to `False` in that case).
52
51
53
-
```py title="Input Schema with Multiple Arguments" linenums="1"
52
+
```py title="Input Schema with Multiple Arguments" linenums="1" hl_lines="32"
@@ -151,7 +152,9 @@ on the robustness the developer is expecting in their application:
151
152
152
153
=== "Single Argument"
153
154
154
-
```py title="Input Schema with Single Argument" linenums="1"
155
+
Type annotate the argument, either plainly or with `Annotated`. A pydantic model will be composed with the argument name as the field name and the type annotation as the field type:
156
+
157
+
```py title="Input Schema with Single Argument" linenums="1" hl_lines="7"
155
158
from typing import Annotated
156
159
157
160
class GentecOpticalEnergyMeter(Thing):
@@ -196,6 +199,8 @@ on the robustness the developer is expecting in their application:
196
199
197
200
=== "Multiple Arguments"
198
201
202
+
Again, type annotate the arguments, either plainly or with `Annotated`:
203
+
199
204
```py title="Input Schema with Multiple Arguments"
200
205
from typing import Literal
201
206
@@ -284,6 +289,8 @@ on the robustness the developer is expecting in their application:
284
289
285
290
=== "Return Type"
286
291
292
+
type annotate the return type:
293
+
287
294
```py
288
295
from typing import Annotated
289
296
from pydantic import Field
@@ -321,8 +328,38 @@ on the robustness the developer is expecting in their application:
321
328
}
322
329
```
323
330
324
-
However, a schema is optional and it only matters that
325
-
the method signature is matching when requested from a client.
331
+
=== "Supply Models Directly"
332
+
333
+
If the composed models from the type annotations are not sufficient or contain errors, one may directly supply the models:
334
+
335
+
```py title="With Direct Models" linenums="1" hl_lines="3 7 22"
336
+
from pydantic import BaseModel, field_validator
337
+
338
+
class CommandModel(BaseModel):
339
+
command: str
340
+
return_data_size: int = Field(0, ge=0)
341
+
342
+
@field_validator("command")
343
+
def validate_command(cls, v):
344
+
if not isinstance(v, str) or not v:
345
+
raise ValueError("Command must be a non-empty string")
346
+
if command not in SerialUtility.supported_commands:
347
+
raise ValueError(f"Command {command} is not supported")
348
+
return v
349
+
350
+
class ResponseModel(BaseModel):
351
+
response: str = Field(..., description="Response from the device")
def execute_instruction(self, command: str, return_data_size: int = 0) -> str:
359
+
"""
360
+
executes instruction given by the ASCII string parameter 'command'
361
+
"""
362
+
```
326
363
327
364
<!-- To enable this, set global attribute`allow_relaxed_schema_actions=True`. This setting is used especially when a schema is useful for validation of arguments but not available - not for methods with no arguments.
328
365
@@ -366,8 +403,7 @@ client side, there is no difference between invoking a normal action and an acti
366
403
367
404
## Threaded & Async Actions
368
405
369
-
Actions can be made asynchronous or threaded by setting the `synchronous` flag to `False` in the decorator. For methods
370
-
that are **not**`async`:
406
+
Actions can be made asynchronous or threaded by setting the `synchronous` flag to `False`. For methods that are **not**`async`:
371
407
372
408
```py title="Threaded Actions" linenums="3"
373
409
classServoMotor(Thing):
@@ -408,32 +444,32 @@ class DCPowerSupply(Thing):
408
444
# The suitability of this example in a realistic use case is untested
409
445
```
410
446
411
-
Same applies for `async`:
447
+
For `async` actions:
412
448
413
449
```py title="Async Actions" linenums="3"
414
450
classDCPowerSupply(Thing):
415
451
416
452
@action(create_task=True)
453
+
# @action(synchronous=False) # exactly the same effect for async methods
Copy file name to clipboardExpand all lines: docs/beginners-guide/articles/events.md
+45-35Lines changed: 45 additions & 35 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -30,16 +30,16 @@ One can subscribe to the event using the attribute name:
30
30
31
31
=== "async"
32
32
33
-
In the asynchronous mode, the `subscribe_event` method creates an event listening task in the running async loop:
33
+
In the asynchronous mode, the `subscribe_event` method creates an event listening task in the running async loop. This requires the client to be running in an async loop, otherwise no events will be received although the server will be publishing it:
@@ -48,70 +48,76 @@ One can subscribe to the event using the attribute name:
48
48
)
49
49
```
50
50
51
-
---
51
+
The callback function(s) must accept a single argument which is the event data payload, an instance of `SSE` object. The payload can be accessed as using the `data` attribute:
One can also supply multiple callbacks which may called in series, threaded or async:
58
+
> The `SSE` object also contains metadata like `id`, `event` name and `retry` interval, but these are currently not well supported. Improvements in the future are expected.
59
+
60
+
Each subscription creates a new event stream. One can also supply multiple callbacks which may called in series or concurrently:
54
61
55
62
=== "sequential"
56
63
57
64
The background thread that listens to the event executes the callbacks in series in its own thread:
> In GUI frameworks like PyQt, you cannot paint the GUI from the event thread. You would need to use signals and slots or other mechanisms to update the GUI to hand over the data.
0 commit comments