-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
285 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from abc import ABC | ||
|
||
from .transform import Transform | ||
|
||
|
||
class CompositeProcessor(Transform, ABC): | ||
@classmethod | ||
def build(cls, options: dict) -> "CompositeProcessor": | ||
try: | ||
name = options["name"] | ||
except: | ||
raise ValueError("post-processor must have a 'name' key") | ||
|
||
try: | ||
composite_processor = cls.registry[name] | ||
except: | ||
raise ValueError(f"invalid post-processor '{name}'") | ||
|
||
if "parameters" in options: | ||
return composite_processor(**options["parameters"]) | ||
else: | ||
return composite_processor() | ||
|
||
|
||
class Concat(CompositeProcessor): | ||
def __init__(self, fields: list[str]) -> None: | ||
if ( | ||
not fields | ||
or isinstance(fields, str) | ||
or not isinstance(fields, list) | ||
or not all(isinstance(field, str) for field in fields) | ||
or len(fields) < 2 | ||
): | ||
raise ValueError( | ||
"composite-processor 'concat': " | ||
"'fields' parameter must be a list[str] with at least two elements" | ||
) | ||
|
||
self.fields = fields | ||
|
||
def apply(self, row: dict) -> str: | ||
return "".join(row[field] for field in self.fields) | ||
|
||
@staticmethod | ||
def key() -> str: | ||
return "concat" | ||
|
||
|
||
builtins = [Concat] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import pytest | ||
|
||
from magicparse.fields import CompositeField | ||
from unittest import TestCase | ||
|
||
|
||
class TestBuild(TestCase): | ||
def test_without_composite_processor(self): | ||
with self.assertRaises(KeyError): | ||
CompositeField({"key": "output", "type": "str"}) | ||
|
||
def test_not_iterable_value_for_composite_processor(self): | ||
with self.assertRaises(TypeError): | ||
CompositeField({"key": "output", "type": "str", "composite-processors": 1}) | ||
|
||
def test_bad_value_for_composite_processor(self): | ||
with self.assertRaises(ValueError): | ||
CompositeField( | ||
{"key": "output", "type": "str", "composite-processors": "really"} | ||
) | ||
|
||
def test_empty_composite_processor(self): | ||
with self.assertRaises(ValueError): | ||
CompositeField({"key": "output", "type": "str", "composite-processors": []}) | ||
|
||
def test_with_one_composite_processor(self): | ||
field = CompositeField( | ||
{ | ||
"key": "output", | ||
"type": "str", | ||
"composite-processors": [ | ||
{"name": "concat", "parameters": {"fields": ["code_1", "code_2"]}} | ||
], | ||
} | ||
) | ||
|
||
computed = field.read_value({"code_1": "01", "code_2": "02"}) | ||
|
||
assert computed == "0102" | ||
|
||
def test_with_two_composite_processor(self): | ||
field = CompositeField( | ||
{ | ||
"key": "output", | ||
"type": "str", | ||
"composite-processors": [ | ||
{"name": "concat", "parameters": {"fields": ["code_1", "code_2"]}}, | ||
{"name": "concat", "parameters": {"fields": ["output", "code_2"]}}, | ||
], | ||
} | ||
) | ||
|
||
computed = field.read_value({"code_1": "01", "code_2": "02"}) | ||
|
||
assert computed == "010202" | ||
|
||
def test_error_format(self): | ||
field = CompositeField( | ||
{ | ||
"key": "output", | ||
"type": "str", | ||
"composite-processors": [ | ||
{"name": "concat", "parameters": {"fields": ["code_1", "code_2"]}} | ||
], | ||
} | ||
) | ||
|
||
with pytest.raises(KeyError) as error: | ||
field.read_value({}) | ||
|
||
assert field.error(error.value) == {"error": "code_1", "field-key": "output"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import pytest | ||
from unittest import TestCase | ||
|
||
from magicparse import CompositeProcessor | ||
|
||
|
||
class TestBuild(TestCase): | ||
class WithoutParamCompositeProcessor(CompositeProcessor): | ||
@staticmethod | ||
def key() -> str: | ||
return "without-param" | ||
|
||
def apply(self, value): | ||
pass | ||
|
||
class WithParamCompositeProcessor(CompositeProcessor): | ||
def __init__(self, setting: str) -> None: | ||
self.setting = setting | ||
|
||
@staticmethod | ||
def key() -> str: | ||
return "with-param" | ||
|
||
def apply(self, value): | ||
pass | ||
|
||
def test_without_parameter(self): | ||
CompositeProcessor.register(self.WithoutParamCompositeProcessor) | ||
|
||
composite_processor = CompositeProcessor.build({"name": "without-param"}) | ||
assert isinstance(composite_processor, self.WithoutParamCompositeProcessor) | ||
|
||
def test_with_parameter(self): | ||
CompositeProcessor.register(self.WithParamCompositeProcessor) | ||
|
||
composite_processor = CompositeProcessor.build( | ||
{"name": "with-param", "parameters": {"setting": "value"}} | ||
) | ||
assert isinstance(composite_processor, self.WithParamCompositeProcessor) | ||
assert composite_processor.setting == "value" | ||
|
||
def test_unknown(self): | ||
with pytest.raises(ValueError, match="invalid post-processor 'anything'"): | ||
CompositeProcessor.build({"name": "anything"}) | ||
|
||
def test_no_name_provided(self): | ||
with pytest.raises(ValueError, match="post-processor must have a 'name' key"): | ||
CompositeProcessor.build({}) | ||
|
||
|
||
class TestConcat(TestCase): | ||
def test_no_params(self): | ||
with pytest.raises(TypeError): | ||
CompositeProcessor.build({"name": "concat"}) | ||
|
||
def test_empty_params(self): | ||
with pytest.raises(TypeError): | ||
CompositeProcessor.build({"name": "concat", "parameters": ""}) | ||
|
||
def test_fields_params_empty(self): | ||
with pytest.raises(ValueError): | ||
CompositeProcessor.build({"name": "concat", "parameters": {"fields": ""}}) | ||
|
||
def test_fields_params_not_a_list_of_str(self): | ||
with pytest.raises(ValueError): | ||
CompositeProcessor.build( | ||
{"name": "concat", "parameters": {"fields": "xxx"}} | ||
) | ||
|
||
def test_fields_params_has_less_than_two_field(self): | ||
with pytest.raises(ValueError): | ||
CompositeProcessor.build( | ||
{"name": "concat", "parameters": {"fields": ["code"]}} | ||
) | ||
|
||
def test_field_not_present(self): | ||
processor = CompositeProcessor.build( | ||
{"name": "concat", "parameters": {"fields": ["code_1", "code_2"]}} | ||
) | ||
with pytest.raises(KeyError): | ||
processor.apply({}) | ||
|
||
def test_concat_two_fields(self): | ||
processor = CompositeProcessor.build( | ||
{"name": "concat", "parameters": {"fields": ["code_1", "code_2"]}} | ||
) | ||
|
||
result = processor.apply({"code_1": "X", "code_2": "Y"}) | ||
|
||
assert result == "XY" | ||
|
||
def test_concat_three_fields(self): | ||
processor = CompositeProcessor.build( | ||
{"name": "concat", "parameters": {"fields": ["code_1", "code_2", "code_3"]}} | ||
) | ||
|
||
result = processor.apply({"code_1": "X", "code_2": "Y", "code_3": "Z"}) | ||
|
||
assert result == "XYZ" | ||
|
||
def test_concat_integer(self): | ||
processor = CompositeProcessor.build( | ||
{"name": "concat", "parameters": {"fields": ["code_1", "code_2"]}} | ||
) | ||
|
||
with pytest.raises(TypeError): | ||
processor.apply({"code_1": 1, "code_2": 2}) |