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,120 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import datetime
5
+ import sqlite3
6
+ import threading
7
+ from collections.abc import Mapping, Sequence
8
+
9
+ from coco_graph.cache.base import BaseCache, FullKey, Namespace, ValueT
10
+ from coco_graph.checkpoint.serde.base import SerializerProtocol
11
+
12
+
13
+ class SqliteCache(BaseCache[ValueT]):
14
+ """File-based cache using SQLite."""
15
+
16
+ def __init__(
17
+ self,
18
+ *,
19
+ path: str,
20
+ serde: SerializerProtocol | None = None,
21
+ ) -> None:
22
+ """Initialize the cache with a file path."""
23
+ super().__init__(serde=serde)
24
+ # SQLite backing store
25
+ self._conn = sqlite3.connect(
26
+ path,
27
+ check_same_thread=False,
28
+ )
29
+ # Serialize access to the shared connection across threads
30
+ self._lock = threading.RLock()
31
+ # Better concurrency & atomicity
32
+ self._conn.execute("PRAGMA journal_mode=WAL;")
33
+ # Schema: key -> (expiry, encoding, value)
34
+ self._conn.execute(
35
+ """CREATE TABLE IF NOT EXISTS cache (
36
+ ns TEXT,
37
+ key TEXT,
38
+ expiry REAL,
39
+ encoding TEXT NOT NULL,
40
+ val BLOB NOT NULL,
41
+ PRIMARY KEY (ns, key)
42
+ )"""
43
+ )
44
+ self._conn.commit()
45
+
46
+ def get(self, keys: Sequence[FullKey]) -> dict[FullKey, ValueT]:
47
+ """Get the cached values for the given keys."""
48
+ with self._lock, self._conn:
49
+ now = datetime.datetime.now(datetime.timezone.utc).timestamp()
50
+ if not keys:
51
+ return {}
52
+ placeholders = ",".join("(?, ?)" for _ in keys)
53
+ params: list[str] = []
54
+ for ns_tuple, key in keys:
55
+ params.extend((",".join(ns_tuple), key))
56
+ cursor = self._conn.execute(
57
+ f"SELECT ns, key, expiry, encoding, val FROM cache WHERE (ns, key) IN ({placeholders})",
58
+ tuple(params),
59
+ )
60
+ values: dict[FullKey, ValueT] = {}
61
+ rows = cursor.fetchall()
62
+ for ns, key, expiry, encoding, raw in rows:
63
+ if expiry is not None and now > expiry:
64
+ # purge expired entry
65
+ self._conn.execute(
66
+ "DELETE FROM cache WHERE (ns, key) = (?, ?)", (ns, key)
67
+ )
68
+ continue
69
+ values[(tuple(ns.split(",")), key)] = self.serde.loads_typed(
70
+ (encoding, raw)
71
+ )
72
+ return values
73
+
74
+ async def aget(self, keys: Sequence[FullKey]) -> dict[FullKey, ValueT]:
75
+ """Asynchronously get the cached values for the given keys."""
76
+ return await asyncio.to_thread(self.get, keys)
77
+
78
+ def set(self, mapping: Mapping[FullKey, tuple[ValueT, int | None]]) -> None:
79
+ """Set the cached values for the given keys and TTLs."""
80
+ with self._lock, self._conn:
81
+ now = datetime.datetime.now(datetime.timezone.utc)
82
+ for key, (value, ttl) in mapping.items():
83
+ if ttl is not None:
84
+ delta = datetime.timedelta(seconds=ttl)
85
+ expiry: float | None = (now + delta).timestamp()
86
+ else:
87
+ expiry = None
88
+ encoding, raw = self.serde.dumps_typed(value)
89
+ self._conn.execute(
90
+ "INSERT OR REPLACE INTO cache (ns, key, expiry, encoding, val) VALUES (?, ?, ?, ?, ?)",
91
+ (",".join(key[0]), key[1], expiry, encoding, raw),
92
+ )
93
+
94
+ async def aset(self, mapping: Mapping[FullKey, tuple[ValueT, int | None]]) -> None:
95
+ """Asynchronously set the cached values for the given keys and TTLs."""
96
+ await asyncio.to_thread(self.set, mapping)
97
+
98
+ def clear(self, namespaces: Sequence[Namespace] | None = None) -> None:
99
+ """Delete the cached values for the given namespaces.
100
+ If no namespaces are provided, clear all cached values."""
101
+ with self._lock, self._conn:
102
+ if namespaces is None:
103
+ self._conn.execute("DELETE FROM cache")
104
+ else:
105
+ placeholders = ",".join("?" for _ in namespaces)
106
+ self._conn.execute(
107
+ f"DELETE FROM cache WHERE (ns) IN ({placeholders})",
108
+ tuple(",".join(key) for key in namespaces),
109
+ )
110
+
111
+ async def aclear(self, namespaces: Sequence[Namespace] | None = None) -> None:
112
+ """Asynchronously delete the cached values for the given namespaces.
113
+ If no namespaces are provided, clear all cached values."""
114
+ await asyncio.to_thread(self.clear, namespaces)
115
+
116
+ def __del__(self) -> None:
117
+ try:
118
+ self._conn.close()
119
+ except Exception:
120
+ pass
@@ -0,0 +1,394 @@
1
+ """Graph lifecycle callback interfaces and event payloads.
2
+
3
+ This module defines the public callback surface for observing CocoGraph-specific
4
+ lifecycle transitions such as interrupt and resume.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Sequence
10
+ from dataclasses import dataclass
11
+ from typing import Any, Literal, TypeAlias, TypeVar
12
+ from uuid import UUID
13
+
14
+ from coco_core.callbacks import BaseCallbackHandler, BaseCallbackManager
15
+ from coco_core.callbacks.manager import ahandle_event, handle_event
16
+ from coco_core.runnables import RunnableConfig
17
+
18
+ from coco_graph.types import Interrupt
19
+
20
+ __all__ = (
21
+ "GraphCallbackHandler",
22
+ "GraphInterruptEvent",
23
+ "GraphLifecycleEvent",
24
+ "GraphLifecycleStatus",
25
+ "GraphResumeEvent",
26
+ "get_async_graph_callback_manager_for_config",
27
+ "get_sync_graph_callback_manager_for_config",
28
+ )
29
+
30
+
31
+ GraphLifecycleStatus: TypeAlias = Literal[
32
+ "input",
33
+ "pending",
34
+ "done",
35
+ "interrupt_before",
36
+ "interrupt_after",
37
+ "out_of_steps",
38
+ ]
39
+ """Allowed lifecycle statuses reported in graph lifecycle callback events."""
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class GraphInterruptEvent:
44
+ """Graph lifecycle event emitted when execution pauses for interrupts."""
45
+
46
+ run_id: UUID | None
47
+ """Run id for the current graph execution, if available."""
48
+
49
+ status: GraphLifecycleStatus
50
+ """Loop status when the interrupt was captured."""
51
+
52
+ checkpoint_id: str
53
+ """Checkpoint id associated with the interrupted execution."""
54
+
55
+ checkpoint_ns: tuple[str, ...]
56
+ """Checkpoint namespace path for the current graph or subgraph."""
57
+
58
+ interrupts: tuple[Interrupt, ...]
59
+ """Interrupt payloads that caused the graph to pause."""
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class GraphResumeEvent:
64
+ """Graph lifecycle event emitted when execution resumes from a checkpoint."""
65
+
66
+ run_id: UUID | None
67
+ """Run id for the current graph execution, if available."""
68
+
69
+ status: GraphLifecycleStatus
70
+ """Loop status when the resume was captured."""
71
+
72
+ checkpoint_id: str
73
+ """Checkpoint id the graph resumed from."""
74
+
75
+ checkpoint_ns: tuple[str, ...]
76
+ """Checkpoint namespace path for the current graph or subgraph."""
77
+
78
+
79
+ GraphLifecycleEvent: TypeAlias = GraphInterruptEvent | GraphResumeEvent
80
+ """Union of all public graph lifecycle callback event payloads.
81
+
82
+ Use this alias when a callback or helper can receive either interrupt or resume
83
+ lifecycle events.
84
+ """
85
+
86
+
87
+ class GraphCallbackHandler(BaseCallbackHandler):
88
+ """Base class for graph-level lifecycle callbacks.
89
+
90
+ Subclass this handler to observe graph lifecycle transitions that are
91
+ specific to CocoGraph execution, rather than generic Coco runnable
92
+ callbacks.
93
+
94
+ Instances can be passed through `config["callbacks"]` when invoking a
95
+ graph. Only handlers that inherit from `GraphCallbackHandler` receive these
96
+ lifecycle events.
97
+ """
98
+
99
+ def on_interrupt(self, event: GraphInterruptEvent) -> Any:
100
+ """Run when graph execution pauses due to one or more interrupts.
101
+
102
+ Args:
103
+ event: Interrupt lifecycle event payload.
104
+ """
105
+
106
+ def on_resume(self, event: GraphResumeEvent) -> Any:
107
+ """Run when graph execution resumes from a persisted checkpoint.
108
+
109
+ Args:
110
+ event: Resume lifecycle event payload.
111
+ """
112
+
113
+
114
+ _MISSING = object()
115
+
116
+
117
+ def _filter_graph_handlers(
118
+ handlers: list[BaseCallbackHandler],
119
+ ) -> list[GraphCallbackHandler]:
120
+ return [h for h in handlers if isinstance(h, GraphCallbackHandler)]
121
+
122
+
123
+ def _init_base_manager(
124
+ manager: BaseCallbackManager,
125
+ handlers: Sequence[GraphCallbackHandler] | None,
126
+ inheritable_handlers: Sequence[GraphCallbackHandler] | None,
127
+ parent_run_id: UUID | None,
128
+ *,
129
+ tags: list[str] | None,
130
+ inheritable_tags: list[str] | None,
131
+ metadata: dict[str, Any] | None,
132
+ inheritable_metadata: dict[str, Any] | None,
133
+ run_id: UUID | None,
134
+ ) -> None:
135
+ base_handlers: list[BaseCallbackHandler] = []
136
+ base_inheritable_handlers: list[BaseCallbackHandler] = []
137
+ if handlers is not None:
138
+ base_handlers.extend(handlers)
139
+ if inheritable_handlers is not None:
140
+ base_inheritable_handlers.extend(inheritable_handlers)
141
+ BaseCallbackManager.__init__(
142
+ manager,
143
+ handlers=base_handlers,
144
+ inheritable_handlers=base_inheritable_handlers,
145
+ parent_run_id=parent_run_id,
146
+ tags=tags,
147
+ inheritable_tags=inheritable_tags,
148
+ metadata=metadata,
149
+ inheritable_metadata=inheritable_metadata,
150
+ )
151
+ manager.run_id = run_id # type: ignore[attr-defined]
152
+
153
+
154
+ def _configure_graph_callbacks(
155
+ cls: type[_GraphManagerT],
156
+ callbacks: object | None,
157
+ *,
158
+ run_id: UUID | None,
159
+ ) -> _GraphManagerT:
160
+ if callbacks is None:
161
+ return cls(run_id=run_id)
162
+ if isinstance(callbacks, cls):
163
+ return callbacks.copy(run_id=run_id)
164
+ if isinstance(callbacks, (_GraphCallbackManager, _AsyncGraphCallbackManager)):
165
+ # Cross-type: extract handlers into the requested cls.
166
+ return cls(
167
+ handlers=_filter_graph_handlers(callbacks.handlers),
168
+ inheritable_handlers=_filter_graph_handlers(callbacks.inheritable_handlers),
169
+ parent_run_id=callbacks.parent_run_id,
170
+ tags=callbacks.tags.copy(),
171
+ inheritable_tags=callbacks.inheritable_tags.copy(),
172
+ metadata=callbacks.metadata.copy(),
173
+ inheritable_metadata=callbacks.inheritable_metadata.copy(),
174
+ run_id=run_id,
175
+ )
176
+ if isinstance(callbacks, BaseCallbackManager):
177
+ return cls(
178
+ handlers=_filter_graph_handlers(callbacks.handlers),
179
+ inheritable_handlers=_filter_graph_handlers(callbacks.inheritable_handlers),
180
+ parent_run_id=callbacks.parent_run_id,
181
+ tags=callbacks.tags.copy(),
182
+ inheritable_tags=callbacks.inheritable_tags.copy(),
183
+ metadata=callbacks.metadata.copy(),
184
+ inheritable_metadata=callbacks.inheritable_metadata.copy(),
185
+ run_id=run_id,
186
+ )
187
+ if isinstance(callbacks, GraphCallbackHandler):
188
+ return cls((callbacks,), run_id=run_id)
189
+ if isinstance(callbacks, (str, bytes)) or not isinstance(callbacks, Sequence):
190
+ raise TypeError("callbacks must be a handler, sequence, or manager")
191
+ return cls(_filter_graph_handlers(list(callbacks)), run_id=run_id)
192
+
193
+
194
+ def _copy_graph_manager(
195
+ manager: _GraphCallbackManager | _AsyncGraphCallbackManager,
196
+ cls: type[_GraphManagerT],
197
+ run_id: UUID | None | object,
198
+ ) -> _GraphManagerT:
199
+ resolved_run_id: UUID | None
200
+ if run_id is _MISSING:
201
+ resolved_run_id = manager.run_id
202
+ else:
203
+ if run_id is not None and not isinstance(run_id, UUID):
204
+ raise TypeError("run_id must be a UUID or None")
205
+ resolved_run_id = run_id
206
+
207
+ return cls(
208
+ handlers=_filter_graph_handlers(manager.handlers),
209
+ inheritable_handlers=_filter_graph_handlers(manager.inheritable_handlers),
210
+ parent_run_id=manager.parent_run_id,
211
+ tags=manager.tags.copy(),
212
+ inheritable_tags=manager.inheritable_tags.copy(),
213
+ metadata=manager.metadata.copy(),
214
+ inheritable_metadata=manager.inheritable_metadata.copy(),
215
+ run_id=resolved_run_id,
216
+ )
217
+
218
+
219
+ class _GraphCallbackManager(BaseCallbackManager):
220
+ """Sync dispatcher for graph lifecycle events."""
221
+
222
+ run_id: UUID | None
223
+
224
+ def __init__(
225
+ self,
226
+ handlers: Sequence[GraphCallbackHandler] | None = None,
227
+ inheritable_handlers: Sequence[GraphCallbackHandler] | None = None,
228
+ parent_run_id: UUID | None = None,
229
+ *,
230
+ tags: list[str] | None = None,
231
+ inheritable_tags: list[str] | None = None,
232
+ metadata: dict[str, Any] | None = None,
233
+ inheritable_metadata: dict[str, Any] | None = None,
234
+ run_id: UUID | None = None,
235
+ ) -> None:
236
+ _init_base_manager(
237
+ self,
238
+ handlers,
239
+ inheritable_handlers,
240
+ parent_run_id,
241
+ tags=tags,
242
+ inheritable_tags=inheritable_tags,
243
+ metadata=metadata,
244
+ inheritable_metadata=inheritable_metadata,
245
+ run_id=run_id,
246
+ )
247
+
248
+ def copy(
249
+ self,
250
+ *,
251
+ run_id: UUID | None | object = _MISSING,
252
+ ) -> _GraphCallbackManager:
253
+ return _copy_graph_manager(self, _GraphCallbackManager, run_id)
254
+
255
+ @classmethod
256
+ def configure(
257
+ cls,
258
+ callbacks: object | None = None,
259
+ *,
260
+ run_id: UUID | None = None,
261
+ ) -> _GraphCallbackManager:
262
+ return _configure_graph_callbacks(cls, callbacks, run_id=run_id)
263
+
264
+ def on_interrupt(self, event: GraphInterruptEvent) -> None:
265
+ handle_event(
266
+ self.handlers,
267
+ "on_interrupt",
268
+ None,
269
+ event,
270
+ )
271
+
272
+ def on_resume(self, event: GraphResumeEvent) -> None:
273
+ handle_event(
274
+ self.handlers,
275
+ "on_resume",
276
+ None,
277
+ event,
278
+ )
279
+
280
+
281
+ class _AsyncGraphCallbackManager(BaseCallbackManager):
282
+ """Async dispatcher for graph lifecycle events."""
283
+
284
+ run_id: UUID | None
285
+
286
+ @property
287
+ def is_async(self) -> bool:
288
+ """Return whether the manager is async."""
289
+ return True
290
+
291
+ def __init__(
292
+ self,
293
+ handlers: Sequence[GraphCallbackHandler] | None = None,
294
+ inheritable_handlers: Sequence[GraphCallbackHandler] | None = None,
295
+ parent_run_id: UUID | None = None,
296
+ *,
297
+ tags: list[str] | None = None,
298
+ inheritable_tags: list[str] | None = None,
299
+ metadata: dict[str, Any] | None = None,
300
+ inheritable_metadata: dict[str, Any] | None = None,
301
+ run_id: UUID | None = None,
302
+ ) -> None:
303
+ _init_base_manager(
304
+ self,
305
+ handlers,
306
+ inheritable_handlers,
307
+ parent_run_id,
308
+ tags=tags,
309
+ inheritable_tags=inheritable_tags,
310
+ metadata=metadata,
311
+ inheritable_metadata=inheritable_metadata,
312
+ run_id=run_id,
313
+ )
314
+
315
+ def copy(
316
+ self,
317
+ *,
318
+ run_id: UUID | None | object = _MISSING,
319
+ ) -> _AsyncGraphCallbackManager:
320
+ return _copy_graph_manager(self, _AsyncGraphCallbackManager, run_id)
321
+
322
+ @classmethod
323
+ def configure(
324
+ cls,
325
+ callbacks: object | None = None,
326
+ *,
327
+ run_id: UUID | None = None,
328
+ ) -> _AsyncGraphCallbackManager:
329
+ return _configure_graph_callbacks(cls, callbacks, run_id=run_id)
330
+
331
+ async def on_interrupt(self, event: GraphInterruptEvent) -> None:
332
+ await ahandle_event(
333
+ self.handlers,
334
+ "on_interrupt",
335
+ None,
336
+ event,
337
+ )
338
+
339
+ async def on_resume(self, event: GraphResumeEvent) -> None:
340
+ await ahandle_event(
341
+ self.handlers,
342
+ "on_resume",
343
+ None,
344
+ event,
345
+ )
346
+
347
+
348
+ _GraphManagerT = TypeVar(
349
+ "_GraphManagerT", _GraphCallbackManager, _AsyncGraphCallbackManager
350
+ )
351
+
352
+ GraphCallbacks: TypeAlias = (
353
+ _GraphCallbackManager
354
+ | _AsyncGraphCallbackManager
355
+ | BaseCallbackManager
356
+ | GraphCallbackHandler
357
+ | Sequence[BaseCallbackHandler]
358
+ | Sequence[GraphCallbackHandler]
359
+ | None
360
+ )
361
+
362
+
363
+ def get_sync_graph_callback_manager_for_config(
364
+ config: RunnableConfig,
365
+ *,
366
+ run_id: UUID | None = None,
367
+ ) -> _GraphCallbackManager:
368
+ """Build a sync graph lifecycle callback manager from a runnable config.
369
+
370
+ This helper filters `config["callbacks"]` down to handlers that inherit
371
+ from `GraphCallbackHandler` and binds the provided `run_id` onto the
372
+ returned manager.
373
+ """
374
+ return _GraphCallbackManager.configure(
375
+ config.get("callbacks"),
376
+ run_id=run_id,
377
+ )
378
+
379
+
380
+ def get_async_graph_callback_manager_for_config(
381
+ config: RunnableConfig,
382
+ *,
383
+ run_id: UUID | None = None,
384
+ ) -> _AsyncGraphCallbackManager:
385
+ """Build an async graph lifecycle callback manager from a runnable config.
386
+
387
+ This helper filters `config["callbacks"]` down to handlers that inherit
388
+ from `GraphCallbackHandler` and binds the provided `run_id` onto the
389
+ returned manager.
390
+ """
391
+ return _AsyncGraphCallbackManager.configure(
392
+ config.get("callbacks"),
393
+ run_id=run_id,
394
+ )
@@ -0,0 +1,29 @@
1
+ from coco_graph.channels.any_value import AnyValue
2
+ from coco_graph.channels.base import BaseChannel
3
+ from coco_graph.channels.binop import BinaryOperatorAggregate
4
+ from coco_graph.channels.delta import DeltaChannel
5
+ from coco_graph.channels.ephemeral_value import EphemeralValue
6
+ from coco_graph.channels.last_value import LastValue, LastValueAfterFinish
7
+ from coco_graph.channels.named_barrier_value import (
8
+ NamedBarrierValue,
9
+ NamedBarrierValueAfterFinish,
10
+ )
11
+ from coco_graph.channels.topic import Topic
12
+ from coco_graph.channels.untracked_value import UntrackedValue
13
+
14
+ __all__ = (
15
+ # base
16
+ "BaseChannel",
17
+ # value types
18
+ "AnyValue",
19
+ "LastValue",
20
+ "LastValueAfterFinish",
21
+ "UntrackedValue",
22
+ "EphemeralValue",
23
+ "BinaryOperatorAggregate",
24
+ "DeltaChannel",
25
+ "NamedBarrierValue",
26
+ "NamedBarrierValueAfterFinish",
27
+ # topics
28
+ "Topic",
29
+ )
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from typing import Any, Generic
5
+
6
+ from typing_extensions import Self
7
+
8
+ from coco_graph._internal._typing import MISSING
9
+ from coco_graph.channels.base import BaseChannel, Value
10
+ from coco_graph.errors import EmptyChannelError
11
+
12
+ __all__ = ("AnyValue",)
13
+
14
+
15
+ class AnyValue(Generic[Value], BaseChannel[Value, Value, Value]):
16
+ """Stores the last value received, assumes that if multiple values are
17
+ received, they are all equal."""
18
+
19
+ __slots__ = ("typ", "value")
20
+
21
+ value: Value | Any
22
+
23
+ def __init__(self, typ: Any, key: str = "") -> None:
24
+ super().__init__(typ, key)
25
+ self.value = MISSING
26
+
27
+ def __eq__(self, value: object) -> bool:
28
+ return isinstance(value, AnyValue)
29
+
30
+ @property
31
+ def ValueType(self) -> type[Value]:
32
+ """The type of the value stored in the channel."""
33
+ return self.typ
34
+
35
+ @property
36
+ def UpdateType(self) -> type[Value]:
37
+ """The type of the update received by the channel."""
38
+ return self.typ
39
+
40
+ def copy(self) -> Self:
41
+ """Return a copy of the channel."""
42
+ empty = self.__class__(self.typ, self.key)
43
+ empty.value = self.value
44
+ return empty
45
+
46
+ def from_checkpoint(self, checkpoint: Value) -> Self:
47
+ empty = self.__class__(self.typ, self.key)
48
+ if checkpoint is not MISSING:
49
+ empty.value = checkpoint
50
+ return empty
51
+
52
+ def update(self, values: Sequence[Value]) -> bool:
53
+ if len(values) == 0:
54
+ if self.value is MISSING:
55
+ return False
56
+ else:
57
+ self.value = MISSING
58
+ return True
59
+
60
+ self.value = values[-1]
61
+ return True
62
+
63
+ def get(self) -> Value:
64
+ if self.value is MISSING:
65
+ raise EmptyChannelError()
66
+ return self.value
67
+
68
+ def is_available(self) -> bool:
69
+ return self.value is not MISSING
70
+
71
+ def checkpoint(self) -> Value:
72
+ return self.value