prediction-market-agent-tooling 0.65.5__py3-none-any.whl → 0.69.17.dev1149__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. prediction_market_agent_tooling/abis/agentresultmapping.abi.json +192 -0
  2. prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
  3. prediction_market_agent_tooling/abis/processor.abi.json +16 -0
  4. prediction_market_agent_tooling/abis/swapr_quoter.abi.json +221 -0
  5. prediction_market_agent_tooling/abis/swapr_router.abi.json +634 -0
  6. prediction_market_agent_tooling/benchmark/benchmark.py +1 -1
  7. prediction_market_agent_tooling/benchmark/utils.py +13 -0
  8. prediction_market_agent_tooling/chains.py +1 -0
  9. prediction_market_agent_tooling/config.py +61 -2
  10. prediction_market_agent_tooling/data_download/langfuse_data_downloader.py +405 -0
  11. prediction_market_agent_tooling/deploy/agent.py +199 -67
  12. prediction_market_agent_tooling/deploy/agent_example.py +1 -1
  13. prediction_market_agent_tooling/deploy/betting_strategy.py +412 -68
  14. prediction_market_agent_tooling/deploy/constants.py +6 -0
  15. prediction_market_agent_tooling/gtypes.py +11 -1
  16. prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
  17. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +19 -20
  18. prediction_market_agent_tooling/loggers.py +9 -1
  19. prediction_market_agent_tooling/logprobs_parser.py +2 -1
  20. prediction_market_agent_tooling/markets/agent_market.py +106 -18
  21. prediction_market_agent_tooling/markets/blockchain_utils.py +37 -19
  22. prediction_market_agent_tooling/markets/data_models.py +120 -7
  23. prediction_market_agent_tooling/markets/manifold/data_models.py +5 -3
  24. prediction_market_agent_tooling/markets/manifold/manifold.py +21 -2
  25. prediction_market_agent_tooling/markets/manifold/utils.py +8 -2
  26. prediction_market_agent_tooling/markets/market_type.py +74 -0
  27. prediction_market_agent_tooling/markets/markets.py +7 -99
  28. prediction_market_agent_tooling/markets/metaculus/data_models.py +3 -3
  29. prediction_market_agent_tooling/markets/metaculus/metaculus.py +5 -8
  30. prediction_market_agent_tooling/markets/omen/cow_contracts.py +5 -1
  31. prediction_market_agent_tooling/markets/omen/data_models.py +63 -32
  32. prediction_market_agent_tooling/markets/omen/omen.py +112 -23
  33. prediction_market_agent_tooling/markets/omen/omen_constants.py +8 -0
  34. prediction_market_agent_tooling/markets/omen/omen_contracts.py +18 -203
  35. prediction_market_agent_tooling/markets/omen/omen_resolving.py +33 -13
  36. prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +23 -18
  37. prediction_market_agent_tooling/markets/polymarket/api.py +123 -100
  38. prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
  39. prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
  40. prediction_market_agent_tooling/markets/polymarket/data_models.py +95 -19
  41. prediction_market_agent_tooling/markets/polymarket/polymarket.py +373 -29
  42. prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
  43. prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +91 -0
  44. prediction_market_agent_tooling/markets/polymarket/utils.py +1 -22
  45. prediction_market_agent_tooling/markets/seer/data_models.py +111 -17
  46. prediction_market_agent_tooling/markets/seer/exceptions.py +2 -0
  47. prediction_market_agent_tooling/markets/seer/price_manager.py +165 -50
  48. prediction_market_agent_tooling/markets/seer/seer.py +393 -106
  49. prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
  50. prediction_market_agent_tooling/markets/seer/seer_contracts.py +115 -5
  51. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +297 -66
  52. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +43 -8
  53. prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +80 -0
  54. prediction_market_agent_tooling/tools/_generic_value.py +8 -2
  55. prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +271 -8
  56. prediction_market_agent_tooling/tools/betting_strategies/utils.py +6 -1
  57. prediction_market_agent_tooling/tools/caches/db_cache.py +219 -117
  58. prediction_market_agent_tooling/tools/caches/serializers.py +11 -2
  59. prediction_market_agent_tooling/tools/contract.py +480 -38
  60. prediction_market_agent_tooling/tools/contract_utils.py +61 -0
  61. prediction_market_agent_tooling/tools/cow/cow_order.py +218 -45
  62. prediction_market_agent_tooling/tools/cow/models.py +122 -0
  63. prediction_market_agent_tooling/tools/cow/semaphore.py +104 -0
  64. prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
  65. prediction_market_agent_tooling/tools/db/db_manager.py +59 -0
  66. prediction_market_agent_tooling/tools/hexbytes_custom.py +4 -1
  67. prediction_market_agent_tooling/tools/httpx_cached_client.py +15 -6
  68. prediction_market_agent_tooling/tools/langfuse_client_utils.py +21 -8
  69. prediction_market_agent_tooling/tools/openai_utils.py +31 -0
  70. prediction_market_agent_tooling/tools/perplexity/perplexity_client.py +86 -0
  71. prediction_market_agent_tooling/tools/perplexity/perplexity_models.py +26 -0
  72. prediction_market_agent_tooling/tools/perplexity/perplexity_search.py +73 -0
  73. prediction_market_agent_tooling/tools/rephrase.py +71 -0
  74. prediction_market_agent_tooling/tools/singleton.py +11 -6
  75. prediction_market_agent_tooling/tools/streamlit_utils.py +188 -0
  76. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +64 -0
  77. prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +8 -0
  78. prediction_market_agent_tooling/tools/tokens/slippage.py +21 -0
  79. prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
  80. prediction_market_agent_tooling/tools/utils.py +61 -3
  81. prediction_market_agent_tooling/tools/web3_utils.py +63 -9
  82. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/METADATA +13 -9
  83. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/RECORD +86 -64
  84. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/WHEEL +1 -1
  85. prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json +0 -171
  86. prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -420
  87. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/entry_points.txt +0 -0
  88. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info/licenses}/LICENSE +0 -0
@@ -1,6 +1,8 @@
1
+ import asyncio
1
2
  import hashlib
2
3
  import inspect
3
4
  import json
5
+ from dataclasses import dataclass
4
6
  from datetime import timedelta
5
7
  from functools import wraps
6
8
  from types import UnionType
@@ -12,6 +14,7 @@ from typing import (
12
14
  cast,
13
15
  get_args,
14
16
  get_origin,
17
+ get_type_hints,
15
18
  overload,
16
19
  )
17
20
 
@@ -25,7 +28,10 @@ from sqlmodel import Field, SQLModel, desc, select
25
28
  from prediction_market_agent_tooling.config import APIKeys
26
29
  from prediction_market_agent_tooling.loggers import logger
27
30
  from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
28
- from prediction_market_agent_tooling.tools.db.db_manager import DBManager
31
+ from prediction_market_agent_tooling.tools.db.db_manager import (
32
+ DBManager,
33
+ EnsureTableManager,
34
+ )
29
35
  from prediction_market_agent_tooling.tools.utils import utcnow
30
36
 
31
37
  DB_CACHE_LOG_PREFIX = "[db-cache]"
@@ -46,6 +52,10 @@ class FunctionCache(SQLModel, table=True):
46
52
  created_at: DatetimeUTC = Field(default_factory=utcnow, index=True)
47
53
 
48
54
 
55
+ # Global instance of the table manager for FunctionCache
56
+ _table_manager = EnsureTableManager([FunctionCache])
57
+
58
+
49
59
  @overload
50
60
  def db_cache(
51
61
  func: None = None,
@@ -101,136 +111,228 @@ def db_cache(
101
111
 
102
112
  api_keys = api_keys if api_keys is not None else APIKeys()
103
113
 
104
- @wraps(func)
105
- def wrapper(*args: Any, **kwargs: Any) -> Any:
106
- # If caching is disabled, just call the function and return it
107
- if not api_keys.ENABLE_CACHE:
108
- return func(*args, **kwargs)
109
-
110
- DBManager(api_keys.sqlalchemy_db_url.get_secret_value()).create_tables(
111
- [FunctionCache]
112
- )
114
+ # Check if the decorated function is async
115
+ if inspect.iscoroutinefunction(func):
113
116
 
114
- # Convert *args and **kwargs to a single dictionary, where we have names for arguments passed as args as well.
115
- signature = inspect.signature(func)
116
- bound_arguments = signature.bind(*args, **kwargs)
117
- bound_arguments.apply_defaults()
118
-
119
- # Convert any argument that is Pydantic model into classic dictionary, otherwise it won't be json-serializable.
120
- args_dict: dict[str, Any] = bound_arguments.arguments
121
-
122
- # Remove `self` or `cls` if present (in case of class' methods)
123
- if "self" in args_dict:
124
- del args_dict["self"]
125
- if "cls" in args_dict:
126
- del args_dict["cls"]
127
-
128
- # Remove ignored arguments
129
- if ignore_args:
130
- for arg in ignore_args:
131
- if arg in args_dict:
132
- del args_dict[arg]
133
-
134
- # Remove arguments of ignored types
135
- if ignore_arg_types:
136
- args_dict = {
137
- k: v
138
- for k, v in args_dict.items()
139
- if not isinstance(v, tuple(ignore_arg_types))
140
- }
117
+ @wraps(func)
118
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
119
+ # If caching is disabled, just call the function and return it
120
+ if not api_keys.ENABLE_CACHE:
121
+ return await func(*args, **kwargs)
141
122
 
142
- # Compute a hash of the function arguments used for lookup of cached results
143
- arg_string = json.dumps(args_dict, sort_keys=True, default=str)
144
- args_hash = hashlib.md5(arg_string.encode()).hexdigest()
123
+ # Ensure tables are created before accessing cache
124
+ await _table_manager.ensure_tables_async(api_keys)
145
125
 
146
- # Get the full function name as concat of module and qualname, to not accidentally clash
147
- full_function_name = func.__module__ + "." + func.__qualname__
148
- # But also get the standard function name to easily search for it in database
149
- function_name = func.__name__
126
+ ctx = _build_context(func, args, kwargs, ignore_args, ignore_arg_types)
150
127
 
151
- # Determine if the function returns or contains Pydantic BaseModel(s)
152
- return_type = func.__annotations__.get("return", None)
153
- is_pydantic_model = return_type is not None and contains_pydantic_model(
154
- return_type
155
- )
128
+ # Fetch cached result in thread pool
129
+ lookup = await asyncio.to_thread(_fetch_cached, api_keys, ctx, max_age)
156
130
 
157
- with DBManager(
158
- api_keys.sqlalchemy_db_url.get_secret_value()
159
- ).get_session() as session:
160
- # Try to get cached result
161
- statement = (
162
- select(FunctionCache)
163
- .where(
164
- FunctionCache.function_name == function_name,
165
- FunctionCache.full_function_name == full_function_name,
166
- FunctionCache.args_hash == args_hash,
131
+ if lookup.hit:
132
+ logger.debug(
133
+ f"{DB_CACHE_LOG_PREFIX} [cache-hit] Cache hit for {ctx.full_function_name}"
167
134
  )
168
- .order_by(desc(FunctionCache.created_at))
169
- )
170
- if max_age is not None:
171
- cutoff_time = utcnow() - max_age
172
- statement = statement.where(FunctionCache.created_at >= cutoff_time)
173
- cached_result = session.exec(statement).first()
174
-
175
- if cached_result:
176
- logger.info(
177
- # Keep the special [case-hit] identifier so we can easily track it in GCP.
178
- f"{DB_CACHE_LOG_PREFIX} [cache-hit] Cache hit for {full_function_name} with args {args_dict} and output {cached_result.result}"
135
+ return lookup.value
136
+
137
+ computed_result = await func(*args, **kwargs)
138
+ logger.debug(
139
+ f"{DB_CACHE_LOG_PREFIX} [cache-miss] Cache miss for {ctx.full_function_name}"
179
140
  )
180
- if is_pydantic_model:
181
- # If the output contains any Pydantic models, we need to initialise them.
182
- try:
183
- return convert_cached_output_to_pydantic(
184
- return_type, cached_result.result
185
- )
186
- except ValueError as e:
187
- # In case of backward-incompatible pydantic model, just treat it as cache miss, to not error out.
188
- logger.warning(
189
- f"{DB_CACHE_LOG_PREFIX} [cache-miss] Can not validate {cached_result=} into {return_type=} because {e=}, treating as cache miss."
141
+
142
+ if cache_none or computed_result is not None:
143
+ # Save cached result in thread pool (fire-and-forget)
144
+ asyncio.create_task(
145
+ asyncio.to_thread(
146
+ _save_cached,
147
+ api_keys,
148
+ ctx,
149
+ computed_result,
150
+ log_error_on_unsavable_data,
190
151
  )
191
- cached_result = None
192
- else:
193
- return cached_result.result
152
+ )
153
+
154
+ return computed_result
155
+
156
+ return cast(FunctionT, async_wrapper)
157
+
158
+ @wraps(func)
159
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
160
+ if not api_keys.ENABLE_CACHE:
161
+ return func(*args, **kwargs)
162
+
163
+ # Ensure tables are created before accessing cache
164
+ _table_manager.ensure_tables_sync(api_keys)
165
+
166
+ ctx = _build_context(func, args, kwargs, ignore_args, ignore_arg_types)
167
+ lookup = _fetch_cached(api_keys, ctx, max_age)
168
+
169
+ if lookup.hit:
170
+ logger.debug(
171
+ f"{DB_CACHE_LOG_PREFIX} [cache-hit] Cache hit for {ctx.full_function_name}"
172
+ )
173
+ return lookup.value
194
174
 
195
- # On cache miss, compute the result
196
175
  computed_result = func(*args, **kwargs)
197
- # Keep the special [case-miss] identifier so we can easily track it in GCP.
198
- logger.info(
199
- f"{DB_CACHE_LOG_PREFIX} [cache-miss] Cache miss for {full_function_name} with args {args_dict}, computed the output {computed_result}"
176
+ logger.debug(
177
+ f"{DB_CACHE_LOG_PREFIX} [cache-miss] Cache miss for {ctx.full_function_name}"
200
178
  )
201
179
 
202
- # If postgres access was specified, save it.
203
180
  if cache_none or computed_result is not None:
204
- cache_entry = FunctionCache(
205
- function_name=function_name,
206
- full_function_name=full_function_name,
207
- args_hash=args_hash,
208
- args=args_dict,
209
- result=computed_result,
210
- created_at=utcnow(),
211
- )
212
- # Do not raise an exception if saving to the database fails, just log it and let the agent continue the work.
213
- try:
214
- with DBManager(
215
- api_keys.sqlalchemy_db_url.get_secret_value()
216
- ).get_session() as session:
217
- logger.info(
218
- f"{DB_CACHE_LOG_PREFIX} [cache-info] Saving {cache_entry} into database."
219
- )
220
- session.add(cache_entry)
221
- session.commit()
222
- except (DataError, psycopg2.errors.UntranslatableCharacter) as e:
223
- (logger.error if log_error_on_unsavable_data else logger.warning)(
224
- f"{DB_CACHE_LOG_PREFIX} [cache-error] Failed to save {cache_entry} into database, ignoring, because: {e}"
225
- )
226
- except Exception:
227
- logger.exception(
228
- f"{DB_CACHE_LOG_PREFIX} [cache-error] Failed to save {cache_entry} into database, ignoring."
229
- )
181
+ _save_cached(api_keys, ctx, computed_result, log_error_on_unsavable_data)
230
182
 
231
183
  return computed_result
232
184
 
233
- return cast(FunctionT, wrapper)
185
+ return cast(FunctionT, sync_wrapper)
186
+
187
+
188
+ @dataclass
189
+ class CallContext:
190
+ args_dict: dict[str, Any]
191
+ args_hash: str
192
+ function_name: str
193
+ full_function_name: str
194
+ return_type: Any
195
+
196
+ @property
197
+ def is_pydantic_model(self) -> bool:
198
+ return self.return_type is not None and contains_pydantic_model(
199
+ self.return_type
200
+ )
201
+
202
+
203
+ @dataclass
204
+ class CacheLookup:
205
+ hit: bool
206
+ value: Any | None = None
207
+
208
+
209
+ def _build_context(
210
+ func: Callable[..., Any],
211
+ args: tuple[Any, ...],
212
+ kwargs: dict[str, Any],
213
+ ignore_args: Sequence[str] | None,
214
+ ignore_arg_types: Sequence[type] | None,
215
+ ) -> CallContext:
216
+ signature = inspect.signature(func)
217
+ bound_arguments = signature.bind(*args, **kwargs)
218
+ bound_arguments.apply_defaults()
219
+
220
+ args_dict: dict[str, Any] = bound_arguments.arguments
221
+
222
+ if "self" in args_dict:
223
+ del args_dict["self"]
224
+ if "cls" in args_dict:
225
+ del args_dict["cls"]
226
+
227
+ if ignore_args:
228
+ for arg in ignore_args:
229
+ if arg in args_dict:
230
+ del args_dict[arg]
231
+
232
+ if ignore_arg_types:
233
+ args_dict = {
234
+ k: v
235
+ for k, v in args_dict.items()
236
+ if not isinstance(v, tuple(ignore_arg_types))
237
+ }
238
+
239
+ arg_string = json.dumps(args_dict, sort_keys=True, default=str)
240
+ args_hash = hashlib.md5(arg_string.encode()).hexdigest()
241
+
242
+ full_function_name = func.__module__ + "." + func.__qualname__
243
+ function_name = func.__name__
244
+
245
+ # Use get_type_hints to resolve forward references instead of __annotations__
246
+ try:
247
+ type_hints = get_type_hints(func)
248
+ return_type = type_hints.get("return", None)
249
+ except (NameError, AttributeError, TypeError) as e:
250
+ # Fallback to raw annotations if get_type_hints fails
251
+ logger.debug(
252
+ f"{DB_CACHE_LOG_PREFIX} Failed to resolve type hints for {full_function_name}, falling back to raw annotations: {e}"
253
+ )
254
+ return_type = func.__annotations__.get("return", None)
255
+
256
+ return CallContext(
257
+ args_dict=args_dict,
258
+ args_hash=args_hash,
259
+ function_name=function_name,
260
+ full_function_name=full_function_name,
261
+ return_type=return_type,
262
+ )
263
+
264
+
265
+ def _fetch_cached(
266
+ api_keys: APIKeys,
267
+ ctx: CallContext,
268
+ max_age: timedelta | None,
269
+ ) -> CacheLookup:
270
+ with DBManager(
271
+ api_keys.sqlalchemy_db_url.get_secret_value()
272
+ ).get_session() as session:
273
+ statement = (
274
+ select(FunctionCache)
275
+ .where(
276
+ FunctionCache.function_name == ctx.function_name,
277
+ FunctionCache.full_function_name == ctx.full_function_name,
278
+ FunctionCache.args_hash == ctx.args_hash,
279
+ )
280
+ .order_by(desc(FunctionCache.created_at))
281
+ )
282
+ if max_age is not None:
283
+ cutoff_time = utcnow() - max_age
284
+ statement = statement.where(FunctionCache.created_at >= cutoff_time)
285
+ cached_result = session.exec(statement).first()
286
+
287
+ if not cached_result:
288
+ return CacheLookup(hit=False)
289
+
290
+ if ctx.is_pydantic_model:
291
+ try:
292
+ value = convert_cached_output_to_pydantic(
293
+ ctx.return_type, cached_result.result
294
+ )
295
+ return CacheLookup(hit=True, value=value)
296
+ except (ValueError, TypeError) as e:
297
+ logger.warning(
298
+ f"{DB_CACHE_LOG_PREFIX} [cache-miss] Failed to validate cached result for {ctx.full_function_name}, treating as cache miss: {e}"
299
+ )
300
+ return CacheLookup(hit=False)
301
+
302
+ return CacheLookup(hit=True, value=cached_result.result)
303
+
304
+
305
+ def _save_cached(
306
+ api_keys: APIKeys,
307
+ ctx: CallContext,
308
+ computed_result: Any,
309
+ log_error_on_unsavable_data: bool,
310
+ ) -> None:
311
+ cache_entry = FunctionCache(
312
+ function_name=ctx.function_name,
313
+ full_function_name=ctx.full_function_name,
314
+ args_hash=ctx.args_hash,
315
+ args=ctx.args_dict,
316
+ result=computed_result,
317
+ created_at=utcnow(),
318
+ )
319
+ try:
320
+ with DBManager(
321
+ api_keys.sqlalchemy_db_url.get_secret_value()
322
+ ).get_session() as session:
323
+ logger.debug(
324
+ f"{DB_CACHE_LOG_PREFIX} [cache-save] Saving cache entry for {ctx.full_function_name}"
325
+ )
326
+ session.add(cache_entry)
327
+ session.commit()
328
+ except (DataError, psycopg2.errors.UntranslatableCharacter) as e:
329
+ (logger.error if log_error_on_unsavable_data else logger.warning)(
330
+ f"{DB_CACHE_LOG_PREFIX} [cache-error] Failed to save cache entry for {ctx.full_function_name}: {e}"
331
+ )
332
+ except Exception:
333
+ logger.exception(
334
+ f"{DB_CACHE_LOG_PREFIX} [cache-error] Failed to save cache entry for {ctx.full_function_name}"
335
+ )
234
336
 
235
337
 
236
338
  def contains_pydantic_model(return_type: Any) -> bool:
@@ -261,8 +363,8 @@ def convert_cached_output_to_pydantic(return_type: Any, data: Any) -> Any:
261
363
  if origin is None:
262
364
  if inspect.isclass(return_type) and issubclass(return_type, BaseModel):
263
365
  # Convert the dictionary to a Pydantic model
264
- return return_type(
265
- **{
366
+ return return_type.model_validate(
367
+ {
266
368
  k: convert_cached_output_to_pydantic(
267
369
  getattr(return_type, k, None), v
268
370
  )
@@ -4,6 +4,7 @@ from datetime import date, timedelta
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
+ from prediction_market_agent_tooling.gtypes import HexBytes
7
8
  from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
8
9
 
9
10
 
@@ -12,7 +13,7 @@ def json_serializer(x: t.Any) -> str:
12
13
 
13
14
 
14
15
  def json_serializer_default_fn(
15
- y: DatetimeUTC | timedelta | date | BaseModel,
16
+ y: DatetimeUTC | timedelta | date | HexBytes | BaseModel,
16
17
  ) -> str | dict[str, t.Any]:
17
18
  """
18
19
  Used to serialize objects that don't support it by default into a specific string that can be deserialized out later.
@@ -25,8 +26,13 @@ def json_serializer_default_fn(
25
26
  return f"timedelta::{y.total_seconds()}"
26
27
  elif isinstance(y, date):
27
28
  return f"date::{y.isoformat()}"
29
+ elif isinstance(y, HexBytes):
30
+ return f"HexBytes::{y.to_0x_hex()}"
28
31
  elif isinstance(y, BaseModel):
29
- return y.model_dump()
32
+ # For some reason, Pydantic by default serializes using the field names (not alias),
33
+ # but also by default, deserializes only using the aliased names.
34
+ # `by_alias=True` here to work by default with models that have some fields with aliased names.
35
+ return y.model_dump(by_alias=True)
30
36
  raise TypeError(
31
37
  f"Unsupported type for the default json serialize function, value is {y}."
32
38
  )
@@ -51,6 +57,9 @@ def replace_custom_stringified_objects(obj: t.Any) -> t.Any:
51
57
  elif obj.startswith("date::"):
52
58
  iso_str = obj[len("date::") :]
53
59
  return date.fromisoformat(iso_str)
60
+ elif obj.startswith("HexBytes::"):
61
+ hex_str = obj[len("HexBytes::") :]
62
+ return HexBytes(hex_str)
54
63
  else:
55
64
  return obj
56
65
  elif isinstance(obj, dict):