4949import inspect
5050import json
5151import math
52+ import sys
5253import traceback
5354import uuid
5455from pathlib import Path , PurePath
@@ -314,33 +315,33 @@ def module_available(module_name):
314315
315316
316317def is_numpy_array (obj ):
317- try :
318- import numpy as np # noqa: F401
319- except Exception :
318+ """Return whether obj is an ndarray without importing NumPy."""
319+ np = sys . modules . get ( 'numpy' )
320+ if np is None :
320321 return False
321322 return isinstance (obj , np .ndarray )
322323
323324
324325def is_pandas_dataframe (obj ):
325- try :
326- import pandas as pd # noqa: F401
327- except Exception :
326+ """Return whether obj is a DataFrame without importing pandas."""
327+ pd = sys . modules . get ( 'pandas' )
328+ if pd is None :
328329 return False
329330 return isinstance (obj , pd .DataFrame )
330331
331332
332333def is_pandas_series (obj ):
333- try :
334- import pandas as pd # noqa: F401
335- except Exception :
334+ """Return whether obj is a Series without importing pandas."""
335+ pd = sys . modules . get ( 'pandas' )
336+ if pd is None :
336337 return False
337338 return isinstance (obj , pd .Series )
338339
339340
340341def is_scipy_sparse (obj ):
341- try :
342- import scipy .sparse as sp # noqa: F401
343- except Exception :
342+ """Return whether obj is sparse without importing SciPy."""
343+ sp = sys . modules . get ( ' scipy.sparse' )
344+ if sp is None :
344345 return False
345346 try :
346347 return sp .issparse (obj )
@@ -349,9 +350,9 @@ def is_scipy_sparse(obj):
349350
350351
351352def is_torch_tensor (obj ):
352- try :
353- import torch # noqa: F401
354- except Exception :
353+ """Return whether obj is a Tensor without importing PyTorch."""
354+ torch = sys . modules . get ( 'torch' )
355+ if torch is None :
355356 return False
356357 try :
357358 return torch .is_tensor (obj )
@@ -360,11 +361,11 @@ def is_torch_tensor(obj):
360361
361362
362363def is_sklearn_estimator (obj ):
363- try :
364- from sklearn .base import BaseEstimator # noqa: F401
365- except Exception :
364+ """Return whether obj is an estimator without importing scikit-learn."""
365+ sklearn_base = sys . modules . get ( ' sklearn.base' )
366+ if sklearn_base is None :
366367 return False
367- return isinstance (obj , BaseEstimator )
368+ return isinstance (obj , sklearn_base . BaseEstimator )
368369
369370
370371# =============================================================================
@@ -748,26 +749,42 @@ def serialize_stdlib(obj):
748749
749750def serialize (obj , * , force_json_markers , torch_allow_copy = False ):
750751 """
751- Top-level result serializer. Dispatch order is significant: numpy ndarray ->
752- dataframe -> series -> scipy.sparse -> torch -> sklearn -> Pydantic -> stdlib
753- -> passthrough. The remaining BridgeCodec value behaviors (numpy/pandas scalars,
754- bytes, sets, complex rejection, NaN/Infinity) are applied later during JSON
755- encoding by default_encoder.
752+ Top-level result serializer.
753+
754+ Scientific codecs are type-first and only inspect packages that the value can
755+ belong to. A value from an optional package implies that package is already in
756+ sys.modules, so these checks never cold-import the scientific stack. The
757+ package dispatch deliberately precedes the JSON-native fast path: e.g. a
758+ package-defined subclass of dict still receives its relevant codec check.
759+ The remaining BridgeCodec value behaviors (numpy/pandas scalars, bytes, sets,
760+ complex rejection, NaN/Infinity) are applied later during JSON encoding by
761+ default_encoder.
756762 """
757- if is_numpy_array (obj ):
758- return serialize_ndarray (obj , force_json_markers = force_json_markers )
759- if is_pandas_dataframe (obj ):
760- return serialize_dataframe (obj , force_json_markers = force_json_markers )
761- if is_pandas_series (obj ):
762- return serialize_series (obj , force_json_markers = force_json_markers )
763- if is_scipy_sparse (obj ):
764- return serialize_sparse_matrix (obj )
765- if is_torch_tensor (obj ):
766- return serialize_torch_tensor (
767- obj , force_json_markers = force_json_markers , torch_allow_copy = torch_allow_copy
768- )
769- if is_sklearn_estimator (obj ):
770- return serialize_sklearn_estimator (obj )
763+ package = type (obj ).__module__ .split ('.' , 1 )[0 ]
764+
765+ if package == 'numpy' and 'numpy' in sys .modules :
766+ if is_numpy_array (obj ):
767+ return serialize_ndarray (obj , force_json_markers = force_json_markers )
768+ elif package == 'pandas' and 'pandas' in sys .modules :
769+ if is_pandas_dataframe (obj ):
770+ return serialize_dataframe (obj , force_json_markers = force_json_markers )
771+ if is_pandas_series (obj ):
772+ return serialize_series (obj , force_json_markers = force_json_markers )
773+ elif package == 'scipy' and 'scipy.sparse' in sys .modules :
774+ if is_scipy_sparse (obj ):
775+ return serialize_sparse_matrix (obj )
776+ elif package == 'torch' and 'torch' in sys .modules :
777+ if is_torch_tensor (obj ):
778+ return serialize_torch_tensor (
779+ obj , force_json_markers = force_json_markers , torch_allow_copy = torch_allow_copy
780+ )
781+ elif package == 'sklearn' and 'sklearn.base' in sys .modules :
782+ if is_sklearn_estimator (obj ):
783+ return serialize_sklearn_estimator (obj )
784+
785+ if isinstance (obj , (type (None ), bool , int , float , str , dict , list , tuple )):
786+ return obj
787+
771788 pydantic_value = serialize_pydantic (obj )
772789 if pydantic_value is not _NO_PYDANTIC :
773790 return pydantic_value
0 commit comments