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
from functools import partial
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
@ -124,6 +124,42 @@ class PersistedDict(Dict[str, Any]):
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]:
"""
Searches for a flavor of a specific type.

View file

@ -24,11 +24,12 @@ Utility tests.
import json
import os
from unittest.mock import call, patch
import pytest
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:
@ -134,7 +135,7 @@ class TestPersistedDict:
"""
Tests data is replaced on load.
"""
extra = {object(): object()}
extra = {'key': object()}
data = PersistedDict(tmpfile)
data.update(extra)
data.load()
@ -192,6 +193,76 @@ class TestPersistedDict:
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:
"""
find_flavor tests.