Source code for fints.fields

import datetime
import decimal
import re
import warnings

from fints.types import Container, SegmentSequence, TypedField
from fints.utils import (
    DocTypeMixin, FieldRenderFormatStringMixin, FixedLengthMixin, Password,
)


[docs]class DataElementField(DocTypeMixin, TypedField): pass
[docs]class ContainerField(TypedField): def _check_value(self, value): if self.type: if not isinstance(value, self.type): raise TypeError("Value {!r} is not of type {!r}".format(value, self.type)) super()._check_value(value) def _default_value(self): return self.type()
[docs]class DataElementGroupField(DocTypeMixin, ContainerField): pass
[docs]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
[docs]class GenericGroupField(DataElementGroupField): type = None def _default_value(self): if self.type is None: return Container() else: return self.type() def _parse_value(self, value): if self.type is None: warnings.warn("Generic field used for type {!r} value {!r}".format(self.type, value)) return value
[docs]class TextField(FieldRenderFormatStringMixin, DataElementField): type = 'txt' _DOC_TYPE = str _FORMAT_STRING = "{}" # FIXME Restrict CRLF def _parse_value(self, value): return str(value)
[docs]class AlphanumericField(TextField): type = 'an'
[docs]class DTAUSField(DataElementField): type = 'dta'
[docs]class NumericField(FieldRenderFormatStringMixin, DataElementField): type = 'num' _DOC_TYPE = int _FORMAT_STRING = "{:d}" def _parse_value(self, value): _value = str(value) if len(_value) > 1 and _value[0] == '0': raise ValueError("Leading zeroes not allowed for value of type 'num': {!r}".format(value)) return int(_value, 10)
[docs]class ZeroPaddedNumericField(NumericField): type = '' _DOC_TYPE = int def __init__(self, *args, **kwargs): if not kwargs.get('length', None): raise ValueError("ZeroPaddedNumericField needs length argument") super().__init__(*args, **kwargs) @property def _FORMAT_STRING(self): return "{:0" + str(self.length) + "d}" def _parse_value(self, value): _value = str(value) return int(_value, 10)
[docs]class DigitsField(FieldRenderFormatStringMixin, DataElementField): type = 'dig' _DOC_TYPE = str _FORMAT_STRING = "{}" def _parse_value(self, value): _value = str(value) if not re.match(r'^\d*$', _value): raise TypeError("Only digits allowed for value of type 'dig': {!r}".format(value)) return _value
[docs]class FloatField(DataElementField): type = 'float' _DOC_TYPE = float _FORMAT_STRING = "{:.12f}" # Warning: Python's float is not exact! # FIXME: Needs test def _parse_value(self, value): if isinstance(value, float): return value if isinstance(value, decimal.Decimal): value = str(value.normalize()).replace(".", ",") _value = str(value) if not re.match(r'^(?:0|[1-9]\d*),(?:\d*[1-9]|)$', _value): raise TypeError("Only digits and ',' allowed for value of type 'float', no superfluous leading or trailing zeroes allowed: {!r}".format(value)) return float(_value.replace(",", ".")) def _render_value(self, value): retval = self._FORMAT_STRING.format(value) retval = retval.replace('.', ',').rstrip('0') self._check_value_length(retval) return retval
[docs]class AmountField(FixedLengthMixin, DataElementField): type = 'wrt' _DOC_TYPE = decimal.Decimal _FIXED_LENGTH = [None, None, 15] # FIXME Needs test def _parse_value(self, value): if isinstance(value, float): return value if isinstance(value, decimal.Decimal): return value _value = str(value) if not re.match(r'^(?:0|[1-9]\d*)(?:,)?(?:\d*[1-9]|)$', _value): raise TypeError("Only digits and ',' allowed for value of type 'decimal', no superfluous leading or trailing zeroes allowed: {!r}".format(value)) return decimal.Decimal(_value.replace(",", ".")) def _render_value(self, value): retval = str(value) retval = retval.replace('.', ',').rstrip('0') self._check_value_length(retval) return retval
[docs]class BinaryField(DataElementField): type = 'bin' _DOC_TYPE = bytes def _render_value(self, value): retval = bytes(value) self._check_value_length(retval) return retval def _parse_value(self, value): return bytes(value)
[docs]class IDField(FixedLengthMixin, AlphanumericField): type = 'id' _DOC_TYPE = str _FIXED_LENGTH = [None, None, 30]
[docs]class BooleanField(FixedLengthMixin, AlphanumericField): type = 'jn' _DOC_TYPE = bool _FIXED_LENGTH = [1] def _render_value(self, value): return "J" if value else "N" def _parse_value(self, value): if value is None: return None if value == "J" or value is True: return True elif value == "N" or value is False: return False else: raise ValueError("Invalid value {!r} for BooleanField".format(value))
[docs]class CodeFieldMixin: # FIXME Need tests def __init__(self, enum=None, *args, **kwargs): if enum: self._DOC_TYPE = enum self._enum = enum else: self._enum = None super().__init__(*args, **kwargs) def _parse_value(self, value): retval = super()._parse_value(value) if self._enum: retval = self._enum(retval) return retval def _render_value(self, value): retval = value if self._enum: retval = str(value.value) return super()._render_value(retval) def _inline_doc_comment(self, value): retval = super()._inline_doc_comment(value) if self._enum: addendum = value.__doc__ if addendum and addendum is not value.__class__.__doc__: if not retval: retval = " # " else: retval = retval + ": " retval = retval + addendum return retval
[docs]class CodeField(CodeFieldMixin, AlphanumericField): type = 'code' _DOC_TYPE = str
[docs]class IntCodeField(CodeFieldMixin, NumericField): type = '' _DOC_TYPE = int _FORMAT_STRING = "{}"
[docs]class CountryField(FixedLengthMixin, DigitsField): type = 'ctr' _FIXED_LENGTH = [3]
[docs]class CurrencyField(FixedLengthMixin, AlphanumericField): type = 'cur' _FIXED_LENGTH = [3]
[docs]class DateField(FixedLengthMixin, NumericField): type = 'dat' # FIXME Need test _DOC_TYPE = datetime.date _FIXED_LENGTH = [8] def _parse_value(self, value): if isinstance(value, datetime.date): return value val = super()._parse_value(value) val = str(val) return datetime.date(int(val[0:4]), int(val[4:6]), int(val[6:8])) def _render_value(self, value): val = "{:04d}{:02d}{:02d}".format(value.year, value.month, value.day) val = int(val) return super()._render_value(val)
[docs]class TimeField(FixedLengthMixin, DigitsField): type = 'tim' # FIXME Need test _DOC_TYPE = datetime.time _FIXED_LENGTH = [6] def _parse_value(self, value): if isinstance(value, datetime.time): return value val = super()._parse_value(value) return datetime.time(int(val[0:2]), int(val[2:4]), int(val[4:6])) def _render_value(self, value): val = "{:02d}{:02d}{:02d}".format(value.hour, value.minute, value.second) return super()._render_value(val)
[docs]class PasswordField(AlphanumericField): type = '' _DOC_TYPE = Password def _parse_value(self, value): return Password(value) def _render_value(self, value): return str(value)
[docs]class SegmentSequenceField(DataElementField): type = 'sf' def _parse_value(self, value): if isinstance(value, SegmentSequence): return value else: return SegmentSequence(value) def _render_value(self, value): return value.render_bytes()