From af0dc5fa1b4a9e7c75c66c4668d4413afb27e550 Mon Sep 17 00:00:00 2001 From: Cariad Eccleston Date: Fri, 21 Jul 2023 13:44:41 +0100 Subject: [PATCH] Add support for binding to constant values (#37) --- .../cook-book/conditional-translating.rst | 2 +- docs/source/cook-book/constants.rst | 62 +++++++++++++++++++ docs/source/cook-book/index.rst | 1 + docs/source/cook-book/translating.rst | 2 +- docs/source/cook-book/udfs-with-repeating.rst | 2 +- rolumns/by_key.py | 2 +- rolumns/by_user_defined_fields.py | 2 +- rolumns/columns.py | 4 +- rolumns/source.py | 12 +++- tests/data/0001-expect-constant.json | 6 ++ tests/renderers/test_markdown.py | 2 +- tests/renderers/test_rows.py | 13 +++- tests/test_readme.py | 2 +- tests/test_source.py | 2 +- 14 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 docs/source/cook-book/constants.rst create mode 100644 tests/data/0001-expect-constant.json diff --git a/docs/source/cook-book/conditional-translating.rst b/docs/source/cook-book/conditional-translating.rst index 5e0620b..a24aece 100644 --- a/docs/source/cook-book/conditional-translating.rst +++ b/docs/source/cook-book/conditional-translating.rst @@ -83,7 +83,7 @@ This code is similar to :doc:`the Translating example ` but note th columns = Columns() columns.add("Name", "name") - columns.add("Address", Source("address.planet", translator=censor)) + columns.add("Address", Source(path="address.planet", translator=censor)) renderer = RowsRenderer(columns) rows = renderer.render(data) diff --git a/docs/source/cook-book/constants.rst b/docs/source/cook-book/constants.rst new file mode 100644 index 0000000..2d64b95 --- /dev/null +++ b/docs/source/cook-book/constants.rst @@ -0,0 +1,62 @@ +.. py:module:: rolumns + :noindex: + +Constants +========= + +The Problem +----------- + +Sometimes you want to bind a column to a static constant that doesn't exist within your source data. + +We'll achieve this by binding the constant to the column's :class:`Source`. + +Code Sample +----------- + +.. testcode:: + + from rolumns import Columns, Source + from rolumns.renderers import RowsRenderer + + data = [ + { + "name": "Robert Pringles", + "address": { + "planet": "Earth" + } + }, + { + "name": "Daniel Sausage", + "address": { + "planet": "Mars" + } + }, + { + "name": "Charlie Marmalade", + "address": { + "planet": "Pluto" + } + } + ] + + columns = Columns() + columns.add("Name", "name") + columns.add("Address", "address.planet") + columns.add("Lives on a Planet?", Source(constant="Yes")) + + renderer = RowsRenderer(columns) + rows = renderer.render(data) + + print(list(rows)) + +Result +------ + +.. testoutput:: + :options: +NORMALIZE_WHITESPACE + + [['Name', 'Address', 'Lives on a Planet?'], + ['Robert Pringles', 'Earth', 'Yes'], + ['Daniel Sausage', 'Mars', 'Yes'], + ['Charlie Marmalade', 'Pluto', 'Yes']] diff --git a/docs/source/cook-book/index.rst b/docs/source/cook-book/index.rst index b3933e2..b86bf7b 100644 --- a/docs/source/cook-book/index.rst +++ b/docs/source/cook-book/index.rst @@ -15,6 +15,7 @@ These articles describe common scenarios and solutions, starting with the simple grouping-dictionary-lists translating conditional-translating + constants udfs udfs-with-repeating choosing-columns diff --git a/docs/source/cook-book/translating.rst b/docs/source/cook-book/translating.rst index 80fd280..97a60b7 100644 --- a/docs/source/cook-book/translating.rst +++ b/docs/source/cook-book/translating.rst @@ -65,7 +65,7 @@ This code is similar to :doc:`the Flat Table example ` except for the :cla columns = Columns() columns.add("Name", "name") - columns.add("Email", Source("email", translator=censor)) + columns.add("Email", Source(path="email", translator=censor)) renderer = RowsRenderer(columns) rows = renderer.render(data) diff --git a/docs/source/cook-book/udfs-with-repeating.rst b/docs/source/cook-book/udfs-with-repeating.rst index eb433c9..bd1f882 100644 --- a/docs/source/cook-book/udfs-with-repeating.rst +++ b/docs/source/cook-book/udfs-with-repeating.rst @@ -80,7 +80,7 @@ This code is similar to :doc:`User-Defined Fields `, but note that the new by_udf = ByUserDefinedFields() by_udf.append("Email", "email") by_udf.append("Title", "title") - by_udf.append("Company", Source("company.name", cursor=columns.cursor)) + by_udf.append("Company", Source(path="company.name", cursor=columns.cursor)) udfs = staff.group(by_udf) udfs.add("Property", ByUserDefinedFields.NAME) diff --git a/rolumns/by_key.py b/rolumns/by_key.py index ae81735..7e5b1c5 100644 --- a/rolumns/by_key.py +++ b/rolumns/by_key.py @@ -91,7 +91,7 @@ class ByKey(Group): _VALUE = "value" def __init__(self, source: Optional[Union[Source, str]] = None) -> None: - self._source = source if isinstance(source, Source) else Source(source) + self._source = source if isinstance(source, Source) else Source(path=source) @classmethod def key(cls) -> str: diff --git a/rolumns/by_user_defined_fields.py b/rolumns/by_user_defined_fields.py index 10851a5..7e7cef9 100644 --- a/rolumns/by_user_defined_fields.py +++ b/rolumns/by_user_defined_fields.py @@ -12,7 +12,7 @@ class UserDefinedField: def __init__(self, name: str, source: Union[Source, str]) -> None: self.name = name - self.source = source if isinstance(source, Source) else Source(source) + self.source = source if isinstance(source, Source) else Source(path=source) class ByUserDefinedFields(Group): diff --git a/rolumns/columns.py b/rolumns/columns.py index 4717b52..784d44c 100644 --- a/rolumns/columns.py +++ b/rolumns/columns.py @@ -69,14 +69,14 @@ def add( columns = Columns() columns.add("Name", "name") - columns.add("Email", Source("email")) + columns.add("Email", Source(path="email")) awards = columns.group("awards") # "awards" is also a column set awards.add("Awards") """ - source = source if isinstance(source, Source) else Source(source) + source = source if isinstance(source, Source) else Source(path=source) column = Column(name, source) self._columns.append(column) diff --git a/rolumns/source.py b/rolumns/source.py index 7296148..bc58f23 100644 --- a/rolumns/source.py +++ b/rolumns/source.py @@ -36,14 +36,20 @@ class Source: ] - The path :code:`None` iterates over the values + + :code:`constant` describes any static constant to bind to. + + :code:`cursor` describes any cursor to bind to. """ def __init__( self, - path: Optional[str], + constant: Optional[Any] = None, cursor: Optional[Cursor] = None, + path: Optional[str] = None, translator: Optional[Translator] = None, ) -> None: + self._constant = constant self._cursor = cursor self._path = path self._translator = translator @@ -53,6 +59,10 @@ def read(self, record: Any) -> Iterable[Any]: Yields each prescribed value of :code:`record`. """ + if self._constant is not None: + yield self._constant + return + if self._cursor is not None: record = self._cursor.current diff --git a/tests/data/0001-expect-constant.json b/tests/data/0001-expect-constant.json new file mode 100644 index 0000000..e274f84 --- /dev/null +++ b/tests/data/0001-expect-constant.json @@ -0,0 +1,6 @@ +[ + ["Name", "Favourite Colour", "Delightful?"], + ["alice", "green", "Yes"], + ["bob", "magenta", "Yes"], + ["charlie", "orange", "Yes"] +] diff --git a/tests/renderers/test_markdown.py b/tests/renderers/test_markdown.py index b449afc..9fae4fd 100644 --- a/tests/renderers/test_markdown.py +++ b/tests/renderers/test_markdown.py @@ -64,7 +64,7 @@ def fire(state: TranslationState) -> Optional[str]: cs = Columns() cs.add("Name", "name") cs.add("Favourite Colour", "favourite_colour") - cs.add("?", Source("favourite_colour", translator=fire)) + cs.add("?", Source(path="favourite_colour", translator=fire)) t = MarkdownRenderer(cs) assert t.render_string(inp) == "\n".join(exp) + "\n" diff --git a/tests/renderers/test_rows.py b/tests/renderers/test_rows.py index a6e224a..4021cc5 100644 --- a/tests/renderers/test_rows.py +++ b/tests/renderers/test_rows.py @@ -124,7 +124,7 @@ def to_upper(state: TranslationState) -> str: ] cs = Columns() - cs.add("Name", Source("name", translator=to_upper)) + cs.add("Name", Source(path="name", translator=to_upper)) assert list(RowsRenderer(cs).render(inp)) == exp @@ -243,7 +243,7 @@ def test_udf_repeat_static() -> None: UserDefinedField( "Emergency", Source( - ByKey.value("static.emergency_phone.value"), + path=ByKey.value("static.emergency_phone.value"), cursor=cs.cursor, ), ), @@ -254,3 +254,12 @@ def test_udf_repeat_static() -> None: udfs.add("Number", "value") assert list(RowsRenderer(cs).render(inp)) == exp + + +def test_constant() -> None: + (inp, exp) = load_test_case(1, expect_variant="constant") + cs = Columns() + cs.add("Name", "name") + cs.add("Favourite Colour", "favourite_colour") + cs.add("Delightful?", Source(constant="Yes")) + assert list(RowsRenderer(cs).render(inp)) == exp diff --git a/tests/test_readme.py b/tests/test_readme.py index 25749ab..e9a874e 100644 --- a/tests/test_readme.py +++ b/tests/test_readme.py @@ -262,7 +262,7 @@ def censor(state: TranslationState) -> str: columns = rolumns.Columns() columns.add("Name", "name") - columns.add("Email", rolumns.Source("email", translator=censor)) + columns.add("Email", rolumns.Source(path="email", translator=censor)) renderer = rolumns.renderers.MarkdownRenderer(columns) diff --git a/tests/test_source.py b/tests/test_source.py index 1d8859f..7e8c5c4 100644 --- a/tests/test_source.py +++ b/tests/test_source.py @@ -9,7 +9,7 @@ def test_fail() -> None: def fail(state: TranslationState) -> str: raise Exception("failed") - source = Source("date", translator=fail) + source = Source(path="date", translator=fail) with raises(TranslationFailed) as ex: list(source.read({"date": "pringles"}))