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

@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
13
13
 
14
14
 
15
15
 
16
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n5qwak/model_group/model_group_repository_details.proto\x12\x1cqwak.model_groups.management\"r\n\x11RepositoryDetails\x12\x45\n\x0frepository_type\x18\x01 \x01(\x0b\x32,.qwak.model_groups.management.RepositoryType\x12\x16\n\x0erepository_key\x18\x02 \x01(\t\"\xf0\x01\n\x17RemoteRepositoryDetails\x12K\n\x11\x64ocker_repository\x18\x01 \x01(\x0b\x32..qwak.model_groups.management.DockerRepositoryH\x00\x12V\n\x17hugging_face_repository\x18\x02 \x01(\x0b\x32\x33.qwak.model_groups.management.HuggingFaceRepositoryH\x00\x12\x1d\n\x15repository_remote_url\x18\x03 \x01(\tB\x11\n\x0frepository_type\"\x12\n\x10\x44ockerRepository\"\x13\n\x11\x44\x61tasetRepository\"\x14\n\x12\x41rtifactRepository\"\x17\n\x15HuggingFaceRepository\"\xdd\x02\n\x0eRepositoryType\x12K\n\x11\x64ocker_repository\x18\x01 \x01(\x0b\x32..qwak.model_groups.management.DockerRepositoryH\x00\x12M\n\x12\x64\x61taset_repository\x18\x02 \x01(\x0b\x32/.qwak.model_groups.management.DatasetRepositoryH\x00\x12O\n\x13\x61rtifact_repository\x18\x03 \x01(\x0b\x32\x30.qwak.model_groups.management.ArtifactRepositoryH\x00\x12V\n\x17hugging_face_repository\x18\x04 \x01(\x0b\x32\x33.qwak.model_groups.management.HuggingFaceRepositoryH\x00\x42\x06\n\x04typeBN\n&com.qwak.ai.management.model.group.apiB\"ModelGroupRepositoriesDetailsProtoP\x01\x62\x06proto3')
16
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n5qwak/model_group/model_group_repository_details.proto\x12\x1cqwak.model_groups.management\"r\n\x11RepositoryDetails\x12\x45\n\x0frepository_type\x18\x01 \x01(\x0b\x32,.qwak.model_groups.management.RepositoryType\x12\x16\n\x0erepository_key\x18\x02 \x01(\t\"\x80\x03\n\x17RemoteRepositoryDetails\x12K\n\x11\x64ocker_repository\x18\x01 \x01(\x0b\x32..qwak.model_groups.management.DockerRepositoryH\x00\x12V\n\x17hugging_face_repository\x18\x02 \x01(\x0b\x32\x33.qwak.model_groups.management.HuggingFaceRepositoryH\x00\x12\x45\n\x0enpm_repository\x18\x04 \x01(\x0b\x32+.qwak.model_groups.management.NpmRepositoryH\x00\x12G\n\x0fpypi_repository\x18\x05 \x01(\x0b\x32,.qwak.model_groups.management.PypiRepositoryH\x00\x12\x1d\n\x15repository_remote_url\x18\x03 \x01(\tB\x11\n\x0frepository_type\"\x12\n\x10\x44ockerRepository\"\x13\n\x11\x44\x61tasetRepository\"\x14\n\x12\x41rtifactRepository\"\x17\n\x15HuggingFaceRepository\"\x0f\n\rNpmRepository\"\x10\n\x0ePypiRepository\"\xed\x03\n\x0eRepositoryType\x12K\n\x11\x64ocker_repository\x18\x01 \x01(\x0b\x32..qwak.model_groups.management.DockerRepositoryH\x00\x12M\n\x12\x64\x61taset_repository\x18\x02 \x01(\x0b\x32/.qwak.model_groups.management.DatasetRepositoryH\x00\x12O\n\x13\x61rtifact_repository\x18\x03 \x01(\x0b\x32\x30.qwak.model_groups.management.ArtifactRepositoryH\x00\x12V\n\x17hugging_face_repository\x18\x04 \x01(\x0b\x32\x33.qwak.model_groups.management.HuggingFaceRepositoryH\x00\x12\x45\n\x0enpm_repository\x18\x05 \x01(\x0b\x32+.qwak.model_groups.management.NpmRepositoryH\x00\x12G\n\x0fpypi_repository\x18\x06 \x01(\x0b\x32,.qwak.model_groups.management.PypiRepositoryH\x00\x42\x06\n\x04typeBN\n&com.qwak.ai.management.model.group.apiB\"ModelGroupRepositoriesDetailsProtoP\x01\x62\x06proto3')
17
17
 
18
18
  _globals = globals()
19
19
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -25,15 +25,19 @@ if _descriptor._USE_C_DESCRIPTORS == False:
25
25
  _globals['_REPOSITORYDETAILS']._serialized_start=87
26
26
  _globals['_REPOSITORYDETAILS']._serialized_end=201
27
27
  _globals['_REMOTEREPOSITORYDETAILS']._serialized_start=204
28
- _globals['_REMOTEREPOSITORYDETAILS']._serialized_end=444
29
- _globals['_DOCKERREPOSITORY']._serialized_start=446
30
- _globals['_DOCKERREPOSITORY']._serialized_end=464
31
- _globals['_DATASETREPOSITORY']._serialized_start=466
32
- _globals['_DATASETREPOSITORY']._serialized_end=485
33
- _globals['_ARTIFACTREPOSITORY']._serialized_start=487
34
- _globals['_ARTIFACTREPOSITORY']._serialized_end=507
35
- _globals['_HUGGINGFACEREPOSITORY']._serialized_start=509
36
- _globals['_HUGGINGFACEREPOSITORY']._serialized_end=532
37
- _globals['_REPOSITORYTYPE']._serialized_start=535
38
- _globals['_REPOSITORYTYPE']._serialized_end=884
28
+ _globals['_REMOTEREPOSITORYDETAILS']._serialized_end=588
29
+ _globals['_DOCKERREPOSITORY']._serialized_start=590
30
+ _globals['_DOCKERREPOSITORY']._serialized_end=608
31
+ _globals['_DATASETREPOSITORY']._serialized_start=610
32
+ _globals['_DATASETREPOSITORY']._serialized_end=629
33
+ _globals['_ARTIFACTREPOSITORY']._serialized_start=631
34
+ _globals['_ARTIFACTREPOSITORY']._serialized_end=651
35
+ _globals['_HUGGINGFACEREPOSITORY']._serialized_start=653
36
+ _globals['_HUGGINGFACEREPOSITORY']._serialized_end=676
37
+ _globals['_NPMREPOSITORY']._serialized_start=678
38
+ _globals['_NPMREPOSITORY']._serialized_end=693
39
+ _globals['_PYPIREPOSITORY']._serialized_start=695
40
+ _globals['_PYPIREPOSITORY']._serialized_end=711
41
+ _globals['_REPOSITORYTYPE']._serialized_start=714
42
+ _globals['_REPOSITORYTYPE']._serialized_end=1207
39
43
  # @@protoc_insertion_point(module_scope)
@@ -40,6 +40,8 @@ class RemoteRepositoryDetails(google.protobuf.message.Message):
40
40
 
41
41
  DOCKER_REPOSITORY_FIELD_NUMBER: builtins.int
42
42
  HUGGING_FACE_REPOSITORY_FIELD_NUMBER: builtins.int
43
+ NPM_REPOSITORY_FIELD_NUMBER: builtins.int
44
+ PYPI_REPOSITORY_FIELD_NUMBER: builtins.int
43
45
  REPOSITORY_REMOTE_URL_FIELD_NUMBER: builtins.int
44
46
  @property
45
47
  def docker_repository(self) -> global___DockerRepository:
@@ -47,6 +49,12 @@ class RemoteRepositoryDetails(google.protobuf.message.Message):
47
49
  @property
48
50
  def hugging_face_repository(self) -> global___HuggingFaceRepository:
49
51
  """HuggingFace repository"""
52
+ @property
53
+ def npm_repository(self) -> global___NpmRepository:
54
+ """NPM repository"""
55
+ @property
56
+ def pypi_repository(self) -> global___PypiRepository:
57
+ """PyPI repository"""
50
58
  repository_remote_url: builtins.str
51
59
  """The remote repository URL"""
52
60
  def __init__(
@@ -54,11 +62,13 @@ class RemoteRepositoryDetails(google.protobuf.message.Message):
54
62
  *,
55
63
  docker_repository: global___DockerRepository | None = ...,
56
64
  hugging_face_repository: global___HuggingFaceRepository | None = ...,
65
+ npm_repository: global___NpmRepository | None = ...,
66
+ pypi_repository: global___PypiRepository | None = ...,
57
67
  repository_remote_url: builtins.str = ...,
58
68
  ) -> None: ...
59
- def HasField(self, field_name: typing_extensions.Literal["docker_repository", b"docker_repository", "hugging_face_repository", b"hugging_face_repository", "repository_type", b"repository_type"]) -> builtins.bool: ...
60
- def ClearField(self, field_name: typing_extensions.Literal["docker_repository", b"docker_repository", "hugging_face_repository", b"hugging_face_repository", "repository_remote_url", b"repository_remote_url", "repository_type", b"repository_type"]) -> None: ...
61
- def WhichOneof(self, oneof_group: typing_extensions.Literal["repository_type", b"repository_type"]) -> typing_extensions.Literal["docker_repository", "hugging_face_repository"] | None: ...
69
+ def HasField(self, field_name: typing_extensions.Literal["docker_repository", b"docker_repository", "hugging_face_repository", b"hugging_face_repository", "npm_repository", b"npm_repository", "pypi_repository", b"pypi_repository", "repository_type", b"repository_type"]) -> builtins.bool: ...
70
+ def ClearField(self, field_name: typing_extensions.Literal["docker_repository", b"docker_repository", "hugging_face_repository", b"hugging_face_repository", "npm_repository", b"npm_repository", "pypi_repository", b"pypi_repository", "repository_remote_url", b"repository_remote_url", "repository_type", b"repository_type"]) -> None: ...
71
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["repository_type", b"repository_type"]) -> typing_extensions.Literal["docker_repository", "hugging_face_repository", "npm_repository", "pypi_repository"] | None: ...
62
72
 
63
73
  global___RemoteRepositoryDetails = RemoteRepositoryDetails
64
74
 
@@ -98,6 +108,24 @@ class HuggingFaceRepository(google.protobuf.message.Message):
98
108
 
99
109
  global___HuggingFaceRepository = HuggingFaceRepository
100
110
 
111
+ class NpmRepository(google.protobuf.message.Message):
112
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
113
+
114
+ def __init__(
115
+ self,
116
+ ) -> None: ...
117
+
118
+ global___NpmRepository = NpmRepository
119
+
120
+ class PypiRepository(google.protobuf.message.Message):
121
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
122
+
123
+ def __init__(
124
+ self,
125
+ ) -> None: ...
126
+
127
+ global___PypiRepository = PypiRepository
128
+
101
129
  class RepositoryType(google.protobuf.message.Message):
102
130
  """The repository type"""
103
131
 
@@ -107,6 +135,8 @@ class RepositoryType(google.protobuf.message.Message):
107
135
  DATASET_REPOSITORY_FIELD_NUMBER: builtins.int
108
136
  ARTIFACT_REPOSITORY_FIELD_NUMBER: builtins.int
109
137
  HUGGING_FACE_REPOSITORY_FIELD_NUMBER: builtins.int
138
+ NPM_REPOSITORY_FIELD_NUMBER: builtins.int
139
+ PYPI_REPOSITORY_FIELD_NUMBER: builtins.int
110
140
  @property
111
141
  def docker_repository(self) -> global___DockerRepository:
112
142
  """Docker repository"""
@@ -119,6 +149,12 @@ class RepositoryType(google.protobuf.message.Message):
119
149
  @property
120
150
  def hugging_face_repository(self) -> global___HuggingFaceRepository:
121
151
  """HuggingFace repository"""
152
+ @property
153
+ def npm_repository(self) -> global___NpmRepository:
154
+ """NPM repository"""
155
+ @property
156
+ def pypi_repository(self) -> global___PypiRepository:
157
+ """PyPI repository"""
122
158
  def __init__(
123
159
  self,
124
160
  *,
@@ -126,9 +162,11 @@ class RepositoryType(google.protobuf.message.Message):
126
162
  dataset_repository: global___DatasetRepository | None = ...,
127
163
  artifact_repository: global___ArtifactRepository | None = ...,
128
164
  hugging_face_repository: global___HuggingFaceRepository | None = ...,
165
+ npm_repository: global___NpmRepository | None = ...,
166
+ pypi_repository: global___PypiRepository | None = ...,
129
167
  ) -> None: ...
130
- def HasField(self, field_name: typing_extensions.Literal["artifact_repository", b"artifact_repository", "dataset_repository", b"dataset_repository", "docker_repository", b"docker_repository", "hugging_face_repository", b"hugging_face_repository", "type", b"type"]) -> builtins.bool: ...
131
- def ClearField(self, field_name: typing_extensions.Literal["artifact_repository", b"artifact_repository", "dataset_repository", b"dataset_repository", "docker_repository", b"docker_repository", "hugging_face_repository", b"hugging_face_repository", "type", b"type"]) -> None: ...
132
- def WhichOneof(self, oneof_group: typing_extensions.Literal["type", b"type"]) -> typing_extensions.Literal["docker_repository", "dataset_repository", "artifact_repository", "hugging_face_repository"] | None: ...
168
+ def HasField(self, field_name: typing_extensions.Literal["artifact_repository", b"artifact_repository", "dataset_repository", b"dataset_repository", "docker_repository", b"docker_repository", "hugging_face_repository", b"hugging_face_repository", "npm_repository", b"npm_repository", "pypi_repository", b"pypi_repository", "type", b"type"]) -> builtins.bool: ...
169
+ def ClearField(self, field_name: typing_extensions.Literal["artifact_repository", b"artifact_repository", "dataset_repository", b"dataset_repository", "docker_repository", b"docker_repository", "hugging_face_repository", b"hugging_face_repository", "npm_repository", b"npm_repository", "pypi_repository", b"pypi_repository", "type", b"type"]) -> None: ...
170
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["type", b"type"]) -> typing_extensions.Literal["docker_repository", "dataset_repository", "artifact_repository", "hugging_face_repository", "npm_repository", "pypi_repository"] | None: ...
133
171
 
134
172
  global___RepositoryType = RepositoryType
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.5.4"
4
+ __version__ = "0.5.12"
5
5
 
6
6
  from qwak.inner.di_configuration import wire_dependencies
7
7
  from qwak.model.experiment_tracking import log_metric, log_param
@@ -1,4 +1,7 @@
1
1
  from _qwak_proto.qwak.execution.v1.backfill_pb2 import BackfillSpec
2
+ from _qwak_proto.qwak.execution.v1.streaming_aggregation_pb2 import (
3
+ StreamingAggregationBackfillIngestion,
4
+ )
2
5
  from _qwak_proto.qwak.execution.v1.batch_pb2 import BatchIngestion
3
6
  from _qwak_proto.qwak.execution.v1.execution_service_pb2 import (
4
7
  GetExecutionEntryRequest,
@@ -9,6 +12,8 @@ from _qwak_proto.qwak.execution.v1.execution_service_pb2 import (
9
12
  TriggerBackfillResponse,
10
13
  TriggerBatchFeaturesetRequest,
11
14
  TriggerBatchFeaturesetResponse,
15
+ TriggerStreamingAggregationBackfillRequest,
16
+ TriggerStreamingAggregationBackfillResponse,
12
17
  )
13
18
  from _qwak_proto.qwak.execution.v1.execution_service_pb2_grpc import (
14
19
  FeatureStoreExecutionServiceStub,
@@ -29,6 +34,29 @@ class ExecutionManagementClient:
29
34
  grpc_channel
30
35
  )
31
36
 
37
+ def trigger_streaming_aggregation_backfill(
38
+ self, backfill_ingestion: StreamingAggregationBackfillIngestion
39
+ ) -> TriggerStreamingAggregationBackfillResponse:
40
+ """
41
+ Receives a configured streaming aggregation backfill proto and triggers a streaming aggregation
42
+ backfill against the execution manager
43
+
44
+ Args:
45
+ backfill_ingestion (StreamingAggregationBackfillIngestion): A protobuf message
46
+ containing the backfill specification details
47
+
48
+ Returns:
49
+ TriggerStreamingAggregationBackfillResponse: response object from the execution manager
50
+ """
51
+ try:
52
+ return self._feature_store_execution_service.TriggerStreamingAggregationBackfill(
53
+ TriggerStreamingAggregationBackfillRequest(backfill=backfill_ingestion)
54
+ )
55
+ except RpcError as e:
56
+ raise QwakException(
57
+ f"Failed to trigger streaming aggregation backfill job, error encountered {e}"
58
+ )
59
+
32
60
  def trigger_batch_backfill(
33
61
  self, batch_backfill_spec: BackfillSpec
34
62
  ) -> TriggerBackfillResponse:
@@ -0,0 +1,48 @@
1
+ import pathlib
2
+
3
+ from qwak.clients.feature_store.execution_management_client import (
4
+ ExecutionManagementClient,
5
+ )
6
+ from qwak.feature_store.feature_sets.streaming_backfill import StreamingBackfill
7
+
8
+
9
+ class StreamingAggregationBackfill:
10
+ def __init__(
11
+ self,
12
+ streaming_backfill: StreamingBackfill,
13
+ source_definition_path: pathlib.Path,
14
+ ):
15
+ """
16
+ Initialize the streaming aggregation backfill executor.
17
+
18
+ Args:
19
+ streaming_backfill (StreamingBackfill): Specification containing the
20
+ featureset name, time range, data sources, and transformation
21
+ source_definition_path (Path): Path to the Python file containing the backfill
22
+ definition. Required for locating UDF artifacts.
23
+ """
24
+ self._streaming_backfill = streaming_backfill
25
+ self._source_definition_path = source_definition_path
26
+
27
+ def trigger(self) -> str:
28
+ """
29
+ Triggers the streaming aggregation backfill execution.
30
+
31
+ Converts the backfill specification to proto format and sends it to
32
+ the execution manager to start the backfill job.
33
+
34
+ Returns:
35
+ str: The execution ID for tracking the backfill job status
36
+
37
+ Raises:
38
+ QwakException: If the execution manager request fails
39
+ """
40
+ backfill_proto = self._streaming_backfill._to_proto(
41
+ str(self._source_definition_path)
42
+ )
43
+
44
+ execution_client = ExecutionManagementClient()
45
+ response = execution_client.trigger_streaming_aggregation_backfill(
46
+ backfill_proto
47
+ )
48
+ return response.execution_id
@@ -2,7 +2,7 @@ import collections
2
2
  import functools
3
3
  import inspect
4
4
  from dataclasses import dataclass, field
5
- from datetime import datetime
5
+ from datetime import datetime, timezone
6
6
  from typing import TYPE_CHECKING, List, Optional, Tuple, Union
7
7
 
8
8
  from _qwak_proto.qwak.feature_store.features.execution_pb2 import (
@@ -21,6 +21,7 @@ from _qwak_proto.qwak.feature_store.sources.streaming_pb2 import (
21
21
  StreamingSource,
22
22
  StreamingSource as ProtoStreamingSource,
23
23
  )
24
+ from google.protobuf.timestamp_pb2 import Timestamp as ProtoTimestamp
24
25
  from qwak.clients.feature_store import FeatureRegistryClient
25
26
  from qwak.exceptions import QwakException
26
27
  from qwak.feature_store._common.artifact_utils import ArtifactSpec, ArtifactsUploader
@@ -34,7 +35,7 @@ from qwak.feature_store.feature_sets.metadata import (
34
35
  set_metadata_on_function,
35
36
  )
36
37
  from qwak.feature_store.feature_sets.streaming_backfill import (
37
- BackfillBatchDataSourceSpec,
38
+ BackfillDataSource,
38
39
  StreamingBackfill,
39
40
  )
40
41
  from qwak.feature_store.feature_sets.transformations import (
@@ -75,6 +76,7 @@ def feature_set(
75
76
  key: Optional[str] = None,
76
77
  auxiliary_sinks: List[BaseSink] = [],
77
78
  repository: Optional[str] = None,
79
+ backfill_max_timestamp: Optional[datetime] = None,
78
80
  ):
79
81
  """
80
82
  Creates a streaming feature set for the specified entity using the given streaming data sources.
@@ -110,6 +112,11 @@ def feature_set(
110
112
  """
111
113
 
112
114
  def decorator(function):
115
+ if isinstance(function, StreamingBackfill):
116
+ raise QwakException(
117
+ "Backfill can no longer be defined as a decorator on the feature set, it must be triggered after feature set creation."
118
+ )
119
+
113
120
  user_transformation = function()
114
121
  FeaturesetUtils.validate_base_featureset_decorator(
115
122
  user_transformation=user_transformation, entity=entity, key=key
@@ -120,10 +127,6 @@ def feature_set(
120
127
  offline_scheduling_policy=offline_scheduling_policy,
121
128
  )
122
129
 
123
- streaming_backfill: Optional[StreamingBackfill] = (
124
- StreamingBackfill.get_streaming_backfill_from_function(function=function)
125
- )
126
-
127
130
  fs_name = name or function.__name__
128
131
  streaming_feature_set = StreamingFeatureSet(
129
132
  name=fs_name,
@@ -150,7 +153,7 @@ def feature_set(
150
153
  online_cluster_template=getattr(
151
154
  function, _ONLINE_CLUSTER_SPEC, ClusterTemplate.SMALL
152
155
  ),
153
- backfill=streaming_backfill,
156
+ backfill_max_timestamp=backfill_max_timestamp,
154
157
  __instance_module_path__=inspect.stack()[1].filename,
155
158
  auxiliary_sinks=auxiliary_sinks,
156
159
  )
@@ -197,55 +200,68 @@ def execution_specification(
197
200
  @typechecked
198
201
  def backfill(
199
202
  *,
200
- start_date: datetime,
201
- end_date: datetime,
202
- data_sources: Union[List[str], List[BackfillBatchDataSourceSpec]],
203
- backfill_transformation: SparkSqlTransformation,
203
+ feature_set_name: str,
204
+ start_date: Optional[datetime],
205
+ end_date: Optional[datetime],
206
+ data_sources: Union[List[str], List[BackfillDataSource]],
204
207
  backfill_cluster_template: Optional[ClusterTemplate] = ClusterTemplate.SMALL,
205
208
  ):
206
209
  """
207
- Configures optional backfill specification for a streaming featureset. Currently available for streaming
210
+ Triggers a backfill execution for an existing streaming featureset. Currently available for streaming
208
211
  aggregation featuresets only.
209
212
 
210
- :param start_date: backfill start date
211
- :param end_date: backfill end date
212
- :param data_sources: a list of dict representations connecting each batch source name to its requested time range
213
- :param backfill_transformation: a backfill SQL transformation. Only SQL transformations are supported for backfill
214
- :param backfill_cluster_template: an optional cluster specification for the backfill job. Defaults to SMALL.
215
-
216
- Example:
217
-
218
- ... code-block:: python
219
-
220
- @streaming.feature_set(
221
- entity="users",
222
- data_sources=["users_registration_stream"],
223
- timestamp_column_name="reg_date"
224
- )
213
+ Args:
214
+ feature_set_name (str): Name of the FeatureSet to trigger a backfill for.
215
+ start_date (datetime): Backfill start date, on Streaming Aggregation Feature Sets,
216
+ needs to align with the FeatureSet tiles.
217
+ end_date (datetime): Backfill end date, on Streaming Aggregation Feature Sets,
218
+ needs to align with the FeatureSet tiles and be smaller than the Feature Set's backfill_max_timestamp.
219
+ data_sources (list[BackfillDataSource] | list[str]): A list of BackfillDataSource objects containing
220
+ batch source name and optional time range, or a list of batch source names (with no time range limits).
221
+ backfill_cluster_template (ClusterTemplate, optional): An optional cluster specification for the backfill job.
222
+ Defaults to SMALL.
223
+
224
+ Examples:
225
225
  @streaming.backfill(
226
- start_date=start_date=(2022,01,01,0,0,0),
227
- end_date=start_date=(2023,09,01,0,0,0),
228
- data_sources=[{"batch_backfill_source_name": BackfillSourceRange(start_date=(2023,01,01,0,0,0),
229
- end_date=(2023,08,01,0,0,0))}],
226
+ feature_set_name="user_streaming_agg_features",
227
+ start_date=datetime(2022,1,1,0,0,0),
228
+ end_date=datetime(2023,9,1,0,0,0),
229
+ data_sources=[BackfillDataSource(data_source_name="backfill_data_source",
230
+ start_datetime=datetime(2023,1,1,0,0,0),
231
+ end_datetime=datetime(2023,8,1,0,0,0))],
230
232
  backfill_cluster_template=ClusterTemplate.SMALL
231
- backfill_transformation=SparkSqlTransformation("SELECT user_id, reg_country, reg_date FROM backfill_data_source")
232
233
  )
233
- def user_streaming_features():
234
- return SparkSqlTransformation("SELECT user_id, reg_country, reg_date FROM data_source")
234
+ def backfill_transformation():
235
+ return SparkSqlTransformation("SELECT user_id, reg_country, reg_date FROM backfill_data_source")
235
236
  """
236
237
 
237
238
  def decorator(function):
238
- _validate_decorator_ordering(function)
239
- StreamingBackfill.set_streaming_backfill_on_function(
240
- function=function,
241
- start_date=start_date,
242
- end_date=end_date,
243
- data_sources=data_sources,
244
- backfill_transformation=backfill_transformation,
245
- backfill_cluster_template=backfill_cluster_template,
239
+ if isinstance(function, StreamingFeatureSet):
240
+ raise QwakException(
241
+ "Backfill can no longer be defined as a decorator on the feature set, it must be triggered after feature set creation."
242
+ )
243
+
244
+ backfill_transformation: SparkSqlTransformation = function()
245
+
246
+ if not isinstance(backfill_transformation, SparkSqlTransformation):
247
+ raise QwakException(
248
+ "Backfill must defined on a method returning a SparkSqlTransformation"
249
+ )
250
+
251
+ streaming_backfill = StreamingBackfill(
252
+ featureset_name=feature_set_name,
253
+ start_datetime=start_date,
254
+ end_datetime=end_date,
255
+ data_sources=StreamingBackfill._get_normalized_backfill_sources_spec(
256
+ data_sources
257
+ ),
258
+ transform=backfill_transformation,
259
+ cluster_template=backfill_cluster_template,
246
260
  )
247
261
 
248
- return function
262
+ functools.update_wrapper(streaming_backfill, backfill_transformation)
263
+
264
+ return streaming_backfill
249
265
 
250
266
  return decorator
251
267
 
@@ -316,7 +332,7 @@ class StreamingFeatureSet(BaseFeatureSet):
316
332
  offline_cluster_template: Optional[ClusterTemplate] = None
317
333
  online_cluster_template: Optional[ClusterTemplate] = None
318
334
  metadata: Optional[Metadata] = None
319
- backfill: Optional[StreamingBackfill] = None
335
+ backfill_max_timestamp: Optional[datetime] = None
320
336
  auxiliary_sinks: List[BaseSink] = field(default_factory=lambda: [])
321
337
 
322
338
  def __post_init__(self):
@@ -399,7 +415,6 @@ class StreamingFeatureSet(BaseFeatureSet):
399
415
  proto_featureset_type = self._get_streaming_aggregation_featureset_proto(
400
416
  artifact_url=artifact_url,
401
417
  streaming_sources=data_sources,
402
- feature_registry=feature_registry,
403
418
  initial_tile_size=maybe_initial_tile_size,
404
419
  )
405
420
 
@@ -453,10 +468,9 @@ class StreamingFeatureSet(BaseFeatureSet):
453
468
  "Auxiliary Sinks Are not supported in Streaming Aggregation Feature Sets"
454
469
  )
455
470
 
456
- # streaming backfill not yet supported for row-level streaming
457
- if self.backfill and not is_streaming_agg:
471
+ if self.backfill_max_timestamp and not is_streaming_agg:
458
472
  raise QwakException(
459
- "Streaming backfill is only supported for streaming aggregation feature sets at the moment"
473
+ "backfill_max_timestamp can only be set for Streaming Aggregation FeatureSet."
460
474
  )
461
475
 
462
476
  # Validate transformation is PySpark when multiple data sources are used
@@ -515,18 +529,29 @@ class StreamingFeatureSet(BaseFeatureSet):
515
529
  )
516
530
  raise QwakException(error_message_str)
517
531
 
518
- # Validate the backfill, if defined
519
- if self.backfill:
520
- self.backfill._validate_tile_size(initial_tile_size)
532
+ if not self.backfill_max_timestamp:
533
+ raise QwakException(
534
+ """
535
+ backfill_max_timestamp must be set for Streaming Aggregation FeatureSet.
536
+ Events earlier than this timestamp can only be processed by triggering backfill,
537
+ the Streaming job will not process events that are earlier than this timestamp.
538
+ """
539
+ )
540
+
541
+ self._validate_streaming_aggregation_backfill_max_timestamp()
521
542
 
522
543
  return initial_tile_size
523
544
 
524
- def _validate_streaming_aggregation_backfill(self):
545
+ def _validate_streaming_aggregation_backfill_max_timestamp(self):
525
546
  initial_tile_size, _ = StreamingFeatureSet._get_default_slide_period(
526
547
  self.transformation.windows
527
548
  )
528
549
 
529
- self.backfill._validate_tile_size(initial_tile_size)
550
+ if self.backfill_max_timestamp.timestamp() % initial_tile_size != 0:
551
+ raise QwakException(
552
+ f"Chosen backfill max timestamp is invalid,"
553
+ f" it has to be exactly dividable by slice size of {initial_tile_size} seconds."
554
+ )
530
555
 
531
556
  @staticmethod
532
557
  def _get_default_slide_period(
@@ -596,9 +621,12 @@ class StreamingFeatureSet(BaseFeatureSet):
596
621
  self,
597
622
  artifact_url: Optional[str],
598
623
  streaming_sources: List[StreamingSource],
599
- feature_registry: FeatureRegistryClient,
600
624
  initial_tile_size: int,
601
625
  ) -> ProtoFeatureSetType:
626
+ backfill_max_timestamp = ProtoTimestamp()
627
+ backfill_max_timestamp.FromDatetime(
628
+ self.backfill_max_timestamp.astimezone(timezone.utc)
629
+ )
602
630
  return ProtoFeatureSetType(
603
631
  streaming_aggregation_feature_set=ProtoStreamingAggregationFeatureSet(
604
632
  transformation=self.transformation._to_proto(
@@ -621,14 +649,7 @@ class StreamingFeatureSet(BaseFeatureSet):
621
649
  allowed_late_arrival_seconds=60 * 10,
622
650
  aggregations=self.transformation._get_aggregations_proto(),
623
651
  ),
624
- backfill_spec=(
625
- self.backfill._to_proto(
626
- feature_registry=feature_registry,
627
- original_instance_module_path=self.__instance_module_path__,
628
- featureset_name=self.name,
629
- )
630
- if self.backfill
631
- else None
632
- ),
652
+ backfill_spec=None,
653
+ backfill_max_timestamp=backfill_max_timestamp,
633
654
  )
634
655
  )
@@ -1,24 +1,18 @@
1
- from abc import ABC, abstractmethod
2
1
  from dataclasses import dataclass
3
2
  from datetime import datetime, timezone
4
3
  from typing import List, Optional, Set, Union
5
4
 
6
5
  from _qwak_proto.qwak.feature_store.features.execution_pb2 import (
7
- BackfillExecutionSpec as ProtoBackfillExecutionSpec,
6
+ ExecutionSpec as ProtoExecutionSpec,
8
7
  )
9
- from _qwak_proto.qwak.feature_store.features.feature_set_types_pb2 import (
10
- BackfillBatchDataSourceSpec as ProtoBackfillBatchDataSourceSpec,
11
- BackfillDataSourceSpec as ProtoBackfillDataSourceSpec,
12
- BackfillSpec as ProtoBackfillSpec,
13
- )
14
- from _qwak_proto.qwak.feature_store.sources.batch_pb2 import (
15
- BatchSource as ProtoBatchSource,
8
+ from _qwak_proto.qwak.execution.v1.streaming_aggregation_pb2 import (
9
+ StreamingAggregationBackfillIngestion as ProtoStreamingAggregationBackfillIngestion,
10
+ BackfillDataSource as ProtoBackfillDataSource,
11
+ TimeRange as ProtoTimeRange,
16
12
  )
17
13
  from google.protobuf.timestamp_pb2 import Timestamp as ProtoTimestamp
18
- from qwak.clients.feature_store import FeatureRegistryClient
19
14
  from qwak.exceptions import QwakException
20
15
  from qwak.feature_store._common.artifact_utils import ArtifactSpec, ArtifactsUploader
21
- from qwak.feature_store._common.feature_set_utils import get_batch_source_for_featureset
22
16
  from qwak.feature_store.feature_sets.execution_spec import ClusterTemplate
23
17
  from qwak.feature_store.feature_sets.transformations import SparkSqlTransformation
24
18
 
@@ -26,36 +20,15 @@ _BACKFILL_ = "_qwak_backfill_specification"
26
20
 
27
21
 
28
22
  @dataclass
29
- class DataSourceBackfillSpec(ABC):
23
+ class BackfillDataSource:
30
24
  data_source_name: str
31
-
32
- @abstractmethod
33
- def _to_proto(self, feature_registry: FeatureRegistryClient):
34
- pass
35
-
36
- @classmethod
37
- def _from_proto(cls, proto: ProtoBackfillDataSourceSpec):
38
- function_mapping = {"batch_data_source_spec": BackfillBatchDataSourceSpec}
39
-
40
- backfill_source_type: str = proto.WhichOneof("type")
41
-
42
- if backfill_source_type in function_mapping:
43
- function_class = function_mapping.get(backfill_source_type)
44
- return function_class._from_proto(proto)
45
-
46
- raise QwakException(
47
- f"Got unsupported backfill source type {backfill_source_type} for streaming backfill"
48
- )
49
-
50
-
51
- @dataclass
52
- class BackfillBatchDataSourceSpec(DataSourceBackfillSpec):
53
25
  start_datetime: Optional[datetime] = None
54
26
  end_datetime: Optional[datetime] = None
55
27
 
56
- def _to_proto(
57
- self, feature_registry: FeatureRegistryClient
58
- ) -> ProtoBackfillBatchDataSourceSpec:
28
+ def __post_init__(self):
29
+ self._validate()
30
+
31
+ def _to_proto(self) -> ProtoBackfillDataSource:
59
32
  start_timestamp: Optional[ProtoTimestamp] = None
60
33
  end_timestamp: Optional[ProtoTimestamp] = None
61
34
 
@@ -67,63 +40,94 @@ class BackfillBatchDataSourceSpec(DataSourceBackfillSpec):
67
40
  start_timestamp = ProtoTimestamp()
68
41
  start_timestamp.FromDatetime(self.start_datetime.astimezone(timezone.utc))
69
42
 
70
- proto_data_source: ProtoBatchSource = get_batch_source_for_featureset(
71
- batch_ds_name=self.data_source_name, feature_registry=feature_registry
72
- )
73
-
74
- return ProtoBackfillBatchDataSourceSpec(
75
- data_source=proto_data_source,
43
+ time_range = ProtoTimeRange(
76
44
  start_timestamp=start_timestamp,
77
45
  end_timestamp=end_timestamp,
78
46
  )
79
47
 
48
+ return ProtoBackfillDataSource(
49
+ data_source_name=self.data_source_name,
50
+ time_range=time_range,
51
+ )
52
+
80
53
  @classmethod
81
- def _from_proto(
82
- cls, proto: ProtoBackfillDataSourceSpec
83
- ) -> "BackfillBatchDataSourceSpec":
54
+ def _from_proto(cls, proto: ProtoBackfillDataSource) -> "BackfillDataSource":
84
55
  start_datetime: Optional[datetime] = None
85
56
  end_datetime: Optional[datetime] = None
86
57
 
87
- batch_backfill_spec: ProtoBackfillBatchDataSourceSpec = (
88
- proto.batch_data_source_spec
89
- )
58
+ time_range: ProtoTimeRange = proto.time_range
90
59
 
91
- proto_start_timestamp: ProtoTimestamp = batch_backfill_spec.start_timestamp
92
- proto_end_timestamp: ProtoTimestamp = batch_backfill_spec.end_timestamp
60
+ proto_start_timestamp: Optional[ProtoTimestamp] = (
61
+ time_range.start_timestamp if time_range.start_timestamp else None
62
+ )
63
+ proto_end_timestamp: Optional[ProtoTimestamp] = (
64
+ time_range.end_timestamp if time_range.end_timestamp else None
65
+ )
93
66
 
94
- start_datetime = datetime.fromtimestamp(
95
- proto_start_timestamp.seconds + proto_start_timestamp.nanos / 1e9
67
+ start_datetime = (
68
+ datetime.fromtimestamp(
69
+ proto_start_timestamp.seconds + proto_start_timestamp.nanos / 1e9
70
+ )
71
+ if proto_start_timestamp
72
+ else None
96
73
  )
97
74
 
98
- end_datetime = datetime.fromtimestamp(
99
- proto_end_timestamp.seconds + proto_end_timestamp.nanos / 1e9
75
+ end_datetime = (
76
+ datetime.fromtimestamp(
77
+ proto_end_timestamp.seconds + proto_end_timestamp.nanos / 1e9
78
+ )
79
+ if proto_end_timestamp
80
+ else None
100
81
  )
101
82
 
102
83
  return cls(
103
- data_source_name=batch_backfill_spec.data_source.name,
84
+ data_source_name=proto.data_source_name,
104
85
  start_datetime=start_datetime,
105
86
  end_datetime=end_datetime,
106
87
  )
107
88
 
89
+ def _validate(self):
90
+ if self.start_datetime and self.end_datetime:
91
+ if self.start_datetime >= self.end_datetime:
92
+ raise QwakException(
93
+ f"Backfill data source {self.data_source_name} has invalid time range: "
94
+ f"start_datetime {self.start_datetime} is after or equal end_datetime {self.end_datetime}."
95
+ )
96
+
97
+ if not self.data_source_name:
98
+ raise QwakException(
99
+ "Backfill data source must have a valid data source name."
100
+ )
101
+
108
102
 
109
103
  @dataclass
110
104
  class StreamingBackfill:
105
+ featureset_name: str
111
106
  start_datetime: datetime
112
107
  end_datetime: datetime
113
- data_sources_specs: List[DataSourceBackfillSpec]
108
+ data_sources: List[BackfillDataSource]
114
109
  transform: "SparkSqlTransformation"
115
110
  cluster_template: Optional[ClusterTemplate] = ClusterTemplate.SMALL
116
111
 
117
112
  def __post_init__(self):
118
- if not self.data_sources_specs:
113
+ if not self.featureset_name:
114
+ raise QwakException("featureset_name must be provided for backfill.")
115
+
116
+ if not self.start_datetime or not self.end_datetime:
119
117
  raise QwakException(
120
- "Trying to create a streaming backfill with no data sources. "
121
- "At least one data source has to be provided when trying to create a streaming backfill."
118
+ "For Streaming backfill, start_datetime and end_datetime are mandatory fields."
122
119
  )
123
120
 
124
- if not self.start_datetime or not self.end_datetime:
121
+ if self.start_datetime >= self.end_datetime:
125
122
  raise QwakException(
126
- "For backfill, start_datetime and end_datetime are mandatory fields."
123
+ f"Backfill has invalid time range: "
124
+ f"start_datetime {self.start_datetime} is after or equal end_datetime {self.end_datetime}."
125
+ )
126
+
127
+ if not self.data_sources:
128
+ raise QwakException(
129
+ "Trying to create a streaming backfill with no data sources. "
130
+ "At least one data source has to be provided when trying to create a streaming backfill."
127
131
  )
128
132
 
129
133
  if type(self.transform) is not SparkSqlTransformation:
@@ -135,7 +139,7 @@ class StreamingBackfill:
135
139
 
136
140
  def _validate_unique_sources(self):
137
141
  source_names: List[str] = [
138
- data_source.data_source_name for data_source in self.data_sources_specs
142
+ data_source.data_source_name for data_source in self.data_sources
139
143
  ]
140
144
  duplicates: Set[str] = {
141
145
  item for item in source_names if source_names.count(item) > 1
@@ -146,23 +150,14 @@ class StreamingBackfill:
146
150
  f"Found these duplicates: {', '.join(set(duplicates))}"
147
151
  )
148
152
 
149
- def _validate_tile_size(self, initial_tile_size: int):
150
- if self.end_datetime.timestamp() % initial_tile_size != 0:
151
- raise QwakException(
152
- f"Chosen backfill end datetime is invalid,"
153
- f" it has to be exactly dividable by slice size of {initial_tile_size} seconds."
154
- )
155
-
156
153
  def _to_proto(
157
154
  self,
158
- feature_registry: FeatureRegistryClient,
159
- featureset_name: str,
160
155
  original_instance_module_path: str,
161
- ) -> ProtoBackfillSpec:
156
+ ) -> ProtoStreamingAggregationBackfillIngestion:
162
157
  artifact_url: Optional[str] = None
163
158
  artifact_spec: Optional[ArtifactSpec] = ArtifactsUploader.get_artifact_spec(
164
159
  transformation=self.transform,
165
- featureset_name=f"{featureset_name}-backfill",
160
+ featureset_name=f"{self.featureset_name}-backfill",
166
161
  __instance_module_path__=original_instance_module_path,
167
162
  )
168
163
 
@@ -175,85 +170,54 @@ class StreamingBackfill:
175
170
  start_timestamp = ProtoTimestamp()
176
171
  start_timestamp.FromDatetime(self.start_datetime.astimezone(timezone.utc))
177
172
 
178
- return ProtoBackfillSpec(
173
+ return ProtoStreamingAggregationBackfillIngestion(
174
+ featureset_name=self.featureset_name,
179
175
  start_timestamp=start_timestamp,
180
176
  end_timestamp=end_timestamp,
181
- execution_spec=ProtoBackfillExecutionSpec(
182
- **{"cluster_template": ClusterTemplate.to_proto(self.cluster_template)}
177
+ execution_spec=ProtoExecutionSpec(
178
+ cluster_template=ClusterTemplate.to_proto(self.cluster_template)
183
179
  ),
184
180
  transformation=self.transform._to_proto(artifact_path=artifact_url),
185
181
  data_source_specs=[
186
- ProtoBackfillDataSourceSpec(
187
- batch_data_source_spec=data_source_spec._to_proto(
188
- feature_registry=feature_registry
189
- )
190
- )
191
- for data_source_spec in self.data_sources_specs
182
+ data_source._to_proto() for data_source in self.data_sources
192
183
  ],
193
184
  )
194
185
 
195
186
  @classmethod
196
- def _from_proto(cls, proto: ProtoBackfillSpec):
197
- datetime.fromtimestamp(
198
- proto.start_timestamp.seconds + proto.start_timestamp.nanos / 1e9
199
- )
200
-
201
- data_sources_specs = [
202
- BackfillBatchDataSourceSpec._from_proto(ds)
203
- for ds in proto.data_source_specs
187
+ def _from_proto(cls, proto: ProtoStreamingAggregationBackfillIngestion):
188
+ backfill_data_sources = [
189
+ BackfillDataSource._from_proto(ds) for ds in proto.data_source_specs
204
190
  ]
205
191
 
206
192
  return cls(
193
+ featureset_name=proto.featureset_name,
207
194
  start_datetime=datetime.fromtimestamp(
208
195
  proto.start_timestamp.seconds + proto.start_timestamp.nanos / 1e9
209
196
  ),
210
197
  end_datetime=datetime.fromtimestamp(
211
198
  proto.end_timestamp.seconds + proto.end_timestamp.nanos / 1e9
212
199
  ),
213
- data_sources_specs=data_sources_specs,
200
+ data_sources=backfill_data_sources,
214
201
  transform=SparkSqlTransformation._from_proto(
215
202
  proto.transformation.sql_transformation
216
203
  ),
204
+ cluster_template=(
205
+ ClusterTemplate.from_proto(proto.execution_spec.cluster_template)
206
+ if proto.execution_spec.cluster_template
207
+ else None
208
+ ),
217
209
  )
218
210
 
219
211
  @staticmethod
220
212
  def _get_normalized_backfill_sources_spec(
221
- data_sources: Union[List[str], List[DataSourceBackfillSpec]],
222
- ) -> List[DataSourceBackfillSpec]:
223
- # reformat all data source specs to 'DataSourceBackfillSpec'
213
+ data_sources: Union[List[str], List[BackfillDataSource]],
214
+ ) -> List[BackfillDataSource]:
215
+ # reformat all data source names to 'BackfillDataSource'
224
216
  return [
225
217
  (
226
- BackfillBatchDataSourceSpec(data_source_name=data_source)
218
+ BackfillDataSource(data_source_name=data_source)
227
219
  if isinstance(data_source, str)
228
220
  else data_source
229
221
  )
230
222
  for data_source in data_sources
231
223
  ]
232
-
233
- @classmethod
234
- def set_streaming_backfill_on_function(
235
- cls,
236
- function,
237
- start_date: datetime,
238
- end_date: datetime,
239
- data_sources: Union[List[str], List[DataSourceBackfillSpec]],
240
- backfill_transformation: SparkSqlTransformation,
241
- backfill_cluster_template: Optional[ClusterTemplate] = ClusterTemplate.SMALL,
242
- ):
243
- setattr(
244
- function,
245
- _BACKFILL_,
246
- cls(
247
- start_datetime=start_date,
248
- end_datetime=end_date,
249
- data_sources_specs=StreamingBackfill._get_normalized_backfill_sources_spec(
250
- data_sources
251
- ),
252
- transform=backfill_transformation,
253
- cluster_template=backfill_cluster_template,
254
- ),
255
- )
256
-
257
- @staticmethod
258
- def get_streaming_backfill_from_function(function):
259
- return getattr(function, _BACKFILL_, None)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: qwak-core
3
- Version: 0.5.4
3
+ Version: 0.5.12
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
@@ -511,8 +511,8 @@ _qwak_proto/qwak/model_descriptor/open_ai_descriptor_pb2_grpc.py,sha256=1oboBPFx
511
511
  _qwak_proto/qwak/model_group/model_group_pb2.py,sha256=oM2O7f-CGykrJ2HCePU1lONl-hJoJtpSLBlvdqrBNO4,5244
512
512
  _qwak_proto/qwak/model_group/model_group_pb2.pyi,sha256=YDCW1Oj16lzWku0xJ5YYPPmzPmrqOfMwG8vqeYxiyZg,9245
513
513
  _qwak_proto/qwak/model_group/model_group_pb2_grpc.py,sha256=OnYtTijqKbxC13VpOUKJavMHae7BMWRusAUp-nz8h_g,9256
514
- _qwak_proto/qwak/model_group/model_group_repository_details_pb2.py,sha256=4rV7Xy1yrQNrO-AfZvekccyAyRr5_ymvuBa2tawMxcc,3089
515
- _qwak_proto/qwak/model_group/model_group_repository_details_pb2.pyi,sha256=GgYhrSXu8L2RdEV0clooKgB9Gye3_no4q0YrMy8QrZw,5701
514
+ _qwak_proto/qwak/model_group/model_group_repository_details_pb2.py,sha256=_MMAM40q5l9Luubv40LEa-Uv4CCkbNmRt3wQ-916Zz0,3735
515
+ _qwak_proto/qwak/model_group/model_group_repository_details_pb2.pyi,sha256=qOjP2x_4qC0gZayL6bkACeZU0GSfkH_6wIuWYHsNOe8,7320
516
516
  _qwak_proto/qwak/model_group/model_group_repository_details_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
517
517
  _qwak_proto/qwak/models/models_pb2.py,sha256=w2jAVJioQN5Ck1XW9jelfsOqmFRryVjmFukD49I_zUQ,18771
518
518
  _qwak_proto/qwak/models/models_pb2.pyi,sha256=T08V38RgCNy2x6ZW4_ZZdK09dZs7TzwXEHaGtNMW0y8,42643
@@ -622,7 +622,7 @@ _qwak_proto/qwak/workspace/workspace_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXH
622
622
  _qwak_proto/qwak/workspace/workspace_service_pb2.py,sha256=93uNm83VVrkLG_XVsuBOBTPs4UJF91YD1xJTOEbRyig,9239
623
623
  _qwak_proto/qwak/workspace/workspace_service_pb2.pyi,sha256=nKKCHwnovZhsy8TSVmdz-Vtl0nviOOoX56HD-41Xo08,13726
624
624
  _qwak_proto/qwak/workspace/workspace_service_pb2_grpc.py,sha256=yKGuexxTBza99Ihe0DSTniV2ZSd_AG47inHenqfi890,27193
625
- qwak/__init__.py,sha256=eS6NWShAiOsKkqTwZM6VUxq-d8TXXV3EZwVwJDhnZsQ,585
625
+ qwak/__init__.py,sha256=eBEVeYrxbXaW9CHSWF1DsMseWHkw5gUHrSzHYBxiMmo,586
626
626
  qwak/automations/__init__.py,sha256=qFZRvCxUUn8gcxkJR0v19ulHW2oJ0x6-Rif7HiheDP4,1522
627
627
  qwak/automations/automation_executions.py,sha256=5MeH_epYYWb8NKXgAozwT_jPyyUDednBHG7izloi7RY,3228
628
628
  qwak/automations/automations.py,sha256=2Wyrgj2mW5VmJzTKAXj3vQM_svYCuq_Bsu3_Vu9SdR4,12732
@@ -671,7 +671,7 @@ qwak/clients/data_versioning/data_tag_filter.py,sha256=vJaZ-Zot4eYxrzonbkdEWWGXZ
671
671
  qwak/clients/deployment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
672
672
  qwak/clients/deployment/client.py,sha256=v8w-xEA7HeKBcsSobVkPdfQq_SLA9IbHo4qMJIAA5dM,6806
673
673
  qwak/clients/feature_store/__init__.py,sha256=mMCPBHDga6Y7dtJfNoHvfOvCyjNUHrVDX5uVsL2JkGk,53
674
- qwak/clients/feature_store/execution_management_client.py,sha256=tKLIML8wsvgsl1hBfx74izFL0rHYdNM69sEstomDfYM,4051
674
+ qwak/clients/feature_store/execution_management_client.py,sha256=P1nZnttIC3QNVUI_XD7K3ywRSIq6jtifa1FnPMvUtFg,5280
675
675
  qwak/clients/feature_store/job_registry_client.py,sha256=7VMtEj7aofKcABrYpldgdxyv8Vkdq_mocjns1O0uCqA,2635
676
676
  qwak/clients/feature_store/management_client.py,sha256=NTfuwV1SQdaVxxyCE-htz1zXOpJHAKEyiB3bnGUyCW4,18220
677
677
  qwak/clients/feature_store/offline_serving_client.py,sha256=gz8hqaboPA1Und8leOf1O0dXa9xorHDTU3b7-Ne9YSE,9344
@@ -775,6 +775,7 @@ qwak/feature_store/execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
775
775
  qwak/feature_store/execution/backfill.py,sha256=bzF_-ZrXEfS14-SfJ5ldI6CKjWVQQnlSsI-X4jI7VrU,6470
776
776
  qwak/feature_store/execution/execution.py,sha256=msFzcZamWTlb5pE8IslP6MmJMAqnmBXt94uSH70koIc,21243
777
777
  qwak/feature_store/execution/execution_query.py,sha256=B5KRU1jvI042kEQoKj7A3G8Hu80nd8orWf8cOD9hSm8,3564
778
+ qwak/feature_store/execution/streaming_backfill.py,sha256=wkQzK1SzZ4AAP35BSDMIiHGZk2mugGa5znRcQqXHIO8,1683
778
779
  qwak/feature_store/feature_sets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
779
780
  qwak/feature_store/feature_sets/_utils/_featureset_utils.py,sha256=AWnQBS8TrpRb_J7h4wQLOzq4D--GMfc2xkRElioOKBE,1636
780
781
  qwak/feature_store/feature_sets/backfill.py,sha256=70yEP3fLTt8ecNuxeBnFp-qp0OUJ53KkOQRsYv2QEzc,1979
@@ -784,8 +785,8 @@ qwak/feature_store/feature_sets/context.py,sha256=zV6r0O70cfM4pmxlfC6xxAtro-wBhe
784
785
  qwak/feature_store/feature_sets/execution_spec.py,sha256=SXlhgNJeFDIKvrH35ZOyjBqGUesdoxjWkPgHmm518Y0,1877
785
786
  qwak/feature_store/feature_sets/metadata.py,sha256=ckr-zr0hZgsK-tRWucMomF7Tj8XIYcp_cnNGwtMV4mA,1814
786
787
  qwak/feature_store/feature_sets/read_policies.py,sha256=ZVMRiducxfb5YbU0NQQDPb78BH5fRttsE5y2TL7Jaj4,6820
787
- qwak/feature_store/feature_sets/streaming.py,sha256=G0Prg6hyjrzimL0u7tA71G-Bq3mnn57ZZCneMfudL0Y,25161
788
- qwak/feature_store/feature_sets/streaming_backfill.py,sha256=NInPNnc0WYx2FDsjaOt5woTO7EtzgrZCbMB_Ull0ux0,9585
788
+ qwak/feature_store/feature_sets/streaming.py,sha256=4_fOOwagmOIRDruZZzNlpMDb6ElsXC3wYj3rlNP9zT0,26462
789
+ qwak/feature_store/feature_sets/streaming_backfill.py,sha256=dAkxZjmMdk1Oj0EFqFm-adwzw2XpURQ12I7bjbpV_2I,8280
789
790
  qwak/feature_store/feature_sets/transformations/__init__.py,sha256=TtzeMNhqxuYu8NIxqBCcGHJoA-B8389RTIHrDMvpMbw,857
790
791
  qwak/feature_store/feature_sets/transformations/aggregations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
791
792
  qwak/feature_store/feature_sets/transformations/aggregations/aggregations.py,sha256=MENQyuDEG5G4TQs7fd1L1FEfP4kWd0LaHbG61Kw4rbY,14117
@@ -1064,7 +1065,7 @@ qwak_services_mock/mocks/build_orchestrator_service_api.py,sha256=IlM6VzovSSV6o1
1064
1065
  qwak_services_mock/mocks/data_versioning_service.py,sha256=dUUrKwE3xc2bZtHa8YMg6wYn8zFa2BSZ5DaRsb1zT_Q,2453
1065
1066
  qwak_services_mock/mocks/deployment_management_service.py,sha256=T4fmFC1sUgEh5sXH6ilwyPmfRfEBFqyYrGGljl_Abg8,20609
1066
1067
  qwak_services_mock/mocks/ecosystem_service_api.py,sha256=f0-IeZMprFKvi1MvdW9aJZgO7BBdnJHQB8spDiVWy-A,1607
1067
- qwak_services_mock/mocks/execution_management_service.py,sha256=D7bmYfCFKnxo6_LAO2ogy5gNqFB-V84KtpZxaCXfczk,886
1068
+ qwak_services_mock/mocks/execution_management_service.py,sha256=L6uSHniFNaJDZUwRuMnt_-j6NmpJoOsD0RwpoI6w9ag,1190
1068
1069
  qwak_services_mock/mocks/feature_store_data_sources_manager_api.py,sha256=zfiq5x7xxg4IvBS1OndqSnFHDOckMW_-4K_Ajlko6no,5241
1069
1070
  qwak_services_mock/mocks/feature_store_entities_manager_api.py,sha256=I9wsVEsb0qVcD8jgekeC-EG-Ke8SziRLUND8uF4OJbc,3349
1070
1071
  qwak_services_mock/mocks/feature_store_feature_set_manager_api.py,sha256=TZIGeKGwOP45MbcnuSAoB2Gc28j_wzU9Vuu6Q757x-s,10918
@@ -1097,6 +1098,6 @@ qwak_services_mock/mocks/workspace_manager_service_mock.py,sha256=O9ZSwln4T4kHVk
1097
1098
  qwak_services_mock/services_mock.py,sha256=0BWwS2re9ryO3JrJJgSNyEQ0lDOjyrpV36oa8t7Pd7A,19163
1098
1099
  qwak_services_mock/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1099
1100
  qwak_services_mock/utils/service_utils.py,sha256=ZlB0CnB1J6oBn6_m7fQO2U8tKoboHdUa6ljjkRMYNXU,265
1100
- qwak_core-0.5.4.dist-info/METADATA,sha256=g7VkaPj5hngSVkBOSPOGivSYzCwiaapUnAqtWL6oHZQ,1986
1101
- qwak_core-0.5.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
1102
- qwak_core-0.5.4.dist-info/RECORD,,
1101
+ qwak_core-0.5.12.dist-info/METADATA,sha256=Idr0TViACsw1-5_03xRXvOcr9a8mg8njLW252T28YHQ,1987
1102
+ qwak_core-0.5.12.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
1103
+ qwak_core-0.5.12.dist-info/RECORD,,
@@ -1,7 +1,8 @@
1
- from _qwak_proto.qwak.execution.v1.execution_service_pb2 import TriggerBackfillResponse
1
+ from _qwak_proto.qwak.execution.v1.execution_service_pb2 import (TriggerBackfillResponse, TriggerStreamingAggregationBackfillResponse)
2
2
  from _qwak_proto.qwak.execution.v1.execution_service_pb2_grpc import (
3
3
  FeatureStoreExecutionServiceServicer,
4
4
  )
5
+
5
6
  from grpc import RpcError
6
7
 
7
8
 
@@ -23,3 +24,10 @@ class ExecutionManagementServiceMock(FeatureStoreExecutionServiceServicer):
23
24
  if self._raise_exception_on_request:
24
25
  raise RpcError
25
26
  return TriggerBackfillResponse(execution_id=self._execution_id)
27
+
28
+ def TriggerStreamingAggregationBackfill(self, request, context):
29
+ if self._raise_exception_on_request:
30
+ raise RpcError
31
+ return TriggerStreamingAggregationBackfillResponse(
32
+ execution_id=self._execution_id
33
+ )