Skip to content

Commit d8cea24

Browse files
committed
[7.15] Only import numpy/pandas once in JSONSerializer.default()
1 parent 38b136a commit d8cea24

File tree

1 file changed

+94
-51
lines changed

1 file changed

+94
-51
lines changed

elasticsearch/serializer.py

Lines changed: 94 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -79,57 +79,9 @@ def default(self, data):
7979

8080
# Special cases for numpy and pandas types
8181
# These are expensive to import so we try them last.
82-
try:
83-
import numpy as np
84-
85-
if isinstance(
86-
data,
87-
(
88-
np.int_,
89-
np.intc,
90-
np.int8,
91-
np.int16,
92-
np.int32,
93-
np.int64,
94-
np.uint8,
95-
np.uint16,
96-
np.uint32,
97-
np.uint64,
98-
),
99-
):
100-
return int(data)
101-
elif isinstance(
102-
data,
103-
(
104-
np.float_,
105-
np.float16,
106-
np.float32,
107-
np.float64,
108-
),
109-
):
110-
return float(data)
111-
elif isinstance(data, np.bool_):
112-
return bool(data)
113-
elif isinstance(data, np.datetime64):
114-
return data.item().isoformat()
115-
elif isinstance(data, np.ndarray):
116-
return data.tolist()
117-
except ImportError:
118-
pass
119-
120-
try:
121-
import pandas as pd
122-
123-
if isinstance(data, (pd.Series, pd.Categorical)):
124-
return data.tolist()
125-
elif isinstance(data, pd.Timestamp) and data is not getattr(
126-
pd, "NaT", None
127-
):
128-
return data.isoformat()
129-
elif data is getattr(pd, "NA", None):
130-
return None
131-
except ImportError:
132-
pass
82+
serialized, value = _attempt_serialize_numpy_or_pandas(data)
83+
if serialized:
84+
return value
13385

13486
raise TypeError("Unable to serialize %r (type: %s)" % (data, type(data)))
13587

@@ -200,3 +152,94 @@ def loads(self, s, mimetype=None):
200152
)
201153

202154
return deserializer.loads(s)
155+
156+
157+
def _attempt_serialize_numpy_or_pandas(data):
158+
"""Attempts to serialize a value from the numpy or pandas libraries.
159+
This function is separate from JSONSerializer because the inner functions
160+
are rewritten to be no-ops if either library isn't available to avoid
161+
attempting to import and raising an ImportError over and over again.
162+
163+
Returns a tuple of (bool, Any) where the bool corresponds to whether
164+
the second value contains a properly serialized value and thus
165+
should be returned by JSONSerializer.default().
166+
"""
167+
serialized, value = _attempt_serialize_numpy(data)
168+
if serialized:
169+
return serialized, value
170+
171+
serialized, value = _attempt_serialize_pandas(data)
172+
if serialized:
173+
return serialized, value
174+
175+
return False, None
176+
177+
178+
def _attempt_serialize_numpy(data):
179+
global _attempt_serialize_numpy
180+
try:
181+
import numpy as np # type: ignore
182+
183+
if isinstance(
184+
data,
185+
(
186+
np.int_,
187+
np.intc,
188+
np.int8,
189+
np.int16,
190+
np.int32,
191+
np.int64,
192+
np.uint8,
193+
np.uint16,
194+
np.uint32,
195+
np.uint64,
196+
),
197+
):
198+
return True, int(data)
199+
elif isinstance(
200+
data,
201+
(
202+
np.float_,
203+
np.float16,
204+
np.float32,
205+
np.float64,
206+
),
207+
):
208+
return True, float(data)
209+
elif isinstance(data, np.bool_):
210+
return True, bool(data)
211+
elif isinstance(data, np.datetime64):
212+
return True, data.item().isoformat()
213+
elif isinstance(data, np.ndarray):
214+
return True, data.tolist()
215+
216+
except ImportError:
217+
# Since we failed to import 'numpy' we don't want to try again.
218+
_attempt_serialize_numpy = _attempt_serialize_noop
219+
220+
return False, None
221+
222+
223+
def _attempt_serialize_pandas(data):
224+
global _attempt_serialize_pandas
225+
try:
226+
import pandas as pd # type: ignore
227+
228+
if isinstance(data, (pd.Series, pd.Categorical)):
229+
return True, data.tolist()
230+
elif isinstance(data, pd.Timestamp) and data is not getattr(pd, "NaT", None):
231+
return True, data.isoformat()
232+
elif data is getattr(pd, "NA", None):
233+
return True, None
234+
235+
except ImportError:
236+
# Since we failed to import 'pandas' we don't want to try again.
237+
_attempt_serialize_pandas = _attempt_serialize_noop
238+
239+
return False, None
240+
241+
242+
def _attempt_serialize_noop(data): # noqa
243+
# Short-circuit if the above functions can't import
244+
# the corresponding library on the first attempt.
245+
return False, None

0 commit comments

Comments
 (0)