Add TemplatedRequests.robot Resource
[integration/test.git] / csit / libraries / norm_json.py
1 """This module contains single a function for normalizing JSON strings."""
2 # Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3 #
4 # This program and the accompanying materials are made available under the
5 # terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 # and is available at http://www.eclipse.org/legal/epl-v10.html
7
8 import collections as _collections
9 try:
10     import simplejson as _json
11 except ImportError:  # Python2.7 calls it json.
12     import json as _json
13
14
15 __author__ = "Vratko Polak"
16 __copyright__ = "Copyright(c) 2015-2016, Cisco Systems, Inc."
17 __license__ = "Eclipse Public License v1.0"
18 __email__ = "vrpolak@cisco.com"
19
20
21 # Internal details.
22
23
24 class _Hsfl(list):
25     """
26     Hashable sorted frozen list implementation stub.
27
28     Supports only __init__, __repr__ and __hash__ methods.
29     Other list methods are available, but they may break contract.
30     """
31
32     def __init__(self, *args, **kwargs):
33         """Contruct super, sort and compute repr and hash cache values."""
34         sup = super(_Hsfl, self)
35         sup.__init__(*args, **kwargs)
36         sup.sort(key=repr)
37         self.__repr = repr(tuple(self))
38         self.__hash = hash(self.__repr)
39
40     def __repr__(self):
41         """Return cached repr string."""
42         return self.__repr
43
44     def __hash__(self):
45         """Return cached hash."""
46         return self.__hash
47
48
49 class _Hsfod(_collections.OrderedDict):
50     """
51     Hashable sorted (by key) frozen OrderedDict implementation stub.
52
53     Supports only __init__, __repr__ and __hash__ methods.
54     Other OrderedDict methods are available, but they may break contract.
55     """
56
57     def __init__(self, *args, **kwargs):
58         """Put arguments to OrderedDict, sort, pass to super, cache values."""
59         self_unsorted = _collections.OrderedDict(*args, **kwargs)
60         items_sorted = sorted(self_unsorted.items(), key=repr)
61         sup = super(_Hsfod, self)  # possibly something else than OrderedDict
62         sup.__init__(items_sorted)
63         # Repr string is used for sorting, keys are more important than values.
64         self.__repr = '{' + repr(self.keys()) + ':' + repr(self.values()) + '}'
65         self.__hash = hash(self.__repr)
66
67     def __repr__(self):
68         """Return cached repr string."""
69         return self.__repr
70
71     def __hash__(self):
72         """Return cached hash."""
73         return self.__hash
74
75
76 def _hsfl_array(s_and_end, scan_once, **kwargs):
77     """Scan JSON array as usual, but return hsfl instead of list."""
78     values, end = _json.decoder.JSONArray(s_and_end, scan_once, **kwargs)
79     return _Hsfl(values), end
80
81
82 class _Decoder(_json.JSONDecoder):
83     """Private class to act as customized JSON decoder.
84
85     Based on: http://stackoverflow.com/questions/10885238/
86     python-change-list-type-for-json-decoding"""
87     def __init__(self, **kwargs):
88         """Initialize decoder with special array implementation."""
89         _json.JSONDecoder.__init__(self, **kwargs)
90         # Use the custom JSONArray
91         self.parse_array = _hsfl_array
92         # Use the python implemenation of the scanner
93         self.scan_once = _json.scanner.py_make_scanner(self)
94
95
96 # Robot Keywords.
97
98
99 def loads_sorted(text, strict=False):
100     """
101     Return Python object with sorted arrays and dictionary keys.
102
103     If strict is true, raise exception on parse error.
104     If strict is not true, return string with error message.
105     """
106     try:
107         object_decoded = _json.loads(text, cls=_Decoder, object_hook=_Hsfod)
108     except ValueError as err:
109         if strict:
110             raise err
111         else:
112             return str(err) + '\n' + text
113     return object_decoded
114
115
116 def dumps_indented(obj, indent=1):
117     """
118     Wrapper for json.dumps with default indentation level. Adds newline.
119
120     The main value is that BuiltIn.Evaluate cannot easily accept Python object
121     as part of its argument.
122     Also, allows to use something different from RequestsLibrary.To_Json
123     """
124     pretty_json = _json.dumps(obj, separators=(',', ': '), indent=indent)
125     return pretty_json + '\n'  # to avoid diff "no newline" warning line
126
127
128 def normalize_json_text(text, strict=False, indent=1):  # pylint likes lowercase
129     """Return sorted indented JSON string, or an error message string."""
130     object_decoded = loads_sorted(text, strict=strict)
131     pretty_json = dumps_indented(object_decoded, indent=indent)
132     return pretty_json