From a98a4bc85b04066fc07bf93a7335ec44ef3d6730 Mon Sep 17 00:00:00 2001 From: Paul Fenwick Date: Thu, 16 Mar 2017 17:30:04 +1100 Subject: [PATCH] mapping.py: Added a new mapping class This makes things so much easier than deeply, deeply nested code. --- .coveragerc | 5 ++++ taas/mapping.py | 59 +++++++++++++++++++++++++++++++++++++++++++ tests/test_mapping.py | 48 +++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 taas/mapping.py create mode 100644 tests/test_mapping.py diff --git a/.coveragerc b/.coveragerc index d934a24..d8ea769 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,3 +4,8 @@ omit= venv/* setup.py tests/* + +[report] +exclude_lines = + # We don't need coverage of abstract/unimplemented methods + raise NotImplementedError diff --git a/taas/mapping.py b/taas/mapping.py new file mode 100644 index 0000000..54107f1 --- /dev/null +++ b/taas/mapping.py @@ -0,0 +1,59 @@ +from abc import ABCMeta, abstractmethod + + +# XXX: Is it recommended I inherit from object? +class Mapping(object): + """ + This class handles mappings between our raw input, and what we generate. + """ + + # We're an abstract class + __metaclass__ = ABCMeta + + # This was originally called 'value', but apparently that's a + # special method name. + @abstractmethod + def emit(self, row): + """ + Given a row of data, emits the value calculated by + this transform. + """ + raise NotImplementedError + + +class Literal(Mapping): + """A literal string that's always the same.""" + + def __init__(self, config): + self.value = config["value"] + + def emit(self, row): + return self.value + + +class Map(Mapping): + """The most common mapping. Turns a field in our data into a field in our output.""" + + def __init__(self, config): + self.field = config["field"] + self.optional = config.get("optional", False) + + def emit(self, row): + value = row[self.field] + + if not self.optional and len(value) == 0: + raise ValueError("Required field {} missing in data".format(self.field)) + + return value + + +class Concat(Map): + """A map, but concatenated with static strings.""" + + def __init__(self, config): + super(self.__class__, self).__init__(config) + self.pre = config.get("prefix", "") + self.post = config.get("postfix", "") + + def emit(self, row): + return self.pre + super(self.__class__, self).emit(row) + self.post diff --git a/tests/test_mapping.py b/tests/test_mapping.py new file mode 100644 index 0000000..8332dff --- /dev/null +++ b/tests/test_mapping.py @@ -0,0 +1,48 @@ +from taas.mapping import Literal, Map, Concat +import unittest + + +class TestMapping(unittest.TestCase): + + row = { + "id": "3", + "foo": "bar", + "baz": "bazza" + } + + def test_literal(self): + + mystr = "foo" + + lit = Literal({"value": mystr}) + + value = lit.emit(self.row) + + self.assertEqual(value, mystr) + + def test_map(self): + mymap = Map({"field": "foo"}) + + self.assertEqual( + mymap.emit(self.row), + "bar" + ) + + self.assertEqual( + mymap.emit({"foo": "baz"}), + "baz" + ) + + # TODO: Test *optional* maps. + + def test_concat(self): + concat = Concat({ + "field": "id", + "prefix": "https://example.com/", + "postfix": "/self" + }) + + self.assertEqual( + concat.emit(self.row), + "https://example.com/3/self" + )