nvidia-nat 1.2.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.
- aiq/__init__.py +66 -0
- nat/agent/__init__.py +0 -0
- nat/agent/base.py +256 -0
- nat/agent/dual_node.py +67 -0
- nat/agent/react_agent/__init__.py +0 -0
- nat/agent/react_agent/agent.py +363 -0
- nat/agent/react_agent/output_parser.py +104 -0
- nat/agent/react_agent/prompt.py +44 -0
- nat/agent/react_agent/register.py +149 -0
- nat/agent/reasoning_agent/__init__.py +0 -0
- nat/agent/reasoning_agent/reasoning_agent.py +225 -0
- nat/agent/register.py +23 -0
- nat/agent/rewoo_agent/__init__.py +0 -0
- nat/agent/rewoo_agent/agent.py +415 -0
- nat/agent/rewoo_agent/prompt.py +110 -0
- nat/agent/rewoo_agent/register.py +157 -0
- nat/agent/tool_calling_agent/__init__.py +0 -0
- nat/agent/tool_calling_agent/agent.py +119 -0
- nat/agent/tool_calling_agent/register.py +106 -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/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 +93 -0
- nat/authentication/oauth2/__init__.py +14 -0
- nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +107 -0
- nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +39 -0
- nat/authentication/oauth2/register.py +25 -0
- nat/authentication/register.py +21 -0
- nat/builder/__init__.py +0 -0
- nat/builder/builder.py +285 -0
- nat/builder/component_utils.py +316 -0
- nat/builder/context.py +270 -0
- nat/builder/embedder.py +24 -0
- nat/builder/eval_builder.py +161 -0
- nat/builder/evaluator.py +29 -0
- nat/builder/framework_enum.py +24 -0
- nat/builder/front_end.py +73 -0
- nat/builder/function.py +344 -0
- nat/builder/function_base.py +380 -0
- nat/builder/function_info.py +627 -0
- nat/builder/intermediate_step_manager.py +174 -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 +148 -0
- nat/builder/workflow_builder.py +1117 -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 +37 -0
- nat/cli/commands/info/list_channels.py +32 -0
- nat/cli/commands/info/list_components.py +129 -0
- nat/cli/commands/info/list_mcp.py +304 -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 +155 -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 +246 -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 +16 -0
- nat/cli/commands/workflow/templates/pyproject.toml.j2 +22 -0
- nat/cli/commands/workflow/templates/register.py.j2 +5 -0
- nat/cli/commands/workflow/templates/workflow.py.j2 +36 -0
- nat/cli/commands/workflow/workflow.py +37 -0
- nat/cli/commands/workflow/workflow_commands.py +317 -0
- nat/cli/entrypoint.py +135 -0
- nat/cli/main.py +57 -0
- nat/cli/register_workflow.py +488 -0
- nat/cli/type_registry.py +1000 -0
- nat/data_models/__init__.py +14 -0
- nat/data_models/api_server.py +716 -0
- nat/data_models/authentication.py +231 -0
- nat/data_models/common.py +171 -0
- nat/data_models/component.py +58 -0
- nat/data_models/component_ref.py +168 -0
- nat/data_models/config.py +410 -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 +127 -0
- nat/data_models/evaluator.py +26 -0
- nat/data_models/front_end.py +26 -0
- nat/data_models/function.py +30 -0
- nat/data_models/function_dependencies.py +72 -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/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 +190 -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/ttc_strategy.py +30 -0
- nat/embedder/__init__.py +0 -0
- nat/embedder/nim_embedder.py +59 -0
- nat/embedder/openai_embedder.py +43 -0
- nat/embedder/register.py +22 -0
- nat/eval/__init__.py +14 -0
- nat/eval/config.py +60 -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 +367 -0
- nat/eval/evaluate.py +510 -0
- nat/eval/evaluator/__init__.py +14 -0
- nat/eval/evaluator/base_evaluator.py +77 -0
- nat/eval/evaluator/evaluator_model.py +45 -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 +23 -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_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 +245 -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/output_uploader.py +140 -0
- nat/eval/utils/tqdm_position_registry.py +40 -0
- nat/eval/utils/weave_eval.py +184 -0
- nat/experimental/__init__.py +0 -0
- nat/experimental/decorators/__init__.py +0 -0
- nat/experimental/decorators/experimental_warning_decorator.py +134 -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 +224 -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 +66 -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 +36 -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 +159 -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 +233 -0
- nat/front_ends/console/console_front_end_config.py +32 -0
- nat/front_ends/console/console_front_end_plugin.py +96 -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 +107 -0
- nat/front_ends/fastapi/fastapi_front_end_config.py +241 -0
- nat/front_ends/fastapi/fastapi_front_end_controller.py +68 -0
- nat/front_ends/fastapi/fastapi_front_end_plugin.py +116 -0
- nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +1087 -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 +183 -0
- nat/front_ends/fastapi/main.py +72 -0
- nat/front_ends/fastapi/message_handler.py +320 -0
- nat/front_ends/fastapi/message_validator.py +352 -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/mcp/__init__.py +14 -0
- nat/front_ends/mcp/mcp_front_end_config.py +36 -0
- nat/front_ends/mcp/mcp_front_end_plugin.py +81 -0
- nat/front_ends/mcp/mcp_front_end_plugin_worker.py +143 -0
- nat/front_ends/mcp/register.py +27 -0
- nat/front_ends/mcp/tool_converter.py +241 -0
- nat/front_ends/register.py +22 -0
- nat/front_ends/simple_base/__init__.py +14 -0
- nat/front_ends/simple_base/simple_front_end_plugin_base.py +54 -0
- nat/llm/__init__.py +0 -0
- nat/llm/aws_bedrock_llm.py +57 -0
- nat/llm/nim_llm.py +46 -0
- nat/llm/openai_llm.py +46 -0
- nat/llm/register.py +23 -0
- nat/llm/utils/__init__.py +14 -0
- nat/llm/utils/env_config_value.py +94 -0
- nat/llm/utils/error.py +17 -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 +20 -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 +322 -0
- nat/observability/exporter/raw_exporter.py +52 -0
- nat/observability/exporter/span_exporter.py +288 -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/resource_conflict_mixin.py +134 -0
- nat/observability/mixin/serialize_mixin.py +61 -0
- nat/observability/mixin/type_introspection_mixin.py +183 -0
- nat/observability/processor/__init__.py +14 -0
- nat/observability/processor/batching_processor.py +310 -0
- nat/observability/processor/callback_processor.py +42 -0
- nat/observability/processor/intermediate_step_serializer.py +28 -0
- nat/observability/processor/processor.py +71 -0
- nat/observability/register.py +96 -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 +627 -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 +290 -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 +131 -0
- nat/profiler/decorators/function_tracking.py +254 -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 +40 -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 +405 -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/profile_runner.py +473 -0
- nat/profiler/utils.py +184 -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 +571 -0
- nat/registry_handlers/pypi/__init__.py +0 -0
- nat/registry_handlers/pypi/pypi_handler.py +251 -0
- nat/registry_handlers/pypi/register_pypi.py +40 -0
- nat/registry_handlers/register.py +21 -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 +237 -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 +22 -0
- nat/runtime/__init__.py +14 -0
- nat/runtime/loader.py +220 -0
- nat/runtime/runner.py +195 -0
- nat/runtime/session.py +162 -0
- nat/runtime/user_metadata.py +130 -0
- nat/settings/__init__.py +0 -0
- nat/settings/global_settings.py +318 -0
- nat/test/.namespace +1 -0
- nat/tool/__init__.py +0 -0
- nat/tool/chat_completion.py +74 -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 +42 -0
- nat/tool/document_search.py +141 -0
- nat/tool/github_tools/__init__.py +0 -0
- nat/tool/github_tools/create_github_commit.py +133 -0
- nat/tool/github_tools/create_github_issue.py +87 -0
- nat/tool/github_tools/create_github_pr.py +106 -0
- nat/tool/github_tools/get_github_file.py +106 -0
- nat/tool/github_tools/get_github_issue.py +166 -0
- nat/tool/github_tools/get_github_pr.py +256 -0
- nat/tool/github_tools/update_github_issue.py +100 -0
- nat/tool/mcp/__init__.py +14 -0
- nat/tool/mcp/exceptions.py +142 -0
- nat/tool/mcp/mcp_client.py +255 -0
- nat/tool/mcp/mcp_tool.py +96 -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 +67 -0
- nat/tool/memory_tools/get_memory_tool.py +72 -0
- nat/tool/nvidia_rag.py +95 -0
- nat/tool/register.py +38 -0
- nat/tool/retriever.py +94 -0
- nat/tool/server_tools.py +66 -0
- nat/utils/__init__.py +0 -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/dump_distro_mapping.py +32 -0
- nat/utils/exception_handlers/__init__.py +0 -0
- nat/utils/exception_handlers/automatic_retries.py +289 -0
- nat/utils/exception_handlers/mcp.py +211 -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_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 +197 -0
- nat/utils/string_utils.py +38 -0
- nat/utils/type_converter.py +290 -0
- nat/utils/type_utils.py +484 -0
- nat/utils/url_utils.py +27 -0
- nvidia_nat-1.2.0.dist-info/METADATA +365 -0
- nvidia_nat-1.2.0.dist-info/RECORD +435 -0
- nvidia_nat-1.2.0.dist-info/WHEEL +5 -0
- nvidia_nat-1.2.0.dist-info/entry_points.txt +21 -0
- nvidia_nat-1.2.0.dist-info/licenses/LICENSE-3rd-party.txt +5478 -0
- nvidia_nat-1.2.0.dist-info/licenses/LICENSE.md +201 -0
- nvidia_nat-1.2.0.dist-info/top_level.txt +2 -0
nat/utils/type_utils.py
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
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 collections
|
|
17
|
+
import collections.abc
|
|
18
|
+
import inspect
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import types
|
|
23
|
+
import typing
|
|
24
|
+
from functools import lru_cache
|
|
25
|
+
from typing import TypeAlias
|
|
26
|
+
|
|
27
|
+
from pydantic import BaseModel
|
|
28
|
+
from pydantic import Field
|
|
29
|
+
from pydantic import create_model
|
|
30
|
+
from pydantic_core import PydanticUndefined
|
|
31
|
+
|
|
32
|
+
# Mimic the `StrPath` type alias from the `typeshed` package. We can't import it directly because it's not available at
|
|
33
|
+
# runtime and causes problems
|
|
34
|
+
StrPath: TypeAlias = str | os.PathLike[str]
|
|
35
|
+
|
|
36
|
+
ClassInfo: TypeAlias = type | types.UnionType | tuple["ClassInfo", ...]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# utility for check if string is a valid json string
|
|
40
|
+
def is_valid_json(string):
|
|
41
|
+
try:
|
|
42
|
+
input_str = string.replace("'", "\"")
|
|
43
|
+
json.loads(input_str)
|
|
44
|
+
return True
|
|
45
|
+
except json.JSONDecodeError:
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# A compatibility layer for typing.override decorator.
|
|
50
|
+
# In Python >= 3.12, it uses the built-in typing.override decorator
|
|
51
|
+
# In Python < 3.12, it acts as a no-op decorator
|
|
52
|
+
if sys.version_info >= (3, 12):
|
|
53
|
+
from typing import override # pylint: disable=unused-import
|
|
54
|
+
else:
|
|
55
|
+
|
|
56
|
+
def override(func):
|
|
57
|
+
return func
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class DecomposedType:
|
|
61
|
+
|
|
62
|
+
def __init__(self, original: type):
|
|
63
|
+
|
|
64
|
+
if (inspect.Signature.empty == original):
|
|
65
|
+
original = types.NoneType
|
|
66
|
+
|
|
67
|
+
self.type = original
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
@lru_cache
|
|
71
|
+
def origin(self):
|
|
72
|
+
"""
|
|
73
|
+
Get the origin of the current type using `typing.get_origin`. For example, if the current type is `list[int]`,
|
|
74
|
+
the origin would be `list`.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
type
|
|
79
|
+
The origin of the current type.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
return typing.get_origin(self.type)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
@lru_cache
|
|
86
|
+
def args(self):
|
|
87
|
+
"""
|
|
88
|
+
Get the arguments of the current type using `typing.get_args`. For example, if the current type is `list[int,
|
|
89
|
+
str]`, the arguments would be `[int, str]`.
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
tuple[type]
|
|
94
|
+
The arguments of the current type.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
return typing.get_args(self.type)
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
@lru_cache
|
|
101
|
+
def root(self):
|
|
102
|
+
"""
|
|
103
|
+
Get the root type of the current type. This is the type without any annotations or async generators.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
type
|
|
108
|
+
The root type of the current type.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
return self.origin if self.origin is not None else self.type
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
@lru_cache
|
|
115
|
+
def is_empty(self):
|
|
116
|
+
"""
|
|
117
|
+
Check if the current type is eqivalent to `NoneType`.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
bool
|
|
122
|
+
True if the current type is `NoneType`, False otherwise.
|
|
123
|
+
"""
|
|
124
|
+
return self.type is types.NoneType
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
@lru_cache
|
|
128
|
+
def is_class(self):
|
|
129
|
+
"""
|
|
130
|
+
Check if the current type is a class using `inspect.isclass`. For example, `list[int]` would return False, but
|
|
131
|
+
`list` would return True.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
bool
|
|
136
|
+
True if the current type is a class, False otherwise.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
return inspect.isclass(self.type)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
@lru_cache
|
|
143
|
+
def is_generic(self):
|
|
144
|
+
"""
|
|
145
|
+
Check if the current type is a generic using `typing.GenericMeta`. For example, `list[int]` would return True,
|
|
146
|
+
but `list` would return False.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
bool
|
|
151
|
+
True if the current type is a generic, False otherwise.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
return self.origin is not None
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
@lru_cache
|
|
158
|
+
def is_annotated(self):
|
|
159
|
+
"""
|
|
160
|
+
Check if the current type is an annotated type using `typing.Annotated`. For example, `Annotated[int, str]`
|
|
161
|
+
would return True, but `int` would return False.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
bool
|
|
166
|
+
True if the current type is an annotated type, False otherwise.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
return self.origin is typing.Annotated
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
@lru_cache
|
|
173
|
+
def is_union(self):
|
|
174
|
+
"""
|
|
175
|
+
Check if the current type is a union type using `typing.Union`. For example, `Union[int, str]` would return
|
|
176
|
+
True, but `int` would return False.
|
|
177
|
+
|
|
178
|
+
Returns
|
|
179
|
+
-------
|
|
180
|
+
bool
|
|
181
|
+
True if the current type is a union type, False otherwise.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
return self.origin in (typing.Union, types.UnionType) # pylint: disable=consider-alternative-union-syntax
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
@lru_cache
|
|
188
|
+
def is_async_generator(self):
|
|
189
|
+
"""
|
|
190
|
+
Check if the current type is an async generator type. For example, `AsyncGenerator[int]` would return True,
|
|
191
|
+
but `int` would return False.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
bool
|
|
196
|
+
True if the current type is an async generator type, False otherwise.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
return self.origin in (
|
|
200
|
+
typing.AsyncGenerator, # pylint: disable=consider-alternative-union-syntax,deprecated-typing-alias
|
|
201
|
+
collections.abc.AsyncGenerator,
|
|
202
|
+
types.AsyncGeneratorType,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
@lru_cache
|
|
207
|
+
def is_optional(self):
|
|
208
|
+
"""
|
|
209
|
+
Check if the current type is an optional type. For example, `Optional[int]` and `int | None` would return True,
|
|
210
|
+
but `int` would return False.
|
|
211
|
+
|
|
212
|
+
Returns
|
|
213
|
+
-------
|
|
214
|
+
bool
|
|
215
|
+
True if the current type is an optional type, False otherwise.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
return self.is_union and types.NoneType in self.args
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
@lru_cache
|
|
222
|
+
def has_base_type(self):
|
|
223
|
+
"""
|
|
224
|
+
Check if the current type has a base type, ignoring any annotations or async generators.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
return self.is_annotated or self.is_async_generator
|
|
228
|
+
|
|
229
|
+
def get_optional_type(self) -> "DecomposedType":
|
|
230
|
+
"""
|
|
231
|
+
If the current type is optional, return the type that is not `NoneType`. If the current type is not optional,
|
|
232
|
+
raise a `ValueError`.
|
|
233
|
+
|
|
234
|
+
Returns
|
|
235
|
+
-------
|
|
236
|
+
DecomposedType
|
|
237
|
+
The optional type that is not `NoneType`.
|
|
238
|
+
|
|
239
|
+
Raises
|
|
240
|
+
------
|
|
241
|
+
ValueError
|
|
242
|
+
If the current type is not optional.
|
|
243
|
+
ValueError
|
|
244
|
+
If the current type is optional but has more than one argument that is not `NoneType`.
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
if (not self.is_optional):
|
|
248
|
+
raise ValueError(f"Type {self.type} is not optional.")
|
|
249
|
+
|
|
250
|
+
remaining_args = tuple(arg for arg in self.args if arg is not types.NoneType)
|
|
251
|
+
|
|
252
|
+
if (len(remaining_args) > 1):
|
|
253
|
+
return DecomposedType(typing.Union[remaining_args]) # pylint: disable=consider-alternative-union-syntax
|
|
254
|
+
if (len(remaining_args) == 1):
|
|
255
|
+
return DecomposedType(remaining_args[0])
|
|
256
|
+
|
|
257
|
+
raise ValueError(f"Type {self.type} is not optional.")
|
|
258
|
+
|
|
259
|
+
def get_annotated_type(self) -> "DecomposedType":
|
|
260
|
+
"""
|
|
261
|
+
If the current type is annotated, return the annotated type. If the current type is not annotated, raise a
|
|
262
|
+
`ValueError`.
|
|
263
|
+
|
|
264
|
+
Returns
|
|
265
|
+
-------
|
|
266
|
+
DecomposedType
|
|
267
|
+
The annotated type.
|
|
268
|
+
|
|
269
|
+
Raises
|
|
270
|
+
------
|
|
271
|
+
ValueError
|
|
272
|
+
If the current type is not annotated.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
if (not self.is_annotated):
|
|
276
|
+
raise ValueError(f"Type {self.type} is not annotated.")
|
|
277
|
+
|
|
278
|
+
return DecomposedType(self.args[0])
|
|
279
|
+
|
|
280
|
+
def get_async_generator_type(self) -> "DecomposedType":
|
|
281
|
+
"""
|
|
282
|
+
If the current type is an async generator, return the async generator type. If the current type is not an async
|
|
283
|
+
generator, raise a `ValueError`.
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
DecomposedType
|
|
288
|
+
The async generator type.
|
|
289
|
+
|
|
290
|
+
Raises
|
|
291
|
+
------
|
|
292
|
+
ValueError
|
|
293
|
+
If the current type is not an async generator.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
if (not self.is_async_generator):
|
|
297
|
+
raise ValueError(f"Type {self.type} is not an async generator.")
|
|
298
|
+
|
|
299
|
+
return DecomposedType(self.args[0])
|
|
300
|
+
|
|
301
|
+
def get_base_type(self) -> "DecomposedType":
|
|
302
|
+
"""
|
|
303
|
+
Returns the base type of the current type, ignoring any annotations or async generators.
|
|
304
|
+
|
|
305
|
+
Returns
|
|
306
|
+
-------
|
|
307
|
+
DecomposedType
|
|
308
|
+
The base type of the current type.
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
base_type = self
|
|
312
|
+
|
|
313
|
+
while (base_type.has_base_type):
|
|
314
|
+
if (base_type.is_annotated):
|
|
315
|
+
base_type = base_type.get_annotated_type()
|
|
316
|
+
elif (base_type.is_async_generator):
|
|
317
|
+
base_type = base_type.get_async_generator_type()
|
|
318
|
+
|
|
319
|
+
return base_type
|
|
320
|
+
|
|
321
|
+
def is_subtype(self, class_or_tuple: ClassInfo) -> bool:
|
|
322
|
+
"""
|
|
323
|
+
Check if the current type is a subtype of the specified class or tuple of classes similar to `issubclass`.
|
|
324
|
+
|
|
325
|
+
Parameters
|
|
326
|
+
----------
|
|
327
|
+
class_or_tuple : ClassInfo
|
|
328
|
+
The class or tuple of classes to check if the current type is a subtype of.
|
|
329
|
+
|
|
330
|
+
Returns
|
|
331
|
+
-------
|
|
332
|
+
bool
|
|
333
|
+
True if the current type is a subtype of the specified class or tuple of classes, False otherwise
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
if (isinstance(class_or_tuple, tuple)):
|
|
337
|
+
return any(issubclass(self.root, DecomposedType(cls).root) for cls in class_or_tuple)
|
|
338
|
+
|
|
339
|
+
return issubclass(self.root, DecomposedType(class_or_tuple).root)
|
|
340
|
+
|
|
341
|
+
def is_instance(self, instance: typing.Any) -> bool:
|
|
342
|
+
"""
|
|
343
|
+
Check if the current type is an instance of the specified instance similar to `isinstance`.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
instance : typing.Any
|
|
348
|
+
The instance to check if the current type is an instance of.
|
|
349
|
+
|
|
350
|
+
Returns
|
|
351
|
+
-------
|
|
352
|
+
bool
|
|
353
|
+
True if the current type is an instance of the specified instance, False otherwise
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
return isinstance(instance, self.root)
|
|
357
|
+
|
|
358
|
+
def get_pydantic_schema(self,
|
|
359
|
+
converters: list[collections.abc.Callable] | None = None) -> type[BaseModel] | type[None]:
|
|
360
|
+
"""
|
|
361
|
+
Get the Pydantic schema for the current type.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
converters : list[Callable], optional
|
|
366
|
+
A list of converters to append new converts to, by default None
|
|
367
|
+
|
|
368
|
+
Returns
|
|
369
|
+
-------
|
|
370
|
+
type[BaseModel]
|
|
371
|
+
The Pydantic schema for the current type.
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
if (converters is None):
|
|
375
|
+
converters = []
|
|
376
|
+
|
|
377
|
+
if (self.has_base_type):
|
|
378
|
+
return self.get_base_type().get_pydantic_schema(converters=converters)
|
|
379
|
+
|
|
380
|
+
if (self.type == types.NoneType):
|
|
381
|
+
return types.NoneType
|
|
382
|
+
|
|
383
|
+
if (self.is_class and issubclass(self.type, BaseModel)):
|
|
384
|
+
return self.type
|
|
385
|
+
|
|
386
|
+
schema = create_model("OutputArgsSchema", value=(self.type, Field(default=PydanticUndefined)))
|
|
387
|
+
|
|
388
|
+
def _convert_to_cls(schema_in: schema) -> self.type:
|
|
389
|
+
return schema_in.value
|
|
390
|
+
|
|
391
|
+
def _convert_to_schema(cls_in: self.type) -> schema:
|
|
392
|
+
return schema.model_validate({"value": cls_in})
|
|
393
|
+
|
|
394
|
+
converters.append(_convert_to_cls)
|
|
395
|
+
converters.append(_convert_to_schema)
|
|
396
|
+
|
|
397
|
+
return schema
|
|
398
|
+
|
|
399
|
+
@staticmethod
|
|
400
|
+
def extract_generic_parameters_from_class(target_class: type,
|
|
401
|
+
expected_param_count: int | None = None) -> tuple[type, ...]:
|
|
402
|
+
"""
|
|
403
|
+
Extract generic type parameters from a class's inheritance chain.
|
|
404
|
+
|
|
405
|
+
This method searches through __orig_bases__ to find generic parameters,
|
|
406
|
+
which is useful for classes that inherit from generic base classes.
|
|
407
|
+
|
|
408
|
+
Parameters
|
|
409
|
+
----------
|
|
410
|
+
target_class : type
|
|
411
|
+
The class to extract parameters from
|
|
412
|
+
expected_param_count : int | None, optional
|
|
413
|
+
Expected number of parameters. If specified, only matches with this count are considered.
|
|
414
|
+
|
|
415
|
+
Returns
|
|
416
|
+
-------
|
|
417
|
+
tuple[type, ...]
|
|
418
|
+
Tuple of generic type parameters found
|
|
419
|
+
|
|
420
|
+
Raises
|
|
421
|
+
------
|
|
422
|
+
ValueError
|
|
423
|
+
If no generic parameters matching the expected count are found
|
|
424
|
+
|
|
425
|
+
Examples
|
|
426
|
+
--------
|
|
427
|
+
>>> class MyClass(SomeGeneric[int, str, bool]):
|
|
428
|
+
... pass
|
|
429
|
+
>>> DecomposedType.extract_generic_parameters_from_class(MyClass, 3)
|
|
430
|
+
(int, str, bool)
|
|
431
|
+
"""
|
|
432
|
+
for base_cls in getattr(target_class, '__orig_bases__', []):
|
|
433
|
+
base_cls_args = typing.get_args(base_cls)
|
|
434
|
+
|
|
435
|
+
if expected_param_count is None or len(base_cls_args) == expected_param_count:
|
|
436
|
+
if base_cls_args: # Only return if we actually found parameters
|
|
437
|
+
return base_cls_args
|
|
438
|
+
|
|
439
|
+
if expected_param_count is not None:
|
|
440
|
+
raise ValueError(
|
|
441
|
+
f"Could not find generic parameters with count {expected_param_count} for class {target_class}")
|
|
442
|
+
raise ValueError(f"Could not find any generic parameters for class {target_class}")
|
|
443
|
+
|
|
444
|
+
@staticmethod
|
|
445
|
+
def is_type_compatible(source_type: type, target_type: type) -> bool:
|
|
446
|
+
"""
|
|
447
|
+
Check if a source type is compatible with a target type.
|
|
448
|
+
|
|
449
|
+
This handles direct compatibility and special cases like batch compatibility
|
|
450
|
+
where list[T] can be compatible with targets that expect T.
|
|
451
|
+
|
|
452
|
+
Parameters
|
|
453
|
+
----------
|
|
454
|
+
source_type : type
|
|
455
|
+
The source type to check
|
|
456
|
+
target_type : type
|
|
457
|
+
The target type to check compatibility with
|
|
458
|
+
|
|
459
|
+
Returns
|
|
460
|
+
-------
|
|
461
|
+
bool
|
|
462
|
+
True if types are compatible, False otherwise
|
|
463
|
+
"""
|
|
464
|
+
# Direct compatibility check
|
|
465
|
+
try:
|
|
466
|
+
if issubclass(source_type, target_type):
|
|
467
|
+
return True
|
|
468
|
+
except TypeError:
|
|
469
|
+
# Handle generic types that can't use issubclass
|
|
470
|
+
pass
|
|
471
|
+
|
|
472
|
+
# Check if source outputs list[T] and target expects T
|
|
473
|
+
source_decomposed = DecomposedType(source_type)
|
|
474
|
+
if source_decomposed.origin is list and source_decomposed.args:
|
|
475
|
+
inner_type = source_decomposed.args[0]
|
|
476
|
+
try:
|
|
477
|
+
if issubclass(inner_type, target_type):
|
|
478
|
+
return True
|
|
479
|
+
except TypeError:
|
|
480
|
+
# If we can't use issubclass, check type equality
|
|
481
|
+
if inner_type == target_type:
|
|
482
|
+
return True
|
|
483
|
+
|
|
484
|
+
return False
|
nat/utils/url_utils.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
|
|
17
|
+
def url_join(*parts):
|
|
18
|
+
"""
|
|
19
|
+
Functionally similar to `os.path.join` but for URLs. This function will join the parts of a URL together, ensuring
|
|
20
|
+
that the resulting URL is valid and all `/` have been deduped.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
str
|
|
25
|
+
The joined URL.
|
|
26
|
+
"""
|
|
27
|
+
return "/".join(str(part).strip("/") for part in parts)
|