M rework/helper.py +8 -28
@@ 19,6 19,8 @@ from sqlhelp import select
from inireader import reader
from dateutil.parser import isoparse, parse as defaultparse
+from rework.input import inputio
+
def utcnow():
return datetime.utcnow().replace(tzinfo=pytz.utc)
@@ 415,34 417,12 @@ def pack_inputs(spec, args):
raw = {}
for field in spec:
- name = field['name']
- val = args.get(name)
- if val is None:
- if field['required']:
- raise ValueError(
- f'missing required input: `{name}`'
- )
- continue
- ftype = field['type']
- if ftype == 'file':
- assert isinstance(val, bytes)
- raw[name] = val
- continue
- if ftype == 'string':
- choices = field['choices']
- if choices:
- assert val in choices
- raw[name] = val.encode('utf-8')
- continue
- if ftype == 'number':
- raw[name] = str(val).encode('utf-8')
- continue
- if ftype == 'datetime':
- if isinstance(val, str):
- val = val.encode('utf-8')
- else:
- val = val.isoformat().encode('utf-8')
- raw[name] = val
+ inp = inputio.from_type(
+ field['type'], field['name'], field['required'], field['choices']
+ )
+ val = inp.binary_encode(args)
+ if val is not None:
+ raw[inp.name] = val
spec_keys = {field['name'] for field in spec}
unknown_keys = set(args) - spec_keys
M rework/input.py +52 -19
@@ 1,9 1,14 @@
import json
-class _base:
+class inputio:
_fields = 'name', 'required', 'choices'
+ def __init__(self, name, required=False, choices=None):
+ self.name = name
+ self.required = required
+ self.choices = choices
+
def __json_encode__(self):
out = {
name: getattr(self, name, None)
@@ 12,31 17,59 @@ class _base:
out['type'] = self.__class__.__name__
return out
-
-class number(_base):
+ @staticmethod
+ def from_type(atype, name, required, choices):
+ return globals()[atype](name, required, choices)
- def __init__(self, name, required=False):
- self.name = name
- self.required = required
+ def val(self, args):
+ val = args.get(self.name)
+ if val is None:
+ if self.required:
+ raise ValueError(
+ f'missing required input: `{self.name}`'
+ )
+ else:
+ if self.choices and val not in self.choices:
+ raise ValueError(
+ f'{self.name} -> value not in {self.choices}'
+ )
+ return val
+
+
+class number(inputio):
+
+ def binary_encode(self, args):
+ val = self.val(args)
+ if val is not None:
+ return str(val).encode('utf-8')
-class string(_base):
+class string(inputio):
- def __init__(self, name, required=False, choices=None):
- self.name = name
- self.required = required
- self.choices = choices
+ def binary_encode(self, args):
+ val = self.val(args)
+ if val is not None:
+ return val.encode('utf-8')
-class file(_base):
+class file(inputio):
- def __init__(self, name, required=False):
- self.name = name
- self.required = required
+ def binary_encode(self, args):
+ val = self.val(args)
+ if val is None:
+ return
+ assert isinstance(val, bytes) or val is None
+ return val
-class datetime(_base):
+class datetime(inputio):
- def __init__(self, name, required=False):
- self.name = name
- self.required = required
+ def binary_encode(self, args):
+ val = self.val(args)
+ if val is None:
+ return
+ if isinstance(val, str):
+ val = val.encode('utf-8')
+ else:
+ val = val.isoformat().encode('utf-8')
+ return val
M tests/test_api.py +15 -2
@@ 143,7 143,18 @@ def test_with_inputs(engine, cleanup):
with pytest.raises(ValueError) as err:
api.schedule(
engine, 'yummy',
+ {
+ 'myfile.txt': b'something',
+ 'option': 'quux'
+ }
+ )
+ assert err.value.args[0] == "option -> value not in ['foo', 'bar']"
+
+ with pytest.raises(ValueError) as err:
+ api.schedule(
+ engine, 'yummy',
{'no-such-thing': 42,
+ 'option': 'foo',
'myfile.txt': b'something'
}
)
@@ 187,8 198,10 @@ def test_prepare_with_inputs(engine, cle
with pytest.raises(ValueError) as err:
api.prepare(
engine, 'yummy',
- inputdata={'no-such-thing': 42,
- 'myfile.txt': b'something'
+ inputdata={
+ 'no-such-thing': 42,
+ 'option': 'foo',
+ 'myfile.txt': b'something'
}
)
assert err.value.args[0] == 'unknown inputs: no-such-thing'