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