Skip to content

Commit b861313

Browse files
SK-2954: Add ISkyflow/IVaultController/IVaultClient interfaces, type insert()
Splits BaseSkyflow, BaseVaultController, and BaseVaultClient into a pure interface (ISkyflow/IVaultController/IVaultClient, declaring the contract via ABC + abstractmethod) plus a base class implementing the shared logic, so future variant-specific overrides have a clear contract to satisfy. Also adds type hints to insert() at every layer (BaseInsertRequest/BaseInsertResponse in the abstract method, each variant's own InsertRequest/InsertResponse in their concrete override), and renames base_vault.py to base_vault_controller.py to match its class name. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent f2bbf42 commit b861313

10 files changed

Lines changed: 156 additions & 59 deletions

File tree

‎common/client/base_skyflow.py‎

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from abc import ABC, abstractmethod
12
from collections import OrderedDict
23
from functools import partial
34

@@ -13,7 +14,79 @@
1314
)
1415

1516

16-
class Skyflow:
17+
class ISkyflow(ABC):
18+
@classmethod
19+
@abstractmethod
20+
def builder(cls):
21+
raise NotImplementedError
22+
23+
@abstractmethod
24+
def add_vault_config(self, config):
25+
raise NotImplementedError
26+
27+
@abstractmethod
28+
def remove_vault_config(self, vault_id):
29+
raise NotImplementedError
30+
31+
@abstractmethod
32+
def update_vault_config(self, config):
33+
raise NotImplementedError
34+
35+
@abstractmethod
36+
def get_vault_config(self, vault_id):
37+
raise NotImplementedError
38+
39+
@abstractmethod
40+
def add_connection_config(self, config):
41+
raise NotImplementedError
42+
43+
@abstractmethod
44+
def remove_connection_config(self, connection_id):
45+
raise NotImplementedError
46+
47+
@abstractmethod
48+
def update_connection_config(self, config):
49+
raise NotImplementedError
50+
51+
@abstractmethod
52+
def get_connection_config(self, connection_id):
53+
raise NotImplementedError
54+
55+
@abstractmethod
56+
def add_skyflow_credentials(self, credentials):
57+
raise NotImplementedError
58+
59+
@abstractmethod
60+
def update_skyflow_credentials(self, credentials):
61+
raise NotImplementedError
62+
63+
@abstractmethod
64+
def set_log_level(self, log_level):
65+
raise NotImplementedError
66+
67+
@abstractmethod
68+
def update_log_level(self, log_level):
69+
raise NotImplementedError
70+
71+
@abstractmethod
72+
def get_log_level(self):
73+
raise NotImplementedError
74+
75+
@abstractmethod
76+
def vault(self, vault_id=None):
77+
raise NotImplementedError
78+
79+
@abstractmethod
80+
def connection(self, connection_id=None):
81+
raise NotImplementedError
82+
83+
@abstractmethod
84+
def detect(self, vault_id=None):
85+
raise NotImplementedError
86+
87+
88+
class BaseSkyflow(ISkyflow):
89+
1790
def __init__(self, builder):
1891
self.__builder = builder
1992
log_info(self.__builder._skyflow_messages.Info.CLIENT_INITIALIZED.value, self.__builder.get_logger())
@@ -87,9 +160,10 @@ def detect(self, vault_id=None):
87160
vault_config = self.__builder.get_vault_config(vault_id)
88161
return vault_config.get(OptionField.DETECT_CONTROLLER)
89162

90-
class Builder:
163+
class Builder(ABC):
91164
# -- hooks, filled in per-variant by make_skyflow_class() -- left None here so using
92-
# this template directly (rather than through make_skyflow_class()) fails fast.
165+
# this template directly (rather than through make_skyflow_class()) fails fast, with a
166+
# clear message (see _REQUIRED_HOOKS check in __init__ below).
93167
_vault_client_cls = None
94168
_vault_controller_cls = None
95169
_connection_cls = None
@@ -106,7 +180,21 @@ class Builder:
106180
_validate_credentials = None
107181
_set_active_log_level = None
108182

183+
# Connection/Detect support and their validators are legitimately optional per variant --
184+
# everything else must be supplied by make_skyflow_class() before this Builder is usable.
185+
_REQUIRED_HOOKS = (
186+
'_vault_client_cls', '_vault_controller_cls', '_logger_cls', '_default_log_level',
187+
'_skyflow_messages', '_skyflow_cls', '_validate_vault_config',
188+
'_validate_update_vault_config', '_validate_log_level', '_validate_credentials',
189+
)
190+
109191
def __init__(self):
192+
missing = [hook for hook in self._REQUIRED_HOOKS if getattr(self, hook) is None]
193+
if missing:
194+
raise NotImplementedError(
195+
"BaseSkyflow.Builder is an interface template -- build a concrete Skyflow "
196+
f"class via make_skyflow_class() instead of using it directly. Missing: {', '.join(missing)}"
197+
)
110198
self.__vault_configs = OrderedDict()
111199
self.__vault_list = list()
112200
self.__connection_configs = OrderedDict()
@@ -326,7 +414,7 @@ def make_skyflow_class(*, vault_client_cls, vault_controller_cls, skyflow_messag
326414
'_validate_credentials': staticmethod(validate_credentials),
327415
'_set_active_log_level': staticmethod(set_active_log_level) if set_active_log_level else None,
328416
}
329-
variant_builder = type('Builder', (Skyflow.Builder,), builder_attrs)
330-
variant_skyflow = type('Skyflow', (Skyflow,), {'Builder': variant_builder})
417+
variant_builder = type('Builder', (BaseSkyflow.Builder,), builder_attrs)
418+
variant_skyflow = type('Skyflow', (BaseSkyflow,), {'Builder': variant_builder})
331419
variant_builder._skyflow_cls = variant_skyflow
332420
return variant_skyflow

‎common/tests/client/test_base_skyflow.py‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from common.errors import SkyflowError
44
from common.utils import LogLevel, SkyflowMessages
55
from common.utils.logger import Logger
6-
from common.client.base_skyflow import make_skyflow_class
6+
from common.client.base_skyflow import BaseSkyflow, make_skyflow_class
77

88

99
class FakeVaultClient:
@@ -185,5 +185,17 @@ def test_make_skyflow_class_requires_connection_validators_when_connection_cls_g
185185
)
186186

187187

188+
class TestBaseSkyflowInterface(unittest.TestCase):
189+
def test_using_the_template_builder_directly_raises_with_a_clear_message(self):
190+
with self.assertRaises(NotImplementedError) as ctx:
191+
BaseSkyflow.Builder()
192+
self.assertIn("make_skyflow_class()", str(ctx.exception))
193+
self.assertIn("_vault_client_cls", str(ctx.exception))
194+
195+
def test_make_skyflow_class_produced_builder_constructs_fine(self):
196+
Skyflow = make_fake_skyflow()
197+
self.assertIsInstance(Skyflow.builder(), BaseSkyflow.Builder)
198+
199+
188200
if __name__ == "__main__":
189201
unittest.main()

common/tests/vault/test_base_vault.py renamed to common/tests/vault/test_base_vault_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from common.errors import SkyflowError
44
from common.utils import SkyflowMessages
5-
from common.vault.base_vault import BaseVaultController
5+
from common.vault.base_vault_controller import BaseVaultController
66

77

88
class DummyVaultController(BaseVaultController):

‎common/vault/base_vault_client.py‎

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@
66
from common.utils.constants import OptionField, CredentialField, ConfigField
77

88

9-
class BaseVaultClient(ABC):
10-
"""Shared credential resolution, vault-URL resolution, and bearer-token fetch/cache/expiry
11-
logic. Uses single-underscore attributes deliberately -- double-underscore would name-mangle
12-
per-subclass and break cross-class state sharing."""
9+
class IVaultClient(ABC):
10+
@abstractmethod
11+
def resolve_vault_url(self, cluster_id, env, vault_id, logger=None):
12+
raise NotImplementedError
13+
14+
@abstractmethod
15+
def initialize_api_client(self, vault_url, bearer_token):
16+
raise NotImplementedError
17+
1318

19+
class BaseVaultClient(IVaultClient):
1420
def __init__(self, config):
1521
self._config = config
1622
self._common_skyflow_credentials = None
@@ -52,19 +58,6 @@ def initialize_client_configuration(self):
5258
if needs_reinit:
5359
self.initialize_api_client(self._vault_url, bearer_token)
5460

55-
@abstractmethod
56-
def resolve_vault_url(self, cluster_id, env, vault_id, logger=None):
57-
"""Per-variant hook: different vault types are hosted on different subdomains for the
58-
same cluster_id/env (v2: vault.skyflowapis.<tld>; v3: skyvault.skyflowapis.<tld>)."""
59-
raise NotImplementedError
60-
61-
@abstractmethod
62-
def initialize_api_client(self, vault_url, bearer_token):
63-
"""Construct the variant's generated API client into self._api_client. v2 bakes
64-
bearer_token into a refreshable callable; v3's client has no token param at all, so auth
65-
is injected per-call instead (see get_current_bearer_token)."""
66-
raise NotImplementedError
67-
6861
def get_current_bearer_token(self):
6962
return self._bearer_token
7063

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,39 @@
22

33
from common.errors import SkyflowError
44
from common.utils import SkyflowMessages as _CommonSkyflowMessages
5+
from common.vault.data import BaseInsertRequest, BaseInsertResponse
56

67
_INVALID_INPUT_ERROR_CODE = _CommonSkyflowMessages.ErrorCodes.INVALID_INPUT.value
78

89

9-
class BaseVaultController(ABC):
10+
class IVaultController(ABC):
11+
12+
@abstractmethod
13+
def insert(self, request: BaseInsertRequest) -> BaseInsertResponse:
14+
raise NotImplementedError
15+
16+
@abstractmethod
17+
def get(self, request):
18+
raise NotImplementedError
19+
20+
@abstractmethod
21+
def update(self, request):
22+
raise NotImplementedError
23+
24+
@abstractmethod
25+
def delete(self, request):
26+
raise NotImplementedError
27+
28+
@abstractmethod
29+
def query(self, request):
30+
raise NotImplementedError
31+
32+
@abstractmethod
33+
def detokenize(self, request):
34+
raise NotImplementedError
35+
36+
37+
class BaseVaultController(IVaultController):
1038

1139
_skyflow_messages = None
1240

@@ -37,27 +65,3 @@ def _validate_field_values(self, values):
3765
# self._skyflow_messages.Error.EMPTY_VALUE_IN_INSERT_DATA.value,
3866
# _INVALID_INPUT_ERROR_CODE,
3967
# )
40-
41-
@abstractmethod
42-
def insert(self, request):
43-
raise NotImplementedError
44-
45-
@abstractmethod
46-
def get(self, request):
47-
raise NotImplementedError
48-
49-
@abstractmethod
50-
def update(self, request):
51-
raise NotImplementedError
52-
53-
@abstractmethod
54-
def delete(self, request):
55-
raise NotImplementedError
56-
57-
@abstractmethod
58-
def query(self, request):
59-
raise NotImplementedError
60-
61-
@abstractmethod
62-
def detokenize(self, request):
63-
raise NotImplementedError

‎flowvault/skyflow_flowvault/vault/controller/_vault.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
from common.utils import SkyflowMessages as CommonMessages
44
from common.utils.constants import SKY_META_DATA_HEADER
55
from common.utils.logger import log_info, log_error_log
6-
from common.vault.base_vault import BaseVaultController
6+
from common.vault.base_vault_controller import BaseVaultController
77
from skyflow_flowvault.generated.rest import V1InsertRecordData, V1Upsert
88
from skyflow_flowvault.generated.rest.core import ApiError
99
from skyflow_flowvault.utils import SkyflowMessages, get_metrics
1010
from skyflow_flowvault.utils.validations import validate_insert_request
11-
from skyflow_flowvault.vault.data import InsertResponse
11+
from skyflow_flowvault.vault.data import InsertRequest, InsertResponse
1212

1313
REQUEST_ID_HEADER = "x-request-id"
1414

@@ -19,7 +19,7 @@ class VaultController(BaseVaultController):
1919
def __init__(self, vault_client):
2020
super().__init__(vault_client)
2121

22-
def insert(self, request):
22+
def insert(self, request: InsertRequest) -> InsertResponse:
2323
log_info(SkyflowMessages.Info.VALIDATE_INSERT_REQUEST.value, self._vault_client.get_logger())
2424
validate_insert_request(self._vault_client.get_logger(), request)
2525
self._validate_table_name_if_present(request.table)

‎flowvault/tests/utils/validations/test__validations.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def test_table_missing_from_one_record_raises(self):
8484
# dict, are now validated by the controller via the shared
8585
# BaseVaultController._validate_field_values() -- see test__vault.py's
8686
# test_insert_raises_on_empty_key/_on_empty_value/_on_non_dict_values/_on_empty_values, and
87-
# common/tests/vault/test_base_vault.py for the shared helper's own unit tests.
87+
# common/tests/vault/test_base_vault_controller.py for the shared helper's own unit tests.
8888

8989
def test_falsy_non_string_values_are_valid(self):
9090
"""0, False, [], {} are all legitimate values -- only None/empty-string should raise
@@ -123,7 +123,7 @@ def test_records_must_not_be_empty(self):
123123
# Table name format (non-empty string if provided) is now validated by the controller via
124124
# the shared BaseVaultController._validate_table_name_if_present() -- see
125125
# test__vault.py's test_insert_raises_on_invalid_table_name and
126-
# common/tests/vault/test_base_vault.py for the shared helper's own unit tests.
126+
# common/tests/vault/test_base_vault_controller.py for the shared helper's own unit tests.
127127

128128
def test_table_is_optional_when_every_record_has_its_own(self):
129129
request = InsertRequest(records=[dict(values={"a": 1}, table="t2")])

‎flowvault/tests/vault/controller/test__vault.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def test_insert_raises_for_invalid_request(self):
6464

6565
# ------------------------------------------------------------------ #
6666
# shared BaseVaultController validation helpers, exercised end-to-end via insert()
67-
# (unit-tested in isolation in common/tests/vault/test_base_vault.py)
67+
# (unit-tested in isolation in common/tests/vault/test_base_vault_controller.py)
6868
# ------------------------------------------------------------------ #
6969

7070
def test_insert_raises_on_empty_key(self):

‎v2/skyflow/vault/controller/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
from ._detect import Detect
44

55
# Public backward-compatible name -- existing consumers do `from skyflow.vault.controller import
6-
# Vault`; VaultController is the canonical internal name (extends common.vault.base_vault's
6+
# Vault`; VaultController is the canonical internal name (extends common.vault.base_vault_controller's
77
# BaseVaultController), but the old public name must keep resolving to the exact same class.
88
Vault = VaultController

‎v2/skyflow/vault/controller/_vault.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import json
33
import os
44
from typing import Optional
5-
from common.vault.base_vault import BaseVaultController
5+
from common.vault.base_vault_controller import BaseVaultController
66
from skyflow.generated.rest import V1FieldRecords, V1BatchRecord, V1TokenizeRecordRequest, \
77
V1DetokenizeRecordRequest
88
from skyflow.generated.rest.core.file import File
@@ -15,7 +15,7 @@
1515
from skyflow.utils.logger import log_info, log_error_log
1616
from skyflow.utils.validations import validate_insert_request, validate_delete_request, validate_query_request, \
1717
validate_get_request, validate_update_request, validate_detokenize_request, validate_tokenize_request, validate_file_upload_request
18-
from skyflow.vault.data import InsertRequest, UpdateRequest, DeleteRequest, GetRequest, QueryRequest, FileUploadRequest, FileUploadResponse
18+
from skyflow.vault.data import InsertRequest, InsertResponse, UpdateRequest, DeleteRequest, GetRequest, QueryRequest, FileUploadRequest, FileUploadResponse
1919
from skyflow.vault.tokens import DetokenizeRequest, TokenizeRequest
2020

2121
class VaultController(BaseVaultController):
@@ -94,7 +94,7 @@ def __get_file_for_file_upload(self, request: FileUploadRequest) -> Optional[Fil
9494
def __get_headers(self):
9595
return {SKY_META_DATA_HEADER: json.dumps(get_metrics())}
9696

97-
def insert(self, request: InsertRequest):
97+
def insert(self, request: InsertRequest) -> InsertResponse:
9898
log_info(SkyflowMessages.Info.VALIDATE_INSERT_REQUEST.value, self.__vault_client.get_logger())
9999
validate_insert_request(self.__vault_client.get_logger(), request)
100100
for item in request.values:

0 commit comments

Comments
 (0)