diff --git a/royalnet/engineer/params.py b/royalnet/engineer/params.py index c79fd411..4457750e 100644 --- a/royalnet/engineer/params.py +++ b/royalnet/engineer/params.py @@ -40,11 +40,11 @@ def parameter_to_field(param: inspect.Parameter, **kwargs) -> Tuple[type, pydant def signature_to_model(f: Callable, __config__: pydantic.BaseConfig = ModelConfig, **extra_params): """ - Convert the signature of a function to a pydantic model. + Convert the signature of a async function to a pydantic model. Arguments starting with ``_`` are ignored. - :param f: The function to use the signature of. + :param f: The async function to use the signature of. :param __config__: The config the pydantic model should use. :param extra_params: Extra parameters to be added to the model. :return: The created pydantic model. diff --git a/royalnet/engineer/router.py b/royalnet/engineer/router.py index d0ec6d76..24391eda 100644 --- a/royalnet/engineer/router.py +++ b/royalnet/engineer/router.py @@ -18,7 +18,8 @@ class EngineerRouter: :param name: The name of the command (``start``, ``settings``, etc). If not specified, it will use the name of the wrapped function. - :param f: The function that should be executed when the command is called. It must have a ``.model`` property. + :param f: The async function that should be executed when the command is called. + It must have a ``.model`` property. .. seealso:: :meth:`.command`, :func:`.params.function_with_model` """ @@ -33,13 +34,13 @@ class EngineerRouter: .. code-block:: python @command() - def ping(): + async def ping(): print("Pong!") .. code-block:: python @command(name="ping") - def xyzzy(): + async def xyzzy(): print("Pong!") :param name: The name of the command (``start``, ``settings``, etc). If not specified, it will use the name @@ -61,7 +62,18 @@ class EngineerRouter: return decorator - def call(self, __name: str, **kwargs): + async def call(self, __name: str, **kwargs) -> Any: + """ + Call the command with the specified name using the passed kwargs. + + :param __name: The name of the function. + :param kwargs: Kwargs to pass to the desired function: + - Kwargs starting with ``__`` are never passed to the function. + - Kwargs starting with ``_`` are passed as they are. + - Other kwargs are validated by the function's :mod:`pydantic` model. + :return: The return value of the function. + :raises pydantic.ValidationError: If the kwargs do not pass the :mod:`pydantic` model validation. + """ model_params = {} extra_params = {} for key, value in kwargs.items(): @@ -74,7 +86,7 @@ class EngineerRouter: # noinspection PyPep8Naming Model: Type[pydantic.BaseModel] = f.model model: pydantic.BaseModel = Model(**model_params) - return f(**model.dict(), **extra_params) + return await f(**model.dict(), **extra_params) __all__ = ( diff --git a/royalnet/engineer/tests/test_params.py b/royalnet/engineer/tests/test_params.py index e636c672..6e0ddc5b 100644 --- a/royalnet/engineer/tests/test_params.py +++ b/royalnet/engineer/tests/test_params.py @@ -6,14 +6,14 @@ import royalnet.engineer as re @pytest.fixture -def a_random_function(): +async def my_async_function(): def f(*, big_f: str, _hidden: int) -> str: return big_f return f -def test_parameter_to_field(a_random_function): - signature = inspect.signature(a_random_function) +def test_parameter_to_field(my_async_function): + signature = inspect.signature(my_async_function) parameter = signature.parameters["big_f"] t, fieldinfo = re.parameter_to_field(parameter) assert isinstance(fieldinfo, pydantic.fields.FieldInfo) @@ -21,8 +21,8 @@ def test_parameter_to_field(a_random_function): assert fieldinfo.title == parameter.name == "big_f" -def test_signature_to_model(a_random_function): - Model = re.signature_to_model(a_random_function) +def test_signature_to_model(my_async_function): + Model = re.signature_to_model(my_async_function) assert callable(Model) model = Model(big_f="banana")