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,631 @@
1
+ """Utilities for pydantic."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ import textwrap
7
+ import warnings
8
+ from contextlib import nullcontext
9
+ from functools import lru_cache, wraps
10
+ from types import GenericAlias
11
+ from typing import (
12
+ TYPE_CHECKING,
13
+ Any,
14
+ TypeGuard,
15
+ TypeVar,
16
+ cast,
17
+ overload,
18
+ )
19
+
20
+ import pydantic
21
+ from packaging import version
22
+
23
+ # root_validator is deprecated but we need it for backward compatibility of @pre_init
24
+ from pydantic import ( # type: ignore[deprecated]
25
+ BaseModel,
26
+ ConfigDict,
27
+ Field,
28
+ PydanticDeprecationWarning,
29
+ RootModel,
30
+ root_validator,
31
+ )
32
+ from pydantic import (
33
+ create_model as _create_model_base,
34
+ )
35
+ from pydantic.fields import FieldInfo as FieldInfoV2
36
+ from pydantic.json_schema import (
37
+ DEFAULT_REF_TEMPLATE,
38
+ GenerateJsonSchema,
39
+ JsonSchemaMode,
40
+ JsonSchemaValue,
41
+ )
42
+ from pydantic.v1 import BaseModel as BaseModelV1
43
+ from pydantic.v1 import create_model as create_model_v1
44
+ from typing_extensions import deprecated, override
45
+
46
+ if TYPE_CHECKING:
47
+ from collections.abc import Callable
48
+
49
+ from pydantic.v1.fields import ModelField
50
+ from pydantic_core import core_schema
51
+
52
+ PYDANTIC_VERSION = version.parse(pydantic.__version__)
53
+
54
+
55
+ @deprecated("Use PYDANTIC_VERSION.major instead.")
56
+ def get_pydantic_major_version() -> int:
57
+ """DEPRECATED - Get the major version of Pydantic.
58
+
59
+ Use `PYDANTIC_VERSION.major` instead.
60
+
61
+ Returns:
62
+ The major version of Pydantic.
63
+ """
64
+ return PYDANTIC_VERSION.major
65
+
66
+
67
+ PYDANTIC_MAJOR_VERSION = PYDANTIC_VERSION.major
68
+ PYDANTIC_MINOR_VERSION = PYDANTIC_VERSION.minor
69
+
70
+ IS_PYDANTIC_V1 = False
71
+ IS_PYDANTIC_V2 = True
72
+
73
+ PydanticBaseModel = BaseModel | BaseModelV1
74
+ TypeBaseModel = type[BaseModel] | type[BaseModelV1]
75
+
76
+ TBaseModel = TypeVar("TBaseModel", bound=PydanticBaseModel)
77
+
78
+
79
+ def is_pydantic_v1_subclass(cls: type) -> bool:
80
+ """Check if the given class is Pydantic v1-like.
81
+
82
+ Returns:
83
+ `True` if the given class is a subclass of Pydantic `BaseModel` 1.x.
84
+ """
85
+ return issubclass(cls, BaseModelV1)
86
+
87
+
88
+ def is_pydantic_v2_subclass(cls: type) -> TypeGuard[type[BaseModel]]:
89
+ """Check if the given class is Pydantic v2-like.
90
+
91
+ Returns:
92
+ `True` if the given class is a subclass of Pydantic `BaseModel` 2.x.
93
+ """
94
+ return issubclass(cls, BaseModel)
95
+
96
+
97
+ def is_basemodel_subclass(cls: type) -> bool:
98
+ """Check if the given class is a subclass of Pydantic `BaseModel`.
99
+
100
+ Check if the given class is a subclass of any of the following:
101
+
102
+ * `pydantic.BaseModel` in Pydantic 2.x
103
+ * `pydantic.v1.BaseModel` in Pydantic 2.x
104
+
105
+ Returns:
106
+ `True` if the given class is a subclass of Pydantic `BaseModel`.
107
+ """
108
+ # Before we can use issubclass on the cls we need to check if it is a class
109
+ if not inspect.isclass(cls) or isinstance(cls, GenericAlias):
110
+ return False
111
+
112
+ return issubclass(cls, (BaseModel, BaseModelV1))
113
+
114
+
115
+ def is_basemodel_instance(obj: Any) -> bool:
116
+ """Check if the given class is an instance of Pydantic `BaseModel`.
117
+
118
+ Check if the given class is an instance of any of the following:
119
+
120
+ * `pydantic.BaseModel` in Pydantic 2.x
121
+ * `pydantic.v1.BaseModel` in Pydantic 2.x
122
+
123
+ Returns:
124
+ `True` if the given class is an instance of Pydantic `BaseModel`.
125
+ """
126
+ return isinstance(obj, (BaseModel, BaseModelV1))
127
+
128
+
129
+ # How to type hint this?
130
+ def pre_init(
131
+ func: Callable[[Any, dict[str, Any]], Any],
132
+ ) -> Callable[[Any, dict[str, Any]], Any]:
133
+ """Decorator to run a function before model initialization.
134
+
135
+ Args:
136
+ func: The function to run before model initialization.
137
+
138
+ Returns:
139
+ The decorated function.
140
+ """
141
+ with warnings.catch_warnings():
142
+ warnings.filterwarnings(action="ignore", category=PydanticDeprecationWarning)
143
+
144
+ # Ideally we would use @model_validator(mode="before") but this would change the
145
+ # order of the validators. See https://github.com/pydantic/pydantic/discussions/7434.
146
+ # So we keep root_validator for backward compatibility.
147
+ @root_validator(pre=True) # type: ignore[deprecated]
148
+ @wraps(func)
149
+ def wrapper(cls: type[BaseModel], values: dict[str, Any]) -> Any:
150
+ """Decorator to run a function before model initialization.
151
+
152
+ Args:
153
+ cls: The model class.
154
+ values: The values to initialize the model with.
155
+
156
+ Returns:
157
+ The values to initialize the model with.
158
+ """
159
+ # Insert default values
160
+ fields = cls.model_fields
161
+ for name, field_info in fields.items():
162
+ # Check if allow_population_by_field_name is enabled
163
+ # If yes, then set the field name to the alias
164
+ if (
165
+ hasattr(cls, "Config")
166
+ and hasattr(cls.Config, "allow_population_by_field_name")
167
+ and cls.Config.allow_population_by_field_name
168
+ and field_info.alias in values
169
+ ):
170
+ values[name] = values.pop(field_info.alias)
171
+ if (
172
+ hasattr(cls, "model_config")
173
+ and cls.model_config.get("populate_by_name")
174
+ and field_info.alias in values
175
+ ):
176
+ values[name] = values.pop(field_info.alias)
177
+
178
+ if (
179
+ name not in values or values[name] is None
180
+ ) and not field_info.is_required():
181
+ if field_info.default_factory is not None:
182
+ values[name] = field_info.default_factory() # type: ignore[call-arg]
183
+ else:
184
+ values[name] = field_info.default
185
+
186
+ # Call the decorated function
187
+ return func(cls, values)
188
+
189
+ return wrapper
190
+
191
+
192
+ class _IgnoreUnserializable(GenerateJsonSchema):
193
+ """A JSON schema generator that ignores unknown types.
194
+
195
+ https://docs.pydantic.dev/latest/concepts/json_schema/#customizing-the-json-schema-generation-process
196
+ """
197
+
198
+ @override
199
+ def handle_invalid_for_json_schema(
200
+ self, schema: core_schema.CoreSchema, error_info: str
201
+ ) -> JsonSchemaValue:
202
+ return {}
203
+
204
+
205
+ def _create_subset_model_v1(
206
+ name: str,
207
+ model: type[BaseModelV1],
208
+ field_names: list[str],
209
+ *,
210
+ descriptions: dict[str, str] | None = None,
211
+ fn_description: str | None = None,
212
+ ) -> type[BaseModelV1]:
213
+ """Create a Pydantic model with only a subset of model's fields."""
214
+ fields = {}
215
+
216
+ for field_name in field_names:
217
+ # Using pydantic v1 so can access __fields__ as a dict.
218
+ field = model.__fields__[field_name]
219
+ t = (
220
+ # this isn't perfect but should work for most functions
221
+ field.outer_type_
222
+ if field.required and not field.allow_none
223
+ else field.outer_type_ | None
224
+ )
225
+ if descriptions and field_name in descriptions:
226
+ field.field_info.description = descriptions[field_name]
227
+ fields[field_name] = (t, field.field_info)
228
+
229
+ rtn = cast("type[BaseModelV1]", create_model_v1(name, **fields)) # type: ignore[call-overload]
230
+ rtn.__doc__ = textwrap.dedent(fn_description or model.__doc__ or "")
231
+ return rtn
232
+
233
+
234
+ def _create_subset_model_v2(
235
+ name: str,
236
+ model: type[BaseModel],
237
+ field_names: list[str],
238
+ *,
239
+ descriptions: dict[str, str] | None = None,
240
+ fn_description: str | None = None,
241
+ ) -> type[BaseModel]:
242
+ """Create a Pydantic model with a subset of the model fields."""
243
+ descriptions_ = descriptions or {}
244
+ fields = {}
245
+ for field_name in field_names:
246
+ field = model.model_fields[field_name]
247
+ description = descriptions_.get(field_name, field.description)
248
+ field_kwargs: dict[str, Any] = {"description": description}
249
+ if field.default_factory is not None:
250
+ field_kwargs["default_factory"] = field.default_factory
251
+ else:
252
+ field_kwargs["default"] = field.default
253
+ field_info = FieldInfoV2(**field_kwargs)
254
+ if field.metadata:
255
+ field_info.metadata = field.metadata
256
+ fields[field_name] = (field.annotation, field_info)
257
+
258
+ rtn = cast(
259
+ "type[BaseModel]",
260
+ _create_model_base( # type: ignore[call-overload]
261
+ name, **fields, __config__=ConfigDict(arbitrary_types_allowed=True)
262
+ ),
263
+ )
264
+
265
+ # TODO(0.3): Determine if there is a more "pydantic" way to preserve annotations.
266
+ # This is done to preserve __annotations__ when working with pydantic 2.x
267
+ # and using the Annotated type with TypedDict.
268
+ # Comment out the following line, to trigger the relevant test case.
269
+ selected_annotations = [
270
+ (name, annotation)
271
+ for name, annotation in model.__annotations__.items()
272
+ if name in field_names
273
+ ]
274
+
275
+ rtn.__annotations__ = dict(selected_annotations)
276
+ rtn.__doc__ = textwrap.dedent(fn_description or model.__doc__ or "")
277
+ return rtn
278
+
279
+
280
+ # Private functionality to create a subset model that's compatible across
281
+ # different versions of pydantic.
282
+ # Handles pydantic versions 2.x. including v1 of pydantic in 2.x.
283
+ # However, can't find a way to type hint this.
284
+ def _create_subset_model(
285
+ name: str,
286
+ model: TypeBaseModel,
287
+ field_names: list[str],
288
+ *,
289
+ descriptions: dict[str, str] | None = None,
290
+ fn_description: str | None = None,
291
+ ) -> TypeBaseModel:
292
+ """Create subset model using the same pydantic version as the input model.
293
+
294
+ Returns:
295
+ The created subset model.
296
+ """
297
+ if issubclass(model, BaseModelV1):
298
+ return _create_subset_model_v1(
299
+ name,
300
+ model,
301
+ field_names,
302
+ descriptions=descriptions,
303
+ fn_description=fn_description,
304
+ )
305
+ return _create_subset_model_v2(
306
+ name,
307
+ model,
308
+ field_names,
309
+ descriptions=descriptions,
310
+ fn_description=fn_description,
311
+ )
312
+
313
+
314
+ @overload
315
+ def get_fields(model: type[BaseModel]) -> dict[str, FieldInfoV2]: ...
316
+
317
+
318
+ @overload
319
+ def get_fields(model: BaseModel) -> dict[str, FieldInfoV2]: ...
320
+
321
+
322
+ @overload
323
+ def get_fields(model: type[BaseModelV1]) -> dict[str, ModelField]: ...
324
+
325
+
326
+ @overload
327
+ def get_fields(model: BaseModelV1) -> dict[str, ModelField]: ...
328
+
329
+
330
+ def get_fields(
331
+ model: type[BaseModel | BaseModelV1] | BaseModel | BaseModelV1,
332
+ ) -> dict[str, FieldInfoV2] | dict[str, ModelField]:
333
+ """Return the field names of a Pydantic model.
334
+
335
+ Args:
336
+ model: The Pydantic model or instance.
337
+
338
+ Raises:
339
+ TypeError: If the model is not a Pydantic model.
340
+ """
341
+ if not isinstance(model, type):
342
+ model = type(model)
343
+ if issubclass(model, BaseModel):
344
+ return model.model_fields
345
+ if issubclass(model, BaseModelV1):
346
+ return model.__fields__
347
+ msg = f"Expected a Pydantic model. Got {model}" # type: ignore[unreachable]
348
+ raise TypeError(msg)
349
+
350
+
351
+ def model_json_schema(model: TypeBaseModel) -> dict[str, Any]:
352
+ """Return the JSON schema of a Pydantic model class of either major version.
353
+
354
+ Dispatches to the correct method for Pydantic v1 (`schema`) or v2
355
+ (`model_json_schema`), so callers holding a `TypeBaseModel` don't have to
356
+ branch on the model's version themselves.
357
+
358
+ Args:
359
+ model: The Pydantic model class.
360
+
361
+ Raises:
362
+ TypeError: If the model is not a Pydantic model class.
363
+ """
364
+ if issubclass(model, BaseModel):
365
+ return model.model_json_schema()
366
+ if issubclass(model, BaseModelV1):
367
+ return model.schema()
368
+ msg = f"Expected a Pydantic model. Got {model}" # type: ignore[unreachable]
369
+ raise TypeError(msg)
370
+
371
+
372
+ def model_validate(model: TypeBaseModel, obj: Any) -> PydanticBaseModel:
373
+ """Validate `obj` against a Pydantic model class of either major version.
374
+
375
+ Dispatches to the correct method for Pydantic v1 (`parse_obj`) or v2
376
+ (`model_validate`), so callers holding a `TypeBaseModel` don't have to
377
+ branch on the model's version themselves.
378
+
379
+ Args:
380
+ model: The Pydantic model class to validate against.
381
+ obj: The object to validate.
382
+
383
+ Raises:
384
+ TypeError: If the model is not a Pydantic model class.
385
+ """
386
+ if issubclass(model, BaseModel):
387
+ return model.model_validate(obj)
388
+ if issubclass(model, BaseModelV1):
389
+ return model.parse_obj(obj)
390
+ msg = f"Expected a Pydantic model. Got {model}" # type: ignore[unreachable]
391
+ raise TypeError(msg)
392
+
393
+
394
+ _SchemaConfig = ConfigDict(
395
+ arbitrary_types_allowed=True, frozen=True, protected_namespaces=()
396
+ )
397
+
398
+ NO_DEFAULT = object()
399
+
400
+
401
+ def _create_root_model(
402
+ name: str,
403
+ type_: Any,
404
+ module_name: str | None = None,
405
+ default_: object = NO_DEFAULT,
406
+ ) -> type[BaseModel]:
407
+ """Create a base class."""
408
+
409
+ def schema(
410
+ cls: type[BaseModelV1],
411
+ by_alias: bool = True, # noqa: FBT001,FBT002
412
+ ref_template: str = DEFAULT_REF_TEMPLATE,
413
+ ) -> dict[str, Any]:
414
+ super_cls = cast("type[BaseModelV1]", super(cls, cls))
415
+ schema_ = super_cls.schema(by_alias=by_alias, ref_template=ref_template)
416
+ schema_["title"] = name
417
+ return schema_
418
+
419
+ def model_json_schema(
420
+ cls: type[BaseModel],
421
+ by_alias: bool = True, # noqa: FBT001,FBT002
422
+ ref_template: str = DEFAULT_REF_TEMPLATE,
423
+ schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,
424
+ mode: JsonSchemaMode = "validation",
425
+ ) -> dict[str, Any]:
426
+ super_cls = cast("type[BaseModel]", super(cls, cls))
427
+ schema_ = super_cls.model_json_schema(
428
+ by_alias=by_alias,
429
+ ref_template=ref_template,
430
+ schema_generator=schema_generator,
431
+ mode=mode,
432
+ )
433
+ schema_["title"] = name
434
+ return schema_
435
+
436
+ base_class_attributes = {
437
+ "__annotations__": {"root": type_},
438
+ "model_config": ConfigDict(arbitrary_types_allowed=True),
439
+ "schema": classmethod(schema),
440
+ "model_json_schema": classmethod(model_json_schema),
441
+ "__module__": module_name or "coco_core.runnables.utils",
442
+ }
443
+
444
+ if default_ is not NO_DEFAULT:
445
+ base_class_attributes["root"] = default_
446
+ with warnings.catch_warnings():
447
+ try:
448
+ if isinstance(type_, type) and issubclass(type_, BaseModelV1):
449
+ warnings.filterwarnings(
450
+ action="ignore", category=PydanticDeprecationWarning
451
+ )
452
+ except TypeError:
453
+ pass
454
+ custom_root_type = type(name, (RootModel,), base_class_attributes)
455
+ return cast("type[BaseModel]", custom_root_type)
456
+
457
+
458
+ @lru_cache(maxsize=256)
459
+ def _create_root_model_cached(
460
+ model_name: str,
461
+ type_: Any,
462
+ *,
463
+ module_name: str | None = None,
464
+ default_: object = NO_DEFAULT,
465
+ ) -> type[BaseModel]:
466
+ return _create_root_model(
467
+ model_name, type_, default_=default_, module_name=module_name
468
+ )
469
+
470
+
471
+ @lru_cache(maxsize=256)
472
+ def _create_model_cached(
473
+ model_name: str,
474
+ /,
475
+ **field_definitions: Any,
476
+ ) -> type[BaseModel]:
477
+ return _create_model_base(
478
+ model_name,
479
+ __config__=_SchemaConfig,
480
+ **_remap_field_definitions(field_definitions),
481
+ )
482
+
483
+
484
+ def create_model(
485
+ model_name: str,
486
+ module_name: str | None = None,
487
+ /,
488
+ **field_definitions: Any,
489
+ ) -> type[BaseModel]:
490
+ """Create a Pydantic model with the given field definitions.
491
+
492
+ Please use `create_model_v2` instead of this function.
493
+
494
+ Args:
495
+ model_name: The name of the model.
496
+ module_name: The name of the module where the model is defined.
497
+
498
+ This is used by Pydantic to resolve any forward references.
499
+ **field_definitions: The field definitions for the model.
500
+
501
+ Returns:
502
+ The created model.
503
+ """
504
+ kwargs = {}
505
+ if "__root__" in field_definitions:
506
+ kwargs["root"] = field_definitions.pop("__root__")
507
+
508
+ return create_model_v2(
509
+ model_name,
510
+ module_name=module_name,
511
+ field_definitions=field_definitions,
512
+ **kwargs,
513
+ )
514
+
515
+
516
+ # Reserved names should capture all the `public` names / methods that are
517
+ # used by BaseModel internally. This will keep the reserved names up-to-date.
518
+ # For reference, the reserved names are:
519
+ # "construct", "copy", "dict", "from_orm", "json", "parse_file", "parse_obj",
520
+ # "parse_raw", "schema", "schema_json", "update_forward_refs", "validate",
521
+ # "model_computed_fields", "model_config", "model_construct", "model_copy",
522
+ # "model_dump", "model_dump_json", "model_extra", "model_fields",
523
+ # "model_fields_set", "model_json_schema", "model_parametrized_name",
524
+ # "model_post_init", "model_rebuild", "model_validate", "model_validate_json",
525
+ # "model_validate_strings"
526
+ _RESERVED_NAMES = {key for key in dir(BaseModel) if not key.startswith("_")}
527
+
528
+
529
+ def _remap_field_definitions(field_definitions: dict[str, Any]) -> dict[str, Any]:
530
+ """This remaps fields to avoid colliding with internal pydantic fields."""
531
+ remapped = {}
532
+ for key, value in field_definitions.items():
533
+ if key.startswith("_") or key in _RESERVED_NAMES:
534
+ # Let's add a prefix to avoid colliding with internal pydantic fields
535
+ if isinstance(value, FieldInfoV2):
536
+ msg = (
537
+ f"Remapping for fields starting with '_' or fields with a name "
538
+ f"matching a reserved name {_RESERVED_NAMES} is not supported if "
539
+ f" the field is a pydantic Field instance. Got {key}."
540
+ )
541
+ raise NotImplementedError(msg)
542
+ type_, default_ = value
543
+ remapped[f"private_{key}"] = (
544
+ type_,
545
+ Field(
546
+ default=default_,
547
+ alias=key,
548
+ serialization_alias=key,
549
+ title=key.lstrip("_").replace("_", " ").title(),
550
+ ),
551
+ )
552
+ else:
553
+ remapped[key] = value
554
+ return remapped
555
+
556
+
557
+ def create_model_v2(
558
+ model_name: str,
559
+ *,
560
+ module_name: str | None = None,
561
+ field_definitions: dict[str, Any] | None = None,
562
+ root: Any | None = None,
563
+ ) -> type[BaseModel]:
564
+ """Create a Pydantic model with the given field definitions.
565
+
566
+ !!! warning
567
+
568
+ Do not use outside of coco packages. This API is subject to change at any
569
+ time.
570
+
571
+ Args:
572
+ model_name: The name of the model.
573
+ module_name: The name of the module where the model is defined.
574
+
575
+ This is used by Pydantic to resolve any forward references.
576
+ field_definitions: The field definitions for the model.
577
+ root: Type for a root model (`RootModel`)
578
+
579
+ Returns:
580
+ The created model.
581
+ """
582
+ field_definitions = field_definitions or {}
583
+
584
+ if root:
585
+ if field_definitions:
586
+ msg = (
587
+ "When specifying __root__ no other "
588
+ f"fields should be provided. Got {field_definitions}"
589
+ )
590
+ raise NotImplementedError(msg)
591
+
592
+ if isinstance(root, tuple):
593
+ kwargs = {"type_": root[0], "default_": root[1]}
594
+ else:
595
+ kwargs = {"type_": root}
596
+
597
+ try:
598
+ named_root_model = _create_root_model_cached(
599
+ model_name, module_name=module_name, **kwargs
600
+ )
601
+ except TypeError:
602
+ # something in the arguments into _create_root_model_cached is not hashable
603
+ named_root_model = _create_root_model(
604
+ model_name,
605
+ module_name=module_name,
606
+ **kwargs,
607
+ )
608
+ return named_root_model
609
+
610
+ # No root, just field definitions
611
+ names = set(field_definitions.keys())
612
+
613
+ capture_warnings = False
614
+
615
+ for name in names:
616
+ # Also if any non-reserved name is used (e.g., model_id or model_name)
617
+ if name.startswith("model"):
618
+ capture_warnings = True
619
+
620
+ with warnings.catch_warnings() if capture_warnings else nullcontext():
621
+ if capture_warnings:
622
+ warnings.filterwarnings(action="ignore")
623
+ try:
624
+ return _create_model_cached(model_name, **field_definitions)
625
+ except TypeError:
626
+ # something in field definitions is not hashable
627
+ return _create_model_base(
628
+ model_name,
629
+ __config__=_SchemaConfig,
630
+ **_remap_field_definitions(field_definitions),
631
+ )
@@ -0,0 +1,69 @@
1
+ """String utilities."""
2
+
3
+ from collections.abc import Iterable
4
+ from typing import Any
5
+
6
+
7
+ def stringify_value(val: Any) -> str:
8
+ """Stringify a value.
9
+
10
+ Args:
11
+ val: The value to stringify.
12
+
13
+ Returns:
14
+ The stringified value.
15
+ """
16
+ if isinstance(val, str):
17
+ return val
18
+ if isinstance(val, dict):
19
+ return "\n" + stringify_dict(val)
20
+ if isinstance(val, list):
21
+ return "\n".join(stringify_value(v) for v in val)
22
+ return str(val)
23
+
24
+
25
+ def stringify_dict(data: dict[Any, Any]) -> str:
26
+ """Stringify a dictionary.
27
+
28
+ Args:
29
+ data: The dictionary to stringify.
30
+
31
+ Returns:
32
+ The stringified dictionary.
33
+ """
34
+ return "".join(f"{key}: {stringify_value(value)}\n" for key, value in data.items())
35
+
36
+
37
+ def comma_list(items: Iterable[Any]) -> str:
38
+ """Convert an iterable to a comma-separated string.
39
+
40
+ Args:
41
+ items: The iterable to convert.
42
+
43
+ Returns:
44
+ The comma-separated string.
45
+ """
46
+ return ", ".join(str(item) for item in items)
47
+
48
+
49
+ def sanitize_for_postgres(text: str, replacement: str = "") -> str:
50
+ r"""Sanitize text by removing NUL bytes that are incompatible with PostgreSQL.
51
+
52
+ PostgreSQL text fields cannot contain `NUL (0x00)` bytes, which can cause
53
+ `psycopg.DataError` when inserting documents. This function removes or replaces
54
+ such characters to ensure compatibility.
55
+
56
+ Args:
57
+ text: The text to sanitize.
58
+ replacement: String to replace `NUL` bytes with.
59
+
60
+ Returns:
61
+ The sanitized text with `NUL` bytes removed or replaced.
62
+
63
+ Example:
64
+ >>> sanitize_for_postgres("Hello\\x00world")
65
+ 'Helloworld'
66
+ >>> sanitize_for_postgres("Hello\\x00world", " ")
67
+ 'Hello world'
68
+ """
69
+ return text.replace("\x00", replacement)