import decimal, datetime import inspect import enums class ValidationError(Exception): def __init__(self, msg, field=None): self.msg = msg self.field = field def __str__(self): if self.field: return "(%s.%s) %s" % (self.field.parent_name, self.field.name, self.msg) else: return repr(self.msg) class Field(object): creation_counter = 0 def __init__(self, name=None, max_length=0, required=True, uppercase=True): self.name = name self._value = None self.max_length = max_length self.required = required self.uppercase = uppercase self.creation_counter = Field.creation_counter Field.creation_counter += 1 def validate(self): raise NotImplemented def get_data(self): raise NotImplemented def __setvalue(self, value): self._value = value def __getvalue(self): return self._value value = property(__getvalue, __setvalue) def read(self, fp): if fp.tell() + self.max_length <= fp.len: data = fp.read(self.max_length) return self.parse(data) return None def parse(self, s): self.value = s.strip() class TextField(Field): def validate(self): if self.value == None and self.required: raise ValidationError("value required", field=self) if len(self.get_data()) > self.max_length: raise ValidationError("value is too long", field=self) def get_data(self): value = self.value or "" if self.uppercase: value = value.upper() return value.ljust(self.max_length).encode('ascii') class StateField(TextField): def __init__(self, name=None, required=True, use_numeric=False): super(StateField, self).__init__(name=name, max_length=2, required=required) self.use_numeric = use_numeric def get_data(self): value = self.value or "" if value.strip() and self.use_numeric: return str(enums.state_postal_numeric[value.upper()]).zfill(self.max_length) else: return value.ljust(self.max_length).encode('ascii') def validate(self): super(StateField, self).validate() if self.value and self.value.upper() not in enums.state_postal_numeric.keys(): raise ValidationError("%s is not a valid state abbreviation" % self.value, field=self) def parse(self, s): if s.strip() and self.use_numeric: states = dict( [(v,k) for (k,v) in enums.state_postal_numeric.items()] ) self.value = states[int(s)] else: self.value = s class EmailField(TextField): def __init__(self, name=None, required=True, max_length=None): return super(EmailField, self).__init__(name=name, max_length=max_length, required=required, uppercase=False) class NumericField(TextField): def validate(self): super(NumericField, self).validate() if self.value: try: int(self.value) except ValueError: raise ValidationError("field contains non-numeric characters", field=self) def get_data(self): value = self.value or "" return str(value).zfill(self.max_length) def parse(self, s): self.value = int(s) class StaticField(TextField): def __init__(self, name=None, required=True, value=None): super(StaticField, self).__init__(name=name, required=required, max_length=len(value)) self._value = value def parse(self, s): pass class BlankField(TextField): def __init__(self, name=None, max_length=0, required=False): super(TextField, self).__init__(name=name, max_length=max_length, required=required, uppercase=False) def get_data(self): return " " * self.max_length def parse(self, s): pass class BooleanField(Field): def __init__(self, name=None, required=True, value=None): super(BooleanField, self).__init__(name=name, required=required, max_length=1) self._value = value def validate(self): pass def get_data(self): return '1' if self._value else '0' def parse(self, s): self.value = (s == '1') class MoneyField(Field): def validate(self): if self.value == None and self.required: raise ValidationError("value required", field=self) if len(str(int((self.value or 0)*100))) > self.max_length: raise ValidationError("value is too long", field=self) def get_data(self): return str(int((self.value or 0)*100)).encode('ascii').zfill(self.max_length) def parse(self, s): self.value = decimal.Decimal(s) * decimal.Decimal('0.01') class DateField(TextField): def __init__(self, name=None, required=True, value=None): super(TextField, self).__init__(name=name, required=required, max_length=8) if value: self.value = value def get_data(self): if self._value: return self._value.strftime('%m%d%Y') return '0' * self.max_length def parse(self, s): if int(s) > 0: self.value = datetime.date(*[int(x) for x in s[4:8], s[0:2], s[2:4]]) else: self.value = None def __setvalue(self, value): if isinstance(value, datetime.date): self._value = value elif value: self._value = datetime.date(*[int(x) for x in value[4:8], value[0:2], value[2:4]]) else: self._value = None def __getvalue(self): return self._value value = property(__getvalue, __setvalue) class MonthYearField(TextField): def __init__(self, name=None, required=True, value=None): super(TextField, self).__init__(name=name, required=required, max_length=6) if value: self.value = value def get_data(self): if self._value: return self._value.strftime("%m%Y") return '0' * self.max_length def parse(self, s): if int(s) > 0: self.value = datetime.date(*[int(x) for x in s[2:6], s[0:2], 1]) else: self.value = None def __setvalue(self, value): if isinstance(value, datetime.date): self._value = value elif value: self._value = datetime.date(*[int(x) for x in value[2:6], value[0:2], 1]) else: self._value = None def __getvalue(self): return self._value value = property(__getvalue, __setvalue)