Repository: simonsobs/ocs

Issue:
param decorator doen't support type check on tuples
For context, this came up in socs on the ACU Agent.

When using the param decorator, if trying to type check a tuple, i.e.:
```
@ocs_agent.param('azpts', type=tuple)
```

Before the check occurs the input parameter value is converted to and from json, effectively casting it to a list. This results in a failed type check, for example:
```
>>> resp = client.constant_velocity_scan(azpts=(20., 120.), el=35., azvel=2., acc=2., ntimes=8, azonly=False, simulator=True)
>>> print(resp)
OCSReply: ERROR : Param 'azpts'=[20.0, 120.0] is not of required type (<class 'tuple'>)
```

We should check if the type is `tuple` and cast before the check within this if block: https://github.com/simonsobs/ocs/blob/076105e9613ef78745d37df3d9bf8a38ce1e28bd/ocs/ocs_agent.py#L1221

Discussion:

Patch:
diff --git a/ocs/ocs_agent.py b/ocs/ocs_agent.py
index fc06a6e..8238a4f 100644
--- a/ocs/ocs_agent.py
+++ b/ocs/ocs_agent.py
@@ -1389,6 +1389,9 @@ class ParamHandler:
                 # Free cast from int to float.
                 if type is float and isinstance(value, int):
                     value = float(value)
+                # Fix type after json conversion
+                if type is tuple and isinstance(value, list) and cast in [tuple, None]:
+                    value = tuple(value)
                 if not isinstance(value, type):
                     raise ParamError(f"Param '{key}'={value} is not of required type ({type})")
             if choices is not None:


Test patch:
diff --git a/tests/test_ocs_agent.py b/tests/test_ocs_agent.py
index e6c1a83..be213ce 100644
--- a/tests/test_ocs_agent.py
+++ b/tests/test_ocs_agent.py
@@ -503,12 +503,14 @@ def test_params_get():
         'float_param': 1e8,
         'numerical_string_param': '145.12',
         'none_param': None,
+        'tuple_param': [20., 120.],  # gets cast to list by json conversion
     })
 
     # Basic successes
     params.get('int_param', type=int)
     params.get('string_param', type=str)
     params.get('float_param', type=float)
+    params.get('tuple_param', type=tuple)
 
     # Tricky successes
     params.get('int_param', type=float)