Skip to content

Commit d6b5991

Browse files
committed
Worked on JSON serializer helper
1 parent 240444e commit d6b5991

File tree

9 files changed

+269
-84
lines changed

9 files changed

+269
-84
lines changed

acstore/containers/manager.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def GetSchema(cls, container_type):
7575
container_class = cls._attribute_container_classes.get(
7676
container_type, None)
7777
if not container_class:
78-
raise ValueError(f'Unsupported container type: {container_type:s}')
78+
raise ValueError(f'Unsupported container type: {container_type!s}')
7979

8080
return getattr(container_class, 'SCHEMA', {})
8181

acstore/helpers/json_serializer.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# -*- coding: utf-8 -*-
2+
"""Attribute container JSON serializer."""
3+
4+
from acstore.containers import manager as containers_manager
5+
from acstore.helpers import schema as schema_helper
6+
7+
8+
class AttributeContainerJSONSerializer(object):
9+
"""Attribute container JSON serializer."""
10+
11+
_CONTAINERS_MANAGER = containers_manager.AttributeContainersManager
12+
13+
@classmethod
14+
def ConvertAttributeContainerToJSON(cls, attribute_container):
15+
"""Converts an attribute container object into a JSON dictioary.
16+
17+
The resulting dictionary of the JSON serialized objects consists of:
18+
{
19+
'__type__': 'AttributeContainer'
20+
'__container_type__': ...
21+
...
22+
}
23+
24+
Here '__type__' indicates the object base type. In this case
25+
'AttributeContainer'.
26+
27+
'__container_type__' indicates the container type and rest of the elements
28+
of the dictionary that make up the attributes of the container.
29+
30+
Args:
31+
attribute_container (AttributeContainer): attribute container.
32+
33+
Returns:
34+
dict[str, object]: JSON serialized objects.
35+
"""
36+
try:
37+
schema = cls._CONTAINERS_MANAGER.GetSchema(
38+
attribute_container.CONTAINER_TYPE)
39+
except ValueError:
40+
schema = {}
41+
42+
json_dict = {
43+
'__type__': 'AttributeContainer',
44+
'__container_type__': attribute_container.CONTAINER_TYPE}
45+
46+
for attribute_name, attribute_value in attribute_container.GetAttributes():
47+
data_type = schema.get(attribute_name, None)
48+
if data_type:
49+
serializer = schema_helper.SchemaHelper.GetAttributeSerializer(
50+
data_type, 'json')
51+
52+
attribute_value = serializer.SerializeValue(attribute_value)
53+
54+
# JSON will not serialize certain runtime types like set, therefore
55+
# these are cast to list first.
56+
if isinstance(attribute_value, set):
57+
attribute_value = list(attribute_value)
58+
59+
json_dict[attribute_name] = attribute_value
60+
61+
return json_dict
62+
63+
@classmethod
64+
def ConvertJSONToAttributeContainer(cls, json_dict):
65+
"""Converts a JSON dictionary into an attribute container object.
66+
67+
The dictionary of the JSON serialized objects consists of:
68+
{
69+
'__type__': 'AttributeContainer'
70+
'__container_type__': ...
71+
...
72+
}
73+
74+
Here '__type__' indicates the object base type. In this case
75+
'AttributeContainer'.
76+
77+
'__container_type__' indicates the container type and rest of the elements
78+
of the dictionary that make up the attributes of the container.
79+
80+
Args:
81+
json_dict (dict[str, object]): JSON serialized objects.
82+
83+
Returns:
84+
AttributeContainer: attribute container.
85+
"""
86+
# Use __container_type__ to indicate the attribute container type.
87+
container_type = json_dict.get('__container_type__', None)
88+
89+
attribute_container = cls._CONTAINERS_MANAGER.CreateAttributeContainer(
90+
container_type)
91+
92+
supported_attribute_names = attribute_container.GetAttributeNames()
93+
for attribute_name, attribute_value in json_dict.items():
94+
if attribute_name in ('__container_type__', '__type__'):
95+
continue
96+
97+
# Be strict about which attributes to set.
98+
if attribute_name not in supported_attribute_names:
99+
continue
100+
101+
setattr(attribute_container, attribute_name, attribute_value)
102+
103+
return attribute_container

acstore/interface.py

+52
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,55 @@ def UpdateAttributeContainer(self, container):
238238
"""
239239
self._RaiseIfNotWritable()
240240
self._WriteExistingAttributeContainer(container)
241+
242+
243+
class AttributeContainerStoreWithReadCache(AttributeContainerStore):
244+
"""Interface of an attribute container store with read cache.
245+
246+
Attributes:
247+
format_version (int): storage format version.
248+
"""
249+
250+
# pylint: disable=abstract-method
251+
252+
# The maximum number of cached attribute containers
253+
_MAXIMUM_CACHED_CONTAINERS = 32 * 1024
254+
255+
def __init__(self):
256+
"""Initializes an attribute container store with read cache."""
257+
super(AttributeContainerStoreWithReadCache, self).__init__()
258+
self._attribute_container_cache = collections.OrderedDict()
259+
260+
def _CacheAttributeContainerByIndex(self, attribute_container, index):
261+
"""Caches a specific attribute container.
262+
263+
Args:
264+
attribute_container (AttributeContainer): attribute container.
265+
index (int): attribute container index.
266+
"""
267+
if len(self._attribute_container_cache) >= self._MAXIMUM_CACHED_CONTAINERS:
268+
self._attribute_container_cache.popitem(last=True)
269+
270+
lookup_key = f'{attribute_container.CONTAINER_TYPE:s}.{index:d}'
271+
self._attribute_container_cache[lookup_key] = attribute_container
272+
self._attribute_container_cache.move_to_end(lookup_key, last=False)
273+
274+
def _GetCachedAttributeContainer(self, container_type, index):
275+
"""Retrieves a specific cached attribute container.
276+
277+
Args:
278+
container_type (str): attribute container type.
279+
index (int): attribute container index.
280+
281+
Returns:
282+
AttributeContainer: attribute container or None if not available.
283+
284+
Raises:
285+
IOError: when there is an error querying the attribute container store.
286+
OSError: when there is an error querying the attribute container store.
287+
"""
288+
lookup_key = f'{container_type:s}.{index:d}'
289+
attribute_container = self._attribute_container_cache.get(lookup_key, None)
290+
if attribute_container:
291+
self._attribute_container_cache.move_to_end(lookup_key, last=False)
292+
return attribute_container

acstore/sqlite_store.py

+9-44
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"""SQLite-based attribute container store."""
33

44
import ast
5-
import collections
65
import itertools
6+
import json
77
import os
88
import pathlib
99
import sqlite3
@@ -122,7 +122,8 @@ def DeserializeValue(self, data_type, value):
122122
elif data_type not in self._MAPPINGS:
123123
serializer = schema_helper.SchemaHelper.GetAttributeSerializer(
124124
data_type, 'json')
125-
value = serializer.DeserializeValue(value)
125+
json_dict = json.loads(value)
126+
value = serializer.DeserializeValue(json_dict)
126127

127128
return value
128129

@@ -160,12 +161,14 @@ def SerializeValue(self, data_type, value):
160161
if isinstance(value, set):
161162
value = list(value)
162163

163-
return serializer.SerializeValue(value)
164+
json_dict = serializer.SerializeValue(value)
165+
return json.dumps(json_dict)
164166

165167
return value
166168

167169

168-
class SQLiteAttributeContainerStore(interface.AttributeContainerStore):
170+
class SQLiteAttributeContainerStore(
171+
interface.AttributeContainerStoreWithReadCache):
169172
"""SQLite-based attribute container store.
170173
171174
Attributes:
@@ -205,15 +208,11 @@ class SQLiteAttributeContainerStore(interface.AttributeContainerStore):
205208
_INSERT_METADATA_VALUE_QUERY = (
206209
'INSERT INTO metadata (key, value) VALUES (?, ?)')
207210

208-
# The maximum number of cached attribute containers
209-
_MAXIMUM_CACHED_CONTAINERS = 32 * 1024
210-
211211
_MAXIMUM_WRITE_CACHE_SIZE = 50
212212

213213
def __init__(self):
214214
"""Initializes a SQLite attribute container store."""
215215
super(SQLiteAttributeContainerStore, self).__init__()
216-
self._attribute_container_cache = collections.OrderedDict()
217216
self._connection = None
218217
self._cursor = None
219218
self._is_open = False
@@ -224,20 +223,6 @@ def __init__(self):
224223
self.format_version = self._FORMAT_VERSION
225224
self.serialization_format = 'json'
226225

227-
def _CacheAttributeContainerByIndex(self, attribute_container, index):
228-
"""Caches a specific attribute container.
229-
230-
Args:
231-
attribute_container (AttributeContainer): attribute container.
232-
index (int): attribute container index.
233-
"""
234-
if len(self._attribute_container_cache) >= self._MAXIMUM_CACHED_CONTAINERS:
235-
self._attribute_container_cache.popitem(last=True)
236-
237-
lookup_key = f'{attribute_container.CONTAINER_TYPE:s}.{index:d}'
238-
self._attribute_container_cache[lookup_key] = attribute_container
239-
self._attribute_container_cache.move_to_end(lookup_key, last=False)
240-
241226
def _CacheAttributeContainerForWrite(
242227
self, container_type, column_names, values):
243228
"""Caches an attribute container for writing.
@@ -515,26 +500,6 @@ def _GetAttributeContainersWithFilter(
515500
if self._storage_profiler:
516501
self._storage_profiler.StopTiming('get_containers')
517502

518-
def _GetCachedAttributeContainer(self, container_type, index):
519-
"""Retrieves a specific cached attribute container.
520-
521-
Args:
522-
container_type (str): attribute container type.
523-
index (int): attribute container index.
524-
525-
Returns:
526-
AttributeContainer: attribute container or None if not available.
527-
528-
Raises:
529-
IOError: when there is an error querying the attribute container store.
530-
OSError: when there is an error querying the attribute container store.
531-
"""
532-
lookup_key = f'{container_type:s}.{index:d}'
533-
attribute_container = self._attribute_container_cache.get(lookup_key, None)
534-
if attribute_container:
535-
self._attribute_container_cache.move_to_end(lookup_key, last=False)
536-
return attribute_container
537-
538503
def _HasTable(self, table_name):
539504
"""Determines if a specific table exists.
540505
@@ -835,7 +800,7 @@ def Close(self):
835800
OSError: if the attribute container store is already closed.
836801
"""
837802
if not self._is_open:
838-
raise IOError('Storage file already closed.')
803+
raise IOError('Attribute container store already closed.')
839804

840805
if self._connection:
841806
self._Flush()
@@ -1034,7 +999,7 @@ def Open(self, path=None, read_only=True, **unused_kwargs): # pylint: disable=a
1034999
ValueError: if path is missing.
10351000
"""
10361001
if self._is_open:
1037-
raise IOError('Storage file already opened.')
1002+
raise IOError('Attribute container store already opened.')
10381003

10391004
if not path:
10401005
raise ValueError('Missing path.')

config/dpkg/changelog

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ acstore (20240121-1) unstable; urgency=low
22

33
* Auto-generated
44

5-
-- Log2Timeline maintainers <log2timeline-maintainers@googlegroups.com> Sun, 21 Jan 2024 06:24:34 +0100
5+
-- Log2Timeline maintainers <log2timeline-maintainers@googlegroups.com> Sun, 21 Jan 2024 08:55:35 +0100

docs/sources/api/acstore.helpers.rst

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ acstore.helpers package
44
Submodules
55
----------
66

7+
acstore.helpers.json\_serializer module
8+
---------------------------------------
9+
10+
.. automodule:: acstore.helpers.json_serializer
11+
:members:
12+
:undoc-members:
13+
:show-inheritance:
14+
715
acstore.helpers.schema module
816
-----------------------------
917

0 commit comments

Comments
 (0)