10
10
11
11
import sys
12
12
import threading
13
+ from functools import partial
13
14
from inspect import signature
14
15
from typing import TYPE_CHECKING , Callable
15
16
@@ -156,7 +157,7 @@ def accept(*args, **kwargs):
156
157
return decorator
157
158
158
159
159
- def to_jsonable (obj : object ) -> object :
160
+ def to_jsonable (obj : object , * , avoid_realization : bool ) -> object :
160
161
"""Recursively convert an object to json-encodable form.
161
162
162
163
This is not intended to round-trip, but rather provide an analysis-ready
@@ -165,26 +166,30 @@ def to_jsonable(obj: object) -> object:
165
166
"""
166
167
if isinstance (obj , (str , int , float , bool , type (None ))):
167
168
if isinstance (obj , int ) and abs (obj ) >= 2 ** 63 :
168
- # Silently clamp very large ints to max_float, to avoid
169
- # OverflowError when casting to float.
169
+ # Silently clamp very large ints to max_float, to avoid OverflowError when
170
+ # casting to float. (but avoid adding more constraints to symbolic values)
171
+ if avoid_realization :
172
+ return "<symbolic>"
170
173
obj = clamp (- sys .float_info .max , obj , sys .float_info .max )
171
174
return float (obj )
172
175
return obj
176
+ if avoid_realization :
177
+ return "<symbolic>"
178
+ recur = partial (to_jsonable , avoid_realization = avoid_realization )
173
179
if isinstance (obj , (list , tuple , set , frozenset )):
174
180
if isinstance (obj , tuple ) and hasattr (obj , "_asdict" ):
175
- return to_jsonable (obj ._asdict ()) # treat namedtuples as dicts
176
- return [to_jsonable (x ) for x in obj ]
181
+ return recur (obj ._asdict ()) # treat namedtuples as dicts
182
+ return [recur (x ) for x in obj ]
177
183
if isinstance (obj , dict ):
178
184
return {
179
- k if isinstance (k , str ) else pretty (k ): to_jsonable (v )
180
- for k , v in obj .items ()
185
+ k if isinstance (k , str ) else pretty (k ): recur (v ) for k , v in obj .items ()
181
186
}
182
187
183
188
# Hey, might as well try calling a .to_json() method - it works for Pandas!
184
189
# We try this before the below general-purpose handlers to give folks a
185
190
# chance to control this behavior on their custom classes.
186
191
try :
187
- return to_jsonable (obj .to_json ()) # type: ignore
192
+ return recur (obj .to_json ()) # type: ignore
188
193
except Exception :
189
194
pass
190
195
@@ -194,11 +199,11 @@ def to_jsonable(obj: object) -> object:
194
199
and dcs .is_dataclass (obj )
195
200
and not isinstance (obj , type )
196
201
):
197
- return to_jsonable (dataclass_asdict (obj ))
202
+ return recur (dataclass_asdict (obj ))
198
203
if attr .has (type (obj )):
199
- return to_jsonable (attr .asdict (obj , recurse = False )) # type: ignore
204
+ return recur (attr .asdict (obj , recurse = False )) # type: ignore
200
205
if (pyd := sys .modules .get ("pydantic" )) and isinstance (obj , pyd .BaseModel ):
201
- return to_jsonable (obj .model_dump ())
206
+ return recur (obj .model_dump ())
202
207
203
208
# If all else fails, we'll just pretty-print as a string.
204
209
return pretty (obj )
0 commit comments