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,371 @@
1
+ """Utilities for batching operations in a background task."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import functools
7
+ import weakref
8
+ from collections.abc import Callable, Iterable, Mapping
9
+ from typing import Any, Literal, TypeVar
10
+
11
+ from coco_graph.store.base import (
12
+ NOT_PROVIDED,
13
+ BaseStore,
14
+ GetOp,
15
+ Item,
16
+ ListNamespacesOp,
17
+ MatchCondition,
18
+ NamespacePath,
19
+ NotProvided,
20
+ Op,
21
+ PutOp,
22
+ Result,
23
+ SearchItem,
24
+ SearchOp,
25
+ _ensure_refresh,
26
+ _ensure_ttl,
27
+ _validate_namespace,
28
+ )
29
+
30
+ F = TypeVar("F", bound=Callable)
31
+
32
+
33
+ def _check_loop(func: F) -> F:
34
+ @functools.wraps(func)
35
+ def wrapper(store: AsyncBatchedBaseStore, *args: Any, **kwargs: Any) -> Any:
36
+ method_name: str = func.__name__
37
+ try:
38
+ current_loop = asyncio.get_running_loop()
39
+ if current_loop is store._loop:
40
+ replacement_str = (
41
+ f"Specifically, replace `store.{method_name}(...)` with `await store.a{method_name}(...)"
42
+ if method_name
43
+ else "For example, replace `store.get(...)` with `await store.aget(...)`"
44
+ )
45
+ raise asyncio.InvalidStateError(
46
+ f"Synchronous calls to {store.__class__.__name__} detected in the main event loop. "
47
+ "This can lead to deadlocks or performance issues. "
48
+ "Please use the asynchronous interface for main thread operations. "
49
+ f"{replacement_str} "
50
+ )
51
+ except RuntimeError:
52
+ pass
53
+ return func(store, *args, **kwargs)
54
+
55
+ return wrapper
56
+
57
+
58
+ class AsyncBatchedBaseStore(BaseStore):
59
+ """Efficiently batch operations in a background task."""
60
+
61
+ __slots__ = ("_loop", "_aqueue", "_task")
62
+
63
+ def __init__(self) -> None:
64
+ super().__init__()
65
+ self._loop = asyncio.get_running_loop()
66
+ self._aqueue: asyncio.Queue[tuple[asyncio.Future, Op]] = asyncio.Queue()
67
+ self._task: asyncio.Task | None = None
68
+ self._ensure_task()
69
+
70
+ def __del__(self) -> None:
71
+ try:
72
+ if self._task:
73
+ self._task.cancel()
74
+ except RuntimeError:
75
+ pass
76
+
77
+ def _ensure_task(self) -> None:
78
+ """Ensure the background processing loop is running."""
79
+ if self._task is None or self._task.done():
80
+ self._task = self._loop.create_task(_run(self._aqueue, weakref.ref(self)))
81
+
82
+ async def aget(
83
+ self,
84
+ namespace: tuple[str, ...],
85
+ key: str,
86
+ *,
87
+ refresh_ttl: bool | None = None,
88
+ ) -> Item | None:
89
+ self._ensure_task()
90
+ fut = self._loop.create_future()
91
+ self._aqueue.put_nowait(
92
+ (
93
+ fut,
94
+ GetOp(
95
+ namespace,
96
+ key,
97
+ refresh_ttl=_ensure_refresh(self.ttl_config, refresh_ttl),
98
+ ),
99
+ )
100
+ )
101
+ return await fut
102
+
103
+ async def asearch(
104
+ self,
105
+ namespace_prefix: tuple[str, ...],
106
+ /,
107
+ *,
108
+ query: str | None = None,
109
+ filter: dict[str, Any] | None = None,
110
+ limit: int = 10,
111
+ offset: int = 0,
112
+ refresh_ttl: bool | None = None,
113
+ ) -> list[SearchItem]:
114
+ self._ensure_task()
115
+ fut = self._loop.create_future()
116
+ self._aqueue.put_nowait(
117
+ (
118
+ fut,
119
+ SearchOp(
120
+ namespace_prefix,
121
+ filter,
122
+ limit,
123
+ offset,
124
+ query,
125
+ refresh_ttl=_ensure_refresh(self.ttl_config, refresh_ttl),
126
+ ),
127
+ )
128
+ )
129
+ return await fut
130
+
131
+ async def aput(
132
+ self,
133
+ namespace: tuple[str, ...],
134
+ key: str,
135
+ value: Mapping[str, Any],
136
+ index: Literal[False] | list[str] | None = None,
137
+ *,
138
+ ttl: float | None | NotProvided = NOT_PROVIDED,
139
+ ) -> None:
140
+ self._ensure_task()
141
+ _validate_namespace(namespace)
142
+ fut = self._loop.create_future()
143
+ self._aqueue.put_nowait(
144
+ (
145
+ fut,
146
+ PutOp(
147
+ namespace, key, value, index, ttl=_ensure_ttl(self.ttl_config, ttl)
148
+ ),
149
+ )
150
+ )
151
+ return await fut
152
+
153
+ async def adelete(
154
+ self,
155
+ namespace: tuple[str, ...],
156
+ key: str,
157
+ ) -> None:
158
+ self._ensure_task()
159
+ fut = self._loop.create_future()
160
+ self._aqueue.put_nowait((fut, PutOp(namespace, key, None)))
161
+ return await fut
162
+
163
+ async def alist_namespaces(
164
+ self,
165
+ *,
166
+ prefix: NamespacePath | None = None,
167
+ suffix: NamespacePath | None = None,
168
+ max_depth: int | None = None,
169
+ limit: int = 100,
170
+ offset: int = 0,
171
+ ) -> list[tuple[str, ...]]:
172
+ self._ensure_task()
173
+ fut = self._loop.create_future()
174
+ match_conditions = []
175
+ if prefix:
176
+ match_conditions.append(MatchCondition(match_type="prefix", path=prefix))
177
+ if suffix:
178
+ match_conditions.append(MatchCondition(match_type="suffix", path=suffix))
179
+
180
+ op = ListNamespacesOp(
181
+ match_conditions=tuple(match_conditions),
182
+ max_depth=max_depth,
183
+ limit=limit,
184
+ offset=offset,
185
+ )
186
+ self._aqueue.put_nowait((fut, op))
187
+ return await fut
188
+
189
+ @_check_loop
190
+ def batch(self, ops: Iterable[Op]) -> list[Result]:
191
+ return asyncio.run_coroutine_threadsafe(self.abatch(ops), self._loop).result()
192
+
193
+ @_check_loop
194
+ def get(
195
+ self,
196
+ namespace: tuple[str, ...],
197
+ key: str,
198
+ *,
199
+ refresh_ttl: bool | None = None,
200
+ ) -> Item | None:
201
+ return asyncio.run_coroutine_threadsafe(
202
+ self.aget(namespace, key=key, refresh_ttl=refresh_ttl), self._loop
203
+ ).result()
204
+
205
+ @_check_loop
206
+ def search(
207
+ self,
208
+ namespace_prefix: tuple[str, ...],
209
+ /,
210
+ *,
211
+ query: str | None = None,
212
+ filter: dict[str, Any] | None = None,
213
+ limit: int = 10,
214
+ offset: int = 0,
215
+ refresh_ttl: bool | None = None,
216
+ ) -> list[SearchItem]:
217
+ return asyncio.run_coroutine_threadsafe(
218
+ self.asearch(
219
+ namespace_prefix,
220
+ query=query,
221
+ filter=filter,
222
+ limit=limit,
223
+ offset=offset,
224
+ refresh_ttl=refresh_ttl,
225
+ ),
226
+ self._loop,
227
+ ).result()
228
+
229
+ @_check_loop
230
+ def put(
231
+ self,
232
+ namespace: tuple[str, ...],
233
+ key: str,
234
+ value: Mapping[str, Any],
235
+ index: Literal[False] | list[str] | None = None,
236
+ *,
237
+ ttl: float | None | NotProvided = NOT_PROVIDED,
238
+ ) -> None:
239
+ _validate_namespace(namespace)
240
+ asyncio.run_coroutine_threadsafe(
241
+ self.aput(
242
+ namespace,
243
+ key=key,
244
+ value=value,
245
+ index=index,
246
+ ttl=_ensure_ttl(self.ttl_config, ttl),
247
+ ),
248
+ self._loop,
249
+ ).result()
250
+
251
+ @_check_loop
252
+ def delete(
253
+ self,
254
+ namespace: tuple[str, ...],
255
+ key: str,
256
+ ) -> None:
257
+ asyncio.run_coroutine_threadsafe(
258
+ self.adelete(namespace, key=key), self._loop
259
+ ).result()
260
+
261
+ @_check_loop
262
+ def list_namespaces(
263
+ self,
264
+ *,
265
+ prefix: NamespacePath | None = None,
266
+ suffix: NamespacePath | None = None,
267
+ max_depth: int | None = None,
268
+ limit: int = 100,
269
+ offset: int = 0,
270
+ ) -> list[tuple[str, ...]]:
271
+ return asyncio.run_coroutine_threadsafe(
272
+ self.alist_namespaces(
273
+ prefix=prefix,
274
+ suffix=suffix,
275
+ max_depth=max_depth,
276
+ limit=limit,
277
+ offset=offset,
278
+ ),
279
+ self._loop,
280
+ ).result()
281
+
282
+
283
+ def _dedupe_ops(values: list[Op]) -> tuple[list[int] | None, list[Op]]:
284
+ """Dedupe operations while preserving order for results.
285
+
286
+ Args:
287
+ values: List of operations to dedupe
288
+
289
+ Returns:
290
+ Tuple of (listen indices, deduped operations)
291
+ where listen indices map deduped operation results back to original positions
292
+ """
293
+ if len(values) <= 1:
294
+ return None, list(values)
295
+
296
+ dedupped: list[Op] = []
297
+ listen: list[int] = []
298
+ puts: dict[tuple[tuple[str, ...], str], int] = {}
299
+
300
+ for op in values:
301
+ if isinstance(op, (GetOp, SearchOp, ListNamespacesOp)):
302
+ try:
303
+ listen.append(dedupped.index(op))
304
+ except ValueError:
305
+ listen.append(len(dedupped))
306
+ dedupped.append(op)
307
+ elif isinstance(op, PutOp):
308
+ putkey = (op.namespace, op.key)
309
+ if putkey in puts:
310
+ # Overwrite previous put
311
+ ix = puts[putkey]
312
+ dedupped[ix] = op
313
+ listen.append(ix)
314
+ else:
315
+ puts[putkey] = len(dedupped)
316
+ listen.append(len(dedupped))
317
+ dedupped.append(op)
318
+
319
+ else: # Any new ops will be treated regularly
320
+ listen.append(len(dedupped))
321
+ dedupped.append(op)
322
+
323
+ return listen, dedupped
324
+
325
+
326
+ async def _run(
327
+ aqueue: asyncio.Queue[tuple[asyncio.Future, Op]],
328
+ store: weakref.ReferenceType[BaseStore],
329
+ ) -> None:
330
+ while item := await aqueue.get():
331
+ # don't run batch if the future is done (e.g. cancelled)
332
+ if item[0].done():
333
+ continue
334
+ # check if store is still alive
335
+ if s := store():
336
+ try:
337
+ # accumulate operations scheduled in same tick
338
+ items = [item]
339
+ try:
340
+ while item := aqueue.get_nowait():
341
+ # don't insert if the future is done (e.g. cancelled)
342
+ if item[0].done():
343
+ continue
344
+ items.append(item)
345
+ except asyncio.QueueEmpty:
346
+ pass
347
+ # get the operations to run
348
+ futs = [item[0] for item in items]
349
+ values = [item[1] for item in items]
350
+ # action each operation
351
+ try:
352
+ listen, dedupped = _dedupe_ops(values)
353
+ results = await s.abatch(dedupped)
354
+ if listen is not None:
355
+ results = [results[ix] for ix in listen]
356
+
357
+ # set the results of each operation
358
+ for fut, result in zip(futs, results, strict=False):
359
+ # guard against future being done (e.g. cancelled)
360
+ if not fut.done():
361
+ fut.set_result(result)
362
+ except Exception as e:
363
+ for fut in futs:
364
+ # guard against future being done (e.g. cancelled)
365
+ if not fut.done():
366
+ fut.set_exception(e)
367
+ finally:
368
+ # remove strong ref to store
369
+ del s
370
+ else:
371
+ break