aiqtoolkit 1.1.0a20250516__py3-none-any.whl → 1.1.0a20251020__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.
Potentially problematic release.
This version of aiqtoolkit might be problematic. Click here for more details.
- aiqtoolkit-1.1.0a20251020.dist-info/METADATA +37 -0
- aiqtoolkit-1.1.0a20251020.dist-info/RECORD +4 -0
- {aiqtoolkit-1.1.0a20250516.dist-info → aiqtoolkit-1.1.0a20251020.dist-info}/WHEEL +1 -1
- aiqtoolkit-1.1.0a20251020.dist-info/top_level.txt +1 -0
- aiq/agent/__init__.py +0 -0
- aiq/agent/base.py +0 -76
- aiq/agent/dual_node.py +0 -67
- aiq/agent/react_agent/__init__.py +0 -0
- aiq/agent/react_agent/agent.py +0 -322
- aiq/agent/react_agent/output_parser.py +0 -104
- aiq/agent/react_agent/prompt.py +0 -46
- aiq/agent/react_agent/register.py +0 -148
- aiq/agent/reasoning_agent/__init__.py +0 -0
- aiq/agent/reasoning_agent/reasoning_agent.py +0 -224
- aiq/agent/register.py +0 -23
- aiq/agent/rewoo_agent/__init__.py +0 -0
- aiq/agent/rewoo_agent/agent.py +0 -410
- aiq/agent/rewoo_agent/prompt.py +0 -108
- aiq/agent/rewoo_agent/register.py +0 -158
- aiq/agent/tool_calling_agent/__init__.py +0 -0
- aiq/agent/tool_calling_agent/agent.py +0 -123
- aiq/agent/tool_calling_agent/register.py +0 -105
- aiq/builder/__init__.py +0 -0
- aiq/builder/builder.py +0 -223
- aiq/builder/component_utils.py +0 -303
- aiq/builder/context.py +0 -227
- aiq/builder/embedder.py +0 -24
- aiq/builder/eval_builder.py +0 -120
- aiq/builder/evaluator.py +0 -29
- aiq/builder/framework_enum.py +0 -24
- aiq/builder/front_end.py +0 -73
- aiq/builder/function.py +0 -297
- aiq/builder/function_base.py +0 -376
- aiq/builder/function_info.py +0 -627
- aiq/builder/intermediate_step_manager.py +0 -176
- aiq/builder/llm.py +0 -25
- aiq/builder/retriever.py +0 -25
- aiq/builder/user_interaction_manager.py +0 -71
- aiq/builder/workflow.py +0 -143
- aiq/builder/workflow_builder.py +0 -757
- aiq/cli/__init__.py +0 -14
- aiq/cli/cli_utils/__init__.py +0 -0
- aiq/cli/cli_utils/config_override.py +0 -231
- aiq/cli/cli_utils/validation.py +0 -37
- aiq/cli/commands/__init__.py +0 -0
- aiq/cli/commands/configure/__init__.py +0 -0
- aiq/cli/commands/configure/channel/__init__.py +0 -0
- aiq/cli/commands/configure/channel/add.py +0 -28
- aiq/cli/commands/configure/channel/channel.py +0 -36
- aiq/cli/commands/configure/channel/remove.py +0 -30
- aiq/cli/commands/configure/channel/update.py +0 -30
- aiq/cli/commands/configure/configure.py +0 -33
- aiq/cli/commands/evaluate.py +0 -139
- aiq/cli/commands/info/__init__.py +0 -14
- aiq/cli/commands/info/info.py +0 -39
- aiq/cli/commands/info/list_channels.py +0 -32
- aiq/cli/commands/info/list_components.py +0 -129
- aiq/cli/commands/info/list_mcp.py +0 -126
- aiq/cli/commands/registry/__init__.py +0 -14
- aiq/cli/commands/registry/publish.py +0 -88
- aiq/cli/commands/registry/pull.py +0 -118
- aiq/cli/commands/registry/registry.py +0 -38
- aiq/cli/commands/registry/remove.py +0 -108
- aiq/cli/commands/registry/search.py +0 -155
- aiq/cli/commands/start.py +0 -250
- aiq/cli/commands/uninstall.py +0 -83
- aiq/cli/commands/validate.py +0 -47
- aiq/cli/commands/workflow/__init__.py +0 -14
- aiq/cli/commands/workflow/templates/__init__.py.j2 +0 -0
- aiq/cli/commands/workflow/templates/config.yml.j2 +0 -16
- aiq/cli/commands/workflow/templates/pyproject.toml.j2 +0 -22
- aiq/cli/commands/workflow/templates/register.py.j2 +0 -5
- aiq/cli/commands/workflow/templates/workflow.py.j2 +0 -36
- aiq/cli/commands/workflow/workflow.py +0 -37
- aiq/cli/commands/workflow/workflow_commands.py +0 -313
- aiq/cli/entrypoint.py +0 -133
- aiq/cli/main.py +0 -44
- aiq/cli/register_workflow.py +0 -408
- aiq/cli/type_registry.py +0 -879
- aiq/data_models/__init__.py +0 -14
- aiq/data_models/api_server.py +0 -588
- aiq/data_models/common.py +0 -143
- aiq/data_models/component.py +0 -46
- aiq/data_models/component_ref.py +0 -135
- aiq/data_models/config.py +0 -349
- aiq/data_models/dataset_handler.py +0 -122
- aiq/data_models/discovery_metadata.py +0 -286
- aiq/data_models/embedder.py +0 -26
- aiq/data_models/evaluate.py +0 -104
- aiq/data_models/evaluator.py +0 -26
- aiq/data_models/front_end.py +0 -26
- aiq/data_models/function.py +0 -30
- aiq/data_models/function_dependencies.py +0 -64
- aiq/data_models/interactive.py +0 -237
- aiq/data_models/intermediate_step.py +0 -269
- aiq/data_models/invocation_node.py +0 -38
- aiq/data_models/llm.py +0 -26
- aiq/data_models/logging.py +0 -26
- aiq/data_models/memory.py +0 -26
- aiq/data_models/profiler.py +0 -53
- aiq/data_models/registry_handler.py +0 -26
- aiq/data_models/retriever.py +0 -30
- aiq/data_models/step_adaptor.py +0 -64
- aiq/data_models/streaming.py +0 -33
- aiq/data_models/swe_bench_model.py +0 -54
- aiq/data_models/telemetry_exporter.py +0 -26
- aiq/embedder/__init__.py +0 -0
- aiq/embedder/langchain_client.py +0 -41
- aiq/embedder/nim_embedder.py +0 -58
- aiq/embedder/openai_embedder.py +0 -42
- aiq/embedder/register.py +0 -24
- aiq/eval/__init__.py +0 -14
- aiq/eval/config.py +0 -42
- aiq/eval/dataset_handler/__init__.py +0 -0
- aiq/eval/dataset_handler/dataset_downloader.py +0 -106
- aiq/eval/dataset_handler/dataset_filter.py +0 -52
- aiq/eval/dataset_handler/dataset_handler.py +0 -169
- aiq/eval/evaluate.py +0 -325
- aiq/eval/evaluator/__init__.py +0 -14
- aiq/eval/evaluator/evaluator_model.py +0 -44
- aiq/eval/intermediate_step_adapter.py +0 -93
- aiq/eval/rag_evaluator/__init__.py +0 -0
- aiq/eval/rag_evaluator/evaluate.py +0 -138
- aiq/eval/rag_evaluator/register.py +0 -138
- aiq/eval/register.py +0 -23
- aiq/eval/remote_workflow.py +0 -128
- aiq/eval/runtime_event_subscriber.py +0 -52
- aiq/eval/swe_bench_evaluator/__init__.py +0 -0
- aiq/eval/swe_bench_evaluator/evaluate.py +0 -215
- aiq/eval/swe_bench_evaluator/register.py +0 -36
- aiq/eval/trajectory_evaluator/__init__.py +0 -0
- aiq/eval/trajectory_evaluator/evaluate.py +0 -118
- aiq/eval/trajectory_evaluator/register.py +0 -40
- aiq/eval/tunable_rag_evaluator/__init__.py +0 -0
- aiq/eval/tunable_rag_evaluator/evaluate.py +0 -263
- aiq/eval/tunable_rag_evaluator/register.py +0 -50
- aiq/eval/utils/__init__.py +0 -0
- aiq/eval/utils/output_uploader.py +0 -131
- aiq/eval/utils/tqdm_position_registry.py +0 -40
- aiq/front_ends/__init__.py +0 -14
- aiq/front_ends/console/__init__.py +0 -14
- aiq/front_ends/console/console_front_end_config.py +0 -32
- aiq/front_ends/console/console_front_end_plugin.py +0 -107
- aiq/front_ends/console/register.py +0 -25
- aiq/front_ends/cron/__init__.py +0 -14
- aiq/front_ends/fastapi/__init__.py +0 -14
- aiq/front_ends/fastapi/fastapi_front_end_config.py +0 -150
- aiq/front_ends/fastapi/fastapi_front_end_plugin.py +0 -103
- aiq/front_ends/fastapi/fastapi_front_end_plugin_worker.py +0 -607
- aiq/front_ends/fastapi/intermediate_steps_subscriber.py +0 -80
- aiq/front_ends/fastapi/job_store.py +0 -161
- aiq/front_ends/fastapi/main.py +0 -70
- aiq/front_ends/fastapi/message_handler.py +0 -279
- aiq/front_ends/fastapi/message_validator.py +0 -345
- aiq/front_ends/fastapi/register.py +0 -25
- aiq/front_ends/fastapi/response_helpers.py +0 -195
- aiq/front_ends/fastapi/step_adaptor.py +0 -320
- aiq/front_ends/fastapi/websocket.py +0 -148
- aiq/front_ends/mcp/__init__.py +0 -14
- aiq/front_ends/mcp/mcp_front_end_config.py +0 -32
- aiq/front_ends/mcp/mcp_front_end_plugin.py +0 -93
- aiq/front_ends/mcp/register.py +0 -27
- aiq/front_ends/mcp/tool_converter.py +0 -242
- aiq/front_ends/register.py +0 -22
- aiq/front_ends/simple_base/__init__.py +0 -14
- aiq/front_ends/simple_base/simple_front_end_plugin_base.py +0 -52
- aiq/llm/__init__.py +0 -0
- aiq/llm/nim_llm.py +0 -45
- aiq/llm/openai_llm.py +0 -45
- aiq/llm/register.py +0 -22
- aiq/llm/utils/__init__.py +0 -14
- aiq/llm/utils/env_config_value.py +0 -94
- aiq/llm/utils/error.py +0 -17
- aiq/memory/__init__.py +0 -20
- aiq/memory/interfaces.py +0 -183
- aiq/memory/models.py +0 -112
- aiq/meta/module_to_distro.json +0 -3
- aiq/meta/pypi.md +0 -58
- aiq/observability/__init__.py +0 -0
- aiq/observability/async_otel_listener.py +0 -429
- aiq/observability/register.py +0 -99
- aiq/plugins/.namespace +0 -1
- aiq/profiler/__init__.py +0 -0
- aiq/profiler/callbacks/__init__.py +0 -0
- aiq/profiler/callbacks/agno_callback_handler.py +0 -295
- aiq/profiler/callbacks/base_callback_class.py +0 -20
- aiq/profiler/callbacks/langchain_callback_handler.py +0 -278
- aiq/profiler/callbacks/llama_index_callback_handler.py +0 -205
- aiq/profiler/callbacks/semantic_kernel_callback_handler.py +0 -238
- aiq/profiler/callbacks/token_usage_base_model.py +0 -27
- aiq/profiler/data_frame_row.py +0 -51
- aiq/profiler/decorators/__init__.py +0 -0
- aiq/profiler/decorators/framework_wrapper.py +0 -131
- aiq/profiler/decorators/function_tracking.py +0 -254
- aiq/profiler/forecasting/__init__.py +0 -0
- aiq/profiler/forecasting/config.py +0 -18
- aiq/profiler/forecasting/model_trainer.py +0 -75
- aiq/profiler/forecasting/models/__init__.py +0 -22
- aiq/profiler/forecasting/models/forecasting_base_model.py +0 -40
- aiq/profiler/forecasting/models/linear_model.py +0 -196
- aiq/profiler/forecasting/models/random_forest_regressor.py +0 -268
- aiq/profiler/inference_metrics_model.py +0 -25
- aiq/profiler/inference_optimization/__init__.py +0 -0
- aiq/profiler/inference_optimization/bottleneck_analysis/__init__.py +0 -0
- aiq/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +0 -452
- aiq/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +0 -258
- aiq/profiler/inference_optimization/data_models.py +0 -386
- aiq/profiler/inference_optimization/experimental/__init__.py +0 -0
- aiq/profiler/inference_optimization/experimental/concurrency_spike_analysis.py +0 -468
- aiq/profiler/inference_optimization/experimental/prefix_span_analysis.py +0 -405
- aiq/profiler/inference_optimization/llm_metrics.py +0 -212
- aiq/profiler/inference_optimization/prompt_caching.py +0 -163
- aiq/profiler/inference_optimization/token_uniqueness.py +0 -107
- aiq/profiler/inference_optimization/workflow_runtimes.py +0 -72
- aiq/profiler/intermediate_property_adapter.py +0 -102
- aiq/profiler/profile_runner.py +0 -433
- aiq/profiler/utils.py +0 -184
- aiq/registry_handlers/__init__.py +0 -0
- aiq/registry_handlers/local/__init__.py +0 -0
- aiq/registry_handlers/local/local_handler.py +0 -176
- aiq/registry_handlers/local/register_local.py +0 -37
- aiq/registry_handlers/metadata_factory.py +0 -60
- aiq/registry_handlers/package_utils.py +0 -198
- aiq/registry_handlers/pypi/__init__.py +0 -0
- aiq/registry_handlers/pypi/pypi_handler.py +0 -251
- aiq/registry_handlers/pypi/register_pypi.py +0 -40
- aiq/registry_handlers/register.py +0 -21
- aiq/registry_handlers/registry_handler_base.py +0 -157
- aiq/registry_handlers/rest/__init__.py +0 -0
- aiq/registry_handlers/rest/register_rest.py +0 -56
- aiq/registry_handlers/rest/rest_handler.py +0 -237
- aiq/registry_handlers/schemas/__init__.py +0 -0
- aiq/registry_handlers/schemas/headers.py +0 -42
- aiq/registry_handlers/schemas/package.py +0 -68
- aiq/registry_handlers/schemas/publish.py +0 -63
- aiq/registry_handlers/schemas/pull.py +0 -82
- aiq/registry_handlers/schemas/remove.py +0 -36
- aiq/registry_handlers/schemas/search.py +0 -91
- aiq/registry_handlers/schemas/status.py +0 -47
- aiq/retriever/__init__.py +0 -0
- aiq/retriever/interface.py +0 -37
- aiq/retriever/milvus/__init__.py +0 -14
- aiq/retriever/milvus/register.py +0 -81
- aiq/retriever/milvus/retriever.py +0 -228
- aiq/retriever/models.py +0 -74
- aiq/retriever/nemo_retriever/__init__.py +0 -14
- aiq/retriever/nemo_retriever/register.py +0 -60
- aiq/retriever/nemo_retriever/retriever.py +0 -190
- aiq/retriever/register.py +0 -22
- aiq/runtime/__init__.py +0 -14
- aiq/runtime/loader.py +0 -188
- aiq/runtime/runner.py +0 -176
- aiq/runtime/session.py +0 -140
- aiq/runtime/user_metadata.py +0 -131
- aiq/settings/__init__.py +0 -0
- aiq/settings/global_settings.py +0 -318
- aiq/test/.namespace +0 -1
- aiq/tool/__init__.py +0 -0
- aiq/tool/code_execution/__init__.py +0 -0
- aiq/tool/code_execution/code_sandbox.py +0 -188
- aiq/tool/code_execution/local_sandbox/Dockerfile.sandbox +0 -60
- aiq/tool/code_execution/local_sandbox/__init__.py +0 -13
- aiq/tool/code_execution/local_sandbox/local_sandbox_server.py +0 -83
- aiq/tool/code_execution/local_sandbox/sandbox.requirements.txt +0 -4
- aiq/tool/code_execution/local_sandbox/start_local_sandbox.sh +0 -25
- aiq/tool/code_execution/register.py +0 -70
- aiq/tool/code_execution/utils.py +0 -100
- aiq/tool/datetime_tools.py +0 -42
- aiq/tool/document_search.py +0 -141
- aiq/tool/github_tools/__init__.py +0 -0
- aiq/tool/github_tools/create_github_commit.py +0 -133
- aiq/tool/github_tools/create_github_issue.py +0 -87
- aiq/tool/github_tools/create_github_pr.py +0 -106
- aiq/tool/github_tools/get_github_file.py +0 -106
- aiq/tool/github_tools/get_github_issue.py +0 -166
- aiq/tool/github_tools/get_github_pr.py +0 -256
- aiq/tool/github_tools/update_github_issue.py +0 -100
- aiq/tool/mcp/__init__.py +0 -14
- aiq/tool/mcp/mcp_client.py +0 -220
- aiq/tool/mcp/mcp_tool.py +0 -95
- aiq/tool/memory_tools/__init__.py +0 -0
- aiq/tool/memory_tools/add_memory_tool.py +0 -79
- aiq/tool/memory_tools/delete_memory_tool.py +0 -67
- aiq/tool/memory_tools/get_memory_tool.py +0 -72
- aiq/tool/nvidia_rag.py +0 -95
- aiq/tool/register.py +0 -37
- aiq/tool/retriever.py +0 -89
- aiq/tool/server_tools.py +0 -63
- aiq/utils/__init__.py +0 -0
- aiq/utils/data_models/__init__.py +0 -0
- aiq/utils/data_models/schema_validator.py +0 -58
- aiq/utils/debugging_utils.py +0 -43
- aiq/utils/exception_handlers/__init__.py +0 -0
- aiq/utils/exception_handlers/schemas.py +0 -114
- aiq/utils/io/__init__.py +0 -0
- aiq/utils/io/yaml_tools.py +0 -119
- aiq/utils/metadata_utils.py +0 -74
- aiq/utils/optional_imports.py +0 -142
- aiq/utils/producer_consumer_queue.py +0 -178
- aiq/utils/reactive/__init__.py +0 -0
- aiq/utils/reactive/base/__init__.py +0 -0
- aiq/utils/reactive/base/observable_base.py +0 -65
- aiq/utils/reactive/base/observer_base.py +0 -55
- aiq/utils/reactive/base/subject_base.py +0 -79
- aiq/utils/reactive/observable.py +0 -59
- aiq/utils/reactive/observer.py +0 -76
- aiq/utils/reactive/subject.py +0 -131
- aiq/utils/reactive/subscription.py +0 -49
- aiq/utils/settings/__init__.py +0 -0
- aiq/utils/settings/global_settings.py +0 -197
- aiq/utils/type_converter.py +0 -232
- aiq/utils/type_utils.py +0 -397
- aiq/utils/url_utils.py +0 -27
- aiqtoolkit-1.1.0a20250516.dist-info/METADATA +0 -331
- aiqtoolkit-1.1.0a20250516.dist-info/RECORD +0 -316
- aiqtoolkit-1.1.0a20250516.dist-info/entry_points.txt +0 -17
- aiqtoolkit-1.1.0a20250516.dist-info/licenses/LICENSE-3rd-party.txt +0 -3686
- aiqtoolkit-1.1.0a20250516.dist-info/licenses/LICENSE.md +0 -201
- aiqtoolkit-1.1.0a20250516.dist-info/top_level.txt +0 -1
aiq/utils/reactive/subject.py
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
#
|
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
# you may not use this file except in compliance with the License.
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
# See the License for the specific language governing permissions and
|
|
14
|
-
# limitations under the License.
|
|
15
|
-
|
|
16
|
-
import threading
|
|
17
|
-
from collections.abc import Callable
|
|
18
|
-
from typing import TypeVar
|
|
19
|
-
|
|
20
|
-
from aiq.utils.reactive.base.subject_base import SubjectBase
|
|
21
|
-
from aiq.utils.reactive.observable import Observable
|
|
22
|
-
from aiq.utils.reactive.observer import Observer
|
|
23
|
-
from aiq.utils.reactive.subscription import Subscription
|
|
24
|
-
|
|
25
|
-
T = TypeVar("T")
|
|
26
|
-
|
|
27
|
-
OnNext = Callable[[T], None]
|
|
28
|
-
OnError = Callable[[Exception], None]
|
|
29
|
-
OnComplete = Callable[[], None]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class Subject(Observable[T], Observer[T], SubjectBase[T]):
|
|
33
|
-
"""
|
|
34
|
-
A Subject is both an Observer (receives events) and an Observable (sends events).
|
|
35
|
-
- Maintains a list of ObserverBase[T].
|
|
36
|
-
- No internal buffering or replay; events are only delivered to current subscribers.
|
|
37
|
-
- Thread-safe via a lock.
|
|
38
|
-
|
|
39
|
-
Once on_error or on_complete is called, the Subject is closed.
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
def __init__(self) -> None:
|
|
43
|
-
super().__init__()
|
|
44
|
-
self._lock = threading.RLock()
|
|
45
|
-
self._closed = False
|
|
46
|
-
self._error: Exception | None = None
|
|
47
|
-
self._observers: list[Observer[T]] = []
|
|
48
|
-
self._disposed = False
|
|
49
|
-
|
|
50
|
-
# ==========================================================================
|
|
51
|
-
# Observable[T] - for consumers
|
|
52
|
-
# ==========================================================================
|
|
53
|
-
def _subscribe_core(self, observer: Observer[T]) -> Subscription:
|
|
54
|
-
"""
|
|
55
|
-
Subscribe to this subject. If disposed, returns a dummy subscription.
|
|
56
|
-
Otherwise, registers the given observer.
|
|
57
|
-
"""
|
|
58
|
-
with self._lock:
|
|
59
|
-
if self._disposed:
|
|
60
|
-
# Already disposed => no subscription
|
|
61
|
-
return Subscription(self, None)
|
|
62
|
-
|
|
63
|
-
self._observers.append(observer)
|
|
64
|
-
return Subscription(self, observer)
|
|
65
|
-
|
|
66
|
-
# ==========================================================================
|
|
67
|
-
# ObserverBase[T] - for producers
|
|
68
|
-
# ==========================================================================
|
|
69
|
-
def on_next(self, value: T) -> None:
|
|
70
|
-
"""
|
|
71
|
-
Called by producers to emit an item. Delivers synchronously to each observer.
|
|
72
|
-
If closed or disposed, do nothing.
|
|
73
|
-
"""
|
|
74
|
-
with self._lock:
|
|
75
|
-
if self._closed or self._disposed:
|
|
76
|
-
return
|
|
77
|
-
# Copy the current observers to avoid mutation issues
|
|
78
|
-
current_observers = list(self._observers)
|
|
79
|
-
|
|
80
|
-
# Deliver outside the lock
|
|
81
|
-
for obs in current_observers:
|
|
82
|
-
obs.on_next(value)
|
|
83
|
-
|
|
84
|
-
def on_error(self, exc: Exception) -> None:
|
|
85
|
-
"""
|
|
86
|
-
Called by producers to signal an error. Notifies all observers.
|
|
87
|
-
"""
|
|
88
|
-
with self._lock:
|
|
89
|
-
if self._closed or self._disposed:
|
|
90
|
-
return
|
|
91
|
-
current_obs = list(self._observers)
|
|
92
|
-
|
|
93
|
-
for obs in current_obs:
|
|
94
|
-
obs.on_error(exc)
|
|
95
|
-
|
|
96
|
-
def on_complete(self) -> None:
|
|
97
|
-
"""
|
|
98
|
-
Called by producers to signal completion. Notifies all observers, then
|
|
99
|
-
clears them. Subject is closed.
|
|
100
|
-
"""
|
|
101
|
-
with self._lock:
|
|
102
|
-
if self._closed or self._disposed:
|
|
103
|
-
return
|
|
104
|
-
current_observers = list(self._observers)
|
|
105
|
-
self.dispose()
|
|
106
|
-
|
|
107
|
-
for obs in current_observers:
|
|
108
|
-
obs.on_complete()
|
|
109
|
-
|
|
110
|
-
# ==========================================================================
|
|
111
|
-
# SubjectBase - internal unsubscribing
|
|
112
|
-
# ==========================================================================
|
|
113
|
-
def _unsubscribe_observer(self, observer: Observer[T]) -> None:
|
|
114
|
-
with self._lock:
|
|
115
|
-
if not self._disposed and observer in self._observers:
|
|
116
|
-
self._observers.remove(observer)
|
|
117
|
-
|
|
118
|
-
# ==========================================================================
|
|
119
|
-
# Disposal
|
|
120
|
-
# ==========================================================================
|
|
121
|
-
def dispose(self) -> None:
|
|
122
|
-
"""
|
|
123
|
-
Immediately close the Subject. No future on_next, on_error, or on_complete.
|
|
124
|
-
Clears all observers.
|
|
125
|
-
"""
|
|
126
|
-
with self._lock:
|
|
127
|
-
if not self._disposed:
|
|
128
|
-
self._disposed = True
|
|
129
|
-
self._observers.clear()
|
|
130
|
-
self._closed = True
|
|
131
|
-
self._error = None
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
#
|
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
# you may not use this file except in compliance with the License.
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
# See the License for the specific language governing permissions and
|
|
14
|
-
# limitations under the License.
|
|
15
|
-
|
|
16
|
-
import typing
|
|
17
|
-
from collections.abc import Callable
|
|
18
|
-
from typing import Generic
|
|
19
|
-
from typing import TypeVar
|
|
20
|
-
|
|
21
|
-
if typing.TYPE_CHECKING:
|
|
22
|
-
from aiq.utils.reactive.base.subject_base import SubjectBase
|
|
23
|
-
|
|
24
|
-
_T = TypeVar("_T") # pylint: disable=invalid-name
|
|
25
|
-
|
|
26
|
-
OnNext = Callable[[_T], None]
|
|
27
|
-
OnError = Callable[[Exception], None]
|
|
28
|
-
OnComplete = Callable[[], None]
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class Subscription(Generic[_T]):
|
|
32
|
-
"""
|
|
33
|
-
Represents a subscription to a Subject.
|
|
34
|
-
Unsubscribing removes the associated observer from the Subject's subscriber list.
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
def __init__(self, subject: "SubjectBase", observer: object | None): # noqa: F821
|
|
38
|
-
self._subject = subject
|
|
39
|
-
self._observer = observer
|
|
40
|
-
self._unsubscribed = False
|
|
41
|
-
|
|
42
|
-
def unsubscribe(self) -> None:
|
|
43
|
-
"""
|
|
44
|
-
Stop receiving further events.
|
|
45
|
-
"""
|
|
46
|
-
if not self._unsubscribed and self._observer is not None:
|
|
47
|
-
self._subject._unsubscribe_observer(self._observer)
|
|
48
|
-
self._observer = None
|
|
49
|
-
self._unsubscribed = True
|
aiq/utils/settings/__init__.py
DELETED
|
File without changes
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
#
|
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
# you may not use this file except in compliance with the License.
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
# See the License for the specific language governing permissions and
|
|
14
|
-
# limitations under the License.
|
|
15
|
-
|
|
16
|
-
import logging
|
|
17
|
-
|
|
18
|
-
from pydantic import create_model
|
|
19
|
-
|
|
20
|
-
from aiq.cli.type_registry import GlobalTypeRegistry
|
|
21
|
-
from aiq.data_models.registry_handler import RegistryHandlerBaseConfig
|
|
22
|
-
from aiq.settings.global_settings import GlobalSettings
|
|
23
|
-
|
|
24
|
-
logger = logging.getLogger(__name__)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def configure_registry_channel(config_type: RegistryHandlerBaseConfig, channel_name: str) -> None:
|
|
28
|
-
"""Perform channel updates, gathering input from user and validatinig against the global settings data model.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
config_type (RegistryHandlerBaseConfig): The registry handler configuration object to ensure valid channel
|
|
32
|
-
settings
|
|
33
|
-
channel_name (str): The name to use to reference the remote registry channel.
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
settings = GlobalSettings.get()
|
|
37
|
-
|
|
38
|
-
channel_registry_pre = {}
|
|
39
|
-
|
|
40
|
-
for field, info in config_type.model_fields.items():
|
|
41
|
-
|
|
42
|
-
if (field == "type"):
|
|
43
|
-
continue
|
|
44
|
-
|
|
45
|
-
while (True):
|
|
46
|
-
human_prompt = " ".join(field.title().split("_"))
|
|
47
|
-
user_input = input(f"{human_prompt}: ")
|
|
48
|
-
model_fields = {}
|
|
49
|
-
model_fields[field] = (info.annotation, ...)
|
|
50
|
-
DynamicFieldModel = create_model("DynamicFieldModel", **model_fields) # pylint: disable=C0103
|
|
51
|
-
dynamic_inputs = {field: user_input}
|
|
52
|
-
|
|
53
|
-
try:
|
|
54
|
-
validated_field_model = DynamicFieldModel(**dynamic_inputs)
|
|
55
|
-
channel_registry_pre[field] = getattr(validated_field_model, field)
|
|
56
|
-
break
|
|
57
|
-
except Exception as e:
|
|
58
|
-
logger.exception(e, exc_info=True)
|
|
59
|
-
logger.warning("Invalid '%s' input, input must be of type %s.", field, info.annotation)
|
|
60
|
-
|
|
61
|
-
validated_model = config_type(**channel_registry_pre)
|
|
62
|
-
settings_dict = settings.model_dump(serialize_as_any=True, by_alias=True)
|
|
63
|
-
settings_dict["channels"] = {**settings_dict["channels"], **{channel_name: validated_model}}
|
|
64
|
-
|
|
65
|
-
settings.update_settings(config_obj=settings_dict)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def add_channel_interative(channel_type: str) -> None:
|
|
69
|
-
"""Add a remote registry channel to publish/search/pull AIQ Toolkit plugin packages.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
channel_type (str): They type of channel to configure.
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
settings = GlobalSettings.get()
|
|
76
|
-
registry = GlobalTypeRegistry.get()
|
|
77
|
-
|
|
78
|
-
try:
|
|
79
|
-
ChannelConfigType = registry.get_registered_channel_info_by_channel_type( # pylint: disable=C0103
|
|
80
|
-
channel_type=channel_type).config_type
|
|
81
|
-
except Exception as e:
|
|
82
|
-
logger.exception("Invalid channel type: %s", e, exc_info=True)
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
while (True):
|
|
86
|
-
channel_name = input("Channel Name: ").strip()
|
|
87
|
-
if len(channel_name) < 1:
|
|
88
|
-
logger.warning("Invalid channel name, cannot be empty or whitespace.")
|
|
89
|
-
if (channel_name in settings.channels):
|
|
90
|
-
logger.warning("Channel name '%s' already exists, choose a different name.", channel_name)
|
|
91
|
-
else:
|
|
92
|
-
settings.channels[channel_name] = {}
|
|
93
|
-
break
|
|
94
|
-
|
|
95
|
-
ChannelConfigType = registry.get_registered_channel_info_by_channel_type( # pylint: disable=C0103
|
|
96
|
-
channel_type=channel_type).config_type
|
|
97
|
-
|
|
98
|
-
configure_registry_channel(config_type=ChannelConfigType, channel_name=channel_name)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def get_existing_channel_interactive(channel_name: str) -> tuple[str, bool]:
|
|
102
|
-
"""Retrieve an existing channel by configured name.
|
|
103
|
-
|
|
104
|
-
Args:
|
|
105
|
-
channel_name (str): The name to use to reference the remote registry channel.
|
|
106
|
-
|
|
107
|
-
Returns:
|
|
108
|
-
tuple[str, bool]: A tuple containing the retrieved channel name and a boolean representing a
|
|
109
|
-
valid match was or was not successful.
|
|
110
|
-
"""
|
|
111
|
-
|
|
112
|
-
settings = GlobalSettings.get()
|
|
113
|
-
valid_channel = False
|
|
114
|
-
remote_channels = settings.channels
|
|
115
|
-
|
|
116
|
-
if (len(remote_channels) == 0):
|
|
117
|
-
logger.warning("No are configured channels to remove.")
|
|
118
|
-
return channel_name, valid_channel
|
|
119
|
-
|
|
120
|
-
while (not valid_channel):
|
|
121
|
-
|
|
122
|
-
if (channel_name not in remote_channels):
|
|
123
|
-
logger.warning("Channel name '%s' does not exist, choose a name from %s",
|
|
124
|
-
channel_name,
|
|
125
|
-
settings.channel_names)
|
|
126
|
-
channel_name = input("Channel Name: ").strip()
|
|
127
|
-
continue
|
|
128
|
-
|
|
129
|
-
valid_channel = True
|
|
130
|
-
|
|
131
|
-
return channel_name, valid_channel
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def remove_channel(channel_name: str) -> None:
|
|
135
|
-
"""Remove a configured registry channel from the global settings.
|
|
136
|
-
|
|
137
|
-
Args:
|
|
138
|
-
channel_name (str): The name to use to reference the remote registry channel.
|
|
139
|
-
"""
|
|
140
|
-
|
|
141
|
-
settings = GlobalSettings.get()
|
|
142
|
-
settings_dict = settings.model_dump(serialize_as_any=True, by_alias=True).copy()
|
|
143
|
-
settings_dict["channels"].pop(channel_name)
|
|
144
|
-
settings.update_settings(config_obj=settings_dict)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def remove_channel_interactive(channel_name: str) -> None:
|
|
148
|
-
channel_name, valid_channel = get_existing_channel_interactive(channel_name=channel_name)
|
|
149
|
-
if (not valid_channel):
|
|
150
|
-
return
|
|
151
|
-
remove_channel(channel_name=channel_name)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def match_valid_channel(channel_name: str) -> None:
|
|
155
|
-
"""Performs a match by registry channel to perform a channel configuration update.
|
|
156
|
-
|
|
157
|
-
Args:
|
|
158
|
-
channel_name (str): The name to use to reference the remote registry channel.
|
|
159
|
-
"""
|
|
160
|
-
|
|
161
|
-
settings = GlobalSettings.get()
|
|
162
|
-
registry = GlobalTypeRegistry.get()
|
|
163
|
-
|
|
164
|
-
if len(settings.channel_names) == 0:
|
|
165
|
-
logger.warning("No channels have been configured, first add a channel.")
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
if (channel_name not in settings.channel_names):
|
|
169
|
-
logger.warning("Provided channel has not yet been configured, choose a different name "
|
|
170
|
-
"from %s .",
|
|
171
|
-
settings.channel_names)
|
|
172
|
-
while (True):
|
|
173
|
-
channel_name = input("Channel Name: ").strip()
|
|
174
|
-
if len(channel_name) < 1:
|
|
175
|
-
logger.warning("Invalid channel name, cannot be empty or whitespace.")
|
|
176
|
-
if (channel_name in settings.channel_names):
|
|
177
|
-
logger.warning("Channel name '%s' already exists, choose a different name.", channel_name)
|
|
178
|
-
else:
|
|
179
|
-
settings.channels[channel_name] = {}
|
|
180
|
-
break
|
|
181
|
-
|
|
182
|
-
channals_settings = settings.channels
|
|
183
|
-
channel_settings = channals_settings.get(channel_name)
|
|
184
|
-
ChannelConfigType = registry.get_registered_channel_info_by_channel_type( # pylint: disable=C0103
|
|
185
|
-
channel_type=channel_settings.static_type()).config_type
|
|
186
|
-
|
|
187
|
-
configure_registry_channel(config_type=ChannelConfigType, channel_name=channel_name)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def update_channel_interactive(channel_name: str):
|
|
191
|
-
"""Launch an interactive session to update a configured channels settings.
|
|
192
|
-
|
|
193
|
-
Args:
|
|
194
|
-
channel_name (str): The name to use to reference the remote registry channel.
|
|
195
|
-
"""
|
|
196
|
-
|
|
197
|
-
match_valid_channel(channel_name=channel_name)
|
aiq/utils/type_converter.py
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
#
|
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
# you may not use this file except in compliance with the License.
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
# See the License for the specific language governing permissions and
|
|
14
|
-
# limitations under the License.
|
|
15
|
-
|
|
16
|
-
import logging
|
|
17
|
-
import typing
|
|
18
|
-
from collections import OrderedDict
|
|
19
|
-
from collections.abc import Callable
|
|
20
|
-
from io import TextIOWrapper
|
|
21
|
-
|
|
22
|
-
from aiq.utils.type_utils import DecomposedType
|
|
23
|
-
|
|
24
|
-
logger = logging.getLogger(__name__)
|
|
25
|
-
|
|
26
|
-
_T = typing.TypeVar("_T")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class ConvertException(Exception):
|
|
30
|
-
pass
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class TypeConverter:
|
|
34
|
-
_global_initialized = False
|
|
35
|
-
|
|
36
|
-
def __init__(self, converters: list[Callable[[typing.Any], typing.Any]], parent: "TypeConverter | None" = None):
|
|
37
|
-
"""
|
|
38
|
-
:param converters: A list of single-argument converter callables
|
|
39
|
-
annotated with their input param and return type.
|
|
40
|
-
:param parent: An optional parent TypeConverter for fallback.
|
|
41
|
-
"""
|
|
42
|
-
# dict[to_type, dict[from_type, converter]]
|
|
43
|
-
self._converters: OrderedDict[type, OrderedDict[type, Callable]] = OrderedDict()
|
|
44
|
-
self._indirect_warnings_shown: set[tuple[type, type]] = set()
|
|
45
|
-
|
|
46
|
-
for converter in converters:
|
|
47
|
-
self.add_converter(converter)
|
|
48
|
-
|
|
49
|
-
if parent is None and TypeConverter._global_initialized:
|
|
50
|
-
parent = GlobalTypeConverter.get()
|
|
51
|
-
self._parent = parent
|
|
52
|
-
|
|
53
|
-
def add_converter(self, converter: Callable) -> None:
|
|
54
|
-
"""
|
|
55
|
-
Registers a converter. Must have exactly one parameter
|
|
56
|
-
and an annotated return type.
|
|
57
|
-
"""
|
|
58
|
-
sig = typing.get_type_hints(converter)
|
|
59
|
-
to_type = sig.pop("return", None)
|
|
60
|
-
if to_type is None:
|
|
61
|
-
raise ValueError("Converter must have a return type.")
|
|
62
|
-
|
|
63
|
-
if len(sig) != 1:
|
|
64
|
-
raise ValueError("Converter must have exactly one argument.")
|
|
65
|
-
|
|
66
|
-
from_type = next(iter(sig.values()))
|
|
67
|
-
if from_type is None:
|
|
68
|
-
raise ValueError("Converter's argument must have a data type.")
|
|
69
|
-
|
|
70
|
-
self._converters.setdefault(to_type, OrderedDict())[from_type] = converter
|
|
71
|
-
# to do(MDD): If needed, sort by specificity here.
|
|
72
|
-
|
|
73
|
-
def try_convert(self, data, to_type: type[_T]) -> _T | None:
|
|
74
|
-
"""
|
|
75
|
-
Attempts to convert `data` into `to_type`. Returns None if no path is found.
|
|
76
|
-
"""
|
|
77
|
-
decomposed = DecomposedType(to_type)
|
|
78
|
-
|
|
79
|
-
# 1) If data is already correct type, return it
|
|
80
|
-
if to_type is None or decomposed.is_instance((data, to_type)):
|
|
81
|
-
return data
|
|
82
|
-
|
|
83
|
-
root = decomposed.root
|
|
84
|
-
|
|
85
|
-
# 2) Attempt direct in *this* converter
|
|
86
|
-
direct_result = self._try_direct_conversion(data, root)
|
|
87
|
-
if direct_result is not None:
|
|
88
|
-
return direct_result
|
|
89
|
-
|
|
90
|
-
# 3) If direct fails entirely, do indirect in *this* converter
|
|
91
|
-
indirect_result = self._try_indirect_convert(data, to_type)
|
|
92
|
-
if indirect_result is not None:
|
|
93
|
-
return indirect_result
|
|
94
|
-
|
|
95
|
-
# 4) If we still haven't succeeded, return None
|
|
96
|
-
return None
|
|
97
|
-
|
|
98
|
-
def convert(self, data, to_type: type[_T]) -> _T:
|
|
99
|
-
"""
|
|
100
|
-
Converts or raises ValueError if no path is found.
|
|
101
|
-
We also give the parent a chance if self fails.
|
|
102
|
-
"""
|
|
103
|
-
result = self.try_convert(data, to_type)
|
|
104
|
-
if result is None and self._parent:
|
|
105
|
-
# fallback on parent entirely
|
|
106
|
-
return self._parent.convert(data, to_type)
|
|
107
|
-
|
|
108
|
-
if result is not None:
|
|
109
|
-
return result
|
|
110
|
-
raise ValueError(f"Cannot convert type {type(data)} to {to_type}. No match found.")
|
|
111
|
-
|
|
112
|
-
# -------------------------------------------------
|
|
113
|
-
# INTERNAL DIRECT CONVERSION (with parent fallback)
|
|
114
|
-
# -------------------------------------------------
|
|
115
|
-
def _try_direct_conversion(self, data, target_root_type: type) -> typing.Any | None:
|
|
116
|
-
"""
|
|
117
|
-
Tries direct conversion in *this* converter's registry.
|
|
118
|
-
If no match here, we forward to parent's direct conversion
|
|
119
|
-
for recursion up the chain.
|
|
120
|
-
"""
|
|
121
|
-
for convert_to_type, to_type_converters in self._converters.items():
|
|
122
|
-
# e.g. if Derived is a subclass of Base, this is valid
|
|
123
|
-
if issubclass(DecomposedType(convert_to_type).root, target_root_type):
|
|
124
|
-
for convert_from_type, from_type_converter in to_type_converters.items():
|
|
125
|
-
if isinstance(data, DecomposedType(convert_from_type).root):
|
|
126
|
-
try:
|
|
127
|
-
return from_type_converter(data)
|
|
128
|
-
except ConvertException:
|
|
129
|
-
pass
|
|
130
|
-
|
|
131
|
-
# If we can't convert directly here, try parent
|
|
132
|
-
if self._parent is not None:
|
|
133
|
-
return self._parent._try_direct_conversion(data, target_root_type)
|
|
134
|
-
|
|
135
|
-
return None
|
|
136
|
-
|
|
137
|
-
# -------------------------------------------------
|
|
138
|
-
# INTERNAL INDIRECT CONVERSION (with parent fallback)
|
|
139
|
-
# -------------------------------------------------
|
|
140
|
-
def _try_indirect_convert(self, data, to_type: type[_T]) -> _T | None:
|
|
141
|
-
"""
|
|
142
|
-
Attempt indirect conversion (DFS) in *this* converter.
|
|
143
|
-
If no success, fallback to parent's indirect attempt.
|
|
144
|
-
"""
|
|
145
|
-
visited = set()
|
|
146
|
-
final = self._try_indirect_conversion(data, to_type, visited)
|
|
147
|
-
if final is not None:
|
|
148
|
-
# Warn once if found a chain
|
|
149
|
-
self._maybe_warn_indirect(type(data), to_type)
|
|
150
|
-
return final
|
|
151
|
-
|
|
152
|
-
# If no success, try parent's indirect
|
|
153
|
-
if self._parent is not None:
|
|
154
|
-
parent_final = self._parent._try_indirect_convert(data, to_type)
|
|
155
|
-
if parent_final is not None:
|
|
156
|
-
self._maybe_warn_indirect(type(data), to_type)
|
|
157
|
-
return parent_final
|
|
158
|
-
|
|
159
|
-
return None
|
|
160
|
-
|
|
161
|
-
def _try_indirect_conversion(self, data: typing.Any, to_type: type[_T], visited: set[type]) -> _T | None:
|
|
162
|
-
"""
|
|
163
|
-
DFS attempt to find a chain of conversions from type(data) to to_type,
|
|
164
|
-
ignoring parent. If not found, returns None.
|
|
165
|
-
"""
|
|
166
|
-
# 1) If data is already correct type
|
|
167
|
-
if isinstance(data, to_type):
|
|
168
|
-
return data
|
|
169
|
-
|
|
170
|
-
current_type = type(data)
|
|
171
|
-
if current_type in visited:
|
|
172
|
-
return None
|
|
173
|
-
|
|
174
|
-
visited.add(current_type)
|
|
175
|
-
|
|
176
|
-
# 2) Attempt each known converter from current_type -> ???, then recurse
|
|
177
|
-
for _, to_type_converters in self._converters.items():
|
|
178
|
-
for convert_from_type, from_type_converter in to_type_converters.items():
|
|
179
|
-
if isinstance(data, convert_from_type):
|
|
180
|
-
try:
|
|
181
|
-
next_data = from_type_converter(data)
|
|
182
|
-
if isinstance(next_data, to_type):
|
|
183
|
-
return next_data
|
|
184
|
-
# else keep going
|
|
185
|
-
deeper = self._try_indirect_conversion(next_data, to_type, visited)
|
|
186
|
-
if deeper is not None:
|
|
187
|
-
return deeper
|
|
188
|
-
except ConvertException:
|
|
189
|
-
pass
|
|
190
|
-
|
|
191
|
-
return None
|
|
192
|
-
|
|
193
|
-
def _maybe_warn_indirect(self, source_type: type, to_type: type):
|
|
194
|
-
"""
|
|
195
|
-
Warn once if an indirect path was used between these two types.
|
|
196
|
-
"""
|
|
197
|
-
pair = (source_type, to_type)
|
|
198
|
-
if pair not in self._indirect_warnings_shown:
|
|
199
|
-
logger.warning(
|
|
200
|
-
"Indirect type conversion used to convert %s to %s, which may lead to unintended conversions. "
|
|
201
|
-
"Consider adding a direct converter from %s to %s to ensure correctness.",
|
|
202
|
-
source_type,
|
|
203
|
-
to_type,
|
|
204
|
-
source_type,
|
|
205
|
-
to_type)
|
|
206
|
-
self._indirect_warnings_shown.add(pair)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
class GlobalTypeConverter:
|
|
210
|
-
_global_converter: TypeConverter = TypeConverter([])
|
|
211
|
-
|
|
212
|
-
@staticmethod
|
|
213
|
-
def get() -> TypeConverter:
|
|
214
|
-
return GlobalTypeConverter._global_converter
|
|
215
|
-
|
|
216
|
-
@staticmethod
|
|
217
|
-
def register_converter(converter: Callable) -> None:
|
|
218
|
-
GlobalTypeConverter._global_converter.add_converter(converter)
|
|
219
|
-
|
|
220
|
-
@staticmethod
|
|
221
|
-
def convert(data, to_type: type[_T]) -> _T:
|
|
222
|
-
return GlobalTypeConverter._global_converter.convert(data, to_type)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
TypeConverter._global_initialized = True
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def _text_io_wrapper_to_string(data: TextIOWrapper) -> str:
|
|
229
|
-
return data.read()
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
GlobalTypeConverter.register_converter(_text_io_wrapper_to_string)
|