Skip to content

Commit db5a77a

Browse files
authored
Only import numpy/pandas once in JSONSerializer.default()
1 parent 5915676 commit db5a77a

File tree

1 file changed

+95
-52
lines changed

1 file changed

+95
-52
lines changed

elasticsearch/serializer.py

Lines changed: 95 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import uuid
2020
from datetime import date, datetime
2121
from decimal import Decimal
22-
from typing import Any, Dict, Optional
22+
from typing import Any, Dict, Optional, Tuple
2323

2424
from .compat import string_types
2525
from .exceptions import ImproperlyConfigured, SerializationError
@@ -76,57 +76,9 @@ def default(self, data: Any) -> Any:
7676

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

13183
raise TypeError(f"Unable to serialize {data!r} (type: {type(data)})")
13284

@@ -201,3 +153,94 @@ def loads(self, s: str, mimetype: Optional[str] = None) -> Any:
201153
)
202154

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

0 commit comments

Comments
 (0)