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,366 @@
1
+ """Draws DAG in ASCII.
2
+
3
+ Adapted from https://github.com/iterative/dvc/blob/main/dvc/dagascii.py.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import math
9
+ import os
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ try:
13
+ from grandalf.graphs import Edge, Graph, Vertex # type: ignore[import-untyped]
14
+ from grandalf.layouts import SugiyamaLayout # type: ignore[import-untyped]
15
+ from grandalf.routing import route_with_lines # type: ignore[import-untyped]
16
+
17
+ _HAS_GRANDALF = True
18
+ except ImportError:
19
+ _HAS_GRANDALF = False
20
+
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Mapping, Sequence
23
+
24
+ from coco_core.runnables.graph import Edge as LangEdge
25
+
26
+
27
+ class VertexViewer:
28
+ """VertexViewer class.
29
+
30
+ Class to define vertex box boundaries that will be accounted for during
31
+ graph building by grandalf.
32
+ """
33
+
34
+ HEIGHT = 3 # top and bottom box edges + text
35
+ """Height of the box."""
36
+
37
+ def __init__(self, name: str) -> None:
38
+ """Create a VertexViewer.
39
+
40
+ Args:
41
+ name: name of the vertex.
42
+ """
43
+ self._h = self.HEIGHT # top and bottom box edges + text
44
+ self._w = len(name) + 2 # right and left bottom edges + text
45
+
46
+ @property
47
+ def h(self) -> int:
48
+ """Height of the box."""
49
+ return self._h
50
+
51
+ @property
52
+ def w(self) -> int:
53
+ """Width of the box."""
54
+ return self._w
55
+
56
+
57
+ class AsciiCanvas:
58
+ """Class for drawing in ASCII."""
59
+
60
+ TIMEOUT = 10
61
+
62
+ def __init__(self, cols: int, lines: int) -> None:
63
+ """Create an ASCII canvas.
64
+
65
+ Args:
66
+ cols: number of columns in the canvas. Should be `> 1`.
67
+ lines: number of lines in the canvas. Should be `> 1`.
68
+
69
+ Raises:
70
+ ValueError: if canvas dimensions are invalid.
71
+ """
72
+ if cols <= 1 or lines <= 1:
73
+ msg = "Canvas dimensions should be > 1"
74
+ raise ValueError(msg)
75
+
76
+ self.cols = cols
77
+ self.lines = lines
78
+
79
+ self.canvas = [[" "] * cols for line in range(lines)]
80
+
81
+ def draw(self) -> str:
82
+ """Draws ASCII canvas on the screen.
83
+
84
+ Returns:
85
+ The ASCII canvas string.
86
+ """
87
+ lines = map("".join, self.canvas)
88
+ return os.linesep.join(lines)
89
+
90
+ def point(self, x: int, y: int, char: str) -> None:
91
+ """Create a point on ASCII canvas.
92
+
93
+ Args:
94
+ x: x coordinate. Should be `>= 0` and `<` number of columns in
95
+ the canvas.
96
+ y: y coordinate. Should be `>= 0` an `<` number of lines in the
97
+ canvas.
98
+ char: character to place in the specified point on the
99
+ canvas.
100
+
101
+ Raises:
102
+ ValueError: if char is not a single character or if
103
+ coordinates are out of bounds.
104
+ """
105
+ if len(char) != 1:
106
+ msg = "char should be a single character"
107
+ raise ValueError(msg)
108
+ if x >= self.cols or x < 0:
109
+ msg = "x should be >= 0 and < number of columns"
110
+ raise ValueError(msg)
111
+ if y >= self.lines or y < 0:
112
+ msg = "y should be >= 0 and < number of lines"
113
+ raise ValueError(msg)
114
+
115
+ self.canvas[y][x] = char
116
+
117
+ def line(self, x0: int, y0: int, x1: int, y1: int, char: str) -> None:
118
+ """Create a line on ASCII canvas.
119
+
120
+ Args:
121
+ x0: x coordinate where the line should start.
122
+ y0: y coordinate where the line should start.
123
+ x1: x coordinate where the line should end.
124
+ y1: y coordinate where the line should end.
125
+ char: character to draw the line with.
126
+ """
127
+ if x0 > x1:
128
+ x1, x0 = x0, x1
129
+ y1, y0 = y0, y1
130
+
131
+ dx = x1 - x0
132
+ dy = y1 - y0
133
+
134
+ if dx == 0 and dy == 0:
135
+ self.point(x0, y0, char)
136
+ elif abs(dx) >= abs(dy):
137
+ for x in range(x0, x1 + 1):
138
+ y = y0 if dx == 0 else y0 + round((x - x0) * dy / float(dx))
139
+ self.point(x, y, char)
140
+ elif y0 < y1:
141
+ for y in range(y0, y1 + 1):
142
+ x = x0 if dy == 0 else x0 + round((y - y0) * dx / float(dy))
143
+ self.point(x, y, char)
144
+ else:
145
+ for y in range(y1, y0 + 1):
146
+ x = x0 if dy == 0 else x1 + round((y - y1) * dx / float(dy))
147
+ self.point(x, y, char)
148
+
149
+ def text(self, x: int, y: int, text: str) -> None:
150
+ """Print a text on ASCII canvas.
151
+
152
+ Args:
153
+ x: x coordinate where the text should start.
154
+ y: y coordinate where the text should start.
155
+ text: string that should be printed.
156
+ """
157
+ for i, char in enumerate(text):
158
+ self.point(x + i, y, char)
159
+
160
+ def box(self, x0: int, y0: int, width: int, height: int) -> None:
161
+ """Create a box on ASCII canvas.
162
+
163
+ Args:
164
+ x0: x coordinate of the box corner.
165
+ y0: y coordinate of the box corner.
166
+ width: box width.
167
+ height: box height.
168
+
169
+ Raises:
170
+ ValueError: if box dimensions are invalid.
171
+ """
172
+ if width <= 1 or height <= 1:
173
+ msg = "Box dimensions should be > 1"
174
+ raise ValueError(msg)
175
+
176
+ width -= 1
177
+ height -= 1
178
+
179
+ for x in range(x0, x0 + width):
180
+ self.point(x, y0, "-")
181
+ self.point(x, y0 + height, "-")
182
+
183
+ for y in range(y0, y0 + height):
184
+ self.point(x0, y, "|")
185
+ self.point(x0 + width, y, "|")
186
+
187
+ self.point(x0, y0, "+")
188
+ self.point(x0 + width, y0, "+")
189
+ self.point(x0, y0 + height, "+")
190
+ self.point(x0 + width, y0 + height, "+")
191
+
192
+
193
+ class _EdgeViewer:
194
+ def __init__(self) -> None:
195
+ self.pts: list[tuple[float]] = []
196
+
197
+ def setpath(self, pts: list[tuple[float]]) -> None:
198
+ self.pts = pts
199
+
200
+
201
+ def _build_sugiyama_layout(
202
+ vertices: Mapping[str, str], edges: Sequence[LangEdge]
203
+ ) -> Any:
204
+ if not _HAS_GRANDALF:
205
+ msg = "Install grandalf to draw graphs: `pip install grandalf`."
206
+ raise ImportError(msg)
207
+
208
+ #
209
+ # Just a reminder about naming conventions:
210
+ # +------------X
211
+ # |
212
+ # |
213
+ # |
214
+ # |
215
+ # Y
216
+ #
217
+
218
+ vertices_ = {id_: Vertex(f" {data} ") for id_, data in vertices.items()}
219
+ edges_ = [Edge(vertices_[s], vertices_[e], data=cond) for s, e, _, cond in edges]
220
+ vertices_list = vertices_.values()
221
+ graph = Graph(vertices_list, edges_)
222
+
223
+ for vertex in vertices_list:
224
+ vertex.view = VertexViewer(vertex.data)
225
+
226
+ # NOTE: determine min box length to create the best layout
227
+ minw = min(v.view.w for v in vertices_list)
228
+
229
+ for edge in edges_:
230
+ edge.view = _EdgeViewer()
231
+
232
+ sug = SugiyamaLayout(graph.C[0])
233
+ graph = graph.C[0]
234
+ roots = list(filter(lambda x: len(x.e_in()) == 0, graph.sV))
235
+
236
+ sug.init_all(roots=roots, optimize=True)
237
+
238
+ sug.yspace = VertexViewer.HEIGHT
239
+ sug.xspace = minw
240
+ sug.route_edge = route_with_lines
241
+
242
+ sug.draw()
243
+
244
+ return sug
245
+
246
+
247
+ def draw_ascii(vertices: Mapping[str, str], edges: Sequence[LangEdge]) -> str:
248
+ """Build a DAG and draw it in ASCII.
249
+
250
+ Args:
251
+ vertices: list of graph vertices.
252
+ edges: list of graph edges.
253
+
254
+ Raises:
255
+ ValueError: if the canvas dimensions are invalid or if
256
+ edge coordinates are invalid.
257
+
258
+ Returns:
259
+ ASCII representation
260
+
261
+ Example:
262
+ ```python
263
+ from coco_core.runnables.graph_ascii import draw_ascii
264
+
265
+ vertices = {1: "1", 2: "2", 3: "3", 4: "4"}
266
+ edges = [
267
+ (source, target, None, None)
268
+ for source, target in [(1, 2), (2, 3), (2, 4), (1, 4)]
269
+ ]
270
+
271
+
272
+ print(draw_ascii(vertices, edges))
273
+ ```
274
+
275
+ ```txt
276
+
277
+ +---+
278
+ | 1 |
279
+ +---+
280
+ * *
281
+ * *
282
+ * *
283
+ +---+ *
284
+ | 2 | *
285
+ +---+** *
286
+ * ** *
287
+ * ** *
288
+ * **
289
+ +---+ +---+
290
+ | 3 | | 4 |
291
+ +---+ +---+
292
+ ```
293
+ """
294
+ # NOTE: coordinates might me negative, so we need to shift
295
+ # everything to the positive plane before we actually draw it.
296
+ xlist: list[float] = []
297
+ ylist: list[float] = []
298
+
299
+ sug = _build_sugiyama_layout(vertices, edges)
300
+
301
+ for vertex in sug.g.sV:
302
+ # NOTE: moving boxes w/2 to the left
303
+ xlist.extend(
304
+ (
305
+ vertex.view.xy[0] - vertex.view.w / 2.0,
306
+ vertex.view.xy[0] + vertex.view.w / 2.0,
307
+ )
308
+ )
309
+ ylist.extend((vertex.view.xy[1], vertex.view.xy[1] + vertex.view.h))
310
+
311
+ for edge in sug.g.sE:
312
+ for x, y in edge.view.pts:
313
+ xlist.append(x)
314
+ ylist.append(y)
315
+
316
+ minx = min(xlist)
317
+ miny = min(ylist)
318
+ maxx = max(xlist)
319
+ maxy = max(ylist)
320
+
321
+ canvas_cols = math.ceil(math.ceil(maxx) - math.floor(minx)) + 1
322
+ canvas_lines = round(maxy - miny)
323
+
324
+ canvas = AsciiCanvas(canvas_cols, canvas_lines)
325
+
326
+ # NOTE: first draw edges so that node boxes could overwrite them
327
+ for edge in sug.g.sE:
328
+ if len(edge.view.pts) <= 1:
329
+ msg = "Not enough points to draw an edge"
330
+ raise ValueError(msg)
331
+ for index in range(1, len(edge.view.pts)):
332
+ start = edge.view.pts[index - 1]
333
+ end = edge.view.pts[index]
334
+
335
+ start_x = round(start[0] - minx)
336
+ start_y = round(start[1] - miny)
337
+ end_x = round(end[0] - minx)
338
+ end_y = round(end[1] - miny)
339
+
340
+ if start_x < 0 or start_y < 0 or end_x < 0 or end_y < 0:
341
+ msg = (
342
+ "Invalid edge coordinates: "
343
+ f"start_x={start_x}, "
344
+ f"start_y={start_y}, "
345
+ f"end_x={end_x}, "
346
+ f"end_y={end_y}"
347
+ )
348
+ raise ValueError(msg)
349
+
350
+ canvas.line(start_x, start_y, end_x, end_y, "." if edge.data else "*")
351
+
352
+ for vertex in sug.g.sV:
353
+ # NOTE: moving boxes w/2 to the left
354
+ x = vertex.view.xy[0] - vertex.view.w / 2.0
355
+ y = vertex.view.xy[1]
356
+
357
+ canvas.box(
358
+ round(x - minx),
359
+ round(y - miny),
360
+ vertex.view.w,
361
+ vertex.view.h,
362
+ )
363
+
364
+ canvas.text(round(x - minx) + 1, round(y - miny) + 1, vertex.data)
365
+
366
+ return canvas.draw()