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,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from typing import Any, Generic, TypeVar
|
|
6
|
+
|
|
7
|
+
from typing_extensions import Self
|
|
8
|
+
|
|
9
|
+
from coco_graph._internal._typing import MISSING
|
|
10
|
+
from coco_graph.errors import EmptyChannelError
|
|
11
|
+
|
|
12
|
+
Value = TypeVar("Value")
|
|
13
|
+
Update = TypeVar("Update")
|
|
14
|
+
Checkpoint = TypeVar("Checkpoint")
|
|
15
|
+
|
|
16
|
+
__all__ = ("BaseChannel",)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseChannel(Generic[Value, Update, Checkpoint], ABC):
|
|
20
|
+
"""Base class for all channels."""
|
|
21
|
+
|
|
22
|
+
__slots__ = ("key", "typ")
|
|
23
|
+
|
|
24
|
+
def __init__(self, typ: Any, key: str = "") -> None:
|
|
25
|
+
self.typ = typ
|
|
26
|
+
self.key = key
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def ValueType(self) -> Any:
|
|
31
|
+
"""The type of the value stored in the channel."""
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def UpdateType(self) -> Any:
|
|
36
|
+
"""The type of the update received by the channel."""
|
|
37
|
+
|
|
38
|
+
# serialize/deserialize methods
|
|
39
|
+
|
|
40
|
+
def copy(self) -> Self:
|
|
41
|
+
"""Return a copy of the channel.
|
|
42
|
+
|
|
43
|
+
By default, delegates to `checkpoint()` and `from_checkpoint()`.
|
|
44
|
+
|
|
45
|
+
Subclasses can override this method with a more efficient implementation.
|
|
46
|
+
"""
|
|
47
|
+
return self.from_checkpoint(self.checkpoint())
|
|
48
|
+
|
|
49
|
+
def checkpoint(self) -> Checkpoint | Any:
|
|
50
|
+
"""Return a serializable representation of the channel's current state.
|
|
51
|
+
|
|
52
|
+
Raises `EmptyChannelError` if the channel is empty (never updated yet),
|
|
53
|
+
or doesn't support checkpoints.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
return self.get()
|
|
57
|
+
except EmptyChannelError:
|
|
58
|
+
return MISSING
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def from_checkpoint(self, checkpoint: Checkpoint | Any) -> Self:
|
|
62
|
+
"""Return a new identical channel, optionally initialized from a checkpoint.
|
|
63
|
+
|
|
64
|
+
If the checkpoint contains complex data structures, they should be copied.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# read methods
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def get(self) -> Value:
|
|
71
|
+
"""Return the current value of the channel.
|
|
72
|
+
|
|
73
|
+
Raises `EmptyChannelError` if the channel is empty (never updated yet)."""
|
|
74
|
+
|
|
75
|
+
def is_available(self) -> bool:
|
|
76
|
+
"""Return `True` if the channel is available (not empty), `False` otherwise.
|
|
77
|
+
|
|
78
|
+
Subclasses should override this method to provide a more efficient
|
|
79
|
+
implementation than calling `get()` and catching `EmptyChannelError`.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
self.get()
|
|
83
|
+
return True
|
|
84
|
+
except EmptyChannelError:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
# write methods
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def update(self, values: Sequence[Update]) -> bool:
|
|
91
|
+
"""Update the channel's value with the given sequence of updates.
|
|
92
|
+
The order of the updates in the sequence is arbitrary.
|
|
93
|
+
This method is called by Pregel for all channels at the end of each step.
|
|
94
|
+
|
|
95
|
+
If there are no updates, it is called with an empty sequence.
|
|
96
|
+
|
|
97
|
+
Raises `InvalidUpdateError` if the sequence of updates is invalid.
|
|
98
|
+
|
|
99
|
+
Returns `True` if the channel was updated, `False` otherwise."""
|
|
100
|
+
|
|
101
|
+
def consume(self) -> bool:
|
|
102
|
+
"""Notify the channel that a subscribed task ran.
|
|
103
|
+
|
|
104
|
+
By default, no-op.
|
|
105
|
+
|
|
106
|
+
A channel can use this method to modify its state, preventing the value from being consumed again.
|
|
107
|
+
|
|
108
|
+
Returns `True` if the channel was updated, `False` otherwise.
|
|
109
|
+
"""
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def finish(self) -> bool:
|
|
113
|
+
"""Notify the channel that the Pregel run is finishing.
|
|
114
|
+
|
|
115
|
+
By default, no-op.
|
|
116
|
+
|
|
117
|
+
A channel can use this method to modify its state, preventing finish.
|
|
118
|
+
|
|
119
|
+
Returns `True` if the channel was updated, `False` otherwise.
|
|
120
|
+
"""
|
|
121
|
+
return False
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import collections.abc
|
|
2
|
+
from collections.abc import Callable, Sequence
|
|
3
|
+
from typing import Any, Generic
|
|
4
|
+
|
|
5
|
+
from typing_extensions import NotRequired, Required, Self
|
|
6
|
+
|
|
7
|
+
from coco_graph._internal._constants import OVERWRITE
|
|
8
|
+
from coco_graph._internal._typing import MISSING
|
|
9
|
+
from coco_graph.channels.base import BaseChannel, Value
|
|
10
|
+
from coco_graph.errors import (
|
|
11
|
+
EmptyChannelError,
|
|
12
|
+
ErrorCode,
|
|
13
|
+
InvalidUpdateError,
|
|
14
|
+
create_error_message,
|
|
15
|
+
)
|
|
16
|
+
from coco_graph.types import Overwrite
|
|
17
|
+
|
|
18
|
+
__all__ = ("BinaryOperatorAggregate",)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Adapted from typing_extensions
|
|
22
|
+
def _strip_extras(t): # type: ignore[no-untyped-def]
|
|
23
|
+
"""Strips Annotated, Required and NotRequired from a given type."""
|
|
24
|
+
if hasattr(t, "__origin__"):
|
|
25
|
+
if t.__origin__ in (Required, NotRequired):
|
|
26
|
+
return _strip_extras(t.__args__[0])
|
|
27
|
+
return _strip_extras(t.__origin__)
|
|
28
|
+
return t
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _get_overwrite(value: Any) -> tuple[bool, Any]:
|
|
32
|
+
"""Inspects the given value and returns (is_overwrite, overwrite_value).
|
|
33
|
+
|
|
34
|
+
Recognises three forms:
|
|
35
|
+
|
|
36
|
+
* The typed `Overwrite` dataclass instance.
|
|
37
|
+
* The sentinel-keyed `{"__overwrite__": value}` dict form.
|
|
38
|
+
* The dataclass-erased `{"value": ..., "type": "__overwrite__"}` form that
|
|
39
|
+
results from JSON-serialising an `Overwrite` (e.g. an `orjson`-encoded
|
|
40
|
+
state update routed through the CocoGraph API server). This keeps the
|
|
41
|
+
`Overwrite` semantics intact across JSON boundaries that strip dataclass
|
|
42
|
+
types.
|
|
43
|
+
"""
|
|
44
|
+
if isinstance(value, Overwrite):
|
|
45
|
+
return True, value.value
|
|
46
|
+
if isinstance(value, dict):
|
|
47
|
+
if len(value) == 1 and OVERWRITE in value:
|
|
48
|
+
return True, value[OVERWRITE]
|
|
49
|
+
if value.get("type") == OVERWRITE and "value" in value:
|
|
50
|
+
return True, value["value"]
|
|
51
|
+
return False, None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _operators_equal(a: Callable, b: Callable) -> bool:
|
|
55
|
+
"""Return True if two reducer operators should be considered equal.
|
|
56
|
+
|
|
57
|
+
Lambdas all share the name '<lambda>' so identity comparison is
|
|
58
|
+
unreliable; treat any pairing that includes a lambda as equal.
|
|
59
|
+
"""
|
|
60
|
+
if a.__name__ == "<lambda>" or b.__name__ == "<lambda>":
|
|
61
|
+
return True
|
|
62
|
+
return a is b
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
|
|
66
|
+
"""Stores the result of applying a binary operator to the current value and each new value.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import operator
|
|
70
|
+
|
|
71
|
+
total = Channels.BinaryOperatorAggregate(int, operator.add)
|
|
72
|
+
```
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
__slots__ = ("value", "operator")
|
|
76
|
+
|
|
77
|
+
def __init__(self, typ: type[Value], operator: Callable[[Value, Value], Value]):
|
|
78
|
+
super().__init__(typ)
|
|
79
|
+
self.operator = operator
|
|
80
|
+
# special forms from typing or collections.abc are not instantiable
|
|
81
|
+
# so we need to replace them with their concrete counterparts
|
|
82
|
+
typ = _strip_extras(typ)
|
|
83
|
+
if typ in (collections.abc.Sequence, collections.abc.MutableSequence):
|
|
84
|
+
typ = list
|
|
85
|
+
if typ in (collections.abc.Set, collections.abc.MutableSet):
|
|
86
|
+
typ = set
|
|
87
|
+
if typ in (collections.abc.Mapping, collections.abc.MutableMapping):
|
|
88
|
+
typ = dict
|
|
89
|
+
try:
|
|
90
|
+
self.value = typ()
|
|
91
|
+
except Exception:
|
|
92
|
+
self.value = MISSING
|
|
93
|
+
|
|
94
|
+
def __eq__(self, value: object) -> bool:
|
|
95
|
+
return isinstance(value, BinaryOperatorAggregate) and _operators_equal(
|
|
96
|
+
self.operator, value.operator
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def ValueType(self) -> type[Value]:
|
|
101
|
+
"""The type of the value stored in the channel."""
|
|
102
|
+
return self.typ
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def UpdateType(self) -> type[Value]:
|
|
106
|
+
"""The type of the update received by the channel."""
|
|
107
|
+
return self.typ
|
|
108
|
+
|
|
109
|
+
def copy(self) -> Self:
|
|
110
|
+
"""Return a copy of the channel."""
|
|
111
|
+
empty = self.__class__(self.typ, self.operator)
|
|
112
|
+
empty.key = self.key
|
|
113
|
+
empty.value = self.value
|
|
114
|
+
return empty
|
|
115
|
+
|
|
116
|
+
def from_checkpoint(self, checkpoint: Value) -> Self:
|
|
117
|
+
empty = self.__class__(self.typ, self.operator)
|
|
118
|
+
empty.key = self.key
|
|
119
|
+
if checkpoint is not MISSING:
|
|
120
|
+
empty.value = checkpoint
|
|
121
|
+
return empty
|
|
122
|
+
|
|
123
|
+
def update(self, values: Sequence[Value]) -> bool:
|
|
124
|
+
if not values:
|
|
125
|
+
return False
|
|
126
|
+
if self.value is MISSING:
|
|
127
|
+
self.value = values[0]
|
|
128
|
+
values = values[1:]
|
|
129
|
+
seen_overwrite: bool = False
|
|
130
|
+
for value in values:
|
|
131
|
+
is_overwrite, overwrite_value = _get_overwrite(value)
|
|
132
|
+
if is_overwrite:
|
|
133
|
+
if seen_overwrite:
|
|
134
|
+
msg = create_error_message(
|
|
135
|
+
message="Can receive only one Overwrite value per super-step.",
|
|
136
|
+
error_code=ErrorCode.INVALID_CONCURRENT_GRAPH_UPDATE,
|
|
137
|
+
)
|
|
138
|
+
raise InvalidUpdateError(msg)
|
|
139
|
+
self.value = overwrite_value
|
|
140
|
+
seen_overwrite = True
|
|
141
|
+
continue
|
|
142
|
+
if not seen_overwrite:
|
|
143
|
+
self.value = self.operator(self.value, value)
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
def get(self) -> Value:
|
|
147
|
+
if self.value is MISSING:
|
|
148
|
+
raise EmptyChannelError()
|
|
149
|
+
return self.value
|
|
150
|
+
|
|
151
|
+
def is_available(self) -> bool:
|
|
152
|
+
return self.value is not MISSING
|
|
153
|
+
|
|
154
|
+
def checkpoint(self) -> Value:
|
|
155
|
+
return self.value
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import collections.abc
|
|
4
|
+
import copy as _copy
|
|
5
|
+
from collections.abc import Callable, Sequence
|
|
6
|
+
from typing import Any, Generic
|
|
7
|
+
|
|
8
|
+
from coco_graph.checkpoint.base import PendingWrite
|
|
9
|
+
from coco_graph.checkpoint.serde.types import _DeltaSnapshot
|
|
10
|
+
from typing_extensions import Self
|
|
11
|
+
|
|
12
|
+
from coco_graph._internal._typing import MISSING
|
|
13
|
+
from coco_graph.channels.base import BaseChannel, Value
|
|
14
|
+
from coco_graph.channels.binop import _get_overwrite, _operators_equal, _strip_extras
|
|
15
|
+
from coco_graph.errors import (
|
|
16
|
+
EmptyChannelError,
|
|
17
|
+
ErrorCode,
|
|
18
|
+
InvalidUpdateError,
|
|
19
|
+
create_error_message,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = ("DeltaChannel",)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DeltaChannel(Generic[Value], BaseChannel[Any, Any, Any]):
|
|
26
|
+
"""Reducer channel that stores only a sentinel in checkpoint blobs and
|
|
27
|
+
reconstructs state by replaying ancestor writes through the reducer.
|
|
28
|
+
|
|
29
|
+
!!! warning "Beta"
|
|
30
|
+
|
|
31
|
+
`DeltaChannel` is in beta. The API and on-disk representation may
|
|
32
|
+
change in future releases. Threads written with `DeltaChannel` today
|
|
33
|
+
are expected to remain readable, but the surrounding contract
|
|
34
|
+
(`BaseCheckpointSaver.get_delta_channel_history`, the
|
|
35
|
+
`_DeltaSnapshot` blob shape, the `counters_since_delta_snapshot`
|
|
36
|
+
metadata field) is not yet stable.
|
|
37
|
+
|
|
38
|
+
The reducer receives the current accumulated value and a batch of writes
|
|
39
|
+
in one call: `reducer(state, [write1, write2, ...]) -> new_state`.
|
|
40
|
+
|
|
41
|
+
Reducers must be deterministic and batching-invariant (associative across
|
|
42
|
+
folds): applying two consecutive write batches separately must produce the
|
|
43
|
+
same state as applying their concatenation once:
|
|
44
|
+
|
|
45
|
+
reducer(reducer(state, xs), ys) == reducer(state, xs + ys)
|
|
46
|
+
|
|
47
|
+
This lets CocoGraph replay checkpointed writes in larger batches than they
|
|
48
|
+
were originally produced without changing reconstructed state.
|
|
49
|
+
|
|
50
|
+
Snapshot cadence is driven by two counters: per-channel update count and
|
|
51
|
+
total supersteps since last snapshot. `create_checkpoint` writes a full
|
|
52
|
+
`_DeltaSnapshot` blob when EITHER the update count reaches
|
|
53
|
+
`snapshot_frequency` OR the supersteps count reaches the system-wide
|
|
54
|
+
`DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT` bound (default 5000), bounding
|
|
55
|
+
replay depth even for channels that stop receiving writes.
|
|
56
|
+
|
|
57
|
+
Parameters:
|
|
58
|
+
reducer: `(state, list[writes]) -> new_state`. Must be deterministic
|
|
59
|
+
and batching-invariant as described above.
|
|
60
|
+
typ: The value type (e.g. `list`, `dict`). Inferred automatically
|
|
61
|
+
from the outer type when used inside `Annotated[T, DeltaChannel(...)]`.
|
|
62
|
+
snapshot_frequency: Every Nth update to this channel writes a snapshot
|
|
63
|
+
blob (default `1000`). Must be a positive int.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
__slots__ = ("value", "reducer", "snapshot_frequency")
|
|
67
|
+
value: Value | Any
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
reducer: Callable[[Any, Sequence[Any]], Any],
|
|
72
|
+
typ: type[Value] | None = None,
|
|
73
|
+
*,
|
|
74
|
+
snapshot_frequency: int = 1000,
|
|
75
|
+
) -> None:
|
|
76
|
+
if snapshot_frequency <= 0:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"snapshot_frequency must be a positive int, got {snapshot_frequency}"
|
|
79
|
+
)
|
|
80
|
+
if typ is None:
|
|
81
|
+
typ = list # type: ignore[assignment] # placeholder; overridden by _is_field_channel
|
|
82
|
+
super().__init__(typ)
|
|
83
|
+
self.reducer = reducer
|
|
84
|
+
self.snapshot_frequency = snapshot_frequency
|
|
85
|
+
typ = _strip_extras(typ)
|
|
86
|
+
if typ in (collections.abc.Sequence, collections.abc.MutableSequence):
|
|
87
|
+
typ = list
|
|
88
|
+
if typ in (collections.abc.Set, collections.abc.MutableSet):
|
|
89
|
+
typ = set
|
|
90
|
+
if typ in (collections.abc.Mapping, collections.abc.MutableMapping):
|
|
91
|
+
typ = dict
|
|
92
|
+
self.typ = typ
|
|
93
|
+
self.value: Any = MISSING
|
|
94
|
+
|
|
95
|
+
def __eq__(self, other: object) -> bool:
|
|
96
|
+
if not isinstance(other, DeltaChannel):
|
|
97
|
+
return False
|
|
98
|
+
if self.snapshot_frequency != other.snapshot_frequency:
|
|
99
|
+
return False
|
|
100
|
+
return _operators_equal(self.reducer, other.reducer)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def ValueType(self) -> Any:
|
|
104
|
+
return self.typ
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def UpdateType(self) -> Any:
|
|
108
|
+
return self.typ
|
|
109
|
+
|
|
110
|
+
def copy(self) -> Self:
|
|
111
|
+
new = self.__class__(
|
|
112
|
+
self.reducer, self.typ, snapshot_frequency=self.snapshot_frequency
|
|
113
|
+
)
|
|
114
|
+
new.key = self.key
|
|
115
|
+
new.value = self.value if self.value is MISSING else _copy.copy(self.value)
|
|
116
|
+
return new
|
|
117
|
+
|
|
118
|
+
def from_checkpoint(self, checkpoint: Any) -> Self:
|
|
119
|
+
"""Initialize from a stored blob.
|
|
120
|
+
|
|
121
|
+
Blob types:
|
|
122
|
+
* `MISSING`: start empty; caller replays writes.
|
|
123
|
+
* `_DeltaSnapshot(value)`: restore value directly from snapshot.
|
|
124
|
+
* plain value (migration from old `BinaryOperatorAggregate` blobs):
|
|
125
|
+
use directly.
|
|
126
|
+
"""
|
|
127
|
+
new = self.__class__(
|
|
128
|
+
self.reducer, self.typ, snapshot_frequency=self.snapshot_frequency
|
|
129
|
+
)
|
|
130
|
+
new.key = self.key
|
|
131
|
+
if checkpoint is MISSING:
|
|
132
|
+
new.value = self.typ()
|
|
133
|
+
elif isinstance(checkpoint, _DeltaSnapshot):
|
|
134
|
+
new.value = checkpoint.value
|
|
135
|
+
else:
|
|
136
|
+
new.value = checkpoint
|
|
137
|
+
return new
|
|
138
|
+
|
|
139
|
+
def replay_writes(self, writes: Sequence[PendingWrite]) -> None:
|
|
140
|
+
"""Apply ancestor writes oldest-to-newest via a single reducer call.
|
|
141
|
+
|
|
142
|
+
If any write is an Overwrite, the last one in the sequence acts as
|
|
143
|
+
the reset point: its value becomes the new base and only writes
|
|
144
|
+
after it are passed to the reducer.
|
|
145
|
+
"""
|
|
146
|
+
values = [v for _, _, v in writes]
|
|
147
|
+
if not values:
|
|
148
|
+
return
|
|
149
|
+
base = self.value
|
|
150
|
+
start = 0
|
|
151
|
+
for i, v in enumerate(values):
|
|
152
|
+
is_ow, ow_value = _get_overwrite(v)
|
|
153
|
+
if is_ow:
|
|
154
|
+
base = _copy.copy(ow_value) if ow_value is not None else self.typ()
|
|
155
|
+
start = i + 1
|
|
156
|
+
remaining = values[start:]
|
|
157
|
+
self.value = self.reducer(base, remaining) if remaining else base
|
|
158
|
+
|
|
159
|
+
def update(self, values: Sequence[Any]) -> bool:
|
|
160
|
+
if not values:
|
|
161
|
+
return False
|
|
162
|
+
overwrite_idx: int | None = None
|
|
163
|
+
for i, v in enumerate(values):
|
|
164
|
+
is_ow, _ = _get_overwrite(v)
|
|
165
|
+
if is_ow:
|
|
166
|
+
if overwrite_idx is not None:
|
|
167
|
+
msg = create_error_message(
|
|
168
|
+
message="Can receive only one Overwrite value per super-step.",
|
|
169
|
+
error_code=ErrorCode.INVALID_CONCURRENT_GRAPH_UPDATE,
|
|
170
|
+
)
|
|
171
|
+
raise InvalidUpdateError(msg)
|
|
172
|
+
overwrite_idx = i
|
|
173
|
+
if overwrite_idx is not None:
|
|
174
|
+
_, overwrite_value = _get_overwrite(values[overwrite_idx])
|
|
175
|
+
self.value = (
|
|
176
|
+
_copy.copy(overwrite_value)
|
|
177
|
+
if overwrite_value is not None
|
|
178
|
+
else self.typ()
|
|
179
|
+
)
|
|
180
|
+
return True
|
|
181
|
+
base = self.typ() if self.value is MISSING else self.value
|
|
182
|
+
self.value = self.reducer(base, list(values))
|
|
183
|
+
return True
|
|
184
|
+
|
|
185
|
+
def get(self) -> Any:
|
|
186
|
+
if self.value is MISSING:
|
|
187
|
+
raise EmptyChannelError()
|
|
188
|
+
return self.value
|
|
189
|
+
|
|
190
|
+
def is_available(self) -> bool:
|
|
191
|
+
return self.value is not MISSING
|
|
192
|
+
|
|
193
|
+
def checkpoint(self) -> Any:
|
|
194
|
+
"""Return stored representation: always `MISSING`.
|
|
195
|
+
|
|
196
|
+
Snapshot decisions live in `create_checkpoint` (which has the channel
|
|
197
|
+
version) and write `_DeltaSnapshot(ch.get())` directly into
|
|
198
|
+
`channel_values`. For non-snapshot steps the channel does not appear
|
|
199
|
+
in `channel_values`; reconstruction walks ancestor writes via the
|
|
200
|
+
saver's `get_delta_channel_history`.
|
|
201
|
+
"""
|
|
202
|
+
return MISSING
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import Any, Generic
|
|
5
|
+
|
|
6
|
+
from typing_extensions import Self
|
|
7
|
+
|
|
8
|
+
from coco_graph._internal._typing import MISSING
|
|
9
|
+
from coco_graph.channels.base import BaseChannel, Value
|
|
10
|
+
from coco_graph.errors import EmptyChannelError, InvalidUpdateError
|
|
11
|
+
|
|
12
|
+
__all__ = ("EphemeralValue",)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EphemeralValue(Generic[Value], BaseChannel[Value, Value, Value]):
|
|
16
|
+
"""Stores the value received in the step immediately preceding, clears after."""
|
|
17
|
+
|
|
18
|
+
__slots__ = ("value", "guard")
|
|
19
|
+
|
|
20
|
+
value: Value | Any
|
|
21
|
+
guard: bool
|
|
22
|
+
|
|
23
|
+
def __init__(self, typ: Any, guard: bool = True) -> None:
|
|
24
|
+
super().__init__(typ)
|
|
25
|
+
self.guard = guard
|
|
26
|
+
self.value = MISSING
|
|
27
|
+
|
|
28
|
+
def __eq__(self, value: object) -> bool:
|
|
29
|
+
return isinstance(value, EphemeralValue) and value.guard == self.guard
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def ValueType(self) -> type[Value]:
|
|
33
|
+
"""The type of the value stored in the channel."""
|
|
34
|
+
return self.typ
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def UpdateType(self) -> type[Value]:
|
|
38
|
+
"""The type of the update received by the channel."""
|
|
39
|
+
return self.typ
|
|
40
|
+
|
|
41
|
+
def copy(self) -> Self:
|
|
42
|
+
"""Return a copy of the channel."""
|
|
43
|
+
empty = self.__class__(self.typ, self.guard)
|
|
44
|
+
empty.key = self.key
|
|
45
|
+
empty.value = self.value
|
|
46
|
+
return empty
|
|
47
|
+
|
|
48
|
+
def from_checkpoint(self, checkpoint: Value) -> Self:
|
|
49
|
+
empty = self.__class__(self.typ, self.guard)
|
|
50
|
+
empty.key = self.key
|
|
51
|
+
if checkpoint is not MISSING:
|
|
52
|
+
empty.value = checkpoint
|
|
53
|
+
return empty
|
|
54
|
+
|
|
55
|
+
def update(self, values: Sequence[Value]) -> bool:
|
|
56
|
+
if len(values) == 0:
|
|
57
|
+
if self.value is not MISSING:
|
|
58
|
+
self.value = MISSING
|
|
59
|
+
return True
|
|
60
|
+
else:
|
|
61
|
+
return False
|
|
62
|
+
if len(values) != 1 and self.guard:
|
|
63
|
+
raise InvalidUpdateError(
|
|
64
|
+
f"At key '{self.key}': EphemeralValue(guard=True) can receive only one value per step. Use guard=False if you want to store any one of multiple values."
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
self.value = values[-1]
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
def get(self) -> Value:
|
|
71
|
+
if self.value is MISSING:
|
|
72
|
+
raise EmptyChannelError()
|
|
73
|
+
return self.value
|
|
74
|
+
|
|
75
|
+
def is_available(self) -> bool:
|
|
76
|
+
return self.value is not MISSING
|
|
77
|
+
|
|
78
|
+
def checkpoint(self) -> Value:
|
|
79
|
+
return self.value
|