add 'blank' field option to allow empty text in required fields (default: false)

This commit is contained in:
Mark Riedesel 2024-03-31 11:14:16 -04:00
parent 74b7935ced
commit 5f4dc8b80f
5 changed files with 71 additions and 9 deletions

View file

@ -66,9 +66,12 @@ def dump(fp, records, delim=None):
fp.write(delim) fp.write(delim)
def dumps(records, delim=None): def dumps(records, delim=None, skip_validation=False):
import io import io
fp = io.BytesIO() fp = io.BytesIO()
if not skip_validation:
for record in records:
record.validate()
dump(fp, records, delim=delim) dump(fp, records, delim=delim)
fp.seek(0) fp.seek(0)
return fp.read() return fp.read()

View file

@ -23,11 +23,12 @@ class Field(object):
is_read_only = False is_read_only = False
_value = None _value = None
def __init__(self, name=None, max_length=0, required=True, uppercase=True, creation_counter=None): def __init__(self, name=None, max_length=0, blank=False, required=True, uppercase=True, creation_counter=None):
self.name = name self.name = name
self._value = None self._value = None
self._orig_value = None self._orig_value = None
self.max_length = max_length self.max_length = max_length
self.blank = blank
self.required = required self.required = required
self.uppercase = uppercase self.uppercase = uppercase
self.creation_counter = creation_counter or Field.creation_counter self.creation_counter = creation_counter or Field.creation_counter
@ -97,9 +98,9 @@ class Field(object):
wrapper.width = 100 wrapper.width = 100
value = wrapper.wrap(value) value = wrapper.wrap(value)
value = list([(" " * 9) + ('"' + x + '"') for x in value]) value = list([(" " * 9) + ('"' + x + '"') for x in value])
value.append(" " * 10 + ('_' * 10) * (wrapper.width / 10)) value.append(" " * 10 + ('_' * 10) * int(wrapper.width / 10))
value.append(" " * 10 + ('0123456789') * (wrapper.width / 10)) value.append(" " * 10 + ('0123456789') * int(wrapper.width / 10))
value.append(" " * 10 + ''.join(([str(x) + (' ' * 9) for x in range(wrapper.width / 10 )]))) value.append(" " * 10 + ''.join(([str(x) + (' ' * 9) for x in range(int(wrapper.width / 10))])))
start = counter['c'] start = counter['c']
counter['c'] += len(self._orig_value or self.value) counter['c'] += len(self._orig_value or self.value)
@ -121,6 +122,9 @@ class TextField(Field):
raise ValidationError("value required", field=self) raise ValidationError("value required", field=self)
if len(self.get_data()) > self.max_length: if len(self.get_data()) > self.max_length:
raise ValidationError("value is too long", field=self) raise ValidationError("value is too long", field=self)
if len(self.get_data().strip()) == 0 and (not self.blank and self.required):
print(self.name, 'blank', self.blank, self.required)
raise ValidationError("field cannot be blank", field=self)
def get_data(self): def get_data(self):
value = str(self.value or '').encode('ascii') or b'' value = str(self.value or '').encode('ascii') or b''
@ -144,7 +148,7 @@ class TextField(Field):
class StateField(TextField): class StateField(TextField):
def __init__(self, name=None, required=True, use_numeric=False, max_length=2): def __init__(self, name=None, required=True, use_numeric=False, max_length=2):
super(StateField, self).__init__(name=name, max_length=2, required=required) super(StateField, self).__init__(name=name, max_length=max_length, required=required)
self.use_numeric = use_numeric self.use_numeric = use_numeric
def get_data(self): def get_data(self):
@ -219,6 +223,10 @@ class BlankField(TextField):
def parse(self, s): def parse(self, s):
pass pass
def validate(self):
if len(self.get_data()) != self.max_length:
raise ValidationError("blank field did not match expected length", field=self)
class ZeroField(BlankField): class ZeroField(BlankField):
is_read_only = True is_read_only = True

View file

@ -39,7 +39,11 @@ class Model(object):
getattr(self, field_name).value = value getattr(self, field_name).value = value
def get_fields(self): def get_fields(self):
identifier = TextField("record_identifier", max_length=len(self.record_identifier), creation_counter=-1) identifier = TextField(
"record_identifier",
max_length = len(self.record_identifier),
blank = len(self.record_identifier) == 0,
creation_counter=-1)
identifier.value = self.record_identifier identifier.value = self.record_identifier
fields = [identifier] fields = [identifier]

View file

@ -7,7 +7,7 @@ def pyaccuwage_tests():
return test_suite return test_suite
setup(name='pyaccuwage', setup(name='pyaccuwage',
version='0.2024.0', version='0.2024.1',
packages=['pyaccuwage'], packages=['pyaccuwage'],
scripts=[ scripts=[
'scripts/pyaccuwage-checkseq', 'scripts/pyaccuwage-checkseq',

View file

@ -8,6 +8,7 @@ from pyaccuwage.fields import StateField
from pyaccuwage.fields import TextField from pyaccuwage.fields import TextField
from pyaccuwage.fields import ZeroField from pyaccuwage.fields import ZeroField
from pyaccuwage.fields import StaticField from pyaccuwage.fields import StaticField
from pyaccuwage.fields import ValidationError
from pyaccuwage.model import Model from pyaccuwage.model import Model
class TestModelOutput(unittest.TestCase): class TestModelOutput(unittest.TestCase):
@ -90,7 +91,7 @@ class TestFileFormats(unittest.TestCase):
record_identifier = 'B' # 1 byte record_identifier = 'B' # 1 byte
zero1 = ZeroField(max_length=32) zero1 = ZeroField(max_length=32)
text1 = TextField(max_length=71) text1 = TextField(max_length=71)
text2 = TextField(max_length=20) text2 = TextField(max_length=20, required=False)
blank2 = BlankField(max_length=4) blank2 = BlankField(max_length=4)
record_types = [TestModelA, TestModelB] record_types = [TestModelA, TestModelB]
@ -130,3 +131,49 @@ class TestFileFormats(unittest.TestCase):
original_bytes = pyaccuwage.dumps(records) original_bytes = pyaccuwage.dumps(records)
reloaded_bytes = pyaccuwage.dumps(records_loaded) reloaded_bytes = pyaccuwage.dumps(records_loaded)
self.assertEqual(original_bytes, reloaded_bytes) self.assertEqual(original_bytes, reloaded_bytes)
class TestRequiredFields(unittest.TestCase):
def createTestRecord(self, required=False, blank=False):
class Record(pyaccuwage.model.Model):
record_length = 16
record_identifier = ''
test_field = TextField(max_length=16, required=required, blank=blank)
record = Record()
def dump():
return pyaccuwage.dumps([record])
return (record, dump)
def testRequiredBlankField(self):
(record, dump) = self.createTestRecord(required=True, blank=True)
record.test_field.value # if nothing is ever assigned, raise error
self.assertRaises(ValidationError, dump)
record.test_field.value = '' # value may be empty string
dump()
def testRequiredNonblankField(self):
(record, dump) = self.createTestRecord(required=True, blank=False)
record.test_field.value # if nothing is ever assigned, raise error
self.assertRaises(ValidationError, dump)
record.test_field.value = '' # value must not be empty string
self.assertRaises(ValidationError, dump)
record.test_field.value = 'hello'
dump()
def testOptionalBlankField(self):
(record, dump) = self.createTestRecord(required=False, blank=True)
record.test_field.value # OK if nothing is ever assigned
dump()
record.test_field.value = '' # OK if empty string is assigned
dump()
record.test_field.value = 'hello'
dump()
def testOptionalNonBlankField(self):
(record, dump) = self.createTestRecord(required=False, blank=False)
record.test_field.value # OK if nothing is ever assigned
dump()
record.test_field.value = '' # OK if empty string is assigned
dump()
record.test_field.value = 'hello'
dump()