diff --git a/magicparse/fields.py b/magicparse/fields.py index cc0aa14..da131da 100644 --- a/magicparse/fields.py +++ b/magicparse/fields.py @@ -18,12 +18,21 @@ def __init__(self, options: dict) -> None: PostProcessor.build(item) for item in options.get("post-processors", []) ] + self.optional = options.get("optional", False) + self.transforms = ( pre_processors + [type_converter] + validators + post_processors ) def _process_raw_value(self, raw_value: str): value = raw_value + if not raw_value: + if self.optional: + return None + else: + raise ValueError( + f"{self.key} field is required but the value was empty" + ) for transform in self.transforms: value = transform.apply(value) return value @@ -34,10 +43,7 @@ def _read_raw_value(self, row) -> str: def read_value(self, row): raw_value = self._read_raw_value(row) - value = raw_value - for transform in self.transforms: - value = transform.apply(value) - return value + return self._process_raw_value(raw_value) @abstractmethod def error(self, exception: Exception): diff --git a/tests/test_fields.py b/tests/test_fields.py index e1f09b1..4599cef 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -86,3 +86,46 @@ def test_columnar_error_format(): "error": "value 'hello' is not a valid decimal", "field-key": "ratio", } + + +def test_optional_field(): + field = DummyField( + { + "key": "ratio", + "type": "decimal", + "optional": True, + "pre-processors": [ + { + "name": "replace", + "parameters": {"pattern": "XXX", "replacement": "000"}, + } + ], + "post-processors": [{"name": "divide", "parameters": {"denominator": 100}}], + } + ) + assert field.read_value("XXX150") == Decimal("1.50") + assert field.read_value("") is None + + +def test_required_field(): + field = DummyField( + { + "key": "ratio", + "type": "decimal", + "optional": False, + } + ) + assert field.read_value("1.5") == Decimal("1.50") + + +def test_require_field_with_empty_value(): + field = DummyField( + { + "key": "pepito", + "type": "decimal", + } + ) + with pytest.raises( + ValueError, match="pepito field is required but the value was empty" + ): + field.read_value("")