initial
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
build/
|
||||
*.egg-info
|
||||
dist
|
||||
.coverage
|
||||
.cache/
|
11
README.rst
Normal file
11
README.rst
Normal file
@@ -0,0 +1,11 @@
|
||||
manipulator
|
||||
=============
|
||||
|
||||
Python data manipulation made braindead.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
::
|
||||
|
||||
pip install manipulator
|
2
manipulator/__init__.py
Normal file
2
manipulator/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from manipulator.get import get
|
||||
from manipulator.update import update, set
|
9
manipulator/get.py
Normal file
9
manipulator/get.py
Normal file
@@ -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
|
53
manipulator/query.py
Normal file
53
manipulator/query.py
Normal file
@@ -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
|
34
manipulator/update.py
Normal file
34
manipulator/update.py
Normal file
@@ -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)
|
21
setup.py
Normal file
21
setup.py
Normal file
@@ -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,
|
||||
)
|
35
test/get_test.py
Normal file
35
test/get_test.py
Normal file
@@ -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]
|
16
test/set_test.py
Normal file
16
test/set_test.py
Normal file
@@ -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}]}}]
|
79
test/update_test.py
Normal file
79
test/update_test.py
Normal file
@@ -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}]}}]
|
Reference in New Issue
Block a user