Source code for ska.low.mccs.utils
"""
Module for MCCS utils
"""
from functools import wraps
import inspect
import json
import jsonschema
from tango import Except, ErrSeverity
from tango.server import Device
[docs]def tango_raise(msg, reason="API_CommandFailed", severity=ErrSeverity.ERR,
_origin=None):
"""Helper function to provide a concise way to throw `tango.Except.throw_exception`
Example::
class MyDevice(Device):
@command
def some_command(self):
if condition:
pass
else:
tango_throw("Condition not true")
:param msg: [description]
:type msg: [type]
:param reason: the tango api DevError description string, defaults to
"API_CommandFailed"
:type reason: str, optional
:param severity: the tango error severity, defaults to `tango.ErrSeverity.ERR`
:type severity: `tango.ErrSeverity`, optional
:param _origin: the calling object name, defaults to None (autodetected)
Note that autodetection only works for class methods not e.g.
decorators
:type _origin: str, optional
"""
if _origin is None:
frame = inspect.currentframe().f_back
calling_method = frame.f_code.co_name
calling_class = frame.f_locals["self"].__class__
if Device not in inspect.getmro(calling_class):
raise TypeError("Can only be used in a tango device instance")
class_name = calling_class.__name__
_origin = f"{class_name}.{calling_method}()"
Except.throw_exception(reason, msg, _origin, severity)
[docs]def call_with_json(func, **kwargs):
"""
Allows the calling of a command that accepts a JSON string as input,
with the actual unserialised parameters.
:param func: the function to call
:ptype func: callable
:param kwargs: parameters to be jsonified and passed to func
:ptype kwargs: any
:return: the return value of func
:example: Suppose you need to use MccsMaster.Allocate() to command
a master device to allocate certain stations and tiles to a
subarray. Allocate() accepts a single JSON string argument.
Instead of
parameters={"id": id, "stations": stations, "tiles": tiles}
json_string=json.dumps(parameters)
master.Allocate(json_string)
save yourself the trouble and
call_with_json(master.Allocate,
id=id, stations=stations, tiles=tiles)
"""
return func(json.dumps(kwargs))
[docs]class json_input:
"""
Method decorator that parses and validates JSON input into a python
object. The wrapped method is thus called with a JSON string, but
can be implemented as if it had been passed an object.
If the string cannot be parsed as JSON, an exception is raised.
:param schema_path: an optional path to a schema against which the
JSON should be validated. Not working at the moment, so leave it
None.
:ptype: string
:raises FileNotFoundException: if no file is found at the schema
path provided
:raises json.JSONDecodeError: if the file at the specified schema
path is not valid JSON
:example: Conceptually, MccsMaster.Allocate() takes as arguments a
subarray id, an array of stations, and an array of tiles. In
practice, however, these arguments are encoded into a JSON
string. Implement the function with its conceptual parameters,
then wrap it in this decorator:
@json_input
def MccsMaster.Allocate(id, stations, tiles):
The decorator will provide the JSON interface and handle the
decoding for you.
"""
def __init__(self, schema_path=None):
"""
Initialises a callable json_input object, to function as a
device method generator.
"""
self.schema = None
if schema_path is not None:
with open(schema_path, "r") as schema_file:
schema_string = schema_file.read()
self.schema = json.loads(schema_string)
def __call__(self, func):
"""
The decorator method. Makes this class callable, and ensures
that when called on a device method, a wrapped method is
returned.
:param func: The target of the decorator
:type func: function
"""
@wraps(func)
def wrapped(cls, json_string):
json_object = self._parse(json_string, func.__name__)
return func(cls, **json_object)
return wrapped
def _parse(self, json_string, origin):
"""
Parses and validates the JSON string input.
:param json_string: a string, purportedly a JSON-encoded object
:type json_string: str
:param origin: the origin of this check; used to construct a
helpful DevFailed exception.
:type origin: str
:raises json.JSONDecodeError if the string cannot be decoded as
a JSON string
:raises jsonschema.ValidationError if the decoded JSON object
does not validate against a schema
"""
json_object = json.loads(json_string)
if self.schema is not None:
jsonschema.validate(json_object, self.schema)
return json_object