Source code for pybankreader.fields

from datetime import datetime
from decimal import Decimal
import re
from .exceptions import ValidationError


[docs]class Field(object): """ Basic field superclass. We have mandatory length and required flags, and we hold the set value (if any). Also, the name of the field as defined in classes using these for reference reasons. """ _creation_counter = 0 _field_name = None _value = None _position = None length = None required = None def __init__(self, length, required): """ Initialize the field and maintain the creation counter so we don't have to pass position argument :param int length: maximum length of the field :param bool required: field is required """ # We set the position from the static attribute, since otherwise, we # would not have a way how to fetch the instance one self._position = Field._creation_counter Field._creation_counter += 1 self.length = length self.required = required def __lt__(self, other): """ Compare with other fields by position :param other: Field :return bool: True if self < other, False if self > other :raises RuntimeError: self == other (which should not ever happen) """ if self._position == other._position: msg = "You cannot have two fields with the same position" raise RuntimeError(msg) return self._position < other._position def _set_value(self, value): """ When the value is being set, we run validations! :param value: The value to be stored in the field :raises ValidationError: Value is not valid """ value = value.strip() if self.required and not len(value): raise ValidationError( self._field_name, "A value is required for this field" ) if len(value) > self.length: msg = u"Value '{}' exceeds maximum length of {}".format( value, self.length ) raise ValidationError(self._field_name, msg) self._value = value if len(value) else None @property def value(self): """ Just return the value, nothing special here :return: object """ return self._value @value.setter def value(self, value): """ Setter for the value. Uses inner method, since setters cannot call super in subclasses. :param value: The value to be stored in the field :raises ValidationError: Value is not valid """ self._set_value(value) @property def field_name(self): """ Return the name of the field it has been assigned to :return string: name of the field """ return self._field_name @field_name.setter def field_name(self, value): """ Sets the name of the field. If that has already been done, raises RuntimeError :param string value: name of the field :raises RuntimeError: you're trying to reassign the field name """ if self._field_name: raise RuntimeError("You cannot reassign field name once it's set") self._field_name = value
[docs]class CharField(Field): """ CharField just uses the Field superclass directly for now, nothing special """ pass
[docs]class RegexField(Field): """ Generic regex field. On top of basic checks, enforces a regex match """ _regex = None def __init__(self, regex, *args, **kwargs): """ Initialize the field :param regex: regular expression that the value is matched against :param list args: args :param dict kwargs: kwargs :return: """ self._regex = regex super(RegexField, self).__init__(*args, **kwargs) def _set_value(self, value): """ Setter for the value. :param strin value: The value to be stored in the field :raises ValidationError: Value is not valid """ super(RegexField, self)._set_value(value) if self._value is None: return if re.match(self._regex, value) is None: msg = u"Value '{}' does not match the regex pattern '{}'".format( value, self._regex ) self._value = None raise ValidationError(self._field_name, msg)
[docs]class IntegerField(RegexField): """ Integer is just a special-case regex, so the field is implemented this way """ def __init__(self, *args, **kwargs): """ Initializes the parent RegexField with integer regex :param list args: args :param dict kwargs: kwargs """ super(IntegerField, self).__init__("^\s*-?\d+\s*$", *args, **kwargs) def _set_value(self, value): """ Setter for the value, typecasts to integer :param string value: The value to be stored in the field :raises ValidationError: Value is not valid """ super(IntegerField, self)._set_value(value) if self._value is None: return self._value = int(self._value)
[docs]class DecimalField(RegexField): """ Decimal is just a special-case regex, so the field is implemented this way. Mind that when you're using decimal, the overall length of the field must count with the decimal dot! """ def __init__(self, *args, **kwargs): """ Initializes the parent RegexField with integer regex :param list args: args :param dict kwargs: kwargs """ super(DecimalField, self).__init__( "^\s*-?\d+(\.\d+)?\s*$", *args, **kwargs ) def _set_value(self, value): """ Setter for the value, creates a Decimal object :param string value: The value to be stored in the field :raises ValidationError: Value is not valid """ super(DecimalField, self)._set_value(value) if self._value is None: return self._value = Decimal(self._value)
[docs]class TimestampField(Field): """ Timestamp field takes on `format` parameter to be fed into `strptime` """ _format = None def __init__(self, format, *args, **kwargs): """ Initialize the field with datetime format mask :param format: datetime format mask that ``datetime.strptime`` can parse :param list args: args :param dict kwargs: kwargs :return: """ super(TimestampField, self).__init__(*args, **kwargs) self._format = format def _set_value(self, value): """ Setter for the value, parses the input to datetime object """ super(TimestampField, self)._set_value(value) if self._value is None: return try: self._value = datetime.strptime(value, self._format) except ValueError as e: if not self.required: self._value = None else: msg = u"Value '{}' cannot be parsed to date using format '{}'. " \ u"Error is: {}".format(value, self._format, str(e)) raise ValidationError(self._field_name, msg)