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.
- coco/__init__.py +3 -0
- coco/agents/__init__.py +9 -0
- coco/agents/_subagent_transformer.py +307 -0
- coco/agents/factory.py +2164 -0
- coco/agents/middleware/__init__.py +100 -0
- coco/agents/middleware/_execution.py +385 -0
- coco/agents/middleware/_redaction.py +454 -0
- coco/agents/middleware/_retry.py +134 -0
- coco/agents/middleware/_trace_policy.py +61 -0
- coco/agents/middleware/context_editing.py +306 -0
- coco/agents/middleware/file_search.py +433 -0
- coco/agents/middleware/human_in_the_loop.py +680 -0
- coco/agents/middleware/internal_call_transformer.py +108 -0
- coco/agents/middleware/model_call_limit.py +267 -0
- coco/agents/middleware/model_fallback.py +417 -0
- coco/agents/middleware/model_retry.py +327 -0
- coco/agents/middleware/pii.py +878 -0
- coco/agents/middleware/provider_tool_search.py +307 -0
- coco/agents/middleware/shell_tool.py +949 -0
- coco/agents/middleware/summarization.py +910 -0
- coco/agents/middleware/todo.py +357 -0
- coco/agents/middleware/tool_call_limit.py +495 -0
- coco/agents/middleware/tool_emulator.py +251 -0
- coco/agents/middleware/tool_error.py +209 -0
- coco/agents/middleware/tool_retry.py +421 -0
- coco/agents/middleware/tool_selection.py +515 -0
- coco/agents/middleware/types.py +2201 -0
- coco/agents/structured_output.py +472 -0
- coco/chat_models/__init__.py +7 -0
- coco/chat_models/base.py +1082 -0
- coco/embeddings/__init__.py +18 -0
- coco/embeddings/base.py +279 -0
- coco/mcp/__init__.py +36 -0
- coco/mcp/adapter.py +245 -0
- coco/mcp/elicitation.py +394 -0
- coco/mcp/tools.py +307 -0
- coco/messages/__init__.py +73 -0
- coco/py.typed +0 -0
- coco/rate_limiters/__init__.py +13 -0
- coco/tools/__init__.py +22 -0
- coco/tools/tool_node.py +20 -0
- coco_anthropic/__init__.py +15 -0
- coco_anthropic/_client_utils.py +81 -0
- coco_anthropic/_compat.py +228 -0
- coco_anthropic/_sdk_compat.py +124 -0
- coco_anthropic/_version.py +3 -0
- coco_anthropic/chat_models.py +3349 -0
- coco_anthropic/data/__init__.py +1 -0
- coco_anthropic/data/_profiles.py +491 -0
- coco_anthropic/data/profile_augmentations.toml +69 -0
- coco_anthropic/experimental.py +140 -0
- coco_anthropic/llms.py +428 -0
- coco_anthropic/middleware/__init__.py +25 -0
- coco_anthropic/middleware/anthropic_tools.py +1197 -0
- coco_anthropic/middleware/bash.py +84 -0
- coco_anthropic/middleware/file_search.py +360 -0
- coco_anthropic/middleware/prompt_caching.py +276 -0
- coco_anthropic/output_parsers.py +93 -0
- coco_anthropic/py.typed +0 -0
- coco_core/__init__.py +20 -0
- coco_core/_api/__init__.py +87 -0
- coco_core/_api/beta_decorator.py +266 -0
- coco_core/_api/deprecation.py +637 -0
- coco_core/_api/internal.py +23 -0
- coco_core/_api/path.py +50 -0
- coco_core/_import_utils.py +41 -0
- coco_core/_protocol.py +625 -0
- coco_core/_security/__init__.py +36 -0
- coco_core/_security/_exceptions.py +9 -0
- coco_core/_security/_policy.py +313 -0
- coco_core/_security/_ssrf_protection.py +155 -0
- coco_core/_security/_transport.py +254 -0
- coco_core/agents.py +258 -0
- coco_core/caches.py +272 -0
- coco_core/callbacks/__init__.py +132 -0
- coco_core/callbacks/base.py +1229 -0
- coco_core/callbacks/file.py +266 -0
- coco_core/callbacks/manager.py +2826 -0
- coco_core/callbacks/stdout.py +123 -0
- coco_core/callbacks/streaming_stdout.py +154 -0
- coco_core/callbacks/usage.py +124 -0
- coco_core/chat_history.py +267 -0
- coco_core/chat_loaders.py +26 -0
- coco_core/chat_sessions.py +19 -0
- coco_core/cross_encoders.py +18 -0
- coco_core/document_loaders/__init__.py +36 -0
- coco_core/document_loaders/base.py +155 -0
- coco_core/document_loaders/blob_loaders.py +38 -0
- coco_core/documents/__init__.py +55 -0
- coco_core/documents/base.py +347 -0
- coco_core/documents/compressor.py +74 -0
- coco_core/documents/transformers.py +79 -0
- coco_core/embeddings/__init__.py +31 -0
- coco_core/embeddings/embeddings.py +78 -0
- coco_core/embeddings/fake.py +129 -0
- coco_core/env.py +22 -0
- coco_core/example_selectors/__init__.py +47 -0
- coco_core/example_selectors/base.py +60 -0
- coco_core/example_selectors/length_based.py +131 -0
- coco_core/example_selectors/semantic_similarity.py +366 -0
- coco_core/exceptions.py +166 -0
- coco_core/globals.py +72 -0
- coco_core/indexing/__init__.py +53 -0
- coco_core/indexing/api.py +954 -0
- coco_core/indexing/base.py +661 -0
- coco_core/indexing/in_memory.py +104 -0
- coco_core/language_models/__init__.py +116 -0
- coco_core/language_models/_compat_bridge.py +844 -0
- coco_core/language_models/_utils.py +343 -0
- coco_core/language_models/base.py +500 -0
- coco_core/language_models/chat_model_stream.py +1525 -0
- coco_core/language_models/chat_models.py +2748 -0
- coco_core/language_models/fake.py +137 -0
- coco_core/language_models/fake_chat_models.py +396 -0
- coco_core/language_models/llms.py +1569 -0
- coco_core/language_models/model_profile.py +183 -0
- coco_core/load/__init__.py +44 -0
- coco_core/load/_validation.py +191 -0
- coco_core/load/dump.py +120 -0
- coco_core/load/load.py +828 -0
- coco_core/load/mapping.py +1097 -0
- coco_core/load/serializable.py +406 -0
- coco_core/messages/__init__.py +198 -0
- coco_core/messages/ai.py +854 -0
- coco_core/messages/base.py +518 -0
- coco_core/messages/block_translators/__init__.py +112 -0
- coco_core/messages/block_translators/anthropic.py +534 -0
- coco_core/messages/block_translators/bedrock.py +108 -0
- coco_core/messages/block_translators/bedrock_converse.py +329 -0
- coco_core/messages/block_translators/coco_v0.py +303 -0
- coco_core/messages/block_translators/google_genai.py +610 -0
- coco_core/messages/block_translators/google_vertexai.py +21 -0
- coco_core/messages/block_translators/groq.py +157 -0
- coco_core/messages/block_translators/openai.py +1089 -0
- coco_core/messages/chat.py +64 -0
- coco_core/messages/content.py +1488 -0
- coco_core/messages/function.py +62 -0
- coco_core/messages/human.py +70 -0
- coco_core/messages/modifier.py +33 -0
- coco_core/messages/system.py +70 -0
- coco_core/messages/tool.py +418 -0
- coco_core/messages/utils.py +2442 -0
- coco_core/output_parsers/__init__.py +101 -0
- coco_core/output_parsers/base.py +361 -0
- coco_core/output_parsers/format_instructions.py +16 -0
- coco_core/output_parsers/json.py +139 -0
- coco_core/output_parsers/list.py +249 -0
- coco_core/output_parsers/openai_functions.py +324 -0
- coco_core/output_parsers/openai_tools.py +383 -0
- coco_core/output_parsers/pydantic.py +141 -0
- coco_core/output_parsers/string.py +63 -0
- coco_core/output_parsers/transform.py +175 -0
- coco_core/output_parsers/xml.py +301 -0
- coco_core/outputs/__init__.py +66 -0
- coco_core/outputs/chat_generation.py +147 -0
- coco_core/outputs/chat_result.py +39 -0
- coco_core/outputs/generation.py +80 -0
- coco_core/outputs/llm_result.py +112 -0
- coco_core/outputs/run_info.py +22 -0
- coco_core/prompt_values.py +161 -0
- coco_core/prompts/__init__.py +101 -0
- coco_core/prompts/base.py +508 -0
- coco_core/prompts/chat.py +1495 -0
- coco_core/prompts/dict.py +177 -0
- coco_core/prompts/few_shot.py +483 -0
- coco_core/prompts/few_shot_with_templates.py +239 -0
- coco_core/prompts/image.py +177 -0
- coco_core/prompts/loading.py +296 -0
- coco_core/prompts/message.py +97 -0
- coco_core/prompts/prompt.py +312 -0
- coco_core/prompts/string.py +399 -0
- coco_core/prompts/structured.py +214 -0
- coco_core/py.typed +0 -0
- coco_core/rate_limiters.py +254 -0
- coco_core/retrievers.py +328 -0
- coco_core/runnables/__init__.py +136 -0
- coco_core/runnables/base.py +6725 -0
- coco_core/runnables/branch.py +457 -0
- coco_core/runnables/config.py +712 -0
- coco_core/runnables/configurable.py +710 -0
- coco_core/runnables/fallbacks.py +663 -0
- coco_core/runnables/graph.py +741 -0
- coco_core/runnables/graph_ascii.py +366 -0
- coco_core/runnables/graph_mermaid.py +503 -0
- coco_core/runnables/graph_png.py +215 -0
- coco_core/runnables/history.py +631 -0
- coco_core/runnables/passthrough.py +849 -0
- coco_core/runnables/retry.py +379 -0
- coco_core/runnables/router.py +239 -0
- coco_core/runnables/schema.py +188 -0
- coco_core/runnables/utils.py +798 -0
- coco_core/stores.py +291 -0
- coco_core/structured_query.py +203 -0
- coco_core/sys_info.py +154 -0
- coco_core/tools/__init__.py +95 -0
- coco_core/tools/base.py +1948 -0
- coco_core/tools/convert.py +497 -0
- coco_core/tools/render.py +67 -0
- coco_core/tools/retriever.py +94 -0
- coco_core/tools/simple.py +208 -0
- coco_core/tools/structured.py +322 -0
- coco_core/tracers/__init__.py +47 -0
- coco_core/tracers/_compat.py +95 -0
- coco_core/tracers/_context.py +337 -0
- coco_core/tracers/_streaming.py +53 -0
- coco_core/tracers/base.py +955 -0
- coco_core/tracers/coco.py +279 -0
- coco_core/tracers/context.py +205 -0
- coco_core/tracers/core.py +766 -0
- coco_core/tracers/event_stream.py +1101 -0
- coco_core/tracers/log_stream.py +765 -0
- coco_core/tracers/memory_stream.py +200 -0
- coco_core/tracers/root_listeners.py +130 -0
- coco_core/tracers/run_collector.py +40 -0
- coco_core/tracers/schemas.py +14 -0
- coco_core/tracers/stdout.py +205 -0
- coco_core/utils/__init__.py +111 -0
- coco_core/utils/_gateway.py +274 -0
- coco_core/utils/_merge.py +218 -0
- coco_core/utils/aiter.py +357 -0
- coco_core/utils/env.py +86 -0
- coco_core/utils/formatting.py +80 -0
- coco_core/utils/function_calling.py +869 -0
- coco_core/utils/html.py +132 -0
- coco_core/utils/image.py +15 -0
- coco_core/utils/input.py +82 -0
- coco_core/utils/interactive_env.py +12 -0
- coco_core/utils/iter.py +229 -0
- coco_core/utils/json.py +230 -0
- coco_core/utils/json_schema.py +273 -0
- coco_core/utils/mustache.py +706 -0
- coco_core/utils/pydantic.py +631 -0
- coco_core/utils/strings.py +69 -0
- coco_core/utils/usage.py +61 -0
- coco_core/utils/utils.py +528 -0
- coco_core/utils/uuid.py +57 -0
- coco_core/vectorstores/__init__.py +53 -0
- coco_core/vectorstores/base.py +1111 -0
- coco_core/vectorstores/in_memory.py +546 -0
- coco_core/vectorstores/utils.py +163 -0
- coco_core/version.py +3 -0
- coco_framework-0.1.0.dist-info/METADATA +184 -0
- coco_framework-0.1.0.dist-info/RECORD +400 -0
- coco_framework-0.1.0.dist-info/WHEEL +4 -0
- coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
- coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
- coco_graph/_internal/__init__.py +4 -0
- coco_graph/_internal/_cache.py +31 -0
- coco_graph/_internal/_config.py +474 -0
- coco_graph/_internal/_constants.py +140 -0
- coco_graph/_internal/_fields.py +213 -0
- coco_graph/_internal/_future.py +220 -0
- coco_graph/_internal/_pydantic.py +275 -0
- coco_graph/_internal/_queue.py +124 -0
- coco_graph/_internal/_replay.py +90 -0
- coco_graph/_internal/_retry.py +29 -0
- coco_graph/_internal/_runnable.py +969 -0
- coco_graph/_internal/_scratchpad.py +19 -0
- coco_graph/_internal/_serde.py +253 -0
- coco_graph/_internal/_timeout.py +25 -0
- coco_graph/_internal/_typing.py +54 -0
- coco_graph/cache/base/__init__.py +48 -0
- coco_graph/cache/base/py.typed +0 -0
- coco_graph/cache/memory/__init__.py +73 -0
- coco_graph/cache/redis/__init__.py +144 -0
- coco_graph/cache/sqlite/__init__.py +120 -0
- coco_graph/callbacks.py +394 -0
- coco_graph/channels/__init__.py +29 -0
- coco_graph/channels/any_value.py +72 -0
- coco_graph/channels/base.py +121 -0
- coco_graph/channels/binop.py +155 -0
- coco_graph/channels/delta.py +202 -0
- coco_graph/channels/ephemeral_value.py +79 -0
- coco_graph/channels/last_value.py +151 -0
- coco_graph/channels/named_barrier_value.py +167 -0
- coco_graph/channels/topic.py +94 -0
- coco_graph/channels/untracked_value.py +73 -0
- coco_graph/checkpoint/base/__init__.py +861 -0
- coco_graph/checkpoint/base/id.py +109 -0
- coco_graph/checkpoint/base/py.typed +0 -0
- coco_graph/checkpoint/memory/__init__.py +698 -0
- coco_graph/checkpoint/memory/py.typed +0 -0
- coco_graph/checkpoint/postgres/__init__.py +602 -0
- coco_graph/checkpoint/postgres/_ainternal.py +23 -0
- coco_graph/checkpoint/postgres/_internal.py +21 -0
- coco_graph/checkpoint/postgres/aio.py +691 -0
- coco_graph/checkpoint/postgres/base.py +666 -0
- coco_graph/checkpoint/postgres/py.typed +0 -0
- coco_graph/checkpoint/postgres/shallow.py +967 -0
- coco_graph/checkpoint/serde/__init__.py +0 -0
- coco_graph/checkpoint/serde/_msgpack.py +97 -0
- coco_graph/checkpoint/serde/base.py +64 -0
- coco_graph/checkpoint/serde/encrypted.py +80 -0
- coco_graph/checkpoint/serde/event_hooks.py +52 -0
- coco_graph/checkpoint/serde/jsonplus.py +883 -0
- coco_graph/checkpoint/serde/py.typed +0 -0
- coco_graph/checkpoint/serde/types.py +68 -0
- coco_graph/checkpoint/sqlite/__init__.py +645 -0
- coco_graph/checkpoint/sqlite/_delta.py +172 -0
- coco_graph/checkpoint/sqlite/aio.py +738 -0
- coco_graph/checkpoint/sqlite/py.typed +0 -0
- coco_graph/checkpoint/sqlite/utils.py +116 -0
- coco_graph/config.py +196 -0
- coco_graph/constants.py +64 -0
- coco_graph/errors.py +241 -0
- coco_graph/func/__init__.py +620 -0
- coco_graph/graph/__init__.py +12 -0
- coco_graph/graph/_branch.py +225 -0
- coco_graph/graph/_node.py +103 -0
- coco_graph/graph/message.py +437 -0
- coco_graph/graph/state.py +1978 -0
- coco_graph/graph/ui.py +227 -0
- coco_graph/managed/__init__.py +3 -0
- coco_graph/managed/base.py +31 -0
- coco_graph/managed/is_last_step.py +24 -0
- coco_graph/prebuilt/__init__.py +23 -0
- coco_graph/prebuilt/_tool_call_stream.py +117 -0
- coco_graph/prebuilt/_tool_call_transformer.py +165 -0
- coco_graph/prebuilt/chat_agent_executor.py +1015 -0
- coco_graph/prebuilt/interrupt.py +105 -0
- coco_graph/prebuilt/py.typed +0 -0
- coco_graph/prebuilt/tool_node.py +2030 -0
- coco_graph/prebuilt/tool_validator.py +221 -0
- coco_graph/pregel/__init__.py +3 -0
- coco_graph/pregel/_algo.py +1460 -0
- coco_graph/pregel/_call.py +298 -0
- coco_graph/pregel/_checkpoint.py +331 -0
- coco_graph/pregel/_config.py +0 -0
- coco_graph/pregel/_draw.py +294 -0
- coco_graph/pregel/_executor.py +223 -0
- coco_graph/pregel/_io.py +174 -0
- coco_graph/pregel/_log.py +3 -0
- coco_graph/pregel/_loop.py +1988 -0
- coco_graph/pregel/_messages.py +461 -0
- coco_graph/pregel/_read.py +312 -0
- coco_graph/pregel/_retry.py +854 -0
- coco_graph/pregel/_runner.py +941 -0
- coco_graph/pregel/_tools.py +268 -0
- coco_graph/pregel/_utils.py +221 -0
- coco_graph/pregel/_validate.py +120 -0
- coco_graph/pregel/_write.py +192 -0
- coco_graph/pregel/debug.py +302 -0
- coco_graph/pregel/main.py +4364 -0
- coco_graph/pregel/protocol.py +288 -0
- coco_graph/pregel/types.py +38 -0
- coco_graph/py.typed +0 -0
- coco_graph/runtime.py +350 -0
- coco_graph/store/base/__init__.py +1322 -0
- coco_graph/store/base/batch.py +371 -0
- coco_graph/store/base/embed.py +436 -0
- coco_graph/store/base/py.typed +0 -0
- coco_graph/store/memory/__init__.py +592 -0
- coco_graph/store/memory/py.typed +0 -0
- coco_graph/store/postgres/__init__.py +4 -0
- coco_graph/store/postgres/aio.py +592 -0
- coco_graph/store/postgres/base.py +1480 -0
- coco_graph/store/postgres/py.typed +0 -0
- coco_graph/store/sqlite/__init__.py +4 -0
- coco_graph/store/sqlite/aio.py +632 -0
- coco_graph/store/sqlite/base.py +1514 -0
- coco_graph/stream/__init__.py +45 -0
- coco_graph/stream/_convert.py +32 -0
- coco_graph/stream/_mux.py +523 -0
- coco_graph/stream/_types.py +330 -0
- coco_graph/stream/run_stream.py +735 -0
- coco_graph/stream/stream_channel.py +341 -0
- coco_graph/stream/transformers.py +1039 -0
- coco_graph/types.py +1079 -0
- coco_graph/typing.py +48 -0
- coco_graph/utils/__init__.py +1 -0
- coco_graph/utils/config.py +4 -0
- coco_graph/utils/runnable.py +3 -0
- coco_graph/version.py +13 -0
- coco_graph/warnings.py +69 -0
- coco_openai/__init__.py +20 -0
- coco_openai/_compat.py +45 -0
- coco_openai/_version.py +3 -0
- coco_openai/chat_models/__init__.py +6 -0
- coco_openai/chat_models/_client_utils.py +684 -0
- coco_openai/chat_models/_compat.py +525 -0
- coco_openai/chat_models/azure.py +1227 -0
- coco_openai/chat_models/base.py +5654 -0
- coco_openai/chat_models/codex.py +557 -0
- coco_openai/chatgpt_oauth.py +1063 -0
- coco_openai/data/__init__.py +1 -0
- coco_openai/data/_profiles.py +1655 -0
- coco_openai/data/profile_augmentations.toml +127 -0
- coco_openai/embeddings/__init__.py +6 -0
- coco_openai/embeddings/azure.py +240 -0
- coco_openai/embeddings/base.py +820 -0
- coco_openai/llms/__init__.py +6 -0
- coco_openai/llms/azure.py +249 -0
- coco_openai/llms/base.py +919 -0
- coco_openai/middleware/__init__.py +11 -0
- coco_openai/middleware/openai_moderation.py +484 -0
- coco_openai/output_parsers/__init__.py +9 -0
- coco_openai/output_parsers/tools.py +9 -0
- coco_openai/py.typed +0 -0
- coco_openai/tools/__init__.py +5 -0
- 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()
|