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,341 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections import deque
|
|
5
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
|
|
6
|
+
from typing import TYPE_CHECKING, Generic, TypeVar
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from coco_graph.stream._mux import StreamMux
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class StreamChannel(Generic[T]):
|
|
15
|
+
"""Single-consumer drainable queue for streaming events, with optional
|
|
16
|
+
protocol auto-forwarding.
|
|
17
|
+
|
|
18
|
+
When constructed with a `name`, the StreamMux auto-wires every
|
|
19
|
+
`push()` to also inject a `ProtocolEvent` into the main event stream
|
|
20
|
+
using the channel's name as the method. When constructed without a
|
|
21
|
+
name, the channel is local-only — items are only visible to
|
|
22
|
+
in-process consumers that iterate the channel directly.
|
|
23
|
+
|
|
24
|
+
Items are popped off the front as the consumer advances — there is
|
|
25
|
+
no retention beyond what's currently queued. A channel accepts
|
|
26
|
+
exactly one subscriber; a second `__iter__` / `__aiter__` call
|
|
27
|
+
raises. Use `tee(n)` / `atee(n)` for fan-out.
|
|
28
|
+
|
|
29
|
+
Starts unbound — neither `__iter__` nor `__aiter__` is available
|
|
30
|
+
until the StreamMux calls `_bind(is_async)`. After binding, only
|
|
31
|
+
the matching iteration protocol works; the other raises `TypeError`.
|
|
32
|
+
|
|
33
|
+
Pump wiring (set by the run stream, not by `_bind`):
|
|
34
|
+
- `_request_more`: sync pump callable, returns True if a new
|
|
35
|
+
event was produced.
|
|
36
|
+
- `_arequest_more`: async pump coroutine factory, same contract.
|
|
37
|
+
|
|
38
|
+
Memory is bounded by caller pace: both sync and async use caller-
|
|
39
|
+
driven pumps, so each cursor advance produces at most one event.
|
|
40
|
+
|
|
41
|
+
Lazy-subscribe: `push` appends to the local buffer only when a
|
|
42
|
+
subscriber has registered. Auto-forward via `_wire_fn` always fires
|
|
43
|
+
regardless of subscription state.
|
|
44
|
+
|
|
45
|
+
Lifecycle (`close` / `fail`) is managed by the mux — transformers
|
|
46
|
+
don't need to close their channels manually.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, name: str | None = None, *, maxlen: int | None = None) -> None:
|
|
50
|
+
"""Initialize the channel.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
name: Optional protocol channel name. When set, the
|
|
54
|
+
StreamMux wires every `push()` to also inject a
|
|
55
|
+
`ProtocolEvent` into the main event stream. Surfaced
|
|
56
|
+
on the wire as `custom:<name>` for user-defined
|
|
57
|
+
transformers, or as `<name>` for channels owned by a
|
|
58
|
+
native transformer (`_native = True`). When `None`,
|
|
59
|
+
the channel is local-only.
|
|
60
|
+
maxlen: Accepted for forward compatibility; currently
|
|
61
|
+
unused. The caller-driven pump bounds memory naturally
|
|
62
|
+
for single-consumer use.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValueError: If `maxlen` is not a positive integer or `None`.
|
|
66
|
+
"""
|
|
67
|
+
if maxlen is not None and maxlen <= 0:
|
|
68
|
+
raise ValueError("StreamChannel maxlen must be a positive int or None")
|
|
69
|
+
self.name = name
|
|
70
|
+
self._items: deque[tuple[int, T]] = deque()
|
|
71
|
+
self._maxlen: int | None = maxlen
|
|
72
|
+
self._closed = False
|
|
73
|
+
self._error: BaseException | None = None
|
|
74
|
+
|
|
75
|
+
self._is_async: bool | None = None
|
|
76
|
+
|
|
77
|
+
self._subscribed = False
|
|
78
|
+
|
|
79
|
+
self._request_more: Callable[[], bool] | None = None
|
|
80
|
+
self._arequest_more: Callable[[], Awaitable[bool]] | None = None
|
|
81
|
+
|
|
82
|
+
self._wire_fn: Callable[[T], None] | None = None
|
|
83
|
+
self._mux: StreamMux | None = None
|
|
84
|
+
|
|
85
|
+
# ------------------------------------------------------------------
|
|
86
|
+
# Binding
|
|
87
|
+
# ------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
def _bind_mux(self, mux: StreamMux) -> None:
|
|
90
|
+
self._mux = mux
|
|
91
|
+
|
|
92
|
+
def _bind(self, *, is_async: bool) -> None:
|
|
93
|
+
"""Bind this channel to sync or async mode.
|
|
94
|
+
|
|
95
|
+
Called by the StreamMux after transformer registration. Must be
|
|
96
|
+
called exactly once before any iteration.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
is_async: True to enable async iteration, False for sync.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
RuntimeError: If the channel has already been bound.
|
|
103
|
+
"""
|
|
104
|
+
if self._is_async is not None:
|
|
105
|
+
raise RuntimeError("StreamChannel is already bound")
|
|
106
|
+
self._is_async = is_async
|
|
107
|
+
|
|
108
|
+
# ------------------------------------------------------------------
|
|
109
|
+
# Mux wiring (not called by transformers directly)
|
|
110
|
+
# ------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
def _wire(self, fn: Callable[[T], None]) -> None:
|
|
113
|
+
"""Install the auto-forward callback (called by StreamMux)."""
|
|
114
|
+
self._wire_fn = fn
|
|
115
|
+
|
|
116
|
+
# ------------------------------------------------------------------
|
|
117
|
+
# Producer API
|
|
118
|
+
# ------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
def push(self, item: T) -> None:
|
|
121
|
+
"""Append an item. Auto-forwards if wired.
|
|
122
|
+
|
|
123
|
+
The local buffer append is a no-op when no subscriber is
|
|
124
|
+
registered, but auto-forwarding always fires so wired events
|
|
125
|
+
reach the main event log regardless of subscription state.
|
|
126
|
+
|
|
127
|
+
Items are stored as `(stamp, item)` tuples where stamp is a
|
|
128
|
+
monotonic counter from the owning mux. Stamps are stripped by
|
|
129
|
+
the default cursors; raw stamped tuples are visible on `_items`.
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
RuntimeError: If the channel is closed (and subscribed).
|
|
133
|
+
"""
|
|
134
|
+
if self._subscribed:
|
|
135
|
+
if self._closed:
|
|
136
|
+
raise RuntimeError("Cannot push to a closed StreamChannel")
|
|
137
|
+
stamp = self._mux._next_push_seq() if self._mux is not None else 0
|
|
138
|
+
self._items.append((stamp, item))
|
|
139
|
+
if self._wire_fn is not None:
|
|
140
|
+
self._wire_fn(item)
|
|
141
|
+
|
|
142
|
+
def close(self) -> None:
|
|
143
|
+
"""Mark the channel as complete."""
|
|
144
|
+
self._closed = True
|
|
145
|
+
|
|
146
|
+
def fail(self, err: BaseException) -> None:
|
|
147
|
+
"""Mark the channel as errored.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
err: The exception to surface to the subscriber.
|
|
151
|
+
"""
|
|
152
|
+
self._error = err
|
|
153
|
+
self._closed = True
|
|
154
|
+
|
|
155
|
+
# ------------------------------------------------------------------
|
|
156
|
+
# Sync iteration (caller-driven pump)
|
|
157
|
+
# ------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
def __iter__(self) -> Iterator[T]:
|
|
160
|
+
"""Subscribe and return a sync cursor. Can be called only once.
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
TypeError: If the channel is unbound or bound to async mode.
|
|
164
|
+
RuntimeError: If the channel already has a subscriber.
|
|
165
|
+
"""
|
|
166
|
+
if self._is_async is None:
|
|
167
|
+
raise TypeError(
|
|
168
|
+
"StreamChannel has not been bound yet. "
|
|
169
|
+
"Register the transformer with a StreamMux first."
|
|
170
|
+
)
|
|
171
|
+
if self._is_async:
|
|
172
|
+
raise TypeError(
|
|
173
|
+
"This StreamChannel is bound to async mode — use 'async for' instead."
|
|
174
|
+
)
|
|
175
|
+
if self._subscribed:
|
|
176
|
+
raise RuntimeError(
|
|
177
|
+
"StreamChannel already has a subscriber; use .tee(n) for fan-out."
|
|
178
|
+
)
|
|
179
|
+
self._subscribed = True
|
|
180
|
+
return self._sync_cursor()
|
|
181
|
+
|
|
182
|
+
def _sync_cursor(self) -> Iterator[T]:
|
|
183
|
+
while True:
|
|
184
|
+
if self._items:
|
|
185
|
+
_stamp, item = self._items.popleft()
|
|
186
|
+
yield item
|
|
187
|
+
elif self._closed:
|
|
188
|
+
if self._error is not None:
|
|
189
|
+
raise self._error
|
|
190
|
+
return
|
|
191
|
+
elif self._request_more is not None:
|
|
192
|
+
if not self._request_more():
|
|
193
|
+
if not self._items and not self._closed:
|
|
194
|
+
return
|
|
195
|
+
else:
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
# ------------------------------------------------------------------
|
|
199
|
+
# Async iteration (caller-driven pump)
|
|
200
|
+
# ------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
def __aiter__(self) -> AsyncIterator[T]:
|
|
203
|
+
"""Subscribe and return an async cursor. Can be called only once.
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
TypeError: If the channel is unbound or bound to sync mode.
|
|
207
|
+
RuntimeError: If the channel already has a subscriber.
|
|
208
|
+
"""
|
|
209
|
+
if self._is_async is None:
|
|
210
|
+
raise TypeError(
|
|
211
|
+
"StreamChannel has not been bound yet. "
|
|
212
|
+
"Register the transformer with a StreamMux first."
|
|
213
|
+
)
|
|
214
|
+
if not self._is_async:
|
|
215
|
+
raise TypeError(
|
|
216
|
+
"This StreamChannel is bound to sync mode — use 'for' instead."
|
|
217
|
+
)
|
|
218
|
+
if self._subscribed:
|
|
219
|
+
raise RuntimeError(
|
|
220
|
+
"StreamChannel already has a subscriber; use .atee(n) for fan-out."
|
|
221
|
+
)
|
|
222
|
+
self._subscribed = True
|
|
223
|
+
return self._async_cursor()
|
|
224
|
+
|
|
225
|
+
async def _async_cursor(self) -> AsyncIterator[T]:
|
|
226
|
+
while True:
|
|
227
|
+
if self._items:
|
|
228
|
+
_stamp, item = self._items.popleft()
|
|
229
|
+
yield item
|
|
230
|
+
elif self._closed:
|
|
231
|
+
if self._error is not None:
|
|
232
|
+
raise self._error
|
|
233
|
+
return
|
|
234
|
+
elif self._arequest_more is not None:
|
|
235
|
+
if not await self._arequest_more():
|
|
236
|
+
if not self._items and not self._closed:
|
|
237
|
+
return
|
|
238
|
+
else:
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
# ------------------------------------------------------------------
|
|
242
|
+
# Fan-out via tee
|
|
243
|
+
# ------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
def tee(self, n: int = 2) -> tuple[Iterator[T], ...]:
|
|
246
|
+
"""Subscribe and return `n` independent sync iterators.
|
|
247
|
+
|
|
248
|
+
Each branch has its own buffer; items pulled from the
|
|
249
|
+
underlying cursor are copied into every branch. Branches are
|
|
250
|
+
naturally bounded by caller pace since the sync pump is
|
|
251
|
+
caller-driven.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
n: Number of branches to create. Must be >= 1.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
A tuple of `n` iterators over the same underlying stream.
|
|
258
|
+
|
|
259
|
+
Raises:
|
|
260
|
+
TypeError: If the channel is unbound or bound to async mode.
|
|
261
|
+
RuntimeError: If the channel already has a subscriber.
|
|
262
|
+
ValueError: If `n` < 1.
|
|
263
|
+
"""
|
|
264
|
+
if n < 1:
|
|
265
|
+
raise ValueError("tee() requires n >= 1")
|
|
266
|
+
source = self.__iter__()
|
|
267
|
+
buffers: list[deque[T]] = [deque() for _ in range(n)]
|
|
268
|
+
exhausted = [False]
|
|
269
|
+
|
|
270
|
+
def branch(i: int) -> Iterator[T]:
|
|
271
|
+
buf = buffers[i]
|
|
272
|
+
while True:
|
|
273
|
+
if buf:
|
|
274
|
+
yield buf.popleft()
|
|
275
|
+
elif exhausted[0]:
|
|
276
|
+
return
|
|
277
|
+
else:
|
|
278
|
+
try:
|
|
279
|
+
item = next(source)
|
|
280
|
+
except StopIteration:
|
|
281
|
+
exhausted[0] = True
|
|
282
|
+
return
|
|
283
|
+
for b in buffers:
|
|
284
|
+
b.append(item)
|
|
285
|
+
|
|
286
|
+
return tuple(branch(i) for i in range(n))
|
|
287
|
+
|
|
288
|
+
def atee(self, n: int = 2) -> tuple[AsyncIterator[T], ...]:
|
|
289
|
+
"""Subscribe and return `n` independent async iterators.
|
|
290
|
+
|
|
291
|
+
Caller-driven fan-out: each branch's `__anext__` either pops
|
|
292
|
+
from its own buffer or, under a shared `asyncio.Lock`, pulls
|
|
293
|
+
one item from the underlying cursor and distributes it to
|
|
294
|
+
every branch's buffer.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
n: Number of branches to create. Must be >= 1.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
A tuple of `n` async iterators over the same underlying
|
|
301
|
+
stream.
|
|
302
|
+
|
|
303
|
+
Raises:
|
|
304
|
+
TypeError: If the channel is unbound or bound to sync mode.
|
|
305
|
+
RuntimeError: If the channel already has a subscriber.
|
|
306
|
+
ValueError: If `n` < 1.
|
|
307
|
+
"""
|
|
308
|
+
if n < 1:
|
|
309
|
+
raise ValueError("atee() requires n >= 1")
|
|
310
|
+
source = self.__aiter__()
|
|
311
|
+
buffers: list[deque[T]] = [deque() for _ in range(n)]
|
|
312
|
+
exhausted = [False]
|
|
313
|
+
error: list[BaseException | None] = [None]
|
|
314
|
+
lock = asyncio.Lock()
|
|
315
|
+
|
|
316
|
+
async def branch(i: int) -> AsyncIterator[T]:
|
|
317
|
+
buf = buffers[i]
|
|
318
|
+
while True:
|
|
319
|
+
if buf:
|
|
320
|
+
yield buf.popleft()
|
|
321
|
+
continue
|
|
322
|
+
if exhausted[0]:
|
|
323
|
+
if error[0] is not None:
|
|
324
|
+
raise error[0]
|
|
325
|
+
return
|
|
326
|
+
async with lock:
|
|
327
|
+
if buf or exhausted[0]:
|
|
328
|
+
continue
|
|
329
|
+
try:
|
|
330
|
+
item = await source.__anext__()
|
|
331
|
+
except StopAsyncIteration:
|
|
332
|
+
exhausted[0] = True
|
|
333
|
+
continue
|
|
334
|
+
except Exception as e:
|
|
335
|
+
error[0] = e
|
|
336
|
+
exhausted[0] = True
|
|
337
|
+
continue
|
|
338
|
+
for b in buffers:
|
|
339
|
+
b.append(item)
|
|
340
|
+
|
|
341
|
+
return tuple(branch(i) for i in range(n))
|