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.
Files changed (202) hide show
  1. common/.coveragerc +4 -0
  2. common/__init__.py +3 -0
  3. common/client/__init__.py +0 -0
  4. common/client/base_skyflow.py +345 -0
  5. common/client/utils/__init__.py +1 -0
  6. common/client/utils/_utils.py +127 -0
  7. common/errors/__init__.py +1 -0
  8. common/errors/_skyflow_error.py +17 -0
  9. common/generated/__init__.py +0 -0
  10. common/generated/rest/__init__.py +24 -0
  11. common/generated/rest/authentication/__init__.py +4 -0
  12. common/generated/rest/authentication/client.py +181 -0
  13. common/generated/rest/authentication/raw_client.py +241 -0
  14. common/generated/rest/client.py +153 -0
  15. common/generated/rest/core/__init__.py +52 -0
  16. common/generated/rest/core/api_error.py +23 -0
  17. common/generated/rest/core/client_wrapper.py +86 -0
  18. common/generated/rest/core/datetime_utils.py +28 -0
  19. common/generated/rest/core/file.py +67 -0
  20. common/generated/rest/core/force_multipart.py +16 -0
  21. common/generated/rest/core/http_client.py +543 -0
  22. common/generated/rest/core/http_response.py +55 -0
  23. common/generated/rest/core/jsonable_encoder.py +100 -0
  24. common/generated/rest/core/pydantic_utilities.py +255 -0
  25. common/generated/rest/core/query_encoder.py +58 -0
  26. common/generated/rest/core/remove_none_from_dict.py +11 -0
  27. common/generated/rest/core/request_options.py +35 -0
  28. common/generated/rest/core/serialization.py +276 -0
  29. common/generated/rest/environment.py +8 -0
  30. common/generated/rest/errors/__init__.py +9 -0
  31. common/generated/rest/errors/bad_request_error.py +14 -0
  32. common/generated/rest/errors/not_found_error.py +14 -0
  33. common/generated/rest/errors/unauthorized_error.py +14 -0
  34. common/generated/rest/py.typed +0 -0
  35. common/generated/rest/types/__init__.py +9 -0
  36. common/generated/rest/types/googlerpc_status.py +22 -0
  37. common/generated/rest/types/protobuf_any.py +21 -0
  38. common/generated/rest/types/v_1_get_auth_token_response.py +33 -0
  39. common/generated/rest/version.py +6 -0
  40. common/service_account/__init__.py +1 -0
  41. common/service_account/_utils.py +248 -0
  42. common/service_account/client/__init__.py +0 -0
  43. common/service_account/client/auth_client.py +13 -0
  44. common/utils/__init__.py +4 -0
  45. common/utils/_helpers.py +18 -0
  46. common/utils/_skyflow_messages.py +445 -0
  47. common/utils/_utils.py +50 -0
  48. common/utils/constants.py +291 -0
  49. common/utils/enums/__init__.py +12 -0
  50. common/utils/enums/content_types.py +9 -0
  51. common/utils/enums/detect_entities.py +73 -0
  52. common/utils/enums/detect_output_transcriptions.py +8 -0
  53. common/utils/enums/env.py +13 -0
  54. common/utils/enums/log_level.py +8 -0
  55. common/utils/enums/masking_method.py +5 -0
  56. common/utils/enums/redaction_type.py +7 -0
  57. common/utils/enums/request_method.py +8 -0
  58. common/utils/enums/token_mode.py +6 -0
  59. common/utils/enums/token_type.py +6 -0
  60. common/utils/logger/__init__.py +2 -0
  61. common/utils/logger/_log_helpers.py +47 -0
  62. common/utils/logger/_logger.py +50 -0
  63. common/utils/validations/__init__.py +11 -0
  64. common/utils/validations/_validations.py +267 -0
  65. common/vault/base_vault_client.py +122 -0
  66. common/vault/base_vault_controller.py +58 -0
  67. common/vault/data/__init__.py +2 -0
  68. common/vault/data/_base_insert_request.py +7 -0
  69. common/vault/data/_base_insert_response.py +11 -0
  70. skyflow/__init__.py +2 -0
  71. skyflow/client/__init__.py +1 -0
  72. skyflow/client/_http_config_builder.py +45 -0
  73. skyflow/client/skyflow.py +15 -0
  74. skyflow/error/__init__.py +3 -0
  75. skyflow/generated/__init__.py +0 -0
  76. skyflow/generated/rest/__init__.py +164 -0
  77. skyflow/generated/rest/_default_clients.py +32 -0
  78. skyflow/generated/rest/client.py +385 -0
  79. skyflow/generated/rest/core/__init__.py +127 -0
  80. skyflow/generated/rest/core/api_error.py +23 -0
  81. skyflow/generated/rest/core/client_wrapper.py +148 -0
  82. skyflow/generated/rest/core/datetime_utils.py +70 -0
  83. skyflow/generated/rest/core/file.py +67 -0
  84. skyflow/generated/rest/core/force_multipart.py +18 -0
  85. skyflow/generated/rest/core/http_client.py +940 -0
  86. skyflow/generated/rest/core/http_response.py +63 -0
  87. skyflow/generated/rest/core/http_sse/__init__.py +42 -0
  88. skyflow/generated/rest/core/http_sse/_api.py +455 -0
  89. skyflow/generated/rest/core/http_sse/_decoders.py +74 -0
  90. skyflow/generated/rest/core/http_sse/_exceptions.py +7 -0
  91. skyflow/generated/rest/core/http_sse/_models.py +17 -0
  92. skyflow/generated/rest/core/jsonable_encoder.py +133 -0
  93. skyflow/generated/rest/core/logging.py +107 -0
  94. skyflow/generated/rest/core/parse_error.py +36 -0
  95. skyflow/generated/rest/core/pydantic_utilities.py +486 -0
  96. skyflow/generated/rest/core/query_encoder.py +58 -0
  97. skyflow/generated/rest/core/remove_none_from_dict.py +11 -0
  98. skyflow/generated/rest/core/request_options.py +40 -0
  99. skyflow/generated/rest/core/serialization.py +347 -0
  100. skyflow/generated/rest/environment.py +8 -0
  101. skyflow/generated/rest/errors/__init__.py +53 -0
  102. skyflow/generated/rest/errors/bad_request_error.py +11 -0
  103. skyflow/generated/rest/errors/forbidden_error.py +11 -0
  104. skyflow/generated/rest/errors/internal_server_error.py +11 -0
  105. skyflow/generated/rest/errors/not_found_error.py +11 -0
  106. skyflow/generated/rest/errors/too_many_requests_error.py +11 -0
  107. skyflow/generated/rest/errors/unauthorized_error.py +11 -0
  108. skyflow/generated/rest/py.typed +0 -0
  109. skyflow/generated/rest/query/__init__.py +4 -0
  110. skyflow/generated/rest/query/client.py +139 -0
  111. skyflow/generated/rest/query/raw_client.py +239 -0
  112. skyflow/generated/rest/raw_client.py +86 -0
  113. skyflow/generated/rest/records/__init__.py +34 -0
  114. skyflow/generated/rest/records/client.py +685 -0
  115. skyflow/generated/rest/records/raw_client.py +988 -0
  116. skyflow/generated/rest/records/types/__init__.py +34 -0
  117. skyflow/generated/rest/records/types/update_request_update_type.py +5 -0
  118. skyflow/generated/rest/tokens/__init__.py +4 -0
  119. skyflow/generated/rest/tokens/client.py +246 -0
  120. skyflow/generated/rest/tokens/raw_client.py +507 -0
  121. skyflow/generated/rest/types/__init__.py +116 -0
  122. skyflow/generated/rest/types/column_redactions.py +33 -0
  123. skyflow/generated/rest/types/delete_response.py +23 -0
  124. skyflow/generated/rest/types/delete_response_object.py +42 -0
  125. skyflow/generated/rest/types/detokenize_response.py +23 -0
  126. skyflow/generated/rest/types/detokenize_response_object.py +54 -0
  127. skyflow/generated/rest/types/error_response.py +20 -0
  128. skyflow/generated/rest/types/error_response_error.py +28 -0
  129. skyflow/generated/rest/types/execute_query_record_response.py +22 -0
  130. skyflow/generated/rest/types/execute_query_response.py +26 -0
  131. skyflow/generated/rest/types/execute_query_response_metadata.py +26 -0
  132. skyflow/generated/rest/types/get_request_data.py +60 -0
  133. skyflow/generated/rest/types/get_response.py +23 -0
  134. skyflow/generated/rest/types/get_tokens_from_values_request_object.py +30 -0
  135. skyflow/generated/rest/types/get_tokens_from_values_response.py +23 -0
  136. skyflow/generated/rest/types/google_protobuf_value.py +8 -0
  137. skyflow/generated/rest/types/http_code.py +6 -0
  138. skyflow/generated/rest/types/insert_record_data.py +36 -0
  139. skyflow/generated/rest/types/insert_response.py +23 -0
  140. skyflow/generated/rest/types/record_response_object.py +70 -0
  141. skyflow/generated/rest/types/token_group_redactions.py +33 -0
  142. skyflow/generated/rest/types/tokenize_response_object.py +49 -0
  143. skyflow/generated/rest/types/unique_value.py +22 -0
  144. skyflow/generated/rest/types/update_record_data.py +52 -0
  145. skyflow/generated/rest/types/update_record_data_update_type.py +5 -0
  146. skyflow/generated/rest/types/update_response.py +23 -0
  147. skyflow/generated/rest/types/upsert.py +45 -0
  148. skyflow/generated/rest/types/upsert_update_type.py +5 -0
  149. skyflow/generated/rest/version.py +6 -0
  150. skyflow/service_account/__init__.py +15 -0
  151. skyflow/utils/__init__.py +9 -0
  152. skyflow/utils/_http_config.py +36 -0
  153. skyflow/utils/_response_parsing.py +66 -0
  154. skyflow/utils/_retry.py +97 -0
  155. skyflow/utils/_skyflow_messages.py +114 -0
  156. skyflow/utils/_utils.py +54 -0
  157. skyflow/utils/_version.py +1 -0
  158. skyflow/utils/enums/__init__.py +3 -0
  159. skyflow/utils/enums/_custom_header_key.py +10 -0
  160. skyflow/utils/enums/_env_urls.py +9 -0
  161. skyflow/utils/enums/_upsert_type.py +7 -0
  162. skyflow/utils/validations/__init__.py +9 -0
  163. skyflow/utils/validations/_validations.py +195 -0
  164. skyflow/vault/__init__.py +0 -0
  165. skyflow/vault/client/__init__.py +0 -0
  166. skyflow/vault/client/client.py +102 -0
  167. skyflow/vault/controller/__init__.py +1 -0
  168. skyflow/vault/controller/_vault.py +475 -0
  169. skyflow/vault/data/__init__.py +30 -0
  170. skyflow/vault/data/_column_redactions.py +4 -0
  171. skyflow/vault/data/_delete_options.py +8 -0
  172. skyflow/vault/data/_delete_request.py +5 -0
  173. skyflow/vault/data/_delete_response.py +9 -0
  174. skyflow/vault/data/_delete_response_record.py +9 -0
  175. skyflow/vault/data/_detokenize_options.py +8 -0
  176. skyflow/vault/data/_detokenize_request.py +9 -0
  177. skyflow/vault/data/_detokenize_response.py +9 -0
  178. skyflow/vault/data/_detokenize_response_record.py +16 -0
  179. skyflow/vault/data/_detokenize_response_record_metadata.py +10 -0
  180. skyflow/vault/data/_get_options.py +8 -0
  181. skyflow/vault/data/_get_request.py +17 -0
  182. skyflow/vault/data/_get_request_record.py +13 -0
  183. skyflow/vault/data/_get_response.py +9 -0
  184. skyflow/vault/data/_get_response_record.py +17 -0
  185. skyflow/vault/data/_insert_options.py +8 -0
  186. skyflow/vault/data/_insert_request.py +11 -0
  187. skyflow/vault/data/_insert_request_record.py +9 -0
  188. skyflow/vault/data/_insert_response.py +9 -0
  189. skyflow/vault/data/_insert_response_record.py +17 -0
  190. skyflow/vault/data/_request_context.py +16 -0
  191. skyflow/vault/data/_token.py +8 -0
  192. skyflow/vault/data/_token_group_redactions.py +4 -0
  193. skyflow/vault/data/_update_options.py +8 -0
  194. skyflow/vault/data/_update_request.py +11 -0
  195. skyflow/vault/data/_update_request_record.py +6 -0
  196. skyflow/vault/data/_update_response.py +9 -0
  197. skyflow/vault/data/_update_response_record.py +17 -0
  198. skyflow/vault/data/_upsert_options.py +7 -0
  199. skyflow_flowvault_python-1.0.0.dist-info/METADATA +896 -0
  200. skyflow_flowvault_python-1.0.0.dist-info/RECORD +202 -0
  201. skyflow_flowvault_python-1.0.0.dist-info/WHEEL +5 -0
  202. skyflow_flowvault_python-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,52 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ import pydantic
6
+ import typing_extensions
7
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
8
+ from ..core.serialization import FieldMetadata
9
+ from .update_record_data_update_type import UpdateRecordDataUpdateType
10
+
11
+
12
+ class UpdateRecordData(UniversalBaseModel):
13
+ skyflow_id: typing_extensions.Annotated[
14
+ str,
15
+ FieldMetadata(alias="skyflowID"),
16
+ pydantic.Field(alias="skyflowID", description="Skyflow ID of the record to update."),
17
+ ]
18
+ """
19
+ Skyflow ID of the record to update.
20
+ """
21
+
22
+ data: typing.Dict[str, typing.Any] = pydantic.Field()
23
+ """
24
+ Columns and values for the record.
25
+ """
26
+
27
+ table_name: typing_extensions.Annotated[
28
+ typing.Optional[str],
29
+ FieldMetadata(alias="tableName"),
30
+ pydantic.Field(alias="tableName", description="Name of the table to update data in."),
31
+ ] = None
32
+ """
33
+ Name of the table to update data in.
34
+ """
35
+
36
+ update_type: typing_extensions.Annotated[
37
+ typing.Optional[UpdateRecordDataUpdateType],
38
+ FieldMetadata(alias="updateType"),
39
+ pydantic.Field(alias="updateType", description="Type of update operation to perform."),
40
+ ] = None
41
+ """
42
+ Type of update operation to perform.
43
+ """
44
+
45
+ if IS_PYDANTIC_V2:
46
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
47
+ else:
48
+
49
+ class Config:
50
+ frozen = True
51
+ smart_union = True
52
+ extra = pydantic.Extra.allow
@@ -0,0 +1,5 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ UpdateRecordDataUpdateType = typing.Union[typing.Literal["UPDATE", "REPLACE"], typing.Any]
@@ -0,0 +1,23 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ import pydantic
6
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
7
+ from .record_response_object import RecordResponseObject
8
+
9
+
10
+ class UpdateResponse(UniversalBaseModel):
11
+ records: typing.List[RecordResponseObject] = pydantic.Field()
12
+ """
13
+ List of updated records.
14
+ """
15
+
16
+ if IS_PYDANTIC_V2:
17
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
18
+ else:
19
+
20
+ class Config:
21
+ frozen = True
22
+ smart_union = True
23
+ extra = pydantic.Extra.allow
@@ -0,0 +1,45 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ import pydantic
6
+ import typing_extensions
7
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
8
+ from ..core.serialization import FieldMetadata
9
+ from .upsert_update_type import UpsertUpdateType
10
+
11
+
12
+ class Upsert(UniversalBaseModel):
13
+ """
14
+ Upsert details.
15
+ """
16
+
17
+ update_type: typing_extensions.Annotated[
18
+ typing.Optional[UpsertUpdateType],
19
+ FieldMetadata(alias="updateType"),
20
+ pydantic.Field(alias="updateType", description="Type of update operation to perform."),
21
+ ] = None
22
+ """
23
+ Type of update operation to perform.
24
+ """
25
+
26
+ unique_columns: typing_extensions.Annotated[
27
+ typing.List[str],
28
+ FieldMetadata(alias="uniqueColumns"),
29
+ pydantic.Field(
30
+ alias="uniqueColumns",
31
+ description="List of unique columns in the table that upsert operations use to identify if a record with matching values exists. If a matching record exists, the record updates with the specified values. If a matching record doesn't exist, the upsert operation inserts a new record.",
32
+ ),
33
+ ]
34
+ """
35
+ List of unique columns in the table that upsert operations use to identify if a record with matching values exists. If a matching record exists, the record updates with the specified values. If a matching record doesn't exist, the upsert operation inserts a new record.
36
+ """
37
+
38
+ if IS_PYDANTIC_V2:
39
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
40
+ else:
41
+
42
+ class Config:
43
+ frozen = True
44
+ smart_union = True
45
+ extra = pydantic.Extra.allow
@@ -0,0 +1,5 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ UpsertUpdateType = typing.Union[typing.Literal["UPDATE", "REPLACE"], typing.Any]
@@ -0,0 +1,6 @@
1
+ from importlib import metadata
2
+
3
+ try:
4
+ __version__ = metadata.version("skyflow-flowvault-python")
5
+ except metadata.PackageNotFoundError:
6
+ __version__ = "0.0.0"
@@ -0,0 +1,15 @@
1
+ from common.service_account import (
2
+ generate_bearer_token,
3
+ generate_bearer_token_from_creds,
4
+ is_expired,
5
+ generate_signed_data_tokens,
6
+ generate_signed_data_tokens_from_creds,
7
+ )
8
+
9
+ __all__ = [
10
+ "generate_bearer_token",
11
+ "generate_bearer_token_from_creds",
12
+ "is_expired",
13
+ "generate_signed_data_tokens",
14
+ "generate_signed_data_tokens_from_creds",
15
+ ]
@@ -0,0 +1,9 @@
1
+ # Must be imported before anything that pulls in common.utils -- see its _skyflow_messages.py
2
+ # comment for why the load order matters.
3
+ from ._version import SDK_VERSION
4
+
5
+ from common.utils import LogLevel, Env
6
+
7
+ from .enums import UpsertType, EnvUrls, CustomHeaderKey
8
+ from ._skyflow_messages import SkyflowMessages
9
+ from ._utils import get_metrics, get_vault_url
@@ -0,0 +1,36 @@
1
+ from common.utils.validations._validations import VALID_VAULT_CONFIG_KEYS
2
+
3
+ TIMEOUT_KEY = "timeout"
4
+ CONNECT_TIMEOUT_KEY = "connect_timeout"
5
+ READ_TIMEOUT_KEY = "read_timeout"
6
+ WRITE_TIMEOUT_KEY = "write_timeout"
7
+ MAX_RETRIES_KEY = "max_retries"
8
+ INITIAL_RETRY_DELAY_MILLIS_KEY = "initial_retry_delay_millis"
9
+ MAX_RETRY_DELAY_MILLIS_KEY = "max_retry_delay_millis"
10
+ VAULT_URL_KEY = "vault_url"
11
+
12
+ DEFAULT_TIMEOUT = 60
13
+ DEFAULT_CONNECT_TIMEOUT = 10
14
+ DEFAULT_READ_TIMEOUT = 10
15
+ DEFAULT_WRITE_TIMEOUT = 10
16
+ DEFAULT_MAX_RETRIES = 0
17
+ DEFAULT_INITIAL_RETRY_DELAY_MILLIS = 500
18
+ DEFAULT_MAX_RETRY_DELAY_MILLIS = 2000
19
+
20
+ DEFAULT_MAX_CONNECTIONS = 100
21
+ DEFAULT_MAX_KEEPALIVE_CONNECTIONS = 100
22
+ DEFAULT_KEEPALIVE_EXPIRY = 60
23
+
24
+ POSITIVE_SECOND_KEYS = (TIMEOUT_KEY, CONNECT_TIMEOUT_KEY, READ_TIMEOUT_KEY, WRITE_TIMEOUT_KEY)
25
+ NON_NEGATIVE_INT_KEYS = (MAX_RETRIES_KEY, INITIAL_RETRY_DELAY_MILLIS_KEY, MAX_RETRY_DELAY_MILLIS_KEY)
26
+
27
+ CLIENT_HTTP_CONFIG_KEYS = (*POSITIVE_SECOND_KEYS, *NON_NEGATIVE_INT_KEYS)
28
+ VAULT_CONFIG_KEYS = [*VALID_VAULT_CONFIG_KEYS, *CLIENT_HTTP_CONFIG_KEYS, VAULT_URL_KEY]
29
+
30
+
31
+ def resolve_setting(vault_config, common_config, key, default):
32
+ if vault_config is not None and key in vault_config:
33
+ return vault_config[key]
34
+ if common_config is not None and key in common_config:
35
+ return common_config[key]
36
+ return default
@@ -0,0 +1,66 @@
1
+ def parse_tokens(raw):
2
+ if raw is None:
3
+ return None
4
+ parsed = {}
5
+ for column, value in raw.items():
6
+ entries = _parse_entries(value, _to_token)
7
+ if entries is not None:
8
+ parsed[column] = entries
9
+ return parsed
10
+
11
+
12
+ def parse_hashed_data(raw):
13
+ if raw is None:
14
+ return None
15
+ parsed = {}
16
+ for column, value in raw.items():
17
+ entries = _parse_entries(value, _to_hash)
18
+ if entries is not None:
19
+ parsed[column] = entries
20
+ return parsed
21
+
22
+
23
+ def parse_metadata(raw):
24
+ if raw is None:
25
+ return None
26
+ from skyflow.vault.data._detokenize_response_record_metadata import DetokenizeResponseRecordMetadata
27
+ return DetokenizeResponseRecordMetadata(
28
+ skyflow_id=raw.get('skyflowID', raw.get('skyflowId', raw.get('skyflow_id'))),
29
+ table_name=raw.get('table', raw.get('tableName', raw.get('table_name'))),
30
+ )
31
+
32
+
33
+ def _parse_entries(raw_value, to_entry):
34
+ if raw_value is None:
35
+ return None
36
+ items = raw_value if isinstance(raw_value, list) else [raw_value]
37
+ entries = []
38
+ for item in items:
39
+ entry = to_entry(item)
40
+ if entry is not None:
41
+ entries.append(entry)
42
+ return entries
43
+
44
+
45
+ def _to_token(entry):
46
+ from skyflow.vault.data._token import Token
47
+ if isinstance(entry, dict):
48
+ return Token(
49
+ token=entry.get('token'),
50
+ token_group_name=entry.get('tokenGroupName', entry.get('token_group_name')),
51
+ path=entry.get('path'),
52
+ )
53
+ if entry is not None:
54
+ return Token(token=entry)
55
+ return None
56
+
57
+
58
+ def _to_hash(entry):
59
+ if isinstance(entry, dict):
60
+ return {
61
+ 'data': entry.get('data'),
62
+ 'hash_name': entry.get('hashName', entry.get('hash_name')),
63
+ }
64
+ if entry is not None:
65
+ return {'data': entry, 'hash_name': None}
66
+ return None
@@ -0,0 +1,97 @@
1
+ import asyncio
2
+ import time
3
+ from random import randint
4
+
5
+ import httpx
6
+
7
+ RETRYABLE_STATUS_CODES = frozenset({408, 429})
8
+ MIN_SERVER_ERROR_STATUS = 500
9
+ JITTER_FACTOR = 0.2
10
+ MILLIS_PER_SECOND = 1000.0
11
+
12
+
13
+ def should_retry(status_code):
14
+ return status_code in RETRYABLE_STATUS_CODES or status_code >= MIN_SERVER_ERROR_STATUS
15
+
16
+
17
+ def backoff_millis(attempt, initial_millis, max_millis):
18
+ delay = initial_millis
19
+ step = 1
20
+ while step < attempt and delay < max_millis:
21
+ delay = max_millis if delay > max_millis / 2 else delay * 2
22
+ step += 1
23
+ delay = min(delay, max_millis)
24
+ jitter = int(delay * JITTER_FACTOR)
25
+ if jitter <= 0:
26
+ return delay
27
+ return max(0, delay - jitter + randint(0, 2 * jitter))
28
+
29
+
30
+ class _RetryPolicy:
31
+ def __init__(self, max_retries, initial_millis, max_millis, call_timeout):
32
+ self.max_retries = max_retries
33
+ self.initial_millis = initial_millis
34
+ self.max_millis = max_millis
35
+ self.call_timeout = call_timeout
36
+
37
+ def deadline(self):
38
+ return time.monotonic() + self.call_timeout if self.call_timeout else None
39
+
40
+ def next_sleep_seconds(self, retry, deadline):
41
+ if retry >= self.max_retries:
42
+ return None
43
+ if deadline is not None and time.monotonic() >= deadline:
44
+ return None
45
+ sleep_seconds = backoff_millis(retry + 1, self.initial_millis, self.max_millis) / MILLIS_PER_SECOND
46
+ if deadline is not None:
47
+ remaining = deadline - time.monotonic()
48
+ if remaining <= 0:
49
+ return None
50
+ sleep_seconds = min(sleep_seconds, remaining)
51
+ return sleep_seconds
52
+
53
+
54
+ class RetryTransport(httpx.BaseTransport):
55
+ def __init__(self, transport, max_retries, initial_millis, max_millis, call_timeout):
56
+ self._transport = transport
57
+ self._policy = _RetryPolicy(max_retries, initial_millis, max_millis, call_timeout)
58
+
59
+ def handle_request(self, request):
60
+ deadline = self._policy.deadline()
61
+ response = self._transport.handle_request(request)
62
+ retry = 0
63
+ while should_retry(response.status_code):
64
+ sleep_seconds = self._policy.next_sleep_seconds(retry, deadline)
65
+ if sleep_seconds is None:
66
+ break
67
+ response.close()
68
+ time.sleep(sleep_seconds)
69
+ response = self._transport.handle_request(request)
70
+ retry += 1
71
+ return response
72
+
73
+ def close(self):
74
+ self._transport.close()
75
+
76
+
77
+ class AsyncRetryTransport(httpx.AsyncBaseTransport):
78
+ def __init__(self, transport, max_retries, initial_millis, max_millis, call_timeout):
79
+ self._transport = transport
80
+ self._policy = _RetryPolicy(max_retries, initial_millis, max_millis, call_timeout)
81
+
82
+ async def handle_async_request(self, request):
83
+ deadline = self._policy.deadline()
84
+ response = await self._transport.handle_async_request(request)
85
+ retry = 0
86
+ while should_retry(response.status_code):
87
+ sleep_seconds = self._policy.next_sleep_seconds(retry, deadline)
88
+ if sleep_seconds is None:
89
+ break
90
+ await response.aclose()
91
+ await asyncio.sleep(sleep_seconds)
92
+ response = await self._transport.handle_async_request(request)
93
+ retry += 1
94
+ return response
95
+
96
+ async def aclose(self):
97
+ await self._transport.aclose()
@@ -0,0 +1,114 @@
1
+ from enum import Enum
2
+
3
+ try:
4
+ from ._version import SDK_VERSION
5
+ except ImportError: # pragma: no cover
6
+ SDK_VERSION = "0.0.0"
7
+
8
+ error_prefix = f"Skyflow Python SDK {SDK_VERSION}"
9
+ INFO = "INFO"
10
+ WARN = "WARN"
11
+ ERROR = "ERROR"
12
+
13
+
14
+ class SkyflowMessages:
15
+ """v3's own operation-specific message catalog, mirroring v2's per-variant pattern. Generic
16
+ infrastructure text lives in common.utils.SkyflowMessages instead."""
17
+
18
+ class Error(Enum):
19
+ INVALID_TIMEOUT = f"{error_prefix} Validation error. '{{}}' must be a positive number of seconds."
20
+ INVALID_RETRY_SETTING = f"{error_prefix} Validation error. '{{}}' must be a non-negative integer."
21
+ INVALID_VAULT_URL = f"{error_prefix} Validation error. 'vault_url' must be a non-empty string."
22
+ EMPTY_RECORDS_IN_INSERT = f"{error_prefix} Insert failed. Specify at least one record to insert."
23
+ INVALID_RECORDS_TYPE_IN_INSERT = f"{error_prefix} Insert failed. 'records' must be a list of InsertRequestRecord objects."
24
+ INVALID_RECORD_DATA_IN_INSERT = f"{error_prefix} Validation error. Each record's 'values' must be a non-empty dict."
25
+ INVALID_TABLE_NAME_IN_INSERT = f"{error_prefix} Validation error. 'table' must be a non-empty string."
26
+ INVALID_UPSERT_TYPE_IN_INSERT = f"{error_prefix} Insert failed. 'upsert' must be an UpsertOptions object."
27
+ INVALID_UPSERT_UNIQUE_COLUMNS_IN_INSERT = f"{error_prefix} Insert failed. Upsert's 'unique_columns' must be a non-empty list of strings."
28
+ INVALID_UPSERT_UPDATE_TYPE_IN_INSERT = f"{error_prefix} Insert failed. Upsert's 'update_type' must be an UpsertType value."
29
+ TABLE_NAME_IN_BOTH_PLACES_IN_INSERT = (
30
+ f"{error_prefix} Insert failed. 'table' cannot be set on InsertRequest at the same "
31
+ "time as any record's 'table' -- the vault accepts a table name outside the records "
32
+ "(request-level, applying to all of them) or inside each record, but not both at once."
33
+ )
34
+ TABLE_NAME_MISSING_IN_INSERT = (
35
+ f"{error_prefix} Insert failed. 'table' is not set on InsertRequest, so every record "
36
+ "must set its own 'table' -- either set 'table' once at the request level, or set it "
37
+ "individually on every record."
38
+ )
39
+ RECORD_LEVEL_UPSERT_NOT_ALLOWED_IN_INSERT = (
40
+ f"{error_prefix} Insert failed. 'table' is set on InsertRequest (request-level), so "
41
+ "'upsert' must also be provided at the request level -- a record cannot set its own "
42
+ "'upsert' while 'table' is set at the request level."
43
+ )
44
+ REQUEST_LEVEL_UPSERT_NOT_ALLOWED_IN_INSERT = (
45
+ f"{error_prefix} Insert failed. 'table' is set per-record, so 'upsert' must also be "
46
+ "provided per-record -- InsertRequest's request-level 'upsert' cannot be used while "
47
+ "'table' is set on individual records."
48
+ )
49
+ EMPTY_KEY_IN_INSERT_DATA = f"{error_prefix} Validation error. Each record's 'values' must not contain a null or empty key."
50
+ EMPTY_VALUE_IN_INSERT_DATA = f"{error_prefix} Insert failed. Each record's 'values' must not contain a null or empty value."
51
+
52
+ MISSING_TABLE_NAME_IN_GET = f"{error_prefix} Get failed. Specify a table name."
53
+ MISSING_IDS_OR_UNIQUE_VALUES_IN_GET = f"{error_prefix} Get failed. Specify at least one of 'ids' or 'unique_values'."
54
+ INVALID_IDS_IN_GET = f"{error_prefix} Get failed. 'ids' must be a non-empty list of strings."
55
+ INVALID_RECORDS_TYPE_IN_GET = f"{error_prefix} Get failed. 'records' must be a non-empty list of GetRequestRecord objects."
56
+ GET_MODE_CONFLICT = f"{error_prefix} Get failed. Use either 'records' (multi-table) or the single-table fields (table/ids/unique_values/columns/column_redactions/limit/offset), not both."
57
+
58
+ EMPTY_RECORDS_IN_UPDATE = f"{error_prefix} Update failed. Specify at least one record to update."
59
+ INVALID_RECORDS_TYPE_IN_UPDATE = f"{error_prefix} Update failed. 'records' must be a list of UpdateRequestRecord objects."
60
+ MISSING_SKYFLOW_ID_IN_UPDATE = f"{error_prefix} Update failed. Each record must specify a non-empty 'skyflow_id'."
61
+ MISSING_DATA_IN_UPDATE = f"{error_prefix} Update failed. Each record must specify a non-empty 'data' object."
62
+ INVALID_DATA_TYPE_IN_UPDATE = f"{error_prefix} Update failed. Each record's 'data' must be a dict."
63
+ INVALID_UPDATE_TYPE_IN_UPDATE = f"{error_prefix} Update failed. 'update_type' must be an UpsertType value."
64
+ TABLE_NAME_IN_BOTH_PLACES_IN_UPDATE = (
65
+ f"{error_prefix} Update failed. 'table' cannot be set on UpdateRequest at the same "
66
+ "time as any record's 'table' -- specify a table name outside the records "
67
+ "(request-level, applying to all of them) or inside each record, but not both at once."
68
+ )
69
+ TABLE_NAME_MISSING_IN_UPDATE = (
70
+ f"{error_prefix} Update failed. 'table' is not set on UpdateRequest, so every record "
71
+ "must set its own 'table' -- either set 'table' once at the request level, or set it "
72
+ "individually on every record."
73
+ )
74
+
75
+ MISSING_TABLE_NAME_IN_DELETE = f"{error_prefix} Delete failed. Specify a table name."
76
+ MISSING_IDS_OR_UNIQUE_VALUES_IN_DELETE = f"{error_prefix} Delete failed. Specify at least one of 'ids' or 'unique_values'."
77
+ INVALID_IDS_IN_DELETE = f"{error_prefix} Delete failed. 'ids' must be a non-empty list of strings."
78
+
79
+ EMPTY_TOKENS_IN_DETOKENIZE = f"{error_prefix} Detokenize failed. Specify at least one token to detokenize."
80
+ INVALID_TOKENS_TYPE_IN_DETOKENIZE = f"{error_prefix} Detokenize failed. 'tokens' must be a non-empty list of strings."
81
+ INVALID_TOKEN_GROUP_REDACTIONS_IN_DETOKENIZE = f"{error_prefix} Detokenize failed. 'token_group_redactions' must be a list of TokenGroupRedactions objects with a non-empty 'token_group_name'."
82
+
83
+ class Info(Enum):
84
+ VALIDATE_INSERT_REQUEST = f"{INFO}: [{error_prefix}] Validating insert request."
85
+ INSERT_TRIGGERED = f"{INFO}: [{error_prefix}] Insert method triggered."
86
+ INSERT_REQUEST_RESOLVED = f"{INFO}: [{error_prefix}] Insert request resolved."
87
+ INSERT_SUCCESS = f"{INFO}: [{error_prefix}] Data inserted."
88
+
89
+ VALIDATE_GET_REQUEST = f"{INFO}: [{error_prefix}] Validating get request."
90
+ GET_TRIGGERED = f"{INFO}: [{error_prefix}] Get method triggered."
91
+ GET_REQUEST_RESOLVED = f"{INFO}: [{error_prefix}] Get request resolved."
92
+ GET_SUCCESS = f"{INFO}: [{error_prefix}] Data fetched."
93
+
94
+ VALIDATE_UPDATE_REQUEST = f"{INFO}: [{error_prefix}] Validating update request."
95
+ UPDATE_TRIGGERED = f"{INFO}: [{error_prefix}] Update method triggered."
96
+ UPDATE_REQUEST_RESOLVED = f"{INFO}: [{error_prefix}] Update request resolved."
97
+ UPDATE_SUCCESS = f"{INFO}: [{error_prefix}] Data updated."
98
+
99
+ VALIDATE_DELETE_REQUEST = f"{INFO}: [{error_prefix}] Validating delete request."
100
+ DELETE_TRIGGERED = f"{INFO}: [{error_prefix}] Delete method triggered."
101
+ DELETE_REQUEST_RESOLVED = f"{INFO}: [{error_prefix}] Delete request resolved."
102
+ DELETE_SUCCESS = f"{INFO}: [{error_prefix}] Data deleted."
103
+
104
+ VALIDATE_DETOKENIZE_REQUEST = f"{INFO}: [{error_prefix}] Validating detokenize request."
105
+ DETOKENIZE_TRIGGERED = f"{INFO}: [{error_prefix}] Detokenize method triggered."
106
+ DETOKENIZE_REQUEST_RESOLVED = f"{INFO}: [{error_prefix}] Detokenize request resolved."
107
+ DETOKENIZE_SUCCESS = f"{INFO}: [{error_prefix}] Tokens detokenized."
108
+
109
+ class ErrorLogs(Enum):
110
+ INSERT_RECORDS_REJECTED = f"{ERROR}: [{error_prefix}] Insert call resulted in failure."
111
+ GET_RECORDS_REJECTED = f"{ERROR}: [{error_prefix}] Get call resulted in failure."
112
+ UPDATE_RECORDS_REJECTED = f"{ERROR}: [{error_prefix}] Update call resulted in failure."
113
+ DELETE_RECORDS_REJECTED = f"{ERROR}: [{error_prefix}] Delete call resulted in failure."
114
+ DETOKENIZE_RECORDS_REJECTED = f"{ERROR}: [{error_prefix}] Detokenize call resulted in failure."
@@ -0,0 +1,54 @@
1
+ import platform
2
+ import sys
3
+
4
+ from common.errors import SkyflowError
5
+ from common.utils import SkyflowMessages as CommonMessages
6
+ from common.utils.constants import PROTOCOL, SdkMetricsKey, SdkPrefix
7
+ from common.utils.enums import Env
8
+ from ._version import SDK_VERSION
9
+ from .enums import EnvUrls
10
+
11
+ _CACHED_METRICS: dict = {}
12
+
13
+ invalid_input_error_code = CommonMessages.ErrorCodes.INVALID_INPUT.value
14
+
15
+
16
+ def get_vault_url(cluster_id, env, vault_id, logger=None):
17
+ """Mirrors common.utils.get_vault_url with v3's own EnvUrls (different subdomain)."""
18
+ if not cluster_id or not isinstance(cluster_id, str) or not cluster_id.strip():
19
+ raise SkyflowError(CommonMessages.Error.INVALID_CLUSTER_ID.value.format(vault_id), invalid_input_error_code)
20
+
21
+ if not isinstance(env, Env):
22
+ raise SkyflowError(CommonMessages.Error.INVALID_ENV.value.format(vault_id), invalid_input_error_code)
23
+
24
+ base_url = EnvUrls[env.name].value
25
+
26
+ return f"{PROTOCOL}://{cluster_id}.{base_url}"
27
+
28
+
29
+ def get_metrics():
30
+ if _CACHED_METRICS:
31
+ return _CACHED_METRICS
32
+
33
+ try:
34
+ sdk_client_device_model = platform.node()
35
+ except Exception:
36
+ sdk_client_device_model = ""
37
+
38
+ try:
39
+ sdk_client_os_details = sys.platform
40
+ except Exception:
41
+ sdk_client_os_details = ""
42
+
43
+ try:
44
+ sdk_runtime_details = sys.version
45
+ except Exception:
46
+ sdk_runtime_details = ""
47
+
48
+ _CACHED_METRICS.update({
49
+ SdkMetricsKey.SDK_NAME_VERSION: SdkPrefix.SKYFLOW_PYTHON + SDK_VERSION,
50
+ SdkMetricsKey.SDK_CLIENT_DEVICE_MODEL: sdk_client_device_model,
51
+ SdkMetricsKey.SDK_CLIENT_OS_DETAILS: sdk_client_os_details,
52
+ SdkMetricsKey.SDK_RUNTIME_DETAILS: SdkPrefix.PYTHON_RUNTIME + sdk_runtime_details,
53
+ })
54
+ return _CACHED_METRICS
@@ -0,0 +1 @@
1
+ SDK_VERSION = '1.0.0'
@@ -0,0 +1,3 @@
1
+ from ._upsert_type import UpsertType
2
+ from ._env_urls import EnvUrls
3
+ from ._custom_header_key import CustomHeaderKey
@@ -0,0 +1,10 @@
1
+ from enum import Enum
2
+
3
+
4
+ class CustomHeaderKey(Enum):
5
+ SKYFLOW_ACCOUNT_ID = "x-skyflow-account-id"
6
+ SKYFLOW_ACCOUNT_NAME = "x-skyflow-account-name"
7
+ REQUEST_ID_HEADER = "x-request-id"
8
+
9
+ def __str__(self):
10
+ return self.value
@@ -0,0 +1,9 @@
1
+ from enum import Enum
2
+
3
+
4
+ class EnvUrls(Enum):
5
+ """v3 vault hosts -- a different subdomain than v2 (skyvault vs. vault). All four confirmed."""
6
+ DEV = "skyvault.skyflowapis.dev"
7
+ PROD = "skyvault.skyflowapis.com"
8
+ SANDBOX = "skyvault.skyflowapis-preview.com"
9
+ STAGE = "skyvault.skyflowapis.tech"
@@ -0,0 +1,7 @@
1
+ from enum import Enum
2
+
3
+
4
+ class UpsertType(Enum):
5
+ """Mirrors the wire enum FlowEnumUpdateType (V1Upsert.update_type)."""
6
+ REPLACE = "REPLACE"
7
+ UPDATE = "UPDATE"
@@ -0,0 +1,9 @@
1
+ from ._validations import (
2
+ validate_vault_config,
3
+ validate_update_vault_config,
4
+ validate_insert_request,
5
+ validate_get_request,
6
+ validate_update_request,
7
+ validate_delete_request,
8
+ validate_detokenize_request,
9
+ )