ts_scriptqueue 2.2 include two very important changes from 1.x:
- SAL scripts must specify a schema, which will be used to validate the configuration and set default values (first introduced in version 2.1 but the 2.2 version is described here).
- This version uses the OpenSplice dds library instead of SALPY libraries (first introduced in version 2.0).
All SAL scripts will need to be updated. I will do this for the scripts in ts_standardscripts. Changes include:
Schema
You must add a class method get_schema
that returns a jsonschema description of the configuration of your script, as a dict
. Please include a description of each field and a default value if a default makes sense. If your script needs no configuration then you can return None
. (Note: ts_scriptqueue 2.1 implemented the schema as a property named schema
instead, but in order to implement DM-18012 I have changed it to a classmethod in version 2.2). Here is an example:
@classmethod def get_schema(cls): """Get the configuration schema as a jsonschema `dict`. """ schema_yaml = """ $schema: http://json-schema.org/draft-07/schema# $id: https://github.com/lsst-ts/ts_standardscripts/auxtel/SlewTelescopeIcrs.yaml title: SlewTelescopeIcrs v1 description: Configuration for SlewTelescopeIcrs type: object properties: ra: description: ICRS right ascension (hour) type: number minimum: 0 maximum: 24 dec: description: ICRS declination (deg) type: number minimum: -90 maximum: 90 rot_pa: description: Desired instrument position angle, Eastwards from North (deg) type: number default: 0 target_name: type: string default: "" required: [ra, dec, rot_pa, target_name] additionalProperties: false """ return yaml.safe_load(schema_yaml)
Note that I created the schema as a yaml string and then use the yaml
library to encode it as a Python dict
. You can create the dict directly if you prefer. I like yaml
because it is much more succinct and I find it easier to read.
Remotes
Your script must now create salobj.Remote
instances after calling super().__init__
, instead of before as used to be done. This is because the dds salobj Remote
constructor now requires a domain
, and that is made by super().__init__
. Here is an example:
class SlewTelescopeIcrs(scriptqueue.BaseScript): """Slew the telescope to a specified ICRS position. ... """ __test__ = False # stop pytest from warning that this is not a test def __init__(self, index): super().__init__(index=index, descr="Slew the auxiliary telescope to an ICRS position") self.atptg = salobj.Remote(domain=self.domain, name="ATPtg") self.tracking_started = False
Unit Tests
As with all dds salobj code, unit tests must now carefully clean up the script (or domain, if there is no script) when done. Most of tests tests create a Harness
object that constructs the script and a remote to talk to it. I recommend that you make your harness an asynchronous context manager by adding __aenter__(self)
and __aexit__(self, *args)
methods. For example:
index_gen = salobj.index_generator()
class Harness:
def __init__(self): self.index = next(index_gen) self.script = SlewTelescopeIcrs(index=self.index) self.atptg = salobj.Controller("ATPtg") async def __aenter__(self): await self.script.start_task await self.atptg.start_task return self async def __aexit__(self, *args): await self.script.close() await self.atptg.close()
Here is an example of using the harness in a test method:
def test_configure_with_defaults(self): async def doit(): async with Harness() as harness: config_data = harness.script.cmd_configure.DataType() config_data.config = "ra: 5.1\ndec: 36.2" await harness.script.do_configure(config_data) self.assertEqual(harness.script.config.ra, 5.1) self.assertEqual(harness.script.config.dec, 36.2) self.assertEqual(harness.script.config.rot_pa, 0) self.assertEqual(harness.script.config.target_name, "") asyncio.get_event_loop().run_until_complete(doit())