qwak-core 0.4.362__py3-none-any.whl → 0.5.4__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.

Potentially problematic release.


This version of qwak-core might be problematic. Click here for more details.

Files changed (88) hide show
  1. _qwak_proto/qwak/administration/account/v1/account_pb2.py +20 -18
  2. _qwak_proto/qwak/administration/account/v1/account_pb2.pyi +21 -2
  3. _qwak_proto/qwak/administration/runtime_configuration/v0/external/databricks/auth_pb2.py +6 -4
  4. _qwak_proto/qwak/administration/runtime_configuration/v0/external/databricks/auth_pb2.pyi +27 -4
  5. _qwak_proto/qwak/administration/runtime_configuration/v0/hosting/azure/auth_pb2.py +5 -3
  6. _qwak_proto/qwak/administration/runtime_configuration/v0/hosting/azure/auth_pb2.pyi +21 -1
  7. _qwak_proto/qwak/admiral/secret/v0/secret_pb2.py +16 -14
  8. _qwak_proto/qwak/admiral/secret/v0/secret_pb2.pyi +21 -2
  9. _qwak_proto/qwak/batch_job/v1/batch_job_service_pb2.py +100 -100
  10. _qwak_proto/qwak/batch_job/v1/batch_job_service_pb2.pyi +5 -1
  11. _qwak_proto/qwak/builds/build_pb2.py +42 -41
  12. _qwak_proto/qwak/builds/build_pb2.pyi +32 -1
  13. _qwak_proto/qwak/builds/build_values_pb2.py +82 -0
  14. _qwak_proto/qwak/builds/build_values_pb2.pyi +553 -0
  15. _qwak_proto/qwak/builds/build_values_pb2_grpc.py +4 -0
  16. _qwak_proto/qwak/execution/v1/streaming_aggregation_pb2.py +18 -11
  17. _qwak_proto/qwak/execution/v1/streaming_aggregation_pb2.pyi +71 -1
  18. _qwak_proto/qwak/feature_store/features/feature_set_pb2.py +4 -4
  19. _qwak_proto/qwak/feature_store/features/feature_set_pb2.pyi +4 -0
  20. _qwak_proto/qwak/feature_store/features/feature_set_types_pb2.py +60 -58
  21. _qwak_proto/qwak/feature_store/features/feature_set_types_pb2.pyi +7 -2
  22. _qwak_proto/qwak/fitness_service/constructs_pb2.py +2 -2
  23. _qwak_proto/qwak/fitness_service/constructs_pb2.pyi +24 -0
  24. _qwak_proto/qwak/kube_deployment_captain/batch_job_pb2.py +40 -40
  25. _qwak_proto/qwak/kube_deployment_captain/batch_job_pb2.pyi +12 -2
  26. _qwak_proto/qwak/projects/projects_pb2.py +17 -15
  27. _qwak_proto/qwak/secret_service/secret_service_pb2.pyi +1 -1
  28. qwak/__init__.py +1 -1
  29. qwak/clients/model_management/client.py +0 -5
  30. qwak/clients/project/client.py +0 -7
  31. qwak/exceptions/__init__.py +1 -0
  32. qwak/exceptions/qwak_grpc_address_exception.py +9 -0
  33. qwak/feature_store/_common/packaging.py +11 -5
  34. qwak/inner/const.py +2 -8
  35. qwak/inner/di_configuration/__init__.py +1 -67
  36. qwak/inner/di_configuration/account.py +6 -66
  37. qwak/inner/di_configuration/dependency_wiring.py +98 -0
  38. qwak/inner/tool/auth.py +0 -86
  39. qwak/inner/tool/grpc/grpc_auth.py +0 -32
  40. qwak/inner/tool/grpc/grpc_tools.py +125 -11
  41. qwak/inner/tool/grpc/grpc_try_wrapping.py +3 -1
  42. qwak/llmops/generation/chat/openai/types/chat/chat_completion.py +24 -6
  43. qwak/llmops/generation/chat/openai/types/chat/chat_completion_chunk.py +44 -8
  44. qwak/llmops/generation/chat/openai/types/chat/chat_completion_message.py +6 -3
  45. qwak/qwak_client/client.py +2 -8
  46. qwak/vector_store/rest_helpers.py +4 -16
  47. qwak_core-0.5.4.dist-info/METADATA +48 -0
  48. {qwak_core-0.4.362.dist-info → qwak_core-0.5.4.dist-info}/RECORD +49 -82
  49. frogml_storage/__init__.py +0 -1
  50. frogml_storage/artifactory/__init__.py +0 -1
  51. frogml_storage/artifactory/_artifactory_api.py +0 -315
  52. frogml_storage/authentication/login/__init__.py +0 -1
  53. frogml_storage/authentication/login/_login_cli.py +0 -239
  54. frogml_storage/authentication/login/_login_command.py +0 -74
  55. frogml_storage/authentication/models/__init__.py +0 -3
  56. frogml_storage/authentication/models/_auth.py +0 -24
  57. frogml_storage/authentication/models/_auth_config.py +0 -70
  58. frogml_storage/authentication/models/_login.py +0 -22
  59. frogml_storage/authentication/utils/__init__.py +0 -17
  60. frogml_storage/authentication/utils/_authentication_utils.py +0 -281
  61. frogml_storage/authentication/utils/_login_checks_utils.py +0 -114
  62. frogml_storage/base_storage.py +0 -140
  63. frogml_storage/constants.py +0 -56
  64. frogml_storage/exceptions/checksum_verification_error.py +0 -3
  65. frogml_storage/exceptions/validation_error.py +0 -4
  66. frogml_storage/frog_ml.py +0 -668
  67. frogml_storage/http/__init__.py +0 -1
  68. frogml_storage/http/http_client.py +0 -83
  69. frogml_storage/logging/__init__.py +0 -1
  70. frogml_storage/logging/_log_config.py +0 -45
  71. frogml_storage/logging/log_utils.py +0 -21
  72. frogml_storage/models/__init__.py +0 -1
  73. frogml_storage/models/_download_context.py +0 -54
  74. frogml_storage/models/dataset_manifest.py +0 -13
  75. frogml_storage/models/entity_manifest.py +0 -93
  76. frogml_storage/models/frogml_dataset_version.py +0 -21
  77. frogml_storage/models/frogml_entity_type_info.py +0 -50
  78. frogml_storage/models/frogml_entity_version.py +0 -34
  79. frogml_storage/models/frogml_model_version.py +0 -21
  80. frogml_storage/models/model_manifest.py +0 -60
  81. frogml_storage/models/serialization_metadata.py +0 -15
  82. frogml_storage/utils/__init__.py +0 -12
  83. frogml_storage/utils/_environment.py +0 -21
  84. frogml_storage/utils/_input_checks_utility.py +0 -104
  85. frogml_storage/utils/_storage_utils.py +0 -15
  86. frogml_storage/utils/_url_utils.py +0 -27
  87. qwak_core-0.4.362.dist-info/METADATA +0 -414
  88. {qwak_core-0.4.362.dist-info → qwak_core-0.5.4.dist-info}/WHEEL +0 -0
qwak/inner/tool/auth.py CHANGED
@@ -1,12 +1,9 @@
1
1
  import warnings
2
2
  from filelock import FileLock
3
- from typing_extensions import Self
4
3
 
5
4
  from qwak.inner.di_configuration.session import Session
6
5
  from abc import ABC, abstractmethod
7
6
  from typing import Optional
8
- from frogml_storage.authentication.utils import get_credentials
9
- from frogml_storage.authentication.models import AuthConfig
10
7
 
11
8
  warnings.filterwarnings(action="ignore", module=".*jose.*")
12
9
 
@@ -140,86 +137,3 @@ class Auth0ClientBase(BaseAuthClient):
140
137
  raise e
141
138
  except Exception:
142
139
  raise QwakLoginException()
143
-
144
-
145
- class FrogMLAuthClient(BaseAuthClient):
146
- __MIN_TOKEN_LENGTH: int = 64
147
-
148
- def __init__(self, auth_config: Optional[AuthConfig] = None):
149
- self.auth_config = auth_config
150
- self._token = None
151
- self._tenant_id = None
152
-
153
- def get_token(self) -> Optional[str]:
154
- if not self._token:
155
- self.login()
156
- return self._token
157
-
158
- def get_tenant_id(self) -> Optional[str]:
159
- if not self._tenant_id:
160
- self.login()
161
- return self._tenant_id
162
-
163
- def get_base_url(self) -> str:
164
- artifactory_url, _ = get_credentials(self.auth_config)
165
- return self.__format_artifactory_url(artifactory_url)
166
-
167
- def login(self) -> None:
168
- artifactory_url, auth = get_credentials(self.auth_config)
169
- # For now, we only support Bearer token authentication
170
- if not hasattr(auth, "token"):
171
- return
172
-
173
- # noinspection PyUnresolvedReferences
174
- self._token = auth.token
175
- self.__validate_token()
176
-
177
- base_url = self.__format_artifactory_url(artifactory_url)
178
- try:
179
- response = requests.get(
180
- f"{base_url}/ui/api/v1/system/auth/screen/footer",
181
- headers={"Authorization": f"Bearer {self._token}"},
182
- timeout=60,
183
- )
184
- response.raise_for_status() # Raises an HTTPError for bad responses
185
- response_data = response.json()
186
- if "serverId" not in response_data:
187
- response = requests.get(
188
- f"{base_url}/jfconnect/api/v1/system/jpd_id",
189
- headers={"Authorization": f"Bearer {self._token}"},
190
- timeout=60,
191
- )
192
- if response.status_code == 200:
193
- self._tenant_id = response.text
194
- elif response.status_code == 401:
195
- raise QwakLoginException(
196
- "Failed to authenticate with JFrog. Please check your credentials"
197
- )
198
- else:
199
- raise QwakLoginException(
200
- "Failed to authenticate with JFrog. Please check your artifactory configuration"
201
- )
202
- else:
203
- self._tenant_id = response_data["serverId"]
204
- except requests.exceptions.RequestException:
205
- raise QwakLoginException(
206
- "Failed to authenticate with JFrog. Please check your artifactory configuration"
207
- )
208
- except ValueError: # This catches JSON decode errors
209
- raise QwakLoginException(
210
- "Failed to authenticate with JFrog. Please check your artifactory configuration"
211
- )
212
-
213
- def __validate_token(self: Self):
214
- if self._token is None or len(self._token) <= self.__MIN_TOKEN_LENGTH:
215
- raise QwakLoginException(
216
- "Authentication with JFrog failed: Only JWT Access Tokens are supported. "
217
- "Please ensure you are using a valid JWT Access Token."
218
- )
219
-
220
- @staticmethod
221
- def __format_artifactory_url(artifactory_url: str) -> str:
222
- # Remove '/artifactory' from the URL
223
- base_url: str = artifactory_url.replace("/artifactory", "")
224
- # Remove trailing slash if exists
225
- return base_url.rstrip("/")
@@ -1,7 +1,6 @@
1
1
  from typing import Callable, Tuple
2
2
 
3
3
  import grpc
4
- from qwak.inner.const import QwakConstants
5
4
 
6
5
  _SIGNATURE_HEADER_KEY = "authorization"
7
6
 
@@ -35,34 +34,3 @@ class Auth0Client(grpc.AuthMetadataPlugin):
35
34
 
36
35
  self._auth_client = Auth0ClientBase()
37
36
  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
- # Get token from FrogML client
58
- if not self._auth_client:
59
- from qwak.inner.tool.auth import FrogMLAuthClient
60
-
61
- self._auth_client = FrogMLAuthClient()
62
- token = self._auth_client.get_token()
63
- jfrog_tenant_id = self._auth_client.get_tenant_id()
64
- metadata = (
65
- (_SIGNATURE_HEADER_KEY, f"Bearer {token}"),
66
- (QwakConstants.JFROG_TENANT_HEADER_KEY.lower(), jfrog_tenant_id),
67
- )
68
- callback(metadata, None)
@@ -1,17 +1,18 @@
1
1
  import logging
2
+ import re
2
3
  import time
3
4
  from abc import ABC, abstractmethod
4
5
  from random import randint
5
6
  from typing import Callable, Optional, Tuple
7
+ from urllib.parse import urlparse, ParseResult
6
8
 
7
9
  import grpc
8
- from qwak.exceptions import QwakException
9
- from qwak.inner.di_configuration.account import UserAccountConfiguration
10
- from qwak.inner.tool.auth import Auth0ClientBase
11
10
 
12
- from .grpc_auth import Auth0Client, FrogMLGrpcClient
11
+ from qwak.exceptions import QwakException, QwakGrpcAddressException
12
+ from .grpc_auth import Auth0Client
13
13
 
14
14
  logger = logging.getLogger()
15
+ HOSTNAME_REGEX: str = r"^(?!-)(?:[A-Za-z0-9-]{1,63}\.)*[A-Za-z0-9-]{1,63}(?<!-)$"
15
16
 
16
17
 
17
18
  def create_grpc_channel(
@@ -21,7 +22,7 @@ def create_grpc_channel(
21
22
  auth_metadata_plugin: grpc.AuthMetadataPlugin = None,
22
23
  timeout: int = 100,
23
24
  options=None,
24
- backoff_options={},
25
+ backoff_options=None,
25
26
  max_attempts=4,
26
27
  status_for_retry=(grpc.StatusCode.UNAVAILABLE,),
27
28
  attempt=0,
@@ -42,6 +43,9 @@ def create_grpc_channel(
42
43
  status_for_retry: grpc statuses to retry upon
43
44
  Returns: Returns a grpc.Channel
44
45
  """
46
+ if backoff_options is None:
47
+ backoff_options = {}
48
+
45
49
  if not url:
46
50
  raise QwakException("Unable to create gRPC channel. URL has not been defined.")
47
51
 
@@ -49,11 +53,7 @@ def create_grpc_channel(
49
53
  credentials = grpc.ssl_channel_credentials()
50
54
  if enable_auth:
51
55
  if auth_metadata_plugin is None:
52
- user_config = UserAccountConfiguration()
53
- if issubclass(user_config._auth_client, Auth0ClientBase):
54
- auth_metadata_plugin = Auth0Client()
55
- else:
56
- auth_metadata_plugin = FrogMLGrpcClient()
56
+ auth_metadata_plugin = Auth0Client()
57
57
  credentials = grpc.composite_channel_credentials(
58
58
  credentials, grpc.metadata_call_credentials(auth_metadata_plugin)
59
59
  )
@@ -107,11 +107,14 @@ def create_grpc_channel_or_none(
107
107
  auth_metadata_plugin: grpc.AuthMetadataPlugin = None,
108
108
  timeout: int = 30,
109
109
  options=None,
110
- backoff_options={},
110
+ backoff_options=None,
111
111
  max_attempts=2,
112
112
  status_for_retry=(grpc.StatusCode.UNAVAILABLE,),
113
113
  attempt=0,
114
114
  ) -> Callable[[Optional[str], Optional[bool]], Optional[grpc.Channel]]:
115
+ if backoff_options is None:
116
+ backoff_options = {}
117
+
115
118
  def deferred_channel(
116
119
  url_overwrite: Optional[str] = None, ssl_overwrite: Optional[bool] = None
117
120
  ):
@@ -135,6 +138,117 @@ def create_grpc_channel_or_none(
135
138
  return deferred_channel
136
139
 
137
140
 
141
+ def validate_grpc_address(
142
+ grpc_address: str,
143
+ is_port_specification_allowed: bool = False,
144
+ is_url_scheme_allowed: bool = False,
145
+ ):
146
+ """
147
+ Validate gRPC address format
148
+ Args:
149
+ grpc_address (str): gRPC address to validate
150
+ is_port_specification_allowed (bool): Whether to allow port specification in the address
151
+ is_url_scheme_allowed (bool): Whether to allow URL scheme in the address
152
+ Raises:
153
+ QwakGrpcAddressException: If the gRPC address is invalid
154
+ """
155
+ parsed_grpc_address: ParseResult = parse_address(grpc_address)
156
+ hostname: str = get_hostname_from_address(parsed_grpc_address)
157
+ validate_paths_are_not_included_in_address(parsed_grpc_address)
158
+
159
+ if not is_url_scheme_allowed:
160
+ __validate_url_scheme_not_included_in_address(parsed_grpc_address)
161
+
162
+ if not is_port_specification_allowed:
163
+ __validate_port_not_included_in_address(parsed_grpc_address)
164
+
165
+ if not is_valid_hostname(hostname):
166
+ raise QwakGrpcAddressException(
167
+ "gRPC address must be a simple hostname or fully qualified domain name.",
168
+ parsed_grpc_address,
169
+ )
170
+
171
+
172
+ def validate_paths_are_not_included_in_address(
173
+ parsed_grpc_address: ParseResult,
174
+ ) -> None:
175
+ has_invalid_path: bool = (
176
+ parsed_grpc_address.path not in {"", "/"}
177
+ or parsed_grpc_address.query
178
+ or parsed_grpc_address.fragment
179
+ )
180
+
181
+ if has_invalid_path:
182
+ raise QwakGrpcAddressException(
183
+ "gRPC address must not contain paths, queries, or fragments.",
184
+ parsed_grpc_address,
185
+ )
186
+
187
+
188
+ def get_hostname_from_address(parsed_grpc_address: ParseResult) -> str:
189
+ hostname: Optional[str] = parsed_grpc_address.hostname
190
+ if not hostname:
191
+ raise QwakGrpcAddressException(
192
+ "gRPC address must contain a valid hostname.", parsed_grpc_address
193
+ )
194
+
195
+ return hostname
196
+
197
+
198
+ def __validate_url_scheme_not_included_in_address(
199
+ parsed_grpc_address: ParseResult,
200
+ ) -> None:
201
+ if parsed_grpc_address.scheme:
202
+ raise QwakGrpcAddressException(
203
+ "URL scheme is not allowed in the gRPC address.", parsed_grpc_address
204
+ )
205
+
206
+
207
+ def __validate_port_not_included_in_address(parsed_grpc_address: ParseResult):
208
+ try:
209
+ port: Optional[int] = parsed_grpc_address.port
210
+ except ValueError as exc:
211
+ raise QwakGrpcAddressException(
212
+ "Invalid port specification in the gRPC address.", parsed_grpc_address
213
+ ) from exc
214
+
215
+ if port:
216
+ raise QwakGrpcAddressException(
217
+ "Port specification is not allowed in the gRPC address.",
218
+ parsed_grpc_address,
219
+ )
220
+
221
+
222
+ def parse_address(grpc_address: str) -> ParseResult:
223
+ if not grpc_address or not grpc_address.strip():
224
+ raise QwakGrpcAddressException(
225
+ "gRPC address must not be empty or whitespace.", grpc_address
226
+ )
227
+
228
+ trimmed_address: str = grpc_address.strip()
229
+ parsed_address: ParseResult = urlparse(
230
+ trimmed_address if "://" in trimmed_address else f"//{trimmed_address}"
231
+ )
232
+
233
+ return parsed_address
234
+
235
+
236
+ def is_valid_hostname(hostname: str) -> bool:
237
+ """
238
+ Validate that the supplied hostname conforms to RFC-style label rules:
239
+ anchored pattern enforces full-string validation, negative lookahead/lookbehind block
240
+ leading or trailing hyphens per label, and each dot-separated label must be 1-63
241
+ alphanumeric/hyphen characters.
242
+
243
+ Args:
244
+ hostname (str): The hostname to validate.
245
+ Returns:
246
+ bool: True if the hostname is valid, False otherwise.
247
+ """
248
+ hostname_pattern: re.Pattern = re.compile(HOSTNAME_REGEX)
249
+ return bool(hostname_pattern.fullmatch(hostname))
250
+
251
+
138
252
  class SleepingPolicy(ABC):
139
253
  @abstractmethod
140
254
  def sleep(self, try_i: int):
@@ -1,12 +1,14 @@
1
+ import logging
1
2
  import functools
2
3
  import inspect
3
4
  from inspect import BoundArguments, Signature
4
5
  from typing import Any, Callable, Optional
5
6
 
6
7
  import grpc
7
- from frogml_storage.logging import logger
8
8
  from qwak.exceptions import QwakException
9
9
 
10
+ logger = logging.getLogger()
11
+
10
12
 
11
13
  def grpc_try_catch_wrapper(
12
14
  exception_message: str,
@@ -16,8 +16,6 @@
16
16
  from dataclasses import dataclass
17
17
  from typing import List, Optional
18
18
 
19
- from typing_extensions import Literal
20
-
21
19
  from qwak.llmops.generation.base import ModelResponse
22
20
  from .chat_completion_message import ChatCompletionMessage
23
21
  from .chat_completion_token_logprob import ChatCompletionTokenLogprob
@@ -34,9 +32,7 @@ class ChoiceLogprobs:
34
32
 
35
33
  @dataclass
36
34
  class Choice:
37
- finish_reason: Literal[
38
- "stop", "length", "tool_calls", "content_filter", "function_call"
39
- ]
35
+ finish_reason: str
40
36
  """The reason the model stopped generating tokens.
41
37
 
42
38
  This will be `stop` if the model hit a natural stop point or a provided stop
@@ -55,6 +51,21 @@ class Choice:
55
51
  logprobs: Optional[ChoiceLogprobs] = None
56
52
  """Log probability information for the choice."""
57
53
 
54
+ def __post_init__(self):
55
+ """Validates that finish_reason is one of the allowed values."""
56
+ allowed_reasons = {
57
+ "stop",
58
+ "length",
59
+ "tool_calls",
60
+ "content_filter",
61
+ "function_call",
62
+ }
63
+ if self.finish_reason not in allowed_reasons:
64
+ raise ValueError(
65
+ f"Invalid finish_reason: '{self.finish_reason}'. "
66
+ f"Must be one of {allowed_reasons}"
67
+ )
68
+
58
69
 
59
70
  @dataclass
60
71
  class ChatCompletion(ModelResponse):
@@ -73,7 +84,7 @@ class ChatCompletion(ModelResponse):
73
84
  model: str
74
85
  """The model used for the chat completion."""
75
86
 
76
- object: Literal["chat.completion"]
87
+ object: str
77
88
  """The object type, which is always `chat.completion`."""
78
89
 
79
90
  system_fingerprint: Optional[str] = None
@@ -85,3 +96,10 @@ class ChatCompletion(ModelResponse):
85
96
 
86
97
  usage: Optional[CompletionUsage] = None
87
98
  """Usage statistics for the completion request."""
99
+
100
+ def __post_init__(self):
101
+ """Validates that the object type is correct."""
102
+ if self.object != "chat.completion":
103
+ raise ValueError(
104
+ f"Invalid object type: '{self.object}'. Must be 'chat.completion'"
105
+ )
@@ -16,8 +16,6 @@
16
16
  from dataclasses import dataclass
17
17
  from typing import List, Optional
18
18
 
19
- from typing_extensions import Literal
20
-
21
19
  from qwak.llmops.generation.base import ModelResponse
22
20
  from .chat_completion_token_logprob import ChatCompletionTokenLogprob
23
21
 
@@ -69,9 +67,16 @@ class ChoiceDeltaToolCall:
69
67
 
70
68
  function: Optional[ChoiceDeltaToolCallFunction] = None
71
69
 
72
- type: Optional[Literal["function"]] = None
70
+ type: Optional[str] = None
73
71
  """The type of the tool. Currently, only `function` is supported."""
74
72
 
73
+ def __post_init__(self):
74
+ """Validates that type is 'function' if present."""
75
+ if self.type is not None and self.type != "function":
76
+ raise ValueError(
77
+ f"Invalid type: '{self.type}'. Must be 'function' or None."
78
+ )
79
+
75
80
 
76
81
  @dataclass
77
82
  class ChoiceDelta:
@@ -85,11 +90,21 @@ class ChoiceDelta:
85
90
  model.
86
91
  """
87
92
 
88
- role: Optional[Literal["system", "user", "assistant", "tool"]] = None
93
+ role: Optional[str] = None
89
94
  """The role of the author of this message."""
90
95
 
91
96
  tool_calls: Optional[List[ChoiceDeltaToolCall]] = None
92
97
 
98
+ def __post_init__(self):
99
+ """Validates that role is one of the allowed values if present."""
100
+ if self.role is not None:
101
+ allowed_roles = {"system", "user", "assistant", "tool"}
102
+ if self.role not in allowed_roles:
103
+ raise ValueError(
104
+ f"Invalid role: '{self.role}'. "
105
+ f"Must be one of {allowed_roles} or None."
106
+ )
107
+
93
108
 
94
109
  @dataclass
95
110
  class ChoiceLogprobs:
@@ -105,9 +120,7 @@ class Choice:
105
120
  index: int
106
121
  """The index of the choice in the list of choices."""
107
122
 
108
- finish_reason: Optional[
109
- Literal["stop", "length", "tool_calls", "content_filter", "function_call"]
110
- ] = None
123
+ finish_reason: Optional[str] = None
111
124
  """The reason the model stopped generating tokens.
112
125
 
113
126
  This will be `stop` if the model hit a natural stop point or a provided stop
@@ -120,6 +133,22 @@ class Choice:
120
133
  logprobs: Optional[ChoiceLogprobs] = None
121
134
  """Log probability information for the choice."""
122
135
 
136
+ def __post_init__(self):
137
+ """Validates that finish_reason is one of the allowed values if present."""
138
+ if self.finish_reason is not None:
139
+ allowed_reasons = {
140
+ "stop",
141
+ "length",
142
+ "tool_calls",
143
+ "content_filter",
144
+ "function_call",
145
+ }
146
+ if self.finish_reason not in allowed_reasons:
147
+ raise ValueError(
148
+ f"Invalid finish_reason: '{self.finish_reason}'. "
149
+ f"Must be one of {allowed_reasons} or None."
150
+ )
151
+
123
152
 
124
153
  @dataclass
125
154
  class ChatCompletionChunk(ModelResponse):
@@ -141,7 +170,7 @@ class ChatCompletionChunk(ModelResponse):
141
170
  model: str
142
171
  """The model to generate the completion."""
143
172
 
144
- object: Literal["chat.completion.chunk"]
173
+ object: str
145
174
  """The object type, which is always `chat.completion.chunk`."""
146
175
 
147
176
  system_fingerprint: Optional[str] = None
@@ -150,3 +179,10 @@ class ChatCompletionChunk(ModelResponse):
150
179
  Can be used in conjunction with the `seed` request parameter to understand when
151
180
  backend changes have been made that might impact determinism.
152
181
  """
182
+
183
+ def __post_init__(self):
184
+ """Validates that the object type is correct."""
185
+ if self.object != "chat.completion.chunk":
186
+ raise ValueError(
187
+ f"Invalid object type: '{self.object}'. Must be 'chat.completion.chunk'"
188
+ )
@@ -16,8 +16,6 @@
16
16
  from dataclasses import dataclass
17
17
  from typing import List, Optional
18
18
 
19
- from typing_extensions import Literal
20
-
21
19
  from .chat_completion_message_tool_call import ChatCompletionMessageToolCall
22
20
 
23
21
  __all__ = ["ChatCompletionMessage", "FunctionCall"]
@@ -39,7 +37,7 @@ class FunctionCall:
39
37
 
40
38
  @dataclass
41
39
  class ChatCompletionMessage:
42
- role: Literal["assistant"]
40
+ role: str
43
41
  """The role of the author of this message."""
44
42
 
45
43
  content: Optional[str] = None
@@ -54,3 +52,8 @@ class ChatCompletionMessage:
54
52
 
55
53
  tool_calls: Optional[List[ChatCompletionMessageToolCall]] = None
56
54
  """The tool calls generated by the model, such as function calls."""
55
+
56
+ def __post_init__(self):
57
+ """Validates that the role type is correct."""
58
+ if self.role != "assistant":
59
+ raise ValueError(f"Invalid object type: '{self.role}'. Must be 'assistant'")
@@ -238,8 +238,8 @@ class QwakClient:
238
238
  model = self._get_model_management().get_model(model_id=model_id)
239
239
  model_uuid = model.uuid
240
240
 
241
- metric_filters = list()
242
- parameter_filters = list()
241
+ metric_filters = []
242
+ parameter_filters = []
243
243
 
244
244
  for build_filter in filters:
245
245
  if isinstance(build_filter, MetricFilter):
@@ -356,7 +356,6 @@ class QwakClient:
356
356
  self,
357
357
  project_name: str,
358
358
  project_description: str,
359
- jfrog_project_key: Optional[str] = None,
360
359
  ) -> str:
361
360
  """
362
361
  Create project
@@ -364,7 +363,6 @@ class QwakClient:
364
363
  Args:
365
364
  project_name (str): The requested name
366
365
  project_description (str): The requested description
367
- jfrog_project_key (Optional[str]): The requested jfrog project key
368
366
 
369
367
  Returns:
370
368
  str: The project ID of the newly created project
@@ -373,7 +371,6 @@ class QwakClient:
373
371
  project = self._get_project_management().create_project(
374
372
  project_name=project_name,
375
373
  project_description=project_description,
376
- jfrog_project_key=jfrog_project_key,
377
374
  )
378
375
 
379
376
  return project.project.project_id
@@ -419,7 +416,6 @@ class QwakClient:
419
416
  project_id: str,
420
417
  model_name: str,
421
418
  model_description: str,
422
- jfrog_project_key: Optional[str] = None,
423
419
  ) -> str:
424
420
  """
425
421
  Create model
@@ -428,7 +424,6 @@ class QwakClient:
428
424
  project_id (str): The project ID to associate the model
429
425
  model_name (str): The requested name
430
426
  model_description (str): The requested description
431
- jfrog_project_key (Optional[str]): The jfrog project key
432
427
 
433
428
  Returns:
434
429
  str: The model ID of the newly created project
@@ -438,7 +433,6 @@ class QwakClient:
438
433
  project_id=project_id,
439
434
  model_name=model_name,
440
435
  model_description=model_description,
441
- jfrog_project_key=jfrog_project_key,
442
436
  )
443
437
 
444
438
  return model.model_id
@@ -4,25 +4,16 @@ import socket
4
4
  from datetime import datetime
5
5
 
6
6
  import requests
7
- from qwak.inner.di_configuration.account import UserAccountConfiguration
8
- from qwak.inner.tool.auth import Auth0ClientBase, FrogMLAuthClient
9
- from qwak.inner.const import QwakConstants
7
+ from qwak.inner.tool.auth import Auth0ClientBase
10
8
 
11
9
 
12
10
  def _get_authorization():
13
- user_account_configuration = UserAccountConfiguration()
14
- if issubclass(user_account_configuration._auth_client, Auth0ClientBase):
15
- auth_client = Auth0ClientBase()
16
- tenant_id = None
17
- else:
18
- auth_client = FrogMLAuthClient()
19
- tenant_id = auth_client.get_tenant_id()
20
-
11
+ auth_client = Auth0ClientBase()
21
12
  token = auth_client.get_token()
22
13
  token_split = token.split(".")
23
14
  decoded_token = json.loads(_base64url_decode(token_split[1]).decode("utf-8"))
24
15
  token_expiration = datetime.fromtimestamp(decoded_token["exp"])
25
- return f"Bearer {token}", token_expiration, tenant_id
16
+ return f"Bearer {token}", token_expiration
26
17
 
27
18
 
28
19
  def _base64url_decode(input):
@@ -77,8 +68,5 @@ class RestSession(requests.Session):
77
68
  return super().prepare_request(request)
78
69
 
79
70
  def prepare_request_token(self):
80
- auth_token, self.jwt_expiration, tenant_id = _get_authorization()
71
+ auth_token, self.jwt_expiration = _get_authorization()
81
72
  self.headers["Authorization"] = auth_token
82
-
83
- if tenant_id:
84
- self.headers[QwakConstants.JFROG_TENANT_HEADER_KEY] = tenant_id
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.3
2
+ Name: qwak-core
3
+ Version: 0.5.4
4
+ Summary: Qwak Core contains the necessary objects and communication tools for using the Qwak Platform
5
+ License: Apache-2.0
6
+ Keywords: mlops,ml,deployment,serving,model
7
+ Author: Qwak
8
+ Author-email: info@qwak.com
9
+ Requires-Python: >=3.9,<3.12
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: Implementation :: CPython
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Provides-Extra: feature-store
19
+ Requires-Dist: PyYAML (>=6.0.2)
20
+ Requires-Dist: cachetools
21
+ Requires-Dist: chevron (==0.14.0)
22
+ Requires-Dist: cloudpickle (==2.2.1) ; extra == "feature-store"
23
+ Requires-Dist: dacite (==1.9.2)
24
+ Requires-Dist: dependency-injector (>=4.0)
25
+ Requires-Dist: filelock
26
+ Requires-Dist: grpcio (>=1.71.2)
27
+ Requires-Dist: joblib (>=1.3.2,<2.0.0)
28
+ Requires-Dist: marshmallow-dataclass (>=8.5.8,<9.0.0)
29
+ Requires-Dist: protobuf (>=4.25.8,<5)
30
+ Requires-Dist: pyarrow (>=20.0.0) ; extra == "feature-store"
31
+ Requires-Dist: pyathena (>=2.2.0,!=2.18.0) ; extra == "feature-store"
32
+ Requires-Dist: pydantic
33
+ Requires-Dist: pyspark (==3.5.7) ; extra == "feature-store"
34
+ Requires-Dist: python-jose[cryptography] (>=3.4.0)
35
+ Requires-Dist: python-json-logger (>=2.0.2)
36
+ Requires-Dist: requests
37
+ Requires-Dist: retrying (==1.4.2)
38
+ Requires-Dist: tqdm
39
+ Requires-Dist: typeguard (>=2,<3)
40
+ Requires-Dist: typer
41
+ Project-URL: Home page, https://www.qwak.com/
42
+ Description-Content-Type: text/markdown
43
+
44
+ # Qwak Core
45
+
46
+ Qwak is an end-to-end production ML platform designed to allow data scientists to build, deploy, and monitor their models in production with minimal engineering friction.
47
+ Qwak Core contains all the objects and tools necessary to use the Qwak Platform
48
+