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,798 @@
|
|
|
1
|
+
"""Utility code for `Runnable` objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import asyncio
|
|
7
|
+
import inspect
|
|
8
|
+
import sys
|
|
9
|
+
import textwrap
|
|
10
|
+
|
|
11
|
+
# Cannot move to TYPE_CHECKING as Mapping and Sequence are needed at runtime by
|
|
12
|
+
# RunnableConfigurableFields.
|
|
13
|
+
from collections.abc import Mapping, Sequence # noqa: TC003
|
|
14
|
+
from functools import lru_cache
|
|
15
|
+
from inspect import signature
|
|
16
|
+
from itertools import groupby
|
|
17
|
+
from typing import (
|
|
18
|
+
TYPE_CHECKING,
|
|
19
|
+
Any,
|
|
20
|
+
NamedTuple,
|
|
21
|
+
Protocol,
|
|
22
|
+
TypeGuard,
|
|
23
|
+
TypeVar,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from typing_extensions import override
|
|
27
|
+
|
|
28
|
+
# Re-export create-model for backwards compatibility
|
|
29
|
+
from coco_core.utils.pydantic import create_model # noqa: F401
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from collections.abc import (
|
|
33
|
+
AsyncIterable,
|
|
34
|
+
AsyncIterator,
|
|
35
|
+
Awaitable,
|
|
36
|
+
Callable,
|
|
37
|
+
Coroutine,
|
|
38
|
+
Iterable,
|
|
39
|
+
)
|
|
40
|
+
from contextvars import Context
|
|
41
|
+
|
|
42
|
+
from coco_core.runnables.schema import StreamEvent
|
|
43
|
+
|
|
44
|
+
Input = TypeVar("Input", contravariant=True) # noqa: PLC0105
|
|
45
|
+
# Output type should implement __concat__, as eg str, list, dict do
|
|
46
|
+
Output = TypeVar("Output", covariant=True) # noqa: PLC0105
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def gated_coro(
|
|
50
|
+
semaphore: asyncio.Semaphore, coro: Coroutine[Any, Any, Any]
|
|
51
|
+
) -> Any:
|
|
52
|
+
"""Run a coroutine with a semaphore.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
semaphore: The semaphore to use.
|
|
56
|
+
coro: The coroutine to run.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
The result of the coroutine.
|
|
60
|
+
"""
|
|
61
|
+
async with semaphore:
|
|
62
|
+
return await coro
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def gather_with_concurrency(
|
|
66
|
+
n: int | None, *coros: Coroutine[Any, Any, Any]
|
|
67
|
+
) -> list[Any]:
|
|
68
|
+
"""Gather coroutines with a limit on the number of concurrent coroutines.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
n: The number of coroutines to run concurrently.
|
|
72
|
+
*coros: The coroutines to run.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The results of the coroutines.
|
|
76
|
+
"""
|
|
77
|
+
if n is None:
|
|
78
|
+
return await asyncio.gather(*coros)
|
|
79
|
+
|
|
80
|
+
semaphore = asyncio.Semaphore(n)
|
|
81
|
+
|
|
82
|
+
return await asyncio.gather(*(gated_coro(semaphore, c) for c in coros))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def accepts_run_manager(callable: Callable[..., Any]) -> bool: # noqa: A002
|
|
86
|
+
"""Check if a callable accepts a run_manager argument.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
callable: The callable to check.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
`True` if the callable accepts a run_manager argument, `False` otherwise.
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
return signature(callable).parameters.get("run_manager") is not None
|
|
96
|
+
except ValueError:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def accepts_config(callable: Callable[..., Any]) -> bool: # noqa: A002
|
|
101
|
+
"""Check if a callable accepts a config argument.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
callable: The callable to check.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
`True` if the callable accepts a config argument, `False` otherwise.
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
return signature(callable).parameters.get("config") is not None
|
|
111
|
+
except ValueError:
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def accepts_context(callable: Callable[..., Any]) -> bool: # noqa: A002
|
|
116
|
+
"""Check if a callable accepts a context argument.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
callable: The callable to check.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
`True` if the callable accepts a context argument, `False` otherwise.
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
return signature(callable).parameters.get("context") is not None
|
|
126
|
+
except ValueError:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def asyncio_accepts_context() -> bool:
|
|
131
|
+
"""Check if asyncio.create_task accepts a `context` arg.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
True if `asyncio.create_task` accepts a context argument, `False` otherwise.
|
|
135
|
+
"""
|
|
136
|
+
return sys.version_info >= (3, 11)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
_T = TypeVar("_T")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def coro_with_context(
|
|
143
|
+
coro: Awaitable[_T], context: Context, *, create_task: bool = False
|
|
144
|
+
) -> Awaitable[_T]:
|
|
145
|
+
"""Await a coroutine with a context.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
coro: The coroutine to await.
|
|
149
|
+
context: The context to use.
|
|
150
|
+
create_task: Kept for compatibility; this helper always creates a task.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
The coroutine with the context.
|
|
154
|
+
"""
|
|
155
|
+
if asyncio_accepts_context():
|
|
156
|
+
return asyncio.create_task(coro, context=context) # type: ignore[arg-type,call-arg,unused-ignore]
|
|
157
|
+
del create_task
|
|
158
|
+
return context.run(asyncio.create_task, coro) # type: ignore[arg-type]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class IsLocalDict(ast.NodeVisitor):
|
|
162
|
+
"""Check if a name is a local dict."""
|
|
163
|
+
|
|
164
|
+
def __init__(self, name: str, keys: set[str]) -> None:
|
|
165
|
+
"""Initialize the visitor.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
name: The name to check.
|
|
169
|
+
keys: The keys to populate.
|
|
170
|
+
"""
|
|
171
|
+
self.name = name
|
|
172
|
+
self.keys = keys
|
|
173
|
+
|
|
174
|
+
@override
|
|
175
|
+
def visit_Subscript(self, node: ast.Subscript) -> None:
|
|
176
|
+
"""Visit a subscript node.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
node: The node to visit.
|
|
180
|
+
"""
|
|
181
|
+
if (
|
|
182
|
+
isinstance(node.ctx, ast.Load)
|
|
183
|
+
and isinstance(node.value, ast.Name)
|
|
184
|
+
and node.value.id == self.name
|
|
185
|
+
and isinstance(node.slice, ast.Constant)
|
|
186
|
+
and isinstance(node.slice.value, str)
|
|
187
|
+
):
|
|
188
|
+
# we've found a subscript access on the name we're looking for
|
|
189
|
+
self.keys.add(node.slice.value)
|
|
190
|
+
|
|
191
|
+
@override
|
|
192
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
193
|
+
"""Visit a call node.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
node: The node to visit.
|
|
197
|
+
"""
|
|
198
|
+
if (
|
|
199
|
+
isinstance(node.func, ast.Attribute)
|
|
200
|
+
and isinstance(node.func.value, ast.Name)
|
|
201
|
+
and node.func.value.id == self.name
|
|
202
|
+
and node.func.attr == "get"
|
|
203
|
+
and len(node.args) in {1, 2}
|
|
204
|
+
and isinstance(node.args[0], ast.Constant)
|
|
205
|
+
and isinstance(node.args[0].value, str)
|
|
206
|
+
):
|
|
207
|
+
# we've found a .get() call on the name we're looking for
|
|
208
|
+
self.keys.add(node.args[0].value)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class IsFunctionArgDict(ast.NodeVisitor):
|
|
212
|
+
"""Check if the first argument of a function is a dict."""
|
|
213
|
+
|
|
214
|
+
def __init__(self) -> None:
|
|
215
|
+
"""Create a IsFunctionArgDict visitor."""
|
|
216
|
+
self.keys: set[str] = set()
|
|
217
|
+
|
|
218
|
+
@override
|
|
219
|
+
def visit_Lambda(self, node: ast.Lambda) -> None:
|
|
220
|
+
"""Visit a lambda function.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
node: The node to visit.
|
|
224
|
+
"""
|
|
225
|
+
if not node.args.args:
|
|
226
|
+
return
|
|
227
|
+
input_arg_name = node.args.args[0].arg
|
|
228
|
+
IsLocalDict(input_arg_name, self.keys).visit(node.body)
|
|
229
|
+
|
|
230
|
+
@override
|
|
231
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
232
|
+
"""Visit a function definition.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
node: The node to visit.
|
|
236
|
+
"""
|
|
237
|
+
if not node.args.args:
|
|
238
|
+
return
|
|
239
|
+
input_arg_name = node.args.args[0].arg
|
|
240
|
+
IsLocalDict(input_arg_name, self.keys).visit(node)
|
|
241
|
+
|
|
242
|
+
@override
|
|
243
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
|
244
|
+
"""Visit an async function definition.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
node: The node to visit.
|
|
248
|
+
"""
|
|
249
|
+
if not node.args.args:
|
|
250
|
+
return
|
|
251
|
+
input_arg_name = node.args.args[0].arg
|
|
252
|
+
IsLocalDict(input_arg_name, self.keys).visit(node)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class NonLocals(ast.NodeVisitor):
|
|
256
|
+
"""Get nonlocal variables accessed."""
|
|
257
|
+
|
|
258
|
+
def __init__(self) -> None:
|
|
259
|
+
"""Create a NonLocals visitor."""
|
|
260
|
+
self.loads: set[str] = set()
|
|
261
|
+
self.stores: set[str] = set()
|
|
262
|
+
|
|
263
|
+
@override
|
|
264
|
+
def visit_Name(self, node: ast.Name) -> None:
|
|
265
|
+
"""Visit a name node.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
node: The node to visit.
|
|
269
|
+
"""
|
|
270
|
+
if isinstance(node.ctx, ast.Load):
|
|
271
|
+
self.loads.add(node.id)
|
|
272
|
+
elif isinstance(node.ctx, ast.Store):
|
|
273
|
+
self.stores.add(node.id)
|
|
274
|
+
|
|
275
|
+
@override
|
|
276
|
+
def visit_Attribute(self, node: ast.Attribute) -> None:
|
|
277
|
+
"""Visit an attribute node.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
node: The node to visit.
|
|
281
|
+
"""
|
|
282
|
+
if isinstance(node.ctx, ast.Load):
|
|
283
|
+
parent = node.value
|
|
284
|
+
attr_expr = node.attr
|
|
285
|
+
while isinstance(parent, ast.Attribute):
|
|
286
|
+
attr_expr = parent.attr + "." + attr_expr
|
|
287
|
+
parent = parent.value
|
|
288
|
+
if isinstance(parent, ast.Name):
|
|
289
|
+
self.loads.add(parent.id + "." + attr_expr)
|
|
290
|
+
self.loads.discard(parent.id)
|
|
291
|
+
elif isinstance(parent, ast.Call):
|
|
292
|
+
if isinstance(parent.func, ast.Name):
|
|
293
|
+
self.loads.add(parent.func.id)
|
|
294
|
+
else:
|
|
295
|
+
parent = parent.func
|
|
296
|
+
attr_expr = ""
|
|
297
|
+
while isinstance(parent, ast.Attribute):
|
|
298
|
+
if attr_expr:
|
|
299
|
+
attr_expr = parent.attr + "." + attr_expr
|
|
300
|
+
else:
|
|
301
|
+
attr_expr = parent.attr
|
|
302
|
+
parent = parent.value
|
|
303
|
+
if isinstance(parent, ast.Name):
|
|
304
|
+
self.loads.add(parent.id + "." + attr_expr)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class FunctionNonLocals(ast.NodeVisitor):
|
|
308
|
+
"""Get the nonlocal variables accessed of a function."""
|
|
309
|
+
|
|
310
|
+
def __init__(self) -> None:
|
|
311
|
+
"""Create a FunctionNonLocals visitor."""
|
|
312
|
+
self.nonlocals: set[str] = set()
|
|
313
|
+
|
|
314
|
+
@override
|
|
315
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
316
|
+
"""Visit a function definition.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
node: The node to visit.
|
|
320
|
+
"""
|
|
321
|
+
visitor = NonLocals()
|
|
322
|
+
visitor.visit(node)
|
|
323
|
+
self.nonlocals.update(visitor.loads - visitor.stores)
|
|
324
|
+
|
|
325
|
+
@override
|
|
326
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
|
327
|
+
"""Visit an async function definition.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
node: The node to visit.
|
|
331
|
+
"""
|
|
332
|
+
visitor = NonLocals()
|
|
333
|
+
visitor.visit(node)
|
|
334
|
+
self.nonlocals.update(visitor.loads - visitor.stores)
|
|
335
|
+
|
|
336
|
+
@override
|
|
337
|
+
def visit_Lambda(self, node: ast.Lambda) -> None:
|
|
338
|
+
"""Visit a lambda function.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
node: The node to visit.
|
|
342
|
+
"""
|
|
343
|
+
visitor = NonLocals()
|
|
344
|
+
visitor.visit(node)
|
|
345
|
+
self.nonlocals.update(visitor.loads - visitor.stores)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class GetLambdaSource(ast.NodeVisitor):
|
|
349
|
+
"""Get the source code of a lambda function."""
|
|
350
|
+
|
|
351
|
+
def __init__(self) -> None:
|
|
352
|
+
"""Initialize the visitor."""
|
|
353
|
+
self.source: str | None = None
|
|
354
|
+
self.count = 0
|
|
355
|
+
|
|
356
|
+
@override
|
|
357
|
+
def visit_Lambda(self, node: ast.Lambda) -> None:
|
|
358
|
+
"""Visit a lambda function.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
node: The node to visit.
|
|
362
|
+
"""
|
|
363
|
+
self.count += 1
|
|
364
|
+
if hasattr(ast, "unparse"):
|
|
365
|
+
self.source = ast.unparse(node)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def get_function_first_arg_dict_keys(func: Callable[..., Any]) -> list[str] | None:
|
|
369
|
+
"""Get the keys of the first argument of a function if it is a dict.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
func: The function to check.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
The keys of the first argument if it is a dict, None otherwise.
|
|
376
|
+
"""
|
|
377
|
+
try:
|
|
378
|
+
code = inspect.getsource(func)
|
|
379
|
+
tree = ast.parse(textwrap.dedent(code))
|
|
380
|
+
visitor = IsFunctionArgDict()
|
|
381
|
+
visitor.visit(tree)
|
|
382
|
+
return sorted(visitor.keys) if visitor.keys else None
|
|
383
|
+
except (SyntaxError, TypeError, OSError, SystemError):
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def get_lambda_source(func: Callable[..., Any]) -> str | None:
|
|
388
|
+
"""Get the source code of a lambda function.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
func: a Callable that can be a lambda function.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
the source code of the lambda function.
|
|
395
|
+
"""
|
|
396
|
+
try:
|
|
397
|
+
name = func.__name__ if func.__name__ != "<lambda>" else None
|
|
398
|
+
except AttributeError:
|
|
399
|
+
name = None
|
|
400
|
+
try:
|
|
401
|
+
code = inspect.getsource(func)
|
|
402
|
+
tree = ast.parse(textwrap.dedent(code))
|
|
403
|
+
visitor = GetLambdaSource()
|
|
404
|
+
visitor.visit(tree)
|
|
405
|
+
except (SyntaxError, TypeError, OSError, SystemError):
|
|
406
|
+
return name
|
|
407
|
+
return visitor.source if visitor.count == 1 else name
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
@lru_cache(maxsize=256)
|
|
411
|
+
def get_function_nonlocals(func: Callable[..., Any]) -> list[Any]:
|
|
412
|
+
"""Get the nonlocal variables accessed by a function.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
func: The function to check.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
The nonlocal variables accessed by the function.
|
|
419
|
+
"""
|
|
420
|
+
try:
|
|
421
|
+
code = inspect.getsource(func)
|
|
422
|
+
tree = ast.parse(textwrap.dedent(code))
|
|
423
|
+
visitor = FunctionNonLocals()
|
|
424
|
+
visitor.visit(tree)
|
|
425
|
+
values: list[Any] = []
|
|
426
|
+
closure = (
|
|
427
|
+
inspect.getclosurevars(func.__wrapped__)
|
|
428
|
+
if hasattr(func, "__wrapped__") and callable(func.__wrapped__)
|
|
429
|
+
else inspect.getclosurevars(func)
|
|
430
|
+
)
|
|
431
|
+
candidates = {**closure.globals, **closure.nonlocals}
|
|
432
|
+
for k, v in candidates.items():
|
|
433
|
+
if k in visitor.nonlocals:
|
|
434
|
+
values.append(v)
|
|
435
|
+
for kk in visitor.nonlocals:
|
|
436
|
+
if "." in kk and kk.startswith(k):
|
|
437
|
+
vv = v
|
|
438
|
+
for part in kk.split(".")[1:]:
|
|
439
|
+
if vv is None:
|
|
440
|
+
break
|
|
441
|
+
try:
|
|
442
|
+
vv = getattr(vv, part)
|
|
443
|
+
except AttributeError:
|
|
444
|
+
break
|
|
445
|
+
else:
|
|
446
|
+
values.append(vv)
|
|
447
|
+
except (SyntaxError, TypeError, OSError, SystemError):
|
|
448
|
+
return []
|
|
449
|
+
|
|
450
|
+
return values
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def indent_lines_after_first(text: str, prefix: str) -> str:
|
|
454
|
+
"""Indent all lines of text after the first line.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
text: The text to indent.
|
|
458
|
+
prefix: Used to determine the number of spaces to indent.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
The indented text.
|
|
462
|
+
"""
|
|
463
|
+
n_spaces = len(prefix)
|
|
464
|
+
spaces = " " * n_spaces
|
|
465
|
+
lines = text.splitlines()
|
|
466
|
+
return "\n".join([lines[0]] + [spaces + line for line in lines[1:]])
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class AddableDict(dict[str, Any]):
|
|
470
|
+
"""Dictionary that can be added to another dictionary."""
|
|
471
|
+
|
|
472
|
+
def __add__(self, other: AddableDict) -> AddableDict:
|
|
473
|
+
"""Add a dictionary to this dictionary.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
other: The other dictionary to add.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
A dictionary that is the result of adding the two dictionaries.
|
|
480
|
+
|
|
481
|
+
Raises:
|
|
482
|
+
TypeError: If a shared key holds values of incompatible types.
|
|
483
|
+
"""
|
|
484
|
+
chunk = AddableDict(self)
|
|
485
|
+
for key in other:
|
|
486
|
+
if key not in chunk or chunk[key] is None:
|
|
487
|
+
chunk[key] = other[key]
|
|
488
|
+
elif other[key] is not None:
|
|
489
|
+
try:
|
|
490
|
+
added = chunk[key] + other[key]
|
|
491
|
+
except TypeError as exc:
|
|
492
|
+
msg = (
|
|
493
|
+
f"Cannot add incompatible types for key {key!r}: "
|
|
494
|
+
f"{type(chunk[key]).__name__!r} and "
|
|
495
|
+
f"{type(other[key]).__name__!r}."
|
|
496
|
+
)
|
|
497
|
+
raise TypeError(msg) from exc
|
|
498
|
+
chunk[key] = added
|
|
499
|
+
return chunk
|
|
500
|
+
|
|
501
|
+
def __radd__(self, other: AddableDict) -> AddableDict:
|
|
502
|
+
"""Add this dictionary to another dictionary.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
other: The other dictionary to be added to.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
A dictionary that is the result of adding the two dictionaries.
|
|
509
|
+
|
|
510
|
+
Raises:
|
|
511
|
+
TypeError: If a shared key holds values of incompatible types.
|
|
512
|
+
"""
|
|
513
|
+
chunk = AddableDict(other)
|
|
514
|
+
for key in self:
|
|
515
|
+
if key not in chunk or chunk[key] is None:
|
|
516
|
+
chunk[key] = self[key]
|
|
517
|
+
elif self[key] is not None:
|
|
518
|
+
try:
|
|
519
|
+
added = chunk[key] + self[key]
|
|
520
|
+
except TypeError as exc:
|
|
521
|
+
msg = (
|
|
522
|
+
f"Cannot add incompatible types for key {key!r}: "
|
|
523
|
+
f"{type(chunk[key]).__name__!r} and "
|
|
524
|
+
f"{type(self[key]).__name__!r}."
|
|
525
|
+
)
|
|
526
|
+
raise TypeError(msg) from exc
|
|
527
|
+
chunk[key] = added
|
|
528
|
+
return chunk
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
_T_co = TypeVar("_T_co", covariant=True)
|
|
532
|
+
_T_contra = TypeVar("_T_contra", contravariant=True)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
class SupportsAdd(Protocol[_T_contra, _T_co]):
|
|
536
|
+
"""Protocol for objects that support addition."""
|
|
537
|
+
|
|
538
|
+
def __add__(self, x: _T_contra, /) -> _T_co:
|
|
539
|
+
"""Add the object to another object."""
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
Addable = TypeVar("Addable", bound=SupportsAdd[Any, Any])
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def add(addables: Iterable[Addable]) -> Addable | None:
|
|
546
|
+
"""Add a sequence of addable objects together.
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
addables: The addable objects to add.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
The result of adding the addable objects.
|
|
553
|
+
"""
|
|
554
|
+
final: Addable | None = None
|
|
555
|
+
for chunk in addables:
|
|
556
|
+
final = chunk if final is None else final + chunk
|
|
557
|
+
return final
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
async def aadd(addables: AsyncIterable[Addable]) -> Addable | None:
|
|
561
|
+
"""Asynchronously add a sequence of addable objects together.
|
|
562
|
+
|
|
563
|
+
Args:
|
|
564
|
+
addables: The addable objects to add.
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
The result of adding the addable objects.
|
|
568
|
+
"""
|
|
569
|
+
final: Addable | None = None
|
|
570
|
+
async for chunk in addables:
|
|
571
|
+
final = chunk if final is None else final + chunk
|
|
572
|
+
return final
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
class ConfigurableField(NamedTuple):
|
|
576
|
+
"""Field that can be configured by the user."""
|
|
577
|
+
|
|
578
|
+
id: str
|
|
579
|
+
"""The unique identifier of the field."""
|
|
580
|
+
|
|
581
|
+
name: str | None = None
|
|
582
|
+
"""The name of the field. """
|
|
583
|
+
|
|
584
|
+
description: str | None = None
|
|
585
|
+
"""The description of the field. """
|
|
586
|
+
|
|
587
|
+
annotation: Any | None = None
|
|
588
|
+
"""The annotation of the field. """
|
|
589
|
+
|
|
590
|
+
is_shared: bool = False
|
|
591
|
+
"""Whether the field is shared."""
|
|
592
|
+
|
|
593
|
+
@override
|
|
594
|
+
def __hash__(self) -> int:
|
|
595
|
+
return hash((self.id, self.annotation))
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
class ConfigurableFieldSingleOption(NamedTuple):
|
|
599
|
+
"""Field that can be configured by the user with a default value."""
|
|
600
|
+
|
|
601
|
+
id: str
|
|
602
|
+
"""The unique identifier of the field."""
|
|
603
|
+
|
|
604
|
+
options: Mapping[str, Any]
|
|
605
|
+
"""The options for the field."""
|
|
606
|
+
|
|
607
|
+
default: str
|
|
608
|
+
"""The default value for the field."""
|
|
609
|
+
|
|
610
|
+
name: str | None = None
|
|
611
|
+
"""The name of the field. """
|
|
612
|
+
|
|
613
|
+
description: str | None = None
|
|
614
|
+
"""The description of the field. """
|
|
615
|
+
|
|
616
|
+
is_shared: bool = False
|
|
617
|
+
"""Whether the field is shared."""
|
|
618
|
+
|
|
619
|
+
@override
|
|
620
|
+
def __hash__(self) -> int:
|
|
621
|
+
return hash((self.id, tuple(self.options.keys()), self.default))
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
class ConfigurableFieldMultiOption(NamedTuple):
|
|
625
|
+
"""Field that can be configured by the user with multiple default values."""
|
|
626
|
+
|
|
627
|
+
id: str
|
|
628
|
+
"""The unique identifier of the field."""
|
|
629
|
+
|
|
630
|
+
options: Mapping[str, Any]
|
|
631
|
+
"""The options for the field."""
|
|
632
|
+
|
|
633
|
+
default: Sequence[str]
|
|
634
|
+
"""The default values for the field."""
|
|
635
|
+
|
|
636
|
+
name: str | None = None
|
|
637
|
+
"""The name of the field. """
|
|
638
|
+
|
|
639
|
+
description: str | None = None
|
|
640
|
+
"""The description of the field. """
|
|
641
|
+
|
|
642
|
+
is_shared: bool = False
|
|
643
|
+
"""Whether the field is shared."""
|
|
644
|
+
|
|
645
|
+
@override
|
|
646
|
+
def __hash__(self) -> int:
|
|
647
|
+
return hash((self.id, tuple(self.options.keys()), tuple(self.default)))
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
AnyConfigurableField = (
|
|
651
|
+
ConfigurableField | ConfigurableFieldSingleOption | ConfigurableFieldMultiOption
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
class ConfigurableFieldSpec(NamedTuple):
|
|
656
|
+
"""Field that can be configured by the user. It is a specification of a field."""
|
|
657
|
+
|
|
658
|
+
id: str
|
|
659
|
+
"""The unique identifier of the field."""
|
|
660
|
+
|
|
661
|
+
annotation: Any
|
|
662
|
+
"""The annotation of the field."""
|
|
663
|
+
|
|
664
|
+
name: str | None = None
|
|
665
|
+
"""The name of the field. """
|
|
666
|
+
|
|
667
|
+
description: str | None = None
|
|
668
|
+
"""The description of the field. """
|
|
669
|
+
|
|
670
|
+
default: Any = None
|
|
671
|
+
"""The default value for the field. """
|
|
672
|
+
|
|
673
|
+
is_shared: bool = False
|
|
674
|
+
"""Whether the field is shared."""
|
|
675
|
+
|
|
676
|
+
dependencies: list[str] | None = None
|
|
677
|
+
"""The dependencies of the field. """
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def get_unique_config_specs(
|
|
681
|
+
specs: Iterable[ConfigurableFieldSpec],
|
|
682
|
+
) -> list[ConfigurableFieldSpec]:
|
|
683
|
+
"""Get the unique config specs from a sequence of config specs.
|
|
684
|
+
|
|
685
|
+
Args:
|
|
686
|
+
specs: The config specs.
|
|
687
|
+
|
|
688
|
+
Returns:
|
|
689
|
+
The unique config specs.
|
|
690
|
+
|
|
691
|
+
Raises:
|
|
692
|
+
ValueError: If the runnable sequence contains conflicting config specs.
|
|
693
|
+
"""
|
|
694
|
+
grouped = groupby(
|
|
695
|
+
sorted(specs, key=lambda s: (s.id, *(s.dependencies or []))), lambda s: s.id
|
|
696
|
+
)
|
|
697
|
+
unique: list[ConfigurableFieldSpec] = []
|
|
698
|
+
for spec_id, dupes in grouped:
|
|
699
|
+
first = next(dupes)
|
|
700
|
+
others = list(dupes)
|
|
701
|
+
if len(others) == 0 or all(o == first for o in others):
|
|
702
|
+
unique.append(first)
|
|
703
|
+
else:
|
|
704
|
+
msg = (
|
|
705
|
+
"RunnableSequence contains conflicting config specs"
|
|
706
|
+
f"for {spec_id}: {[first, *others]}"
|
|
707
|
+
)
|
|
708
|
+
raise ValueError(msg)
|
|
709
|
+
return unique
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
class _RootEventFilter:
|
|
713
|
+
def __init__(
|
|
714
|
+
self,
|
|
715
|
+
*,
|
|
716
|
+
include_names: Sequence[str] | None = None,
|
|
717
|
+
include_types: Sequence[str] | None = None,
|
|
718
|
+
include_tags: Sequence[str] | None = None,
|
|
719
|
+
exclude_names: Sequence[str] | None = None,
|
|
720
|
+
exclude_types: Sequence[str] | None = None,
|
|
721
|
+
exclude_tags: Sequence[str] | None = None,
|
|
722
|
+
) -> None:
|
|
723
|
+
"""Utility to filter the root event in the astream_events implementation.
|
|
724
|
+
|
|
725
|
+
This is simply binding the arguments to the namespace to make save on
|
|
726
|
+
a bit of typing in the astream_events implementation.
|
|
727
|
+
"""
|
|
728
|
+
self.include_names = include_names
|
|
729
|
+
self.include_types = include_types
|
|
730
|
+
self.include_tags = include_tags
|
|
731
|
+
self.exclude_names = exclude_names
|
|
732
|
+
self.exclude_types = exclude_types
|
|
733
|
+
self.exclude_tags = exclude_tags
|
|
734
|
+
|
|
735
|
+
def include_event(self, event: StreamEvent, root_type: str) -> bool:
|
|
736
|
+
"""Determine whether to include an event."""
|
|
737
|
+
if (
|
|
738
|
+
self.include_names is None
|
|
739
|
+
and self.include_types is None
|
|
740
|
+
and self.include_tags is None
|
|
741
|
+
):
|
|
742
|
+
include = True
|
|
743
|
+
else:
|
|
744
|
+
include = False
|
|
745
|
+
|
|
746
|
+
event_tags = event.get("tags") or []
|
|
747
|
+
|
|
748
|
+
if self.include_names is not None:
|
|
749
|
+
include = include or event["name"] in self.include_names
|
|
750
|
+
if self.include_types is not None:
|
|
751
|
+
include = include or root_type in self.include_types
|
|
752
|
+
if self.include_tags is not None:
|
|
753
|
+
include = include or any(tag in self.include_tags for tag in event_tags)
|
|
754
|
+
|
|
755
|
+
if self.exclude_names is not None:
|
|
756
|
+
include = include and event["name"] not in self.exclude_names
|
|
757
|
+
if self.exclude_types is not None:
|
|
758
|
+
include = include and root_type not in self.exclude_types
|
|
759
|
+
if self.exclude_tags is not None:
|
|
760
|
+
include = include and all(
|
|
761
|
+
tag not in self.exclude_tags for tag in event_tags
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
return include
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def is_async_generator(
|
|
768
|
+
func: Any,
|
|
769
|
+
) -> TypeGuard[Callable[..., AsyncIterator[Any]]]:
|
|
770
|
+
"""Check if a function is an async generator.
|
|
771
|
+
|
|
772
|
+
Args:
|
|
773
|
+
func: The function to check.
|
|
774
|
+
|
|
775
|
+
Returns:
|
|
776
|
+
`True` if the function is an async generator, `False` otherwise.
|
|
777
|
+
"""
|
|
778
|
+
return inspect.isasyncgenfunction(func) or (
|
|
779
|
+
hasattr(func, "__call__") # noqa: B004
|
|
780
|
+
and inspect.isasyncgenfunction(func.__call__)
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def is_async_callable(
|
|
785
|
+
func: Any,
|
|
786
|
+
) -> TypeGuard[Callable[..., Awaitable[Any]]]:
|
|
787
|
+
"""Check if a function is async.
|
|
788
|
+
|
|
789
|
+
Args:
|
|
790
|
+
func: The function to check.
|
|
791
|
+
|
|
792
|
+
Returns:
|
|
793
|
+
`True` if the function is async, `False` otherwise.
|
|
794
|
+
"""
|
|
795
|
+
return inspect.iscoroutinefunction(func) or (
|
|
796
|
+
hasattr(func, "__call__") # noqa: B004
|
|
797
|
+
and inspect.iscoroutinefunction(func.__call__)
|
|
798
|
+
)
|