mirror of
https://github.com/JockeTF/fimfarchive.git
synced 2024-11-25 22:47:59 +01:00
Add walker for JSON objects
This commit is contained in:
parent
8e8dac864a
commit
aa6cafa305
2 changed files with 110 additions and 3 deletions
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue