Skip to content
This repository has been archived by the owner on Jul 18, 2023. It is now read-only.

Commit

Permalink
mapping.py: Added a new mapping class
Browse files Browse the repository at this point in the history
This makes things so much easier than deeply, deeply nested code.
  • Loading branch information
pjf committed Mar 16, 2017
1 parent dd6c23c commit a98a4bc
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ omit=
venv/*
setup.py
tests/*

[report]
exclude_lines =
# We don't need coverage of abstract/unimplemented methods
raise NotImplementedError
59 changes: 59 additions & 0 deletions taas/mapping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from abc import ABCMeta, abstractmethod


# XXX: Is it recommended I inherit from object?

This comment has been minimized.

Copy link
@ncoghlan

ncoghlan Mar 16, 2017

This is Python 2.7, so yes. If you don't, you'll get an old-style class, and find a lot of things don't work the way articles and other documentation suggest they should.

This comment has been minimized.

Copy link
@pjf

pjf Mar 22, 2017

Author Contributor

@ncoghlan : You rock, thank you!!

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.

This comment has been minimized.

Copy link
@ncoghlan

ncoghlan Mar 16, 2017

value isn't a special method name per se, but the value instance attributes defined in the subclasses would prevent anyone from calling the method using the normal obj.method(arg) syntax (as they'd get the value of the instance attribute instead of the bound method reference).

@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
48 changes: 48 additions & 0 deletions tests/test_mapping.py
Original file line number Diff line number Diff line change
@@ -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"
)

0 comments on commit a98a4bc

Please sign in to comment.