with suppress(NotImplementedError):
self._render_value(value)
+ def _check_value_length(self, value):
+ if self.max_length is not None and len(value) > self.max_length:
+ raise ValueError("Value {!r} cannot be rendered: max_length={} exceeded".format(value, self.max_length))
+
+ if self.min_length is not None and len(value) < self.min_length:
+ raise ValueError("Value {!r} cannot be rendered: min_length={} not reached".format(value, self.min_length))
+
+ if self.length is not None and len(value) != self.length:
+ raise ValueError("Value {!r} cannot be rendered: length={} not satisfied".format(value, self.length))
+
+
class TypedField(Field, SubclassesMixin):
flat_length = 1
class DataElementField(TypedField):
pass
+class FieldRenderFormatStringMixin:
+ FORMAT_STRING = None
+
+ def _render_value(self, value):
+ retval = self.FORMAT_STRING.format(value)
+ self._check_value_length(retval)
+
+ return retval
+
class ContainerField(TypedField):
def _check_value(self, value):
if self.type:
class DataElementGroupField(ContainerField):
pass
-class GenericField(DataElementField):
+class GenericField(FieldRenderFormatStringMixin, DataElementField):
type = None
+ FORMAT_STRING = "{}"
+
def _parse_value(self, value):
warnings.warn("Generic field used for type {!r} value {!r}".format(self.type, value))
return value
warnings.warn("Generic field used for type {!r} value {!r}".format(self.type, value))
return value
-class TextField(DataElementField):
+class TextField(FieldRenderFormatStringMixin, DataElementField):
type = 'txt'
+ FORMAT_STRING = "{}" ## FIXME Restrict CRLF
def _parse_value(self, value): return str(value)
class DTAUSField(DataElementField):
type = 'dta'
-class NumericField(DataElementField):
+class NumericField(FieldRenderFormatStringMixin, DataElementField):
type = 'num'
+ FORMAT_STRING = "{:d}"
def _parse_value(self, value):
_value = str(value)
raise TypeError("Leading zeroes not allowed for value of type 'num': {!r}".format(value))
return int(_value, 10)
-class DigitsField(DataElementField):
+class DigitsField(FieldRenderFormatStringMixin, DataElementField):
type = 'dig'
+ FORMAT_STRING = "{}"
def _parse_value(self, value):
_value = str(value)
raise TypeError("Only digits allowed for value of type 'dig': {!r}".format(value))
return _value
-class FloatField(DataElementField):
+class FloatField(FieldRenderFormatStringMixin, DataElementField):
type = 'float'
class BinaryField(DataElementField):
type = 'bin'
+ def _render_value(self, value):
+ retval = bytes(value)
+ self._check_value_length(retval)
+
+ return retval
+
def _parse_value(self, value): return bytes(value)
import pytest
-from fints.formals import Container, ContainerField, DataElementField, DataElementGroupField, DigitsField, NumericField, Field, SegmentSequence, SegmentHeader
+from fints.formals import Container, ContainerField, DataElementField, DataElementGroupField, DigitsField, NumericField, Field, SegmentSequence, SegmentHeader, AlphanumericField
def test_container_simple():
class A(Container):
A(a=123)
+def test_parse_restrictions():
+ class A(Container):
+ a = NumericField(min_length=2, max_length=3)
+ b = DigitsField(length=3)
+
+ i1 = A()
+ with pytest.raises(ValueError, match='max_length=3 exceeded'):
+ i1.a = '1234'
+
+ i2 = A(a=123)
+ with pytest.raises(ValueError, match='max_length=3 exceeded'):
+ i2.a = 1234
+
+ with pytest.raises(ValueError, match='length=3 not satisfied'):
+ A(b='01')
+
+ with pytest.raises(ValueError, match='min_length=2 not reached'):
+ A(a=1)
+
+
def test_sequence_repr():
s = SegmentSequence()