nvidia-nat 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.
- aiq/__init__.py +66 -0
- nat/agent/__init__.py +0 -0
- nat/agent/base.py +265 -0
- nat/agent/dual_node.py +72 -0
- nat/agent/prompt_optimizer/__init__.py +0 -0
- nat/agent/prompt_optimizer/prompt.py +68 -0
- nat/agent/prompt_optimizer/register.py +149 -0
- nat/agent/react_agent/__init__.py +0 -0
- nat/agent/react_agent/agent.py +394 -0
- nat/agent/react_agent/output_parser.py +104 -0
- nat/agent/react_agent/prompt.py +44 -0
- nat/agent/react_agent/register.py +168 -0
- nat/agent/reasoning_agent/__init__.py +0 -0
- nat/agent/reasoning_agent/reasoning_agent.py +227 -0
- nat/agent/register.py +23 -0
- nat/agent/rewoo_agent/__init__.py +0 -0
- nat/agent/rewoo_agent/agent.py +593 -0
- nat/agent/rewoo_agent/prompt.py +107 -0
- nat/agent/rewoo_agent/register.py +175 -0
- nat/agent/tool_calling_agent/__init__.py +0 -0
- nat/agent/tool_calling_agent/agent.py +246 -0
- nat/agent/tool_calling_agent/register.py +129 -0
- nat/authentication/__init__.py +14 -0
- nat/authentication/api_key/__init__.py +14 -0
- nat/authentication/api_key/api_key_auth_provider.py +96 -0
- nat/authentication/api_key/api_key_auth_provider_config.py +124 -0
- nat/authentication/api_key/register.py +26 -0
- nat/authentication/credential_validator/__init__.py +14 -0
- nat/authentication/credential_validator/bearer_token_validator.py +557 -0
- nat/authentication/exceptions/__init__.py +14 -0
- nat/authentication/exceptions/api_key_exceptions.py +38 -0
- nat/authentication/http_basic_auth/__init__.py +0 -0
- nat/authentication/http_basic_auth/http_basic_auth_provider.py +81 -0
- nat/authentication/http_basic_auth/register.py +30 -0
- nat/authentication/interfaces.py +96 -0
- nat/authentication/oauth2/__init__.py +14 -0
- nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +140 -0
- nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +39 -0
- nat/authentication/oauth2/oauth2_resource_server_config.py +124 -0
- nat/authentication/oauth2/register.py +25 -0
- nat/authentication/register.py +20 -0
- nat/builder/__init__.py +0 -0
- nat/builder/builder.py +317 -0
- nat/builder/component_utils.py +320 -0
- nat/builder/context.py +321 -0
- nat/builder/embedder.py +24 -0
- nat/builder/eval_builder.py +166 -0
- nat/builder/evaluator.py +29 -0
- nat/builder/framework_enum.py +25 -0
- nat/builder/front_end.py +73 -0
- nat/builder/function.py +714 -0
- nat/builder/function_base.py +380 -0
- nat/builder/function_info.py +625 -0
- nat/builder/intermediate_step_manager.py +206 -0
- nat/builder/llm.py +25 -0
- nat/builder/retriever.py +25 -0
- nat/builder/user_interaction_manager.py +78 -0
- nat/builder/workflow.py +160 -0
- nat/builder/workflow_builder.py +1365 -0
- nat/cli/__init__.py +14 -0
- nat/cli/cli_utils/__init__.py +0 -0
- nat/cli/cli_utils/config_override.py +231 -0
- nat/cli/cli_utils/validation.py +37 -0
- nat/cli/commands/__init__.py +0 -0
- nat/cli/commands/configure/__init__.py +0 -0
- nat/cli/commands/configure/channel/__init__.py +0 -0
- nat/cli/commands/configure/channel/add.py +28 -0
- nat/cli/commands/configure/channel/channel.py +34 -0
- nat/cli/commands/configure/channel/remove.py +30 -0
- nat/cli/commands/configure/channel/update.py +30 -0
- nat/cli/commands/configure/configure.py +33 -0
- nat/cli/commands/evaluate.py +139 -0
- nat/cli/commands/info/__init__.py +14 -0
- nat/cli/commands/info/info.py +47 -0
- nat/cli/commands/info/list_channels.py +32 -0
- nat/cli/commands/info/list_components.py +128 -0
- nat/cli/commands/mcp/__init__.py +14 -0
- nat/cli/commands/mcp/mcp.py +986 -0
- nat/cli/commands/object_store/__init__.py +14 -0
- nat/cli/commands/object_store/object_store.py +227 -0
- nat/cli/commands/optimize.py +90 -0
- nat/cli/commands/registry/__init__.py +14 -0
- nat/cli/commands/registry/publish.py +88 -0
- nat/cli/commands/registry/pull.py +118 -0
- nat/cli/commands/registry/registry.py +36 -0
- nat/cli/commands/registry/remove.py +108 -0
- nat/cli/commands/registry/search.py +153 -0
- nat/cli/commands/sizing/__init__.py +14 -0
- nat/cli/commands/sizing/calc.py +297 -0
- nat/cli/commands/sizing/sizing.py +27 -0
- nat/cli/commands/start.py +257 -0
- nat/cli/commands/uninstall.py +81 -0
- nat/cli/commands/validate.py +47 -0
- nat/cli/commands/workflow/__init__.py +14 -0
- nat/cli/commands/workflow/templates/__init__.py.j2 +0 -0
- nat/cli/commands/workflow/templates/config.yml.j2 +17 -0
- nat/cli/commands/workflow/templates/pyproject.toml.j2 +25 -0
- nat/cli/commands/workflow/templates/register.py.j2 +4 -0
- nat/cli/commands/workflow/templates/workflow.py.j2 +50 -0
- nat/cli/commands/workflow/workflow.py +37 -0
- nat/cli/commands/workflow/workflow_commands.py +403 -0
- nat/cli/entrypoint.py +141 -0
- nat/cli/main.py +60 -0
- nat/cli/register_workflow.py +522 -0
- nat/cli/type_registry.py +1069 -0
- nat/control_flow/__init__.py +0 -0
- nat/control_flow/register.py +20 -0
- nat/control_flow/router_agent/__init__.py +0 -0
- nat/control_flow/router_agent/agent.py +329 -0
- nat/control_flow/router_agent/prompt.py +48 -0
- nat/control_flow/router_agent/register.py +91 -0
- nat/control_flow/sequential_executor.py +166 -0
- nat/data_models/__init__.py +14 -0
- nat/data_models/agent.py +34 -0
- nat/data_models/api_server.py +843 -0
- nat/data_models/authentication.py +245 -0
- nat/data_models/common.py +171 -0
- nat/data_models/component.py +60 -0
- nat/data_models/component_ref.py +179 -0
- nat/data_models/config.py +434 -0
- nat/data_models/dataset_handler.py +169 -0
- nat/data_models/discovery_metadata.py +305 -0
- nat/data_models/embedder.py +27 -0
- nat/data_models/evaluate.py +130 -0
- nat/data_models/evaluator.py +26 -0
- nat/data_models/front_end.py +26 -0
- nat/data_models/function.py +64 -0
- nat/data_models/function_dependencies.py +80 -0
- nat/data_models/gated_field_mixin.py +242 -0
- nat/data_models/interactive.py +246 -0
- nat/data_models/intermediate_step.py +302 -0
- nat/data_models/invocation_node.py +38 -0
- nat/data_models/llm.py +27 -0
- nat/data_models/logging.py +26 -0
- nat/data_models/memory.py +27 -0
- nat/data_models/object_store.py +44 -0
- nat/data_models/optimizable.py +119 -0
- nat/data_models/optimizer.py +149 -0
- nat/data_models/profiler.py +54 -0
- nat/data_models/registry_handler.py +26 -0
- nat/data_models/retriever.py +30 -0
- nat/data_models/retry_mixin.py +35 -0
- nat/data_models/span.py +228 -0
- nat/data_models/step_adaptor.py +64 -0
- nat/data_models/streaming.py +33 -0
- nat/data_models/swe_bench_model.py +54 -0
- nat/data_models/telemetry_exporter.py +26 -0
- nat/data_models/temperature_mixin.py +44 -0
- nat/data_models/thinking_mixin.py +86 -0
- nat/data_models/top_p_mixin.py +44 -0
- nat/data_models/ttc_strategy.py +30 -0
- nat/embedder/__init__.py +0 -0
- nat/embedder/azure_openai_embedder.py +46 -0
- nat/embedder/nim_embedder.py +59 -0
- nat/embedder/openai_embedder.py +42 -0
- nat/embedder/register.py +22 -0
- nat/eval/__init__.py +14 -0
- nat/eval/config.py +62 -0
- nat/eval/dataset_handler/__init__.py +0 -0
- nat/eval/dataset_handler/dataset_downloader.py +106 -0
- nat/eval/dataset_handler/dataset_filter.py +52 -0
- nat/eval/dataset_handler/dataset_handler.py +431 -0
- nat/eval/evaluate.py +565 -0
- nat/eval/evaluator/__init__.py +14 -0
- nat/eval/evaluator/base_evaluator.py +77 -0
- nat/eval/evaluator/evaluator_model.py +58 -0
- nat/eval/intermediate_step_adapter.py +99 -0
- nat/eval/rag_evaluator/__init__.py +0 -0
- nat/eval/rag_evaluator/evaluate.py +178 -0
- nat/eval/rag_evaluator/register.py +143 -0
- nat/eval/register.py +26 -0
- nat/eval/remote_workflow.py +133 -0
- nat/eval/runners/__init__.py +14 -0
- nat/eval/runners/config.py +39 -0
- nat/eval/runners/multi_eval_runner.py +54 -0
- nat/eval/runtime_evaluator/__init__.py +14 -0
- nat/eval/runtime_evaluator/evaluate.py +123 -0
- nat/eval/runtime_evaluator/register.py +100 -0
- nat/eval/runtime_event_subscriber.py +52 -0
- nat/eval/swe_bench_evaluator/__init__.py +0 -0
- nat/eval/swe_bench_evaluator/evaluate.py +215 -0
- nat/eval/swe_bench_evaluator/register.py +36 -0
- nat/eval/trajectory_evaluator/__init__.py +0 -0
- nat/eval/trajectory_evaluator/evaluate.py +75 -0
- nat/eval/trajectory_evaluator/register.py +40 -0
- nat/eval/tunable_rag_evaluator/__init__.py +0 -0
- nat/eval/tunable_rag_evaluator/evaluate.py +242 -0
- nat/eval/tunable_rag_evaluator/register.py +52 -0
- nat/eval/usage_stats.py +41 -0
- nat/eval/utils/__init__.py +0 -0
- nat/eval/utils/eval_trace_ctx.py +89 -0
- nat/eval/utils/output_uploader.py +140 -0
- nat/eval/utils/tqdm_position_registry.py +40 -0
- nat/eval/utils/weave_eval.py +193 -0
- nat/experimental/__init__.py +0 -0
- nat/experimental/decorators/__init__.py +0 -0
- nat/experimental/decorators/experimental_warning_decorator.py +154 -0
- nat/experimental/test_time_compute/__init__.py +0 -0
- nat/experimental/test_time_compute/editing/__init__.py +0 -0
- nat/experimental/test_time_compute/editing/iterative_plan_refinement_editor.py +147 -0
- nat/experimental/test_time_compute/editing/llm_as_a_judge_editor.py +204 -0
- nat/experimental/test_time_compute/editing/motivation_aware_summarization.py +107 -0
- nat/experimental/test_time_compute/functions/__init__.py +0 -0
- nat/experimental/test_time_compute/functions/execute_score_select_function.py +105 -0
- nat/experimental/test_time_compute/functions/plan_select_execute_function.py +228 -0
- nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +205 -0
- nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +146 -0
- nat/experimental/test_time_compute/models/__init__.py +0 -0
- nat/experimental/test_time_compute/models/editor_config.py +132 -0
- nat/experimental/test_time_compute/models/scoring_config.py +112 -0
- nat/experimental/test_time_compute/models/search_config.py +120 -0
- nat/experimental/test_time_compute/models/selection_config.py +154 -0
- nat/experimental/test_time_compute/models/stage_enums.py +43 -0
- nat/experimental/test_time_compute/models/strategy_base.py +67 -0
- nat/experimental/test_time_compute/models/tool_use_config.py +41 -0
- nat/experimental/test_time_compute/models/ttc_item.py +48 -0
- nat/experimental/test_time_compute/register.py +35 -0
- nat/experimental/test_time_compute/scoring/__init__.py +0 -0
- nat/experimental/test_time_compute/scoring/llm_based_agent_scorer.py +168 -0
- nat/experimental/test_time_compute/scoring/llm_based_plan_scorer.py +168 -0
- nat/experimental/test_time_compute/scoring/motivation_aware_scorer.py +111 -0
- nat/experimental/test_time_compute/search/__init__.py +0 -0
- nat/experimental/test_time_compute/search/multi_llm_planner.py +128 -0
- nat/experimental/test_time_compute/search/multi_query_retrieval_search.py +122 -0
- nat/experimental/test_time_compute/search/single_shot_multi_plan_planner.py +128 -0
- nat/experimental/test_time_compute/selection/__init__.py +0 -0
- nat/experimental/test_time_compute/selection/best_of_n_selector.py +63 -0
- nat/experimental/test_time_compute/selection/llm_based_agent_output_selector.py +131 -0
- nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +157 -0
- nat/experimental/test_time_compute/selection/llm_based_plan_selector.py +128 -0
- nat/experimental/test_time_compute/selection/threshold_selector.py +58 -0
- nat/front_ends/__init__.py +14 -0
- nat/front_ends/console/__init__.py +14 -0
- nat/front_ends/console/authentication_flow_handler.py +285 -0
- nat/front_ends/console/console_front_end_config.py +32 -0
- nat/front_ends/console/console_front_end_plugin.py +108 -0
- nat/front_ends/console/register.py +25 -0
- nat/front_ends/cron/__init__.py +14 -0
- nat/front_ends/fastapi/__init__.py +14 -0
- nat/front_ends/fastapi/auth_flow_handlers/__init__.py +0 -0
- nat/front_ends/fastapi/auth_flow_handlers/http_flow_handler.py +27 -0
- nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +142 -0
- nat/front_ends/fastapi/dask_client_mixin.py +65 -0
- nat/front_ends/fastapi/fastapi_front_end_config.py +272 -0
- nat/front_ends/fastapi/fastapi_front_end_controller.py +68 -0
- nat/front_ends/fastapi/fastapi_front_end_plugin.py +247 -0
- nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +1257 -0
- nat/front_ends/fastapi/html_snippets/__init__.py +14 -0
- nat/front_ends/fastapi/html_snippets/auth_code_grant_success.py +35 -0
- nat/front_ends/fastapi/intermediate_steps_subscriber.py +80 -0
- nat/front_ends/fastapi/job_store.py +602 -0
- nat/front_ends/fastapi/main.py +64 -0
- nat/front_ends/fastapi/message_handler.py +344 -0
- nat/front_ends/fastapi/message_validator.py +351 -0
- nat/front_ends/fastapi/register.py +25 -0
- nat/front_ends/fastapi/response_helpers.py +195 -0
- nat/front_ends/fastapi/step_adaptor.py +319 -0
- nat/front_ends/fastapi/utils.py +57 -0
- nat/front_ends/mcp/__init__.py +14 -0
- nat/front_ends/mcp/introspection_token_verifier.py +73 -0
- nat/front_ends/mcp/mcp_front_end_config.py +90 -0
- nat/front_ends/mcp/mcp_front_end_plugin.py +113 -0
- nat/front_ends/mcp/mcp_front_end_plugin_worker.py +268 -0
- nat/front_ends/mcp/memory_profiler.py +320 -0
- nat/front_ends/mcp/register.py +27 -0
- nat/front_ends/mcp/tool_converter.py +290 -0
- nat/front_ends/register.py +21 -0
- nat/front_ends/simple_base/__init__.py +14 -0
- nat/front_ends/simple_base/simple_front_end_plugin_base.py +56 -0
- nat/llm/__init__.py +0 -0
- nat/llm/aws_bedrock_llm.py +69 -0
- nat/llm/azure_openai_llm.py +57 -0
- nat/llm/litellm_llm.py +69 -0
- nat/llm/nim_llm.py +58 -0
- nat/llm/openai_llm.py +54 -0
- nat/llm/register.py +27 -0
- nat/llm/utils/__init__.py +14 -0
- nat/llm/utils/env_config_value.py +93 -0
- nat/llm/utils/error.py +17 -0
- nat/llm/utils/thinking.py +215 -0
- nat/memory/__init__.py +20 -0
- nat/memory/interfaces.py +183 -0
- nat/memory/models.py +112 -0
- nat/meta/pypi.md +58 -0
- nat/object_store/__init__.py +20 -0
- nat/object_store/in_memory_object_store.py +76 -0
- nat/object_store/interfaces.py +84 -0
- nat/object_store/models.py +38 -0
- nat/object_store/register.py +19 -0
- nat/observability/__init__.py +14 -0
- nat/observability/exporter/__init__.py +14 -0
- nat/observability/exporter/base_exporter.py +449 -0
- nat/observability/exporter/exporter.py +78 -0
- nat/observability/exporter/file_exporter.py +33 -0
- nat/observability/exporter/processing_exporter.py +550 -0
- nat/observability/exporter/raw_exporter.py +52 -0
- nat/observability/exporter/span_exporter.py +308 -0
- nat/observability/exporter_manager.py +335 -0
- nat/observability/mixin/__init__.py +14 -0
- nat/observability/mixin/batch_config_mixin.py +26 -0
- nat/observability/mixin/collector_config_mixin.py +23 -0
- nat/observability/mixin/file_mixin.py +288 -0
- nat/observability/mixin/file_mode.py +23 -0
- nat/observability/mixin/redaction_config_mixin.py +42 -0
- nat/observability/mixin/resource_conflict_mixin.py +134 -0
- nat/observability/mixin/serialize_mixin.py +61 -0
- nat/observability/mixin/tagging_config_mixin.py +62 -0
- nat/observability/mixin/type_introspection_mixin.py +496 -0
- nat/observability/processor/__init__.py +14 -0
- nat/observability/processor/batching_processor.py +308 -0
- nat/observability/processor/callback_processor.py +42 -0
- nat/observability/processor/falsy_batch_filter_processor.py +55 -0
- nat/observability/processor/intermediate_step_serializer.py +28 -0
- nat/observability/processor/processor.py +74 -0
- nat/observability/processor/processor_factory.py +70 -0
- nat/observability/processor/redaction/__init__.py +24 -0
- nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
- nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
- nat/observability/processor/redaction/redaction_processor.py +177 -0
- nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
- nat/observability/processor/span_tagging_processor.py +68 -0
- nat/observability/register.py +114 -0
- nat/observability/utils/__init__.py +14 -0
- nat/observability/utils/dict_utils.py +236 -0
- nat/observability/utils/time_utils.py +31 -0
- nat/plugins/.namespace +1 -0
- nat/profiler/__init__.py +0 -0
- nat/profiler/calc/__init__.py +14 -0
- nat/profiler/calc/calc_runner.py +626 -0
- nat/profiler/calc/calculations.py +288 -0
- nat/profiler/calc/data_models.py +188 -0
- nat/profiler/calc/plot.py +345 -0
- nat/profiler/callbacks/__init__.py +0 -0
- nat/profiler/callbacks/agno_callback_handler.py +295 -0
- nat/profiler/callbacks/base_callback_class.py +20 -0
- nat/profiler/callbacks/langchain_callback_handler.py +297 -0
- nat/profiler/callbacks/llama_index_callback_handler.py +205 -0
- nat/profiler/callbacks/semantic_kernel_callback_handler.py +238 -0
- nat/profiler/callbacks/token_usage_base_model.py +27 -0
- nat/profiler/data_frame_row.py +51 -0
- nat/profiler/data_models.py +24 -0
- nat/profiler/decorators/__init__.py +0 -0
- nat/profiler/decorators/framework_wrapper.py +180 -0
- nat/profiler/decorators/function_tracking.py +411 -0
- nat/profiler/forecasting/__init__.py +0 -0
- nat/profiler/forecasting/config.py +18 -0
- nat/profiler/forecasting/model_trainer.py +75 -0
- nat/profiler/forecasting/models/__init__.py +22 -0
- nat/profiler/forecasting/models/forecasting_base_model.py +42 -0
- nat/profiler/forecasting/models/linear_model.py +197 -0
- nat/profiler/forecasting/models/random_forest_regressor.py +269 -0
- nat/profiler/inference_metrics_model.py +28 -0
- nat/profiler/inference_optimization/__init__.py +0 -0
- nat/profiler/inference_optimization/bottleneck_analysis/__init__.py +0 -0
- nat/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +460 -0
- nat/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +258 -0
- nat/profiler/inference_optimization/data_models.py +386 -0
- nat/profiler/inference_optimization/experimental/__init__.py +0 -0
- nat/profiler/inference_optimization/experimental/concurrency_spike_analysis.py +468 -0
- nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +404 -0
- nat/profiler/inference_optimization/llm_metrics.py +212 -0
- nat/profiler/inference_optimization/prompt_caching.py +163 -0
- nat/profiler/inference_optimization/token_uniqueness.py +107 -0
- nat/profiler/inference_optimization/workflow_runtimes.py +72 -0
- nat/profiler/intermediate_property_adapter.py +102 -0
- nat/profiler/parameter_optimization/__init__.py +0 -0
- nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
- nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
- nat/profiler/parameter_optimization/parameter_optimizer.py +153 -0
- nat/profiler/parameter_optimization/parameter_selection.py +107 -0
- nat/profiler/parameter_optimization/pareto_visualizer.py +380 -0
- nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
- nat/profiler/parameter_optimization/update_helpers.py +66 -0
- nat/profiler/profile_runner.py +478 -0
- nat/profiler/utils.py +186 -0
- nat/registry_handlers/__init__.py +0 -0
- nat/registry_handlers/local/__init__.py +0 -0
- nat/registry_handlers/local/local_handler.py +176 -0
- nat/registry_handlers/local/register_local.py +37 -0
- nat/registry_handlers/metadata_factory.py +60 -0
- nat/registry_handlers/package_utils.py +570 -0
- nat/registry_handlers/pypi/__init__.py +0 -0
- nat/registry_handlers/pypi/pypi_handler.py +248 -0
- nat/registry_handlers/pypi/register_pypi.py +40 -0
- nat/registry_handlers/register.py +20 -0
- nat/registry_handlers/registry_handler_base.py +157 -0
- nat/registry_handlers/rest/__init__.py +0 -0
- nat/registry_handlers/rest/register_rest.py +56 -0
- nat/registry_handlers/rest/rest_handler.py +236 -0
- nat/registry_handlers/schemas/__init__.py +0 -0
- nat/registry_handlers/schemas/headers.py +42 -0
- nat/registry_handlers/schemas/package.py +68 -0
- nat/registry_handlers/schemas/publish.py +68 -0
- nat/registry_handlers/schemas/pull.py +82 -0
- nat/registry_handlers/schemas/remove.py +36 -0
- nat/registry_handlers/schemas/search.py +91 -0
- nat/registry_handlers/schemas/status.py +47 -0
- nat/retriever/__init__.py +0 -0
- nat/retriever/interface.py +41 -0
- nat/retriever/milvus/__init__.py +14 -0
- nat/retriever/milvus/register.py +81 -0
- nat/retriever/milvus/retriever.py +228 -0
- nat/retriever/models.py +77 -0
- nat/retriever/nemo_retriever/__init__.py +14 -0
- nat/retriever/nemo_retriever/register.py +60 -0
- nat/retriever/nemo_retriever/retriever.py +190 -0
- nat/retriever/register.py +21 -0
- nat/runtime/__init__.py +14 -0
- nat/runtime/loader.py +220 -0
- nat/runtime/runner.py +292 -0
- nat/runtime/session.py +223 -0
- nat/runtime/user_metadata.py +130 -0
- nat/settings/__init__.py +0 -0
- nat/settings/global_settings.py +329 -0
- nat/test/.namespace +1 -0
- nat/tool/__init__.py +0 -0
- nat/tool/chat_completion.py +77 -0
- nat/tool/code_execution/README.md +151 -0
- nat/tool/code_execution/__init__.py +0 -0
- nat/tool/code_execution/code_sandbox.py +267 -0
- nat/tool/code_execution/local_sandbox/.gitignore +1 -0
- nat/tool/code_execution/local_sandbox/Dockerfile.sandbox +60 -0
- nat/tool/code_execution/local_sandbox/__init__.py +13 -0
- nat/tool/code_execution/local_sandbox/local_sandbox_server.py +198 -0
- nat/tool/code_execution/local_sandbox/sandbox.requirements.txt +6 -0
- nat/tool/code_execution/local_sandbox/start_local_sandbox.sh +50 -0
- nat/tool/code_execution/register.py +74 -0
- nat/tool/code_execution/test_code_execution_sandbox.py +414 -0
- nat/tool/code_execution/utils.py +100 -0
- nat/tool/datetime_tools.py +82 -0
- nat/tool/document_search.py +141 -0
- nat/tool/github_tools.py +450 -0
- nat/tool/memory_tools/__init__.py +0 -0
- nat/tool/memory_tools/add_memory_tool.py +79 -0
- nat/tool/memory_tools/delete_memory_tool.py +66 -0
- nat/tool/memory_tools/get_memory_tool.py +72 -0
- nat/tool/nvidia_rag.py +95 -0
- nat/tool/register.py +31 -0
- nat/tool/retriever.py +95 -0
- nat/tool/server_tools.py +66 -0
- nat/utils/__init__.py +0 -0
- nat/utils/callable_utils.py +70 -0
- nat/utils/data_models/__init__.py +0 -0
- nat/utils/data_models/schema_validator.py +58 -0
- nat/utils/debugging_utils.py +43 -0
- nat/utils/decorators.py +210 -0
- nat/utils/dump_distro_mapping.py +32 -0
- nat/utils/exception_handlers/__init__.py +0 -0
- nat/utils/exception_handlers/automatic_retries.py +342 -0
- nat/utils/exception_handlers/schemas.py +114 -0
- nat/utils/io/__init__.py +0 -0
- nat/utils/io/model_processing.py +28 -0
- nat/utils/io/yaml_tools.py +119 -0
- nat/utils/log_levels.py +25 -0
- nat/utils/log_utils.py +37 -0
- nat/utils/metadata_utils.py +74 -0
- nat/utils/optional_imports.py +142 -0
- nat/utils/producer_consumer_queue.py +178 -0
- nat/utils/reactive/__init__.py +0 -0
- nat/utils/reactive/base/__init__.py +0 -0
- nat/utils/reactive/base/observable_base.py +65 -0
- nat/utils/reactive/base/observer_base.py +55 -0
- nat/utils/reactive/base/subject_base.py +79 -0
- nat/utils/reactive/observable.py +59 -0
- nat/utils/reactive/observer.py +76 -0
- nat/utils/reactive/subject.py +131 -0
- nat/utils/reactive/subscription.py +49 -0
- nat/utils/settings/__init__.py +0 -0
- nat/utils/settings/global_settings.py +195 -0
- nat/utils/string_utils.py +38 -0
- nat/utils/type_converter.py +299 -0
- nat/utils/type_utils.py +488 -0
- nat/utils/url_utils.py +27 -0
- nvidia_nat-1.1.0a20251020.dist-info/METADATA +195 -0
- nvidia_nat-1.1.0a20251020.dist-info/RECORD +480 -0
- nvidia_nat-1.1.0a20251020.dist-info/WHEEL +5 -0
- nvidia_nat-1.1.0a20251020.dist-info/entry_points.txt +22 -0
- nvidia_nat-1.1.0a20251020.dist-info/licenses/LICENSE-3rd-party.txt +5478 -0
- nvidia_nat-1.1.0a20251020.dist-info/licenses/LICENSE.md +201 -0
- nvidia_nat-1.1.0a20251020.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,49 @@
|
|
|
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 nat.utils.reactive.base.subject_base import SubjectBase
|
|
23
|
+
|
|
24
|
+
_T = TypeVar("_T")
|
|
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
|
|
File without changes
|
|
@@ -0,0 +1,195 @@
|
|
|
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 nat.cli.type_registry import GlobalTypeRegistry
|
|
21
|
+
from nat.data_models.registry_handler import RegistryHandlerBaseConfig
|
|
22
|
+
from nat.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)
|
|
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)
|
|
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 NAT 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(channel_type=channel_type).config_type
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.exception("Invalid channel type: %s", e)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
while (True):
|
|
85
|
+
channel_name = input("Channel Name: ").strip()
|
|
86
|
+
if len(channel_name) < 1:
|
|
87
|
+
logger.warning("Invalid channel name, cannot be empty or whitespace.")
|
|
88
|
+
if (channel_name in settings.channels):
|
|
89
|
+
logger.warning("Channel name '%s' already exists, choose a different name.", channel_name)
|
|
90
|
+
else:
|
|
91
|
+
settings.channels[channel_name] = {}
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
ChannelConfigType = registry.get_registered_channel_info_by_channel_type(channel_type=channel_type).config_type
|
|
95
|
+
|
|
96
|
+
configure_registry_channel(config_type=ChannelConfigType, channel_name=channel_name)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_existing_channel_interactive(channel_name: str) -> tuple[str, bool]:
|
|
100
|
+
"""Retrieve an existing channel by configured name.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
channel_name (str): The name to use to reference the remote registry channel.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
tuple[str, bool]: A tuple containing the retrieved channel name and a boolean representing a
|
|
107
|
+
valid match was or was not successful.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
settings = GlobalSettings.get()
|
|
111
|
+
valid_channel = False
|
|
112
|
+
remote_channels = settings.channels
|
|
113
|
+
|
|
114
|
+
if (len(remote_channels) == 0):
|
|
115
|
+
logger.warning("No are configured channels to remove.")
|
|
116
|
+
return channel_name, valid_channel
|
|
117
|
+
|
|
118
|
+
while (not valid_channel):
|
|
119
|
+
|
|
120
|
+
if (channel_name not in remote_channels):
|
|
121
|
+
logger.warning("Channel name '%s' does not exist, choose a name from %s",
|
|
122
|
+
channel_name,
|
|
123
|
+
settings.channel_names)
|
|
124
|
+
channel_name = input("Channel Name: ").strip()
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
valid_channel = True
|
|
128
|
+
|
|
129
|
+
return channel_name, valid_channel
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def remove_channel(channel_name: str) -> None:
|
|
133
|
+
"""Remove a configured registry channel from the global settings.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
channel_name (str): The name to use to reference the remote registry channel.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
settings = GlobalSettings.get()
|
|
140
|
+
settings_dict = settings.model_dump(serialize_as_any=True, by_alias=True).copy()
|
|
141
|
+
settings_dict["channels"].pop(channel_name)
|
|
142
|
+
settings.update_settings(config_obj=settings_dict)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def remove_channel_interactive(channel_name: str) -> None:
|
|
146
|
+
channel_name, valid_channel = get_existing_channel_interactive(channel_name=channel_name)
|
|
147
|
+
if (not valid_channel):
|
|
148
|
+
return
|
|
149
|
+
remove_channel(channel_name=channel_name)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def match_valid_channel(channel_name: str) -> None:
|
|
153
|
+
"""Performs a match by registry channel to perform a channel configuration update.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
channel_name (str): The name to use to reference the remote registry channel.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
settings = GlobalSettings.get()
|
|
160
|
+
registry = GlobalTypeRegistry.get()
|
|
161
|
+
|
|
162
|
+
if len(settings.channel_names) == 0:
|
|
163
|
+
logger.warning("No channels have been configured, first add a channel.")
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
if (channel_name not in settings.channel_names):
|
|
167
|
+
logger.warning("Provided channel has not yet been configured, choose a different name "
|
|
168
|
+
"from %s .",
|
|
169
|
+
settings.channel_names)
|
|
170
|
+
while (True):
|
|
171
|
+
channel_name = input("Channel Name: ").strip()
|
|
172
|
+
if len(channel_name) < 1:
|
|
173
|
+
logger.warning("Invalid channel name, cannot be empty or whitespace.")
|
|
174
|
+
if (channel_name in settings.channel_names):
|
|
175
|
+
logger.warning("Channel name '%s' already exists, choose a different name.", channel_name)
|
|
176
|
+
else:
|
|
177
|
+
settings.channels[channel_name] = {}
|
|
178
|
+
break
|
|
179
|
+
|
|
180
|
+
channals_settings = settings.channels
|
|
181
|
+
channel_settings = channals_settings.get(channel_name)
|
|
182
|
+
ChannelConfigType = registry.get_registered_channel_info_by_channel_type(
|
|
183
|
+
channel_type=channel_settings.static_type()).config_type
|
|
184
|
+
|
|
185
|
+
configure_registry_channel(config_type=ChannelConfigType, channel_name=channel_name)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def update_channel_interactive(channel_name: str):
|
|
189
|
+
"""Launch an interactive session to update a configured channels settings.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
channel_name (str): The name to use to reference the remote registry channel.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
match_valid_channel(channel_name=channel_name)
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def convert_to_str(value: Any) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Convert a value to a string representation.
|
|
24
|
+
Handles various types including lists, dictionaries, and other objects.
|
|
25
|
+
"""
|
|
26
|
+
if isinstance(value, str):
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
if isinstance(value, list):
|
|
30
|
+
return ", ".join(map(str, value))
|
|
31
|
+
elif isinstance(value, BaseModel):
|
|
32
|
+
return value.model_dump_json(exclude_none=True, exclude_unset=True)
|
|
33
|
+
elif isinstance(value, dict):
|
|
34
|
+
return ", ".join(f"{k}: {v}" for k, v in value.items())
|
|
35
|
+
elif hasattr(value, '__str__'):
|
|
36
|
+
return str(value)
|
|
37
|
+
else:
|
|
38
|
+
raise ValueError(f"Unsupported type for conversion to string: {type(value)}")
|
|
@@ -0,0 +1,299 @@
|
|
|
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 nat.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
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
converters : list[Callable[[typing.Any], typing.Any]]
|
|
41
|
+
A list of single-argument converter callables annotated with their input param and return type.
|
|
42
|
+
parent : TypeConverter | None
|
|
43
|
+
An optional parent TypeConverter for fallback.
|
|
44
|
+
"""
|
|
45
|
+
# dict[to_type, dict[from_type, converter]]
|
|
46
|
+
self._converters: OrderedDict[type, OrderedDict[type, Callable]] = OrderedDict()
|
|
47
|
+
self._indirect_warnings_shown: set[tuple[type, type]] = set()
|
|
48
|
+
|
|
49
|
+
for converter in converters:
|
|
50
|
+
self.add_converter(converter)
|
|
51
|
+
|
|
52
|
+
if parent is None and TypeConverter._global_initialized:
|
|
53
|
+
parent = GlobalTypeConverter.get()
|
|
54
|
+
self._parent = parent
|
|
55
|
+
|
|
56
|
+
def add_converter(self, converter: Callable) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Registers a converter. Must have exactly one parameter
|
|
59
|
+
and an annotated return type.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
converter : Callable
|
|
64
|
+
A converter function. Must have exactly one parameter and an annotated return type.
|
|
65
|
+
|
|
66
|
+
Raises
|
|
67
|
+
------
|
|
68
|
+
ValueError
|
|
69
|
+
If the converter does not have a return type or exactly one argument or the argument has no data type.
|
|
70
|
+
"""
|
|
71
|
+
sig = typing.get_type_hints(converter)
|
|
72
|
+
to_type = sig.pop("return", None)
|
|
73
|
+
if to_type is None:
|
|
74
|
+
raise ValueError("Converter must have a return type.")
|
|
75
|
+
|
|
76
|
+
if len(sig) != 1:
|
|
77
|
+
raise ValueError("Converter must have exactly one argument.")
|
|
78
|
+
|
|
79
|
+
from_type = next(iter(sig.values()))
|
|
80
|
+
if from_type is None:
|
|
81
|
+
raise ValueError("Converter's argument must have a data type.")
|
|
82
|
+
|
|
83
|
+
self._converters.setdefault(to_type, OrderedDict())[from_type] = converter
|
|
84
|
+
# to do(MDD): If needed, sort by specificity here.
|
|
85
|
+
|
|
86
|
+
def _convert(self, data: typing.Any, to_type: type[_T]) -> _T | None:
|
|
87
|
+
"""
|
|
88
|
+
Attempts to convert `data` into `to_type`. Returns None if no path is found.
|
|
89
|
+
"""
|
|
90
|
+
decomposed = DecomposedType(to_type)
|
|
91
|
+
|
|
92
|
+
# 1) If data is already correct type, return it
|
|
93
|
+
if to_type is None or decomposed.is_instance(data):
|
|
94
|
+
return data
|
|
95
|
+
|
|
96
|
+
# 2) If data is a union type, try to convert to each type in the union
|
|
97
|
+
if decomposed.is_union:
|
|
98
|
+
for union_type in decomposed.args:
|
|
99
|
+
result = self._convert(data, union_type)
|
|
100
|
+
if result is not None:
|
|
101
|
+
return result
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
root = decomposed.root
|
|
105
|
+
|
|
106
|
+
# 2) Attempt direct in *this* converter
|
|
107
|
+
direct_result = self._try_direct_conversion(data, root)
|
|
108
|
+
if direct_result is not None:
|
|
109
|
+
return direct_result
|
|
110
|
+
|
|
111
|
+
# 3) If direct fails entirely, do indirect in *this* converter
|
|
112
|
+
indirect_result = self._try_indirect_convert(data, to_type)
|
|
113
|
+
if indirect_result is not None:
|
|
114
|
+
return indirect_result
|
|
115
|
+
|
|
116
|
+
# 4) If we still haven't succeeded, return None
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
def convert(self, data: typing.Any, to_type: type[_T]) -> _T:
|
|
120
|
+
"""
|
|
121
|
+
Converts or raises ValueError if no conversion path is found.
|
|
122
|
+
We also give the parent a chance if self fails.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
data : typing.Any
|
|
127
|
+
The value to convert.
|
|
128
|
+
to_type : type
|
|
129
|
+
The type to convert the value to.
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
_T
|
|
134
|
+
The converted value.
|
|
135
|
+
|
|
136
|
+
Raises
|
|
137
|
+
------
|
|
138
|
+
ValueError
|
|
139
|
+
If the value cannot be converted to the specified type.
|
|
140
|
+
"""
|
|
141
|
+
result = self._convert(data, to_type)
|
|
142
|
+
if result is None and self._parent:
|
|
143
|
+
# fallback on parent entirely
|
|
144
|
+
return self._parent.convert(data, to_type)
|
|
145
|
+
|
|
146
|
+
if result is not None:
|
|
147
|
+
return result
|
|
148
|
+
raise ValueError(f"Cannot convert type {type(data)} to {to_type}. No match found.")
|
|
149
|
+
|
|
150
|
+
def try_convert(self, data: typing.Any, to_type: type[_T]) -> _T | typing.Any:
|
|
151
|
+
"""
|
|
152
|
+
Converts with graceful error handling. If conversion fails, returns the original data
|
|
153
|
+
and continues processing.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
data : typing.Any
|
|
158
|
+
The value to convert.
|
|
159
|
+
to_type : type
|
|
160
|
+
The type to convert the value to.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
_T | typing.Any
|
|
165
|
+
The converted value, or original value if conversion fails.
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
return self.convert(data, to_type)
|
|
169
|
+
except ValueError:
|
|
170
|
+
logger.warning("Type conversion failed, using original value. From %s to %s", type(data), to_type)
|
|
171
|
+
# Return original data, let downstream code handle it
|
|
172
|
+
return data
|
|
173
|
+
|
|
174
|
+
# -------------------------------------------------
|
|
175
|
+
# INTERNAL DIRECT CONVERSION (with parent fallback)
|
|
176
|
+
# -------------------------------------------------
|
|
177
|
+
def _try_direct_conversion(self, data: typing.Any, target_root_type: type) -> typing.Any | None:
|
|
178
|
+
"""
|
|
179
|
+
Tries direct conversion in *this* converter's registry.
|
|
180
|
+
If no match here, we forward to parent's direct conversion
|
|
181
|
+
for recursion up the chain.
|
|
182
|
+
"""
|
|
183
|
+
for convert_to_type, to_type_converters in self._converters.items():
|
|
184
|
+
# e.g. if Derived is a subclass of Base, this is valid
|
|
185
|
+
if issubclass(DecomposedType(convert_to_type).root, target_root_type):
|
|
186
|
+
for convert_from_type, from_type_converter in to_type_converters.items():
|
|
187
|
+
if isinstance(data, DecomposedType(convert_from_type).root):
|
|
188
|
+
try:
|
|
189
|
+
return from_type_converter(data)
|
|
190
|
+
except ConvertException:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
# If we can't convert directly here, try parent
|
|
194
|
+
if self._parent is not None:
|
|
195
|
+
return self._parent._try_direct_conversion(data, target_root_type)
|
|
196
|
+
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
# -------------------------------------------------
|
|
200
|
+
# INTERNAL INDIRECT CONVERSION (with parent fallback)
|
|
201
|
+
# -------------------------------------------------
|
|
202
|
+
def _try_indirect_convert(self, data: typing.Any, to_type: type[_T]) -> _T | None:
|
|
203
|
+
"""
|
|
204
|
+
Attempt indirect conversion (DFS) in *this* converter.
|
|
205
|
+
If no success, fallback to parent's indirect attempt.
|
|
206
|
+
"""
|
|
207
|
+
visited = set()
|
|
208
|
+
final = self._try_indirect_conversion(data, to_type, visited)
|
|
209
|
+
src_type = type(data)
|
|
210
|
+
if final is not None:
|
|
211
|
+
# Warn once if found a chain
|
|
212
|
+
self._maybe_warn_indirect(src_type, to_type)
|
|
213
|
+
return final
|
|
214
|
+
|
|
215
|
+
# If no success, try parent's indirect
|
|
216
|
+
if self._parent is not None:
|
|
217
|
+
parent_final = self._parent._try_indirect_convert(data, to_type)
|
|
218
|
+
if parent_final is not None:
|
|
219
|
+
self._maybe_warn_indirect(src_type, to_type)
|
|
220
|
+
return parent_final
|
|
221
|
+
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
def _try_indirect_conversion(self, data: typing.Any, to_type: type[_T], visited: set[type]) -> _T | None:
|
|
225
|
+
"""
|
|
226
|
+
DFS attempt to find a chain of conversions from type(data) to to_type,
|
|
227
|
+
ignoring parent. If not found, returns None.
|
|
228
|
+
"""
|
|
229
|
+
# 1) If data is already correct type
|
|
230
|
+
if isinstance(data, to_type):
|
|
231
|
+
return data
|
|
232
|
+
|
|
233
|
+
current_type = type(data)
|
|
234
|
+
if current_type in visited:
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
visited.add(current_type)
|
|
238
|
+
|
|
239
|
+
# 2) Attempt each known converter from current_type -> ???, then recurse
|
|
240
|
+
for _, to_type_converters in self._converters.items():
|
|
241
|
+
for convert_from_type, from_type_converter in to_type_converters.items():
|
|
242
|
+
if isinstance(data, convert_from_type):
|
|
243
|
+
try:
|
|
244
|
+
next_data = from_type_converter(data)
|
|
245
|
+
if isinstance(next_data, to_type):
|
|
246
|
+
return next_data
|
|
247
|
+
# else keep going
|
|
248
|
+
deeper = self._try_indirect_conversion(next_data, to_type, visited)
|
|
249
|
+
if deeper is not None:
|
|
250
|
+
return deeper
|
|
251
|
+
except ConvertException:
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
def _maybe_warn_indirect(self, source_type: type, to_type: type):
|
|
257
|
+
"""
|
|
258
|
+
Warn once if an indirect path was used between these two types.
|
|
259
|
+
"""
|
|
260
|
+
pair = (source_type, to_type)
|
|
261
|
+
if pair not in self._indirect_warnings_shown:
|
|
262
|
+
logger.warning(
|
|
263
|
+
"Indirect type conversion used to convert %s to %s, which may lead to unintended conversions. "
|
|
264
|
+
"Consider adding a direct converter from %s to %s to ensure correctness.",
|
|
265
|
+
source_type,
|
|
266
|
+
to_type,
|
|
267
|
+
source_type,
|
|
268
|
+
to_type)
|
|
269
|
+
self._indirect_warnings_shown.add(pair)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class GlobalTypeConverter:
|
|
273
|
+
_global_converter: TypeConverter = TypeConverter([])
|
|
274
|
+
|
|
275
|
+
@staticmethod
|
|
276
|
+
def get() -> TypeConverter:
|
|
277
|
+
return GlobalTypeConverter._global_converter
|
|
278
|
+
|
|
279
|
+
@staticmethod
|
|
280
|
+
def register_converter(converter: Callable) -> None:
|
|
281
|
+
GlobalTypeConverter._global_converter.add_converter(converter)
|
|
282
|
+
|
|
283
|
+
@staticmethod
|
|
284
|
+
def convert(data, to_type: type[_T]) -> _T:
|
|
285
|
+
return GlobalTypeConverter._global_converter.convert(data, to_type)
|
|
286
|
+
|
|
287
|
+
@staticmethod
|
|
288
|
+
def try_convert(data: typing.Any, to_type: type[_T]) -> _T | typing.Any:
|
|
289
|
+
return GlobalTypeConverter._global_converter.try_convert(data, to_type)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
TypeConverter._global_initialized = True
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _text_io_wrapper_to_string(data: TextIOWrapper) -> str:
|
|
296
|
+
return data.read()
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
GlobalTypeConverter.register_converter(_text_io_wrapper_to_string)
|