coco-framework 0.1.0__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 (400) hide show
  1. coco/__init__.py +3 -0
  2. coco/agents/__init__.py +9 -0
  3. coco/agents/_subagent_transformer.py +307 -0
  4. coco/agents/factory.py +2164 -0
  5. coco/agents/middleware/__init__.py +100 -0
  6. coco/agents/middleware/_execution.py +385 -0
  7. coco/agents/middleware/_redaction.py +454 -0
  8. coco/agents/middleware/_retry.py +134 -0
  9. coco/agents/middleware/_trace_policy.py +61 -0
  10. coco/agents/middleware/context_editing.py +306 -0
  11. coco/agents/middleware/file_search.py +433 -0
  12. coco/agents/middleware/human_in_the_loop.py +680 -0
  13. coco/agents/middleware/internal_call_transformer.py +108 -0
  14. coco/agents/middleware/model_call_limit.py +267 -0
  15. coco/agents/middleware/model_fallback.py +417 -0
  16. coco/agents/middleware/model_retry.py +327 -0
  17. coco/agents/middleware/pii.py +878 -0
  18. coco/agents/middleware/provider_tool_search.py +307 -0
  19. coco/agents/middleware/shell_tool.py +949 -0
  20. coco/agents/middleware/summarization.py +910 -0
  21. coco/agents/middleware/todo.py +357 -0
  22. coco/agents/middleware/tool_call_limit.py +495 -0
  23. coco/agents/middleware/tool_emulator.py +251 -0
  24. coco/agents/middleware/tool_error.py +209 -0
  25. coco/agents/middleware/tool_retry.py +421 -0
  26. coco/agents/middleware/tool_selection.py +515 -0
  27. coco/agents/middleware/types.py +2201 -0
  28. coco/agents/structured_output.py +472 -0
  29. coco/chat_models/__init__.py +7 -0
  30. coco/chat_models/base.py +1082 -0
  31. coco/embeddings/__init__.py +18 -0
  32. coco/embeddings/base.py +279 -0
  33. coco/mcp/__init__.py +36 -0
  34. coco/mcp/adapter.py +245 -0
  35. coco/mcp/elicitation.py +394 -0
  36. coco/mcp/tools.py +307 -0
  37. coco/messages/__init__.py +73 -0
  38. coco/py.typed +0 -0
  39. coco/rate_limiters/__init__.py +13 -0
  40. coco/tools/__init__.py +22 -0
  41. coco/tools/tool_node.py +20 -0
  42. coco_anthropic/__init__.py +15 -0
  43. coco_anthropic/_client_utils.py +81 -0
  44. coco_anthropic/_compat.py +228 -0
  45. coco_anthropic/_sdk_compat.py +124 -0
  46. coco_anthropic/_version.py +3 -0
  47. coco_anthropic/chat_models.py +3349 -0
  48. coco_anthropic/data/__init__.py +1 -0
  49. coco_anthropic/data/_profiles.py +491 -0
  50. coco_anthropic/data/profile_augmentations.toml +69 -0
  51. coco_anthropic/experimental.py +140 -0
  52. coco_anthropic/llms.py +428 -0
  53. coco_anthropic/middleware/__init__.py +25 -0
  54. coco_anthropic/middleware/anthropic_tools.py +1197 -0
  55. coco_anthropic/middleware/bash.py +84 -0
  56. coco_anthropic/middleware/file_search.py +360 -0
  57. coco_anthropic/middleware/prompt_caching.py +276 -0
  58. coco_anthropic/output_parsers.py +93 -0
  59. coco_anthropic/py.typed +0 -0
  60. coco_core/__init__.py +20 -0
  61. coco_core/_api/__init__.py +87 -0
  62. coco_core/_api/beta_decorator.py +266 -0
  63. coco_core/_api/deprecation.py +637 -0
  64. coco_core/_api/internal.py +23 -0
  65. coco_core/_api/path.py +50 -0
  66. coco_core/_import_utils.py +41 -0
  67. coco_core/_protocol.py +625 -0
  68. coco_core/_security/__init__.py +36 -0
  69. coco_core/_security/_exceptions.py +9 -0
  70. coco_core/_security/_policy.py +313 -0
  71. coco_core/_security/_ssrf_protection.py +155 -0
  72. coco_core/_security/_transport.py +254 -0
  73. coco_core/agents.py +258 -0
  74. coco_core/caches.py +272 -0
  75. coco_core/callbacks/__init__.py +132 -0
  76. coco_core/callbacks/base.py +1229 -0
  77. coco_core/callbacks/file.py +266 -0
  78. coco_core/callbacks/manager.py +2826 -0
  79. coco_core/callbacks/stdout.py +123 -0
  80. coco_core/callbacks/streaming_stdout.py +154 -0
  81. coco_core/callbacks/usage.py +124 -0
  82. coco_core/chat_history.py +267 -0
  83. coco_core/chat_loaders.py +26 -0
  84. coco_core/chat_sessions.py +19 -0
  85. coco_core/cross_encoders.py +18 -0
  86. coco_core/document_loaders/__init__.py +36 -0
  87. coco_core/document_loaders/base.py +155 -0
  88. coco_core/document_loaders/blob_loaders.py +38 -0
  89. coco_core/documents/__init__.py +55 -0
  90. coco_core/documents/base.py +347 -0
  91. coco_core/documents/compressor.py +74 -0
  92. coco_core/documents/transformers.py +79 -0
  93. coco_core/embeddings/__init__.py +31 -0
  94. coco_core/embeddings/embeddings.py +78 -0
  95. coco_core/embeddings/fake.py +129 -0
  96. coco_core/env.py +22 -0
  97. coco_core/example_selectors/__init__.py +47 -0
  98. coco_core/example_selectors/base.py +60 -0
  99. coco_core/example_selectors/length_based.py +131 -0
  100. coco_core/example_selectors/semantic_similarity.py +366 -0
  101. coco_core/exceptions.py +166 -0
  102. coco_core/globals.py +72 -0
  103. coco_core/indexing/__init__.py +53 -0
  104. coco_core/indexing/api.py +954 -0
  105. coco_core/indexing/base.py +661 -0
  106. coco_core/indexing/in_memory.py +104 -0
  107. coco_core/language_models/__init__.py +116 -0
  108. coco_core/language_models/_compat_bridge.py +844 -0
  109. coco_core/language_models/_utils.py +343 -0
  110. coco_core/language_models/base.py +500 -0
  111. coco_core/language_models/chat_model_stream.py +1525 -0
  112. coco_core/language_models/chat_models.py +2748 -0
  113. coco_core/language_models/fake.py +137 -0
  114. coco_core/language_models/fake_chat_models.py +396 -0
  115. coco_core/language_models/llms.py +1569 -0
  116. coco_core/language_models/model_profile.py +183 -0
  117. coco_core/load/__init__.py +44 -0
  118. coco_core/load/_validation.py +191 -0
  119. coco_core/load/dump.py +120 -0
  120. coco_core/load/load.py +828 -0
  121. coco_core/load/mapping.py +1097 -0
  122. coco_core/load/serializable.py +406 -0
  123. coco_core/messages/__init__.py +198 -0
  124. coco_core/messages/ai.py +854 -0
  125. coco_core/messages/base.py +518 -0
  126. coco_core/messages/block_translators/__init__.py +112 -0
  127. coco_core/messages/block_translators/anthropic.py +534 -0
  128. coco_core/messages/block_translators/bedrock.py +108 -0
  129. coco_core/messages/block_translators/bedrock_converse.py +329 -0
  130. coco_core/messages/block_translators/coco_v0.py +303 -0
  131. coco_core/messages/block_translators/google_genai.py +610 -0
  132. coco_core/messages/block_translators/google_vertexai.py +21 -0
  133. coco_core/messages/block_translators/groq.py +157 -0
  134. coco_core/messages/block_translators/openai.py +1089 -0
  135. coco_core/messages/chat.py +64 -0
  136. coco_core/messages/content.py +1488 -0
  137. coco_core/messages/function.py +62 -0
  138. coco_core/messages/human.py +70 -0
  139. coco_core/messages/modifier.py +33 -0
  140. coco_core/messages/system.py +70 -0
  141. coco_core/messages/tool.py +418 -0
  142. coco_core/messages/utils.py +2442 -0
  143. coco_core/output_parsers/__init__.py +101 -0
  144. coco_core/output_parsers/base.py +361 -0
  145. coco_core/output_parsers/format_instructions.py +16 -0
  146. coco_core/output_parsers/json.py +139 -0
  147. coco_core/output_parsers/list.py +249 -0
  148. coco_core/output_parsers/openai_functions.py +324 -0
  149. coco_core/output_parsers/openai_tools.py +383 -0
  150. coco_core/output_parsers/pydantic.py +141 -0
  151. coco_core/output_parsers/string.py +63 -0
  152. coco_core/output_parsers/transform.py +175 -0
  153. coco_core/output_parsers/xml.py +301 -0
  154. coco_core/outputs/__init__.py +66 -0
  155. coco_core/outputs/chat_generation.py +147 -0
  156. coco_core/outputs/chat_result.py +39 -0
  157. coco_core/outputs/generation.py +80 -0
  158. coco_core/outputs/llm_result.py +112 -0
  159. coco_core/outputs/run_info.py +22 -0
  160. coco_core/prompt_values.py +161 -0
  161. coco_core/prompts/__init__.py +101 -0
  162. coco_core/prompts/base.py +508 -0
  163. coco_core/prompts/chat.py +1495 -0
  164. coco_core/prompts/dict.py +177 -0
  165. coco_core/prompts/few_shot.py +483 -0
  166. coco_core/prompts/few_shot_with_templates.py +239 -0
  167. coco_core/prompts/image.py +177 -0
  168. coco_core/prompts/loading.py +296 -0
  169. coco_core/prompts/message.py +97 -0
  170. coco_core/prompts/prompt.py +312 -0
  171. coco_core/prompts/string.py +399 -0
  172. coco_core/prompts/structured.py +214 -0
  173. coco_core/py.typed +0 -0
  174. coco_core/rate_limiters.py +254 -0
  175. coco_core/retrievers.py +328 -0
  176. coco_core/runnables/__init__.py +136 -0
  177. coco_core/runnables/base.py +6725 -0
  178. coco_core/runnables/branch.py +457 -0
  179. coco_core/runnables/config.py +712 -0
  180. coco_core/runnables/configurable.py +710 -0
  181. coco_core/runnables/fallbacks.py +663 -0
  182. coco_core/runnables/graph.py +741 -0
  183. coco_core/runnables/graph_ascii.py +366 -0
  184. coco_core/runnables/graph_mermaid.py +503 -0
  185. coco_core/runnables/graph_png.py +215 -0
  186. coco_core/runnables/history.py +631 -0
  187. coco_core/runnables/passthrough.py +849 -0
  188. coco_core/runnables/retry.py +379 -0
  189. coco_core/runnables/router.py +239 -0
  190. coco_core/runnables/schema.py +188 -0
  191. coco_core/runnables/utils.py +798 -0
  192. coco_core/stores.py +291 -0
  193. coco_core/structured_query.py +203 -0
  194. coco_core/sys_info.py +154 -0
  195. coco_core/tools/__init__.py +95 -0
  196. coco_core/tools/base.py +1948 -0
  197. coco_core/tools/convert.py +497 -0
  198. coco_core/tools/render.py +67 -0
  199. coco_core/tools/retriever.py +94 -0
  200. coco_core/tools/simple.py +208 -0
  201. coco_core/tools/structured.py +322 -0
  202. coco_core/tracers/__init__.py +47 -0
  203. coco_core/tracers/_compat.py +95 -0
  204. coco_core/tracers/_context.py +337 -0
  205. coco_core/tracers/_streaming.py +53 -0
  206. coco_core/tracers/base.py +955 -0
  207. coco_core/tracers/coco.py +279 -0
  208. coco_core/tracers/context.py +205 -0
  209. coco_core/tracers/core.py +766 -0
  210. coco_core/tracers/event_stream.py +1101 -0
  211. coco_core/tracers/log_stream.py +765 -0
  212. coco_core/tracers/memory_stream.py +200 -0
  213. coco_core/tracers/root_listeners.py +130 -0
  214. coco_core/tracers/run_collector.py +40 -0
  215. coco_core/tracers/schemas.py +14 -0
  216. coco_core/tracers/stdout.py +205 -0
  217. coco_core/utils/__init__.py +111 -0
  218. coco_core/utils/_gateway.py +274 -0
  219. coco_core/utils/_merge.py +218 -0
  220. coco_core/utils/aiter.py +357 -0
  221. coco_core/utils/env.py +86 -0
  222. coco_core/utils/formatting.py +80 -0
  223. coco_core/utils/function_calling.py +869 -0
  224. coco_core/utils/html.py +132 -0
  225. coco_core/utils/image.py +15 -0
  226. coco_core/utils/input.py +82 -0
  227. coco_core/utils/interactive_env.py +12 -0
  228. coco_core/utils/iter.py +229 -0
  229. coco_core/utils/json.py +230 -0
  230. coco_core/utils/json_schema.py +273 -0
  231. coco_core/utils/mustache.py +706 -0
  232. coco_core/utils/pydantic.py +631 -0
  233. coco_core/utils/strings.py +69 -0
  234. coco_core/utils/usage.py +61 -0
  235. coco_core/utils/utils.py +528 -0
  236. coco_core/utils/uuid.py +57 -0
  237. coco_core/vectorstores/__init__.py +53 -0
  238. coco_core/vectorstores/base.py +1111 -0
  239. coco_core/vectorstores/in_memory.py +546 -0
  240. coco_core/vectorstores/utils.py +163 -0
  241. coco_core/version.py +3 -0
  242. coco_framework-0.1.0.dist-info/METADATA +184 -0
  243. coco_framework-0.1.0.dist-info/RECORD +400 -0
  244. coco_framework-0.1.0.dist-info/WHEEL +4 -0
  245. coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
  246. coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
  247. coco_graph/_internal/__init__.py +4 -0
  248. coco_graph/_internal/_cache.py +31 -0
  249. coco_graph/_internal/_config.py +474 -0
  250. coco_graph/_internal/_constants.py +140 -0
  251. coco_graph/_internal/_fields.py +213 -0
  252. coco_graph/_internal/_future.py +220 -0
  253. coco_graph/_internal/_pydantic.py +275 -0
  254. coco_graph/_internal/_queue.py +124 -0
  255. coco_graph/_internal/_replay.py +90 -0
  256. coco_graph/_internal/_retry.py +29 -0
  257. coco_graph/_internal/_runnable.py +969 -0
  258. coco_graph/_internal/_scratchpad.py +19 -0
  259. coco_graph/_internal/_serde.py +253 -0
  260. coco_graph/_internal/_timeout.py +25 -0
  261. coco_graph/_internal/_typing.py +54 -0
  262. coco_graph/cache/base/__init__.py +48 -0
  263. coco_graph/cache/base/py.typed +0 -0
  264. coco_graph/cache/memory/__init__.py +73 -0
  265. coco_graph/cache/redis/__init__.py +144 -0
  266. coco_graph/cache/sqlite/__init__.py +120 -0
  267. coco_graph/callbacks.py +394 -0
  268. coco_graph/channels/__init__.py +29 -0
  269. coco_graph/channels/any_value.py +72 -0
  270. coco_graph/channels/base.py +121 -0
  271. coco_graph/channels/binop.py +155 -0
  272. coco_graph/channels/delta.py +202 -0
  273. coco_graph/channels/ephemeral_value.py +79 -0
  274. coco_graph/channels/last_value.py +151 -0
  275. coco_graph/channels/named_barrier_value.py +167 -0
  276. coco_graph/channels/topic.py +94 -0
  277. coco_graph/channels/untracked_value.py +73 -0
  278. coco_graph/checkpoint/base/__init__.py +861 -0
  279. coco_graph/checkpoint/base/id.py +109 -0
  280. coco_graph/checkpoint/base/py.typed +0 -0
  281. coco_graph/checkpoint/memory/__init__.py +698 -0
  282. coco_graph/checkpoint/memory/py.typed +0 -0
  283. coco_graph/checkpoint/postgres/__init__.py +602 -0
  284. coco_graph/checkpoint/postgres/_ainternal.py +23 -0
  285. coco_graph/checkpoint/postgres/_internal.py +21 -0
  286. coco_graph/checkpoint/postgres/aio.py +691 -0
  287. coco_graph/checkpoint/postgres/base.py +666 -0
  288. coco_graph/checkpoint/postgres/py.typed +0 -0
  289. coco_graph/checkpoint/postgres/shallow.py +967 -0
  290. coco_graph/checkpoint/serde/__init__.py +0 -0
  291. coco_graph/checkpoint/serde/_msgpack.py +97 -0
  292. coco_graph/checkpoint/serde/base.py +64 -0
  293. coco_graph/checkpoint/serde/encrypted.py +80 -0
  294. coco_graph/checkpoint/serde/event_hooks.py +52 -0
  295. coco_graph/checkpoint/serde/jsonplus.py +883 -0
  296. coco_graph/checkpoint/serde/py.typed +0 -0
  297. coco_graph/checkpoint/serde/types.py +68 -0
  298. coco_graph/checkpoint/sqlite/__init__.py +645 -0
  299. coco_graph/checkpoint/sqlite/_delta.py +172 -0
  300. coco_graph/checkpoint/sqlite/aio.py +738 -0
  301. coco_graph/checkpoint/sqlite/py.typed +0 -0
  302. coco_graph/checkpoint/sqlite/utils.py +116 -0
  303. coco_graph/config.py +196 -0
  304. coco_graph/constants.py +64 -0
  305. coco_graph/errors.py +241 -0
  306. coco_graph/func/__init__.py +620 -0
  307. coco_graph/graph/__init__.py +12 -0
  308. coco_graph/graph/_branch.py +225 -0
  309. coco_graph/graph/_node.py +103 -0
  310. coco_graph/graph/message.py +437 -0
  311. coco_graph/graph/state.py +1978 -0
  312. coco_graph/graph/ui.py +227 -0
  313. coco_graph/managed/__init__.py +3 -0
  314. coco_graph/managed/base.py +31 -0
  315. coco_graph/managed/is_last_step.py +24 -0
  316. coco_graph/prebuilt/__init__.py +23 -0
  317. coco_graph/prebuilt/_tool_call_stream.py +117 -0
  318. coco_graph/prebuilt/_tool_call_transformer.py +165 -0
  319. coco_graph/prebuilt/chat_agent_executor.py +1015 -0
  320. coco_graph/prebuilt/interrupt.py +105 -0
  321. coco_graph/prebuilt/py.typed +0 -0
  322. coco_graph/prebuilt/tool_node.py +2030 -0
  323. coco_graph/prebuilt/tool_validator.py +221 -0
  324. coco_graph/pregel/__init__.py +3 -0
  325. coco_graph/pregel/_algo.py +1460 -0
  326. coco_graph/pregel/_call.py +298 -0
  327. coco_graph/pregel/_checkpoint.py +331 -0
  328. coco_graph/pregel/_config.py +0 -0
  329. coco_graph/pregel/_draw.py +294 -0
  330. coco_graph/pregel/_executor.py +223 -0
  331. coco_graph/pregel/_io.py +174 -0
  332. coco_graph/pregel/_log.py +3 -0
  333. coco_graph/pregel/_loop.py +1988 -0
  334. coco_graph/pregel/_messages.py +461 -0
  335. coco_graph/pregel/_read.py +312 -0
  336. coco_graph/pregel/_retry.py +854 -0
  337. coco_graph/pregel/_runner.py +941 -0
  338. coco_graph/pregel/_tools.py +268 -0
  339. coco_graph/pregel/_utils.py +221 -0
  340. coco_graph/pregel/_validate.py +120 -0
  341. coco_graph/pregel/_write.py +192 -0
  342. coco_graph/pregel/debug.py +302 -0
  343. coco_graph/pregel/main.py +4364 -0
  344. coco_graph/pregel/protocol.py +288 -0
  345. coco_graph/pregel/types.py +38 -0
  346. coco_graph/py.typed +0 -0
  347. coco_graph/runtime.py +350 -0
  348. coco_graph/store/base/__init__.py +1322 -0
  349. coco_graph/store/base/batch.py +371 -0
  350. coco_graph/store/base/embed.py +436 -0
  351. coco_graph/store/base/py.typed +0 -0
  352. coco_graph/store/memory/__init__.py +592 -0
  353. coco_graph/store/memory/py.typed +0 -0
  354. coco_graph/store/postgres/__init__.py +4 -0
  355. coco_graph/store/postgres/aio.py +592 -0
  356. coco_graph/store/postgres/base.py +1480 -0
  357. coco_graph/store/postgres/py.typed +0 -0
  358. coco_graph/store/sqlite/__init__.py +4 -0
  359. coco_graph/store/sqlite/aio.py +632 -0
  360. coco_graph/store/sqlite/base.py +1514 -0
  361. coco_graph/stream/__init__.py +45 -0
  362. coco_graph/stream/_convert.py +32 -0
  363. coco_graph/stream/_mux.py +523 -0
  364. coco_graph/stream/_types.py +330 -0
  365. coco_graph/stream/run_stream.py +735 -0
  366. coco_graph/stream/stream_channel.py +341 -0
  367. coco_graph/stream/transformers.py +1039 -0
  368. coco_graph/types.py +1079 -0
  369. coco_graph/typing.py +48 -0
  370. coco_graph/utils/__init__.py +1 -0
  371. coco_graph/utils/config.py +4 -0
  372. coco_graph/utils/runnable.py +3 -0
  373. coco_graph/version.py +13 -0
  374. coco_graph/warnings.py +69 -0
  375. coco_openai/__init__.py +20 -0
  376. coco_openai/_compat.py +45 -0
  377. coco_openai/_version.py +3 -0
  378. coco_openai/chat_models/__init__.py +6 -0
  379. coco_openai/chat_models/_client_utils.py +684 -0
  380. coco_openai/chat_models/_compat.py +525 -0
  381. coco_openai/chat_models/azure.py +1227 -0
  382. coco_openai/chat_models/base.py +5654 -0
  383. coco_openai/chat_models/codex.py +557 -0
  384. coco_openai/chatgpt_oauth.py +1063 -0
  385. coco_openai/data/__init__.py +1 -0
  386. coco_openai/data/_profiles.py +1655 -0
  387. coco_openai/data/profile_augmentations.toml +127 -0
  388. coco_openai/embeddings/__init__.py +6 -0
  389. coco_openai/embeddings/azure.py +240 -0
  390. coco_openai/embeddings/base.py +820 -0
  391. coco_openai/llms/__init__.py +6 -0
  392. coco_openai/llms/azure.py +249 -0
  393. coco_openai/llms/base.py +919 -0
  394. coco_openai/middleware/__init__.py +11 -0
  395. coco_openai/middleware/openai_moderation.py +484 -0
  396. coco_openai/output_parsers/__init__.py +9 -0
  397. coco_openai/output_parsers/tools.py +9 -0
  398. coco_openai/py.typed +0 -0
  399. coco_openai/tools/__init__.py +5 -0
  400. coco_openai/tools/custom_tool.py +111 -0
@@ -0,0 +1,883 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import dataclasses
5
+ import decimal
6
+ import importlib
7
+ import json
8
+ import logging
9
+ import pathlib
10
+ import pickle
11
+ import re
12
+ import sys
13
+ from collections import deque
14
+ from collections.abc import Callable, Iterable, Sequence
15
+ from datetime import date, datetime, time, timedelta, timezone
16
+ from enum import Enum
17
+ from inspect import isclass
18
+ from ipaddress import (
19
+ IPv4Address,
20
+ IPv4Interface,
21
+ IPv4Network,
22
+ IPv6Address,
23
+ IPv6Interface,
24
+ IPv6Network,
25
+ )
26
+ from typing import TYPE_CHECKING, Any, Literal, cast
27
+ from uuid import UUID
28
+ from zoneinfo import ZoneInfo
29
+
30
+ import ormsgpack
31
+ from coco_core.load.load import Reviver
32
+
33
+ from coco_graph.checkpoint.serde import _msgpack as _lg_msgpack
34
+ from coco_graph.checkpoint.serde.base import SerializerProtocol
35
+ from coco_graph.checkpoint.serde.event_hooks import emit_serde_event
36
+ from coco_graph.checkpoint.serde.types import (
37
+ SendProtocol,
38
+ _DeltaSnapshot,
39
+ )
40
+ from coco_graph.store.base import Item
41
+
42
+ if TYPE_CHECKING:
43
+ from coco_graph.checkpoint.serde._msgpack import (
44
+ AllowedMsgpackModules,
45
+ )
46
+
47
+ LC_REVIVER = Reviver(allowed_objects="core")
48
+ EMPTY_BYTES = b""
49
+ logger = logging.getLogger(__name__)
50
+
51
+ # Dedup log warnings across process lifetime; cap bounds state if types are
52
+ # dynamically generated (also acts as a circuit breaker on warning volume).
53
+ # Dedup is best-effort: racing threads may each emit once for the same key,
54
+ # and warnings are silently dropped once _MAX_WARNED_TYPES is reached.
55
+ _MAX_WARNED_TYPES = 1000
56
+ _warned_unregistered_types: set[tuple[str, str]] = set()
57
+ _warned_blocked_types: set[tuple[str, str]] = set()
58
+
59
+
60
+ def _is_safe_json_type(id_list: list[str]) -> bool:
61
+ """Return True if an lc=2 id refers to a type in SAFE_MSGPACK_TYPES.
62
+
63
+ Safe types bypass the ``allowed_json_modules`` gate so that old "json" format
64
+ checkpoints (written before the msgpack migration) can be resumed without
65
+ requiring users to configure an explicit allowlist.
66
+ """
67
+ if len(id_list) < 2:
68
+ return False
69
+ module_name = ".".join(id_list[:-1])
70
+ return (module_name, id_list[-1]) in _lg_msgpack.SAFE_MSGPACK_TYPES
71
+
72
+
73
+ def _warn_once(
74
+ seen: set[tuple[str, str]], key: tuple[str, str], msg: str, *args: object
75
+ ) -> None:
76
+ if key in seen or len(seen) >= _MAX_WARNED_TYPES:
77
+ return
78
+ seen.add(key)
79
+ logger.warning(msg, *args)
80
+
81
+
82
+ class JsonPlusSerializer(SerializerProtocol):
83
+ """Serializer that uses ormsgpack, with optional fallbacks.
84
+
85
+ !!! warning
86
+
87
+ Security note: This serializer is intended for use within the `BaseCheckpointSaver`
88
+ class and called within the Pregel loop. It should not be used on untrusted
89
+ python objects. If an attacker can write directly to your checkpoint database,
90
+ they may be able to trigger code execution when data is deserialized.
91
+
92
+ Set the environment variable ``COCO_GRAPH_STRICT_MSGPACK=true`` to restrict
93
+ deserialization to a built-in allowlist of safe types. You can also pass
94
+ an explicit ``allowed_msgpack_modules`` to the constructor.
95
+ """
96
+
97
+ def __init__(
98
+ self,
99
+ *,
100
+ pickle_fallback: bool = False,
101
+ allowed_json_modules: Iterable[tuple[str, ...]] | Literal[True] | None = None,
102
+ allowed_msgpack_modules: (
103
+ AllowedMsgpackModules | Literal[True] | None
104
+ ) = _lg_msgpack._SENTINEL,
105
+ __unpack_ext_hook__: Callable[[int, bytes], Any] | None = None,
106
+ ) -> None:
107
+ if allowed_msgpack_modules is _lg_msgpack._SENTINEL:
108
+ if _lg_msgpack.STRICT_MSGPACK_ENABLED:
109
+ # Strict: only SAFE_MSGPACK_TYPES are allowed.
110
+ allowed_msgpack_modules = None
111
+ else:
112
+ # Permissive (default): all types allowed with a warning.
113
+ # Set COCO_GRAPH_STRICT_MSGPACK=true to lock this down.
114
+ allowed_msgpack_modules = True
115
+ self.pickle_fallback = pickle_fallback
116
+ self._allowed_json_modules: set[tuple[str, ...]] | Literal[True] | None = (
117
+ _normalize_allowlist(allowed_json_modules)
118
+ )
119
+ self._allowed_msgpack_modules = _normalize_allowlist(allowed_msgpack_modules)
120
+
121
+ self._custom_unpack_ext_hook = __unpack_ext_hook__ is not None
122
+ self._unpack_ext_hook = (
123
+ __unpack_ext_hook__
124
+ if __unpack_ext_hook__ is not None
125
+ else _create_msgpack_ext_hook(self._allowed_msgpack_modules)
126
+ )
127
+
128
+ def with_msgpack_allowlist(
129
+ self, extra_allowlist: Iterable[tuple[str, ...] | type]
130
+ ) -> JsonPlusSerializer:
131
+ """Return a new serializer with a merged msgpack allowlist."""
132
+ base_allowlist = self._allowed_msgpack_modules
133
+ if base_allowlist is True or base_allowlist is False:
134
+ return self
135
+ elif base_allowlist:
136
+ base_allowlist = set(base_allowlist)
137
+ else:
138
+ base_allowlist = set()
139
+ extra = _normalize_module_keys(tuple(extra_allowlist))
140
+ merged = base_allowlist | extra
141
+ if merged == base_allowlist:
142
+ return self
143
+ allowed_msgpack_modules: AllowedMsgpackModules | Literal[True] | None
144
+ if merged:
145
+ allowed_msgpack_modules = tuple(merged)
146
+ elif isinstance(self._allowed_msgpack_modules, set):
147
+ allowed_msgpack_modules = tuple(self._allowed_msgpack_modules)
148
+ else:
149
+ allowed_msgpack_modules = self._allowed_msgpack_modules
150
+
151
+ clone = copy.copy(self)
152
+ clone._allowed_json_modules = _normalize_allowlist(self._allowed_json_modules)
153
+ clone._allowed_msgpack_modules = _normalize_allowlist(allowed_msgpack_modules)
154
+ if not clone._custom_unpack_ext_hook:
155
+ clone._unpack_ext_hook = _create_msgpack_ext_hook(
156
+ clone._allowed_msgpack_modules
157
+ )
158
+ return clone
159
+
160
+ def _reviver(self, value: dict[str, Any]) -> Any:
161
+ if (
162
+ value.get("lc", None) == 2
163
+ and value.get("type", None) == "constructor"
164
+ and value.get("id", None) is not None
165
+ ):
166
+ id_list = value["id"]
167
+ is_safe = _is_safe_json_type(id_list)
168
+ if self._allowed_json_modules or is_safe:
169
+ try:
170
+ return self._revive_lc2(value)
171
+ except InvalidModuleError as e:
172
+ if not is_safe:
173
+ logger.warning(
174
+ "Object %s is not in the deserialization allowlist.\n%s",
175
+ value["id"],
176
+ e.message,
177
+ )
178
+
179
+ return LC_REVIVER(value)
180
+
181
+ def _revive_lc2(self, value: dict[str, Any]) -> Any:
182
+ self._check_allowed_json_modules(value)
183
+
184
+ [*module, name] = value["id"]
185
+ # The `method` field on lc:2 envelopes is intentionally ignored.
186
+ # Revival is restricted to the default constructor (no `getattr`
187
+ # dispatch on attacker-influenced names). The framework's own
188
+ # encoder has not emitted `method=` since the msgpack migration,
189
+ # and the only legacy emission was `method=(None, "construct")`
190
+ # for pydantic models, where the first entry (`None`) already
191
+ # meant "default constructor", which is what we do here.
192
+ try:
193
+ mod = importlib.import_module(".".join(module))
194
+ cls = getattr(mod, name)
195
+ if isclass(cls) and issubclass(cls, BaseException):
196
+ return None
197
+ args = value.get("args")
198
+ kwargs = value.get("kwargs")
199
+ if args and kwargs:
200
+ return cls(*args, **kwargs)
201
+ elif args:
202
+ return cls(*args)
203
+ elif kwargs:
204
+ return cls(**kwargs)
205
+ else:
206
+ return cls()
207
+ except Exception as exc:
208
+ # Method-field dispatch has been removed (GHSA-fjqc-hq36-qh5p), so
209
+ # legacy pydantic payloads emitting `method=[None, "construct"]`
210
+ # no longer fall back to `cls.construct(**kwargs)` when the
211
+ # default constructor rejects the serialized kwargs. Surface a
212
+ # one-line warning so operators can spot payloads that now revive
213
+ # to `None` instead of silently degrading to the raw envelope.
214
+ logger.warning(
215
+ "Failed to revive lc:2 envelope %s "
216
+ "(legacy_method_field=%s, error=%s); returning None",
217
+ ".".join((*module, name)),
218
+ "method" in value,
219
+ type(exc).__name__,
220
+ )
221
+ return None
222
+
223
+ def _check_allowed_json_modules(self, value: dict[str, Any]) -> None:
224
+ needed = tuple(value["id"])
225
+ dotted = ".".join(needed)
226
+ # Safe types (the same set already allowed for msgpack deserialization) are
227
+ # permitted without an explicit allowlist — they are known-safe CocoGraph and
228
+ # Coco types. This restores backwards-compat for old "json" checkpoints
229
+ # that pre-date the msgpack migration without reopening the broader security gate.
230
+ if _is_safe_json_type(list(needed)):
231
+ return
232
+
233
+ if not self._allowed_json_modules:
234
+ raise InvalidModuleError(
235
+ f"Refused to deserialize JSON constructor: {dotted}. "
236
+ "No allowed_json_modules configured.\n\n"
237
+ "Unblock with ONE of:\n"
238
+ f" • JsonPlusSerializer(allowed_json_modules=[{needed!r}, ...])\n"
239
+ " • (DANGEROUS) JsonPlusSerializer(allowed_json_modules=True)\n\n"
240
+ "Note: Prefix allowlists are intentionally unsupported; prefer exact symbols "
241
+ "or plain-JSON representations revived without import-time side effects."
242
+ )
243
+
244
+ if self._allowed_json_modules is True:
245
+ return
246
+ if needed in self._allowed_json_modules:
247
+ return
248
+
249
+ raise InvalidModuleError(
250
+ f"Refused to deserialize JSON constructor: {dotted}. "
251
+ "Symbol is not in the deserialization allowlist.\n\n"
252
+ "Add exactly this symbol to unblock:\n"
253
+ f" JsonPlusSerializer(allowed_json_modules=[{needed!r}, ...])\n"
254
+ "Or, as a last resort (DANGEROUS):\n"
255
+ " JsonPlusSerializer(allowed_json_modules=True)"
256
+ )
257
+
258
+ def dumps_typed(self, obj: Any) -> tuple[str, bytes]:
259
+ if obj is None:
260
+ return "null", EMPTY_BYTES
261
+ elif isinstance(obj, bytes):
262
+ return "bytes", obj
263
+ elif isinstance(obj, bytearray):
264
+ return "bytearray", obj
265
+ else:
266
+ try:
267
+ return "msgpack", _msgpack_enc(obj)
268
+ except ormsgpack.MsgpackEncodeError as exc:
269
+ if self.pickle_fallback:
270
+ return "pickle", pickle.dumps(obj)
271
+ raise exc
272
+
273
+ def loads_typed(self, data: tuple[str, bytes]) -> Any:
274
+ type_, data_ = data
275
+ if type_ == "null":
276
+ return None
277
+ elif type_ == "bytes":
278
+ return data_
279
+ elif type_ == "bytearray":
280
+ return bytearray(data_)
281
+ elif type_ == "json":
282
+ return json.loads(data_, object_hook=self._reviver)
283
+ elif type_ == "msgpack":
284
+ return ormsgpack.unpackb(
285
+ data_, ext_hook=self._unpack_ext_hook, option=ormsgpack.OPT_NON_STR_KEYS
286
+ )
287
+ elif self.pickle_fallback and type_ == "pickle":
288
+ return pickle.loads(data_)
289
+ else:
290
+ raise NotImplementedError(f"Unknown serialization type: {type_}")
291
+
292
+
293
+ # --- msgpack ---
294
+
295
+ EXT_CONSTRUCTOR_SINGLE_ARG = 0
296
+ EXT_CONSTRUCTOR_POS_ARGS = 1
297
+ EXT_CONSTRUCTOR_KW_ARGS = 2
298
+ EXT_METHOD_SINGLE_ARG = 3
299
+ EXT_PYDANTIC_V1 = 4
300
+ EXT_PYDANTIC_V2 = 5
301
+ EXT_NUMPY_ARRAY = 6
302
+ EXT_DELTA_SNAPSHOT = 7
303
+
304
+
305
+ def _msgpack_default(obj: Any) -> str | ormsgpack.Ext:
306
+ if isinstance(obj, _DeltaSnapshot):
307
+ return ormsgpack.Ext(EXT_DELTA_SNAPSHOT, _msgpack_enc(obj.value))
308
+ elif hasattr(obj, "model_dump") and callable(obj.model_dump): # pydantic v2
309
+ return ormsgpack.Ext(
310
+ EXT_PYDANTIC_V2,
311
+ _msgpack_enc(
312
+ (
313
+ obj.__class__.__module__,
314
+ obj.__class__.__name__,
315
+ obj.model_dump(),
316
+ "model_validate_json",
317
+ ),
318
+ ),
319
+ )
320
+ elif hasattr(obj, "get_secret_value") and callable(obj.get_secret_value):
321
+ return ormsgpack.Ext(
322
+ EXT_CONSTRUCTOR_SINGLE_ARG,
323
+ _msgpack_enc(
324
+ (
325
+ obj.__class__.__module__,
326
+ obj.__class__.__name__,
327
+ obj.get_secret_value(),
328
+ ),
329
+ ),
330
+ )
331
+ elif hasattr(obj, "dict") and callable(obj.dict): # pydantic v1
332
+ return ormsgpack.Ext(
333
+ EXT_PYDANTIC_V1,
334
+ _msgpack_enc(
335
+ (
336
+ obj.__class__.__module__,
337
+ obj.__class__.__name__,
338
+ obj.dict(),
339
+ ),
340
+ ),
341
+ )
342
+ elif hasattr(obj, "_asdict") and callable(obj._asdict): # namedtuple
343
+ return ormsgpack.Ext(
344
+ EXT_CONSTRUCTOR_KW_ARGS,
345
+ _msgpack_enc(
346
+ (
347
+ obj.__class__.__module__,
348
+ obj.__class__.__name__,
349
+ obj._asdict(),
350
+ ),
351
+ ),
352
+ )
353
+ elif isinstance(obj, pathlib.Path):
354
+ return ormsgpack.Ext(
355
+ EXT_CONSTRUCTOR_POS_ARGS,
356
+ _msgpack_enc(
357
+ (obj.__class__.__module__, obj.__class__.__name__, obj.parts),
358
+ ),
359
+ )
360
+ elif isinstance(obj, re.Pattern):
361
+ return ormsgpack.Ext(
362
+ EXT_CONSTRUCTOR_POS_ARGS,
363
+ _msgpack_enc(
364
+ ("re", "compile", (obj.pattern, obj.flags)),
365
+ ),
366
+ )
367
+ elif isinstance(obj, UUID):
368
+ return ormsgpack.Ext(
369
+ EXT_CONSTRUCTOR_SINGLE_ARG,
370
+ _msgpack_enc(
371
+ (obj.__class__.__module__, obj.__class__.__name__, obj.hex),
372
+ ),
373
+ )
374
+ elif isinstance(obj, decimal.Decimal):
375
+ return ormsgpack.Ext(
376
+ EXT_CONSTRUCTOR_SINGLE_ARG,
377
+ _msgpack_enc(
378
+ (obj.__class__.__module__, obj.__class__.__name__, str(obj)),
379
+ ),
380
+ )
381
+ elif isinstance(obj, (set, frozenset, deque)):
382
+ return ormsgpack.Ext(
383
+ EXT_CONSTRUCTOR_SINGLE_ARG,
384
+ _msgpack_enc(
385
+ (obj.__class__.__module__, obj.__class__.__name__, tuple(obj)),
386
+ ),
387
+ )
388
+ elif isinstance(obj, (IPv4Address, IPv4Interface, IPv4Network)):
389
+ return ormsgpack.Ext(
390
+ EXT_CONSTRUCTOR_SINGLE_ARG,
391
+ _msgpack_enc(
392
+ (obj.__class__.__module__, obj.__class__.__name__, str(obj)),
393
+ ),
394
+ )
395
+ elif isinstance(obj, (IPv6Address, IPv6Interface, IPv6Network)):
396
+ return ormsgpack.Ext(
397
+ EXT_CONSTRUCTOR_SINGLE_ARG,
398
+ _msgpack_enc(
399
+ (obj.__class__.__module__, obj.__class__.__name__, str(obj)),
400
+ ),
401
+ )
402
+ elif isinstance(obj, datetime):
403
+ return ormsgpack.Ext(
404
+ EXT_METHOD_SINGLE_ARG,
405
+ _msgpack_enc(
406
+ (
407
+ obj.__class__.__module__,
408
+ obj.__class__.__name__,
409
+ obj.isoformat(),
410
+ "fromisoformat",
411
+ ),
412
+ ),
413
+ )
414
+ elif isinstance(obj, timedelta):
415
+ return ormsgpack.Ext(
416
+ EXT_CONSTRUCTOR_POS_ARGS,
417
+ _msgpack_enc(
418
+ (
419
+ obj.__class__.__module__,
420
+ obj.__class__.__name__,
421
+ (obj.days, obj.seconds, obj.microseconds),
422
+ ),
423
+ ),
424
+ )
425
+ elif isinstance(obj, date):
426
+ return ormsgpack.Ext(
427
+ EXT_CONSTRUCTOR_POS_ARGS,
428
+ _msgpack_enc(
429
+ (
430
+ obj.__class__.__module__,
431
+ obj.__class__.__name__,
432
+ (obj.year, obj.month, obj.day),
433
+ ),
434
+ ),
435
+ )
436
+ elif isinstance(obj, time):
437
+ return ormsgpack.Ext(
438
+ EXT_CONSTRUCTOR_KW_ARGS,
439
+ _msgpack_enc(
440
+ (
441
+ obj.__class__.__module__,
442
+ obj.__class__.__name__,
443
+ {
444
+ "hour": obj.hour,
445
+ "minute": obj.minute,
446
+ "second": obj.second,
447
+ "microsecond": obj.microsecond,
448
+ "tzinfo": obj.tzinfo,
449
+ "fold": obj.fold,
450
+ },
451
+ ),
452
+ ),
453
+ )
454
+ elif isinstance(obj, timezone):
455
+ return ormsgpack.Ext(
456
+ EXT_CONSTRUCTOR_POS_ARGS,
457
+ _msgpack_enc(
458
+ (
459
+ obj.__class__.__module__,
460
+ obj.__class__.__name__,
461
+ obj.__getinitargs__(), # type: ignore[attr-defined]
462
+ ),
463
+ ),
464
+ )
465
+ elif isinstance(obj, ZoneInfo):
466
+ return ormsgpack.Ext(
467
+ EXT_CONSTRUCTOR_SINGLE_ARG,
468
+ _msgpack_enc(
469
+ (obj.__class__.__module__, obj.__class__.__name__, obj.key),
470
+ ),
471
+ )
472
+ elif isinstance(obj, Enum):
473
+ return ormsgpack.Ext(
474
+ EXT_CONSTRUCTOR_SINGLE_ARG,
475
+ _msgpack_enc(
476
+ (obj.__class__.__module__, obj.__class__.__name__, obj.value),
477
+ ),
478
+ )
479
+ elif isinstance(obj, SendProtocol):
480
+ args: tuple[Any, ...] = (obj.node, obj.arg)
481
+ if (timeout := getattr(obj, "timeout", None)) is not None:
482
+ args = (obj.node, obj.arg, timeout)
483
+ return ormsgpack.Ext(
484
+ EXT_CONSTRUCTOR_POS_ARGS,
485
+ _msgpack_enc(
486
+ (obj.__class__.__module__, obj.__class__.__name__, args),
487
+ ),
488
+ )
489
+ elif dataclasses.is_dataclass(obj):
490
+ # doesn't use dataclasses.asdict to avoid deepcopy and recursion
491
+ return ormsgpack.Ext(
492
+ EXT_CONSTRUCTOR_KW_ARGS,
493
+ _msgpack_enc(
494
+ (
495
+ obj.__class__.__module__,
496
+ obj.__class__.__name__,
497
+ {
498
+ field.name: getattr(obj, field.name)
499
+ for field in dataclasses.fields(obj)
500
+ },
501
+ ),
502
+ ),
503
+ )
504
+ elif isinstance(obj, Item):
505
+ return ormsgpack.Ext(
506
+ EXT_CONSTRUCTOR_KW_ARGS,
507
+ _msgpack_enc(
508
+ (
509
+ obj.__class__.__module__,
510
+ obj.__class__.__name__,
511
+ {k: getattr(obj, k) for k in obj.__slots__},
512
+ ),
513
+ ),
514
+ )
515
+ elif (np_mod := sys.modules.get("numpy")) is not None and isinstance(
516
+ obj, np_mod.ndarray
517
+ ):
518
+ order = "F" if obj.flags.f_contiguous and not obj.flags.c_contiguous else "C"
519
+ if obj.flags.c_contiguous:
520
+ mv = memoryview(obj)
521
+ try:
522
+ meta = (obj.dtype.str, obj.shape, order, mv)
523
+ return ormsgpack.Ext(EXT_NUMPY_ARRAY, _msgpack_enc(meta))
524
+ finally:
525
+ mv.release()
526
+ else:
527
+ buf = obj.tobytes(order="A")
528
+ meta = (obj.dtype.str, obj.shape, order, buf)
529
+ return ormsgpack.Ext(EXT_NUMPY_ARRAY, _msgpack_enc(meta))
530
+
531
+ elif isinstance(obj, BaseException):
532
+ return repr(obj)
533
+ else:
534
+ raise TypeError(f"Object of type {obj.__class__.__name__} is not serializable")
535
+
536
+
537
+ def _send_from_args(args: Sequence[Any]) -> Any:
538
+ # ya we have a cyclic import here ¯\_(ツ)_/¯
539
+ from coco_graph.types import Send # type: ignore
540
+
541
+ if len(args) == 2:
542
+ return Send(*args)
543
+ return Send(args[0], args[1], timeout=args[2])
544
+
545
+
546
+ def _create_msgpack_ext_hook(
547
+ allowed_modules: set[tuple[str, ...]] | Literal[True] | None,
548
+ ) -> Callable[[int, bytes], Any]:
549
+ """Create msgpack ext hook with allowlist.
550
+
551
+ Args:
552
+ allowed_modules: Set of (module, name) tuples that are allowed to be
553
+ deserialized, or True to allow all with warnings for unregistered types, or None to only allow safe types.
554
+
555
+ Returns:
556
+ An ext_hook function for use with ormsgpack.unpackb.
557
+ """
558
+
559
+ def _check_allowed(module: str, name: str) -> bool:
560
+ """Check if type is allowed. Returns True if allowed, False if blocked."""
561
+ key = (module, name)
562
+
563
+ if key in _lg_msgpack.SAFE_MSGPACK_TYPES:
564
+ return True
565
+
566
+ if allowed_modules is True:
567
+ # default is to warn but allow unregistered types
568
+ emit_serde_event(
569
+ {
570
+ "kind": "msgpack_unregistered_allowed",
571
+ "module": module,
572
+ "name": name,
573
+ }
574
+ )
575
+ _warn_once(
576
+ _warned_unregistered_types,
577
+ key,
578
+ "Deserializing unregistered type %s.%s from checkpoint. "
579
+ "This will be blocked in a future version. "
580
+ "Set COCO_GRAPH_STRICT_MSGPACK=true to block now, or add "
581
+ "to allowed_msgpack_modules to allow explicitly: [(%r, %r)]",
582
+ module,
583
+ name,
584
+ module,
585
+ name,
586
+ )
587
+ return True
588
+ if allowed_modules is not None:
589
+ if key in allowed_modules:
590
+ return True
591
+ # strict mode blocks unregistered types
592
+ emit_serde_event(
593
+ {
594
+ "kind": "msgpack_blocked",
595
+ "module": module,
596
+ "name": name,
597
+ }
598
+ )
599
+ _warn_once(
600
+ _warned_blocked_types,
601
+ key,
602
+ "Blocked deserialization of %s.%s - not in allowed_msgpack_modules. "
603
+ "Add to allowed_msgpack_modules to allow: [(%r, %r)]",
604
+ module,
605
+ name,
606
+ module,
607
+ name,
608
+ )
609
+ return False
610
+
611
+ def _check_allowed_method(module: str, name: str, method: str) -> bool:
612
+ """Check if a method invocation is allowed."""
613
+ key = (module, name, method)
614
+ if key in _lg_msgpack.SAFE_MSGPACK_METHODS:
615
+ return True
616
+ emit_serde_event(
617
+ {
618
+ "kind": "msgpack_method_blocked",
619
+ "module": module,
620
+ "name": name,
621
+ "method": method,
622
+ }
623
+ )
624
+ logger.warning(
625
+ "Blocked deserialization of method call %s.%s.%s - "
626
+ "not in allowed methods set.",
627
+ module,
628
+ name,
629
+ method,
630
+ )
631
+ return False
632
+
633
+ def ext_hook(code: int, data: bytes) -> Any:
634
+ if code == EXT_DELTA_SNAPSHOT:
635
+ return _DeltaSnapshot(
636
+ ormsgpack.unpackb(
637
+ data, ext_hook=ext_hook, option=ormsgpack.OPT_NON_STR_KEYS
638
+ )
639
+ )
640
+ elif code == EXT_CONSTRUCTOR_SINGLE_ARG:
641
+ try:
642
+ tup = ormsgpack.unpackb(
643
+ data, ext_hook=ext_hook, option=ormsgpack.OPT_NON_STR_KEYS
644
+ )
645
+ if not _check_allowed(tup[0], tup[1]):
646
+ # We default to returning the raw data. If the user
647
+ # is using this in the context of a pydantic state, etc., then
648
+ # it would be validated upon construction.
649
+ return tup[2]
650
+ # module, name, arg
651
+ return getattr(importlib.import_module(tup[0]), tup[1])(tup[2])
652
+ except Exception:
653
+ return None
654
+ elif code == EXT_CONSTRUCTOR_POS_ARGS:
655
+ try:
656
+ tup = ormsgpack.unpackb(
657
+ data, ext_hook=ext_hook, option=ormsgpack.OPT_NON_STR_KEYS
658
+ )
659
+ if not _check_allowed(tup[0], tup[1]):
660
+ return tup[2]
661
+ if tup[0] == "coco_graph.types" and tup[1] == "Send":
662
+ return _send_from_args(tup[2])
663
+ # module, name, args
664
+ return getattr(importlib.import_module(tup[0]), tup[1])(*tup[2])
665
+ except Exception:
666
+ return None
667
+ elif code == EXT_CONSTRUCTOR_KW_ARGS:
668
+ try:
669
+ tup = ormsgpack.unpackb(
670
+ data, ext_hook=ext_hook, option=ormsgpack.OPT_NON_STR_KEYS
671
+ )
672
+ if not _check_allowed(tup[0], tup[1]):
673
+ return tup[2]
674
+ # module, name, kwargs
675
+ return getattr(importlib.import_module(tup[0]), tup[1])(**tup[2])
676
+ except Exception:
677
+ return None
678
+ elif code == EXT_METHOD_SINGLE_ARG:
679
+ try:
680
+ tup = ormsgpack.unpackb(
681
+ data, ext_hook=ext_hook, option=ormsgpack.OPT_NON_STR_KEYS
682
+ )
683
+ if not _check_allowed_method(tup[0], tup[1], tup[3]):
684
+ return tup[2]
685
+ # module, name, arg, method
686
+ return getattr(
687
+ getattr(importlib.import_module(tup[0]), tup[1]), tup[3]
688
+ )(tup[2])
689
+ except Exception:
690
+ return None
691
+ elif code == EXT_PYDANTIC_V1:
692
+ try:
693
+ tup = ormsgpack.unpackb(
694
+ data, ext_hook=ext_hook, option=ormsgpack.OPT_NON_STR_KEYS
695
+ )
696
+ if not _check_allowed(tup[0], tup[1]):
697
+ return tup[2]
698
+ # module, name, kwargs
699
+ cls = getattr(importlib.import_module(tup[0]), tup[1])
700
+ try:
701
+ return cls(**tup[2])
702
+ except Exception:
703
+ return cls.construct(**tup[2])
704
+ except Exception:
705
+ # for pydantic objects we can't find/reconstruct
706
+ # let's return the kwargs dict instead
707
+ try:
708
+ return tup[2]
709
+ except NameError:
710
+ return None
711
+ elif code == EXT_PYDANTIC_V2:
712
+ try:
713
+ tup = ormsgpack.unpackb(
714
+ data, ext_hook=ext_hook, option=ormsgpack.OPT_NON_STR_KEYS
715
+ )
716
+ if not _check_allowed(tup[0], tup[1]):
717
+ return tup[2]
718
+ # module, name, kwargs, method
719
+ cls = getattr(importlib.import_module(tup[0]), tup[1])
720
+ try:
721
+ return cls(**tup[2])
722
+ except Exception:
723
+ return cls.model_construct(**tup[2])
724
+ except Exception:
725
+ # for pydantic objects we can't find/reconstruct
726
+ # let's return the kwargs dict instead
727
+ try:
728
+ return tup[2]
729
+ except NameError:
730
+ return None
731
+ elif code == EXT_NUMPY_ARRAY:
732
+ try:
733
+ import numpy as _np
734
+
735
+ dtype_str, shape, order, buf = ormsgpack.unpackb(
736
+ data, ext_hook=ext_hook, option=ormsgpack.OPT_NON_STR_KEYS
737
+ )
738
+ arr = _np.frombuffer(buf, dtype=_np.dtype(dtype_str))
739
+ return arr.reshape(shape, order=order)
740
+ except Exception:
741
+ return None
742
+ return None
743
+
744
+ return ext_hook
745
+
746
+
747
+ # Aliasing in case anyone imported it directly
748
+ _msgpack_ext_hook = _create_msgpack_ext_hook(allowed_modules=None)
749
+
750
+
751
+ def _msgpack_ext_hook_to_json(code: int, data: bytes) -> Any:
752
+ if code == EXT_CONSTRUCTOR_SINGLE_ARG:
753
+ try:
754
+ tup = ormsgpack.unpackb(
755
+ data,
756
+ ext_hook=_msgpack_ext_hook_to_json,
757
+ option=ormsgpack.OPT_NON_STR_KEYS,
758
+ )
759
+ if tup[0] == "uuid" and tup[1] == "UUID":
760
+ hex_ = tup[2]
761
+ return (
762
+ f"{hex_[:8]}-{hex_[8:12]}-{hex_[12:16]}-{hex_[16:20]}-{hex_[20:]}"
763
+ )
764
+ # module, name, arg
765
+ return tup[2]
766
+ except Exception:
767
+ return
768
+ elif code == EXT_CONSTRUCTOR_POS_ARGS:
769
+ try:
770
+ tup = ormsgpack.unpackb(
771
+ data,
772
+ ext_hook=_msgpack_ext_hook_to_json,
773
+ option=ormsgpack.OPT_NON_STR_KEYS,
774
+ )
775
+ if tup[0] == "coco_graph.types" and tup[1] == "Send":
776
+ return _send_from_args(tup[2])
777
+ # module, name, args
778
+ return tup[2]
779
+ except Exception:
780
+ return
781
+ elif code == EXT_CONSTRUCTOR_KW_ARGS:
782
+ try:
783
+ tup = ormsgpack.unpackb(
784
+ data,
785
+ ext_hook=_msgpack_ext_hook_to_json,
786
+ option=ormsgpack.OPT_NON_STR_KEYS,
787
+ )
788
+ # module, name, args
789
+ return tup[2]
790
+ except Exception:
791
+ return
792
+ elif code == EXT_METHOD_SINGLE_ARG:
793
+ try:
794
+ tup = ormsgpack.unpackb(
795
+ data,
796
+ ext_hook=_msgpack_ext_hook_to_json,
797
+ option=ormsgpack.OPT_NON_STR_KEYS,
798
+ )
799
+ # module, name, arg, method
800
+ return tup[2]
801
+ except Exception:
802
+ return
803
+ elif code == EXT_PYDANTIC_V1:
804
+ try:
805
+ tup = ormsgpack.unpackb(
806
+ data,
807
+ ext_hook=_msgpack_ext_hook_to_json,
808
+ option=ormsgpack.OPT_NON_STR_KEYS,
809
+ )
810
+ # module, name, kwargs
811
+ return tup[2]
812
+ except Exception:
813
+ # for pydantic objects we can't find/reconstruct
814
+ # let's return the kwargs dict instead
815
+ return
816
+ elif code == EXT_PYDANTIC_V2:
817
+ try:
818
+ tup = ormsgpack.unpackb(
819
+ data,
820
+ ext_hook=_msgpack_ext_hook_to_json,
821
+ option=ormsgpack.OPT_NON_STR_KEYS,
822
+ )
823
+ # module, name, kwargs, method
824
+ return tup[2]
825
+ except Exception:
826
+ return
827
+ elif code == EXT_NUMPY_ARRAY:
828
+ try:
829
+ import numpy as _np
830
+
831
+ dtype_str, shape, order, buf = ormsgpack.unpackb(
832
+ data,
833
+ ext_hook=_msgpack_ext_hook_to_json,
834
+ option=ormsgpack.OPT_NON_STR_KEYS,
835
+ )
836
+ arr = _np.frombuffer(buf, dtype=_np.dtype(dtype_str))
837
+ return arr.reshape(shape, order=order).tolist()
838
+ except Exception:
839
+ return
840
+
841
+
842
+ class InvalidModuleError(Exception):
843
+ """Exception raised when a module is not in the allowlist."""
844
+
845
+ def __init__(self, message: str):
846
+ self.message = message
847
+
848
+
849
+ _option = (
850
+ ormsgpack.OPT_NON_STR_KEYS
851
+ | ormsgpack.OPT_PASSTHROUGH_DATACLASS
852
+ | ormsgpack.OPT_PASSTHROUGH_DATETIME
853
+ | ormsgpack.OPT_PASSTHROUGH_ENUM
854
+ | ormsgpack.OPT_PASSTHROUGH_UUID
855
+ | ormsgpack.OPT_REPLACE_SURROGATES
856
+ )
857
+
858
+
859
+ def _msgpack_enc(data: Any) -> bytes:
860
+ return ormsgpack.packb(data, default=_msgpack_default, option=_option)
861
+
862
+
863
+ def _normalize_allowlist(
864
+ allowlist: AllowedMsgpackModules | Literal[True] | None,
865
+ ) -> set[tuple[str, ...]] | Literal[True] | None:
866
+ if allowlist is True:
867
+ return allowlist
868
+ elif allowlist:
869
+ return _normalize_module_keys(allowlist)
870
+ else:
871
+ return None
872
+
873
+
874
+ def _normalize_module_keys(
875
+ modules: AllowedMsgpackModules,
876
+ ) -> set[tuple[str, ...]]:
877
+ normalized: set[tuple[str, ...]] = set()
878
+ for module in modules:
879
+ if isclass(module):
880
+ normalized.add((module.__module__, module.__name__))
881
+ else:
882
+ normalized.add(cast(tuple[str, ...], module))
883
+ return normalized