scalekit-sdk-python 2.21.0__tar.gz → 2.23.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.
- {scalekit_sdk_python-2.21.0/scalekit_sdk_python.egg-info → scalekit_sdk_python-2.23.0}/PKG-INFO +3 -2
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/README.md +1 -1
- scalekit_sdk_python-2.23.0/scalekit/__init__.py +22 -0
- scalekit_sdk_python-2.23.0/scalekit/_convert.py +163 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/_version.py +1 -1
- scalekit_sdk_python-2.23.0/scalekit/actions/__init__.py +112 -0
- scalekit_sdk_python-2.23.0/scalekit/actions/_proxy_path.py +254 -0
- scalekit_sdk_python-2.23.0/scalekit/actions/_resumable_upload.py +901 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/actions.py +370 -22
- scalekit_sdk_python-2.23.0/scalekit/actions/connections/__init__.py +26 -0
- scalekit_sdk_python-2.23.0/scalekit/actions/connections/errors.py +148 -0
- scalekit_sdk_python-2.23.0/scalekit/actions/connections/models.py +257 -0
- scalekit_sdk_python-2.23.0/scalekit/actions/connections/service.py +352 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/custom_provider.py +20 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/mcp_config.py +34 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/requests/list_providers_request.py +3 -2
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/get_connected_account_auth_response.py +5 -1
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/list_connected_accounts_response.py +20 -2
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/list_providers_response.py +11 -1
- scalekit_sdk_python-2.23.0/scalekit/actions/models/trigger_event.py +192 -0
- scalekit_sdk_python-2.23.0/scalekit/actions/models/triggers.py +1247 -0
- scalekit_sdk_python-2.23.0/scalekit/actions/models/upload_progress.py +32 -0
- scalekit_sdk_python-2.23.0/scalekit/actions/triggers.py +1052 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/types.py +100 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/client.py +25 -59
- scalekit_sdk_python-2.23.0/scalekit/common/_webhook_signature.py +165 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/common/exceptions.py +106 -2
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/connected_accounts.py +18 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/core.py +1 -1
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/mcp.py +6 -1
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/providers.py +3 -1
- scalekit_sdk_python-2.23.0/scalekit/triggers.py +390 -0
- scalekit_sdk_python-2.23.0/scalekit/v1/billing/billing_pb2.py +277 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/billing/billing_pb2.pyi +253 -4
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/billing/billing_pb2_grpc.py +217 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/clients/clients_pb2.py +214 -208
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/clients/clients_pb2.pyi +12 -6
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/commons/commons_pb2.py +6 -6
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/commons/commons_pb2.pyi +2 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/connected_accounts/connected_accounts_pb2.py +71 -57
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/connected_accounts/connected_accounts_pb2.pyi +16 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/connected_accounts/connected_accounts_pb2_grpc.py +34 -0
- scalekit_sdk_python-2.23.0/scalekit/v1/connections/connections_pb2.py +920 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/connections/connections_pb2.pyi +222 -2
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/connections/connections_pb2_grpc.py +132 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/events/events_pb2.py +8 -8
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/events/events_pb2.pyi +2 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/mcp/mcp_pb2.py +74 -68
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/mcp/mcp_pb2.pyi +18 -4
- scalekit_sdk_python-2.23.0/scalekit/v1/providers/providers_pb2.py +282 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/providers/providers_pb2.pyi +54 -6
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/providers/providers_pb2_grpc.py +67 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/tools/tools_pb2.py +42 -40
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/tools/tools_pb2.pyi +4 -2
- scalekit_sdk_python-2.23.0/scalekit/v1/triggers/triggers_pb2.py +642 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/triggers/triggers_pb2.pyi +360 -4
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/triggers/triggers_pb2_grpc.py +303 -1
- scalekit_sdk_python-2.23.0/scalekit/v1/workspaces/workspaces_pb2.py +279 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/workspaces/workspaces_pb2.pyi +4 -2
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0/scalekit_sdk_python.egg-info}/PKG-INFO +3 -2
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit_sdk_python.egg-info/SOURCES.txt +23 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit_sdk_python.egg-info/requires.txt +1 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/setup.py +1 -0
- scalekit_sdk_python-2.23.0/tests/test_actions_request_path.py +555 -0
- scalekit_sdk_python-2.23.0/tests/test_actions_response_fields.py +118 -0
- scalekit_sdk_python-2.23.0/tests/test_agentkit_connections.py +217 -0
- scalekit_sdk_python-2.23.0/tests/test_list_connected_accounts_query_and_org_wide.py +78 -0
- scalekit_sdk_python-2.23.0/tests/test_mcp_tool_exposure.py +34 -0
- scalekit_sdk_python-2.23.0/tests/test_resumable_upload.py +1325 -0
- scalekit_sdk_python-2.23.0/tests/test_resumable_upload_live.py +214 -0
- scalekit_sdk_python-2.23.0/tests/test_trigger_events.py +1049 -0
- scalekit_sdk_python-2.23.0/tests/test_triggers_api.py +1233 -0
- scalekit_sdk_python-2.23.0/tests/test_triggers_api_live.py +400 -0
- scalekit_sdk_python-2.21.0/scalekit/__init__.py +0 -5
- scalekit_sdk_python-2.21.0/scalekit/actions/__init__.py +0 -5
- scalekit_sdk_python-2.21.0/scalekit/v1/billing/billing_pb2.py +0 -171
- scalekit_sdk_python-2.21.0/scalekit/v1/connections/connections_pb2.py +0 -751
- scalekit_sdk_python-2.21.0/scalekit/v1/providers/providers_pb2.py +0 -190
- scalekit_sdk_python-2.21.0/scalekit/v1/triggers/triggers_pb2.py +0 -376
- scalekit_sdk_python-2.21.0/scalekit/v1/workspaces/workspaces_pb2.py +0 -277
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/LICENSE +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/buf/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/buf/validate/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/buf/validate/validate_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/buf/validate/validate_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/buf/validate/validate_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/pyproject.toml +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/frameworks/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/frameworks/google_adk.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/frameworks/langchain.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/frameworks/types/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/frameworks/types/google_adk_tool.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/frameworks/util.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/mcp_connection_auth_state.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/mcp_instance.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/requests/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/requests/create_connected_account_request.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/requests/create_custom_provider_request.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/requests/delete_custom_provider_request.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/requests/mcp_request.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/requests/tool_request.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/requests/update_connected_account_request.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/requests/update_custom_provider_request.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/create_connected_account_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/create_custom_provider_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/create_mcp_config_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/create_mcp_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/create_mcp_session_token_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/delete_connected_account_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/delete_custom_provider_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/delete_mcp_config_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/delete_mcp_instance_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/ensure_mcp_instance_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/execute_tool_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/get_mcp_instance_auth_state_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/get_mcp_instance_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/get_mcp_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/list_mcp_configs_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/list_mcp_connected_accounts_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/list_mcp_instances_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/list_tools_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/magic_link_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/update_connected_account_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/update_custom_provider_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/update_mcp_config_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/update_mcp_instance_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/responses/verify_connected_account_user_response.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/tool_input_output.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/models/tool_mapping.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/actions/modifier.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/auth.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/common/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/common/scalekit.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/common/user.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/connection.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/constants/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/constants/user.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/directory.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/domain.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/events.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/frameworks/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/frameworks/django.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/frameworks/fastapi.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/frameworks/flask.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/m2m_client.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/middleware/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/middleware/csrf_state.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/middleware/protocol.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/middleware/session_crypto.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/middleware/session_manager.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/organization.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/passwordless.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/permissions.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/resource.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/role.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/sessions.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/token.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/tools.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/users.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/utils/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/utils/directory.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/agentkit_logs/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/agentkit_logs/agentkit_analytics_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/agentkit_logs/agentkit_analytics_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/agentkit_logs/agentkit_analytics_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/agentkit_logs/agentkit_logs_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/agentkit_logs/agentkit_logs_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/agentkit_logs/agentkit_logs_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auditlogs/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auditlogs/auditlogs_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auditlogs/auditlogs_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auditlogs/auditlogs_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/auth_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/auth_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/auth_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/passwordless_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/passwordless_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/passwordless_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/totp_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/totp_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/totp_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/webauthn_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/webauthn_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/auth/webauthn_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/billing/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/clients/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/clients/clients_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/commons/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/commons/commons_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/connected_accounts/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/connections/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/directories/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/directories/directories_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/directories/directories_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/directories/directories_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/domains/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/domains/domains_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/domains/domains_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/domains/domains_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/emails/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/emails/emails_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/emails/emails_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/emails/emails_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/environments/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/environments/environments_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/environments/environments_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/environments/environments_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/errdetails/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/errdetails/errdetails_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/errdetails/errdetails_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/errdetails/errdetails_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/events/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/events/events_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/gateway/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/gateway/gateway_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/gateway/gateway_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/gateway/gateway_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/interceptors/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/interceptors/interceptors_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/interceptors/interceptors_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/interceptors/interceptors_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/keys/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/keys/keys_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/keys/keys_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/keys/keys_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/logstreams/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/logstreams/logstreams_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/logstreams/logstreams_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/logstreams/logstreams_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/mcp/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/mcp/mcp_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/members/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/members/members_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/members/members_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/members/members_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/migrations/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/migrations/migrations_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/migrations/migrations_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/migrations/migrations_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/options/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/options/options_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/options/options_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/options/options_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/organizations/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/organizations/organizations_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/organizations/organizations_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/organizations/organizations_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/providers/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/roles/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/roles/roles_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/roles/roles_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/roles/roles_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/secrets/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/secrets/secrets_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/secrets/secrets_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/secrets/secrets_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/sessions/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/sessions/sessions_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/sessions/sessions_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/sessions/sessions_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/tokens/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/tokens/tokens_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/tokens/tokens_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/tokens/tokens_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/tools/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/tools/tools_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/triggers/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/user_attributes/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/user_attributes/user_attributes_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/user_attributes/user_attributes_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/user_attributes/user_attributes_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/users/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/users/users_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/users/users_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/users/users_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/webhooks/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/webhooks/webhooks_pb2.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/webhooks/webhooks_pb2.pyi +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/webhooks/webhooks_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/workspaces/__init__.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/v1/workspaces/workspaces_pb2_grpc.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit/webauthn.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit_sdk_python.egg-info/dependency_links.txt +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/scalekit_sdk_python.egg-info/top_level.txt +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/setup.cfg +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_actions.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_actions_list_connected_accounts_pagination.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_call_timeout.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_connected_accounts.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_connection.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_directory.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_domain.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_events.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_http_timeout.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_keepalive_channel_options.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_login.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_m2m_client.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_mcp.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_mcp_connection_session_token.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_mcp_session_token_access_level.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_middleware_csrf_state.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_middleware_django.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_middleware_fastapi.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_middleware_flask.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_middleware_session_crypto.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_middleware_session_manager.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_multi_issuer.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_org_roles.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_organization.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_organization_session_policy.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_organization_slug_logo.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_passwordless.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_permissions.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_providers.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_resource.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_role_defaults_dependent.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_roles.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_sessions.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_sk819_retry_behavior.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_tokens.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_tools.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_transient_error_no_retry.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_user_roles_permissions.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_users.py +0 -0
- {scalekit_sdk_python-2.21.0 → scalekit_sdk_python-2.23.0}/tests/test_webauthn_credentials.py +0 -0
{scalekit_sdk_python-2.21.0/scalekit_sdk_python.egg-info → scalekit_sdk_python-2.23.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scalekit-sdk-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.23.0
|
|
4
4
|
Summary: Scalekit official Python SDK
|
|
5
5
|
Home-page: https://github.com/scalekit-inc/scalekit-sdk-python
|
|
6
6
|
Author: Team Scalekit
|
|
@@ -15,6 +15,7 @@ Requires-Dist: grpcio<2.0,>=1.81.0
|
|
|
15
15
|
Requires-Dist: protobuf<8.0.0,>=5.29.5
|
|
16
16
|
Requires-Dist: google>=3.0
|
|
17
17
|
Requires-Dist: requests>=2.34.0
|
|
18
|
+
Requires-Dist: urllib3<3,>=1.26
|
|
18
19
|
Requires-Dist: PyJWT>=2.13.0
|
|
19
20
|
Requires-Dist: cffi>=1.15.1
|
|
20
21
|
Requires-Dist: cryptography>=50.0.0
|
|
@@ -60,7 +61,7 @@ Dynamic: summary
|
|
|
60
61
|
[](https://opensource.org/licenses/MIT)
|
|
61
62
|
[](https://pypi.org/project/scalekit-sdk-python/)
|
|
62
63
|
|
|
63
|
-
<a href="https://scalekit.com" target="_blank" rel="noopener noreferrer">Scalekit</a> provides auth and actions on behalf of users, with
|
|
64
|
+
<a href="https://scalekit.com" target="_blank" rel="noopener noreferrer">Scalekit</a> provides auth and actions on behalf of users, with 600+ connectors and 35,000+ tools. Build secure AI products faster with authentication for humans (SSO, passwordless, full-stack auth) and agents (MCP/APIs, delegated actions), all unified on one platform. This Python SDK enables both traditional B2B authentication and cutting-edge agentic workflows.
|
|
64
65
|
|
|
65
66
|
## 🤖 Agent-First Features
|
|
66
67
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
[](https://pypi.org/project/scalekit-sdk-python/)
|
|
15
15
|
|
|
16
|
-
<a href="https://scalekit.com" target="_blank" rel="noopener noreferrer">Scalekit</a> provides auth and actions on behalf of users, with
|
|
16
|
+
<a href="https://scalekit.com" target="_blank" rel="noopener noreferrer">Scalekit</a> provides auth and actions on behalf of users, with 600+ connectors and 35,000+ tools. Build secure AI products faster with authentication for humans (SSO, passwordless, full-stack auth) and agents (MCP/APIs, delegated actions), all unified on one platform. This Python SDK enables both traditional B2B authentication and cutting-edge agentic workflows.
|
|
17
17
|
|
|
18
18
|
## 🤖 Agent-First Features
|
|
19
19
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
from scalekit.client import ScalekitClient
|
|
3
|
+
from scalekit.common.scalekit import CodeAuthenticationOptions, AuthorizationUrlOptions
|
|
4
|
+
from scalekit.actions.models.trigger_event import (
|
|
5
|
+
DeliveryScope,
|
|
6
|
+
DetectionMode,
|
|
7
|
+
PayloadState,
|
|
8
|
+
TriggerEvent,
|
|
9
|
+
)
|
|
10
|
+
from scalekit.actions.triggers import HeadersLike, verify_trigger_event
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'ScalekitClient',
|
|
14
|
+
'AuthorizationUrlOptions',
|
|
15
|
+
'CodeAuthenticationOptions',
|
|
16
|
+
'DeliveryScope',
|
|
17
|
+
'DetectionMode',
|
|
18
|
+
'HeadersLike',
|
|
19
|
+
'PayloadState',
|
|
20
|
+
'TriggerEvent',
|
|
21
|
+
'verify_trigger_event',
|
|
22
|
+
]
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Private converters between protobuf values and SDK types.
|
|
2
|
+
|
|
3
|
+
One place for the rules every SDK-owned model follows: timestamps are read only when
|
|
4
|
+
the field is present and come out as timezone-aware UTC ``datetime``; inputs must be
|
|
5
|
+
timezone-aware; enum values the SDK does not know never raise; durations are
|
|
6
|
+
``timedelta``. Nothing here imports the rest of the SDK, so any module can use it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
from datetime import date, datetime, timedelta, timezone
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import TypeVar
|
|
16
|
+
|
|
17
|
+
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
|
|
18
|
+
from google.protobuf.message import Message
|
|
19
|
+
from google.protobuf.timestamp_pb2 import Timestamp
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"date_to_str",
|
|
23
|
+
"datetime_to_timestamp",
|
|
24
|
+
"enum_from_proto",
|
|
25
|
+
"enum_to_proto",
|
|
26
|
+
"require",
|
|
27
|
+
"seconds_from_proto",
|
|
28
|
+
"str_list",
|
|
29
|
+
"timedelta_to_seconds",
|
|
30
|
+
"timestamp_from_proto",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
_E = TypeVar("_E", bound=Enum)
|
|
34
|
+
_CAMEL_BOUNDARY = re.compile(r"(?<!^)(?=[A-Z])")
|
|
35
|
+
_ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}")
|
|
36
|
+
_ONE_SECOND = timedelta(seconds=1)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def require(name: str, value: object) -> None:
|
|
40
|
+
"""Raise ``ValueError`` when a required identifier is missing or empty."""
|
|
41
|
+
if value is None or value == "":
|
|
42
|
+
raise ValueError(f"{name} is required")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def str_list(name: str, values: Sequence[str]) -> list[str]:
|
|
46
|
+
"""Copy ``values`` into a list, refusing a bare ``str`` (which would split into characters)."""
|
|
47
|
+
if isinstance(values, (str, bytes)):
|
|
48
|
+
raise TypeError(f"{name} must be a list of strings, not a single string")
|
|
49
|
+
return list(values)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _enum_prefix(wrapper: EnumTypeWrapper) -> str:
|
|
53
|
+
"""``TriggerDeliveryStatus`` -> ``"TRIGGER_DELIVERY_STATUS_"``."""
|
|
54
|
+
return _CAMEL_BOUNDARY.sub("_", wrapper.DESCRIPTOR.name).upper() + "_"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def enum_from_proto(enum_cls: type[_E], wrapper: EnumTypeWrapper, value: int) -> _E | str | None:
|
|
58
|
+
"""Convert a proto enum number to an SDK enum member.
|
|
59
|
+
|
|
60
|
+
``0`` (the ``*_UNSPECIFIED`` value) becomes ``None``. A value the generated code
|
|
61
|
+
knows is looked up by its name without the ``<ENUM_NAME>_`` prefix; when the SDK
|
|
62
|
+
enum has no such member, that name is returned as a plain ``str``. A number the
|
|
63
|
+
generated code does not know is returned as ``str(value)``. Never raises.
|
|
64
|
+
"""
|
|
65
|
+
if value == 0:
|
|
66
|
+
return None
|
|
67
|
+
try:
|
|
68
|
+
name: str = wrapper.Name(value)
|
|
69
|
+
except ValueError:
|
|
70
|
+
return str(value)
|
|
71
|
+
name = name.removeprefix(_enum_prefix(wrapper))
|
|
72
|
+
try:
|
|
73
|
+
return enum_cls[name]
|
|
74
|
+
except KeyError:
|
|
75
|
+
return name
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def enum_to_proto(wrapper: EnumTypeWrapper, value: Enum | str | int, field: str) -> int:
|
|
79
|
+
"""Encode an enum member, a value name or a raw number as a proto enum number.
|
|
80
|
+
|
|
81
|
+
Names are accepted with or without the ``<ENUM_NAME>_`` prefix. A raw ``int`` is
|
|
82
|
+
sent as is, so values newer than this SDK can still be sent. Raises ``ValueError``
|
|
83
|
+
for a name the generated code does not know (it cannot be encoded).
|
|
84
|
+
"""
|
|
85
|
+
if isinstance(value, bool):
|
|
86
|
+
raise TypeError(f"{field} must be an enum member, a name or an int, not bool")
|
|
87
|
+
if isinstance(value, Enum):
|
|
88
|
+
name = value.name
|
|
89
|
+
elif isinstance(value, str):
|
|
90
|
+
name = value
|
|
91
|
+
elif isinstance(value, int):
|
|
92
|
+
return value
|
|
93
|
+
else:
|
|
94
|
+
raise TypeError(f"{field} must be an enum member, a name or an int")
|
|
95
|
+
prefix = _enum_prefix(wrapper)
|
|
96
|
+
for candidate in (name, prefix + name):
|
|
97
|
+
try:
|
|
98
|
+
number: int = wrapper.Value(candidate)
|
|
99
|
+
except ValueError:
|
|
100
|
+
continue
|
|
101
|
+
return number
|
|
102
|
+
known = ", ".join(
|
|
103
|
+
v.name.removeprefix(prefix) for v in wrapper.DESCRIPTOR.values if v.number != 0
|
|
104
|
+
)
|
|
105
|
+
raise ValueError(f"unknown {field} {value!r}; expected one of: {known}, or an int")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def timestamp_from_proto(message: Message, field: str) -> datetime | None:
|
|
109
|
+
"""Read a ``Timestamp`` field as aware UTC ``datetime``; ``None`` when it is unset."""
|
|
110
|
+
if not message.HasField(field):
|
|
111
|
+
return None
|
|
112
|
+
result: datetime = getattr(message, field).ToDatetime(tzinfo=timezone.utc)
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def datetime_to_timestamp(value: datetime, field: str) -> Timestamp:
|
|
117
|
+
"""Encode an aware ``datetime`` as a ``Timestamp``; naive values raise ``ValueError``."""
|
|
118
|
+
if not isinstance(value, datetime):
|
|
119
|
+
raise TypeError(f"{field} must be a datetime, got {type(value).__name__}")
|
|
120
|
+
if value.tzinfo is None or value.utcoffset() is None:
|
|
121
|
+
raise ValueError(f"{field} must be timezone-aware, for example datetime.now(timezone.utc)")
|
|
122
|
+
timestamp = Timestamp()
|
|
123
|
+
timestamp.FromDatetime(value.astimezone(timezone.utc))
|
|
124
|
+
return timestamp
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def seconds_from_proto(seconds: int) -> timedelta | None:
|
|
128
|
+
"""A whole-seconds proto integer as ``timedelta``; ``0`` (not set) becomes ``None``."""
|
|
129
|
+
return timedelta(seconds=seconds) if seconds else None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def timedelta_to_seconds(value: timedelta, field: str) -> int:
|
|
133
|
+
"""Encode a ``timedelta`` as whole seconds; a fractional second raises ``ValueError``."""
|
|
134
|
+
if not isinstance(value, timedelta):
|
|
135
|
+
raise TypeError(f"{field} must be a timedelta, got {type(value).__name__}")
|
|
136
|
+
if value % _ONE_SECOND:
|
|
137
|
+
raise ValueError(f"{field} must be a whole number of seconds, got {value!r}")
|
|
138
|
+
return value // _ONE_SECOND
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def date_to_str(value: date | str, field: str) -> str:
|
|
142
|
+
"""Encode a calendar date as ``YYYY-MM-DD``.
|
|
143
|
+
|
|
144
|
+
A ``date`` is used as is; an aware ``datetime`` is converted to its UTC date (a
|
|
145
|
+
naive one raises ``ValueError``); a ``str`` must already be a real ``YYYY-MM-DD``
|
|
146
|
+
date.
|
|
147
|
+
"""
|
|
148
|
+
if isinstance(value, datetime):
|
|
149
|
+
if value.tzinfo is None or value.utcoffset() is None:
|
|
150
|
+
raise ValueError(f"{field} must be a date or a timezone-aware datetime")
|
|
151
|
+
return value.astimezone(timezone.utc).date().isoformat()
|
|
152
|
+
if isinstance(value, date):
|
|
153
|
+
return value.isoformat()
|
|
154
|
+
if isinstance(value, str):
|
|
155
|
+
if _ISO_DATE.fullmatch(value):
|
|
156
|
+
try:
|
|
157
|
+
date.fromisoformat(value)
|
|
158
|
+
except ValueError:
|
|
159
|
+
pass
|
|
160
|
+
else:
|
|
161
|
+
return value
|
|
162
|
+
raise ValueError(f"{field} must be a YYYY-MM-DD date, got {value!r}")
|
|
163
|
+
raise TypeError(f"{field} must be a date or a YYYY-MM-DD string, got {type(value).__name__}")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from .actions import ActionClient
|
|
2
|
+
from .models.trigger_event import DeliveryScope, DetectionMode, PayloadState, TriggerEvent
|
|
3
|
+
from .models.triggers import (
|
|
4
|
+
AccountHealth,
|
|
5
|
+
AccountHealthAction,
|
|
6
|
+
AccountHealthState,
|
|
7
|
+
AvailableTrigger,
|
|
8
|
+
BatchCreateTriggerSubscriptionResult,
|
|
9
|
+
BatchCreateTriggerSubscriptionsResponse,
|
|
10
|
+
BatchDeleteTriggerSubscriptionResult,
|
|
11
|
+
BatchDeleteTriggerSubscriptionsResponse,
|
|
12
|
+
BatchLookupTriggerDeliveriesResponse,
|
|
13
|
+
BatchLookupTriggerDeliveryResult,
|
|
14
|
+
BatchResendTriggerDeliveriesResponse,
|
|
15
|
+
BatchResendTriggerDeliveryResult,
|
|
16
|
+
ConfigField,
|
|
17
|
+
EligibleAccount,
|
|
18
|
+
GenerateTriggerSigningSecretResponse,
|
|
19
|
+
GetTriggerActivityResponse,
|
|
20
|
+
GetTriggerUsageResponse,
|
|
21
|
+
ListAvailableTriggersResponse,
|
|
22
|
+
ListEligibleAccountsResponse,
|
|
23
|
+
ListTriggerDeliveriesByEndpointResponse,
|
|
24
|
+
ListTriggerDeliveryAttemptsByEndpointResponse,
|
|
25
|
+
ListTriggerDeliveryAttemptsByMessageResponse,
|
|
26
|
+
ListTriggerSubscriptionsResponse,
|
|
27
|
+
LookupTriggerDeliveryResponse,
|
|
28
|
+
RecoverTriggerDeliveriesResponse,
|
|
29
|
+
RequiredScope,
|
|
30
|
+
TriggerDelivery,
|
|
31
|
+
TriggerDeliveryAttempt,
|
|
32
|
+
TriggerDeliveryAttemptStatus,
|
|
33
|
+
TriggerDeliveryAttemptTriggerType,
|
|
34
|
+
TriggerDeliveryLookupOutcome,
|
|
35
|
+
TriggerDeliveryResendOutcome,
|
|
36
|
+
TriggerDeliveryResendTarget,
|
|
37
|
+
TriggerDeliveryResendTargetParam,
|
|
38
|
+
TriggerDeliveryStatus,
|
|
39
|
+
TriggerIngressEndpoint,
|
|
40
|
+
TriggerPlatformCredentials,
|
|
41
|
+
TriggerPollErrorCode,
|
|
42
|
+
TriggerPollState,
|
|
43
|
+
TriggerRefusalCode,
|
|
44
|
+
TriggerSubscribeOutcome,
|
|
45
|
+
TriggerSubscription,
|
|
46
|
+
TriggerSubscriptionDeleteOutcome,
|
|
47
|
+
TriggerSubscriptionStatus,
|
|
48
|
+
TriggerUsageBucket,
|
|
49
|
+
TriggerUsageDay,
|
|
50
|
+
TriggerUsageHour,
|
|
51
|
+
TriggerUsagePeriod,
|
|
52
|
+
)
|
|
53
|
+
from .triggers import ActionTriggers, HeadersLike, verify_trigger_event
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
'AccountHealth',
|
|
57
|
+
'AccountHealthAction',
|
|
58
|
+
'AccountHealthState',
|
|
59
|
+
'ActionClient',
|
|
60
|
+
'ActionTriggers',
|
|
61
|
+
'AvailableTrigger',
|
|
62
|
+
'BatchCreateTriggerSubscriptionResult',
|
|
63
|
+
'BatchCreateTriggerSubscriptionsResponse',
|
|
64
|
+
'BatchDeleteTriggerSubscriptionResult',
|
|
65
|
+
'BatchDeleteTriggerSubscriptionsResponse',
|
|
66
|
+
'BatchLookupTriggerDeliveriesResponse',
|
|
67
|
+
'BatchLookupTriggerDeliveryResult',
|
|
68
|
+
'BatchResendTriggerDeliveriesResponse',
|
|
69
|
+
'BatchResendTriggerDeliveryResult',
|
|
70
|
+
'ConfigField',
|
|
71
|
+
'DeliveryScope',
|
|
72
|
+
'DetectionMode',
|
|
73
|
+
'EligibleAccount',
|
|
74
|
+
'GenerateTriggerSigningSecretResponse',
|
|
75
|
+
'GetTriggerActivityResponse',
|
|
76
|
+
'GetTriggerUsageResponse',
|
|
77
|
+
'HeadersLike',
|
|
78
|
+
'ListAvailableTriggersResponse',
|
|
79
|
+
'ListEligibleAccountsResponse',
|
|
80
|
+
'ListTriggerDeliveriesByEndpointResponse',
|
|
81
|
+
'ListTriggerDeliveryAttemptsByEndpointResponse',
|
|
82
|
+
'ListTriggerDeliveryAttemptsByMessageResponse',
|
|
83
|
+
'ListTriggerSubscriptionsResponse',
|
|
84
|
+
'LookupTriggerDeliveryResponse',
|
|
85
|
+
'PayloadState',
|
|
86
|
+
'RecoverTriggerDeliveriesResponse',
|
|
87
|
+
'RequiredScope',
|
|
88
|
+
'TriggerDelivery',
|
|
89
|
+
'TriggerDeliveryAttempt',
|
|
90
|
+
'TriggerDeliveryAttemptStatus',
|
|
91
|
+
'TriggerDeliveryAttemptTriggerType',
|
|
92
|
+
'TriggerDeliveryLookupOutcome',
|
|
93
|
+
'TriggerDeliveryResendOutcome',
|
|
94
|
+
'TriggerDeliveryResendTarget',
|
|
95
|
+
'TriggerDeliveryResendTargetParam',
|
|
96
|
+
'TriggerDeliveryStatus',
|
|
97
|
+
'TriggerEvent',
|
|
98
|
+
'TriggerIngressEndpoint',
|
|
99
|
+
'TriggerPlatformCredentials',
|
|
100
|
+
'TriggerPollErrorCode',
|
|
101
|
+
'TriggerPollState',
|
|
102
|
+
'TriggerRefusalCode',
|
|
103
|
+
'TriggerSubscribeOutcome',
|
|
104
|
+
'TriggerSubscription',
|
|
105
|
+
'TriggerSubscriptionDeleteOutcome',
|
|
106
|
+
'TriggerSubscriptionStatus',
|
|
107
|
+
'TriggerUsageBucket',
|
|
108
|
+
'TriggerUsageDay',
|
|
109
|
+
'TriggerUsageHour',
|
|
110
|
+
'TriggerUsagePeriod',
|
|
111
|
+
'verify_trigger_event',
|
|
112
|
+
]
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""Keep ``ActionClient.request`` and its credentials inside the Scalekit HTTP proxy.
|
|
2
|
+
|
|
3
|
+
``ActionClient.request`` sends every call to ``{env_url}/proxy{path}`` with the
|
|
4
|
+
client's bearer token and the connection headers. This module
|
|
5
|
+
|
|
6
|
+
* verifies, before anything is sent, that the server will route the path under
|
|
7
|
+
``<env base path>/proxy/`` (``ensure_under_proxy_prefix``), and
|
|
8
|
+
* removes those credentials from any redirect hop whose target is outside that
|
|
9
|
+
prefix (``ProxySession``), while following redirects exactly as ``requests``
|
|
10
|
+
does.
|
|
11
|
+
|
|
12
|
+
It never rewrites a URL: a request that passes is sent exactly as built.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import contextlib
|
|
18
|
+
import re
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from typing import cast
|
|
21
|
+
from urllib.parse import unquote
|
|
22
|
+
|
|
23
|
+
import requests
|
|
24
|
+
from requests.adapters import HTTPAdapter
|
|
25
|
+
from urllib3.exceptions import LocationParseError
|
|
26
|
+
from urllib3.util import parse_url
|
|
27
|
+
|
|
28
|
+
_PROXY_PREFIX = "/proxy/"
|
|
29
|
+
|
|
30
|
+
_VALID_ESCAPE_RE = re.compile(r"%[0-9A-Fa-f]{2}")
|
|
31
|
+
|
|
32
|
+
# Headers request() adds that identify the caller to Scalekit. Header names are
|
|
33
|
+
# case-insensitive in requests, so "Connection_name" is covered too.
|
|
34
|
+
_CREDENTIAL_HEADERS = ("Authorization", "connection_name", "identifier")
|
|
35
|
+
|
|
36
|
+
# Typed handles on two requests.Session methods. Some stub distributions
|
|
37
|
+
# (types-requests) declare them without annotations; calling them through these
|
|
38
|
+
# keeps strict type checking without suppressions.
|
|
39
|
+
_session_rebuild_auth = cast(
|
|
40
|
+
"Callable[[requests.Session, requests.PreparedRequest, requests.Response], None]",
|
|
41
|
+
requests.Session.rebuild_auth,
|
|
42
|
+
)
|
|
43
|
+
_session_should_strip_auth = cast(
|
|
44
|
+
"Callable[[requests.Session, str, str], bool]",
|
|
45
|
+
requests.Session.should_strip_auth,
|
|
46
|
+
)
|
|
47
|
+
# Only asked for request-targets (request_url); it never sends anything.
|
|
48
|
+
_URL_ADAPTER = HTTPAdapter()
|
|
49
|
+
|
|
50
|
+
# A proxy for every URL: request_url uses only whether one applies and its scheme.
|
|
51
|
+
_ANY_FORWARD_PROXY = {"all": "http://forward-proxy.invalid"}
|
|
52
|
+
|
|
53
|
+
_OUTSIDE_PROXY_MESSAGE = (
|
|
54
|
+
"path must resolve under the proxy prefix: start it with '/' and do not let '..' "
|
|
55
|
+
"segments (including percent-encoded or backslash forms) climb above it"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def ensure_under_proxy_prefix(env_url: str, url: str) -> None:
|
|
60
|
+
r"""Raise ``ValueError`` if the server would route ``url`` outside ``<base>/proxy/``.
|
|
61
|
+
|
|
62
|
+
The check starts from the path ``requests`` actually sends for ``url``
|
|
63
|
+
(``requests`` and ``urllib3`` may already have removed dot segments or
|
|
64
|
+
decoded ``%2e``). For a URL that is not ``https``, a forward proxy may be
|
|
65
|
+
used: ``requests`` then hands ``urllib3`` the absolute URL, and both
|
|
66
|
+
rebuild it (dropping an empty ``;`` parameter, removing dot segments
|
|
67
|
+
again), so that path is checked as well.
|
|
68
|
+
Each path is percent-decoded once and cleaned as Go
|
|
69
|
+
routers clean it (``clean_path``): runs of ``/`` collapse and dot
|
|
70
|
+
segments are resolved. This is done twice, as is and with ``\`` treated as
|
|
71
|
+
``/``, and each result must equal ``<base>/proxy`` or start with
|
|
72
|
+
``<base>/proxy/``. ``<base>`` is the path of ``env_url``, usually empty.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
env_url: The environment URL the client was configured with, for
|
|
76
|
+
example ``"https://acme.scalekit.cloud"`` or
|
|
77
|
+
``"https://host/base"``.
|
|
78
|
+
url: The full URL ``request()`` is about to send, built as
|
|
79
|
+
``env_url.rstrip("/") + "/proxy" + path``.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: If any server view falls outside the proxy prefix. The
|
|
83
|
+
message does not include the path.
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
>>> ensure_under_proxy_prefix("https://h", "https://h/proxy/gmail/v1/users/me")
|
|
87
|
+
>>> ensure_under_proxy_prefix("https://h", "https://h/proxy/x/../../outside")
|
|
88
|
+
Traceback (most recent call last):
|
|
89
|
+
...
|
|
90
|
+
ValueError: path must resolve under the proxy prefix: ...
|
|
91
|
+
"""
|
|
92
|
+
prepared = requests.Request(method="GET", url=url).prepare()
|
|
93
|
+
if not all(_path_is_under_prefix(env_url, sent) for sent in _sent_paths(prepared)):
|
|
94
|
+
raise ValueError(_OUTSIDE_PROXY_MESSAGE)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class ProxySession(requests.Session):
|
|
98
|
+
"""A ``requests.Session`` that keeps proxy credentials inside the proxy prefix.
|
|
99
|
+
|
|
100
|
+
Redirects are followed exactly as ``requests`` follows them. On each hop,
|
|
101
|
+
``requests`` calls ``rebuild_auth``; before deferring to it, this removes
|
|
102
|
+
``Authorization``, ``connection_name`` and ``identifier`` when the hop's
|
|
103
|
+
target is outside ``<env base path>/proxy/``: a different origin (as
|
|
104
|
+
``requests.Session.should_strip_auth`` decides) or the same origin with a
|
|
105
|
+
path the server would route outside the prefix (the rule
|
|
106
|
+
``ensure_under_proxy_prefix`` applies). Once removed, they stay
|
|
107
|
+
removed for later hops. Hops that stay under the prefix are untouched.
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
>>> with ProxySession("https://acme.scalekit.cloud") as session:
|
|
111
|
+
... response = session.request("GET", "https://acme.scalekit.cloud/proxy/x")
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(self, env_url: str) -> None:
|
|
115
|
+
"""Create a session for proxy calls to ``env_url``."""
|
|
116
|
+
super().__init__()
|
|
117
|
+
self._env_url = env_url
|
|
118
|
+
self._prefix_url = env_url.rstrip("/") + _PROXY_PREFIX
|
|
119
|
+
|
|
120
|
+
def rebuild_auth(
|
|
121
|
+
self, prepared_request: requests.PreparedRequest, response: requests.Response
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Drop proxy credentials when a redirect leaves the proxy prefix, then defer."""
|
|
124
|
+
if not self._stays_in_proxy(prepared_request):
|
|
125
|
+
for name in _CREDENTIAL_HEADERS:
|
|
126
|
+
prepared_request.headers.pop(name, None)
|
|
127
|
+
_session_rebuild_auth(self, prepared_request, response)
|
|
128
|
+
|
|
129
|
+
def _stays_in_proxy(self, prepared_request: requests.PreparedRequest) -> bool:
|
|
130
|
+
url = prepared_request.url
|
|
131
|
+
if not url or _session_should_strip_auth(self, self._prefix_url, url):
|
|
132
|
+
return False
|
|
133
|
+
return all(
|
|
134
|
+
_path_is_under_prefix(self._env_url, sent) for sent in _sent_paths(prepared_request)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _path_is_under_prefix(env_url: str, sent: str) -> bool:
|
|
139
|
+
"""Return whether the server will route ``sent`` under ``<base>/proxy/``.
|
|
140
|
+
|
|
141
|
+
``sent`` is the path as ``requests`` puts it on the wire, so any
|
|
142
|
+
normalisation ``requests``/``urllib3`` applied is already included. Each of
|
|
143
|
+
its server views (``_server_views``) must equal ``<base>/proxy`` or start
|
|
144
|
+
with ``<base>/proxy/``. Equality admits inputs that leave the prefix and
|
|
145
|
+
come back to exactly ``<base>/proxy`` (``/..%2fproxy``): they were sent the
|
|
146
|
+
same way before this check existed, and no proxy route matches them.
|
|
147
|
+
Sibling paths such as ``<base>/proxyx`` are rejected.
|
|
148
|
+
"""
|
|
149
|
+
prefix = _wire_path(env_url.rstrip("/") + _PROXY_PREFIX)
|
|
150
|
+
for prefix_view, cleaned in zip(_server_views(prefix), _server_views(sent), strict=True):
|
|
151
|
+
bare = prefix_view.rstrip("/")
|
|
152
|
+
if cleaned != bare and not cleaned.startswith(bare + "/"):
|
|
153
|
+
return False
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _sent_paths(prepared: requests.PreparedRequest) -> list[str]:
|
|
158
|
+
"""Return the path of every request line ``prepared`` may be sent with.
|
|
159
|
+
|
|
160
|
+
``requests`` decides the request-target (``HTTPAdapter.request_url``), and
|
|
161
|
+
this asks it for both cases: without a proxy (also a SOCKS proxy or an
|
|
162
|
+
``https`` tunnel) and through a forward proxy, whether or not one is
|
|
163
|
+
configured. ``urllib3`` then writes the target as follows.
|
|
164
|
+
|
|
165
|
+
* A path (``/...``) is sent as is, except that ``urllib3`` may re-encode a
|
|
166
|
+
redirect hop's path (``_as_sent``); for a first request ``requests``
|
|
167
|
+
already did, and ``_as_sent`` changes nothing.
|
|
168
|
+
* An absolute URL (through a forward proxy, for any URL that is not
|
|
169
|
+
``https``) is sent as ``urllib3.util.parse_url`` rebuilds it. That
|
|
170
|
+
removes dot segments again and treats ``%2F`` as part of a segment;
|
|
171
|
+
``requests`` may already have dropped an empty ``;`` parameter from the
|
|
172
|
+
last segment, which can leave a ``..`` behind.
|
|
173
|
+
"""
|
|
174
|
+
paths: list[str] = []
|
|
175
|
+
for proxies in (None, _ANY_FORWARD_PROXY):
|
|
176
|
+
target = _URL_ADAPTER.request_url(prepared, proxies)
|
|
177
|
+
if target.startswith("/"):
|
|
178
|
+
paths.append(_as_sent(target.split("?", 1)[0]))
|
|
179
|
+
continue
|
|
180
|
+
# Like urllib3's urlopen, parse any other target as an absolute URL.
|
|
181
|
+
# If that fails here, the send fails the same way before writing
|
|
182
|
+
# anything, so there is no further path to check.
|
|
183
|
+
with contextlib.suppress(LocationParseError):
|
|
184
|
+
paths.append(parse_url(target).path or "/")
|
|
185
|
+
return paths
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _wire_path(url: str) -> str:
|
|
189
|
+
"""Return the path (without the query) that ``requests`` sends for ``url``.
|
|
190
|
+
|
|
191
|
+
Preparing the URL runs it through ``urllib3``'s URL parser, which already
|
|
192
|
+
applies the encoding ``_as_sent`` describes, so this is the wire form.
|
|
193
|
+
"""
|
|
194
|
+
path_url: str = requests.Request(method="GET", url=url).prepare().path_url
|
|
195
|
+
return path_url.split("?", 1)[0]
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _as_sent(path: str) -> str:
|
|
199
|
+
"""Return ``path`` as ``urllib3`` writes it in an origin-form request line.
|
|
200
|
+
|
|
201
|
+
When any ``%`` in the path does not start a valid ``%XX`` escape, ``urllib3``
|
|
202
|
+
encodes every ``%`` as ``%25``, so the server decodes ``%2F`` back to the
|
|
203
|
+
literal text ``%2F``, not to ``/``. ``urllib3`` escapes other characters as
|
|
204
|
+
well, but one decode restores those, so they do not change the server view.
|
|
205
|
+
"""
|
|
206
|
+
if path.count("%") != len(_VALID_ESCAPE_RE.findall(path)):
|
|
207
|
+
return path.replace("%", "%25")
|
|
208
|
+
return path
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _server_views(path: str) -> tuple[str, str]:
|
|
212
|
+
r"""Return how a server may route ``path``: decoded once, then cleaned.
|
|
213
|
+
|
|
214
|
+
The first view is what a Go router sees (``clean_path`` of the decoded
|
|
215
|
+
path). The second also treats ``\`` as ``/``, as some servers and proxies
|
|
216
|
+
do. Both are checked: neither view alone is stricter than the other.
|
|
217
|
+
"""
|
|
218
|
+
decoded = unquote(path)
|
|
219
|
+
return clean_path(decoded), clean_path(decoded.replace("\\", "/"))
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def clean_path(path: str) -> str:
|
|
223
|
+
"""Return ``path`` cleaned the way Go HTTP routers clean a request path.
|
|
224
|
+
|
|
225
|
+
This is Go's ``path.Clean`` on the rooted path, with a trailing slash put
|
|
226
|
+
back when ``path`` had one, which is how common Go routers clean the decoded
|
|
227
|
+
path before matching routes (and redirect to the cleaned path when it
|
|
228
|
+
differs). Empty and ``.`` segments are dropped, so runs of ``/`` collapse;
|
|
229
|
+
each ``..`` removes the segment before it, and a ``..`` at the root is
|
|
230
|
+
dropped.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
path: A decoded URL path. A missing leading ``/`` is treated as present.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
The cleaned path, always starting with ``/``.
|
|
237
|
+
|
|
238
|
+
Example:
|
|
239
|
+
>>> clean_path("/proxy//../x/./y/")
|
|
240
|
+
'/x/y/'
|
|
241
|
+
"""
|
|
242
|
+
segments: list[str] = []
|
|
243
|
+
for segment in path.split("/"):
|
|
244
|
+
if segment in ("", "."):
|
|
245
|
+
continue
|
|
246
|
+
if segment == "..":
|
|
247
|
+
if segments:
|
|
248
|
+
segments.pop()
|
|
249
|
+
continue
|
|
250
|
+
segments.append(segment)
|
|
251
|
+
cleaned = "/" + "/".join(segments)
|
|
252
|
+
if path.endswith("/") and cleaned != "/":
|
|
253
|
+
cleaned += "/"
|
|
254
|
+
return cleaned
|