skyflow-flowvault-python 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- common/.coveragerc +4 -0
- common/__init__.py +3 -0
- common/client/__init__.py +0 -0
- common/client/base_skyflow.py +345 -0
- common/client/utils/__init__.py +1 -0
- common/client/utils/_utils.py +127 -0
- common/errors/__init__.py +1 -0
- common/errors/_skyflow_error.py +17 -0
- common/generated/__init__.py +0 -0
- common/generated/rest/__init__.py +24 -0
- common/generated/rest/authentication/__init__.py +4 -0
- common/generated/rest/authentication/client.py +181 -0
- common/generated/rest/authentication/raw_client.py +241 -0
- common/generated/rest/client.py +153 -0
- common/generated/rest/core/__init__.py +52 -0
- common/generated/rest/core/api_error.py +23 -0
- common/generated/rest/core/client_wrapper.py +86 -0
- common/generated/rest/core/datetime_utils.py +28 -0
- common/generated/rest/core/file.py +67 -0
- common/generated/rest/core/force_multipart.py +16 -0
- common/generated/rest/core/http_client.py +543 -0
- common/generated/rest/core/http_response.py +55 -0
- common/generated/rest/core/jsonable_encoder.py +100 -0
- common/generated/rest/core/pydantic_utilities.py +255 -0
- common/generated/rest/core/query_encoder.py +58 -0
- common/generated/rest/core/remove_none_from_dict.py +11 -0
- common/generated/rest/core/request_options.py +35 -0
- common/generated/rest/core/serialization.py +276 -0
- common/generated/rest/environment.py +8 -0
- common/generated/rest/errors/__init__.py +9 -0
- common/generated/rest/errors/bad_request_error.py +14 -0
- common/generated/rest/errors/not_found_error.py +14 -0
- common/generated/rest/errors/unauthorized_error.py +14 -0
- common/generated/rest/py.typed +0 -0
- common/generated/rest/types/__init__.py +9 -0
- common/generated/rest/types/googlerpc_status.py +22 -0
- common/generated/rest/types/protobuf_any.py +21 -0
- common/generated/rest/types/v_1_get_auth_token_response.py +33 -0
- common/generated/rest/version.py +6 -0
- common/service_account/__init__.py +1 -0
- common/service_account/_utils.py +248 -0
- common/service_account/client/__init__.py +0 -0
- common/service_account/client/auth_client.py +13 -0
- common/utils/__init__.py +4 -0
- common/utils/_helpers.py +18 -0
- common/utils/_skyflow_messages.py +445 -0
- common/utils/_utils.py +50 -0
- common/utils/constants.py +291 -0
- common/utils/enums/__init__.py +12 -0
- common/utils/enums/content_types.py +9 -0
- common/utils/enums/detect_entities.py +73 -0
- common/utils/enums/detect_output_transcriptions.py +8 -0
- common/utils/enums/env.py +13 -0
- common/utils/enums/log_level.py +8 -0
- common/utils/enums/masking_method.py +5 -0
- common/utils/enums/redaction_type.py +7 -0
- common/utils/enums/request_method.py +8 -0
- common/utils/enums/token_mode.py +6 -0
- common/utils/enums/token_type.py +6 -0
- common/utils/logger/__init__.py +2 -0
- common/utils/logger/_log_helpers.py +47 -0
- common/utils/logger/_logger.py +50 -0
- common/utils/validations/__init__.py +11 -0
- common/utils/validations/_validations.py +267 -0
- common/vault/base_vault_client.py +122 -0
- common/vault/base_vault_controller.py +58 -0
- common/vault/data/__init__.py +2 -0
- common/vault/data/_base_insert_request.py +7 -0
- common/vault/data/_base_insert_response.py +11 -0
- skyflow/__init__.py +2 -0
- skyflow/client/__init__.py +1 -0
- skyflow/client/_http_config_builder.py +45 -0
- skyflow/client/skyflow.py +15 -0
- skyflow/error/__init__.py +3 -0
- skyflow/generated/__init__.py +0 -0
- skyflow/generated/rest/__init__.py +164 -0
- skyflow/generated/rest/_default_clients.py +32 -0
- skyflow/generated/rest/client.py +385 -0
- skyflow/generated/rest/core/__init__.py +127 -0
- skyflow/generated/rest/core/api_error.py +23 -0
- skyflow/generated/rest/core/client_wrapper.py +148 -0
- skyflow/generated/rest/core/datetime_utils.py +70 -0
- skyflow/generated/rest/core/file.py +67 -0
- skyflow/generated/rest/core/force_multipart.py +18 -0
- skyflow/generated/rest/core/http_client.py +940 -0
- skyflow/generated/rest/core/http_response.py +63 -0
- skyflow/generated/rest/core/http_sse/__init__.py +42 -0
- skyflow/generated/rest/core/http_sse/_api.py +455 -0
- skyflow/generated/rest/core/http_sse/_decoders.py +74 -0
- skyflow/generated/rest/core/http_sse/_exceptions.py +7 -0
- skyflow/generated/rest/core/http_sse/_models.py +17 -0
- skyflow/generated/rest/core/jsonable_encoder.py +133 -0
- skyflow/generated/rest/core/logging.py +107 -0
- skyflow/generated/rest/core/parse_error.py +36 -0
- skyflow/generated/rest/core/pydantic_utilities.py +486 -0
- skyflow/generated/rest/core/query_encoder.py +58 -0
- skyflow/generated/rest/core/remove_none_from_dict.py +11 -0
- skyflow/generated/rest/core/request_options.py +40 -0
- skyflow/generated/rest/core/serialization.py +347 -0
- skyflow/generated/rest/environment.py +8 -0
- skyflow/generated/rest/errors/__init__.py +53 -0
- skyflow/generated/rest/errors/bad_request_error.py +11 -0
- skyflow/generated/rest/errors/forbidden_error.py +11 -0
- skyflow/generated/rest/errors/internal_server_error.py +11 -0
- skyflow/generated/rest/errors/not_found_error.py +11 -0
- skyflow/generated/rest/errors/too_many_requests_error.py +11 -0
- skyflow/generated/rest/errors/unauthorized_error.py +11 -0
- skyflow/generated/rest/py.typed +0 -0
- skyflow/generated/rest/query/__init__.py +4 -0
- skyflow/generated/rest/query/client.py +139 -0
- skyflow/generated/rest/query/raw_client.py +239 -0
- skyflow/generated/rest/raw_client.py +86 -0
- skyflow/generated/rest/records/__init__.py +34 -0
- skyflow/generated/rest/records/client.py +685 -0
- skyflow/generated/rest/records/raw_client.py +988 -0
- skyflow/generated/rest/records/types/__init__.py +34 -0
- skyflow/generated/rest/records/types/update_request_update_type.py +5 -0
- skyflow/generated/rest/tokens/__init__.py +4 -0
- skyflow/generated/rest/tokens/client.py +246 -0
- skyflow/generated/rest/tokens/raw_client.py +507 -0
- skyflow/generated/rest/types/__init__.py +116 -0
- skyflow/generated/rest/types/column_redactions.py +33 -0
- skyflow/generated/rest/types/delete_response.py +23 -0
- skyflow/generated/rest/types/delete_response_object.py +42 -0
- skyflow/generated/rest/types/detokenize_response.py +23 -0
- skyflow/generated/rest/types/detokenize_response_object.py +54 -0
- skyflow/generated/rest/types/error_response.py +20 -0
- skyflow/generated/rest/types/error_response_error.py +28 -0
- skyflow/generated/rest/types/execute_query_record_response.py +22 -0
- skyflow/generated/rest/types/execute_query_response.py +26 -0
- skyflow/generated/rest/types/execute_query_response_metadata.py +26 -0
- skyflow/generated/rest/types/get_request_data.py +60 -0
- skyflow/generated/rest/types/get_response.py +23 -0
- skyflow/generated/rest/types/get_tokens_from_values_request_object.py +30 -0
- skyflow/generated/rest/types/get_tokens_from_values_response.py +23 -0
- skyflow/generated/rest/types/google_protobuf_value.py +8 -0
- skyflow/generated/rest/types/http_code.py +6 -0
- skyflow/generated/rest/types/insert_record_data.py +36 -0
- skyflow/generated/rest/types/insert_response.py +23 -0
- skyflow/generated/rest/types/record_response_object.py +70 -0
- skyflow/generated/rest/types/token_group_redactions.py +33 -0
- skyflow/generated/rest/types/tokenize_response_object.py +49 -0
- skyflow/generated/rest/types/unique_value.py +22 -0
- skyflow/generated/rest/types/update_record_data.py +52 -0
- skyflow/generated/rest/types/update_record_data_update_type.py +5 -0
- skyflow/generated/rest/types/update_response.py +23 -0
- skyflow/generated/rest/types/upsert.py +45 -0
- skyflow/generated/rest/types/upsert_update_type.py +5 -0
- skyflow/generated/rest/version.py +6 -0
- skyflow/service_account/__init__.py +15 -0
- skyflow/utils/__init__.py +9 -0
- skyflow/utils/_http_config.py +36 -0
- skyflow/utils/_response_parsing.py +66 -0
- skyflow/utils/_retry.py +97 -0
- skyflow/utils/_skyflow_messages.py +114 -0
- skyflow/utils/_utils.py +54 -0
- skyflow/utils/_version.py +1 -0
- skyflow/utils/enums/__init__.py +3 -0
- skyflow/utils/enums/_custom_header_key.py +10 -0
- skyflow/utils/enums/_env_urls.py +9 -0
- skyflow/utils/enums/_upsert_type.py +7 -0
- skyflow/utils/validations/__init__.py +9 -0
- skyflow/utils/validations/_validations.py +195 -0
- skyflow/vault/__init__.py +0 -0
- skyflow/vault/client/__init__.py +0 -0
- skyflow/vault/client/client.py +102 -0
- skyflow/vault/controller/__init__.py +1 -0
- skyflow/vault/controller/_vault.py +475 -0
- skyflow/vault/data/__init__.py +30 -0
- skyflow/vault/data/_column_redactions.py +4 -0
- skyflow/vault/data/_delete_options.py +8 -0
- skyflow/vault/data/_delete_request.py +5 -0
- skyflow/vault/data/_delete_response.py +9 -0
- skyflow/vault/data/_delete_response_record.py +9 -0
- skyflow/vault/data/_detokenize_options.py +8 -0
- skyflow/vault/data/_detokenize_request.py +9 -0
- skyflow/vault/data/_detokenize_response.py +9 -0
- skyflow/vault/data/_detokenize_response_record.py +16 -0
- skyflow/vault/data/_detokenize_response_record_metadata.py +10 -0
- skyflow/vault/data/_get_options.py +8 -0
- skyflow/vault/data/_get_request.py +17 -0
- skyflow/vault/data/_get_request_record.py +13 -0
- skyflow/vault/data/_get_response.py +9 -0
- skyflow/vault/data/_get_response_record.py +17 -0
- skyflow/vault/data/_insert_options.py +8 -0
- skyflow/vault/data/_insert_request.py +11 -0
- skyflow/vault/data/_insert_request_record.py +9 -0
- skyflow/vault/data/_insert_response.py +9 -0
- skyflow/vault/data/_insert_response_record.py +17 -0
- skyflow/vault/data/_request_context.py +16 -0
- skyflow/vault/data/_token.py +8 -0
- skyflow/vault/data/_token_group_redactions.py +4 -0
- skyflow/vault/data/_update_options.py +8 -0
- skyflow/vault/data/_update_request.py +11 -0
- skyflow/vault/data/_update_request_record.py +6 -0
- skyflow/vault/data/_update_response.py +9 -0
- skyflow/vault/data/_update_response_record.py +17 -0
- skyflow/vault/data/_upsert_options.py +7 -0
- skyflow_flowvault_python-1.0.0.dist-info/METADATA +896 -0
- skyflow_flowvault_python-1.0.0.dist-info/RECORD +202 -0
- skyflow_flowvault_python-1.0.0.dist-info/WHEEL +5 -0
- skyflow_flowvault_python-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,896 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: skyflow-flowvault-python
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Skyflow SDK for the Python programming language (v3 / flowservice API)
|
|
5
|
+
Home-page: https://github.com/skyflowapi/skyflow-python/
|
|
6
|
+
Author: Skyflow
|
|
7
|
+
Author-email: service-ops@skyflow.com
|
|
8
|
+
License: LICENSE
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: pydantic>=2.0.0
|
|
12
|
+
Requires-Dist: typing-extensions>=4.0.0
|
|
13
|
+
Requires-Dist: PyJWT<3,>=2.12
|
|
14
|
+
Requires-Dist: cryptography>=44.0.2
|
|
15
|
+
Requires-Dist: httpx>=0.21.2
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: codespell>=2.4.1; extra == "dev"
|
|
18
|
+
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
|
19
|
+
Requires-Dist: pre-commit>=4.3.0; extra == "dev"
|
|
20
|
+
Requires-Dist: griffe==2.2.0; python_version >= "3.10" and extra == "dev"
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: provides-extra
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
Dynamic: summary
|
|
31
|
+
|
|
32
|
+
# Skyflow FlowVault Python SDK
|
|
33
|
+
|
|
34
|
+
The `flowvault` module is a Skyflow Python SDK for vault operations. It shares its client, credentials, and configuration with the [skyvault SDK](../skyvault/README.md) (both depend on the `common` module) but exposes a different, narrower surface: **unary** vault operations — insert, get, update, delete, and detokenize.
|
|
35
|
+
|
|
36
|
+
> Meant for **FlowVault** vaults.
|
|
37
|
+
|
|
38
|
+
> **`skyflow-flowvault-python` is versioned independently of the main `skyflow` SDK.** The two are separate PyPI artifacts with separate version lines, so a lower `flowvault` version number does not mean it is older or behind. Upgrade each artifact on its own; the current release is listed on [PyPI](https://pypi.org/project/skyflow-flowvault-python/).
|
|
39
|
+
|
|
40
|
+
[](https://github.com/skyflowapi/skyflow-python/actions)
|
|
41
|
+
[](https://github.com/skyflowapi/skyflow-python/blob/main/LICENSE)
|
|
42
|
+
|
|
43
|
+
# Table of Contents
|
|
44
|
+
|
|
45
|
+
- [Table of Contents](#table-of-contents)
|
|
46
|
+
- [Overview](#overview)
|
|
47
|
+
- [Install](#install)
|
|
48
|
+
- [Requirements](#requirements)
|
|
49
|
+
- [Configuration](#configuration)
|
|
50
|
+
- [Quickstart](#quickstart)
|
|
51
|
+
- [Authenticate](#authenticate)
|
|
52
|
+
- [Credential types](#credential-types)
|
|
53
|
+
- [Where credentials can be set](#where-credentials-can-be-set)
|
|
54
|
+
- [Generate a bearer token](#generate-a-bearer-token)
|
|
55
|
+
- [Generate bearer tokens with context](#generate-bearer-tokens-with-context)
|
|
56
|
+
- [Generate scoped bearer tokens](#generate-scoped-bearer-tokens)
|
|
57
|
+
- [Generate signed data tokens](#generate-signed-data-tokens)
|
|
58
|
+
- [Initialize the client](#initialize-the-client)
|
|
59
|
+
- [VaultConfig reference](#vaultconfig-reference)
|
|
60
|
+
- [Skyflow.builder() reference](#skyflowbuilder-reference)
|
|
61
|
+
- [Timeouts and retries](#timeouts-and-retries)
|
|
62
|
+
- [Logging](#logging)
|
|
63
|
+
- [Thread safety and resource lifecycle](#thread-safety-and-resource-lifecycle)
|
|
64
|
+
- [Schema vs. schemaless vaults](#schema-vs-schemaless-vaults)
|
|
65
|
+
- [VaultController — Unary operations](#vaultcontroller--unary-operations)
|
|
66
|
+
- [Vault type support](#vault-type-support)
|
|
67
|
+
- [Insert](#insert)
|
|
68
|
+
- [Detokenize](#detokenize)
|
|
69
|
+
- [Get](#get)
|
|
70
|
+
- [Update](#update)
|
|
71
|
+
- [Delete](#delete)
|
|
72
|
+
- [Custom Request Headers](#custom-request-headers)
|
|
73
|
+
- [Error Handling](#error-handling)
|
|
74
|
+
- [Two layers of errors](#two-layers-of-errors)
|
|
75
|
+
- [Per-record success and failure](#per-record-success-and-failure)
|
|
76
|
+
- [Catching SkyflowError](#catching-skyflowerror)
|
|
77
|
+
- [SkyflowError properties](#skyflowerror-properties)
|
|
78
|
+
- [Retrying the failed records](#retrying-the-failed-records)
|
|
79
|
+
- [Samples](#samples)
|
|
80
|
+
|
|
81
|
+
# Overview
|
|
82
|
+
|
|
83
|
+
- Authenticate using a Skyflow service account, an API key, or a bearer token — see [Authenticate](#authenticate).
|
|
84
|
+
- Perform unary Vault API operations — insert, get, update, delete, and detokenize — a single API call each. See [VaultController — Unary operations](#vaultcontroller--unary-operations).
|
|
85
|
+
- **Per-record reporting, not all-or-nothing.** A call succeeds as a call even when individual records fail; every response reports the outcome of each individual record or token. See [Error Handling](#error-handling).
|
|
86
|
+
|
|
87
|
+
# Install
|
|
88
|
+
|
|
89
|
+
## Requirements
|
|
90
|
+
|
|
91
|
+
- Python 3.9 and above
|
|
92
|
+
|
|
93
|
+
## Configuration
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install skyflow-flowvault-python
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The package installs under the import name `skyflow`:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from skyflow import Skyflow, LogLevel, Env
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
> **Do not install this alongside the main `skyflow` SDK.** This package and the main [skyflow SDK](../skyvault/README.md) (published to PyPI as `skyflow`) both ship the same top-level `skyflow` import package, so installing both in one environment makes them shadow each other — whichever was installed last wins, and imports resolve to the wrong SDK. Pick the one you need per environment, and if you need both, keep them in separate virtual environments.
|
|
106
|
+
|
|
107
|
+
Check [PyPI](https://pypi.org/project/skyflow-flowvault-python/) for the current version and [GitHub releases](https://github.com/skyflowapi/skyflow-python/releases) for what changed in each one.
|
|
108
|
+
|
|
109
|
+
# Quickstart
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from skyflow import Skyflow, LogLevel, Env
|
|
113
|
+
from skyflow.vault.data import InsertRequest, InsertRequestRecord
|
|
114
|
+
|
|
115
|
+
credentials = {'api_key': '<API_KEY>'} # or 'token' / 'path' / 'credentials_string'
|
|
116
|
+
|
|
117
|
+
vault_config = {
|
|
118
|
+
'vault_id': '<VAULT_ID>',
|
|
119
|
+
'cluster_id': '<CLUSTER_ID>', # part of the vault URL: https://{cluster_id}.vault.skyflowapis.com
|
|
120
|
+
'env': Env.PROD, # DEV, STAGE, SANDBOX, or PROD (default)
|
|
121
|
+
'credentials': credentials,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
skyflow_client = (
|
|
125
|
+
Skyflow.builder()
|
|
126
|
+
.add_vault_config(vault_config)
|
|
127
|
+
.build()
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Returns the controller for the given vault
|
|
131
|
+
vault = skyflow_client.vault('<VAULT_ID>')
|
|
132
|
+
|
|
133
|
+
response = vault.insert(InsertRequest(
|
|
134
|
+
table_name='cards',
|
|
135
|
+
records=[InsertRequestRecord(data={'card_number': '4111111111111111'})],
|
|
136
|
+
))
|
|
137
|
+
print(response.records)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`vault('<VAULT_ID>')` returns the controller for a specific registered vault. `vault()` with no argument returns the controller for the first vault added to the builder. To talk to more than one vault from a single client, register each with `add_vault_config(...)` and fetch each controller by ID.
|
|
141
|
+
|
|
142
|
+
# Authenticate
|
|
143
|
+
|
|
144
|
+
Requests are authorized with Skyflow credentials that you attach to the vault config's `credentials` dict. Credential handling comes from the shared `common` module, so it works the same way `skyvault` does.
|
|
145
|
+
|
|
146
|
+
## Credential types
|
|
147
|
+
|
|
148
|
+
Set **exactly one** of the following keys on the `credentials` dict. If you set more than one, the resolution is undefined — pick one.
|
|
149
|
+
|
|
150
|
+
| Key | What it is |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `api_key` | A long-lived key that authenticates and authorizes requests to the API. Simplest option. |
|
|
153
|
+
| `token` | A short-lived bearer token, typically generated from service account credentials. See [Generate a bearer token](#generate-a-bearer-token). |
|
|
154
|
+
| `path` | Filesystem path to a service account `credentials.json`. The SDK generates and refreshes bearer tokens from it. |
|
|
155
|
+
| `credentials_string` | The contents of a service account `credentials.json` as a JSON string — use this when the credentials come from a secret store rather than a file. |
|
|
156
|
+
|
|
157
|
+
Two optional modifiers apply when the SDK is generating tokens for you (that is, with `path` or `credentials_string`):
|
|
158
|
+
|
|
159
|
+
| Key | Description |
|
|
160
|
+
|---|---|
|
|
161
|
+
| `roles` | Restrict the generated token to specific role IDs (a scoped token). |
|
|
162
|
+
| `context` | Attach context to the generated token for context-aware authorization. |
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# API key
|
|
166
|
+
credentials = {'api_key': '<API_KEY>'}
|
|
167
|
+
|
|
168
|
+
# Bearer token you generated yourself
|
|
169
|
+
credentials = {'token': '<BEARER_TOKEN>'}
|
|
170
|
+
|
|
171
|
+
# Service account credentials file — the SDK handles token generation and refresh
|
|
172
|
+
credentials = {'path': '<PATH_TO_CREDENTIALS_JSON>'}
|
|
173
|
+
|
|
174
|
+
# Service account credentials as a JSON string
|
|
175
|
+
credentials = {'credentials_string': '<CREDENTIALS_JSON_STRING>'}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Where credentials can be set
|
|
179
|
+
|
|
180
|
+
Credentials resolve **most specific first**:
|
|
181
|
+
|
|
182
|
+
1. **Per-vault** — `vault_config['credentials']`. Wins for that vault.
|
|
183
|
+
2. **Client-wide** — `Skyflow.builder().add_skyflow_credentials(credentials)`. Used by any vault that has none of its own.
|
|
184
|
+
3. **Environment** — if neither is provided, the SDK reads the `SKYFLOW_CREDENTIALS` environment variable.
|
|
185
|
+
|
|
186
|
+
If none of the three yields credentials, the call fails with a `SkyflowError`.
|
|
187
|
+
|
|
188
|
+
## Generate a bearer token
|
|
189
|
+
|
|
190
|
+
If you would rather manage tokens yourself, the service-account utilities ship inside `skyflow-flowvault-python` under `skyflow.service_account`. They are plain functions:
|
|
191
|
+
|
|
192
|
+
- `generate_bearer_token(path, options=None)` — mint a token from a service account credentials **file path**.
|
|
193
|
+
- `generate_bearer_token_from_creds(credentials_string, options=None)` — the same, from the credentials JSON as a **string**.
|
|
194
|
+
- `is_expired(token)` — `True` when the token is empty or past expiry; use it to reuse a cached token until it expires.
|
|
195
|
+
- `generate_signed_data_tokens(path, options)` / `generate_signed_data_tokens_from_creds(credentials_string, options)` — see [Generate signed data tokens](#generate-signed-data-tokens).
|
|
196
|
+
|
|
197
|
+
Each `generate_bearer_token*` call returns a `(token, ...)` tuple — unpack the first element. Tokens are valid for 60 minutes and carry the service account's permissions.
|
|
198
|
+
|
|
199
|
+
[Example](https://github.com/skyflowapi/skyflow-python/blob/main/flowvault/samples/service_account/bearer_token_generation_example.py):
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from skyflow.service_account import generate_bearer_token, generate_bearer_token_from_creds, is_expired
|
|
203
|
+
|
|
204
|
+
bearer_token = ''
|
|
205
|
+
|
|
206
|
+
def get_token(file_path):
|
|
207
|
+
global bearer_token
|
|
208
|
+
if not is_expired(bearer_token):
|
|
209
|
+
return bearer_token # reuse until it expires
|
|
210
|
+
token, _ = generate_bearer_token(file_path)
|
|
211
|
+
bearer_token = token
|
|
212
|
+
return bearer_token
|
|
213
|
+
|
|
214
|
+
# ...or from a credentials JSON string
|
|
215
|
+
token, _ = generate_bearer_token_from_creds(credentials_string)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
To use the token with the SDK, put it on the `credentials` dict:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
credentials = {'token': token}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Generate bearer tokens with context
|
|
225
|
+
|
|
226
|
+
**Context-aware authorization** embeds context values into a bearer token during generation so your policies can reference them. This enables more flexible access controls, such as tracking end-user identity when calling through a service account, and is required for detokenizing signed data tokens.
|
|
227
|
+
|
|
228
|
+
Pass an `options` dict with a `ctx` key — either a **string** or a **dict**:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
# String context — a single value your policy references as request.context
|
|
232
|
+
token, _ = generate_bearer_token(file_path, {'ctx': 'user_12345'})
|
|
233
|
+
|
|
234
|
+
# Dict context — each key maps to a Skyflow CEL policy variable under request.context.*
|
|
235
|
+
token, _ = generate_bearer_token(file_path, {'ctx': {
|
|
236
|
+
'role': 'admin',
|
|
237
|
+
'department': 'finance',
|
|
238
|
+
'user_id': 'user_12345',
|
|
239
|
+
}})
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
With the dict above, your Skyflow policies can reference `request.context.role`, `request.context.department`, and `request.context.user_id` to make conditional access decisions.
|
|
243
|
+
|
|
244
|
+
[Full example](https://github.com/skyflowapi/skyflow-python/blob/main/flowvault/samples/service_account/bearer_token_generation_with_context_example.py)
|
|
245
|
+
|
|
246
|
+
## Generate scoped bearer tokens
|
|
247
|
+
|
|
248
|
+
A service account with multiple roles can generate bearer tokens limited to specific roles by passing their role IDs in `options['role_ids']`. This is useful for services with several responsibilities, such as separating billing access from analytics access. The generated tokens are valid for 60 minutes and can only execute operations permitted by the designated roles.
|
|
249
|
+
|
|
250
|
+
[Example](https://github.com/skyflowapi/skyflow-python/blob/main/flowvault/samples/service_account/scoped_token_generation_example.py):
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from skyflow.service_account import generate_bearer_token
|
|
254
|
+
|
|
255
|
+
options = {'role_ids': ['<YOUR_ROLE_ID1>', '<YOUR_ROLE_ID2>']}
|
|
256
|
+
token, _ = generate_bearer_token(file_path, options)
|
|
257
|
+
# ...or from a credentials string: generate_bearer_token_from_creds(credentials_string, options)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
To generate bearer tokens concurrently from several threads, see [bearer_token_generation_using_threads_example.py](https://github.com/skyflowapi/skyflow-python/blob/main/flowvault/samples/service_account/bearer_token_generation_using_threads_example.py).
|
|
261
|
+
|
|
262
|
+
## Generate signed data tokens
|
|
263
|
+
|
|
264
|
+
Skyflow generates data tokens when sensitive data is inserted into the vault. Those data tokens can be digitally signed with the private key of the service account credentials, which adds a further layer of protection. A signed token can only be detokenized by passing it together with a bearer token generated from service account credentials that hold the matching context and permissions.
|
|
265
|
+
|
|
266
|
+
`options` accepts `ctx` (string or dict, same format as bearer tokens), `data_tokens` (the tokens to sign), and `time_to_live` (seconds):
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
from skyflow.service_account import generate_signed_data_tokens, generate_signed_data_tokens_from_creds
|
|
270
|
+
|
|
271
|
+
options = {
|
|
272
|
+
'ctx': 'user_12345',
|
|
273
|
+
'data_tokens': ['<DATA_TOKEN1>', '<DATA_TOKEN2>'],
|
|
274
|
+
'time_to_live': 30, # seconds
|
|
275
|
+
}
|
|
276
|
+
results = generate_signed_data_tokens(file_path, options)
|
|
277
|
+
# ...or from a credentials string: generate_signed_data_tokens_from_creds(credentials_string, options)
|
|
278
|
+
|
|
279
|
+
for data_token, signed_data_token in results:
|
|
280
|
+
print(data_token, '->', signed_data_token)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
`generate_signed_data_tokens*` returns a list of `(data_token, signed_data_token)` tuples.
|
|
284
|
+
|
|
285
|
+
[Full example](https://github.com/skyflowapi/skyflow-python/blob/main/flowvault/samples/service_account/signed_token_generation_example.py)
|
|
286
|
+
|
|
287
|
+
# Initialize the client
|
|
288
|
+
|
|
289
|
+
`Skyflow` is the client. Build it once, keep it for the lifetime of your application, and get a `VaultController` from it with `vault(...)`.
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from skyflow import Skyflow, LogLevel, Env
|
|
293
|
+
|
|
294
|
+
# Step 1: Credentials — exactly one credential type
|
|
295
|
+
credentials = {'path': '<PATH_TO_CREDENTIALS_JSON>'}
|
|
296
|
+
|
|
297
|
+
# Step 2: Vault configuration
|
|
298
|
+
vault_config = {
|
|
299
|
+
'vault_id': '<VAULT_ID>',
|
|
300
|
+
'cluster_id': '<CLUSTER_ID>',
|
|
301
|
+
'env': Env.PROD, # DEV, STAGE, SANDBOX, or PROD (default)
|
|
302
|
+
'credentials': credentials,
|
|
303
|
+
# Optional: vault-level HTTP overrides
|
|
304
|
+
'timeout': 120, # overall call timeout, in seconds
|
|
305
|
+
'max_retries': 2, # retries after the first failure
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
# Step 3: Build the client
|
|
309
|
+
skyflow_client = (
|
|
310
|
+
Skyflow.builder()
|
|
311
|
+
.set_log_level(LogLevel.INFO) # default is ERROR
|
|
312
|
+
.add_vault_config(vault_config)
|
|
313
|
+
.build()
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Step 4: Get the controller and issue calls
|
|
317
|
+
vault = skyflow_client.vault('<VAULT_ID>')
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## VaultConfig reference
|
|
321
|
+
|
|
322
|
+
The vault config is a plain dict:
|
|
323
|
+
|
|
324
|
+
| Key | Required | Description |
|
|
325
|
+
|---|---|---|
|
|
326
|
+
| `vault_id` | required | The vault's ID. |
|
|
327
|
+
| `cluster_id` | required | The cluster portion of the vault URL — `https://{cluster_id}.vault.skyflowapis.com`. |
|
|
328
|
+
| `env` | optional | `Env.DEV`, `Env.STAGE`, `Env.SANDBOX`, or `Env.PROD`. Defaults to `PROD`. |
|
|
329
|
+
| `credentials` | optional | Credentials for this vault. Falls back to client-wide credentials, then `SKYFLOW_CREDENTIALS`. |
|
|
330
|
+
| `vault_url` | optional | Full vault URL, when it cannot be derived from `cluster_id` and `env`. |
|
|
331
|
+
| `timeout` | optional | Overall call timeout in seconds, including retries. |
|
|
332
|
+
| `connect_timeout` | optional | Per-attempt connection timeout, in seconds. |
|
|
333
|
+
| `read_timeout` | optional | Per-attempt response-read timeout, in seconds. |
|
|
334
|
+
| `write_timeout` | optional | Per-attempt request-write timeout, in seconds. |
|
|
335
|
+
| `max_retries` | optional | Retry attempts after the first failure. |
|
|
336
|
+
| `initial_retry_delay_millis` | optional | Backoff before the first retry, in milliseconds. |
|
|
337
|
+
| `max_retry_delay_millis` | optional | Ceiling the exponential backoff grows to, in milliseconds. |
|
|
338
|
+
|
|
339
|
+
## Skyflow.builder() reference
|
|
340
|
+
|
|
341
|
+
| Method | Description |
|
|
342
|
+
|---|---|
|
|
343
|
+
| `add_vault_config(config)` | Register a vault. The first one registered is what `vault()` (no argument) returns. |
|
|
344
|
+
| `update_vault_config(config)` | Update a registered vault in place. |
|
|
345
|
+
| `remove_vault_config(vault_id)` | Unregister a vault. |
|
|
346
|
+
| `add_skyflow_credentials(credentials)` | Client-wide credentials for vaults that don't set their own. |
|
|
347
|
+
| `set_log_level(log_level)` | `LogLevel.DEBUG`, `INFO`, `WARN`, `ERROR` (default), or `OFF`. |
|
|
348
|
+
| `timeout(s)` / `connect_timeout(s)` / `read_timeout(s)` / `write_timeout(s)` | Client-wide HTTP timeouts, in seconds. |
|
|
349
|
+
| `max_retries(n)` / `initial_retry_delay_millis(ms)` / `max_retry_delay_millis(ms)` | Client-wide retry policy. |
|
|
350
|
+
| `build()` | Produce the `Skyflow` client. |
|
|
351
|
+
|
|
352
|
+
Once built, `skyflow_client.vault('<VAULT_ID>')` returns the controller for a specific registered vault, which is how one client talks to more than one vault.
|
|
353
|
+
|
|
354
|
+
## Timeouts and retries
|
|
355
|
+
|
|
356
|
+
Each HTTP setting resolves **most specific first**: the value on the vault config, else the client-wide value on `Skyflow.builder()`, else the SDK default.
|
|
357
|
+
|
|
358
|
+
| Setting | SDK default |
|
|
359
|
+
|---|---|
|
|
360
|
+
| `timeout` (overall call, incl. retries) | 60 s |
|
|
361
|
+
| `connect_timeout` / `read_timeout` / `write_timeout` (per attempt) | 10 s |
|
|
362
|
+
| `max_retries` | `0` — retries are **opt-in**, so non-idempotent writes are never replayed silently |
|
|
363
|
+
| `initial_retry_delay_millis` | 500 ms |
|
|
364
|
+
| `max_retry_delay_millis` | 2000 ms |
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
skyflow_client = (
|
|
368
|
+
Skyflow.builder()
|
|
369
|
+
.timeout(60).max_retries(3) # client-wide policy
|
|
370
|
+
.initial_retry_delay_millis(500).max_retry_delay_millis(4000)
|
|
371
|
+
.add_vault_config({
|
|
372
|
+
'vault_id': '<VAULT_ID>', 'cluster_id': '<CLUSTER_ID>', 'env': Env.PROD,
|
|
373
|
+
'credentials': {'api_key': '<API_KEY>'},
|
|
374
|
+
'timeout': 300, 'read_timeout': 30, # per-vault overrides
|
|
375
|
+
})
|
|
376
|
+
.build()
|
|
377
|
+
)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
When retries are enabled, retryable responses (HTTP `408` / `429` / `5xx`) are retried with exponential backoff and jitter, bounded by the `initial`/`max` delays and the overall `timeout`.
|
|
381
|
+
|
|
382
|
+
## Logging
|
|
383
|
+
|
|
384
|
+
The SDK logs at `LogLevel.ERROR` by default. Levels rank `DEBUG` < `INFO` < `WARN` < `ERROR` < `OFF`; setting a level prints that level and everything above it. Change it with `Skyflow.builder().set_log_level(LogLevel.DEBUG)`.
|
|
385
|
+
|
|
386
|
+
## Thread safety and resource lifecycle
|
|
387
|
+
|
|
388
|
+
- **Thread safety** — `Skyflow` and every `VaultController` it hands out are meant to be built once and reused for the application's lifetime; the underlying HTTP client is reused across calls rather than recreated per request.
|
|
389
|
+
|
|
390
|
+
## Schema vs. schemaless vaults
|
|
391
|
+
|
|
392
|
+
Which operations make sense depends on whether the vault is **structured** (has a schema — tables and columns) or **schemaless** (stores standalone tokens with no table structure): the record operations (`insert`, `get`, `update`, `delete`) address a table's columns and so need a structured vault, while `detokenize` needs only the token itself and works against either kind. See [Vault type support](#vault-type-support) for the per-operation breakdown.
|
|
393
|
+
|
|
394
|
+
# VaultController — Unary operations
|
|
395
|
+
|
|
396
|
+
`VaultController` is returned by `skyflow_client.vault(...)` and exposes these vault operations. Each sends exactly one API call and hands the result straight back:
|
|
397
|
+
|
|
398
|
+
| Method | Parameters | Returns | Description |
|
|
399
|
+
|--------|-----------|---------|-------------|
|
|
400
|
+
| `insert(request)` | `InsertRequest`, optional `InsertOptions` | `InsertResponse` | Insert records, optionally across multiple tables, in one call |
|
|
401
|
+
| `detokenize(request)` | `DetokenizeRequest`, optional `DetokenizeOptions` | `DetokenizeResponse` | Detokenize tokens, optionally with a redaction override per token group |
|
|
402
|
+
| `get(request)` | `GetRequest`, optional `GetOptions` | `GetResponse` | Read records by skyflow ID or unique value, optionally with a redaction override per column |
|
|
403
|
+
| `update(request)` | `UpdateRequest`, optional `UpdateOptions` | `UpdateResponse` | Update records by skyflow ID |
|
|
404
|
+
| `delete(request)` | `DeleteRequest`, optional `DeleteOptions` | `DeleteResponse` | Delete records by skyflow ID or unique value |
|
|
405
|
+
|
|
406
|
+
Each method also accepts an optional options object (`InsertOptions`, `DetokenizeOptions`, `GetOptions`, `UpdateOptions`, `DeleteOptions`) — see [Custom Request Headers](#custom-request-headers).
|
|
407
|
+
|
|
408
|
+
## Vault type support
|
|
409
|
+
|
|
410
|
+
The same distinction as [Schema vs. schemaless vaults](#schema-vs-schemaless-vaults) applies. Four of the five unary operations address records inside a table, so they only make sense against a structured vault:
|
|
411
|
+
|
|
412
|
+
| Operation | Supported on |
|
|
413
|
+
|---|---|
|
|
414
|
+
| `insert` | Structured (schema) vaults — inserts into a table's columns. |
|
|
415
|
+
| `get` | Structured vaults — reads a table's records by skyflow ID or unique value. |
|
|
416
|
+
| `update` | Structured vaults — updates a table's records by skyflow ID. |
|
|
417
|
+
| `delete` | Structured vaults — deletes a table's records. |
|
|
418
|
+
| `detokenize` | Both — detokenizing only needs the token itself, not a table, so it works regardless of which kind of vault the token came from. |
|
|
419
|
+
|
|
420
|
+
# Insert
|
|
421
|
+
|
|
422
|
+
Insert records in a single API call. Each record is an `InsertRequestRecord` with its own `data` and, optionally, its own `table_name`, `tokens`, and `upsert`.
|
|
423
|
+
|
|
424
|
+
> **Vault type supported:** structured (schema) vaults. See [Vault type support](#vault-type-support).
|
|
425
|
+
|
|
426
|
+
**Note:**
|
|
427
|
+
|
|
428
|
+
- `table_name`/`upsert` follow a one-level rule: on the request (applies to all records) or on every record — never both.
|
|
429
|
+
- `tokens` is optional bring-your-own-token; when supplied, the map must not be empty and no key or value may be blank.
|
|
430
|
+
|
|
431
|
+
```python
|
|
432
|
+
from skyflow.vault.data import InsertRequest, InsertRequestRecord, UpsertOptions
|
|
433
|
+
from skyflow.utils.enums import UpsertType
|
|
434
|
+
|
|
435
|
+
vault = skyflow_client.vault('<VAULT_ID>') # skyflow_client from Quickstart
|
|
436
|
+
|
|
437
|
+
# Insert several records into one table — set table_name once on the request
|
|
438
|
+
request = InsertRequest(
|
|
439
|
+
table_name='cards',
|
|
440
|
+
records=[
|
|
441
|
+
InsertRequestRecord(data={'card_number': '4111111111111111', 'cardholder_name': 'john doe'}),
|
|
442
|
+
InsertRequestRecord(data={'card_number': '4222222222222222', 'cardholder_name': 'jane doe'}),
|
|
443
|
+
],
|
|
444
|
+
)
|
|
445
|
+
response = vault.insert(request)
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
To put the table name on each record instead — the same one-level rule — drop the request-level `table_name` and set it on **every** record. This is also how you insert across different tables in one call, and where per-record `upsert` goes:
|
|
449
|
+
|
|
450
|
+
```python
|
|
451
|
+
request = InsertRequest(records=[
|
|
452
|
+
# table_name lives on each record here
|
|
453
|
+
InsertRequestRecord(table_name='table1', data={'card_number': '4111111111111111', 'cardholder_name': 'john doe'}),
|
|
454
|
+
InsertRequestRecord(
|
|
455
|
+
table_name='table2',
|
|
456
|
+
data={'email': 'jane.doe@example.com'},
|
|
457
|
+
upsert=UpsertOptions(unique_columns=['email'], update_type=UpsertType.UPDATE),
|
|
458
|
+
),
|
|
459
|
+
])
|
|
460
|
+
response = vault.insert(request)
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
There is no async variant: `insert` returns its `InsertResponse` directly.
|
|
464
|
+
|
|
465
|
+
Sample response:
|
|
466
|
+
|
|
467
|
+
```json
|
|
468
|
+
{
|
|
469
|
+
"records": [
|
|
470
|
+
{
|
|
471
|
+
"table_name": "cards",
|
|
472
|
+
"skyflow_id": "9fac9201-7b8a-4446-93f8-5244e1213bd1",
|
|
473
|
+
"tokens": {
|
|
474
|
+
"card_number": [ { "token": "5484-7829-1702-9110", "token_group_name": "card_number_cg", "path": null } ]
|
|
475
|
+
},
|
|
476
|
+
"data": { "card_number": "4111111111111111", "cardholder_name": "john doe" },
|
|
477
|
+
"hashed_data": { "card_number": [ { "data": "b6e6d...c3f9", "hash_name": "hash1" } ] },
|
|
478
|
+
"http_code": 200,
|
|
479
|
+
"error": null,
|
|
480
|
+
"request_id": null
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
"table_name": "cards",
|
|
484
|
+
"skyflow_id": "1c7e4f02-9a3b-4d18-8f21-6b0c9d5e3a44",
|
|
485
|
+
"tokens": {
|
|
486
|
+
"card_number": [ { "token": "6011-3821-4490-7752", "token_group_name": "card_number_cg", "path": null } ]
|
|
487
|
+
},
|
|
488
|
+
"data": { "card_number": "4222222222222222", "cardholder_name": "jane doe" },
|
|
489
|
+
"hashed_data": { "card_number": [ { "data": "1a2b3...9f0e", "hash_name": "hash1" } ] },
|
|
490
|
+
"http_code": 200,
|
|
491
|
+
"error": null,
|
|
492
|
+
"request_id": null
|
|
493
|
+
}
|
|
494
|
+
]
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
The records come back in the order you submitted them. `request_id` is `None` on success and the failing call's `x-request-id` on error. `.tokens` maps each column to a **list** of `Token` objects — one entry per token group configured on that column — each with `.token`, `.token_group_name`, and `.path` (the location within a structured column's value the token came from, e.g. `"phone_numbers[0].type"`; `None` for a flat column). Insert records also carry `.data`, the stored column values the vault returns.
|
|
499
|
+
|
|
500
|
+
```python
|
|
501
|
+
for record in response.records:
|
|
502
|
+
if record.error is None:
|
|
503
|
+
print(record.skyflow_id, record.tokens)
|
|
504
|
+
else:
|
|
505
|
+
print('insert failed', record.http_code, record.error)
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Accessors on each `InsertResponseRecord`: `.table_name`, `.skyflow_id`, `.tokens`, `.data`, `.hashed_data`, `.http_code`, `.error`, `.request_id`.
|
|
509
|
+
|
|
510
|
+
# Detokenize
|
|
511
|
+
|
|
512
|
+
Detokenize tokens in a single API call, optionally overriding the redaction applied per token group via `token_group_redactions`.
|
|
513
|
+
|
|
514
|
+
> **Vault type supported:** both. See [Vault type support](#vault-type-support).
|
|
515
|
+
|
|
516
|
+
**Note:**
|
|
517
|
+
|
|
518
|
+
- `tokens` is required and must not be empty, and no entry may be blank.
|
|
519
|
+
- `token_group_redactions` is optional; when supplied, each entry needs a non-blank `token_group_name` and `redaction`. Entries are `TokenGroupRedactions` objects.
|
|
520
|
+
|
|
521
|
+
```python
|
|
522
|
+
from skyflow.vault.data import DetokenizeRequest, TokenGroupRedactions
|
|
523
|
+
|
|
524
|
+
vault = skyflow_client.vault('<VAULT_ID>') # skyflow_client from Quickstart
|
|
525
|
+
|
|
526
|
+
request = DetokenizeRequest(
|
|
527
|
+
tokens=['5479-4229-4622-1393', 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'],
|
|
528
|
+
token_group_redactions=[TokenGroupRedactions(token_group_name='card_number_cg', redaction='MASKED')],
|
|
529
|
+
)
|
|
530
|
+
response = vault.detokenize(request)
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
There is no async variant: `detokenize` returns its `DetokenizeResponse` directly.
|
|
534
|
+
|
|
535
|
+
Sample response:
|
|
536
|
+
|
|
537
|
+
```json
|
|
538
|
+
{
|
|
539
|
+
"records": [
|
|
540
|
+
{
|
|
541
|
+
"token": "5479-4229-4622-1393",
|
|
542
|
+
"value": "4111111111111111",
|
|
543
|
+
"token_group_name": "card_number_cg",
|
|
544
|
+
"metadata": { "skyflow_id": "9fac9201-7b8a-4446-93f8-5244e1213bd1", "table_name": "table1" },
|
|
545
|
+
"http_code": 200,
|
|
546
|
+
"error": null,
|
|
547
|
+
"request_id": null
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
"token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
551
|
+
"value": null,
|
|
552
|
+
"token_group_name": null,
|
|
553
|
+
"metadata": null,
|
|
554
|
+
"http_code": 404,
|
|
555
|
+
"error": "Token Not Found",
|
|
556
|
+
"request_id": "a1b2c3d4-..."
|
|
557
|
+
}
|
|
558
|
+
]
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
`record.metadata` is a typed `DetokenizeResponseRecordMetadata` with `.skyflow_id` / `.table_name` (`None` on records that errored).
|
|
563
|
+
|
|
564
|
+
```python
|
|
565
|
+
for record in response.records:
|
|
566
|
+
if record.error is None:
|
|
567
|
+
print(record.token, '->', record.value, '(', record.token_group_name, ')')
|
|
568
|
+
else:
|
|
569
|
+
print(record.token, 'failed', record.http_code, record.error)
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Accessors on each `DetokenizeResponseRecord`: `.token`, `.value`, `.token_group_name`, `.metadata`, `.http_code`, `.error`, `.request_id`.
|
|
573
|
+
|
|
574
|
+
# Get
|
|
575
|
+
|
|
576
|
+
Read records back from a table, by skyflow ID or by unique value, optionally overriding the redaction applied per column via `column_redactions`.
|
|
577
|
+
|
|
578
|
+
> **Vault type supported:** structured (schema) vaults. See [Vault type support](#vault-type-support).
|
|
579
|
+
|
|
580
|
+
**Note:**
|
|
581
|
+
|
|
582
|
+
- A `GetRequest` works in one of two modes, and they are mutually exclusive: **single-table** (`table_name`, `skyflow_ids`/`unique_values`, `columns`, `column_redactions`, `limit`, `offset`) or **multi-table** (`records`, a list of `GetRequestRecord`). Setting fields from both modes fails validation.
|
|
583
|
+
- `table_name` is required, and exactly one of `skyflow_ids` or `unique_values` must be supplied — both, or neither, fails validation. This holds per record in multi-table mode.
|
|
584
|
+
- `unique_values` is a list of dicts: one dict per record, each holding the unique column-name/value pairs that identify it.
|
|
585
|
+
- `columns` selects the columns to return; omit it for all of them.
|
|
586
|
+
- `limit` and `offset` apply to the call as a whole and are **only used in single-table mode**.
|
|
587
|
+
- `column_redactions` entries are `ColumnRedactions` objects.
|
|
588
|
+
|
|
589
|
+
```python
|
|
590
|
+
from skyflow.vault.data import GetRequest, GetRequestRecord, ColumnRedactions
|
|
591
|
+
|
|
592
|
+
vault = skyflow_client.vault('<VAULT_ID>') # skyflow_client from Quickstart
|
|
593
|
+
|
|
594
|
+
# single-table, by skyflow ID
|
|
595
|
+
vault.get(GetRequest(
|
|
596
|
+
table_name='table1',
|
|
597
|
+
skyflow_ids=['9fac9201-7b8a-4446-93f8-5244e1213bd1'],
|
|
598
|
+
columns=['card_number', 'cardholder_name'],
|
|
599
|
+
column_redactions=[ColumnRedactions(column_name='card_number', redaction='MASKED')],
|
|
600
|
+
limit=10, offset=0,
|
|
601
|
+
))
|
|
602
|
+
|
|
603
|
+
# single-table, by unique value
|
|
604
|
+
vault.get(GetRequest(table_name='table2', unique_values=[{'email': 'jane.doe@example.com'}]))
|
|
605
|
+
|
|
606
|
+
# multi-table batch — each GetRequestRecord carries its own table and lookup fields
|
|
607
|
+
vault.get(GetRequest(records=[
|
|
608
|
+
GetRequestRecord(table_name='table1', skyflow_ids=['9fac9201-...'], columns=['card_number']),
|
|
609
|
+
GetRequestRecord(table_name='table2', unique_values=[{'email': 'jane.doe@example.com'}]),
|
|
610
|
+
]))
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
There is no async variant: `get` returns its `GetResponse` directly.
|
|
614
|
+
|
|
615
|
+
Sample response:
|
|
616
|
+
|
|
617
|
+
```json
|
|
618
|
+
{
|
|
619
|
+
"records": [
|
|
620
|
+
{
|
|
621
|
+
"table_name": "table1",
|
|
622
|
+
"skyflow_id": "9fac9201-7b8a-4446-93f8-5244e1213bd1",
|
|
623
|
+
"tokens": {
|
|
624
|
+
"card_number": [ { "token": "5484-7829-1702-9110", "token_group_name": "card_number_cg", "path": null } ]
|
|
625
|
+
},
|
|
626
|
+
"data": { "card_number": "4111-XXXX-XXXX-1111", "cardholder_name": "John Doe" },
|
|
627
|
+
"hashed_data": null,
|
|
628
|
+
"http_code": 200,
|
|
629
|
+
"error": null,
|
|
630
|
+
"request_id": null
|
|
631
|
+
}
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
`GetResponseRecord` carries the same fields as an insert record, plus plaintext `.data`: `.table_name`, `.skyflow_id`, `.tokens`, `.data`, `.hashed_data`, `.http_code`, `.error`, `.request_id`.
|
|
637
|
+
|
|
638
|
+
```python
|
|
639
|
+
for record in response.records:
|
|
640
|
+
if record.error is None:
|
|
641
|
+
print(record.skyflow_id, '->', record.data)
|
|
642
|
+
else:
|
|
643
|
+
print(record.skyflow_id, 'failed', record.http_code, record.error)
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
# Update
|
|
647
|
+
|
|
648
|
+
Update records in a table by skyflow ID, in a single API call.
|
|
649
|
+
|
|
650
|
+
> **Vault type supported:** structured (schema) vaults. See [Vault type support](#vault-type-support).
|
|
651
|
+
|
|
652
|
+
**Note:**
|
|
653
|
+
|
|
654
|
+
- `table_name` is required on the request. A record may override it with its own `table_name`, which applies to that record only.
|
|
655
|
+
- Every `UpdateRequestRecord` needs a non-blank `skyflow_id`.
|
|
656
|
+
- `data` holds the columns to change; no key or value may be blank. `tokens` is optional bring-your-own-token.
|
|
657
|
+
- `update_type` accepts `UpsertType.UPDATE` (merge the supplied columns) or `UpsertType.REPLACE` (overwrite the whole record). Omitting it sends no `update_type`, which the vault treats the same as an update. It is a request-level setting.
|
|
658
|
+
|
|
659
|
+
```python
|
|
660
|
+
from skyflow.vault.data import UpdateRequest, UpdateRequestRecord
|
|
661
|
+
from skyflow.utils.enums import UpsertType
|
|
662
|
+
|
|
663
|
+
vault = skyflow_client.vault('<VAULT_ID>') # skyflow_client from Quickstart
|
|
664
|
+
|
|
665
|
+
request = UpdateRequest(
|
|
666
|
+
table_name='table1',
|
|
667
|
+
update_type=UpsertType.UPDATE,
|
|
668
|
+
records=[UpdateRequestRecord(skyflow_id='9fac9201-7b8a-4446-93f8-5244e1213bd1', data={'cardholder_name': 'jane doe'})],
|
|
669
|
+
)
|
|
670
|
+
response = vault.update(request)
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
There is no async variant: `update` returns its `UpdateResponse` directly.
|
|
674
|
+
|
|
675
|
+
Sample response:
|
|
676
|
+
|
|
677
|
+
```json
|
|
678
|
+
{
|
|
679
|
+
"records": [
|
|
680
|
+
{
|
|
681
|
+
"table_name": "table1",
|
|
682
|
+
"skyflow_id": "9fac9201-7b8a-4446-93f8-5244e1213bd1",
|
|
683
|
+
"tokens": {
|
|
684
|
+
"cardholder_name": [ { "token": "f1a2b3c4-d5e6-7890-abcd-ef1234567890", "token_group_name": "deterministic_string", "path": null } ]
|
|
685
|
+
},
|
|
686
|
+
"data": { "cardholder_name": "Jane Doe" },
|
|
687
|
+
"hashed_data": null,
|
|
688
|
+
"http_code": 200,
|
|
689
|
+
"error": null,
|
|
690
|
+
"request_id": null
|
|
691
|
+
}
|
|
692
|
+
]
|
|
693
|
+
}
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
Like `GetResponseRecord`, `UpdateResponseRecord` carries the insert record's fields plus `.data`: `.table_name`, `.skyflow_id`, `.tokens`, `.data`, `.hashed_data`, `.http_code`, `.error`, `.request_id`. Successes and failures come back in one `records` list, each tagged with its own `.http_code`/`.error`.
|
|
697
|
+
|
|
698
|
+
```python
|
|
699
|
+
for record in response.records:
|
|
700
|
+
if record.error is None:
|
|
701
|
+
print(record.skyflow_id, 'updated')
|
|
702
|
+
else:
|
|
703
|
+
print(record.skyflow_id, 'failed', record.http_code, record.error)
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
# Delete
|
|
707
|
+
|
|
708
|
+
Delete records from a table by skyflow ID or unique value, in a single API call.
|
|
709
|
+
|
|
710
|
+
> **Vault type supported:** structured (schema) vaults. See [Vault type support](#vault-type-support).
|
|
711
|
+
|
|
712
|
+
**Note:**
|
|
713
|
+
|
|
714
|
+
- `table_name` is required, and exactly one of `ids` or `unique_values` must be supplied — both, or neither, fails validation.
|
|
715
|
+
- `unique_values` takes the same shape as in [Get](#get): one dict per record, holding the unique column-name/value pairs that identify it.
|
|
716
|
+
|
|
717
|
+
```python
|
|
718
|
+
from skyflow.vault.data import DeleteRequest
|
|
719
|
+
|
|
720
|
+
vault = skyflow_client.vault('<VAULT_ID>') # skyflow_client from Quickstart
|
|
721
|
+
|
|
722
|
+
request = DeleteRequest(
|
|
723
|
+
table_name='table1',
|
|
724
|
+
ids=['9fac9201-7b8a-4446-93f8-5244e1213bd1', 'b2308e2a-c1f5-469b-97b7-1f193159399b'],
|
|
725
|
+
)
|
|
726
|
+
response = vault.delete(request)
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
There is no async variant: `delete` returns its `DeleteResponse` directly.
|
|
730
|
+
|
|
731
|
+
Sample response:
|
|
732
|
+
|
|
733
|
+
```json
|
|
734
|
+
{
|
|
735
|
+
"records": [
|
|
736
|
+
{ "skyflow_id": "9fac9201-7b8a-4446-93f8-5244e1213bd1", "http_code": 200, "error": null, "request_id": null },
|
|
737
|
+
{ "skyflow_id": "b2308e2a-c1f5-469b-97b7-1f193159399b", "http_code": 404, "error": "Record Not Found", "request_id": "a1b2c3d4-..." }
|
|
738
|
+
]
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
`DeleteResponseRecord` is flatter than the insert-shaped records above — the vault returns no data, tokens, or hashed data for a delete. Accessors: `.skyflow_id`, `.http_code`, `.error`, `.request_id`.
|
|
743
|
+
|
|
744
|
+
```python
|
|
745
|
+
for record in response.records:
|
|
746
|
+
if record.error is None:
|
|
747
|
+
print(record.skyflow_id, 'deleted')
|
|
748
|
+
else:
|
|
749
|
+
print(record.skyflow_id, 'failed', record.http_code, record.error)
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
# Custom Request Headers
|
|
753
|
+
|
|
754
|
+
To include custom HTTP headers on an outgoing request, pass an **interceptor** via that operation's options object. The interceptor is a callable that receives a `RequestContext` and can add headers to it. The headers available are defined by the `CustomHeaderKey` enum:
|
|
755
|
+
|
|
756
|
+
| `CustomHeaderKey` | HTTP header name |
|
|
757
|
+
|---|---|
|
|
758
|
+
| `SKYFLOW_ACCOUNT_ID` | `x-skyflow-account-id` |
|
|
759
|
+
| `SKYFLOW_ACCOUNT_NAME` | `x-skyflow-account-name` |
|
|
760
|
+
| `REQUEST_ID_HEADER` | `x-request-id` |
|
|
761
|
+
|
|
762
|
+
```python
|
|
763
|
+
from skyflow.vault.data import InsertOptions, CustomHeaderKey
|
|
764
|
+
|
|
765
|
+
def add_request_id(context):
|
|
766
|
+
context.add_header(CustomHeaderKey.REQUEST_ID_HEADER, 'req-001')
|
|
767
|
+
|
|
768
|
+
response = vault.insert(request, InsertOptions(interceptor=add_request_id))
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
The interceptor runs once per call. Headers it adds are merged on top of the SDK's own headers (metrics + `Authorization`).
|
|
772
|
+
|
|
773
|
+
The same pattern applies to every operation, via its corresponding options class:
|
|
774
|
+
|
|
775
|
+
| Operation | Options class |
|
|
776
|
+
|---|---|
|
|
777
|
+
| `insert` | `InsertOptions` |
|
|
778
|
+
| `detokenize` | `DetokenizeOptions` |
|
|
779
|
+
| `get` | `GetOptions` |
|
|
780
|
+
| `update` | `UpdateOptions` |
|
|
781
|
+
| `delete` | `DeleteOptions` |
|
|
782
|
+
|
|
783
|
+
# Error Handling
|
|
784
|
+
|
|
785
|
+
## Two layers of errors
|
|
786
|
+
|
|
787
|
+
This is the mental model to hold for every operation:
|
|
788
|
+
|
|
789
|
+
| Layer | What it covers | How you see it |
|
|
790
|
+
|---|---|---|
|
|
791
|
+
| **Request-level** | The call could not be made or the whole call failed: invalid request shape, missing credentials, auth failure, or a whole-call API rejection. | A raised `SkyflowError`. No results at all. |
|
|
792
|
+
| **Record-level** | The call succeeded, but individual records or tokens inside it did not. | A returned response. **Nothing is raised.** Each entry in `response.records` reports its own `http_code` and `error`. |
|
|
793
|
+
|
|
794
|
+
The second layer is what distinguishes `flowvault` from an all-or-nothing API: **a call that returns normally can still contain failures, and a call where every single record failed also returns normally rather than raising.** Checking only for a raised exception will silently miss failed records — always read the per-record results.
|
|
795
|
+
|
|
796
|
+
Each record carries its own `request_id` — `None` on success, the failing call's `x-request-id` on error.
|
|
797
|
+
|
|
798
|
+
## Per-record success and failure
|
|
799
|
+
|
|
800
|
+
Every response exposes `.records`. The records list has one entry per submitted item, in the order you submitted it, and each entry carries:
|
|
801
|
+
|
|
802
|
+
| Attribute | Present on | Meaning |
|
|
803
|
+
|---|---|---|
|
|
804
|
+
| `.http_code` | always | Per-item status. `2xx` for success; `4xx`/`5xx` for failure. |
|
|
805
|
+
| `.error` | always | Error message for this item, populated only on failure — `None` means this item succeeded. |
|
|
806
|
+
| `.request_id` | always | The `x-request-id` of the call this item was part of, populated only on failure (`None` on success) — quote it in support escalations. |
|
|
807
|
+
|
|
808
|
+
The success payload sits alongside those attributes on the same object: `.skyflow_id`/`.tokens`/`.data`/`.hashed_data` for record-shaped operations (`insert`, `get`, `update`), `.value`/`.token_group_name`/`.metadata` for detokenize, `.skyflow_id` alone for delete.
|
|
809
|
+
|
|
810
|
+
A response has just `.records`, in submitted order, with `.http_code`, `.error`, and `.request_id` on each entry alongside that operation's payload.
|
|
811
|
+
|
|
812
|
+
The idiomatic way to consume a response:
|
|
813
|
+
|
|
814
|
+
```python
|
|
815
|
+
response = vault.insert(request)
|
|
816
|
+
|
|
817
|
+
for record in response.records:
|
|
818
|
+
if record.error is None:
|
|
819
|
+
print(record.skyflow_id)
|
|
820
|
+
else:
|
|
821
|
+
print('failed', record.http_code, record.error, '(request_id', record.request_id, ')')
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
## Catching SkyflowError
|
|
825
|
+
|
|
826
|
+
`SkyflowError` covers the request-level layer only — client-side validation errors and whole-call API errors. It comes from `common`, so it is the same exception type `skyvault` raises.
|
|
827
|
+
|
|
828
|
+
```python
|
|
829
|
+
from skyflow.error import SkyflowError
|
|
830
|
+
|
|
831
|
+
try:
|
|
832
|
+
response = vault.insert(request)
|
|
833
|
+
# reaching here means the CALL succeeded — individual records may still have failed
|
|
834
|
+
except SkyflowError as e:
|
|
835
|
+
print('HTTP code :', e.http_code)
|
|
836
|
+
print('Message :', e.message)
|
|
837
|
+
print('Request ID:', e.request_id)
|
|
838
|
+
print('Details :', e.details)
|
|
839
|
+
except Exception as e:
|
|
840
|
+
print('Unexpected error:', e)
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
## SkyflowError properties
|
|
844
|
+
|
|
845
|
+
| Property | Attribute | Description |
|
|
846
|
+
|---|---|---|
|
|
847
|
+
| HTTP status code | `.http_code` | Integer status code (e.g. `400`, `404`, `500`). |
|
|
848
|
+
| Message | `.message` | Human-readable description of the error. |
|
|
849
|
+
| gRPC code | `.grpc_code` | gRPC status code from the server. |
|
|
850
|
+
| HTTP status string | `.http_status` | Status string from the server. |
|
|
851
|
+
| Request ID | `.request_id` | The `x-request-id` header — useful for support escalations. |
|
|
852
|
+
| Details | `.details` | Additional error context from the server. Empty for validation errors, `None` if the server response omitted the field. |
|
|
853
|
+
|
|
854
|
+
**Validation errors** (table name at the wrong level, empty token list, and similar) are raised before any network call, with `http_code` `400`. **API errors** are returned by the Skyflow server and have all fields populated from the response body and headers.
|
|
855
|
+
|
|
856
|
+
## Retrying the failed records
|
|
857
|
+
|
|
858
|
+
Because failures are reported per record, a partial failure can be retried without resubmitting the whole payload. Resend only the records worth retrying — **server-side failures (HTTP 500–599), excluding 529**, which is a permanent capacity-limit code. Client-side (`4xx`) failures are deliberately excluded — those need a fix to the data, not a retry. This is separate from the transport-level `max_retries` setting in [Timeouts and retries](#timeouts-and-retries), which retries whole HTTP attempts and is off by default.
|
|
859
|
+
|
|
860
|
+
Records come back in input order, so you can filter `response.records` yourself with this predicate:
|
|
861
|
+
|
|
862
|
+
```python
|
|
863
|
+
def is_retryable(http_code):
|
|
864
|
+
return isinstance(http_code, int) and 500 <= http_code <= 599 and http_code != 529
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
For `insert` — and likewise `update`, `get`, and `delete` — correlate each response record back to your input by position, then resubmit the retryable ones:
|
|
868
|
+
|
|
869
|
+
```python
|
|
870
|
+
request = InsertRequest(table_name='cards', records=[...])
|
|
871
|
+
response = vault.insert(request)
|
|
872
|
+
|
|
873
|
+
retryable = [
|
|
874
|
+
request.records[i]
|
|
875
|
+
for i, record in enumerate(response.records)
|
|
876
|
+
if is_retryable(record.http_code)
|
|
877
|
+
]
|
|
878
|
+
if retryable:
|
|
879
|
+
retry_response = vault.insert(InsertRequest(table_name='cards', records=retryable))
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
> **Make insert retries idempotent.** A record can succeed server-side even when the response never reaches you — a dropped connection, or a `5xx` returned after the row was already written — so a blind resubmit can create a duplicate. Guard against it with `upsert` on a unique column, kept at the same level as `table_name`: a retried record then updates the existing row instead of inserting a second one.
|
|
883
|
+
|
|
884
|
+
For `detokenize`, each response record carries its own `.token`, so filter on that directly — no position bookkeeping needed:
|
|
885
|
+
|
|
886
|
+
```python
|
|
887
|
+
response = vault.detokenize(request)
|
|
888
|
+
|
|
889
|
+
retry_tokens = [record.token for record in response.records if is_retryable(record.http_code)]
|
|
890
|
+
if retry_tokens:
|
|
891
|
+
retry_response = vault.detokenize(DetokenizeRequest(tokens=retry_tokens))
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
# Samples
|
|
895
|
+
|
|
896
|
+
Runnable examples live in [samples/](samples/) — one file per operation, plus custom-header, timeout/retry, and service-account examples. See [samples/README.md](samples/README.md) to run them.
|