Source code for clu.parsers.json

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# @Author: José Sánchez-Gallego (gallegoj@uw.edu)
# @Date: 2021-04-08
# @Filename: json.py
# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause)

import asyncio
import json

from typing import Any, Callable, Coroutine, Dict, List, TypeVar

from ..command import Command


__all__ = ["JSONParser"]


T = TypeVar("T", bound=Command)


DEFAULT_CALLBACKS = {}


[docs] class JSONParser: """A parser that receives commands and arguments as a JSON string. See :ref:`json-parser` for details on implementation and use-cases. """ #: list: Additional arguments to be passed to each command in the parser. #: Note that the command is always passed first. parser_args: List[Any] = [] #: dict: Mapping of command verb to callback coroutine. callbacks: Dict[str, Callable[..., Coroutine]] = DEFAULT_CALLBACKS
[docs] def parse_command(self, command: T) -> T: """Parses a user command. The command string must be a serialised JSON-like string that contains at least a keyword ``command`` with the name of the callback, and any number of additional arguments which will be passed to it. """ # This will pass the command as the first argument for each command. # If self.parser_args is defined, those arguments will be passed next. parser_args = [command] parser_args += self.parser_args # Empty command. Just finish the command. if not command.body: command.done() return command command.set_status(command.status.RUNNING) try: payload = json.loads(command.body) except json.JSONDecodeError as err: return command.fail( error=f"Cannot deserialise command string {command.body!r}: {err}" ) if "command" not in payload: return command.fail( error=f"Command {command.body!r} does not contain " "a 'command' parameter." ) verb = payload.pop("command") if verb not in self.callbacks: return command.fail(error=f"Cannot find a callback for command {verb!r}.") elif not asyncio.iscoroutinefunction(self.callbacks[verb]): return command.fail(error=f"Callback {verb!r} is not a coroutine function.") try: asyncio.create_task(self.callbacks[verb](*parser_args, payload)) except Exception as err: return command.fail( error="Errored scheduling callback coroutine for " f"command {verb!r}: {err}" ) return command