hotdata 0.2.5__tar.gz → 0.3.0__tar.gz
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.
- {hotdata-0.2.5 → hotdata-0.3.0}/PKG-INFO +1 -1
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/__init__.py +10 -0
- hotdata-0.3.0/hotdata/_auth.py +233 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/connections_api.py +617 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/databases_api.py +620 -3
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/datasets_api.py +21 -3
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/query_api.py +9 -9
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/saved_queries_api.py +21 -3
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api_client.py +1 -1
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/configuration.py +40 -4
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/__init__.py +5 -0
- hotdata-0.3.0/hotdata/models/add_managed_schema_request.py +98 -0
- hotdata-0.3.0/hotdata/models/add_managed_table_decl.py +88 -0
- hotdata-0.3.0/hotdata/models/add_managed_table_request.py +88 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_database_request.py +18 -4
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_database_response.py +3 -1
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/database_detail_response.py +3 -1
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/database_summary.py +4 -2
- hotdata-0.3.0/hotdata/models/managed_schema_response.py +92 -0
- hotdata-0.3.0/hotdata/models/managed_table_response.py +92 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/query_request.py +22 -1
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata.egg-info/PKG-INFO +1 -1
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata.egg-info/SOURCES.txt +12 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/pyproject.toml +1 -1
- hotdata-0.3.0/test/test_add_managed_schema_request.py +57 -0
- hotdata-0.3.0/test/test_add_managed_table_decl.py +53 -0
- hotdata-0.3.0/test/test_add_managed_table_request.py +53 -0
- hotdata-0.3.0/test/test_managed_schema_response.py +61 -0
- hotdata-0.3.0/test/test_managed_table_response.py +57 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/tests/test_arrow.py +5 -0
- hotdata-0.3.0/tests/test_auth.py +616 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/README.md +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/__init__.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/connection_types_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/database_context_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/embedding_providers_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/indexes_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/information_schema_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/jobs_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/query_runs_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/refresh_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/results_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/sandboxes_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/secrets_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/uploads_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/workspace_context_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api/workspaces_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/api_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/arrow.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/exceptions.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/api_error_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/api_error_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/async_query_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/attach_database_catalog_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/boolean_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/categorical_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/category_value_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/column_definition.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/column_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/column_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/column_profile_detail_one_of.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/column_profile_detail_one_of1.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/column_profile_detail_one_of2.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/column_profile_detail_one_of3.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/column_profile_detail_one_of4.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/column_profile_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/column_type_spec.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/connection_health_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/connection_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/connection_refresh_result.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/connection_schema_error.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/connection_type_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/connection_type_summary.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_connection_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_connection_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_dataset_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_dataset_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_embedding_provider_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_embedding_provider_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_index_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_sandbox_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_saved_query_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_secret_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_secret_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_workspace201_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_workspace_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/create_workspace_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/database_attachment_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/database_context_entry.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/database_default_schema_decl.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/database_default_table_decl.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/dataset_source_one_of.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/dataset_source_one_of1.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/dataset_source_one_of2.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/dataset_source_one_of3.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/dataset_source_one_of4.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/dataset_summary.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/dataset_version_summary.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/delete_sandbox_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/discovery_status.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/embedding_provider_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/error.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/execute_saved_query_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/get_connection_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/get_database_context_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/get_dataset_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/get_result_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/get_secret_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/get_workspace_context_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/index_info_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/index_status.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/information_schema_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/inline_data.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/inline_dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/job_result.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/job_status.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/job_status_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/job_type.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_connection_types_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_connections_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_database_contexts_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_databases_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_dataset_versions_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_datasets_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_embedding_providers_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_indexes_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_jobs_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_query_runs_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_results_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_sandboxes_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_saved_queries_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_saved_query_versions_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_secrets_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_uploads_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_workspace_contexts_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_workspaces200_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/list_workspaces_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/load_managed_table_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/load_managed_table_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/numeric_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/query_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/query_run_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/refresh_dataset_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/refresh_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/refresh_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/refresh_warning.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/result_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/results_format_query.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/sandbox.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/sandbox_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/saved_query_dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/saved_query_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/saved_query_summary.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/saved_query_version_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/schema_refresh_result.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/secret_metadata_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/sql_query_dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/submit_job_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/table_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/table_profile_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/table_refresh_error.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/table_refresh_result.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/temporal_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/text_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/update_dataset_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/update_dataset_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/update_embedding_provider_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/update_embedding_provider_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/update_sandbox_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/update_saved_query_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/update_secret_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/update_secret_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/upload_dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/upload_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/upload_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/upsert_database_context_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/upsert_database_context_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/upsert_workspace_context_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/upsert_workspace_context_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/url_dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/workspace_context_entry.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/workspace_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/models/workspace_list_item.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/py.typed +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata/rest.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata.egg-info/dependency_links.txt +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata.egg-info/requires.txt +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/hotdata.egg-info/top_level.txt +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/setup.cfg +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_api_client_close.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_api_error_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_api_error_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_async_query_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_attach_database_catalog_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_boolean_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_categorical_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_category_value_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_column_definition.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_column_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_column_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_column_profile_detail_one_of.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_column_profile_detail_one_of1.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_column_profile_detail_one_of2.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_column_profile_detail_one_of3.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_column_profile_detail_one_of4.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_column_profile_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_column_type_spec.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_connection_health_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_connection_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_connection_refresh_result.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_connection_schema_error.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_connection_type_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_connection_type_summary.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_connection_types_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_connections_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_connection_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_connection_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_database_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_database_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_dataset_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_dataset_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_embedding_provider_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_embedding_provider_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_index_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_sandbox_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_saved_query_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_secret_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_secret_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_workspace201_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_workspace_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_create_workspace_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_database_attachment_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_database_context_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_database_context_entry.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_database_default_schema_decl.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_database_default_table_decl.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_database_detail_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_database_summary.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_databases_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_dataset_source_one_of.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_dataset_source_one_of1.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_dataset_source_one_of2.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_dataset_source_one_of3.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_dataset_source_one_of4.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_dataset_summary.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_dataset_version_summary.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_datasets_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_delete_sandbox_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_discovery_status.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_embedding_provider_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_embedding_providers_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_error.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_execute_saved_query_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_get_connection_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_get_database_context_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_get_dataset_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_get_result_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_get_secret_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_get_workspace_context_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_index_info_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_index_status.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_indexes_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_information_schema_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_information_schema_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_inline_data.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_inline_dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_job_result.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_job_status.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_job_status_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_job_type.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_jobs_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_connection_types_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_connections_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_database_contexts_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_databases_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_dataset_versions_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_datasets_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_embedding_providers_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_indexes_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_jobs_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_query_runs_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_results_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_sandboxes_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_saved_queries_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_saved_query_versions_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_secrets_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_uploads_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_workspace_contexts_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_workspaces200_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_list_workspaces_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_load_managed_table_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_load_managed_table_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_numeric_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_query_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_query_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_query_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_query_run_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_query_runs_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_refresh_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_refresh_dataset_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_refresh_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_refresh_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_refresh_warning.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_result_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_results_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_results_format_query.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_sandbox.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_sandbox_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_sandboxes_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_saved_queries_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_saved_query_dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_saved_query_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_saved_query_summary.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_saved_query_version_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_schema_refresh_result.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_secret_metadata_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_secrets_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_sql_query_dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_submit_job_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_table_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_table_profile_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_table_refresh_error.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_table_refresh_result.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_temporal_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_text_profile_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_update_dataset_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_update_dataset_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_update_embedding_provider_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_update_embedding_provider_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_update_sandbox_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_update_saved_query_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_update_secret_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_update_secret_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_upload_dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_upload_info.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_upload_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_uploads_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_upsert_database_context_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_upsert_database_context_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_upsert_workspace_context_request.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_upsert_workspace_context_response.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_url_dataset_source.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_workspace_context_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_workspace_context_entry.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_workspace_detail.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_workspace_list_item.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/test/test_workspaces_api.py +0 -0
- {hotdata-0.2.5 → hotdata-0.3.0}/tests/test_update_changelog.py +0 -0
|
@@ -51,6 +51,9 @@ __all__ = [
|
|
|
51
51
|
"ApiKeyError",
|
|
52
52
|
"ApiAttributeError",
|
|
53
53
|
"ApiException",
|
|
54
|
+
"AddManagedSchemaRequest",
|
|
55
|
+
"AddManagedTableDecl",
|
|
56
|
+
"AddManagedTableRequest",
|
|
54
57
|
"ApiErrorDetail",
|
|
55
58
|
"ApiErrorResponse",
|
|
56
59
|
"AsyncQueryResponse",
|
|
@@ -140,6 +143,8 @@ __all__ = [
|
|
|
140
143
|
"ListWorkspacesResponse",
|
|
141
144
|
"LoadManagedTableRequest",
|
|
142
145
|
"LoadManagedTableResponse",
|
|
146
|
+
"ManagedSchemaResponse",
|
|
147
|
+
"ManagedTableResponse",
|
|
143
148
|
"NumericProfileDetail",
|
|
144
149
|
"QueryRequest",
|
|
145
150
|
"QueryResponse",
|
|
@@ -216,6 +221,9 @@ from hotdata.exceptions import ApiAttributeError as ApiAttributeError
|
|
|
216
221
|
from hotdata.exceptions import ApiException as ApiException
|
|
217
222
|
|
|
218
223
|
# import models into sdk package
|
|
224
|
+
from hotdata.models.add_managed_schema_request import AddManagedSchemaRequest as AddManagedSchemaRequest
|
|
225
|
+
from hotdata.models.add_managed_table_decl import AddManagedTableDecl as AddManagedTableDecl
|
|
226
|
+
from hotdata.models.add_managed_table_request import AddManagedTableRequest as AddManagedTableRequest
|
|
219
227
|
from hotdata.models.api_error_detail import ApiErrorDetail as ApiErrorDetail
|
|
220
228
|
from hotdata.models.api_error_response import ApiErrorResponse as ApiErrorResponse
|
|
221
229
|
from hotdata.models.async_query_response import AsyncQueryResponse as AsyncQueryResponse
|
|
@@ -305,6 +313,8 @@ from hotdata.models.list_uploads_response import ListUploadsResponse as ListUplo
|
|
|
305
313
|
from hotdata.models.list_workspaces_response import ListWorkspacesResponse as ListWorkspacesResponse
|
|
306
314
|
from hotdata.models.load_managed_table_request import LoadManagedTableRequest as LoadManagedTableRequest
|
|
307
315
|
from hotdata.models.load_managed_table_response import LoadManagedTableResponse as LoadManagedTableResponse
|
|
316
|
+
from hotdata.models.managed_schema_response import ManagedSchemaResponse as ManagedSchemaResponse
|
|
317
|
+
from hotdata.models.managed_table_response import ManagedTableResponse as ManagedTableResponse
|
|
308
318
|
from hotdata.models.numeric_profile_detail import NumericProfileDetail as NumericProfileDetail
|
|
309
319
|
from hotdata.models.query_request import QueryRequest as QueryRequest
|
|
310
320
|
from hotdata.models.query_response import QueryResponse as QueryResponse
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Transparent API-token -> JWT exchange for the Hotdata Python SDK.
|
|
2
|
+
|
|
3
|
+
Hotdata is moving API authentication to short-lived JWTs. Users still configure
|
|
4
|
+
the SDK with their long-lived ``hd_`` API token, but every request should carry
|
|
5
|
+
a fresh JWT instead. This module is the hand-written, regeneration-immune piece
|
|
6
|
+
that makes that happen behind the scenes: :class:`_TokenManager` exchanges the
|
|
7
|
+
API token for a JWT at ``POST {host}/v1/auth/jwt`` and keeps it fresh, mirroring
|
|
8
|
+
the CLI's ``jwt.rs`` logic so the CLI and SDK behave identically.
|
|
9
|
+
|
|
10
|
+
OpenAPI Generator only rewrites the files it generates, so a hand-added module
|
|
11
|
+
like this one (precedent: :mod:`hotdata.arrow`) survives regeneration. It is
|
|
12
|
+
additionally listed in ``.openapi-generator-ignore`` as belt-and-suspenders.
|
|
13
|
+
|
|
14
|
+
Key behaviors:
|
|
15
|
+
|
|
16
|
+
* **Pass-through** -- a credential that already looks like a JWT (``eyJ``
|
|
17
|
+
prefix, matching the Gateway's own ``^Bearer eyJ.*`` detection) is returned
|
|
18
|
+
unchanged and never exchanged. Every other (opaque) credential is treated as
|
|
19
|
+
an API token and exchanged; set ``HOTDATA_DISABLE_JWT_EXCHANGE`` to force a
|
|
20
|
+
raw, non-JWT credential through as-is (local/dev setups, rollback).
|
|
21
|
+
* **Opt-out** -- if ``HOTDATA_DISABLE_JWT_EXCHANGE`` is set to an affirmative
|
|
22
|
+
value (``1``/``true``/``yes``/``on``), the credential is always returned
|
|
23
|
+
as-is (hard escape hatch for rollout); ``0``/``false``/empty do not opt out.
|
|
24
|
+
* **In-memory cache only** -- no disk writes. The server already de-duplicates
|
|
25
|
+
mints (keyed by ``sha256(api_token)``), so per-process caching is sufficient.
|
|
26
|
+
* **Thread-safe** -- a :class:`threading.Lock` with single-flight mint covers
|
|
27
|
+
the case where a shared ``ApiClient`` is hit from many threads at once.
|
|
28
|
+
* **Refresh, then re-mint** -- prefer the refresh token when available; on
|
|
29
|
+
refresh failure, re-mint from the held API token (always possible since the
|
|
30
|
+
SDK holds it). Matches the CLI.
|
|
31
|
+
* **TLS/proxy reuse** -- the exchange call reuses the SDK's configured TLS,
|
|
32
|
+
client cert and proxy settings (see :func:`_pool_from_config`) so it behaves
|
|
33
|
+
like every other SDK request, with a bounded timeout so a stalled token
|
|
34
|
+
endpoint fails fast instead of hanging every call.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import json
|
|
38
|
+
import os
|
|
39
|
+
import ssl
|
|
40
|
+
import threading
|
|
41
|
+
import time
|
|
42
|
+
from urllib.parse import urlencode
|
|
43
|
+
|
|
44
|
+
import urllib3
|
|
45
|
+
|
|
46
|
+
_LEEWAY = 30 # refresh when <30s of life remains
|
|
47
|
+
_TIMEOUT = 30.0 # seconds -- never let a stalled token endpoint hang every request
|
|
48
|
+
_CLIENT_ID = "hotdata-python-sdk"
|
|
49
|
+
|
|
50
|
+
# Env var that disables exchange entirely. Used as a hard escape hatch during
|
|
51
|
+
# the rollout window and for local/dev setups. Only affirmative values opt out
|
|
52
|
+
# (see _DISABLE_VALUES) so that ``=0`` / ``=false`` do NOT silently disable it.
|
|
53
|
+
_DISABLE_ENV = "HOTDATA_DISABLE_JWT_EXCHANGE"
|
|
54
|
+
_DISABLE_VALUES = {"1", "true", "yes", "on"}
|
|
55
|
+
|
|
56
|
+
# The SOCKS schemes urllib3 routes through SOCKSProxyManager rather than the
|
|
57
|
+
# plain ProxyManager. Mirrors hotdata/rest.py's SUPPORTED_SOCKS_PROXIES.
|
|
58
|
+
_SUPPORTED_SOCKS_PROXIES = {"socks5", "socks5h", "socks4", "socks4a"}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TokenExchangeError(Exception):
|
|
62
|
+
"""Raised when an API token cannot be exchanged for a JWT.
|
|
63
|
+
|
|
64
|
+
Surfacing ``invalid_grant`` (expired/revoked API token) here keeps the
|
|
65
|
+
failure clear instead of a confusing downstream 401.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _is_socks_proxy_url(url):
|
|
70
|
+
# Mirror hotdata/rest.py.is_socks_proxy_url so the exchange pool routes
|
|
71
|
+
# SOCKS proxies the same way the generated REST client does.
|
|
72
|
+
if url is None:
|
|
73
|
+
return False
|
|
74
|
+
split_section = url.split("://")
|
|
75
|
+
if len(split_section) < 2:
|
|
76
|
+
return False
|
|
77
|
+
return split_section[0].lower() in _SUPPORTED_SOCKS_PROXIES
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _pool_from_config(configuration):
|
|
81
|
+
"""Build a urllib3 pool manager from the SDK's TLS/proxy configuration.
|
|
82
|
+
|
|
83
|
+
Deliberately parallels ``RESTClientObject.__init__`` in
|
|
84
|
+
:mod:`hotdata.rest` so the token-exchange call honors the same
|
|
85
|
+
``ssl_ca_cert`` / ``ca_cert_data`` / ``cert_file`` / ``key_file`` /
|
|
86
|
+
``proxy`` / ``verify_ssl`` settings as every other SDK request. We build a
|
|
87
|
+
fresh, lightweight pool here rather than reaching into the ``ApiClient``'s
|
|
88
|
+
REST client (which the ``Configuration`` does not hold a reference to).
|
|
89
|
+
"""
|
|
90
|
+
# cert_reqs -- honor verify_ssl exactly as the generated client does.
|
|
91
|
+
if configuration.verify_ssl:
|
|
92
|
+
cert_reqs = ssl.CERT_REQUIRED
|
|
93
|
+
else:
|
|
94
|
+
cert_reqs = ssl.CERT_NONE
|
|
95
|
+
|
|
96
|
+
pool_args = {
|
|
97
|
+
"cert_reqs": cert_reqs,
|
|
98
|
+
"ca_certs": configuration.ssl_ca_cert,
|
|
99
|
+
"cert_file": configuration.cert_file,
|
|
100
|
+
"key_file": configuration.key_file,
|
|
101
|
+
"ca_cert_data": configuration.ca_cert_data,
|
|
102
|
+
}
|
|
103
|
+
# Mirror rest.py's hostname/SNI handling so the exchange call does not
|
|
104
|
+
# silently fail for users who customize them (corporate MITM proxies set
|
|
105
|
+
# assert_hostname; some gateways require an explicit tls_server_name/SNI).
|
|
106
|
+
if configuration.assert_hostname is not None:
|
|
107
|
+
pool_args["assert_hostname"] = configuration.assert_hostname
|
|
108
|
+
if configuration.tls_server_name:
|
|
109
|
+
pool_args["server_hostname"] = configuration.tls_server_name
|
|
110
|
+
if configuration.socket_options is not None:
|
|
111
|
+
pool_args["socket_options"] = configuration.socket_options
|
|
112
|
+
# `retries`/`maxsize` are intentionally not mirrored: the exchange is a
|
|
113
|
+
# single bounded-timeout request that fails fast rather than retrying.
|
|
114
|
+
|
|
115
|
+
if configuration.proxy:
|
|
116
|
+
if _is_socks_proxy_url(configuration.proxy):
|
|
117
|
+
from urllib3.contrib.socks import SOCKSProxyManager
|
|
118
|
+
pool_args["proxy_url"] = configuration.proxy
|
|
119
|
+
pool_args["headers"] = configuration.proxy_headers
|
|
120
|
+
return SOCKSProxyManager(**pool_args)
|
|
121
|
+
pool_args["proxy_url"] = configuration.proxy
|
|
122
|
+
pool_args["proxy_headers"] = configuration.proxy_headers
|
|
123
|
+
return urllib3.ProxyManager(**pool_args)
|
|
124
|
+
|
|
125
|
+
return urllib3.PoolManager(**pool_args)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class _TokenManager:
|
|
129
|
+
"""Exchanges an API token for short-lived JWTs and keeps them fresh.
|
|
130
|
+
|
|
131
|
+
A credential that already looks like a JWT (``eyJ`` prefix) is passed
|
|
132
|
+
through unchanged, as is any credential when
|
|
133
|
+
``HOTDATA_DISABLE_JWT_EXCHANGE`` is set; every other (opaque) API token is
|
|
134
|
+
exchanged.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, credential, configuration, pool=None):
|
|
138
|
+
self._credential = credential
|
|
139
|
+
self._config = configuration # read host + TLS lazily at mint time
|
|
140
|
+
self._pool = pool # injected in tests; else built from config TLS
|
|
141
|
+
self._lock = threading.Lock()
|
|
142
|
+
self._jwt = None
|
|
143
|
+
self._exp = 0.0
|
|
144
|
+
self._refresh = None
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def _needs_exchange(self):
|
|
148
|
+
# Opt-out wins outright: an affirmative HOTDATA_DISABLE_JWT_EXCHANGE
|
|
149
|
+
# (1/true/yes/on) means send the credential as-is, never touching the
|
|
150
|
+
# token endpoint. Other values (incl. 0/false/empty) do not opt out.
|
|
151
|
+
if os.environ.get(_DISABLE_ENV, "").strip().lower() in _DISABLE_VALUES:
|
|
152
|
+
return False
|
|
153
|
+
# A compact JWT always starts with "eyJ" (base64 of '{"'), matching the
|
|
154
|
+
# Gateway's own ``^Bearer eyJ.*`` detection -- those already are what we
|
|
155
|
+
# want on the wire, so pass them through. Everything else is an opaque
|
|
156
|
+
# API token to be exchanged. (Hotdata API tokens are bare hex; the
|
|
157
|
+
# ``hd_`` prefix seen in docs/comments is cosmetic and not enforced by
|
|
158
|
+
# the server, so we must not gate on it.) Use HOTDATA_DISABLE_JWT_EXCHANGE
|
|
159
|
+
# to force a raw, non-JWT credential through unchanged (local/dev).
|
|
160
|
+
return isinstance(self._credential, str) and not self._credential.startswith("eyJ")
|
|
161
|
+
|
|
162
|
+
def bearer_value(self):
|
|
163
|
+
"""Return a live JWT (exchanging + caching), or the credential as-is.
|
|
164
|
+
|
|
165
|
+
Returns the credential unchanged when it is already a JWT or when
|
|
166
|
+
exchange is disabled; otherwise returns a cached JWT, refreshing or
|
|
167
|
+
re-minting it when it is within ``_LEEWAY`` seconds of expiry.
|
|
168
|
+
"""
|
|
169
|
+
if not self._needs_exchange:
|
|
170
|
+
return self._credential # already a JWT (or opt-out) -> unchanged
|
|
171
|
+
with self._lock:
|
|
172
|
+
# Fast path: a still-valid cached JWT, no network call.
|
|
173
|
+
if self._jwt and time.time() < self._exp - _LEEWAY:
|
|
174
|
+
return self._jwt
|
|
175
|
+
# Prefer the refresh token; on failure, drop it and re-mint below.
|
|
176
|
+
if self._refresh and not self._mint(
|
|
177
|
+
{"grant_type": "refresh_token", "refresh_token": self._refresh}
|
|
178
|
+
):
|
|
179
|
+
self._refresh = None # refresh failed -> fall through to re-mint
|
|
180
|
+
# Re-mint from the held API token if we still lack a fresh JWT.
|
|
181
|
+
if not self._jwt or time.time() >= self._exp - _LEEWAY:
|
|
182
|
+
self._mint({"grant_type": "api_token", "api_token": self._credential})
|
|
183
|
+
return self._jwt
|
|
184
|
+
|
|
185
|
+
def _mint(self, params):
|
|
186
|
+
# Returns True on success. The refresh path is best-effort: ANY failure
|
|
187
|
+
# -- a non-200, a transport error, or a malformed/missing-token body --
|
|
188
|
+
# returns False so the caller re-mints from the held API token. An
|
|
189
|
+
# api_token mint instead raises TokenExchangeError on any failure, since
|
|
190
|
+
# there is no further fallback.
|
|
191
|
+
params["client_id"] = _CLIENT_ID
|
|
192
|
+
is_refresh = params["grant_type"] == "refresh_token"
|
|
193
|
+
try:
|
|
194
|
+
pool = self._pool or _pool_from_config(self._config) # reuses ssl_ca_cert/cert/proxy
|
|
195
|
+
host = self._config.host.rstrip("/") # read host lazily -- may be set post-construct
|
|
196
|
+
resp = pool.request(
|
|
197
|
+
"POST",
|
|
198
|
+
f"{host}/v1/auth/jwt",
|
|
199
|
+
body=urlencode(params),
|
|
200
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
201
|
+
timeout=_TIMEOUT,
|
|
202
|
+
)
|
|
203
|
+
if resp.status != 200:
|
|
204
|
+
raise TokenExchangeError(
|
|
205
|
+
f"token exchange failed: {resp.status} {resp.data[:200]!r}"
|
|
206
|
+
)
|
|
207
|
+
data = json.loads(resp.data)
|
|
208
|
+
token = data["access_token"]
|
|
209
|
+
expires_in = float(data.get("expires_in", 300))
|
|
210
|
+
except (
|
|
211
|
+
TokenExchangeError,
|
|
212
|
+
urllib3.exceptions.HTTPError,
|
|
213
|
+
ValueError,
|
|
214
|
+
TypeError,
|
|
215
|
+
KeyError,
|
|
216
|
+
) as exc:
|
|
217
|
+
if is_refresh:
|
|
218
|
+
return False # let caller re-mint from the API token
|
|
219
|
+
if isinstance(exc, TokenExchangeError):
|
|
220
|
+
raise
|
|
221
|
+
raise TokenExchangeError(f"token exchange failed: {exc!r}") from exc
|
|
222
|
+
self._jwt = token
|
|
223
|
+
self._exp = time.time() + expires_in
|
|
224
|
+
self._refresh = data.get("refresh_token") or self._refresh
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
__all__ = [
|
|
229
|
+
"TokenExchangeError",
|
|
230
|
+
"_TokenManager",
|
|
231
|
+
"_CLIENT_ID",
|
|
232
|
+
"_pool_from_config",
|
|
233
|
+
]
|