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,213 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import types
5
+ import weakref
6
+ from collections.abc import Generator, Sequence
7
+ from typing import Annotated, Any, Optional, Union, get_origin, get_type_hints
8
+
9
+ from pydantic import BaseModel
10
+ from typing_extensions import NotRequired, ReadOnly, Required
11
+
12
+ from coco_graph._internal._typing import MISSING
13
+
14
+
15
+ def _is_optional_type(type_: Any) -> bool:
16
+ """Check if a type is Optional."""
17
+
18
+ # Handle new union syntax (PEP 604): str | None
19
+ if isinstance(type_, types.UnionType):
20
+ return any(
21
+ arg is type(None) or _is_optional_type(arg) for arg in type_.__args__
22
+ )
23
+
24
+ if hasattr(type_, "__origin__") and hasattr(type_, "__args__"):
25
+ origin = get_origin(type_)
26
+ if origin is Optional:
27
+ return True
28
+ if origin is Union:
29
+ return any(
30
+ arg is type(None) or _is_optional_type(arg) for arg in type_.__args__
31
+ )
32
+ if origin is Annotated:
33
+ return _is_optional_type(type_.__args__[0])
34
+ return origin is None
35
+ if hasattr(type_, "__bound__") and type_.__bound__ is not None:
36
+ return _is_optional_type(type_.__bound__)
37
+ return type_ is None
38
+
39
+
40
+ def _is_required_type(type_: Any) -> bool | None:
41
+ """Check if an annotation is marked as Required/NotRequired.
42
+
43
+ Returns:
44
+ - True if required
45
+ - False if not required
46
+ - None if not annotated with either
47
+ """
48
+ origin = get_origin(type_)
49
+ if origin is Required:
50
+ return True
51
+ if origin is NotRequired:
52
+ return False
53
+ if origin is Annotated or getattr(origin, "__args__", None):
54
+ # See https://typing.readthedocs.io/en/latest/spec/typeddict.html#interaction-with-annotated
55
+ return _is_required_type(type_.__args__[0])
56
+ return None
57
+
58
+
59
+ def _is_readonly_type(type_: Any) -> bool:
60
+ """Check if an annotation is marked as ReadOnly.
61
+
62
+ Returns:
63
+ - True if is read only
64
+ - False if not read only
65
+ """
66
+
67
+ # See: https://typing.readthedocs.io/en/latest/spec/typeddict.html#typing-readonly-type-qualifier
68
+ origin = get_origin(type_)
69
+ if origin is Annotated:
70
+ return _is_readonly_type(type_.__args__[0])
71
+ if origin is ReadOnly:
72
+ return True
73
+ return False
74
+
75
+
76
+ _DEFAULT_KEYS: frozenset[str] = frozenset()
77
+
78
+
79
+ def get_field_default(name: str, type_: Any, schema: type[Any]) -> Any:
80
+ """Determine the default value for a field in a state schema.
81
+
82
+ This is based on:
83
+ If TypedDict:
84
+ - Required/NotRequired
85
+ - total=False -> everything optional
86
+ - Type annotation (Optional/Union[None])
87
+ """
88
+ optional_keys = getattr(schema, "__optional_keys__", _DEFAULT_KEYS)
89
+ irq = _is_required_type(type_)
90
+ if name in optional_keys:
91
+ # Either total=False or explicit NotRequired.
92
+ # No type annotation trumps this.
93
+ if irq:
94
+ # Unless it's earlier versions of python & explicit Required
95
+ return ...
96
+ return None
97
+ if irq is not None:
98
+ if irq:
99
+ # Handle Required[<type>]
100
+ # (we already handled NotRequired and total=False)
101
+ return ...
102
+ # Handle NotRequired[<type>] for earlier versions of python
103
+ return None
104
+ if dataclasses.is_dataclass(schema):
105
+ field_info = next(
106
+ (f for f in dataclasses.fields(schema) if f.name == name), None
107
+ )
108
+ if field_info:
109
+ if (
110
+ field_info.default is not dataclasses.MISSING
111
+ and field_info.default is not ...
112
+ ):
113
+ return field_info.default
114
+ elif field_info.default_factory is not dataclasses.MISSING:
115
+ return field_info.default_factory()
116
+ # Note, we ignore ReadOnly attributes,
117
+ # as they don't make much sense. (we don't care if you mutate the state in your node)
118
+ # and mutating state in your node has no effect on our graph state.
119
+ # Base case is the annotation
120
+ if _is_optional_type(type_):
121
+ return None
122
+ return ...
123
+
124
+
125
+ def get_enhanced_type_hints(
126
+ type: type[Any],
127
+ ) -> Generator[tuple[str, Any, Any, str | None], None, None]:
128
+ """Attempt to extract default values and descriptions from provided type, used for config schema."""
129
+ for name, typ in get_type_hints(type).items():
130
+ default = None
131
+ description = None
132
+
133
+ # Pydantic models
134
+ try:
135
+ if hasattr(type, "model_fields") and name in type.model_fields:
136
+ field = type.model_fields[name]
137
+
138
+ if hasattr(field, "description") and field.description is not None:
139
+ description = field.description
140
+
141
+ if hasattr(field, "default") and field.default is not None:
142
+ default = field.default
143
+ if (
144
+ hasattr(default, "__class__")
145
+ and getattr(default.__class__, "__name__", "")
146
+ == "PydanticUndefinedType"
147
+ ):
148
+ default = None
149
+
150
+ except (AttributeError, KeyError, TypeError):
151
+ pass
152
+
153
+ # TypedDict, dataclass
154
+ try:
155
+ if hasattr(type, "__dict__"):
156
+ type_dict = getattr(type, "__dict__")
157
+
158
+ if name in type_dict:
159
+ default = type_dict[name]
160
+ except (AttributeError, KeyError, TypeError):
161
+ pass
162
+
163
+ yield name, typ, default, description
164
+
165
+
166
+ def get_update_as_tuples(input: Any, keys: Sequence[str]) -> list[tuple[str, Any]]:
167
+ """Get Pydantic state update as a list of (key, value) tuples."""
168
+ if isinstance(input, BaseModel):
169
+ keep = input.model_fields_set
170
+ defaults = {k: v.default for k, v in type(input).model_fields.items()}
171
+ else:
172
+ keep = None
173
+ defaults = {}
174
+
175
+ # NOTE: This behavior for Pydantic is somewhat inelegant,
176
+ # but we keep around for backwards compatibility
177
+ # if input is a Pydantic model, only update values
178
+ # that are different from the default values or in the keep set
179
+ return [
180
+ (k, value)
181
+ for k in keys
182
+ if (value := getattr(input, k, MISSING)) is not MISSING
183
+ and (
184
+ value is not None
185
+ or defaults.get(k, MISSING) is not None
186
+ or (keep is not None and k in keep)
187
+ )
188
+ ]
189
+
190
+
191
+ ANNOTATED_KEYS_CACHE: weakref.WeakKeyDictionary[type[Any], tuple[str, ...]] = (
192
+ weakref.WeakKeyDictionary()
193
+ )
194
+
195
+
196
+ def get_cached_annotated_keys(obj: type[Any]) -> tuple[str, ...]:
197
+ """Return cached annotated keys for a Python class."""
198
+ if obj in ANNOTATED_KEYS_CACHE:
199
+ return ANNOTATED_KEYS_CACHE[obj]
200
+ if isinstance(obj, type):
201
+ keys: list[str] = []
202
+ for base in reversed(obj.__mro__):
203
+ ann = base.__dict__.get("__annotations__")
204
+ # In Python 3.14+, Pydantic models use descriptors for __annotations__
205
+ # so we need to fall back to getattr if __dict__.get returns None
206
+ if ann is None:
207
+ ann = getattr(base, "__annotations__", None)
208
+ if ann is None or isinstance(ann, types.GetSetDescriptorType):
209
+ continue
210
+ keys.extend(ann.keys())
211
+ return ANNOTATED_KEYS_CACHE.setdefault(obj, tuple(keys))
212
+ else:
213
+ raise TypeError(f"Expected a type, got {type(obj)}. ")
@@ -0,0 +1,220 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import concurrent.futures
5
+ import contextvars
6
+ import inspect
7
+ import sys
8
+ import types
9
+ from collections.abc import Awaitable, Coroutine, Generator
10
+ from typing import TypeVar, cast
11
+
12
+ T = TypeVar("T")
13
+ AnyFuture = asyncio.Future | concurrent.futures.Future
14
+
15
+ CONTEXT_NOT_SUPPORTED = sys.version_info < (3, 11)
16
+ EAGER_NOT_SUPPORTED = sys.version_info < (3, 12)
17
+
18
+
19
+ def _get_loop(fut: asyncio.Future) -> asyncio.AbstractEventLoop:
20
+ # Tries to call Future.get_loop() if it's available.
21
+ # Otherwise fallbacks to using the old '_loop' property.
22
+ try:
23
+ get_loop = fut.get_loop
24
+ except AttributeError:
25
+ pass
26
+ else:
27
+ return get_loop()
28
+ return fut._loop
29
+
30
+
31
+ def _convert_future_exc(exc: BaseException) -> BaseException:
32
+ exc_class = type(exc)
33
+ if exc_class is concurrent.futures.CancelledError:
34
+ return asyncio.CancelledError(*exc.args)
35
+ elif exc_class is concurrent.futures.TimeoutError:
36
+ return asyncio.TimeoutError(*exc.args)
37
+ elif exc_class is concurrent.futures.InvalidStateError:
38
+ return asyncio.InvalidStateError(*exc.args)
39
+ else:
40
+ return exc
41
+
42
+
43
+ def _set_concurrent_future_state(
44
+ concurrent: concurrent.futures.Future,
45
+ source: AnyFuture,
46
+ ) -> None:
47
+ """Copy state from a future to a concurrent.futures.Future."""
48
+ assert source.done()
49
+ if source.cancelled():
50
+ concurrent.cancel()
51
+ if not concurrent.set_running_or_notify_cancel():
52
+ return
53
+ exception = source.exception()
54
+ if exception is not None:
55
+ concurrent.set_exception(_convert_future_exc(exception))
56
+ else:
57
+ result = source.result()
58
+ concurrent.set_result(result)
59
+
60
+
61
+ def _copy_future_state(source: AnyFuture, dest: asyncio.Future) -> None:
62
+ """Internal helper to copy state from another Future.
63
+
64
+ The other Future may be a concurrent.futures.Future.
65
+ """
66
+ if dest.done():
67
+ return
68
+ assert source.done()
69
+ if dest.cancelled():
70
+ return
71
+ if source.cancelled():
72
+ dest.cancel()
73
+ else:
74
+ exception = source.exception()
75
+ if exception is not None:
76
+ dest.set_exception(_convert_future_exc(exception))
77
+ else:
78
+ result = source.result()
79
+ dest.set_result(result)
80
+
81
+
82
+ def _chain_future(source: AnyFuture, destination: AnyFuture) -> None:
83
+ """Chain two futures so that when one completes, so does the other.
84
+
85
+ The result (or exception) of source will be copied to destination.
86
+ If destination is cancelled, source gets cancelled too.
87
+ Compatible with both asyncio.Future and concurrent.futures.Future.
88
+ """
89
+ if not asyncio.isfuture(source) and not isinstance(
90
+ source, concurrent.futures.Future
91
+ ):
92
+ raise TypeError("A future is required for source argument")
93
+ if not asyncio.isfuture(destination) and not isinstance(
94
+ destination, concurrent.futures.Future
95
+ ):
96
+ raise TypeError("A future is required for destination argument")
97
+ source_loop = _get_loop(source) if asyncio.isfuture(source) else None
98
+ dest_loop = _get_loop(destination) if asyncio.isfuture(destination) else None
99
+
100
+ def _set_state(future: AnyFuture, other: AnyFuture) -> None:
101
+ if asyncio.isfuture(future):
102
+ _copy_future_state(other, future)
103
+ else:
104
+ _set_concurrent_future_state(future, other)
105
+
106
+ def _call_check_cancel(destination: AnyFuture) -> None:
107
+ if destination.cancelled():
108
+ if source_loop is None or source_loop is dest_loop:
109
+ source.cancel()
110
+ else:
111
+ source_loop.call_soon_threadsafe(source.cancel)
112
+
113
+ def _call_set_state(source: AnyFuture) -> None:
114
+ if destination.cancelled() and dest_loop is not None and dest_loop.is_closed():
115
+ return
116
+ if dest_loop is None or dest_loop is source_loop:
117
+ _set_state(destination, source)
118
+ else:
119
+ if dest_loop.is_closed():
120
+ return
121
+ dest_loop.call_soon_threadsafe(_set_state, destination, source)
122
+
123
+ destination.add_done_callback(_call_check_cancel)
124
+ source.add_done_callback(_call_set_state)
125
+
126
+
127
+ def chain_future(source: AnyFuture, destination: AnyFuture) -> AnyFuture:
128
+ # adapted from asyncio.run_coroutine_threadsafe
129
+ try:
130
+ _chain_future(source, destination)
131
+ return destination
132
+ except (SystemExit, KeyboardInterrupt):
133
+ raise
134
+ except BaseException as exc:
135
+ if isinstance(destination, concurrent.futures.Future):
136
+ if destination.set_running_or_notify_cancel():
137
+ destination.set_exception(exc)
138
+ else:
139
+ destination.set_exception(exc)
140
+ raise
141
+
142
+
143
+ def _ensure_future(
144
+ coro_or_future: Coroutine[None, None, T] | Awaitable[T],
145
+ *,
146
+ loop: asyncio.AbstractEventLoop,
147
+ name: str | None = None,
148
+ context: contextvars.Context | None = None,
149
+ lazy: bool = True,
150
+ ) -> asyncio.Task[T]:
151
+ called_wrap_awaitable = False
152
+ if not asyncio.iscoroutine(coro_or_future):
153
+ if inspect.isawaitable(coro_or_future):
154
+ coro_or_future = cast(
155
+ Coroutine[None, None, T], _wrap_awaitable(coro_or_future)
156
+ )
157
+ called_wrap_awaitable = True
158
+ else:
159
+ raise TypeError(
160
+ "An asyncio.Future, a coroutine or an awaitable is required."
161
+ f" Got {type(coro_or_future).__name__} instead."
162
+ )
163
+
164
+ try:
165
+ if CONTEXT_NOT_SUPPORTED:
166
+ return loop.create_task(coro_or_future, name=name)
167
+ elif EAGER_NOT_SUPPORTED or lazy:
168
+ return loop.create_task(coro_or_future, name=name, context=context)
169
+ else:
170
+ return asyncio.eager_task_factory(
171
+ loop, coro_or_future, name=name, context=context
172
+ )
173
+ except RuntimeError:
174
+ if not called_wrap_awaitable:
175
+ coro_or_future.close()
176
+ raise
177
+
178
+
179
+ @types.coroutine
180
+ def _wrap_awaitable(awaitable: Awaitable[T]) -> Generator[None, None, T]:
181
+ """Helper for asyncio.ensure_future().
182
+
183
+ Wraps awaitable (an object with __await__) into a coroutine
184
+ that will later be wrapped in a Task by ensure_future().
185
+ """
186
+ return (yield from awaitable.__await__())
187
+
188
+
189
+ def run_coroutine_threadsafe(
190
+ coro: Coroutine[None, None, T],
191
+ loop: asyncio.AbstractEventLoop,
192
+ *,
193
+ lazy: bool,
194
+ name: str | None = None,
195
+ context: contextvars.Context | None = None,
196
+ ) -> asyncio.Future[T]:
197
+ """Submit a coroutine object to a given event loop.
198
+
199
+ Return an asyncio.Future to access the result.
200
+ """
201
+
202
+ if asyncio._get_running_loop() is loop:
203
+ return _ensure_future(coro, loop=loop, name=name, context=context, lazy=lazy)
204
+ else:
205
+ future: asyncio.Future[T] = asyncio.Future(loop=loop)
206
+
207
+ def callback() -> None:
208
+ try:
209
+ chain_future(
210
+ _ensure_future(coro, loop=loop, name=name, context=context),
211
+ future,
212
+ )
213
+ except (SystemExit, KeyboardInterrupt):
214
+ raise
215
+ except BaseException as exc:
216
+ future.set_exception(exc)
217
+ raise
218
+
219
+ loop.call_soon_threadsafe(callback, context=context)
220
+ return future
@@ -0,0 +1,275 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import typing
5
+ import warnings
6
+ from contextlib import nullcontext
7
+ from dataclasses import is_dataclass
8
+ from functools import lru_cache
9
+ from typing import (
10
+ Any,
11
+ cast,
12
+ overload,
13
+ )
14
+
15
+ from pydantic import (
16
+ BaseModel,
17
+ ConfigDict,
18
+ Field,
19
+ RootModel,
20
+ )
21
+ from pydantic import (
22
+ create_model as _create_model_base,
23
+ )
24
+ from pydantic.fields import FieldInfo
25
+ from pydantic.json_schema import (
26
+ DEFAULT_REF_TEMPLATE,
27
+ GenerateJsonSchema,
28
+ JsonSchemaMode,
29
+ )
30
+ from typing_extensions import TypedDict
31
+
32
+
33
+ @overload
34
+ def get_fields(model: type[BaseModel]) -> dict[str, FieldInfo]: ...
35
+
36
+
37
+ @overload
38
+ def get_fields(model: BaseModel) -> dict[str, FieldInfo]: ...
39
+
40
+
41
+ def get_fields(
42
+ model: type[BaseModel] | BaseModel,
43
+ ) -> dict[str, FieldInfo]:
44
+ """Get the field names of a Pydantic model."""
45
+ if hasattr(model, "model_fields"):
46
+ return model.model_fields
47
+
48
+ if hasattr(model, "__fields__"):
49
+ return model.__fields__
50
+ msg = f"Expected a Pydantic model. Got {type(model)}"
51
+ raise TypeError(msg)
52
+
53
+
54
+ _SchemaConfig = ConfigDict(
55
+ arbitrary_types_allowed=True, frozen=True, protected_namespaces=()
56
+ )
57
+
58
+ NO_DEFAULT = object()
59
+
60
+
61
+ def _create_root_model(
62
+ name: str,
63
+ type_: Any,
64
+ module_name: str | None = None,
65
+ default_: object = NO_DEFAULT,
66
+ ) -> type[BaseModel]:
67
+ """Create a base class."""
68
+
69
+ def schema(
70
+ cls: type[BaseModel],
71
+ by_alias: bool = True,
72
+ ref_template: str = DEFAULT_REF_TEMPLATE,
73
+ ) -> dict[str, Any]:
74
+ # Complains about schema not being defined in superclass
75
+ schema_ = super(cls, cls).schema( # type: ignore[misc]
76
+ by_alias=by_alias, ref_template=ref_template
77
+ )
78
+ schema_["title"] = name
79
+ return schema_
80
+
81
+ def model_json_schema(
82
+ cls: type[BaseModel],
83
+ by_alias: bool = True,
84
+ ref_template: str = DEFAULT_REF_TEMPLATE,
85
+ schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,
86
+ mode: JsonSchemaMode = "validation",
87
+ ) -> dict[str, Any]:
88
+ # Complains about model_json_schema not being defined in superclass
89
+ schema_ = super(cls, cls).model_json_schema( # type: ignore[misc]
90
+ by_alias=by_alias,
91
+ ref_template=ref_template,
92
+ schema_generator=schema_generator,
93
+ mode=mode,
94
+ )
95
+ schema_["title"] = name
96
+ return schema_
97
+
98
+ base_class_attributes = {
99
+ "__annotations__": {"root": type_},
100
+ "model_config": ConfigDict(arbitrary_types_allowed=True),
101
+ "schema": classmethod(schema),
102
+ "model_json_schema": classmethod(model_json_schema),
103
+ "__module__": module_name or "coco_core.runnables.utils",
104
+ }
105
+
106
+ if default_ is not NO_DEFAULT:
107
+ base_class_attributes["root"] = default_
108
+ with warnings.catch_warnings():
109
+ custom_root_type = type(name, (RootModel,), base_class_attributes)
110
+ return cast("type[BaseModel]", custom_root_type)
111
+
112
+
113
+ @lru_cache(maxsize=256)
114
+ def _create_root_model_cached(
115
+ model_name: str,
116
+ type_: Any,
117
+ *,
118
+ module_name: str | None = None,
119
+ default_: object = NO_DEFAULT,
120
+ ) -> type[BaseModel]:
121
+ return _create_root_model(
122
+ model_name, type_, default_=default_, module_name=module_name
123
+ )
124
+
125
+
126
+ @lru_cache(maxsize=256)
127
+ def _create_model_cached(
128
+ model_name: str,
129
+ /,
130
+ **field_definitions: Any,
131
+ ) -> type[BaseModel]:
132
+ return _create_model_base(
133
+ model_name,
134
+ __config__=_SchemaConfig,
135
+ **_remap_field_definitions(field_definitions),
136
+ )
137
+
138
+
139
+ # Reserved names should capture all the `public` names / methods that are
140
+ # used by BaseModel internally. This will keep the reserved names up-to-date.
141
+ # For reference, the reserved names are:
142
+ # "construct", "copy", "dict", "from_orm", "json", "parse_file", "parse_obj",
143
+ # "parse_raw", "schema", "schema_json", "update_forward_refs", "validate",
144
+ # "model_computed_fields", "model_config", "model_construct", "model_copy",
145
+ # "model_dump", "model_dump_json", "model_extra", "model_fields",
146
+ # "model_fields_set", "model_json_schema", "model_parametrized_name",
147
+ # "model_post_init", "model_rebuild", "model_validate", "model_validate_json",
148
+ # "model_validate_strings"
149
+ _RESERVED_NAMES = {key for key in dir(BaseModel) if not key.startswith("_")}
150
+
151
+
152
+ def _remap_field_definitions(field_definitions: dict[str, Any]) -> dict[str, Any]:
153
+ """This remaps fields to avoid colliding with internal pydantic fields."""
154
+
155
+ remapped = {}
156
+ for key, value in field_definitions.items():
157
+ if key.startswith("_") or key in _RESERVED_NAMES:
158
+ # Let's add a prefix to avoid colliding with internal pydantic fields
159
+ if isinstance(value, FieldInfo):
160
+ msg = (
161
+ f"Remapping for fields starting with '_' or fields with a name "
162
+ f"matching a reserved name {_RESERVED_NAMES} is not supported if "
163
+ f" the field is a pydantic Field instance. Got {key}."
164
+ )
165
+ raise NotImplementedError(msg)
166
+ type_, default_ = value
167
+ remapped[f"private_{key}"] = (
168
+ type_,
169
+ Field(
170
+ default=default_,
171
+ alias=key,
172
+ serialization_alias=key,
173
+ title=key.lstrip("_").replace("_", " ").title(),
174
+ ),
175
+ )
176
+ else:
177
+ remapped[key] = value
178
+ return remapped
179
+
180
+
181
+ def create_model(
182
+ model_name: str,
183
+ *,
184
+ field_definitions: dict[str, Any] | None = None,
185
+ root: Any | None = None,
186
+ ) -> type[BaseModel]:
187
+ """Create a pydantic model with the given field definitions.
188
+
189
+ Attention:
190
+ Please do not use outside of coco packages. This API
191
+ is subject to change at any time.
192
+
193
+ Args:
194
+ model_name: The name of the model.
195
+ module_name: The name of the module where the model is defined.
196
+ This is used by Pydantic to resolve any forward references.
197
+ field_definitions: The field definitions for the model.
198
+ root: Type for a root model (RootModel)
199
+
200
+ Returns:
201
+ Type[BaseModel]: The created model.
202
+ """
203
+ field_definitions = field_definitions or {}
204
+
205
+ if root:
206
+ if field_definitions:
207
+ msg = (
208
+ "When specifying __root__ no other "
209
+ f"fields should be provided. Got {field_definitions}"
210
+ )
211
+ raise NotImplementedError(msg)
212
+
213
+ if isinstance(root, tuple):
214
+ kwargs = {"type_": root[0], "default_": root[1]}
215
+ else:
216
+ kwargs = {"type_": root}
217
+
218
+ try:
219
+ named_root_model = _create_root_model_cached(model_name, **kwargs)
220
+ except TypeError:
221
+ # something in the arguments into _create_root_model_cached is not hashable
222
+ named_root_model = _create_root_model(
223
+ model_name,
224
+ **kwargs,
225
+ )
226
+ return named_root_model
227
+
228
+ # No root, just field definitions
229
+ names = set(field_definitions.keys())
230
+
231
+ capture_warnings = False
232
+
233
+ for name in names:
234
+ # Also if any non-reserved name is used (e.g., model_id or model_name)
235
+ if name.startswith("model"):
236
+ capture_warnings = True
237
+
238
+ with warnings.catch_warnings() if capture_warnings else nullcontext():
239
+ if capture_warnings:
240
+ warnings.filterwarnings(action="ignore")
241
+ try:
242
+ return _create_model_cached(model_name, **field_definitions)
243
+ except TypeError:
244
+ # something in field definitions is not hashable
245
+ return _create_model_base(
246
+ model_name,
247
+ __config__=_SchemaConfig,
248
+ **_remap_field_definitions(field_definitions),
249
+ )
250
+
251
+
252
+ def is_supported_by_pydantic(type_: Any) -> bool:
253
+ """Check if a given "complex" type is supported by pydantic.
254
+
255
+ This will return False for primitive types like int, str, etc.
256
+
257
+ The check is meant for container types like dataclasses, TypedDicts, etc.
258
+ """
259
+ if is_dataclass(type_):
260
+ return True
261
+
262
+ if isinstance(type_, type) and issubclass(type_, BaseModel):
263
+ return True
264
+
265
+ if hasattr(type_, "__orig_bases__"):
266
+ for base in type_.__orig_bases__:
267
+ if base is TypedDict:
268
+ return True
269
+ elif base is typing.TypedDict: # noqa: TID251
270
+ # ignoring TID251 since it's OK to use typing.TypedDict in this case.
271
+ # Pydantic supports typing.TypedDict from Python 3.12
272
+ # For older versions, only typing_extensions.TypedDict is supported.
273
+ if sys.version_info >= (3, 12):
274
+ return True
275
+ return False