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.
Files changed (68) hide show
  1. frogml_storage/__init__.py +1 -0
  2. frogml_storage/artifactory/__init__.py +1 -0
  3. frogml_storage/artifactory/_artifactory_api.py +315 -0
  4. frogml_storage/authentication/login/__init__.py +1 -0
  5. frogml_storage/authentication/login/_login_cli.py +239 -0
  6. frogml_storage/authentication/login/_login_command.py +74 -0
  7. frogml_storage/authentication/models/__init__.py +3 -0
  8. frogml_storage/authentication/models/_auth.py +24 -0
  9. frogml_storage/authentication/models/_auth_config.py +70 -0
  10. frogml_storage/authentication/models/_login.py +22 -0
  11. frogml_storage/authentication/utils/__init__.py +18 -0
  12. frogml_storage/authentication/utils/_authentication_utils.py +284 -0
  13. frogml_storage/authentication/utils/_login_checks_utils.py +114 -0
  14. frogml_storage/base_storage.py +140 -0
  15. frogml_storage/constants.py +56 -0
  16. frogml_storage/exceptions/checksum_verification_error.py +3 -0
  17. frogml_storage/exceptions/validation_error.py +4 -0
  18. frogml_storage/frog_ml.py +668 -0
  19. frogml_storage/http/__init__.py +1 -0
  20. frogml_storage/http/http_client.py +83 -0
  21. frogml_storage/logging/__init__.py +1 -0
  22. frogml_storage/logging/_log_config.py +45 -0
  23. frogml_storage/logging/log_utils.py +21 -0
  24. frogml_storage/models/__init__.py +1 -0
  25. frogml_storage/models/_download_context.py +54 -0
  26. frogml_storage/models/dataset_manifest.py +13 -0
  27. frogml_storage/models/entity_manifest.py +93 -0
  28. frogml_storage/models/frogml_dataset_version.py +21 -0
  29. frogml_storage/models/frogml_entity_type_info.py +50 -0
  30. frogml_storage/models/frogml_entity_version.py +34 -0
  31. frogml_storage/models/frogml_model_version.py +21 -0
  32. frogml_storage/models/model_manifest.py +60 -0
  33. frogml_storage/models/serialization_metadata.py +15 -0
  34. frogml_storage/utils/__init__.py +12 -0
  35. frogml_storage/utils/_environment.py +21 -0
  36. frogml_storage/utils/_input_checks_utility.py +104 -0
  37. frogml_storage/utils/_storage_utils.py +15 -0
  38. frogml_storage/utils/_url_utils.py +27 -0
  39. qwak/__init__.py +1 -1
  40. qwak/clients/model_management/client.py +5 -0
  41. qwak/clients/project/client.py +7 -0
  42. qwak/inner/const.py +8 -0
  43. qwak/inner/di_configuration/account.py +67 -6
  44. qwak/inner/di_configuration/dependency_wiring.py +0 -2
  45. qwak/inner/tool/auth.py +83 -0
  46. qwak/inner/tool/grpc/grpc_auth.py +35 -0
  47. qwak/inner/tool/grpc/grpc_tools.py +37 -14
  48. qwak/inner/tool/grpc/grpc_try_wrapping.py +1 -3
  49. qwak/qwak_client/client.py +6 -0
  50. {qwak_core-0.6.7.dist-info → qwak_core-0.7.0.dist-info}/METADATA +1 -1
  51. {qwak_core-0.6.7.dist-info → qwak_core-0.7.0.dist-info}/RECORD +54 -30
  52. qwak_services_mock/mocks/qwak_mocks.py +2 -8
  53. qwak_services_mock/services_mock.py +0 -24
  54. qwak/clients/vector_store/__init__.py +0 -2
  55. qwak/clients/vector_store/management_client.py +0 -124
  56. qwak/clients/vector_store/serving_client.py +0 -156
  57. qwak/vector_store/__init__.py +0 -4
  58. qwak/vector_store/client.py +0 -150
  59. qwak/vector_store/collection.py +0 -426
  60. qwak/vector_store/filters.py +0 -354
  61. qwak/vector_store/inference_client.py +0 -103
  62. qwak/vector_store/rest_helpers.py +0 -72
  63. qwak/vector_store/utils/__init__.py +0 -0
  64. qwak/vector_store/utils/filter_utils.py +0 -21
  65. qwak/vector_store/utils/upsert_utils.py +0 -217
  66. qwak_services_mock/mocks/vector_serving_api.py +0 -154
  67. qwak_services_mock/mocks/vectors_management_api.py +0 -96
  68. {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
@@ -1,7 +1,7 @@
1
1
  """Top-level package for qwak-core."""
2
2
 
3
3
  __author__ = "Qwak.ai"
4
- __version__ = "0.6.7"
4
+ __version__ = "0.7.0"
5
5
 
6
6
  from qwak.inner.di_configuration import wire_dependencies
7
7
  from qwak.model.experiment_tracking import log_metric, log_param
@@ -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
 
@@ -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: str = QwakConstants.QWAK_CONFIG_FILE,
34
- auth_file: str = QwakConstants.QWAK_AUTHORIZATION_FILE,
35
- auth_client: Type[Auth0ClientBase] = Auth0ClientBase,
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.__qwak_login(user_account)
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 .grpc_auth import Auth0Client
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
- if enable_ssl or url.endswith(":443"):
53
- credentials = grpc.ssl_channel_credentials()
54
- if enable_auth:
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,
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: qwak-core
3
- Version: 0.6.7
3
+ Version: 0.7.0
4
4
  Summary: Qwak Core contains the necessary objects and communication tools for using the Qwak Platform
5
5
  License: Apache-2.0
6
6
  Keywords: mlops,ml,deployment,serving,model