Completed
Pull Request — master (#147)
by Jasper
01:31
created

JsonFormat   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 87
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 87
rs 10
wmc 15

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __init__() 0 4 1
A _strcust() 0 5 2
A serializeSingle() 0 13 1
B _deflate() 0 11 6
A deserialize() 0 14 1
A serializeList() 0 13 2
A deserializeList() 0 14 2
1
import json
2
import copy
3
from datetime import datetime, timedelta
4
from niprov.format import Format
5
6
7
class JsonFormat(Format):
8
    """Helper to convert provenance data to and from json encoded strings.
9
    """
10
11
    datetimeFields = []#['acquired','created','added']
12
    timedeltaFields = []#['duration']
13
14
    def __init__(self, dependencies):
15
        super(JsonFormat, self).__init__(dependencies)
16
        self.fileExtension = 'json'
17
        self.file = dependencies.getFileFactory()
18
19
    def serializeSingle(self, record):
20
        """
21
        Convert one provenance item from its native python dict type to
22
        a json string.
23
24
        Args:
25
            record (dict): The provenance item to convert.
26
27
        Returns:
28
            str: Json version of the provenance.
29
        """
30
        flat = self._deflate(record.provenance)
31
        return json.dumps(flat, cls=DateTimeAwareJSONEncoder)
32
33
    def deserialize(self, jsonRecord):
34
        """
35
        Convert one provenance item from its json string version to the 
36
        native python dictionary format.
37
38
        Args:
39
            jsonRecord (str): The provenance item to convert as json-encoded 
40
                string.
41
42
        Returns:
43
            dict: Python dictionary of the provenance.
44
        """
45
        provenance = json.loads(jsonRecord, cls=DateTimeAwareJSONDecoder)
46
        return self.file.fromProvenance(provenance)
47
48
    def serializeList(self, listOfRecords):
49
        """
50
        Convert a list of provenance items from its native list of python dict 
51
        type to a json string.
52
53
        Args:
54
            listOfRecords (list): The provenance items to convert.
55
56
        Returns:
57
            str: Json version of the provenance items.
58
        """
59
        flatRecords = [self._deflate(r.provenance) for r in listOfRecords]
60
        return json.dumps(flatRecords, cls=DateTimeAwareJSONEncoder)
61
62
    def deserializeList(self, jsonListOfRecords):
63
        """
64
        Convert a list of provenance items from its json string version to the 
65
        a list of the native python dictionary format.
66
67
        Args:
68
            jsonListOfRecords (str): The provenance items to convert as 
69
                json-encoded string.
70
71
        Returns:
72
            list: Python list of dictionaries of the provenance.
73
        """
74
        provenanceList = json.loads(jsonListOfRecords, cls=DateTimeAwareJSONDecoder)
75
        return [self.file.fromProvenance(p) for p in provenanceList]
76
77
    def _deflate(self, record):
78
        flatRecord = copy.deepcopy(record)
79
        if 'args' in record:
80
            flatRecord['args'] = [self._strcust(a) for a in record['args']]
81
        if 'kwargs' in record:
82
            kwargs = record['kwargs']
83
            flatRecord['kwargs'] = {k: self._strcust(kwargs[k]) 
84
                for k in kwargs.keys()}
85
        if '_id' in record:
86
            del flatRecord['_id']
87
        return flatRecord
88
89
    def _strcust(self, val):
90
        """Stringify an object that is not of a simple type."""
91
        if not isinstance(val, (str, unicode, int, float, bool, type(None))):
92
            return str(val)
93
        return val
94
95
# Taken from http://taketwoprogramming.blogspot.com/2009/06/subclassing-jsonencoder-and-jsondecoder.html
96
97
class DateTimeAwareJSONEncoder(json.JSONEncoder):
98
    """ 
99
    Converts a python object, where datetime and timedelta objects are converted
100
    into objects that can be decoded using the DateTimeAwareJSONDecoder.
101
    """
102
    def default(self, obj):
103
        if isinstance(obj, datetime):
104
            return {
105
                '__type__' : 'datetime',
106
                'year' : obj.year,
107
                'month' : obj.month,
108
                'day' : obj.day,
109
                'hour' : obj.hour,
110
                'minute' : obj.minute,
111
                'second' : obj.second,
112
                'microsecond' : obj.microsecond,
113
            }
114
115
        elif isinstance(obj, timedelta):
116
            return {
117
                '__type__' : 'timedelta',
118
                'days' : obj.days,
119
                'seconds' : obj.seconds,
120
                'microseconds' : obj.microseconds,
121
          }
122
123
        else:
124
            return json.JSONEncoder.default(self, obj)
125
126
class DateTimeAwareJSONDecoder(json.JSONDecoder):
127
    """ 
128
    Converts a json string, where datetime and timedelta objects were converted
129
    into objects using the DateTimeAwareJSONEncoder, back into a python object.
130
    """
131
132
    def __init__(self, **kwargs):
133
        json.JSONDecoder.__init__(self, object_hook=self.dict_to_object)
134
135
    def dict_to_object(self, d):
136
        if '__type__' not in d:
137
            return d
138
139
        type = d.pop('__type__')
140
        if type == 'datetime':
141
            return datetime(**d)
142
        elif type == 'timedelta':
143
            return timedelta(**d)
144
        else:
145
            # Oops... better put this back together.
146
            d['__type__'] = type
147
            return d
148