commit e8c62738a16e57233d3c323ed80bbaa6f8425221 Author: hellerve Date: Tue Jan 17 14:49:18 2017 +0100 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4cbc123 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +__pycache__/ +*.pyc +build/ +*.egg-info +dist +.coverage +.cache/ diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..3edcbbe --- /dev/null +++ b/README.rst @@ -0,0 +1,11 @@ +manipulator +============= + +Python data manipulation made braindead. + +Installation +------------ + +:: + + pip install manipulator diff --git a/manipulator/__init__.py b/manipulator/__init__.py new file mode 100644 index 0000000..b6b54ac --- /dev/null +++ b/manipulator/__init__.py @@ -0,0 +1,2 @@ +from manipulator.get import get +from manipulator.update import update, set diff --git a/manipulator/get.py b/manipulator/get.py new file mode 100644 index 0000000..ae9a5b1 --- /dev/null +++ b/manipulator/get.py @@ -0,0 +1,9 @@ +from manipulator.query import treat_query + +def get(data, query): + selectors = treat_query(query) + + for selector in selectors: + _, data = selector(data) + + return data diff --git a/manipulator/query.py b/manipulator/query.py new file mode 100644 index 0000000..668cd6d --- /dev/null +++ b/manipulator/query.py @@ -0,0 +1,53 @@ +from functools import wraps + +def manipulate_all(name, data): + def manipulate_internal(fn): + for elem in data: + k, v = select_id(name, elem) + elem[k] = fn(v) + return data + return manipulate_internal + + +def curry(fn): + @wraps(fn) + def wrapped(x): + return lambda y: fn(x, y) + return wrapped + + +def select_id(name, data): + if isinstance(data, list): + name = int(name) + + return name, data[name] + + +def select_class(name, data): + if isinstance(data, list): + return manipulate_all(name, data), [select_id(name, elem)[1] for elem in data] + return name, data[name] + + +def lookup_selector(start): + if start == "#": + return curry(select_id) + elif start == ".": + return curry(select_class) + raise ValueError("Unknown selector: {}".format(start)) + + +def treat_query(query): + selectors = [] + + for elem in query.split(" "): + elem = elem.strip() + + if not elem: + continue + + selector = lookup_selector(elem[0]) + + selectors.append(selector(elem[1:])) + + return selectors diff --git a/manipulator/update.py b/manipulator/update.py new file mode 100644 index 0000000..6305ca1 --- /dev/null +++ b/manipulator/update.py @@ -0,0 +1,34 @@ +import copy + +from manipulator.query import treat_query + +def update(inpt, query, fn, in_place=True): + if in_place: + data = inpt + else: + data = copy.deepcopy(inpt) + + selectors = treat_query(query) + + if not selectors: + return data + + d = data + last_key = None + last_d = data + for selector in selectors: + last_d = d + last_key, d = selector(d) + + if callable(last_key): + last_d = last_key(fn) + elif isinstance(d, list) or isinstance(d, dict): + d = fn(d) + else: + last_d[last_key] = fn(d) + + return data + + +def set(inpt, query, val, in_place=True): + return update(inpt, query, lambda _: val, in_place) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4944958 --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +# coding=utf-8 +import os + +from setuptools import find_packages, setup + +with open('README.rst') as readme: + long_description = readme.read() + +setup( + name = "manipulator", + version = "0.0.1", + description = "Python data manipulation made braindead", + long_description = long_description, + author = "Veit Heller", + author_email = "veit@veitheller.de", + license = "MIT License", + url = "https://github.com/hellerve/manipulator", + download_url = 'https://github.com/hellerve/hawkweed/tarball/0.0.1', + packages = find_packages(), + include_package_data = True, +) diff --git a/test/get_test.py b/test/get_test.py new file mode 100644 index 0000000..1baeba6 --- /dev/null +++ b/test/get_test.py @@ -0,0 +1,35 @@ +from manipulator import get + +def test_id(): + x = {} + assert get(x, "") == x + + +def test_simple_id_query(): + x = [1] + + assert get(x, "#0") == 1 + + +def test_simple_class_query(): + x = [{"k": "v"}, {"k": "v2"}] + + assert get(x, ".k") == ["v", "v2"] + + +def test_nested_id_query(): + x = [{"k": "v"}] + + assert get(x, "#0 #k") == "v" + + +def test_nested_class_query(): + x = [{"k": {"k2": "v"}}, {"k": {"k2": "v2"}}] + + assert get(x, ".k .k2") == ["v", "v2"] + + +def test_complex_query(): + x = [{"k": "v"}, {"k": {"a": [{"k": 10}, {"k": 11}]}}] + + assert get(x, ".k #1 #a .k") == [10, 11] diff --git a/test/set_test.py b/test/set_test.py new file mode 100644 index 0000000..7bfe1fc --- /dev/null +++ b/test/set_test.py @@ -0,0 +1,16 @@ +from manipulator import set # bad idea, overriding set constructor! + +def test_complex_set(): + x = [{"k": "v"}, {"k": {"a": [{"k": 10}, {"k": 11}]}}] + after = [{"k": "v"}, {"k": {"a": [{"k": 100}, {"k": 100}]}}] + + assert set(x, ".k #1 #a .k", 100) == after + assert x == after + + +def test_complex_update_copy(): + x = [{"k": "v"}, {"k": {"a": [{"k": 10}, {"k": 11}]}}] + after = [{"k": "v"}, {"k": {"a": [{"k": 100}, {"k": 100}]}}] + + assert set(x, ".k #1 #a .k", 100, in_place=False) == after + assert x == [{"k": "v"}, {"k": {"a": [{"k": 10}, {"k": 11}]}}] diff --git a/test/update_test.py b/test/update_test.py new file mode 100644 index 0000000..ca50937 --- /dev/null +++ b/test/update_test.py @@ -0,0 +1,79 @@ +from manipulator import update + +def test_update_id(): + x = [1] + after = [2] + + assert update(x, "#0", lambda x: x+1) == [2] + assert x == after + + +def test_update_id_copy(): + x = [1] + + assert update(x, "#0", lambda x: x+1, in_place=False) == [2] + assert x == [1] + + +def test_update_class(): + x = [{"k": 1}, {"k": 2}] + after = [{"k": 2}, {"k": 3}] + + assert update(x, ".k", lambda x: x+1) == after + assert x == after + + +def test_update_class_copy(): + x = [{"k": 1}, {"k": 2}] + after = [{"k": 2}, {"k": 3}] + + assert update(x, ".k", lambda x: x+1, in_place=False) == after + assert x == [{"k": 1}, {"k": 2}] + + +def test_update_nested_id(): + x = [{"k": 1}] + after = [{"k": 2}] + + assert update(x, "#0 #k", lambda x: x+1) == after + assert x == after + + +def test_update_nested_id_copy(): + x = [{"k": 1}] + after = [{"k": 2}] + + assert update(x, "#0 #k", lambda x: x+1, in_place=False) == after + assert x == [{"k": 1}] + + +def test_update_nested_class(): + x = [{"k": {"k2": 1}}, {"k": {"k2": 2}}] + after = [{"k": {"k2": 2}}, {"k": {"k2": 3}}] + + assert update(x, ".k .k2", lambda x: x+1) == after + assert x == after + + +def test_update_nested_class_copy(): + x = [{"k": {"k2": 1}}, {"k": {"k2": 2}}] + after = [{"k": {"k2": 2}}, {"k": {"k2": 3}}] + + assert update(x, ".k .k2", lambda x: x+1, in_place=False) == after + assert x == [{"k": {"k2": 1}}, {"k": {"k2": 2}}] + + +def test_complex_update(): + x = [{"k": "v"}, {"k": {"a": [{"k": 10}, {"k": 11}]}}] + after = [{"k": "v"}, {"k": {"a": [{"k": 11}, {"k": 12}]}}] + + assert update(x, ".k #1 #a .k", lambda x: x+1) == after + assert x == after + + +def test_complex_update_copy(): + x = [{"k": "v"}, {"k": {"a": [{"k": 10}, {"k": 11}]}}] + after = [{"k": "v"}, {"k": {"a": [{"k": 11}, {"k": 12}]}}] + + assert update(x, ".k #1 #a .k", lambda x: x+1, in_place=False) == after + assert x == [{"k": "v"}, {"k": {"a": [{"k": 10}, {"k": 11}]}}]