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,631 @@
|
|
|
1
|
+
"""Utilities for pydantic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import textwrap
|
|
7
|
+
import warnings
|
|
8
|
+
from contextlib import nullcontext
|
|
9
|
+
from functools import lru_cache, wraps
|
|
10
|
+
from types import GenericAlias
|
|
11
|
+
from typing import (
|
|
12
|
+
TYPE_CHECKING,
|
|
13
|
+
Any,
|
|
14
|
+
TypeGuard,
|
|
15
|
+
TypeVar,
|
|
16
|
+
cast,
|
|
17
|
+
overload,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
import pydantic
|
|
21
|
+
from packaging import version
|
|
22
|
+
|
|
23
|
+
# root_validator is deprecated but we need it for backward compatibility of @pre_init
|
|
24
|
+
from pydantic import ( # type: ignore[deprecated]
|
|
25
|
+
BaseModel,
|
|
26
|
+
ConfigDict,
|
|
27
|
+
Field,
|
|
28
|
+
PydanticDeprecationWarning,
|
|
29
|
+
RootModel,
|
|
30
|
+
root_validator,
|
|
31
|
+
)
|
|
32
|
+
from pydantic import (
|
|
33
|
+
create_model as _create_model_base,
|
|
34
|
+
)
|
|
35
|
+
from pydantic.fields import FieldInfo as FieldInfoV2
|
|
36
|
+
from pydantic.json_schema import (
|
|
37
|
+
DEFAULT_REF_TEMPLATE,
|
|
38
|
+
GenerateJsonSchema,
|
|
39
|
+
JsonSchemaMode,
|
|
40
|
+
JsonSchemaValue,
|
|
41
|
+
)
|
|
42
|
+
from pydantic.v1 import BaseModel as BaseModelV1
|
|
43
|
+
from pydantic.v1 import create_model as create_model_v1
|
|
44
|
+
from typing_extensions import deprecated, override
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from collections.abc import Callable
|
|
48
|
+
|
|
49
|
+
from pydantic.v1.fields import ModelField
|
|
50
|
+
from pydantic_core import core_schema
|
|
51
|
+
|
|
52
|
+
PYDANTIC_VERSION = version.parse(pydantic.__version__)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@deprecated("Use PYDANTIC_VERSION.major instead.")
|
|
56
|
+
def get_pydantic_major_version() -> int:
|
|
57
|
+
"""DEPRECATED - Get the major version of Pydantic.
|
|
58
|
+
|
|
59
|
+
Use `PYDANTIC_VERSION.major` instead.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The major version of Pydantic.
|
|
63
|
+
"""
|
|
64
|
+
return PYDANTIC_VERSION.major
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
PYDANTIC_MAJOR_VERSION = PYDANTIC_VERSION.major
|
|
68
|
+
PYDANTIC_MINOR_VERSION = PYDANTIC_VERSION.minor
|
|
69
|
+
|
|
70
|
+
IS_PYDANTIC_V1 = False
|
|
71
|
+
IS_PYDANTIC_V2 = True
|
|
72
|
+
|
|
73
|
+
PydanticBaseModel = BaseModel | BaseModelV1
|
|
74
|
+
TypeBaseModel = type[BaseModel] | type[BaseModelV1]
|
|
75
|
+
|
|
76
|
+
TBaseModel = TypeVar("TBaseModel", bound=PydanticBaseModel)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def is_pydantic_v1_subclass(cls: type) -> bool:
|
|
80
|
+
"""Check if the given class is Pydantic v1-like.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
`True` if the given class is a subclass of Pydantic `BaseModel` 1.x.
|
|
84
|
+
"""
|
|
85
|
+
return issubclass(cls, BaseModelV1)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def is_pydantic_v2_subclass(cls: type) -> TypeGuard[type[BaseModel]]:
|
|
89
|
+
"""Check if the given class is Pydantic v2-like.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
`True` if the given class is a subclass of Pydantic `BaseModel` 2.x.
|
|
93
|
+
"""
|
|
94
|
+
return issubclass(cls, BaseModel)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def is_basemodel_subclass(cls: type) -> bool:
|
|
98
|
+
"""Check if the given class is a subclass of Pydantic `BaseModel`.
|
|
99
|
+
|
|
100
|
+
Check if the given class is a subclass of any of the following:
|
|
101
|
+
|
|
102
|
+
* `pydantic.BaseModel` in Pydantic 2.x
|
|
103
|
+
* `pydantic.v1.BaseModel` in Pydantic 2.x
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
`True` if the given class is a subclass of Pydantic `BaseModel`.
|
|
107
|
+
"""
|
|
108
|
+
# Before we can use issubclass on the cls we need to check if it is a class
|
|
109
|
+
if not inspect.isclass(cls) or isinstance(cls, GenericAlias):
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
return issubclass(cls, (BaseModel, BaseModelV1))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def is_basemodel_instance(obj: Any) -> bool:
|
|
116
|
+
"""Check if the given class is an instance of Pydantic `BaseModel`.
|
|
117
|
+
|
|
118
|
+
Check if the given class is an instance of any of the following:
|
|
119
|
+
|
|
120
|
+
* `pydantic.BaseModel` in Pydantic 2.x
|
|
121
|
+
* `pydantic.v1.BaseModel` in Pydantic 2.x
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
`True` if the given class is an instance of Pydantic `BaseModel`.
|
|
125
|
+
"""
|
|
126
|
+
return isinstance(obj, (BaseModel, BaseModelV1))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# How to type hint this?
|
|
130
|
+
def pre_init(
|
|
131
|
+
func: Callable[[Any, dict[str, Any]], Any],
|
|
132
|
+
) -> Callable[[Any, dict[str, Any]], Any]:
|
|
133
|
+
"""Decorator to run a function before model initialization.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
func: The function to run before model initialization.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
The decorated function.
|
|
140
|
+
"""
|
|
141
|
+
with warnings.catch_warnings():
|
|
142
|
+
warnings.filterwarnings(action="ignore", category=PydanticDeprecationWarning)
|
|
143
|
+
|
|
144
|
+
# Ideally we would use @model_validator(mode="before") but this would change the
|
|
145
|
+
# order of the validators. See https://github.com/pydantic/pydantic/discussions/7434.
|
|
146
|
+
# So we keep root_validator for backward compatibility.
|
|
147
|
+
@root_validator(pre=True) # type: ignore[deprecated]
|
|
148
|
+
@wraps(func)
|
|
149
|
+
def wrapper(cls: type[BaseModel], values: dict[str, Any]) -> Any:
|
|
150
|
+
"""Decorator to run a function before model initialization.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
cls: The model class.
|
|
154
|
+
values: The values to initialize the model with.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
The values to initialize the model with.
|
|
158
|
+
"""
|
|
159
|
+
# Insert default values
|
|
160
|
+
fields = cls.model_fields
|
|
161
|
+
for name, field_info in fields.items():
|
|
162
|
+
# Check if allow_population_by_field_name is enabled
|
|
163
|
+
# If yes, then set the field name to the alias
|
|
164
|
+
if (
|
|
165
|
+
hasattr(cls, "Config")
|
|
166
|
+
and hasattr(cls.Config, "allow_population_by_field_name")
|
|
167
|
+
and cls.Config.allow_population_by_field_name
|
|
168
|
+
and field_info.alias in values
|
|
169
|
+
):
|
|
170
|
+
values[name] = values.pop(field_info.alias)
|
|
171
|
+
if (
|
|
172
|
+
hasattr(cls, "model_config")
|
|
173
|
+
and cls.model_config.get("populate_by_name")
|
|
174
|
+
and field_info.alias in values
|
|
175
|
+
):
|
|
176
|
+
values[name] = values.pop(field_info.alias)
|
|
177
|
+
|
|
178
|
+
if (
|
|
179
|
+
name not in values or values[name] is None
|
|
180
|
+
) and not field_info.is_required():
|
|
181
|
+
if field_info.default_factory is not None:
|
|
182
|
+
values[name] = field_info.default_factory() # type: ignore[call-arg]
|
|
183
|
+
else:
|
|
184
|
+
values[name] = field_info.default
|
|
185
|
+
|
|
186
|
+
# Call the decorated function
|
|
187
|
+
return func(cls, values)
|
|
188
|
+
|
|
189
|
+
return wrapper
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class _IgnoreUnserializable(GenerateJsonSchema):
|
|
193
|
+
"""A JSON schema generator that ignores unknown types.
|
|
194
|
+
|
|
195
|
+
https://docs.pydantic.dev/latest/concepts/json_schema/#customizing-the-json-schema-generation-process
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
@override
|
|
199
|
+
def handle_invalid_for_json_schema(
|
|
200
|
+
self, schema: core_schema.CoreSchema, error_info: str
|
|
201
|
+
) -> JsonSchemaValue:
|
|
202
|
+
return {}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _create_subset_model_v1(
|
|
206
|
+
name: str,
|
|
207
|
+
model: type[BaseModelV1],
|
|
208
|
+
field_names: list[str],
|
|
209
|
+
*,
|
|
210
|
+
descriptions: dict[str, str] | None = None,
|
|
211
|
+
fn_description: str | None = None,
|
|
212
|
+
) -> type[BaseModelV1]:
|
|
213
|
+
"""Create a Pydantic model with only a subset of model's fields."""
|
|
214
|
+
fields = {}
|
|
215
|
+
|
|
216
|
+
for field_name in field_names:
|
|
217
|
+
# Using pydantic v1 so can access __fields__ as a dict.
|
|
218
|
+
field = model.__fields__[field_name]
|
|
219
|
+
t = (
|
|
220
|
+
# this isn't perfect but should work for most functions
|
|
221
|
+
field.outer_type_
|
|
222
|
+
if field.required and not field.allow_none
|
|
223
|
+
else field.outer_type_ | None
|
|
224
|
+
)
|
|
225
|
+
if descriptions and field_name in descriptions:
|
|
226
|
+
field.field_info.description = descriptions[field_name]
|
|
227
|
+
fields[field_name] = (t, field.field_info)
|
|
228
|
+
|
|
229
|
+
rtn = cast("type[BaseModelV1]", create_model_v1(name, **fields)) # type: ignore[call-overload]
|
|
230
|
+
rtn.__doc__ = textwrap.dedent(fn_description or model.__doc__ or "")
|
|
231
|
+
return rtn
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _create_subset_model_v2(
|
|
235
|
+
name: str,
|
|
236
|
+
model: type[BaseModel],
|
|
237
|
+
field_names: list[str],
|
|
238
|
+
*,
|
|
239
|
+
descriptions: dict[str, str] | None = None,
|
|
240
|
+
fn_description: str | None = None,
|
|
241
|
+
) -> type[BaseModel]:
|
|
242
|
+
"""Create a Pydantic model with a subset of the model fields."""
|
|
243
|
+
descriptions_ = descriptions or {}
|
|
244
|
+
fields = {}
|
|
245
|
+
for field_name in field_names:
|
|
246
|
+
field = model.model_fields[field_name]
|
|
247
|
+
description = descriptions_.get(field_name, field.description)
|
|
248
|
+
field_kwargs: dict[str, Any] = {"description": description}
|
|
249
|
+
if field.default_factory is not None:
|
|
250
|
+
field_kwargs["default_factory"] = field.default_factory
|
|
251
|
+
else:
|
|
252
|
+
field_kwargs["default"] = field.default
|
|
253
|
+
field_info = FieldInfoV2(**field_kwargs)
|
|
254
|
+
if field.metadata:
|
|
255
|
+
field_info.metadata = field.metadata
|
|
256
|
+
fields[field_name] = (field.annotation, field_info)
|
|
257
|
+
|
|
258
|
+
rtn = cast(
|
|
259
|
+
"type[BaseModel]",
|
|
260
|
+
_create_model_base( # type: ignore[call-overload]
|
|
261
|
+
name, **fields, __config__=ConfigDict(arbitrary_types_allowed=True)
|
|
262
|
+
),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# TODO(0.3): Determine if there is a more "pydantic" way to preserve annotations.
|
|
266
|
+
# This is done to preserve __annotations__ when working with pydantic 2.x
|
|
267
|
+
# and using the Annotated type with TypedDict.
|
|
268
|
+
# Comment out the following line, to trigger the relevant test case.
|
|
269
|
+
selected_annotations = [
|
|
270
|
+
(name, annotation)
|
|
271
|
+
for name, annotation in model.__annotations__.items()
|
|
272
|
+
if name in field_names
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
rtn.__annotations__ = dict(selected_annotations)
|
|
276
|
+
rtn.__doc__ = textwrap.dedent(fn_description or model.__doc__ or "")
|
|
277
|
+
return rtn
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# Private functionality to create a subset model that's compatible across
|
|
281
|
+
# different versions of pydantic.
|
|
282
|
+
# Handles pydantic versions 2.x. including v1 of pydantic in 2.x.
|
|
283
|
+
# However, can't find a way to type hint this.
|
|
284
|
+
def _create_subset_model(
|
|
285
|
+
name: str,
|
|
286
|
+
model: TypeBaseModel,
|
|
287
|
+
field_names: list[str],
|
|
288
|
+
*,
|
|
289
|
+
descriptions: dict[str, str] | None = None,
|
|
290
|
+
fn_description: str | None = None,
|
|
291
|
+
) -> TypeBaseModel:
|
|
292
|
+
"""Create subset model using the same pydantic version as the input model.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
The created subset model.
|
|
296
|
+
"""
|
|
297
|
+
if issubclass(model, BaseModelV1):
|
|
298
|
+
return _create_subset_model_v1(
|
|
299
|
+
name,
|
|
300
|
+
model,
|
|
301
|
+
field_names,
|
|
302
|
+
descriptions=descriptions,
|
|
303
|
+
fn_description=fn_description,
|
|
304
|
+
)
|
|
305
|
+
return _create_subset_model_v2(
|
|
306
|
+
name,
|
|
307
|
+
model,
|
|
308
|
+
field_names,
|
|
309
|
+
descriptions=descriptions,
|
|
310
|
+
fn_description=fn_description,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@overload
|
|
315
|
+
def get_fields(model: type[BaseModel]) -> dict[str, FieldInfoV2]: ...
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@overload
|
|
319
|
+
def get_fields(model: BaseModel) -> dict[str, FieldInfoV2]: ...
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@overload
|
|
323
|
+
def get_fields(model: type[BaseModelV1]) -> dict[str, ModelField]: ...
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@overload
|
|
327
|
+
def get_fields(model: BaseModelV1) -> dict[str, ModelField]: ...
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def get_fields(
|
|
331
|
+
model: type[BaseModel | BaseModelV1] | BaseModel | BaseModelV1,
|
|
332
|
+
) -> dict[str, FieldInfoV2] | dict[str, ModelField]:
|
|
333
|
+
"""Return the field names of a Pydantic model.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
model: The Pydantic model or instance.
|
|
337
|
+
|
|
338
|
+
Raises:
|
|
339
|
+
TypeError: If the model is not a Pydantic model.
|
|
340
|
+
"""
|
|
341
|
+
if not isinstance(model, type):
|
|
342
|
+
model = type(model)
|
|
343
|
+
if issubclass(model, BaseModel):
|
|
344
|
+
return model.model_fields
|
|
345
|
+
if issubclass(model, BaseModelV1):
|
|
346
|
+
return model.__fields__
|
|
347
|
+
msg = f"Expected a Pydantic model. Got {model}" # type: ignore[unreachable]
|
|
348
|
+
raise TypeError(msg)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def model_json_schema(model: TypeBaseModel) -> dict[str, Any]:
|
|
352
|
+
"""Return the JSON schema of a Pydantic model class of either major version.
|
|
353
|
+
|
|
354
|
+
Dispatches to the correct method for Pydantic v1 (`schema`) or v2
|
|
355
|
+
(`model_json_schema`), so callers holding a `TypeBaseModel` don't have to
|
|
356
|
+
branch on the model's version themselves.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
model: The Pydantic model class.
|
|
360
|
+
|
|
361
|
+
Raises:
|
|
362
|
+
TypeError: If the model is not a Pydantic model class.
|
|
363
|
+
"""
|
|
364
|
+
if issubclass(model, BaseModel):
|
|
365
|
+
return model.model_json_schema()
|
|
366
|
+
if issubclass(model, BaseModelV1):
|
|
367
|
+
return model.schema()
|
|
368
|
+
msg = f"Expected a Pydantic model. Got {model}" # type: ignore[unreachable]
|
|
369
|
+
raise TypeError(msg)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def model_validate(model: TypeBaseModel, obj: Any) -> PydanticBaseModel:
|
|
373
|
+
"""Validate `obj` against a Pydantic model class of either major version.
|
|
374
|
+
|
|
375
|
+
Dispatches to the correct method for Pydantic v1 (`parse_obj`) or v2
|
|
376
|
+
(`model_validate`), so callers holding a `TypeBaseModel` don't have to
|
|
377
|
+
branch on the model's version themselves.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
model: The Pydantic model class to validate against.
|
|
381
|
+
obj: The object to validate.
|
|
382
|
+
|
|
383
|
+
Raises:
|
|
384
|
+
TypeError: If the model is not a Pydantic model class.
|
|
385
|
+
"""
|
|
386
|
+
if issubclass(model, BaseModel):
|
|
387
|
+
return model.model_validate(obj)
|
|
388
|
+
if issubclass(model, BaseModelV1):
|
|
389
|
+
return model.parse_obj(obj)
|
|
390
|
+
msg = f"Expected a Pydantic model. Got {model}" # type: ignore[unreachable]
|
|
391
|
+
raise TypeError(msg)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
_SchemaConfig = ConfigDict(
|
|
395
|
+
arbitrary_types_allowed=True, frozen=True, protected_namespaces=()
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
NO_DEFAULT = object()
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _create_root_model(
|
|
402
|
+
name: str,
|
|
403
|
+
type_: Any,
|
|
404
|
+
module_name: str | None = None,
|
|
405
|
+
default_: object = NO_DEFAULT,
|
|
406
|
+
) -> type[BaseModel]:
|
|
407
|
+
"""Create a base class."""
|
|
408
|
+
|
|
409
|
+
def schema(
|
|
410
|
+
cls: type[BaseModelV1],
|
|
411
|
+
by_alias: bool = True, # noqa: FBT001,FBT002
|
|
412
|
+
ref_template: str = DEFAULT_REF_TEMPLATE,
|
|
413
|
+
) -> dict[str, Any]:
|
|
414
|
+
super_cls = cast("type[BaseModelV1]", super(cls, cls))
|
|
415
|
+
schema_ = super_cls.schema(by_alias=by_alias, ref_template=ref_template)
|
|
416
|
+
schema_["title"] = name
|
|
417
|
+
return schema_
|
|
418
|
+
|
|
419
|
+
def model_json_schema(
|
|
420
|
+
cls: type[BaseModel],
|
|
421
|
+
by_alias: bool = True, # noqa: FBT001,FBT002
|
|
422
|
+
ref_template: str = DEFAULT_REF_TEMPLATE,
|
|
423
|
+
schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,
|
|
424
|
+
mode: JsonSchemaMode = "validation",
|
|
425
|
+
) -> dict[str, Any]:
|
|
426
|
+
super_cls = cast("type[BaseModel]", super(cls, cls))
|
|
427
|
+
schema_ = super_cls.model_json_schema(
|
|
428
|
+
by_alias=by_alias,
|
|
429
|
+
ref_template=ref_template,
|
|
430
|
+
schema_generator=schema_generator,
|
|
431
|
+
mode=mode,
|
|
432
|
+
)
|
|
433
|
+
schema_["title"] = name
|
|
434
|
+
return schema_
|
|
435
|
+
|
|
436
|
+
base_class_attributes = {
|
|
437
|
+
"__annotations__": {"root": type_},
|
|
438
|
+
"model_config": ConfigDict(arbitrary_types_allowed=True),
|
|
439
|
+
"schema": classmethod(schema),
|
|
440
|
+
"model_json_schema": classmethod(model_json_schema),
|
|
441
|
+
"__module__": module_name or "coco_core.runnables.utils",
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if default_ is not NO_DEFAULT:
|
|
445
|
+
base_class_attributes["root"] = default_
|
|
446
|
+
with warnings.catch_warnings():
|
|
447
|
+
try:
|
|
448
|
+
if isinstance(type_, type) and issubclass(type_, BaseModelV1):
|
|
449
|
+
warnings.filterwarnings(
|
|
450
|
+
action="ignore", category=PydanticDeprecationWarning
|
|
451
|
+
)
|
|
452
|
+
except TypeError:
|
|
453
|
+
pass
|
|
454
|
+
custom_root_type = type(name, (RootModel,), base_class_attributes)
|
|
455
|
+
return cast("type[BaseModel]", custom_root_type)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
@lru_cache(maxsize=256)
|
|
459
|
+
def _create_root_model_cached(
|
|
460
|
+
model_name: str,
|
|
461
|
+
type_: Any,
|
|
462
|
+
*,
|
|
463
|
+
module_name: str | None = None,
|
|
464
|
+
default_: object = NO_DEFAULT,
|
|
465
|
+
) -> type[BaseModel]:
|
|
466
|
+
return _create_root_model(
|
|
467
|
+
model_name, type_, default_=default_, module_name=module_name
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@lru_cache(maxsize=256)
|
|
472
|
+
def _create_model_cached(
|
|
473
|
+
model_name: str,
|
|
474
|
+
/,
|
|
475
|
+
**field_definitions: Any,
|
|
476
|
+
) -> type[BaseModel]:
|
|
477
|
+
return _create_model_base(
|
|
478
|
+
model_name,
|
|
479
|
+
__config__=_SchemaConfig,
|
|
480
|
+
**_remap_field_definitions(field_definitions),
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def create_model(
|
|
485
|
+
model_name: str,
|
|
486
|
+
module_name: str | None = None,
|
|
487
|
+
/,
|
|
488
|
+
**field_definitions: Any,
|
|
489
|
+
) -> type[BaseModel]:
|
|
490
|
+
"""Create a Pydantic model with the given field definitions.
|
|
491
|
+
|
|
492
|
+
Please use `create_model_v2` instead of this function.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
model_name: The name of the model.
|
|
496
|
+
module_name: The name of the module where the model is defined.
|
|
497
|
+
|
|
498
|
+
This is used by Pydantic to resolve any forward references.
|
|
499
|
+
**field_definitions: The field definitions for the model.
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
The created model.
|
|
503
|
+
"""
|
|
504
|
+
kwargs = {}
|
|
505
|
+
if "__root__" in field_definitions:
|
|
506
|
+
kwargs["root"] = field_definitions.pop("__root__")
|
|
507
|
+
|
|
508
|
+
return create_model_v2(
|
|
509
|
+
model_name,
|
|
510
|
+
module_name=module_name,
|
|
511
|
+
field_definitions=field_definitions,
|
|
512
|
+
**kwargs,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
# Reserved names should capture all the `public` names / methods that are
|
|
517
|
+
# used by BaseModel internally. This will keep the reserved names up-to-date.
|
|
518
|
+
# For reference, the reserved names are:
|
|
519
|
+
# "construct", "copy", "dict", "from_orm", "json", "parse_file", "parse_obj",
|
|
520
|
+
# "parse_raw", "schema", "schema_json", "update_forward_refs", "validate",
|
|
521
|
+
# "model_computed_fields", "model_config", "model_construct", "model_copy",
|
|
522
|
+
# "model_dump", "model_dump_json", "model_extra", "model_fields",
|
|
523
|
+
# "model_fields_set", "model_json_schema", "model_parametrized_name",
|
|
524
|
+
# "model_post_init", "model_rebuild", "model_validate", "model_validate_json",
|
|
525
|
+
# "model_validate_strings"
|
|
526
|
+
_RESERVED_NAMES = {key for key in dir(BaseModel) if not key.startswith("_")}
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _remap_field_definitions(field_definitions: dict[str, Any]) -> dict[str, Any]:
|
|
530
|
+
"""This remaps fields to avoid colliding with internal pydantic fields."""
|
|
531
|
+
remapped = {}
|
|
532
|
+
for key, value in field_definitions.items():
|
|
533
|
+
if key.startswith("_") or key in _RESERVED_NAMES:
|
|
534
|
+
# Let's add a prefix to avoid colliding with internal pydantic fields
|
|
535
|
+
if isinstance(value, FieldInfoV2):
|
|
536
|
+
msg = (
|
|
537
|
+
f"Remapping for fields starting with '_' or fields with a name "
|
|
538
|
+
f"matching a reserved name {_RESERVED_NAMES} is not supported if "
|
|
539
|
+
f" the field is a pydantic Field instance. Got {key}."
|
|
540
|
+
)
|
|
541
|
+
raise NotImplementedError(msg)
|
|
542
|
+
type_, default_ = value
|
|
543
|
+
remapped[f"private_{key}"] = (
|
|
544
|
+
type_,
|
|
545
|
+
Field(
|
|
546
|
+
default=default_,
|
|
547
|
+
alias=key,
|
|
548
|
+
serialization_alias=key,
|
|
549
|
+
title=key.lstrip("_").replace("_", " ").title(),
|
|
550
|
+
),
|
|
551
|
+
)
|
|
552
|
+
else:
|
|
553
|
+
remapped[key] = value
|
|
554
|
+
return remapped
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def create_model_v2(
|
|
558
|
+
model_name: str,
|
|
559
|
+
*,
|
|
560
|
+
module_name: str | None = None,
|
|
561
|
+
field_definitions: dict[str, Any] | None = None,
|
|
562
|
+
root: Any | None = None,
|
|
563
|
+
) -> type[BaseModel]:
|
|
564
|
+
"""Create a Pydantic model with the given field definitions.
|
|
565
|
+
|
|
566
|
+
!!! warning
|
|
567
|
+
|
|
568
|
+
Do not use outside of coco packages. This API is subject to change at any
|
|
569
|
+
time.
|
|
570
|
+
|
|
571
|
+
Args:
|
|
572
|
+
model_name: The name of the model.
|
|
573
|
+
module_name: The name of the module where the model is defined.
|
|
574
|
+
|
|
575
|
+
This is used by Pydantic to resolve any forward references.
|
|
576
|
+
field_definitions: The field definitions for the model.
|
|
577
|
+
root: Type for a root model (`RootModel`)
|
|
578
|
+
|
|
579
|
+
Returns:
|
|
580
|
+
The created model.
|
|
581
|
+
"""
|
|
582
|
+
field_definitions = field_definitions or {}
|
|
583
|
+
|
|
584
|
+
if root:
|
|
585
|
+
if field_definitions:
|
|
586
|
+
msg = (
|
|
587
|
+
"When specifying __root__ no other "
|
|
588
|
+
f"fields should be provided. Got {field_definitions}"
|
|
589
|
+
)
|
|
590
|
+
raise NotImplementedError(msg)
|
|
591
|
+
|
|
592
|
+
if isinstance(root, tuple):
|
|
593
|
+
kwargs = {"type_": root[0], "default_": root[1]}
|
|
594
|
+
else:
|
|
595
|
+
kwargs = {"type_": root}
|
|
596
|
+
|
|
597
|
+
try:
|
|
598
|
+
named_root_model = _create_root_model_cached(
|
|
599
|
+
model_name, module_name=module_name, **kwargs
|
|
600
|
+
)
|
|
601
|
+
except TypeError:
|
|
602
|
+
# something in the arguments into _create_root_model_cached is not hashable
|
|
603
|
+
named_root_model = _create_root_model(
|
|
604
|
+
model_name,
|
|
605
|
+
module_name=module_name,
|
|
606
|
+
**kwargs,
|
|
607
|
+
)
|
|
608
|
+
return named_root_model
|
|
609
|
+
|
|
610
|
+
# No root, just field definitions
|
|
611
|
+
names = set(field_definitions.keys())
|
|
612
|
+
|
|
613
|
+
capture_warnings = False
|
|
614
|
+
|
|
615
|
+
for name in names:
|
|
616
|
+
# Also if any non-reserved name is used (e.g., model_id or model_name)
|
|
617
|
+
if name.startswith("model"):
|
|
618
|
+
capture_warnings = True
|
|
619
|
+
|
|
620
|
+
with warnings.catch_warnings() if capture_warnings else nullcontext():
|
|
621
|
+
if capture_warnings:
|
|
622
|
+
warnings.filterwarnings(action="ignore")
|
|
623
|
+
try:
|
|
624
|
+
return _create_model_cached(model_name, **field_definitions)
|
|
625
|
+
except TypeError:
|
|
626
|
+
# something in field definitions is not hashable
|
|
627
|
+
return _create_model_base(
|
|
628
|
+
model_name,
|
|
629
|
+
__config__=_SchemaConfig,
|
|
630
|
+
**_remap_field_definitions(field_definitions),
|
|
631
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""String utilities."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def stringify_value(val: Any) -> str:
|
|
8
|
+
"""Stringify a value.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
val: The value to stringify.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
The stringified value.
|
|
15
|
+
"""
|
|
16
|
+
if isinstance(val, str):
|
|
17
|
+
return val
|
|
18
|
+
if isinstance(val, dict):
|
|
19
|
+
return "\n" + stringify_dict(val)
|
|
20
|
+
if isinstance(val, list):
|
|
21
|
+
return "\n".join(stringify_value(v) for v in val)
|
|
22
|
+
return str(val)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def stringify_dict(data: dict[Any, Any]) -> str:
|
|
26
|
+
"""Stringify a dictionary.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
data: The dictionary to stringify.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
The stringified dictionary.
|
|
33
|
+
"""
|
|
34
|
+
return "".join(f"{key}: {stringify_value(value)}\n" for key, value in data.items())
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def comma_list(items: Iterable[Any]) -> str:
|
|
38
|
+
"""Convert an iterable to a comma-separated string.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
items: The iterable to convert.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The comma-separated string.
|
|
45
|
+
"""
|
|
46
|
+
return ", ".join(str(item) for item in items)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def sanitize_for_postgres(text: str, replacement: str = "") -> str:
|
|
50
|
+
r"""Sanitize text by removing NUL bytes that are incompatible with PostgreSQL.
|
|
51
|
+
|
|
52
|
+
PostgreSQL text fields cannot contain `NUL (0x00)` bytes, which can cause
|
|
53
|
+
`psycopg.DataError` when inserting documents. This function removes or replaces
|
|
54
|
+
such characters to ensure compatibility.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
text: The text to sanitize.
|
|
58
|
+
replacement: String to replace `NUL` bytes with.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
The sanitized text with `NUL` bytes removed or replaced.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
>>> sanitize_for_postgres("Hello\\x00world")
|
|
65
|
+
'Helloworld'
|
|
66
|
+
>>> sanitize_for_postgres("Hello\\x00world", " ")
|
|
67
|
+
'Hello world'
|
|
68
|
+
"""
|
|
69
|
+
return text.replace("\x00", replacement)
|