Use typing.Protocol for defining SimAction rather than Callable TypeAlias - #2988
Conversation
|
Using a Protocol also means that docs can be generated by Sphinx using diff --git a/doc/source/simulator/datamodel.rst b/doc/source/simulator/datamodel.rst
index be1ae396..d35af229 100644
--- a/doc/source/simulator/datamodel.rst
+++ b/doc/source/simulator/datamodel.rst
@@ -39,3 +39,7 @@ Datastore definitions
:undoc-members:
:show-inheritance:
:member-order: bysource
+
+.. autoclass:: pymodbus.simulator.SimAction
+ :special-members: __call__
+ :show-inheritance:
Doing it that way doesn't require any additional extensions for Sphinx, but there are also ways that can take advantage of the However, currently It's possible to add the following line to autodoc_use_legacy_class_based = TrueThis fixes the problem with |
|
@janiversen I can update the docs in a separate PR. |
|
It might also be useful to add
Lines 52 to 57 in 2480640 |
typing.Protocol for defining SimAction rather than callable
typing.Protocol for defining SimAction rather than callabletyping.Protocol for defining SimAction rather than Callable TypeAlias
janiversen
left a comment
There was a problem hiding this comment.
I think I wrote this before.
Using a class for a simple function, looks a lot more complicated for users.
It is important that the typing and documentation remains very simple....to make it obvious that writing a simple function is enough.
Please remember typing is optional and not enforced, independent what you write as type you can pass nearly anything.
|
It was discussed to do a better parameter check of action() but decided not to. There is work ongoing to allow sync functions as well, something SimDevice could easily control. |
Yeah that sounds useful. Adding sync support could be done by just making it a union with a sync I can switch back to the type alias if the goal to keep it simple. The same thing could also be done with To be more consistent with the check |
|
Sorry for laughing a bit, but your "just" hit me. The type alias is the lesser part of the change needed. With async it is easy to do a timeout if the function takes too long, that is quite hard with sync....furthermore e.g. a sync function with db calls, might cause problems im other partsof the code, meaning we get to solve bugs not relatedto pymodbus. |
Co-authored-by: jan iversen <jancasacondor@gmail.com>
You could try making the sync function into a coroutine with The default executor for the running loop is
There is some overhead with switching to a thread and wrapping it in async (maybe not ideal for something called frequently like action), and there are also some considerations about the thread-safety of the contents of whatever the use passes an action. |

The protocol has been made to conform better with the requirement that the
SimActionmust be an async function:pymodbus/pymodbus/simulator/simdevice.py
Lines 123 to 126 in 2480640
Using
Callable[..., Awaitable[...]currently allows something more general to be passed, so usingasync __call__is more inline with the requirement that the action is an async function. Alternatively theCoroutinetype could have been used in place ofAwaitable. This is becauseCoroutineis assignable toAwaitable, but not the other way around (Callableis a subtype ofAwaitable).The protocol is also a convenient place to put documentation for each parameter and is easier to understand than the long list of parameters in the
Callable:pymodbus/pymodbus/simulator/simdevice.py
Lines 16 to 19 in 2480640
Note:
/was intentionally added to the protocol to make it positional only. This means that it behaves exactly the same as the TypeAlias during type checking. The only exception being that it is more specific to reflect the requirement of an async function, not just a callable that returns anAwaitableobject.