Add walker for JSON objects

This commit is contained in:
Joakim Soderlund 2018-09-05 18:15:57 +02:00
parent 8e8dac864a
commit aa6cafa305
2 changed files with 110 additions and 3 deletions

View file

@ -27,7 +27,7 @@ import os
import shutil import shutil
from functools import partial from functools import partial
from importlib_resources import read_binary, read_text from importlib_resources import read_binary, read_text
from typing import Any, Dict, Optional, Type, TypeVar, Union from typing import Any, Dict, Iterator, Optional, Type, TypeVar, Union
from tqdm import tqdm from tqdm import tqdm
@ -124,6 +124,42 @@ class PersistedDict(Dict[str, Any]):
os.remove(self.temp) os.remove(self.temp)
class JayWalker:
"""
Walker for JSON objects.
"""
def walk(self, data) -> None:
"""
Walks the attributes of a JSON object.
Args:
data: The object to walk.
"""
iterator: Iterator
if isinstance(data, dict):
iterator = iter(data.items())
elif isinstance(data, list):
iterator = enumerate(data)
else:
return
for key, value in iterator:
self.handle(data, key, value)
def handle(self, data, key, value) -> None:
"""
Handles a single JSON entry.
Args:
data: The current object.
key: The key of the entry.
value: The value of the entry.
"""
self.walk(value)
def find_flavor(story: Story, flavor: Type[F]) -> Optional[F]: def find_flavor(story: Story, flavor: Type[F]) -> Optional[F]:
""" """
Searches for a flavor of a specific type. Searches for a flavor of a specific type.

View file

@ -24,11 +24,12 @@ Utility tests.
import json import json
import os import os
from unittest.mock import call, patch
import pytest import pytest
from fimfarchive.flavors import DataFormat, MetaFormat, MetaPurity from fimfarchive.flavors import DataFormat, MetaFormat, MetaPurity
from fimfarchive.utils import find_flavor, Empty, PersistedDict from fimfarchive.utils import find_flavor, Empty, JayWalker, PersistedDict
class TestEmpty: class TestEmpty:
@ -134,7 +135,7 @@ class TestPersistedDict:
""" """
Tests data is replaced on load. Tests data is replaced on load.
""" """
extra = {object(): object()} extra = {'key': object()}
data = PersistedDict(tmpfile) data = PersistedDict(tmpfile)
data.update(extra) data.update(extra)
data.load() data.load()
@ -192,6 +193,76 @@ class TestPersistedDict:
assert dict(data) == sample assert dict(data) == sample
class TestJayWalker:
"""
JayWalker tests.
"""
@pytest.fixture
def walker(self):
"""
Returns a walker instance.
"""
walker = JayWalker()
with patch.object(walker, 'handle', wraps=walker.handle):
yield walker
@pytest.mark.parametrize('obj', [None, 'alpaca', 42])
def test_ignored_walk(self, walker, obj):
"""
Tests walker ignores plain values.
"""
walker.walk(obj)
walker.handle.assert_not_called()
def test_list_walk(self, walker):
"""
Tests walker can walk lists.
"""
data = ['a', 'b', 'c']
walker.walk(data)
assert walker.handle.mock_calls == [
call(data, 0, 'a'),
call(data, 1, 'b'),
call(data, 2, 'c'),
]
def test_dict_walk(self, walker):
"""
Tests walker can walk dicts.
"""
data = {0: 'a', 1: 'b', 2: 'c'}
walker.walk(data)
assert walker.handle.mock_calls == [
call(data, 0, 'a'),
call(data, 1, 'b'),
call(data, 2, 'c'),
]
def test_nested_walk(self, walker):
"""
Tests walker can walk nested objects.
"""
data = {
'a': ['b', 'c'],
'd': {'e': 'f'},
}
walker.walk([data])
assert walker.handle.mock_calls == [
call([data], 0, data),
call(data, 'a', data['a']),
call(data['a'], 0, 'b'),
call(data['a'], 1, 'c'),
call(data, 'd', data['d']),
call(data['d'], 'e', 'f'),
]
class TestFindFlavor: class TestFindFlavor:
""" """
find_flavor tests. find_flavor tests.