hotdata 0.9.1__tar.gz → 0.10.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.9.1 → hotdata-0.10.0}/PKG-INFO +1 -1
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api_client.py +0 -93
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/configuration.py +5 -38
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata.egg-info/PKG-INFO +1 -1
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata.egg-info/SOURCES.txt +1 -2
- {hotdata-0.9.1 → hotdata-0.10.0}/pyproject.toml +1 -1
- hotdata-0.10.0/tests/test_api_token_auth.py +156 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_arrow.py +0 -5
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_config_auth_surface.py +2 -4
- hotdata-0.9.1/hotdata/_auth.py +0 -458
- hotdata-0.9.1/tests/test_auth.py +0 -1239
- {hotdata-0.9.1 → hotdata-0.10.0}/README.md +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/__init__.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/_retry.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/_upload_watchdog.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/__init__.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/connection_types_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/connections_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/database_context_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/databases_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/embedding_providers_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/indexes_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/information_schema_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/jobs_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/query_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/query_runs_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/refresh_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/results_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/saved_queries_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/secrets_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/uploads_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/usage_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api/workspaces_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/api_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/arrow.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/exceptions.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/__init__.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/add_managed_schema_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/add_managed_table_decl.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/add_managed_table_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/api_error_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/api_error_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/async_query_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/attach_database_catalog_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/batch_create_upload_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/batch_create_upload_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/boolean_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/bulk_create_databases_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/bulk_create_databases_result.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/categorical_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/category_value_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/column_definition.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/column_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/column_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/column_profile_detail_one_of.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/column_profile_detail_one_of1.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/column_profile_detail_one_of2.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/column_profile_detail_one_of3.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/column_profile_detail_one_of4.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/column_profile_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/column_type_spec.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/connection_health_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/connection_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/connection_refresh_result.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/connection_schema_error.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/connection_type_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/connection_type_summary.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_connection_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_connection_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_database_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_database_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_embedding_provider_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_embedding_provider_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_index_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_saved_query_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_secret_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_secret_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_upload_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_workspace_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/create_workspace_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/database_attachment_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/database_batch_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/database_context_entry.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/database_count_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/database_default_schema_decl.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/database_default_table_decl.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/database_detail_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/database_summary.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/delete_database_batch_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/discovery_status.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/embedding_provider_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/error.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/execute_saved_query_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/finalize_upload_part.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/finalize_upload_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/finalize_upload_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/fork_database_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/get_connection_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/get_database_context_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/get_result_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/get_secret_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/index_entry_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/index_info_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/index_status.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/information_schema_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/job_result.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/job_status.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/job_status_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/job_type.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_connection_types_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_connections_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_database_contexts_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_databases_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_embedding_providers_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_indexes_page_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_indexes_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_jobs_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_query_runs_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_results_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_saved_queries_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_saved_query_versions_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_secrets_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/list_workspaces_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/load_managed_table_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/load_managed_table_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/managed_schema_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/managed_table_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/mint_upload_parts_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/mint_upload_parts_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/minted_upload_part_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/numeric_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/query_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/query_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/query_run_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/refresh_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/refresh_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/refresh_warning.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/result_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/results_format_query.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/saved_query_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/saved_query_summary.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/saved_query_version_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/schema_refresh_result.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/secret_metadata_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/submit_job_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/table_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/table_partition_key.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/table_profile_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/table_refresh_error.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/table_refresh_result.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/table_sort_key.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/temporal_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/text_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/update_embedding_provider_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/update_embedding_provider_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/update_saved_query_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/update_secret_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/update_secret_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/upload_session_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/upsert_database_context_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/upsert_database_context_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/workspace_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/workspace_list_item.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/models/workspace_usage_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/py.typed +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/query.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/rest.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata/uploads.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata.egg-info/dependency_links.txt +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata.egg-info/requires.txt +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/hotdata.egg-info/top_level.txt +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/setup.cfg +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_add_managed_schema_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_add_managed_table_decl.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_add_managed_table_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_api_client_close.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_api_error_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_api_error_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_async_query_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_attach_database_catalog_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_batch_create_upload_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_batch_create_upload_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_boolean_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_bulk_create_databases_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_bulk_create_databases_result.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_categorical_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_category_value_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_column_definition.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_column_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_column_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_column_profile_detail_one_of.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_column_profile_detail_one_of1.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_column_profile_detail_one_of2.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_column_profile_detail_one_of3.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_column_profile_detail_one_of4.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_column_profile_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_column_type_spec.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_connection_health_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_connection_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_connection_refresh_result.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_connection_schema_error.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_connection_type_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_connection_type_summary.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_connection_types_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_connections_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_connection_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_connection_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_database_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_database_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_embedding_provider_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_embedding_provider_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_index_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_saved_query_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_secret_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_secret_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_upload_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_workspace_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_create_workspace_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_database_attachment_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_database_batch_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_database_context_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_database_context_entry.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_database_count_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_database_default_schema_decl.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_database_default_table_decl.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_database_detail_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_database_summary.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_databases_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_delete_database_batch_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_discovery_status.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_embedding_provider_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_embedding_providers_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_error.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_execute_saved_query_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_finalize_upload_part.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_finalize_upload_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_finalize_upload_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_fork_database_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_get_connection_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_get_database_context_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_get_result_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_get_secret_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_index_entry_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_index_info_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_index_status.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_indexes_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_information_schema_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_information_schema_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_job_result.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_job_status.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_job_status_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_job_type.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_jobs_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_connection_types_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_connections_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_database_contexts_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_databases_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_embedding_providers_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_indexes_page_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_indexes_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_jobs_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_query_runs_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_results_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_saved_queries_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_saved_query_versions_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_secrets_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_list_workspaces_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_load_managed_table_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_load_managed_table_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_managed_schema_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_managed_table_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_mint_upload_parts_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_mint_upload_parts_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_minted_upload_part_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_numeric_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_query_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_query_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_query_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_query_run_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_query_runs_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_refresh_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_refresh_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_refresh_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_refresh_warning.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_result_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_results_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_results_format_query.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_saved_queries_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_saved_query_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_saved_query_summary.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_saved_query_version_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_schema_refresh_result.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_secret_metadata_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_secrets_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_submit_job_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_table_info.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_table_partition_key.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_table_profile_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_table_refresh_error.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_table_refresh_result.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_table_sort_key.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_temporal_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_text_profile_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_update_embedding_provider_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_update_embedding_provider_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_update_saved_query_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_update_secret_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_update_secret_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_upload_session_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_uploads_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_upsert_database_context_request.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_upsert_database_context_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_usage_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_workspace_detail.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_workspace_list_item.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_workspace_usage_response.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/test/test_workspaces_api.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_query.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_retry.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_update_changelog.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_upload_codex_fixes.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_upload_multipart_resilient.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_upload_parity_fixes.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_upload_resilient.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_upload_retry_policy.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_upload_watchdog.py +0 -0
- {hotdata-0.9.1 → hotdata-0.10.0}/tests/test_uploads.py +0 -0
|
@@ -281,105 +281,12 @@ class ApiClient:
|
|
|
281
281
|
body=body, post_params=post_params,
|
|
282
282
|
_request_timeout=_request_timeout
|
|
283
283
|
)
|
|
284
|
-
if response_data.status == 401:
|
|
285
|
-
# `header_params` is Optional on this method. `param_serialize`
|
|
286
|
-
# always passes a dict so nothing in-tree omits it, but a direct
|
|
287
|
-
# caller that does would otherwise crash HERE, on the failure
|
|
288
|
-
# path, rather than anywhere it could be noticed.
|
|
289
|
-
header_params = header_params if header_params is not None else {}
|
|
290
|
-
fresh = self._remint_bearer(header_params)
|
|
291
|
-
if fresh is not None:
|
|
292
|
-
# Drain the response being discarded. urllib3 only returns a
|
|
293
|
-
# connection to the pool once its body is consumed or
|
|
294
|
-
# released; abandoning it un-read leaks the connection for
|
|
295
|
-
# the life of the pool, and a client that 401s on every
|
|
296
|
-
# request would exhaust it.
|
|
297
|
-
try:
|
|
298
|
-
response_data.read()
|
|
299
|
-
except Exception: # noqa: BLE001 -- draining must not mask the retry
|
|
300
|
-
pass
|
|
301
|
-
header_params["Authorization"] = fresh
|
|
302
|
-
response_data = self.rest_client.request(
|
|
303
|
-
method, url,
|
|
304
|
-
headers=header_params,
|
|
305
|
-
body=body, post_params=post_params,
|
|
306
|
-
_request_timeout=_request_timeout
|
|
307
|
-
)
|
|
308
284
|
|
|
309
285
|
except ApiException as e:
|
|
310
286
|
raise e
|
|
311
287
|
|
|
312
288
|
return response_data
|
|
313
289
|
|
|
314
|
-
def _remint_bearer(self, header_params):
|
|
315
|
-
"""A replacement `Authorization` value after a 401, or None to give up.
|
|
316
|
-
|
|
317
|
-
ONCE per request, and only for a bearer this client minted. A 401 on an
|
|
318
|
-
exchanged JWT is recoverable in a way it is not for a caller-supplied
|
|
319
|
-
credential: the SDK swapped an API token for a short-lived JWT behind the
|
|
320
|
-
caller's back, so a JWT the server refuses is the SDK's problem to fix,
|
|
321
|
-
not an error the caller can act on.
|
|
322
|
-
|
|
323
|
-
The proactive `_exp` check covers the expected case. This covers the one
|
|
324
|
-
it cannot: a token the client still believed in but the server rejected —
|
|
325
|
-
clock skew, a revoked JWT, or a mint that reported a lifetime the token
|
|
326
|
-
did not have. Returns None when there is nothing to re-mint (no manager,
|
|
327
|
-
a caller-supplied JWT, exchange opted out) so the 401 surfaces unchanged.
|
|
328
|
-
|
|
329
|
-
Never loops. A second 401 is a real rejection and is returned to the
|
|
330
|
-
caller; retrying a genuine auth failure would only turn one clear error
|
|
331
|
-
into a slower one.
|
|
332
|
-
"""
|
|
333
|
-
from hotdata._auth import TokenExchangeError
|
|
334
|
-
|
|
335
|
-
config = self.configuration
|
|
336
|
-
manager = getattr(config, "_token_manager", None)
|
|
337
|
-
if manager is None or not getattr(manager, "_needs_exchange", False):
|
|
338
|
-
return None
|
|
339
|
-
|
|
340
|
-
# Only a header THIS manager produced. `_request_auth` lets a caller
|
|
341
|
-
# override auth for a single request, and a per-request credential that
|
|
342
|
-
# the server rejects is the caller's to deal with -- replacing it with a
|
|
343
|
-
# managed token would silently answer a different question than the one
|
|
344
|
-
# asked, and would do it only on the failure path where it is hardest to
|
|
345
|
-
# notice.
|
|
346
|
-
stale = manager._jwt
|
|
347
|
-
before = header_params.get("Authorization")
|
|
348
|
-
if stale is not None and before == "Bearer " + stale:
|
|
349
|
-
# We carried the token the manager still holds: clear it so the read
|
|
350
|
-
# below mints. Compare-and-clear, so a thread that is beaten to it
|
|
351
|
-
# takes the winner's token rather than clearing it again.
|
|
352
|
-
manager.invalidate(only_if=stale)
|
|
353
|
-
elif before == "Bearer " + (manager._prev_jwt or "\0"):
|
|
354
|
-
# We carried the token the manager has ALREADY rotated away from --
|
|
355
|
-
# another request 401'd on it first and minted the replacement. Do
|
|
356
|
-
# not invalidate: retry with what it minted. Without this the winner
|
|
357
|
-
# recovers and every sibling fails, since their header no longer
|
|
358
|
-
# matches the current token, which is the stampede case the
|
|
359
|
-
# compare-and-clear was added for in the first place.
|
|
360
|
-
pass
|
|
361
|
-
else:
|
|
362
|
-
# A credential this manager never issued -- `_request_auth`, or an
|
|
363
|
-
# endpoint with no auth at all. Not ours to replace.
|
|
364
|
-
return None
|
|
365
|
-
try:
|
|
366
|
-
token = config.api_key
|
|
367
|
-
except TokenExchangeError:
|
|
368
|
-
# The commonest cause of a 401 is a revoked or expired API token, and
|
|
369
|
-
# re-minting from it fails for the same reason. `invalidate` cleared
|
|
370
|
-
# the refresh token, so this raise has no best-effort path behind it.
|
|
371
|
-
# Surfacing it here would replace the server's own 401 -- body and
|
|
372
|
-
# all -- with an exchange error from a retry the caller never asked
|
|
373
|
-
# for, turning a clear answer into a confusing one.
|
|
374
|
-
return None
|
|
375
|
-
if token is None:
|
|
376
|
-
return None
|
|
377
|
-
after = "Bearer " + token
|
|
378
|
-
# A re-mint that hands back the same credential means the server would
|
|
379
|
-
# refuse it again; sending it a second time buys a round trip and the
|
|
380
|
-
# same 401. This is the replayed-cache case, so it is not hypothetical.
|
|
381
|
-
return None if after == before else after
|
|
382
|
-
|
|
383
290
|
def response_deserialize(
|
|
384
291
|
self,
|
|
385
292
|
response_data: rest.RESTResponse,
|
|
@@ -220,13 +220,8 @@ conf = hotdata.Configuration(
|
|
|
220
220
|
self.temp_folder_path = None
|
|
221
221
|
"""Temp file folder for downloading files
|
|
222
222
|
"""
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# the manager is created eagerly here (never lazily in the getter) so
|
|
226
|
-
# concurrent first requests don't each build one. The setter rebuilds it.
|
|
227
|
-
from hotdata._auth import _TokenManager
|
|
228
|
-
self._token_manager = _TokenManager(api_key, self) if api_key is not None else None
|
|
229
|
-
"""Hotdata API key, sent as `Authorization: Bearer <key>`."""
|
|
223
|
+
self.api_key = api_key
|
|
224
|
+
"""Hotdata API key, sent verbatim as `Authorization: Bearer <key>`."""
|
|
230
225
|
# apiKey-security values (X-Workspace-Id), keyed by scheme name. Read by
|
|
231
226
|
# the generated `auth_settings()` below.
|
|
232
227
|
self.api_keys = dict(api_keys) if api_keys else {}
|
|
@@ -343,20 +338,13 @@ conf = hotdata.Configuration(
|
|
|
343
338
|
result = cls.__new__(cls)
|
|
344
339
|
memo[id(self)] = result
|
|
345
340
|
for k, v in self.__dict__.items():
|
|
346
|
-
|
|
347
|
-
# neither of which is deepcopy-able; rebuild it below from the
|
|
348
|
-
# (deepcopy-safe) credential string instead.
|
|
349
|
-
if k not in ('logger', 'logger_file_handler', '_token_manager'):
|
|
341
|
+
if k not in ('logger', 'logger_file_handler'):
|
|
350
342
|
setattr(result, k, copy.deepcopy(v, memo))
|
|
351
343
|
# shallow copy of loggers
|
|
352
344
|
result.logger = copy.copy(self.logger)
|
|
353
345
|
# use setters to configure loggers
|
|
354
346
|
result.logger_file = self.logger_file
|
|
355
347
|
result.debug = self.debug
|
|
356
|
-
# rebuild the token manager bound to the copy (never deepcopy lock/pool)
|
|
357
|
-
from hotdata._auth import _TokenManager
|
|
358
|
-
tm = self._token_manager
|
|
359
|
-
result._token_manager = _TokenManager(tm._credential, result) if tm else None
|
|
360
348
|
return result
|
|
361
349
|
|
|
362
350
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
@@ -501,26 +489,6 @@ conf = hotdata.Configuration(
|
|
|
501
489
|
|
|
502
490
|
return None
|
|
503
491
|
|
|
504
|
-
@property
|
|
505
|
-
def api_key(self) -> Optional[str]:
|
|
506
|
-
"""Live bearer credential, sent as `Authorization: Bearer <value>`.
|
|
507
|
-
|
|
508
|
-
Backed by the regeneration-immune `_TokenManager` (see `hotdata._auth`):
|
|
509
|
-
an opaque API token is transparently exchanged for a short-lived JWT and
|
|
510
|
-
kept fresh, while a credential already shaped like a JWT (or exchange
|
|
511
|
-
opted out) is returned unchanged. `auth_settings()` reads this on every
|
|
512
|
-
request, so the wire always carries a current token.
|
|
513
|
-
"""
|
|
514
|
-
# Read the manager once: a concurrent `api_key` reset could otherwise
|
|
515
|
-
# set it to None between the check and the `.bearer_value()` call.
|
|
516
|
-
tm = self._token_manager
|
|
517
|
-
return None if tm is None else tm.bearer_value()
|
|
518
|
-
|
|
519
|
-
@api_key.setter
|
|
520
|
-
def api_key(self, value: Optional[str]) -> None:
|
|
521
|
-
from hotdata._auth import _TokenManager
|
|
522
|
-
self._token_manager = _TokenManager(value, self) if value is not None else None
|
|
523
|
-
|
|
524
492
|
@property
|
|
525
493
|
def workspace_id(self) -> Optional[str]:
|
|
526
494
|
"""Public id of the target workspace (sent as `X-Workspace-Id`)."""
|
|
@@ -555,9 +523,8 @@ conf = hotdata.Configuration(
|
|
|
555
523
|
:return: The Auth Settings information dict.
|
|
556
524
|
"""
|
|
557
525
|
auth: AuthSettings = {}
|
|
558
|
-
#
|
|
559
|
-
#
|
|
560
|
-
# and could race a concurrent `api_key` reset (yielding `Bearer None`).
|
|
526
|
+
# Read the credential once so a concurrent `api_key` reset can't yield
|
|
527
|
+
# `Bearer None` between the check and the header build.
|
|
561
528
|
BearerAuth_token = self.api_key
|
|
562
529
|
if BearerAuth_token is not None:
|
|
563
530
|
auth['BearerAuth'] = {
|
|
@@ -2,7 +2,6 @@ README.md
|
|
|
2
2
|
pyproject.toml
|
|
3
3
|
setup.cfg
|
|
4
4
|
hotdata/__init__.py
|
|
5
|
-
hotdata/_auth.py
|
|
6
5
|
hotdata/_retry.py
|
|
7
6
|
hotdata/_upload_watchdog.py
|
|
8
7
|
hotdata/api_client.py
|
|
@@ -310,8 +309,8 @@ test/test_workspace_detail.py
|
|
|
310
309
|
test/test_workspace_list_item.py
|
|
311
310
|
test/test_workspace_usage_response.py
|
|
312
311
|
test/test_workspaces_api.py
|
|
312
|
+
tests/test_api_token_auth.py
|
|
313
313
|
tests/test_arrow.py
|
|
314
|
-
tests/test_auth.py
|
|
315
314
|
tests/test_config_auth_surface.py
|
|
316
315
|
tests/test_query.py
|
|
317
316
|
tests/test_retry.py
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""The API token is the only credential: it goes on the wire verbatim.
|
|
2
|
+
|
|
3
|
+
Hotdata deprecated the API-token -> JWT key exchange. The SDK used to hold a
|
|
4
|
+
hand-written `_TokenManager` (`hotdata/_auth.py`) that POSTed the configured
|
|
5
|
+
token to `/v1/auth/jwt`, cached the returned JWT, and refreshed it with a
|
|
6
|
+
`refresh_token` grant; `Configuration.api_key` was a property that minted on
|
|
7
|
+
every read. All of that is gone -- the configured token is sent as
|
|
8
|
+
`Authorization: Bearer <token>` unchanged.
|
|
9
|
+
|
|
10
|
+
These tests pin that end to end: the token reaches the wire as-is, no token
|
|
11
|
+
endpoint is ever contacted, and the exchange machinery cannot creep back in via
|
|
12
|
+
the generated `configuration.py` or its mustache template.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import copy
|
|
18
|
+
import importlib
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
import pytest
|
|
22
|
+
import urllib3
|
|
23
|
+
|
|
24
|
+
from hotdata import ApiClient, Configuration
|
|
25
|
+
from hotdata.api.workspaces_api import WorkspacesApi
|
|
26
|
+
|
|
27
|
+
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
28
|
+
CONFIGURATION_TEMPLATE = REPO_ROOT / ".openapi-generator-templates" / "configuration.mustache"
|
|
29
|
+
CONFIGURATION_MODULE = REPO_ROOT / "hotdata" / "configuration.py"
|
|
30
|
+
|
|
31
|
+
# An opaque (non-JWT-shaped) token: exactly the credential the old exchange
|
|
32
|
+
# would have swapped for a JWT before sending.
|
|
33
|
+
API_TOKEN = "hd_opaque_api_token"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _config(**kwargs) -> Configuration:
|
|
37
|
+
return Configuration(host="https://api.example.test", api_key=API_TOKEN, **kwargs)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _no_network(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
41
|
+
"""Make any outbound urllib3 request an immediate failure.
|
|
42
|
+
|
|
43
|
+
The exchange built its own pool manager, so a surviving mint would show up
|
|
44
|
+
here rather than as a silently different header.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def explode(*args, **kwargs): # pragma: no cover - only runs on regression
|
|
48
|
+
raise AssertionError(f"unexpected outbound request: {args!r} {kwargs!r}")
|
|
49
|
+
|
|
50
|
+
monkeypatch.setattr(urllib3.PoolManager, "request", explode)
|
|
51
|
+
monkeypatch.setattr(urllib3.PoolManager, "urlopen", explode)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_api_token_is_sent_verbatim_as_bearer(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
55
|
+
"""The configured token itself is the bearer credential."""
|
|
56
|
+
_no_network(monkeypatch)
|
|
57
|
+
config = _config()
|
|
58
|
+
|
|
59
|
+
assert config.auth_settings()["BearerAuth"]["value"] == f"Bearer {API_TOKEN}"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_authorization_header_carries_the_api_token(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
63
|
+
"""Serializing a real operation puts the raw token in the header."""
|
|
64
|
+
_no_network(monkeypatch)
|
|
65
|
+
api = WorkspacesApi(ApiClient(_config(workspace_id="ws_abc")))
|
|
66
|
+
|
|
67
|
+
_method, _url, headers, *_rest = api._list_workspaces_serialize(
|
|
68
|
+
organization_public_id=None,
|
|
69
|
+
_request_auth=None,
|
|
70
|
+
_content_type=None,
|
|
71
|
+
_headers=None,
|
|
72
|
+
_host_index=0,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
assert headers["Authorization"] == f"Bearer {API_TOKEN}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_api_token_is_read_repeatedly_without_exchange(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
79
|
+
"""Every request re-reads the credential; none of those reads mint anything."""
|
|
80
|
+
_no_network(monkeypatch)
|
|
81
|
+
config = _config()
|
|
82
|
+
|
|
83
|
+
values = {config.auth_settings()["BearerAuth"]["value"] for _ in range(5)}
|
|
84
|
+
|
|
85
|
+
assert values == {f"Bearer {API_TOKEN}"}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_api_token_can_be_replaced_and_cleared(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
89
|
+
"""`api_key` stays writable, and clearing it drops the bearer header."""
|
|
90
|
+
_no_network(monkeypatch)
|
|
91
|
+
config = _config()
|
|
92
|
+
|
|
93
|
+
config.api_key = "hd_rotated"
|
|
94
|
+
assert config.auth_settings()["BearerAuth"]["value"] == "Bearer hd_rotated"
|
|
95
|
+
|
|
96
|
+
config.api_key = None
|
|
97
|
+
assert "BearerAuth" not in config.auth_settings()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_deepcopy_preserves_the_api_token(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
101
|
+
"""`ApiClient` deepcopies the configuration; the credential must survive it.
|
|
102
|
+
|
|
103
|
+
The exchange needed a `__deepcopy__` special case because the token manager
|
|
104
|
+
held a lock and a pool manager. A plain string needs none, but the copy must
|
|
105
|
+
still authenticate.
|
|
106
|
+
"""
|
|
107
|
+
_no_network(monkeypatch)
|
|
108
|
+
config = _config(workspace_id="ws_abc")
|
|
109
|
+
|
|
110
|
+
clone = copy.deepcopy(config)
|
|
111
|
+
|
|
112
|
+
assert clone.api_key == API_TOKEN
|
|
113
|
+
assert clone.auth_settings()["BearerAuth"]["value"] == f"Bearer {API_TOKEN}"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_jwt_opt_out_env_var_is_inert(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
117
|
+
"""`HOTDATA_DISABLE_JWT_EXCHANGE` was the exchange escape hatch.
|
|
118
|
+
|
|
119
|
+
With no exchange left there is nothing to disable, so the variable must not
|
|
120
|
+
change behavior -- callers who still export it get the same raw token.
|
|
121
|
+
"""
|
|
122
|
+
_no_network(monkeypatch)
|
|
123
|
+
monkeypatch.setenv("HOTDATA_DISABLE_JWT_EXCHANGE", "0")
|
|
124
|
+
|
|
125
|
+
assert _config().auth_settings()["BearerAuth"]["value"] == f"Bearer {API_TOKEN}"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_auth_exchange_module_is_gone() -> None:
|
|
129
|
+
"""`hotdata._auth` existed only to run the key exchange."""
|
|
130
|
+
with pytest.raises(ModuleNotFoundError):
|
|
131
|
+
importlib.import_module("hotdata._auth")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_api_key_is_a_plain_attribute() -> None:
|
|
135
|
+
"""A property getter is what made every read a potential mint."""
|
|
136
|
+
assert not isinstance(getattr(Configuration, "api_key", None), property)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@pytest.mark.parametrize(
|
|
140
|
+
"path",
|
|
141
|
+
[CONFIGURATION_MODULE, CONFIGURATION_TEMPLATE],
|
|
142
|
+
ids=["generated", "template"],
|
|
143
|
+
)
|
|
144
|
+
def test_no_exchange_machinery_in_configuration(path: Path) -> None:
|
|
145
|
+
"""Editing only the generated file would be undone by the next regen."""
|
|
146
|
+
source = path.read_text()
|
|
147
|
+
|
|
148
|
+
for needle in (
|
|
149
|
+
"_TokenManager",
|
|
150
|
+
"_token_manager",
|
|
151
|
+
"_auth import",
|
|
152
|
+
"/v1/auth/jwt",
|
|
153
|
+
"refresh_token",
|
|
154
|
+
"HOTDATA_DISABLE_JWT_EXCHANGE",
|
|
155
|
+
):
|
|
156
|
+
assert needle not in source, f"{path.name} still references {needle}"
|
|
@@ -87,11 +87,6 @@ def _install_fake_response(
|
|
|
87
87
|
) -> None:
|
|
88
88
|
"""Replace RESTClientObject.request with a stub that records the call."""
|
|
89
89
|
|
|
90
|
-
# The api_key used here ("test-key") is a dummy, not a real token. Disable
|
|
91
|
-
# transparent JWT exchange so auth_settings() does not try to mint one
|
|
92
|
-
# against a non-existent endpoint when the (stubbed) request is built.
|
|
93
|
-
monkeypatch.setenv("HOTDATA_DISABLE_JWT_EXCHANGE", "1")
|
|
94
|
-
|
|
95
90
|
from hotdata import rest
|
|
96
91
|
|
|
97
92
|
def fake_request(
|
|
@@ -32,13 +32,11 @@ from hotdata.api.results_api import ResultsApi
|
|
|
32
32
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
33
33
|
CONFIGURATION_TEMPLATE = REPO_ROOT / ".openapi-generator-templates" / "configuration.mustache"
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
# request is ever made from these tests.
|
|
37
|
-
FAKE_JWT = "eyJfake.payload.signature"
|
|
35
|
+
FAKE_API_TOKEN = "hd_fake_api_token"
|
|
38
36
|
|
|
39
37
|
|
|
40
38
|
def _config(**kwargs) -> Configuration:
|
|
41
|
-
return Configuration(host="https://api.example.test", api_key=
|
|
39
|
+
return Configuration(host="https://api.example.test", api_key=FAKE_API_TOKEN, **kwargs)
|
|
42
40
|
|
|
43
41
|
|
|
44
42
|
def _typed_api_key_kwargs() -> list[str]:
|