1#  Licensed to Elasticsearch B.V. under one or more contributor
2#  license agreements. See the NOTICE file distributed with
3#  this work for additional information regarding copyright
4#  ownership. Elasticsearch B.V. licenses this file to you under
5#  the Apache License, Version 2.0 (the "License"); you may
6#  not use this file except in compliance with the License.
7#  You may obtain a copy of the License at
8#
9# 	http://www.apache.org/licenses/LICENSE-2.0
10#
11#  Unless required by applicable law or agreed to in writing,
12#  software distributed under the License is distributed on an
13#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14#  KIND, either express or implied.  See the License for the
15#  specific language governing permissions and limitations
16#  under the License.
17
18try:
19    import simplejson as json
20except ImportError:
21    import json
22
23import uuid
24from datetime import date, datetime
25from decimal import Decimal
26
27from .exceptions import SerializationError, ImproperlyConfigured
28from .compat import string_types
29
30INTEGER_TYPES = ()
31FLOAT_TYPES = (Decimal,)
32TIME_TYPES = (date, datetime)
33
34try:
35    import numpy as np
36
37    INTEGER_TYPES += (
38        np.int_,
39        np.intc,
40        np.int8,
41        np.int16,
42        np.int32,
43        np.int64,
44        np.uint8,
45        np.uint16,
46        np.uint32,
47        np.uint64,
48    )
49    FLOAT_TYPES += (
50        np.float_,
51        np.float16,
52        np.float32,
53        np.float64,
54    )
55except ImportError:
56    np = None
57
58try:
59    import pandas as pd
60
61    TIME_TYPES += (pd.Timestamp,)
62except ImportError:
63    pd = None
64
65
66class TextSerializer(object):
67    mimetype = "text/plain"
68
69    def loads(self, s):
70        return s
71
72    def dumps(self, data):
73        if isinstance(data, string_types):
74            return data
75
76        raise SerializationError("Cannot serialize %r into text." % data)
77
78
79class JSONSerializer(object):
80    mimetype = "application/json"
81
82    def default(self, data):
83        if isinstance(data, TIME_TYPES):
84            return data.isoformat()
85        elif isinstance(data, uuid.UUID):
86            return str(data)
87        elif isinstance(data, FLOAT_TYPES):
88            return float(data)
89        elif INTEGER_TYPES and isinstance(data, INTEGER_TYPES):
90            return int(data)
91
92        # Special cases for numpy and pandas types
93        elif np:
94            if isinstance(data, np.bool_):
95                return bool(data)
96            elif isinstance(data, np.datetime64):
97                return data.item().isoformat()
98            elif isinstance(data, np.ndarray):
99                return data.tolist()
100        if pd:
101            if isinstance(data, (pd.Series, pd.Categorical)):
102                return data.tolist()
103            elif hasattr(pd, "NA") and pd.isna(data):
104                return None
105
106        raise TypeError("Unable to serialize %r (type: %s)" % (data, type(data)))
107
108    def loads(self, s):
109        try:
110            return json.loads(s)
111        except (ValueError, TypeError) as e:
112            raise SerializationError(s, e)
113
114    def dumps(self, data):
115        # don't serialize strings
116        if isinstance(data, string_types):
117            return data
118
119        try:
120            return json.dumps(
121                data, default=self.default, ensure_ascii=False, separators=(",", ":")
122            )
123        except (ValueError, TypeError) as e:
124            raise SerializationError(data, e)
125
126
127DEFAULT_SERIALIZERS = {
128    JSONSerializer.mimetype: JSONSerializer(),
129    TextSerializer.mimetype: TextSerializer(),
130}
131
132
133class Deserializer(object):
134    def __init__(self, serializers, default_mimetype="application/json"):
135        try:
136            self.default = serializers[default_mimetype]
137        except KeyError:
138            raise ImproperlyConfigured(
139                "Cannot find default serializer (%s)" % default_mimetype
140            )
141        self.serializers = serializers
142
143    def loads(self, s, mimetype=None):
144        if not mimetype:
145            deserializer = self.default
146        else:
147            # split out charset
148            mimetype, _, _ = mimetype.partition(";")
149            try:
150                deserializer = self.serializers[mimetype]
151            except KeyError:
152                raise SerializationError(
153                    "Unknown mimetype, unable to deserialize: %s" % mimetype
154                )
155
156        return deserializer.loads(s)
157