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,698 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ import pickle
6
+ import random
7
+ import shutil
8
+ from collections import defaultdict
9
+ from collections.abc import AsyncIterator, Iterator, Mapping, Sequence
10
+ from contextlib import AbstractAsyncContextManager, AbstractContextManager, ExitStack
11
+ from types import TracebackType
12
+ from typing import Any
13
+
14
+ from coco_core.runnables import RunnableConfig
15
+
16
+ from coco_graph.checkpoint.base import (
17
+ WRITES_IDX_MAP,
18
+ BaseCheckpointSaver,
19
+ ChannelVersions,
20
+ Checkpoint,
21
+ CheckpointMetadata,
22
+ CheckpointTuple,
23
+ DeltaChannelHistory,
24
+ PendingWrite,
25
+ SerializerProtocol,
26
+ get_checkpoint_id,
27
+ get_checkpoint_metadata,
28
+ )
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class InMemorySaver(
34
+ BaseCheckpointSaver[str], AbstractContextManager, AbstractAsyncContextManager
35
+ ):
36
+ """An in-memory checkpoint saver.
37
+
38
+ This checkpoint saver stores checkpoints in memory using a `defaultdict`.
39
+
40
+ Note:
41
+ Only use `InMemorySaver` for debugging or testing purposes.
42
+ For production use cases we recommend installing [trustcoco-graph-checkpoint-postgres](https://pypi.org/project/langgraph-checkpoint-postgres/) and using `PostgresSaver` / `AsyncPostgresSaver`.
43
+
44
+ If you are using LangSmith Deployment, no checkpointer needs to be specified. The correct managed checkpointer will be used automatically.
45
+
46
+ Args:
47
+ serde: The serializer to use for serializing and deserializing checkpoints.
48
+
49
+ Example:
50
+ ```python
51
+ import asyncio
52
+
53
+ from coco_graph.checkpoint.memory import InMemorySaver
54
+ from coco_graph.graph import StateGraph
55
+
56
+ builder = StateGraph(int)
57
+ builder.add_node("add_one", lambda x: x + 1)
58
+ builder.set_entry_point("add_one")
59
+ builder.set_finish_point("add_one")
60
+
61
+ memory = InMemorySaver()
62
+ graph = builder.compile(checkpointer=memory)
63
+ coro = graph.ainvoke(1, {"configurable": {"thread_id": "thread-1"}})
64
+ asyncio.run(coro) # Output: 2
65
+ ```
66
+ """
67
+
68
+ # thread ID -> checkpoint NS -> checkpoint ID -> checkpoint mapping
69
+ storage: defaultdict[
70
+ str,
71
+ dict[str, dict[str, tuple[tuple[str, bytes], tuple[str, bytes], str | None]]],
72
+ ]
73
+ # (thread ID, checkpoint NS, checkpoint ID) -> (task ID, write idx)
74
+ writes: defaultdict[
75
+ tuple[str, str, str],
76
+ dict[tuple[str, int], tuple[str, str, tuple[str, bytes], str]],
77
+ ]
78
+ blobs: dict[
79
+ tuple[
80
+ str, str, str, str | int | float
81
+ ], # thread id, checkpoint ns, channel, version
82
+ tuple[str, bytes],
83
+ ]
84
+
85
+ def __init__(
86
+ self,
87
+ *,
88
+ serde: SerializerProtocol | None = None,
89
+ factory: type[defaultdict] = defaultdict,
90
+ ) -> None:
91
+ super().__init__(serde=serde)
92
+ self.storage = factory(lambda: defaultdict(dict))
93
+ self.writes = factory(dict)
94
+ self.blobs = factory()
95
+ self.stack = ExitStack()
96
+ if factory is not defaultdict:
97
+ self.stack.enter_context(self.storage) # type: ignore[arg-type]
98
+ self.stack.enter_context(self.writes) # type: ignore[arg-type]
99
+ self.stack.enter_context(self.blobs) # type: ignore[arg-type]
100
+
101
+ def __enter__(self) -> InMemorySaver:
102
+ self.stack.__enter__()
103
+ return self
104
+
105
+ def __exit__(
106
+ self,
107
+ exc_type: type[BaseException] | None,
108
+ exc_value: BaseException | None,
109
+ traceback: TracebackType | None,
110
+ ) -> bool | None:
111
+ return self.stack.__exit__(exc_type, exc_value, traceback)
112
+
113
+ async def __aenter__(self) -> InMemorySaver:
114
+ self.stack.__enter__()
115
+ return self
116
+
117
+ async def __aexit__(
118
+ self,
119
+ __exc_type: type[BaseException] | None,
120
+ __exc_value: BaseException | None,
121
+ __traceback: TracebackType | None,
122
+ ) -> bool | None:
123
+ return self.stack.__exit__(__exc_type, __exc_value, __traceback)
124
+
125
+ def _load_blobs(
126
+ self,
127
+ thread_id: str,
128
+ checkpoint_ns: str,
129
+ versions: ChannelVersions,
130
+ ) -> dict[str, Any]:
131
+ result: dict[str, Any] = {}
132
+ for k, ver in versions.items():
133
+ kk = (thread_id, checkpoint_ns, k, ver)
134
+ if kk not in self.blobs:
135
+ continue
136
+ vv = self.blobs[kk]
137
+ if vv[0] == "empty":
138
+ continue
139
+ result[k] = self.serde.loads_typed(vv)
140
+ return result
141
+
142
+ def get_delta_channel_history(
143
+ self, *, config: RunnableConfig, channels: Sequence[str]
144
+ ) -> Mapping[str, DeltaChannelHistory]:
145
+ """Override: walk the parent chain ONCE for all requested channels.
146
+
147
+ Each channel terminates independently at the nearest ancestor
148
+ whose stored blob is non-empty. Other channels keep walking until
149
+ they find their own terminator or hit the root.
150
+
151
+ A blob is the value AT its ancestor, prior to the writes stored
152
+ under that same ancestor (those writes produce its child, which
153
+ is on the path to the target). This holds for `_DeltaSnapshot`
154
+ blobs and for pre-delta plain values alike, so the seed
155
+ ancestor's own writes are always collected. Writes at ancestors
156
+ older than the seed are subsumed by the seed value and are never
157
+ reached — the walk terminates there.
158
+ """
159
+ if not channels:
160
+ return {}
161
+ thread_id = config["configurable"]["thread_id"]
162
+ checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
163
+ checkpoint_id = config["configurable"].get("checkpoint_id", "")
164
+ ns_storage = self.storage.get(thread_id, {}).get(checkpoint_ns, {})
165
+
166
+ chain: list[str] = []
167
+ target_entry = ns_storage.get(checkpoint_id)
168
+ current: str | None = target_entry[2] if target_entry is not None else None
169
+ while current is not None:
170
+ entry = ns_storage.get(current)
171
+ if entry is None:
172
+ break
173
+ chain.append(current)
174
+ _, _, parent = entry
175
+ current = parent
176
+
177
+ collected_by_ch: dict[str, list[PendingWrite]] = {c: [] for c in channels}
178
+ seed_by_ch: dict[str, Any] = {}
179
+ remaining: set[str] = set(channels)
180
+
181
+ for cp_id in chain:
182
+ if not remaining:
183
+ break
184
+ entry = ns_storage.get(cp_id)
185
+ ckpt = self.serde.loads_typed(entry[0]) if entry is not None else None
186
+
187
+ terminated_here: set[str] = set()
188
+ blob_value_by_ch: dict[str, Any] = {}
189
+ if ckpt is not None:
190
+ versions = ckpt.get("channel_versions", {})
191
+ for ch in remaining:
192
+ ver = versions.get(ch)
193
+ if ver is None:
194
+ continue
195
+ blob_entry = self.blobs.get((thread_id, checkpoint_ns, ch, ver))
196
+ if blob_entry is None or blob_entry[0] == "empty":
197
+ continue
198
+ blob_value_by_ch[ch] = self.serde.loads_typed(blob_entry)
199
+ terminated_here.add(ch)
200
+
201
+ step_writes = self.writes.get((thread_id, checkpoint_ns, cp_id), {})
202
+ for (_task_id, _idx), (tid, ch, serialized, _) in sorted(
203
+ step_writes.items(), reverse=True
204
+ ):
205
+ if ch not in remaining:
206
+ continue
207
+ collected_by_ch[ch].append(
208
+ (tid, ch, self.serde.loads_typed(serialized))
209
+ )
210
+
211
+ for ch in terminated_here:
212
+ seed_by_ch[ch] = blob_value_by_ch[ch]
213
+ remaining.discard(ch)
214
+
215
+ result: dict[str, DeltaChannelHistory] = {}
216
+ for ch in channels:
217
+ entry_h: DeltaChannelHistory = {
218
+ "writes": list(reversed(collected_by_ch[ch]))
219
+ }
220
+ if ch in seed_by_ch:
221
+ entry_h["seed"] = seed_by_ch[ch]
222
+ result[ch] = entry_h
223
+ return result
224
+
225
+ async def aget_delta_channel_history(
226
+ self, *, config: RunnableConfig, channels: Sequence[str]
227
+ ) -> Mapping[str, DeltaChannelHistory]:
228
+ return self.get_delta_channel_history(config=config, channels=channels)
229
+
230
+ def get_tuple(self, config: RunnableConfig) -> CheckpointTuple | None:
231
+ """Get a checkpoint tuple from the in-memory storage.
232
+
233
+ This method retrieves a checkpoint tuple from the in-memory storage based on the
234
+ provided config. If the config contains a `checkpoint_id` key, the checkpoint with
235
+ the matching thread ID and timestamp is retrieved. Otherwise, the latest checkpoint
236
+ for the given thread ID is retrieved.
237
+
238
+ Args:
239
+ config: The config to use for retrieving the checkpoint.
240
+
241
+ Returns:
242
+ The retrieved checkpoint tuple, or None if no matching checkpoint was found.
243
+ """
244
+ thread_id: str = config["configurable"]["thread_id"]
245
+ checkpoint_ns: str = config["configurable"].get("checkpoint_ns", "")
246
+ if checkpoint_id := get_checkpoint_id(config):
247
+ if saved := self.storage[thread_id][checkpoint_ns].get(checkpoint_id):
248
+ checkpoint, metadata, parent_checkpoint_id = saved
249
+ writes = self.writes[(thread_id, checkpoint_ns, checkpoint_id)].values()
250
+ checkpoint_: Checkpoint = self.serde.loads_typed(checkpoint)
251
+ return CheckpointTuple(
252
+ config=config,
253
+ checkpoint={
254
+ **checkpoint_,
255
+ "channel_values": self._load_blobs(
256
+ thread_id, checkpoint_ns, checkpoint_["channel_versions"]
257
+ ),
258
+ },
259
+ metadata=self.serde.loads_typed(metadata),
260
+ pending_writes=[
261
+ (id, c, self.serde.loads_typed(v)) for id, c, v, _ in writes
262
+ ],
263
+ parent_config=(
264
+ {
265
+ "configurable": {
266
+ "thread_id": thread_id,
267
+ "checkpoint_ns": checkpoint_ns,
268
+ "checkpoint_id": parent_checkpoint_id,
269
+ }
270
+ }
271
+ if parent_checkpoint_id
272
+ else None
273
+ ),
274
+ )
275
+ else:
276
+ if checkpoints := self.storage[thread_id][checkpoint_ns]:
277
+ checkpoint_id = max(checkpoints.keys())
278
+ checkpoint, metadata, parent_checkpoint_id = checkpoints[checkpoint_id]
279
+ writes = self.writes[(thread_id, checkpoint_ns, checkpoint_id)].values()
280
+ checkpoint_ = self.serde.loads_typed(checkpoint)
281
+ return CheckpointTuple(
282
+ config={
283
+ "configurable": {
284
+ "thread_id": thread_id,
285
+ "checkpoint_ns": checkpoint_ns,
286
+ "checkpoint_id": checkpoint_id,
287
+ }
288
+ },
289
+ checkpoint={
290
+ **checkpoint_,
291
+ "channel_values": self._load_blobs(
292
+ thread_id, checkpoint_ns, checkpoint_["channel_versions"]
293
+ ),
294
+ },
295
+ metadata=self.serde.loads_typed(metadata),
296
+ pending_writes=[
297
+ (id, c, self.serde.loads_typed(v)) for id, c, v, _ in writes
298
+ ],
299
+ parent_config=(
300
+ {
301
+ "configurable": {
302
+ "thread_id": thread_id,
303
+ "checkpoint_ns": checkpoint_ns,
304
+ "checkpoint_id": parent_checkpoint_id,
305
+ }
306
+ }
307
+ if parent_checkpoint_id
308
+ else None
309
+ ),
310
+ )
311
+
312
+ def list(
313
+ self,
314
+ config: RunnableConfig | None,
315
+ *,
316
+ filter: dict[str, Any] | None = None,
317
+ before: RunnableConfig | None = None,
318
+ limit: int | None = None,
319
+ ) -> Iterator[CheckpointTuple]:
320
+ """List checkpoints from the in-memory storage.
321
+
322
+ This method retrieves a list of checkpoint tuples from the in-memory storage based
323
+ on the provided criteria.
324
+
325
+ Args:
326
+ config: Base configuration for filtering checkpoints.
327
+ filter: Additional filtering criteria for metadata.
328
+ before: List checkpoints created before this configuration.
329
+ limit: Maximum number of checkpoints to return.
330
+
331
+ Yields:
332
+ An iterator of matching checkpoint tuples.
333
+ """
334
+ thread_ids = (config["configurable"]["thread_id"],) if config else self.storage
335
+ config_checkpoint_ns = (
336
+ config["configurable"].get("checkpoint_ns") if config else None
337
+ )
338
+ config_checkpoint_id = get_checkpoint_id(config) if config else None
339
+ for thread_id in thread_ids:
340
+ for checkpoint_ns in self.storage[thread_id].keys():
341
+ if (
342
+ config_checkpoint_ns is not None
343
+ and checkpoint_ns != config_checkpoint_ns
344
+ ):
345
+ continue
346
+
347
+ for checkpoint_id, (
348
+ checkpoint,
349
+ metadata_b,
350
+ parent_checkpoint_id,
351
+ ) in sorted(
352
+ self.storage[thread_id][checkpoint_ns].items(),
353
+ key=lambda x: x[0],
354
+ reverse=True,
355
+ ):
356
+ # filter by checkpoint ID from config
357
+ if config_checkpoint_id and checkpoint_id != config_checkpoint_id:
358
+ continue
359
+
360
+ # filter by checkpoint ID from `before` config
361
+ if (
362
+ before
363
+ and (before_checkpoint_id := get_checkpoint_id(before))
364
+ and checkpoint_id >= before_checkpoint_id
365
+ ):
366
+ continue
367
+
368
+ # filter by metadata
369
+ metadata = self.serde.loads_typed(metadata_b)
370
+ if filter and not all(
371
+ query_value == metadata.get(query_key)
372
+ for query_key, query_value in filter.items()
373
+ ):
374
+ continue
375
+
376
+ # limit search results
377
+ if limit is not None and limit <= 0:
378
+ break
379
+ elif limit is not None:
380
+ limit -= 1
381
+
382
+ writes = self.writes[
383
+ (thread_id, checkpoint_ns, checkpoint_id)
384
+ ].values()
385
+
386
+ checkpoint_: Checkpoint = self.serde.loads_typed(checkpoint)
387
+
388
+ yield CheckpointTuple(
389
+ config={
390
+ "configurable": {
391
+ "thread_id": thread_id,
392
+ "checkpoint_ns": checkpoint_ns,
393
+ "checkpoint_id": checkpoint_id,
394
+ }
395
+ },
396
+ checkpoint={
397
+ **checkpoint_,
398
+ "channel_values": self._load_blobs(
399
+ thread_id,
400
+ checkpoint_ns,
401
+ checkpoint_["channel_versions"],
402
+ ),
403
+ },
404
+ metadata=metadata,
405
+ parent_config=(
406
+ {
407
+ "configurable": {
408
+ "thread_id": thread_id,
409
+ "checkpoint_ns": checkpoint_ns,
410
+ "checkpoint_id": parent_checkpoint_id,
411
+ }
412
+ }
413
+ if parent_checkpoint_id
414
+ else None
415
+ ),
416
+ pending_writes=[
417
+ (id, c, self.serde.loads_typed(v)) for id, c, v, _ in writes
418
+ ],
419
+ )
420
+
421
+ def put(
422
+ self,
423
+ config: RunnableConfig,
424
+ checkpoint: Checkpoint,
425
+ metadata: CheckpointMetadata,
426
+ new_versions: ChannelVersions,
427
+ ) -> RunnableConfig:
428
+ """Save a checkpoint to the in-memory storage.
429
+
430
+ This method saves a checkpoint to the in-memory storage. The checkpoint is associated
431
+ with the provided config.
432
+
433
+ Args:
434
+ config: The config to associate with the checkpoint.
435
+ checkpoint: The checkpoint to save.
436
+ metadata: Additional metadata to save with the checkpoint.
437
+ new_versions: New versions as of this write
438
+
439
+ Returns:
440
+ RunnableConfig: The updated config containing the saved checkpoint's timestamp.
441
+ """
442
+ c = checkpoint.copy()
443
+ thread_id = config["configurable"]["thread_id"]
444
+ checkpoint_ns = config["configurable"]["checkpoint_ns"]
445
+ values: dict[str, Any] = c.pop("channel_values") # type: ignore[misc]
446
+ for k, v in new_versions.items():
447
+ self.blobs[(thread_id, checkpoint_ns, k, v)] = (
448
+ self.serde.dumps_typed(values[k]) if k in values else ("empty", b"")
449
+ )
450
+ self.storage[thread_id][checkpoint_ns].update(
451
+ {
452
+ checkpoint["id"]: (
453
+ self.serde.dumps_typed(c),
454
+ self.serde.dumps_typed(get_checkpoint_metadata(config, metadata)),
455
+ config["configurable"].get("checkpoint_id"), # parent
456
+ )
457
+ }
458
+ )
459
+ return {
460
+ "configurable": {
461
+ "thread_id": thread_id,
462
+ "checkpoint_ns": checkpoint_ns,
463
+ "checkpoint_id": checkpoint["id"],
464
+ }
465
+ }
466
+
467
+ def put_writes(
468
+ self,
469
+ config: RunnableConfig,
470
+ writes: Sequence[tuple[str, Any]],
471
+ task_id: str,
472
+ task_path: str = "",
473
+ ) -> None:
474
+ """Save a list of writes to the in-memory storage.
475
+
476
+ This method saves a list of writes to the in-memory storage. The writes are associated
477
+ with the provided config.
478
+
479
+ Args:
480
+ config: The config to associate with the writes.
481
+ writes: The writes to save.
482
+ task_id: Identifier for the task creating the writes.
483
+ task_path: Path of the task creating the writes.
484
+
485
+ Returns:
486
+ RunnableConfig: The updated config containing the saved writes' timestamp.
487
+ """
488
+ thread_id = config["configurable"]["thread_id"]
489
+ checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
490
+ checkpoint_id = config["configurable"]["checkpoint_id"]
491
+ outer_key = (thread_id, checkpoint_ns, checkpoint_id)
492
+ outer_writes_ = self.writes.get(outer_key)
493
+ for idx, (c, v) in enumerate(writes):
494
+ inner_key = (task_id, WRITES_IDX_MAP.get(c, idx))
495
+ if inner_key[1] >= 0 and outer_writes_ and inner_key in outer_writes_:
496
+ continue
497
+
498
+ self.writes[outer_key][inner_key] = (
499
+ task_id,
500
+ c,
501
+ self.serde.dumps_typed(v),
502
+ task_path,
503
+ )
504
+
505
+ def delete_thread(self, thread_id: str) -> None:
506
+ """Delete all checkpoints and writes associated with a thread ID.
507
+
508
+ Args:
509
+ thread_id: The thread ID to delete.
510
+
511
+ Returns:
512
+ None
513
+ """
514
+ if thread_id in self.storage:
515
+ del self.storage[thread_id]
516
+ for k in list(self.writes.keys()):
517
+ if k[0] == thread_id:
518
+ del self.writes[k]
519
+ for k in list(self.blobs.keys()):
520
+ if k[0] == thread_id:
521
+ del self.blobs[k]
522
+
523
+ async def aget_tuple(self, config: RunnableConfig) -> CheckpointTuple | None:
524
+ """Asynchronous version of `get_tuple`.
525
+
526
+ This method is an asynchronous wrapper around `get_tuple` that runs the synchronous
527
+ method in a separate thread using asyncio.
528
+
529
+ Args:
530
+ config: The config to use for retrieving the checkpoint.
531
+
532
+ Returns:
533
+ The retrieved checkpoint tuple, or None if no matching checkpoint was found.
534
+ """
535
+ return self.get_tuple(config)
536
+
537
+ async def alist(
538
+ self,
539
+ config: RunnableConfig | None,
540
+ *,
541
+ filter: dict[str, Any] | None = None,
542
+ before: RunnableConfig | None = None,
543
+ limit: int | None = None,
544
+ ) -> AsyncIterator[CheckpointTuple]:
545
+ """Asynchronous version of `list`.
546
+
547
+ This method is an asynchronous wrapper around `list` that runs the synchronous
548
+ method in a separate thread using asyncio.
549
+
550
+ Args:
551
+ config: The config to use for listing the checkpoints.
552
+
553
+ Yields:
554
+ An asynchronous iterator of checkpoint tuples.
555
+ """
556
+ for item in self.list(config, filter=filter, before=before, limit=limit):
557
+ yield item
558
+
559
+ async def aput(
560
+ self,
561
+ config: RunnableConfig,
562
+ checkpoint: Checkpoint,
563
+ metadata: CheckpointMetadata,
564
+ new_versions: ChannelVersions,
565
+ ) -> RunnableConfig:
566
+ """Asynchronous version of `put`.
567
+
568
+ Args:
569
+ config: The config to associate with the checkpoint.
570
+ checkpoint: The checkpoint to save.
571
+ metadata: Additional metadata to save with the checkpoint.
572
+ new_versions: New versions as of this write
573
+
574
+ Returns:
575
+ RunnableConfig: The updated config containing the saved checkpoint's timestamp.
576
+ """
577
+ return self.put(config, checkpoint, metadata, new_versions)
578
+
579
+ async def aput_writes(
580
+ self,
581
+ config: RunnableConfig,
582
+ writes: Sequence[tuple[str, Any]],
583
+ task_id: str,
584
+ task_path: str = "",
585
+ ) -> None:
586
+ """Asynchronous version of `put_writes`.
587
+
588
+ This method is an asynchronous wrapper around `put_writes` that runs the synchronous
589
+ method in a separate thread using asyncio.
590
+
591
+ Args:
592
+ config: The config to associate with the writes.
593
+ writes: The writes to save, each as a (channel, value) pair.
594
+ task_id: Identifier for the task creating the writes.
595
+ task_path: Path of the task creating the writes.
596
+
597
+ Returns:
598
+ None
599
+ """
600
+ return self.put_writes(config, writes, task_id, task_path)
601
+
602
+ async def adelete_thread(self, thread_id: str) -> None:
603
+ """Delete all checkpoints and writes associated with a thread ID.
604
+
605
+ Args:
606
+ thread_id: The thread ID to delete.
607
+
608
+ Returns:
609
+ None
610
+ """
611
+ return self.delete_thread(thread_id)
612
+
613
+ def get_next_version(self, current: str | None, channel: None) -> str:
614
+ if current is None:
615
+ current_v = 0
616
+ elif isinstance(current, int):
617
+ current_v = current
618
+ else:
619
+ current_v = int(current.split(".")[0])
620
+ next_v = current_v + 1
621
+ next_h = random.random()
622
+ return f"{next_v:032}.{next_h:016}"
623
+
624
+
625
+ MemorySaver = InMemorySaver # Kept for backwards compatibility
626
+
627
+
628
+ class PersistentDict(defaultdict):
629
+ """Persistent dictionary with an API compatible with shelve and anydbm.
630
+
631
+ The dict is kept in memory, so the dictionary operations run as fast as
632
+ a regular dictionary.
633
+
634
+ Write to disk is delayed until close or sync (similar to gdbm's fast mode).
635
+
636
+ Input file format is automatically discovered.
637
+ Output file format is selectable between pickle, json, and csv.
638
+ All three serialization formats are backed by fast C implementations.
639
+
640
+ Adapted from https://code.activestate.com/recipes/576642-persistent-dict-with-multiple-standard-file-format/
641
+
642
+ """
643
+
644
+ def __init__(self, *args: Any, filename: str, **kwds: Any) -> None:
645
+ self.flag = "c" # r=readonly, c=create, or n=new
646
+ self.mode = None # None or an octal triple like 0644
647
+ self.format = "pickle" # 'csv', 'json', or 'pickle'
648
+ self.filename = filename
649
+ super().__init__(*args, **kwds)
650
+
651
+ def sync(self) -> None:
652
+ "Write dict to disk"
653
+ if self.flag == "r":
654
+ return
655
+ tempname = self.filename + ".tmp"
656
+ fileobj = open(tempname, "wb" if self.format == "pickle" else "w")
657
+ try:
658
+ self.dump(fileobj)
659
+ except Exception:
660
+ os.remove(tempname)
661
+ raise
662
+ finally:
663
+ fileobj.close()
664
+ shutil.move(tempname, self.filename) # atomic commit
665
+ if self.mode is not None:
666
+ os.chmod(self.filename, self.mode)
667
+
668
+ def close(self) -> None:
669
+ self.sync()
670
+ self.clear()
671
+
672
+ def __enter__(self) -> PersistentDict:
673
+ return self
674
+
675
+ def __exit__(self, *exc_info: Any) -> None:
676
+ self.close()
677
+
678
+ def dump(self, fileobj: Any) -> None:
679
+ if self.format == "pickle":
680
+ pickle.dump(dict(self), fileobj, 2)
681
+ else:
682
+ raise NotImplementedError("Unknown format: " + repr(self.format))
683
+
684
+ def load(self) -> None:
685
+ # try formats from most restrictive to least restrictive
686
+ if self.flag == "n":
687
+ return
688
+ with open(self.filename, "rb" if self.format == "pickle" else "r") as fileobj:
689
+ for loader in (pickle.load,):
690
+ fileobj.seek(0)
691
+ try:
692
+ return self.update(loader(fileobj))
693
+ except EOFError:
694
+ return
695
+ except Exception:
696
+ logger.error(f"Failed to load file: {fileobj.name}")
697
+ raise
698
+ raise ValueError("File not in a supported format")
File without changes