qwak-core 0.6.7__py3-none-any.whl → 0.7.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.
- frogml_storage/__init__.py +1 -0
- frogml_storage/artifactory/__init__.py +1 -0
- frogml_storage/artifactory/_artifactory_api.py +315 -0
- frogml_storage/authentication/login/__init__.py +1 -0
- frogml_storage/authentication/login/_login_cli.py +239 -0
- frogml_storage/authentication/login/_login_command.py +74 -0
- frogml_storage/authentication/models/__init__.py +3 -0
- frogml_storage/authentication/models/_auth.py +24 -0
- frogml_storage/authentication/models/_auth_config.py +70 -0
- frogml_storage/authentication/models/_login.py +22 -0
- frogml_storage/authentication/utils/__init__.py +18 -0
- frogml_storage/authentication/utils/_authentication_utils.py +284 -0
- frogml_storage/authentication/utils/_login_checks_utils.py +114 -0
- frogml_storage/base_storage.py +140 -0
- frogml_storage/constants.py +56 -0
- frogml_storage/exceptions/checksum_verification_error.py +3 -0
- frogml_storage/exceptions/validation_error.py +4 -0
- frogml_storage/frog_ml.py +668 -0
- frogml_storage/http/__init__.py +1 -0
- frogml_storage/http/http_client.py +83 -0
- frogml_storage/logging/__init__.py +1 -0
- frogml_storage/logging/_log_config.py +45 -0
- frogml_storage/logging/log_utils.py +21 -0
- frogml_storage/models/__init__.py +1 -0
- frogml_storage/models/_download_context.py +54 -0
- frogml_storage/models/dataset_manifest.py +13 -0
- frogml_storage/models/entity_manifest.py +93 -0
- frogml_storage/models/frogml_dataset_version.py +21 -0
- frogml_storage/models/frogml_entity_type_info.py +50 -0
- frogml_storage/models/frogml_entity_version.py +34 -0
- frogml_storage/models/frogml_model_version.py +21 -0
- frogml_storage/models/model_manifest.py +60 -0
- frogml_storage/models/serialization_metadata.py +15 -0
- frogml_storage/utils/__init__.py +12 -0
- frogml_storage/utils/_environment.py +21 -0
- frogml_storage/utils/_input_checks_utility.py +104 -0
- frogml_storage/utils/_storage_utils.py +15 -0
- frogml_storage/utils/_url_utils.py +27 -0
- qwak/__init__.py +1 -1
- qwak/clients/model_management/client.py +5 -0
- qwak/clients/project/client.py +7 -0
- qwak/inner/const.py +8 -0
- qwak/inner/di_configuration/account.py +67 -6
- qwak/inner/di_configuration/dependency_wiring.py +0 -2
- qwak/inner/tool/auth.py +83 -0
- qwak/inner/tool/grpc/grpc_auth.py +35 -0
- qwak/inner/tool/grpc/grpc_tools.py +37 -14
- qwak/inner/tool/grpc/grpc_try_wrapping.py +1 -3
- qwak/qwak_client/client.py +6 -0
- {qwak_core-0.6.7.dist-info → qwak_core-0.7.0.dist-info}/METADATA +1 -1
- {qwak_core-0.6.7.dist-info → qwak_core-0.7.0.dist-info}/RECORD +54 -30
- qwak_services_mock/mocks/qwak_mocks.py +2 -8
- qwak_services_mock/services_mock.py +0 -24
- qwak/clients/vector_store/__init__.py +0 -2
- qwak/clients/vector_store/management_client.py +0 -124
- qwak/clients/vector_store/serving_client.py +0 -156
- qwak/vector_store/__init__.py +0 -4
- qwak/vector_store/client.py +0 -150
- qwak/vector_store/collection.py +0 -426
- qwak/vector_store/filters.py +0 -354
- qwak/vector_store/inference_client.py +0 -103
- qwak/vector_store/rest_helpers.py +0 -72
- qwak/vector_store/utils/__init__.py +0 -0
- qwak/vector_store/utils/filter_utils.py +0 -21
- qwak/vector_store/utils/upsert_utils.py +0 -217
- qwak_services_mock/mocks/vector_serving_api.py +0 -154
- qwak_services_mock/mocks/vectors_management_api.py +0 -96
- {qwak_core-0.6.7.dist-info → qwak_core-0.7.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
from urllib.parse import urlparse
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def join_url(base_uri: str, *parts: str) -> str:
|
|
6
|
+
base_uri = base_uri.rstrip("/")
|
|
7
|
+
|
|
8
|
+
cleaned_parts: List[str] = [
|
|
9
|
+
part.strip("/") for part in parts if part is not None and part.strip("/")
|
|
10
|
+
]
|
|
11
|
+
uri_parts: List[str] = [base_uri, *cleaned_parts]
|
|
12
|
+
|
|
13
|
+
return "/".join(uri_parts)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def assemble_artifact_url(uri: Optional[str]) -> str:
|
|
17
|
+
if uri is None:
|
|
18
|
+
raise Exception("Artifactory URI is required")
|
|
19
|
+
|
|
20
|
+
parsed_url = urlparse(uri)
|
|
21
|
+
if parsed_url.scheme not in ["http", "https"]:
|
|
22
|
+
raise Exception(
|
|
23
|
+
f"Not a valid Artifactory URI: {uri}. "
|
|
24
|
+
f"Artifactory URI example: `https://frogger.jfrog.io/artifactory/ml-local`"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return f"{parsed_url.scheme}://{parsed_url.netloc}/artifactory"
|
qwak/__init__.py
CHANGED
|
@@ -12,6 +12,7 @@ from _qwak_proto.qwak.models.models_pb2 import (
|
|
|
12
12
|
ModelSpec,
|
|
13
13
|
)
|
|
14
14
|
from _qwak_proto.qwak.models.models_pb2_grpc import ModelsManagementServiceStub
|
|
15
|
+
from _qwak_proto.qwak.projects.jfrog_project_spec_pb2 import ModelRepositoryJFrogSpec
|
|
15
16
|
from dependency_injector.wiring import Provide
|
|
16
17
|
from qwak.exceptions import QwakException
|
|
17
18
|
from qwak.inner.di_configuration import QwakContainer
|
|
@@ -58,6 +59,7 @@ class ModelsManagementClient:
|
|
|
58
59
|
project_id,
|
|
59
60
|
model_name,
|
|
60
61
|
model_description,
|
|
62
|
+
jfrog_project_key: Optional[str] = None,
|
|
61
63
|
):
|
|
62
64
|
try:
|
|
63
65
|
return self._models_management_service.CreateModel(
|
|
@@ -68,6 +70,9 @@ class ModelsManagementClient:
|
|
|
68
70
|
project_id=project_id,
|
|
69
71
|
model_description=model_description,
|
|
70
72
|
),
|
|
73
|
+
jfrog_project_spec=ModelRepositoryJFrogSpec(
|
|
74
|
+
jfrog_project_key=jfrog_project_key,
|
|
75
|
+
),
|
|
71
76
|
)
|
|
72
77
|
)
|
|
73
78
|
|
qwak/clients/project/client.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
1
3
|
import grpc
|
|
2
4
|
from _qwak_proto.qwak.projects.projects_pb2 import (
|
|
3
5
|
CreateProjectRequest,
|
|
@@ -6,6 +8,7 @@ from _qwak_proto.qwak.projects.projects_pb2 import (
|
|
|
6
8
|
ListProjectsRequest,
|
|
7
9
|
)
|
|
8
10
|
from _qwak_proto.qwak.projects.projects_pb2_grpc import ProjectsManagementServiceStub
|
|
11
|
+
from _qwak_proto.qwak.projects.jfrog_project_spec_pb2 import ModelRepositoryJFrogSpec
|
|
9
12
|
from dependency_injector.wiring import Provide, inject
|
|
10
13
|
from qwak.exceptions import QwakException
|
|
11
14
|
from qwak.inner.di_configuration import QwakContainer
|
|
@@ -31,12 +34,16 @@ class ProjectsManagementClient:
|
|
|
31
34
|
self,
|
|
32
35
|
project_name,
|
|
33
36
|
project_description,
|
|
37
|
+
jfrog_project_key: Optional[str] = None,
|
|
34
38
|
):
|
|
35
39
|
try:
|
|
36
40
|
return self._projects_management_service.CreateProject(
|
|
37
41
|
CreateProjectRequest(
|
|
38
42
|
project_name=project_name,
|
|
39
43
|
project_description=project_description,
|
|
44
|
+
jfrog_spec=ModelRepositoryJFrogSpec(
|
|
45
|
+
jfrog_project_key=jfrog_project_key
|
|
46
|
+
),
|
|
40
47
|
)
|
|
41
48
|
)
|
|
42
49
|
|
qwak/inner/const.py
CHANGED
|
@@ -35,6 +35,14 @@ class QwakConstants:
|
|
|
35
35
|
|
|
36
36
|
TOKEN_AUDIENCE: str = "https://auth-token.qwak.ai/" # nosec B105
|
|
37
37
|
|
|
38
|
+
QWAK_AUTHENTICATION_URL = "https://grpc.qwak.ai/api/v1/authentication/qwak-api-key"
|
|
39
|
+
|
|
40
|
+
QWAK_AUTHENTICATED_USER_ENDPOINT: str = (
|
|
41
|
+
"https://grpc.qwak.ai/api/v0/runtime/get-authenticated-user-context"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
JFROG_TENANT_HEADER_KEY = "X-JFrog-Tenant-Id"
|
|
45
|
+
|
|
38
46
|
QWAK_APP_URL: str = "https://app.qwak.ai"
|
|
39
47
|
|
|
40
48
|
CONTROL_PLANE_GRPC_ADDRESS_ENVAR_NAME: str = "CONTROL_PLANE_GRPC_ADDRESS"
|
|
@@ -2,12 +2,13 @@ import configparser
|
|
|
2
2
|
import errno
|
|
3
3
|
import os
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Optional, Type
|
|
5
|
+
from typing import Optional, Type, Union
|
|
6
6
|
|
|
7
7
|
from qwak.exceptions import QwakLoginException
|
|
8
8
|
from qwak.inner.const import QwakConstants
|
|
9
9
|
from qwak.inner.di_configuration.session import Session
|
|
10
|
-
from qwak.inner.tool.auth import Auth0ClientBase
|
|
10
|
+
from qwak.inner.tool.auth import Auth0ClientBase, FrogMLAuthClient
|
|
11
|
+
from frogml_storage.authentication.login import frogml_login
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@dataclass
|
|
@@ -22,6 +23,18 @@ class UserAccount:
|
|
|
22
23
|
# Assigned username
|
|
23
24
|
username: Optional[str] = None
|
|
24
25
|
|
|
26
|
+
# Assigned password
|
|
27
|
+
password: Optional[str] = None
|
|
28
|
+
|
|
29
|
+
# Assigned URL
|
|
30
|
+
url: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
# Anonymous login
|
|
33
|
+
anonymous: bool = False
|
|
34
|
+
|
|
35
|
+
# Interactive login
|
|
36
|
+
is_interactive: bool = False
|
|
37
|
+
|
|
25
38
|
|
|
26
39
|
class UserAccountConfiguration:
|
|
27
40
|
USER_FIELD = "user"
|
|
@@ -30,9 +43,11 @@ class UserAccountConfiguration:
|
|
|
30
43
|
|
|
31
44
|
def __init__(
|
|
32
45
|
self,
|
|
33
|
-
config_file
|
|
34
|
-
auth_file
|
|
35
|
-
auth_client:
|
|
46
|
+
config_file=QwakConstants.QWAK_CONFIG_FILE,
|
|
47
|
+
auth_file=QwakConstants.QWAK_AUTHORIZATION_FILE,
|
|
48
|
+
auth_client: Optional[
|
|
49
|
+
Union[Type[Auth0ClientBase], Type[FrogMLAuthClient]]
|
|
50
|
+
] = None,
|
|
36
51
|
):
|
|
37
52
|
self._config_file = config_file
|
|
38
53
|
self._auth_file = auth_file
|
|
@@ -40,12 +55,51 @@ class UserAccountConfiguration:
|
|
|
40
55
|
self._auth = configparser.ConfigParser()
|
|
41
56
|
self._environment = Session().get_environment()
|
|
42
57
|
self._auth_client = auth_client
|
|
58
|
+
self._force_qwak_auth = os.getenv("FORCE_QWAK_AUTH", "False") == "True"
|
|
59
|
+
|
|
60
|
+
if not self._auth_client:
|
|
61
|
+
# Determine auth client based on FrogML configuration
|
|
62
|
+
try:
|
|
63
|
+
from frogml_storage.authentication.utils import (
|
|
64
|
+
get_frogml_configuration,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
get_frogml_configuration() or os.getenv("JF_URL")
|
|
69
|
+
) and not self._force_qwak_auth:
|
|
70
|
+
self._auth_client = FrogMLAuthClient
|
|
71
|
+
else:
|
|
72
|
+
self._auth_client = Auth0ClientBase
|
|
73
|
+
except Exception:
|
|
74
|
+
self._auth_client = Auth0ClientBase
|
|
43
75
|
|
|
44
76
|
def configure_user(self, user_account: UserAccount):
|
|
45
77
|
"""
|
|
46
78
|
Configure user authentication based on the authentication client type
|
|
47
79
|
"""
|
|
48
|
-
self.
|
|
80
|
+
if issubclass(self._auth_client, Auth0ClientBase):
|
|
81
|
+
# Existing Qwak authentication flow
|
|
82
|
+
self.__qwak_login(user_account)
|
|
83
|
+
|
|
84
|
+
elif issubclass(self._auth_client, FrogMLAuthClient):
|
|
85
|
+
# Use FrogML's login flow
|
|
86
|
+
success = frogml_login(
|
|
87
|
+
url=user_account.url,
|
|
88
|
+
username=user_account.username,
|
|
89
|
+
password=user_account.password,
|
|
90
|
+
token=user_account.api_key,
|
|
91
|
+
anonymous=user_account.anonymous,
|
|
92
|
+
is_interactive=user_account.is_interactive,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if not success:
|
|
96
|
+
raise QwakLoginException("Failed to authenticate with JFrog")
|
|
97
|
+
# Validate access token
|
|
98
|
+
token = self._auth_client().get_token()
|
|
99
|
+
if not token or len(token) <= 64:
|
|
100
|
+
raise QwakLoginException(
|
|
101
|
+
"Authentication with JFrog failed: Only Access Tokens are supported. Please ensure you are using a valid Access Token."
|
|
102
|
+
)
|
|
49
103
|
|
|
50
104
|
def __qwak_login(self, user_account: UserAccount):
|
|
51
105
|
self._auth.read(self._auth_file)
|
|
@@ -86,6 +140,8 @@ class UserAccountConfiguration:
|
|
|
86
140
|
:return:
|
|
87
141
|
"""
|
|
88
142
|
try:
|
|
143
|
+
if issubclass(self._auth_client, FrogMLAuthClient):
|
|
144
|
+
return UserAccount()
|
|
89
145
|
username = os.environ.get("QWAK_USERNAME")
|
|
90
146
|
api_key = os.environ.get("QWAK_API_KEY")
|
|
91
147
|
if not api_key and (
|
|
@@ -124,6 +180,8 @@ class UserAccountConfiguration:
|
|
|
124
180
|
:return:
|
|
125
181
|
"""
|
|
126
182
|
try:
|
|
183
|
+
if issubclass(self._auth_client, FrogMLAuthClient):
|
|
184
|
+
return ""
|
|
127
185
|
api_key = os.environ.get("QWAK_API_KEY")
|
|
128
186
|
if api_key:
|
|
129
187
|
Session().set_environment(api_key)
|
|
@@ -156,4 +214,7 @@ class UserAccountConfiguration:
|
|
|
156
214
|
auth_client_instance = self._auth_client()
|
|
157
215
|
base_url = auth_client_instance.get_base_url()
|
|
158
216
|
|
|
217
|
+
if issubclass(self._auth_client, FrogMLAuthClient):
|
|
218
|
+
return f"{base_url}/ui/ml"
|
|
219
|
+
|
|
159
220
|
return base_url
|
|
@@ -50,7 +50,6 @@ def wire_dependencies():
|
|
|
50
50
|
prompt_manager,
|
|
51
51
|
system_secret,
|
|
52
52
|
user_application_instance,
|
|
53
|
-
vector_store,
|
|
54
53
|
workspace_manager,
|
|
55
54
|
)
|
|
56
55
|
|
|
@@ -76,7 +75,6 @@ def wire_dependencies():
|
|
|
76
75
|
user_application_instance,
|
|
77
76
|
alerts_registry,
|
|
78
77
|
workspace_manager,
|
|
79
|
-
vector_store,
|
|
80
78
|
integration_management,
|
|
81
79
|
system_secret,
|
|
82
80
|
prompt_manager,
|
qwak/inner/tool/auth.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import warnings
|
|
2
2
|
from filelock import FileLock
|
|
3
|
+
from typing_extensions import Self
|
|
3
4
|
|
|
4
5
|
from qwak.inner.di_configuration.session import Session
|
|
5
6
|
from abc import ABC, abstractmethod
|
|
6
7
|
from typing import Optional
|
|
8
|
+
from frogml_storage.authentication.utils import get_credentials
|
|
9
|
+
from frogml_storage.authentication.models import AuthConfig
|
|
7
10
|
|
|
8
11
|
warnings.filterwarnings(action="ignore", module=".*jose.*")
|
|
9
12
|
|
|
@@ -137,3 +140,83 @@ class Auth0ClientBase(BaseAuthClient):
|
|
|
137
140
|
raise e
|
|
138
141
|
except Exception:
|
|
139
142
|
raise QwakLoginException()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class FrogMLAuthClient(BaseAuthClient):
|
|
146
|
+
__MIN_TOKEN_LENGTH: int = 64
|
|
147
|
+
__FAIL_TO_AUTH_ERROR_MESSAGE = (
|
|
148
|
+
"Failed to authenticate with JFrog. Please check your artifactory configuration"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def __init__(self, auth_config: Optional[AuthConfig] = None):
|
|
152
|
+
self.auth_config = auth_config
|
|
153
|
+
self._token = None
|
|
154
|
+
self._tenant_id = None
|
|
155
|
+
|
|
156
|
+
def get_token(self) -> Optional[str]:
|
|
157
|
+
if not self._token:
|
|
158
|
+
self.login()
|
|
159
|
+
return self._token
|
|
160
|
+
|
|
161
|
+
def get_tenant_id(self) -> Optional[str]:
|
|
162
|
+
if not self._tenant_id:
|
|
163
|
+
self.login()
|
|
164
|
+
return self._tenant_id
|
|
165
|
+
|
|
166
|
+
def get_base_url(self) -> str:
|
|
167
|
+
artifactory_url, _ = get_credentials(self.auth_config)
|
|
168
|
+
return self.__format_artifactory_url(artifactory_url)
|
|
169
|
+
|
|
170
|
+
def login(self) -> None:
|
|
171
|
+
artifactory_url, auth = get_credentials(self.auth_config)
|
|
172
|
+
# For now, we only support Bearer token authentication
|
|
173
|
+
if not hasattr(auth, "token"):
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
# noinspection PyUnresolvedReferences
|
|
177
|
+
self._token = auth.token
|
|
178
|
+
self.__validate_token()
|
|
179
|
+
|
|
180
|
+
base_url = self.__format_artifactory_url(artifactory_url)
|
|
181
|
+
try:
|
|
182
|
+
response = requests.get(
|
|
183
|
+
f"{base_url}/ui/api/v1/system/auth/screen/footer",
|
|
184
|
+
headers={"Authorization": f"Bearer {self._token}"},
|
|
185
|
+
timeout=60,
|
|
186
|
+
)
|
|
187
|
+
response.raise_for_status() # Raises an HTTPError for bad responses
|
|
188
|
+
response_data = response.json()
|
|
189
|
+
if "serverId" not in response_data:
|
|
190
|
+
response = requests.get(
|
|
191
|
+
f"{base_url}/jfconnect/api/v1/system/jpd_id",
|
|
192
|
+
headers={"Authorization": f"Bearer {self._token}"},
|
|
193
|
+
timeout=60,
|
|
194
|
+
)
|
|
195
|
+
if response.status_code == 200:
|
|
196
|
+
self._tenant_id = response.text
|
|
197
|
+
elif response.status_code == 401:
|
|
198
|
+
raise QwakLoginException(
|
|
199
|
+
"Failed to authenticate with JFrog. Please check your credentials"
|
|
200
|
+
)
|
|
201
|
+
else:
|
|
202
|
+
raise QwakLoginException(self.__FAIL_TO_AUTH_ERROR_MESSAGE)
|
|
203
|
+
else:
|
|
204
|
+
self._tenant_id = response_data["serverId"]
|
|
205
|
+
except requests.exceptions.RequestException:
|
|
206
|
+
raise QwakLoginException(self.__FAIL_TO_AUTH_ERROR_MESSAGE)
|
|
207
|
+
except ValueError: # This catches JSON decode errors
|
|
208
|
+
raise QwakLoginException(self.__FAIL_TO_AUTH_ERROR_MESSAGE)
|
|
209
|
+
|
|
210
|
+
def __validate_token(self: Self):
|
|
211
|
+
if self._token is None or len(self._token) <= self.__MIN_TOKEN_LENGTH:
|
|
212
|
+
raise QwakLoginException(
|
|
213
|
+
"Authentication with JFrog failed: Only JWT Access Tokens are supported. "
|
|
214
|
+
"Please ensure you are using a valid JWT Access Token."
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def __format_artifactory_url(artifactory_url: str) -> str:
|
|
219
|
+
# Remove '/artifactory' from the URL
|
|
220
|
+
base_url: str = artifactory_url.replace("/artifactory", "")
|
|
221
|
+
# Remove trailing slash if exists
|
|
222
|
+
return base_url.rstrip("/")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Callable, Tuple
|
|
2
2
|
|
|
3
3
|
import grpc
|
|
4
|
+
from qwak.inner.const import QwakConstants
|
|
4
5
|
|
|
5
6
|
_SIGNATURE_HEADER_KEY = "authorization"
|
|
6
7
|
|
|
@@ -34,3 +35,37 @@ class Auth0Client(grpc.AuthMetadataPlugin):
|
|
|
34
35
|
|
|
35
36
|
self._auth_client = Auth0ClientBase()
|
|
36
37
|
return self._auth_client.get_token()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class FrogMLGrpcClient(grpc.AuthMetadataPlugin):
|
|
41
|
+
def __init__(self):
|
|
42
|
+
self._auth_client = None
|
|
43
|
+
|
|
44
|
+
def __call__(
|
|
45
|
+
self,
|
|
46
|
+
context: grpc.AuthMetadataContext,
|
|
47
|
+
callback: Callable[[Tuple[Tuple[str, str]], None], None],
|
|
48
|
+
):
|
|
49
|
+
"""Implements authentication by passing metadata to a callback.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
context: An AuthMetadataContext providing information on the RPC that
|
|
53
|
+
the plugin is being called to authenticate.
|
|
54
|
+
callback: A callback that accepts a tuple of metadata key/value pairs and a None
|
|
55
|
+
parameter.
|
|
56
|
+
"""
|
|
57
|
+
token = self.get_token()
|
|
58
|
+
jfrog_tenant_id = self._auth_client.get_tenant_id()
|
|
59
|
+
metadata = (
|
|
60
|
+
(_SIGNATURE_HEADER_KEY, f"Bearer {token}"),
|
|
61
|
+
(QwakConstants.JFROG_TENANT_HEADER_KEY.lower(), jfrog_tenant_id),
|
|
62
|
+
)
|
|
63
|
+
callback(metadata, None)
|
|
64
|
+
|
|
65
|
+
def get_token(self) -> str:
|
|
66
|
+
"""Get the authentication token."""
|
|
67
|
+
if not self._auth_client:
|
|
68
|
+
from qwak.inner.tool.auth import FrogMLAuthClient
|
|
69
|
+
|
|
70
|
+
self._auth_client = FrogMLAuthClient()
|
|
71
|
+
return self._auth_client.get_token()
|
|
@@ -7,9 +7,12 @@ from typing import Callable, Optional, Tuple
|
|
|
7
7
|
from urllib.parse import urlparse, ParseResult
|
|
8
8
|
|
|
9
9
|
import grpc
|
|
10
|
+
from grpc import AuthMetadataPlugin, Channel
|
|
10
11
|
|
|
11
12
|
from qwak.exceptions import QwakException, QwakGrpcAddressException
|
|
12
|
-
from .
|
|
13
|
+
from qwak.inner.di_configuration.account import UserAccountConfiguration
|
|
14
|
+
from qwak.inner.tool.auth import Auth0ClientBase
|
|
15
|
+
from .grpc_auth import Auth0Client, FrogMLGrpcClient
|
|
13
16
|
|
|
14
17
|
logger = logging.getLogger()
|
|
15
18
|
HOSTNAME_REGEX: str = r"^(?!-)(?:[A-Za-z0-9-]{1,63}\.)*[A-Za-z0-9-]{1,63}(?<!-)$"
|
|
@@ -19,7 +22,7 @@ def create_grpc_channel(
|
|
|
19
22
|
url: str,
|
|
20
23
|
enable_ssl: bool = True,
|
|
21
24
|
enable_auth: bool = True,
|
|
22
|
-
auth_metadata_plugin: grpc.AuthMetadataPlugin = None,
|
|
25
|
+
auth_metadata_plugin: Optional[grpc.AuthMetadataPlugin] = None,
|
|
23
26
|
timeout: int = 100,
|
|
24
27
|
options=None,
|
|
25
28
|
backoff_options=None,
|
|
@@ -49,18 +52,9 @@ def create_grpc_channel(
|
|
|
49
52
|
if not url:
|
|
50
53
|
raise QwakException("Unable to create gRPC channel. URL has not been defined.")
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if auth_metadata_plugin is None:
|
|
56
|
-
auth_metadata_plugin = Auth0Client()
|
|
57
|
-
credentials = grpc.composite_channel_credentials(
|
|
58
|
-
credentials, grpc.metadata_call_credentials(auth_metadata_plugin)
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
channel = grpc.secure_channel(url, credentials=credentials, options=options)
|
|
62
|
-
else:
|
|
63
|
-
channel = grpc.insecure_channel(url, options=options)
|
|
55
|
+
auth_metadata_plugin, channel = __get_channel_definition(
|
|
56
|
+
auth_metadata_plugin, enable_auth, enable_ssl, options, url
|
|
57
|
+
)
|
|
64
58
|
try:
|
|
65
59
|
interceptors = (
|
|
66
60
|
RetryOnRpcErrorClientInterceptor(
|
|
@@ -100,6 +94,35 @@ def create_grpc_channel(
|
|
|
100
94
|
) from e
|
|
101
95
|
|
|
102
96
|
|
|
97
|
+
def __get_channel_definition(
|
|
98
|
+
auth_metadata_plugin: Optional[AuthMetadataPlugin],
|
|
99
|
+
enable_auth: bool,
|
|
100
|
+
enable_ssl: bool,
|
|
101
|
+
options,
|
|
102
|
+
url: str,
|
|
103
|
+
) -> tuple[Optional[AuthMetadataPlugin], Channel]:
|
|
104
|
+
if enable_ssl or url.endswith(":443"):
|
|
105
|
+
credentials = grpc.ssl_channel_credentials()
|
|
106
|
+
if enable_auth:
|
|
107
|
+
if auth_metadata_plugin is None:
|
|
108
|
+
user_config = UserAccountConfiguration()
|
|
109
|
+
|
|
110
|
+
if issubclass(user_config._auth_client, Auth0ClientBase):
|
|
111
|
+
auth_metadata_plugin = Auth0Client()
|
|
112
|
+
else:
|
|
113
|
+
auth_metadata_plugin = FrogMLGrpcClient()
|
|
114
|
+
|
|
115
|
+
credentials = grpc.composite_channel_credentials(
|
|
116
|
+
credentials, grpc.metadata_call_credentials(auth_metadata_plugin)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
channel = grpc.secure_channel(url, credentials=credentials, options=options)
|
|
120
|
+
else:
|
|
121
|
+
channel = grpc.insecure_channel(url, options=options)
|
|
122
|
+
|
|
123
|
+
return auth_metadata_plugin, channel
|
|
124
|
+
|
|
125
|
+
|
|
103
126
|
def create_grpc_channel_or_none(
|
|
104
127
|
url: str,
|
|
105
128
|
enable_ssl: bool = True,
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
import functools
|
|
3
2
|
import inspect
|
|
4
3
|
from inspect import BoundArguments, Signature
|
|
5
4
|
from typing import Any, Callable, Optional
|
|
6
5
|
|
|
7
6
|
import grpc
|
|
7
|
+
from frogml_storage.logging import logger
|
|
8
8
|
from qwak.exceptions import QwakException
|
|
9
9
|
|
|
10
|
-
logger = logging.getLogger()
|
|
11
|
-
|
|
12
10
|
|
|
13
11
|
def grpc_try_catch_wrapper(
|
|
14
12
|
exception_message: str,
|
qwak/qwak_client/client.py
CHANGED
|
@@ -356,6 +356,7 @@ class QwakClient:
|
|
|
356
356
|
self,
|
|
357
357
|
project_name: str,
|
|
358
358
|
project_description: str,
|
|
359
|
+
jfrog_project_key: Optional[str] = None,
|
|
359
360
|
) -> str:
|
|
360
361
|
"""
|
|
361
362
|
Create project
|
|
@@ -363,6 +364,7 @@ class QwakClient:
|
|
|
363
364
|
Args:
|
|
364
365
|
project_name (str): The requested name
|
|
365
366
|
project_description (str): The requested description
|
|
367
|
+
jfrog_project_key (Optional[str]): The requested jfrog project key
|
|
366
368
|
|
|
367
369
|
Returns:
|
|
368
370
|
str: The project ID of the newly created project
|
|
@@ -371,6 +373,7 @@ class QwakClient:
|
|
|
371
373
|
project = self._get_project_management().create_project(
|
|
372
374
|
project_name=project_name,
|
|
373
375
|
project_description=project_description,
|
|
376
|
+
jfrog_project_key=jfrog_project_key,
|
|
374
377
|
)
|
|
375
378
|
|
|
376
379
|
return project.project.project_id
|
|
@@ -416,6 +419,7 @@ class QwakClient:
|
|
|
416
419
|
project_id: str,
|
|
417
420
|
model_name: str,
|
|
418
421
|
model_description: str,
|
|
422
|
+
jfrog_project_key: Optional[str] = None,
|
|
419
423
|
) -> str:
|
|
420
424
|
"""
|
|
421
425
|
Create model
|
|
@@ -424,6 +428,7 @@ class QwakClient:
|
|
|
424
428
|
project_id (str): The project ID to associate the model
|
|
425
429
|
model_name (str): The requested name
|
|
426
430
|
model_description (str): The requested description
|
|
431
|
+
jfrog_project_key (Optional[str]): The jfrog project key
|
|
427
432
|
|
|
428
433
|
Returns:
|
|
429
434
|
str: The model ID of the newly created project
|
|
@@ -433,6 +438,7 @@ class QwakClient:
|
|
|
433
438
|
project_id=project_id,
|
|
434
439
|
model_name=model_name,
|
|
435
440
|
model_description=model_description,
|
|
441
|
+
jfrog_project_key=jfrog_project_key,
|
|
436
442
|
)
|
|
437
443
|
|
|
438
444
|
return model.model_id
|