qwak-core 0.4.246__py3-none-any.whl → 0.4.247__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.
- _qwak_proto/qwak/service_discovery/service_discovery_location_pb2.py +65 -0
- _qwak_proto/qwak/service_discovery/service_discovery_location_pb2.pyi +73 -0
- _qwak_proto/qwak/service_discovery/service_discovery_location_pb2_grpc.py +4 -0
- _qwak_proto/qwak/service_discovery/service_discovery_location_service_pb2.py +49 -0
- _qwak_proto/qwak/service_discovery/service_discovery_location_service_pb2.pyi +41 -0
- _qwak_proto/qwak/service_discovery/service_discovery_location_service_pb2_grpc.py +231 -0
- qwak/__init__.py +1 -1
- qwak/clients/feature_store/offline_serving_client.py +29 -4
- qwak/clients/location_discovery/__init__.py +1 -0
- qwak/clients/location_discovery/client.py +73 -0
- qwak/feature_store/_common/functions.py +0 -19
- qwak/feature_store/offline/__init__.py +1 -2
- {qwak_core-0.4.246.dist-info → qwak_core-0.4.247.dist-info}/METADATA +1 -1
- {qwak_core-0.4.246.dist-info → qwak_core-0.4.247.dist-info}/RECORD +18 -14
- qwak_services_mock/mocks/location_discovery_service_api.py +104 -0
- qwak_services_mock/mocks/qwak_mocks.py +4 -0
- qwak_services_mock/services_mock.py +13 -0
- qwak/feature_store/_common/featureset_asterisk_handler.py +0 -115
- qwak/feature_store/offline/_query_engine.py +0 -32
- qwak/feature_store/offline/athena/__init__.py +0 -0
- qwak/feature_store/offline/athena/athena_query_engine.py +0 -153
- qwak/feature_store/offline/client.py +0 -718
- {qwak_core-0.4.246.dist-info → qwak_core-0.4.247.dist-info}/WHEEL +0 -0
@@ -540,6 +540,12 @@ _qwak_proto/qwak/self_service/user/v1/user_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7E
|
|
540
540
|
_qwak_proto/qwak/self_service/user/v1/user_service_pb2.py,sha256=31GMuClpxHWHnFPXKxKlxLby-NofL9yHj4GULLJQ8ng,10389
|
541
541
|
_qwak_proto/qwak/self_service/user/v1/user_service_pb2.pyi,sha256=Y4BlejbLGPraMkujz9Du_uC4WwJdCUgWSPv1IEFTYKk,8148
|
542
542
|
_qwak_proto/qwak/self_service/user/v1/user_service_pb2_grpc.py,sha256=HPNgP5ejFRwdgF_eVoDqu2-wpG1odK_p5nyZ9iXTIL8,10512
|
543
|
+
_qwak_proto/qwak/service_discovery/service_discovery_location_pb2.py,sha256=KnfvXbH36EvzBtG0aWgYKy761JEsRkvgCdMhG5-Zq9Q,3329
|
544
|
+
_qwak_proto/qwak/service_discovery/service_discovery_location_pb2.pyi,sha256=33gciRiYabLn_gmN5Ksw5reequ4GkfUbGxFW4gionf4,2406
|
545
|
+
_qwak_proto/qwak/service_discovery/service_discovery_location_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
546
|
+
_qwak_proto/qwak/service_discovery/service_discovery_location_service_pb2.py,sha256=NkkenQmPrFSm9T9bji2vJtD5difVTdFE8pN5-2Z5gmM,3813
|
547
|
+
_qwak_proto/qwak/service_discovery/service_discovery_location_service_pb2.pyi,sha256=7lfas7n0SXrqcrjCjivcqPVEV8SOQhW8vEXbhvdVFeg,1357
|
548
|
+
_qwak_proto/qwak/service_discovery/service_discovery_location_service_pb2_grpc.py,sha256=LfpjLTdJ8lTJjErhDHgoM9ew-7c_FfXcnSqFEL2plVU,13684
|
543
549
|
_qwak_proto/qwak/traffic/v1/traffic_api_pb2.py,sha256=4KmYUxK7Gku8IddNOc7VCOjS89H5qtAv4eOMqAJ4v2o,6909
|
544
550
|
_qwak_proto/qwak/traffic/v1/traffic_api_pb2.pyi,sha256=tsh59fnAmLLsKzc5-dj9Jx8MDXJWukoNzJtvGjGQjfI,5186
|
545
551
|
_qwak_proto/qwak/traffic/v1/traffic_api_pb2_grpc.py,sha256=46oskdNbo8xUzu8QGNtqtvUL93zYuOsSHCVFY1e8TLM,8228
|
@@ -576,7 +582,7 @@ _qwak_proto/qwak/workspace/workspace_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXH
|
|
576
582
|
_qwak_proto/qwak/workspace/workspace_service_pb2.py,sha256=AB3C9S_AbOD7Nx1Ni4j1rW6PNtYTV1zjiqFQk-goQ74,21429
|
577
583
|
_qwak_proto/qwak/workspace/workspace_service_pb2.pyi,sha256=nKKCHwnovZhsy8TSVmdz-Vtl0nviOOoX56HD-41Xo08,13726
|
578
584
|
_qwak_proto/qwak/workspace/workspace_service_pb2_grpc.py,sha256=yKGuexxTBza99Ihe0DSTniV2ZSd_AG47inHenqfi890,27193
|
579
|
-
qwak/__init__.py,sha256=
|
585
|
+
qwak/__init__.py,sha256=J_KtOzJioSVXYmoJWJVbRcqfxi3x8qL7akMQxB_RlhQ,587
|
580
586
|
qwak/automations/__init__.py,sha256=qFZRvCxUUn8gcxkJR0v19ulHW2oJ0x6-Rif7HiheDP4,1522
|
581
587
|
qwak/automations/automation_executions.py,sha256=5MeH_epYYWb8NKXgAozwT_jPyyUDednBHG7izloi7RY,3228
|
582
588
|
qwak/automations/automations.py,sha256=3yx8e2v0uSKDnXbqyknasyEoQ5vxGni6K40Hbi1_zkk,12599
|
@@ -630,7 +636,7 @@ qwak/clients/feature_store/__init__.py,sha256=mMCPBHDga6Y7dtJfNoHvfOvCyjNUHrVDX5
|
|
630
636
|
qwak/clients/feature_store/execution_management_client.py,sha256=tKLIML8wsvgsl1hBfx74izFL0rHYdNM69sEstomDfYM,4051
|
631
637
|
qwak/clients/feature_store/job_registry_client.py,sha256=7VMtEj7aofKcABrYpldgdxyv8Vkdq_mocjns1O0uCqA,2635
|
632
638
|
qwak/clients/feature_store/management_client.py,sha256=UqFx_wVrDx0N7ArMmaLiBqAzpekqZgkmQlC9UHkze_M,20498
|
633
|
-
qwak/clients/feature_store/offline_serving_client.py,sha256=
|
639
|
+
qwak/clients/feature_store/offline_serving_client.py,sha256=gz8hqaboPA1Und8leOf1O0dXa9xorHDTU3b7-Ne9YSE,9344
|
634
640
|
qwak/clients/feature_store/operator_client.py,sha256=qChgvX8m7A_tffMIaRx22pLSt5YxAbWeHPO-67LrRKE,5811
|
635
641
|
qwak/clients/file_versioning/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
636
642
|
qwak/clients/file_versioning/client.py,sha256=ywOy9olB4mekjQQrKUufAbaC-3sWzQnqd3ChcwrAJzc,2505
|
@@ -644,6 +650,8 @@ qwak/clients/integration_management/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW
|
|
644
650
|
qwak/clients/integration_management/openai/openai_system_secret.py,sha256=y7NT-F-S50ol7CUcnrSK522xZr7k0lD7bYo9VlyZ1j8,2119
|
645
651
|
qwak/clients/kube_deployment_captain/__init__.py,sha256=rJUEEy3zNH0aTFyuO_UBexzaUKdjvwU9P2vV1MDj684,41
|
646
652
|
qwak/clients/kube_deployment_captain/client.py,sha256=g5nLf4l7A4SFpdfF8Q5KwEincSFiWTHKKEFKztE5QVw,9308
|
653
|
+
qwak/clients/location_discovery/__init__.py,sha256=sqGQ75YHFE6nvOcir38fykUUmAa6cFEIze8PJYgYWRc,44
|
654
|
+
qwak/clients/location_discovery/client.py,sha256=O-wjMGiSTJ5qGjD0VhofE6Mv7NqvmwCpRnJgzyxKg6I,2867
|
647
655
|
qwak/clients/logging_client/__init__.py,sha256=1OCHnigQBYThBwGbxCreYA0BgP0HcuLFzNEWd3Yxh-c,34
|
648
656
|
qwak/clients/logging_client/client.py,sha256=IdPa93JjlVm0B_Gdc4lH9LYHoVn5DjT_gIPaZ-W3Hdw,4906
|
649
657
|
qwak/clients/model_management/__init__.py,sha256=vjWVP8MjmK4_A70WOgJqa6x24AeLK-ABjGJtogGzw9w,43
|
@@ -685,8 +693,7 @@ qwak/feature_store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
685
693
|
qwak/feature_store/_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
686
694
|
qwak/feature_store/_common/artifact_utils.py,sha256=iIdz6EfFuE_yub6g5KXlOOMt7TA7pEuuLwjlzVSRpO8,1977
|
687
695
|
qwak/feature_store/_common/feature_set_utils.py,sha256=dhtPWnwv1nB3h3EA8FAYklx1qGkO_Dj1jecaBGNNpew,8634
|
688
|
-
qwak/feature_store/_common/
|
689
|
-
qwak/feature_store/_common/functions.py,sha256=z033VNAqGZak2oB7pUSS9iJrh9aMRblVGP3iLWCChko,1298
|
696
|
+
qwak/feature_store/_common/functions.py,sha256=kSNYJ7dy48NN09HG9asm4ibQh0JaCGcZYsRDjRWlUHE,659
|
690
697
|
qwak/feature_store/_common/packaging.py,sha256=eyTyjIB_C6wGHcdU7xR5u7-N2Ld98s4LPU_iZtcbRO0,8122
|
691
698
|
qwak/feature_store/_common/source_code_spec.py,sha256=OqlrGVtLvZNtJjycEyC5v5VCIfD12JkYcM2JbnR-x_k,1804
|
692
699
|
qwak/feature_store/_common/source_code_spec_factory.py,sha256=zDif0nlWs43T87-k2VCUYyQyR6O3vbCHYCOtVfLUI8g,1760
|
@@ -748,12 +755,8 @@ qwak/feature_store/feature_sets/transformations/functions/qwak_pandas.py,sha256=
|
|
748
755
|
qwak/feature_store/feature_sets/transformations/functions/schema.py,sha256=kuu8MZ3d2Y9DkmgPZCRgDb0ecsc8isFHQG1lFLPBR3Y,1156
|
749
756
|
qwak/feature_store/feature_sets/transformations/transformations.py,sha256=k10Tg64XOfvpL9pQPMEfkuXtMT5EYxZ9-xkTOoD1fpM,15328
|
750
757
|
qwak/feature_store/feature_sets/transformations/validations/validations_util.py,sha256=Nr7MyWTLxe9J5VwQYCPRpaQdsv5VmWsWLwPC3C-cwOk,3179
|
751
|
-
qwak/feature_store/offline/__init__.py,sha256=
|
758
|
+
qwak/feature_store/offline/__init__.py,sha256=v6cf_Ne02UxZpsxPCWna0cZBLyF2czN_iL4eO3XIPOI,96
|
752
759
|
qwak/feature_store/offline/_offline_serving_validations.py,sha256=a3Ga9-IPTS0TPf_ghjuQ9qumBCMKFaQBkWxdoawUr-s,1216
|
753
|
-
qwak/feature_store/offline/_query_engine.py,sha256=LBVejISK9daYYWUYclXuMifezfsuzF4JgBhp2YlHZf0,738
|
754
|
-
qwak/feature_store/offline/athena/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
755
|
-
qwak/feature_store/offline/athena/athena_query_engine.py,sha256=Z8Y46nf9-_TI7AXpS5aO57UTDQFnFvARislMQcwQb8A,5182
|
756
|
-
qwak/feature_store/offline/client.py,sha256=dGtCnqlhuL4y5T7AtbTj1Pzu78-8JR_O1wnrnKmozHE,28651
|
757
760
|
qwak/feature_store/offline/client_v2.py,sha256=MKIlVGohW7aMmCpBUKE9wP9X6gtU_pLVfOgnBn2Bp98,14393
|
758
761
|
qwak/feature_store/offline/feature_set_features.py,sha256=Eh_rPz_m12cAQvrhIF-xtA-ZGVKy1HlgKJdjNvsmPoo,739
|
759
762
|
qwak/feature_store/online/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -1035,11 +1038,12 @@ qwak_services_mock/mocks/integration_management_service.py,sha256=j5ew7epeG0-n-q
|
|
1035
1038
|
qwak_services_mock/mocks/internal_build_orchestrator_service.py,sha256=deupu0eGAepqwh8bbebCY3i1BavrXv_mTpRdktaENPk,1046
|
1036
1039
|
qwak_services_mock/mocks/job_registry_service_api.py,sha256=BEU8eH9yE0_Jrc792rdp_M2KtrXO97uB3hklmQZsUtI,2696
|
1037
1040
|
qwak_services_mock/mocks/kube_captain_service_api.py,sha256=raZEWAP3aOk-DeM9qVHMGl97AK-8_smiCYCo0Euj0zg,1583
|
1041
|
+
qwak_services_mock/mocks/location_discovery_service_api.py,sha256=sEy-FEZg1rQnGqmNiewH4azkxbj-riaoiQ4h8XjeA2o,3679
|
1038
1042
|
qwak_services_mock/mocks/logging_service.py,sha256=rkOud7CFAvb0xfP6WXf3kP541eXdYJK5Lv_Q_2I1Fek,7381
|
1039
1043
|
qwak_services_mock/mocks/model_management_service.py,sha256=H3bMTr7VLrGjZjIrjgrjBk3kNDy1UjQXOeUyL5eAL_o,4153
|
1040
1044
|
qwak_services_mock/mocks/project_manager_service.py,sha256=WzcYzN3O04kSpvuTwX0QIF0HCh_ggsV9-gMel_vQVuc,3090
|
1041
1045
|
qwak_services_mock/mocks/prompt_manager_service.py,sha256=tEhVUVwkvRy68r7MNX9V3V8ZBZ1ynqzr6v1zREMm0_s,9895
|
1042
|
-
qwak_services_mock/mocks/qwak_mocks.py,sha256=
|
1046
|
+
qwak_services_mock/mocks/qwak_mocks.py,sha256=abayoevvX2gzUftDef_SYlL2t44P0r_7IaVh4GM0Hx0,6142
|
1043
1047
|
qwak_services_mock/mocks/secret_service.py,sha256=BwNyhNSTmxHDACzrr7NHjZ0YchX3wHWuJkl1ioi0Aig,1406
|
1044
1048
|
qwak_services_mock/mocks/self_service_user_service.py,sha256=UK22V-wAc0zHScXCE0QKDO0TdbrHf_uk7tEEIM47MpI,1205
|
1045
1049
|
qwak_services_mock/mocks/system_secret_service.py,sha256=S8MfGGEex7bs0AvnAf8SbPOzE-YDfCvePT1TxXWzHRQ,1896
|
@@ -1049,9 +1053,9 @@ qwak_services_mock/mocks/utils/exception_handlers.py,sha256=Fhi9cx_qNytDnl4BtZmE
|
|
1049
1053
|
qwak_services_mock/mocks/vector_serving_api.py,sha256=7ZSmDJVdw7_ne7BxLLrmy8eZkzVCeZ508gfL51wpKeY,5722
|
1050
1054
|
qwak_services_mock/mocks/vectors_management_api.py,sha256=ePh8l3MlDGzs7E4NuPZzF5xK-bC9AdMgDaNZrq3FkHc,3594
|
1051
1055
|
qwak_services_mock/mocks/workspace_manager_service_mock.py,sha256=O9ZSwln4T4kHVkR_usXnDQtarTeNfffSMON0P6wbT4g,7740
|
1052
|
-
qwak_services_mock/services_mock.py,sha256=
|
1056
|
+
qwak_services_mock/services_mock.py,sha256=zXtHcX8a_acz7ynxuCBxxVpHpde7aAGjIn6Uw52LY1s,19593
|
1053
1057
|
qwak_services_mock/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1054
1058
|
qwak_services_mock/utils/service_utils.py,sha256=ZlB0CnB1J6oBn6_m7fQO2U8tKoboHdUa6ljjkRMYNXU,265
|
1055
|
-
qwak_core-0.4.
|
1056
|
-
qwak_core-0.4.
|
1057
|
-
qwak_core-0.4.
|
1059
|
+
qwak_core-0.4.247.dist-info/METADATA,sha256=R1THTra1pOBCXCvAxwZkPPIov3RUG4-KODuL00yyLTk,2150
|
1060
|
+
qwak_core-0.4.247.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
1061
|
+
qwak_core-0.4.247.dist-info/RECORD,,
|
@@ -0,0 +1,104 @@
|
|
1
|
+
from typing import Dict, Optional
|
2
|
+
|
3
|
+
import grpc
|
4
|
+
from _qwak_proto.qwak.service_discovery.service_discovery_location_pb2 import (
|
5
|
+
ServiceLocationDescriptor,
|
6
|
+
)
|
7
|
+
from _qwak_proto.qwak.service_discovery.service_discovery_location_service_pb2 import (
|
8
|
+
GetServingUrlRequestResponse,
|
9
|
+
)
|
10
|
+
from _qwak_proto.qwak.service_discovery.service_discovery_location_service_pb2_grpc import (
|
11
|
+
LocationDiscoveryServiceServicer,
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
class LocationDiscoveryServiceApiMock(LocationDiscoveryServiceServicer):
|
16
|
+
"""
|
17
|
+
Mock implementation of the LocationDiscoveryService for testing SDK behavior.
|
18
|
+
Allows setting mock responses and optional error codes for each endpoint.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self):
|
22
|
+
super().__init__()
|
23
|
+
self._responses: Dict[str, Optional[ServiceLocationDescriptor]] = {}
|
24
|
+
self._error_codes: Dict[str, grpc.StatusCode] = {}
|
25
|
+
|
26
|
+
def _set_mock(
|
27
|
+
self,
|
28
|
+
key: str,
|
29
|
+
response: Optional[ServiceLocationDescriptor],
|
30
|
+
error_code: grpc.StatusCode = grpc.StatusCode.NOT_FOUND,
|
31
|
+
):
|
32
|
+
self._responses[key] = response
|
33
|
+
self._error_codes[key] = error_code
|
34
|
+
|
35
|
+
def _handle(
|
36
|
+
self, key: str, context: grpc.ServicerContext
|
37
|
+
) -> GetServingUrlRequestResponse:
|
38
|
+
response = self._responses.get(key)
|
39
|
+
if response:
|
40
|
+
return GetServingUrlRequestResponse(location=response)
|
41
|
+
context.set_code(self._error_codes.get(key, grpc.StatusCode.NOT_FOUND))
|
42
|
+
context.set_details(f"No mock response set for {key}")
|
43
|
+
return GetServingUrlRequestResponse()
|
44
|
+
|
45
|
+
# Setters
|
46
|
+
def set_get_offline_serving_url_response(
|
47
|
+
self,
|
48
|
+
response: Optional[ServiceLocationDescriptor],
|
49
|
+
error_code: grpc.StatusCode = grpc.StatusCode.NOT_FOUND,
|
50
|
+
):
|
51
|
+
self._set_mock("offline", response, error_code)
|
52
|
+
|
53
|
+
def set_get_distribution_manager_url_response(
|
54
|
+
self,
|
55
|
+
response: Optional[ServiceLocationDescriptor],
|
56
|
+
error_code: grpc.StatusCode = grpc.StatusCode.NOT_FOUND,
|
57
|
+
):
|
58
|
+
self._set_mock("distribution", response, error_code)
|
59
|
+
|
60
|
+
def set_get_analytics_engine_url_response(
|
61
|
+
self,
|
62
|
+
response: Optional[ServiceLocationDescriptor],
|
63
|
+
error_code: grpc.StatusCode = grpc.StatusCode.NOT_FOUND,
|
64
|
+
):
|
65
|
+
self._set_mock("analytics", response, error_code)
|
66
|
+
|
67
|
+
def set_get_metrics_gateway_url_response(
|
68
|
+
self,
|
69
|
+
response: Optional[ServiceLocationDescriptor],
|
70
|
+
error_code: grpc.StatusCode = grpc.StatusCode.NOT_FOUND,
|
71
|
+
):
|
72
|
+
self._set_mock("metrics", response, error_code)
|
73
|
+
|
74
|
+
def set_get_features_operator_url_response(
|
75
|
+
self,
|
76
|
+
response: Optional[ServiceLocationDescriptor],
|
77
|
+
error_code: grpc.StatusCode = grpc.StatusCode.NOT_FOUND,
|
78
|
+
):
|
79
|
+
self._set_mock("features", response, error_code)
|
80
|
+
|
81
|
+
def set_get_hosting_gateway_url_response(
|
82
|
+
self,
|
83
|
+
response: Optional[ServiceLocationDescriptor],
|
84
|
+
error_code: grpc.StatusCode = grpc.StatusCode.NOT_FOUND,
|
85
|
+
):
|
86
|
+
self._set_mock("hosting", response, error_code)
|
87
|
+
|
88
|
+
def GetOfflineServingUrl(self, request, context):
|
89
|
+
return self._handle("offline", context)
|
90
|
+
|
91
|
+
def GetDistributionManagerUrl(self, request, context):
|
92
|
+
return self._handle("distribution", context)
|
93
|
+
|
94
|
+
def GetAnalyticsEngineUrl(self, request, context):
|
95
|
+
return self._handle("analytics", context)
|
96
|
+
|
97
|
+
def GetMetricsGatewayUrl(self, request, context):
|
98
|
+
return self._handle("metrics", context)
|
99
|
+
|
100
|
+
def GetFeaturesOperatorUrl(self, request, context):
|
101
|
+
return self._handle("features", context)
|
102
|
+
|
103
|
+
def GetHostingGatewayUrl(self, request, context):
|
104
|
+
return self._handle("hosting", context)
|
@@ -60,6 +60,9 @@ from qwak_services_mock.mocks.internal_build_orchestrator_service import (
|
|
60
60
|
)
|
61
61
|
from qwak_services_mock.mocks.job_registry_service_api import JobRegistryServiceApiMock
|
62
62
|
from qwak_services_mock.mocks.kube_captain_service_api import KubeCaptainServiceApiMock
|
63
|
+
from qwak_services_mock.mocks.location_discovery_service_api import (
|
64
|
+
LocationDiscoveryServiceApiMock,
|
65
|
+
)
|
63
66
|
from qwak_services_mock.mocks.logging_service import LoggingServiceApiMock
|
64
67
|
from qwak_services_mock.mocks.model_management_service import (
|
65
68
|
ModelsManagementServiceMock,
|
@@ -121,3 +124,4 @@ class QwakMocks:
|
|
121
124
|
system_secret_service: SystemSecretServiceMock
|
122
125
|
integration_management_service: IntegrationManagementServiceMock
|
123
126
|
prompt_manager_service: PromptManagerServiceMock
|
127
|
+
location_discovery_service: LocationDiscoveryServiceApiMock
|
@@ -106,6 +106,9 @@ from _qwak_proto.qwak.secret_service.secret_service_pb2_grpc import (
|
|
106
106
|
from _qwak_proto.qwak.self_service.user.v1.user_service_pb2_grpc import (
|
107
107
|
add_UserServiceServicer_to_server,
|
108
108
|
)
|
109
|
+
from _qwak_proto.qwak.service_discovery.service_discovery_location_service_pb2_grpc import (
|
110
|
+
add_LocationDiscoveryServiceServicer_to_server,
|
111
|
+
)
|
109
112
|
from _qwak_proto.qwak.vectors.v1.collection.collection_service_pb2_grpc import (
|
110
113
|
add_VectorCollectionServiceServicer_to_server,
|
111
114
|
)
|
@@ -175,6 +178,9 @@ from qwak_services_mock.mocks.internal_build_orchestrator_service import (
|
|
175
178
|
)
|
176
179
|
from qwak_services_mock.mocks.job_registry_service_api import JobRegistryServiceApiMock
|
177
180
|
from qwak_services_mock.mocks.kube_captain_service_api import KubeCaptainServiceApiMock
|
181
|
+
from qwak_services_mock.mocks.location_discovery_service_api import (
|
182
|
+
LocationDiscoveryServiceApiMock,
|
183
|
+
)
|
178
184
|
from qwak_services_mock.mocks.logging_service import LoggingServiceApiMock
|
179
185
|
from qwak_services_mock.mocks.model_management_service import (
|
180
186
|
ModelsManagementServiceMock,
|
@@ -235,6 +241,7 @@ def qwak_container():
|
|
235
241
|
file_versioning,
|
236
242
|
instance_template,
|
237
243
|
kube_deployment_captain,
|
244
|
+
location_discovery,
|
238
245
|
logging_client,
|
239
246
|
model_management,
|
240
247
|
project,
|
@@ -280,6 +287,7 @@ def qwak_container():
|
|
280
287
|
integration_manager_client,
|
281
288
|
system_secret_client,
|
282
289
|
prompt_manager_client,
|
290
|
+
location_discovery,
|
283
291
|
]
|
284
292
|
)
|
285
293
|
|
@@ -492,6 +500,11 @@ def attach_servicers(free_port, server):
|
|
492
500
|
PromptManagerServiceMock,
|
493
501
|
add_PromptManagerServiceServicer_to_server,
|
494
502
|
),
|
503
|
+
(
|
504
|
+
"location_discovery_service",
|
505
|
+
LocationDiscoveryServiceApiMock,
|
506
|
+
add_LocationDiscoveryServiceServicer_to_server,
|
507
|
+
),
|
495
508
|
("port", free_port, None),
|
496
509
|
],
|
497
510
|
)
|
@@ -1,115 +0,0 @@
|
|
1
|
-
import dataclasses
|
2
|
-
import itertools
|
3
|
-
import re
|
4
|
-
from typing import Callable, Dict, List
|
5
|
-
|
6
|
-
from qwak.feature_store.offline import OfflineClient
|
7
|
-
from qwak.model.schema_entities import BaseFeature, Entity, FeatureStoreInput
|
8
|
-
|
9
|
-
|
10
|
-
def is_asterisk(feature: BaseFeature) -> bool:
|
11
|
-
"""
|
12
|
-
Checks whether the given Feature is a FeatureStoreInput with an '*'
|
13
|
-
Args:
|
14
|
-
feature: input feature
|
15
|
-
Return
|
16
|
-
"""
|
17
|
-
p = "^[^.]+.\\*$"
|
18
|
-
# if this is not a FeatureStoreInput, no need to check
|
19
|
-
if isinstance(feature, FeatureStoreInput):
|
20
|
-
return bool(re.match(p, feature.name))
|
21
|
-
return False
|
22
|
-
|
23
|
-
|
24
|
-
def inflate_feature(feature: BaseFeature, offline_fs: OfflineClient) -> List:
|
25
|
-
"""
|
26
|
-
Inflates a Feature into a list. if the feature contains an '*', a list
|
27
|
-
a feature for each feature in the associate FeatureSet is returned. else -
|
28
|
-
a list containing the original feature is returned.
|
29
|
-
Args:
|
30
|
-
feature: input feature
|
31
|
-
offline_fs: OfflineFeatureStore to query
|
32
|
-
Return:
|
33
|
-
if an '*' exists, returns a List of FeatureStoreInuput for each feature in the feature set
|
34
|
-
else returns a list containing the feature.
|
35
|
-
|
36
|
-
"""
|
37
|
-
if is_asterisk(feature):
|
38
|
-
featureset_name = feature.name.lower().split(".")[0]
|
39
|
-
|
40
|
-
p = f"^{featureset_name}+.*$"
|
41
|
-
feature_names = [
|
42
|
-
feature.lower()
|
43
|
-
for feature in offline_fs.get_columns_by_feature_set(featureset_name)
|
44
|
-
if re.match(p, feature.lower())
|
45
|
-
]
|
46
|
-
return [dataclasses.replace(feature, name=name) for name in feature_names]
|
47
|
-
|
48
|
-
return [feature]
|
49
|
-
|
50
|
-
|
51
|
-
def unpack_asterisk_features(
|
52
|
-
features: List[BaseFeature],
|
53
|
-
feature_store_generator: Callable[[], OfflineClient] = lambda: OfflineClient(),
|
54
|
-
) -> List[BaseFeature]:
|
55
|
-
"""
|
56
|
-
Handles features with an '*'.
|
57
|
-
If a feature of type FeatureStoreInput that matches '<featureset_name>.*' exists,
|
58
|
-
transforms this feature into the list of features actually present in the FeatureSet.
|
59
|
-
Other features remain unchanged.
|
60
|
-
the original order is maintained, and the internal order (inside a FeatureStoreInput
|
61
|
-
that has an '*') is inferred from the order of columns returned from
|
62
|
-
OfflineFeatureStore.get_columns_by_feature_set(...)
|
63
|
-
Args:
|
64
|
-
features: List of features
|
65
|
-
feature_store_generator: A function that generates an instance of OfflineFeatureStore
|
66
|
-
If no feature with an '*' is found, the function is not called.
|
67
|
-
|
68
|
-
Returns:
|
69
|
-
the unpacked features
|
70
|
-
"""
|
71
|
-
asterisk_found = any([is_asterisk(f) for f in features])
|
72
|
-
if asterisk_found:
|
73
|
-
# at least 1 feature contains an asterisk, map each feature to
|
74
|
-
# a list of features (features with no '*' are mapped to a list of size 1), then flatten it.
|
75
|
-
offline_feature_store = feature_store_generator()
|
76
|
-
inflated_features = [
|
77
|
-
inflate_feature(feature, offline_feature_store) for feature in features
|
78
|
-
]
|
79
|
-
return list(itertools.chain.from_iterable(inflated_features))
|
80
|
-
|
81
|
-
return features
|
82
|
-
|
83
|
-
|
84
|
-
def unpack_asterisk_features_from_key_mapping(
|
85
|
-
key_to_features: Dict[str, List[str]],
|
86
|
-
feature_store_generator: Callable[[], OfflineClient] = lambda: OfflineClient(),
|
87
|
-
) -> dict:
|
88
|
-
"""
|
89
|
-
Handles features with an '*'.
|
90
|
-
If a feature name that matches '<featureset_name>.*' exists in one of the lists,
|
91
|
-
transforms this feature into the list of feature names actually present in the FeatureSet.
|
92
|
-
Other features remain unchanged.
|
93
|
-
the original order is maintained, and the internal order (inside a feature
|
94
|
-
that has an '*') is inferred from the order of columns returned from
|
95
|
-
OfflineFeatureStore.get_columns_by_feature_set(...)
|
96
|
-
Args:
|
97
|
-
key_to_features: dictionary of entity keys to requested features (same as in the offline store)
|
98
|
-
>>> key_to_features = {'uuid': ['user_purchases.*',
|
99
|
-
>>> 'user_purchases.avg_purchase_amount']}
|
100
|
-
feature_store_generator: A function that generates an instance of OfflineFeatureStore
|
101
|
-
If no feature with an '*' is found, the function is not called.
|
102
|
-
Returns:
|
103
|
-
the unpacked features
|
104
|
-
"""
|
105
|
-
inflated_key_to_features = {}
|
106
|
-
|
107
|
-
for entity, features in key_to_features.items():
|
108
|
-
feature_inputs = [
|
109
|
-
FeatureStoreInput(name=feature, entity=Entity(name=entity, type=str))
|
110
|
-
for feature in features
|
111
|
-
]
|
112
|
-
unpacked = unpack_asterisk_features(feature_inputs, feature_store_generator)
|
113
|
-
feature_list = [feature.name for feature in unpacked]
|
114
|
-
inflated_key_to_features[entity] = feature_list
|
115
|
-
return inflated_key_to_features
|
@@ -1,32 +0,0 @@
|
|
1
|
-
import uuid
|
2
|
-
from abc import ABC, abstractmethod
|
3
|
-
|
4
|
-
import pandas as pd
|
5
|
-
|
6
|
-
|
7
|
-
class BaseQueryEngine(ABC):
|
8
|
-
@abstractmethod
|
9
|
-
def upload_table(self, df: pd.DataFrame):
|
10
|
-
pass
|
11
|
-
|
12
|
-
@abstractmethod
|
13
|
-
def cleanup(self):
|
14
|
-
pass
|
15
|
-
|
16
|
-
@abstractmethod
|
17
|
-
def run_query(self, query: str):
|
18
|
-
pass
|
19
|
-
|
20
|
-
@abstractmethod
|
21
|
-
def read_pandas_from_query(self, query: str, parse_dates=None):
|
22
|
-
pass
|
23
|
-
|
24
|
-
@staticmethod
|
25
|
-
@abstractmethod
|
26
|
-
def get_quotes():
|
27
|
-
pass
|
28
|
-
|
29
|
-
class JoinTableSpec:
|
30
|
-
def __init__(self, join_tables_db_name: str, quotes: str):
|
31
|
-
self.table_name = str(uuid.uuid4())
|
32
|
-
self.join_table_full_path = f"{quotes}{join_tables_db_name}{quotes}.{quotes}{self.table_name}{quotes}"
|
File without changes
|
@@ -1,153 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
import uuid
|
3
|
-
|
4
|
-
import pandas as pd
|
5
|
-
from _qwak_proto.qwak.ecosystem.v0.ecosystem_runtime_service_pb2 import (
|
6
|
-
GetCloudCredentialsParameters,
|
7
|
-
GetCloudCredentialsRequest,
|
8
|
-
OfflineFeatureStoreClient,
|
9
|
-
PermissionSet,
|
10
|
-
)
|
11
|
-
from google.protobuf.duration_pb2 import Duration
|
12
|
-
from qwak.clients.administration.eco_system.client import EcosystemClient
|
13
|
-
from qwak.exceptions import QwakException
|
14
|
-
from qwak.feature_store.offline._query_engine import BaseQueryEngine
|
15
|
-
|
16
|
-
RECONNECT_THRESHOLD_SEC = 300
|
17
|
-
|
18
|
-
|
19
|
-
class AthenaQueryEngine(BaseQueryEngine):
|
20
|
-
def __init__(self):
|
21
|
-
eco_client = EcosystemClient()
|
22
|
-
self.bucket, environment_id = self._get_env_details(eco_client)
|
23
|
-
|
24
|
-
self.staging_folder_prefix = (
|
25
|
-
f"{environment_id}/tmp/offline_fs/{str(uuid.uuid4())}" # nosec B108
|
26
|
-
)
|
27
|
-
self.temp_join_table_base_folder = (
|
28
|
-
f"s3://{self.bucket}/{self.staging_folder_prefix}"
|
29
|
-
)
|
30
|
-
|
31
|
-
self.conn, self.expiration_time = self._init_connection()
|
32
|
-
self.cursor = self.conn.cursor()
|
33
|
-
|
34
|
-
self.join_table_specs = []
|
35
|
-
|
36
|
-
self.join_tables_db_name = f"qwak_temp_data_{environment_id.replace('-', '_')}"
|
37
|
-
self.cursor.execute(f"CREATE DATABASE IF NOT EXISTS {self.join_tables_db_name}")
|
38
|
-
|
39
|
-
@staticmethod
|
40
|
-
def _get_env_details(eco_client):
|
41
|
-
environment_configuration = eco_client.get_environment_configuration()
|
42
|
-
|
43
|
-
return (
|
44
|
-
environment_configuration.configuration.object_storage_bucket,
|
45
|
-
environment_configuration.id,
|
46
|
-
)
|
47
|
-
|
48
|
-
def _init_connection(self):
|
49
|
-
try:
|
50
|
-
# obtain credentials through STS
|
51
|
-
eco_client = EcosystemClient()
|
52
|
-
cloud_credentials_response = eco_client.get_cloud_credentials(
|
53
|
-
request=GetCloudCredentialsRequest(
|
54
|
-
parameters=GetCloudCredentialsParameters(
|
55
|
-
duration=Duration(seconds=60 * 60, nanos=0),
|
56
|
-
permission_set=PermissionSet(
|
57
|
-
offline_feature_store_client=OfflineFeatureStoreClient()
|
58
|
-
),
|
59
|
-
)
|
60
|
-
)
|
61
|
-
)
|
62
|
-
|
63
|
-
aws_credentials = (
|
64
|
-
cloud_credentials_response.cloud_credentials.aws_temporary_credentials
|
65
|
-
)
|
66
|
-
|
67
|
-
try:
|
68
|
-
from pyathena import connect
|
69
|
-
from pyathena.pandas.cursor import PandasCursor
|
70
|
-
except ImportError:
|
71
|
-
raise QwakException(
|
72
|
-
"""
|
73
|
-
Missing 'pyathena' dependency required for fetching data from the offline store.
|
74
|
-
Please pip install pyathena
|
75
|
-
"""
|
76
|
-
)
|
77
|
-
|
78
|
-
conn = connect(
|
79
|
-
s3_staging_dir=self.temp_join_table_base_folder,
|
80
|
-
aws_access_key_id=aws_credentials.access_key_id,
|
81
|
-
aws_secret_access_key=aws_credentials.secret_access_key,
|
82
|
-
aws_session_token=aws_credentials.session_token,
|
83
|
-
region_name=aws_credentials.region,
|
84
|
-
cursor_class=PandasCursor,
|
85
|
-
)
|
86
|
-
|
87
|
-
return (
|
88
|
-
conn,
|
89
|
-
aws_credentials.expiration_time.seconds,
|
90
|
-
)
|
91
|
-
|
92
|
-
except QwakException as e:
|
93
|
-
raise e
|
94
|
-
|
95
|
-
except Exception as e:
|
96
|
-
raise QwakException(
|
97
|
-
f"Got an error trying to retrieve credentials to query the offline store "
|
98
|
-
f"in the cloud, error is: {e}"
|
99
|
-
)
|
100
|
-
|
101
|
-
def upload_table(self, df: pd.DataFrame):
|
102
|
-
join_table_spec = super().JoinTableSpec(
|
103
|
-
self.join_tables_db_name, AthenaQueryEngine.get_quotes()
|
104
|
-
)
|
105
|
-
self.join_table_specs.append(join_table_spec)
|
106
|
-
|
107
|
-
from pyathena.pandas.util import to_sql
|
108
|
-
|
109
|
-
to_sql(
|
110
|
-
df,
|
111
|
-
join_table_spec.table_name,
|
112
|
-
self.conn,
|
113
|
-
f"{self.temp_join_table_base_folder}/{join_table_spec.table_name}/",
|
114
|
-
schema=self.join_tables_db_name,
|
115
|
-
index=False,
|
116
|
-
if_exists="replace",
|
117
|
-
)
|
118
|
-
|
119
|
-
return join_table_spec.join_table_full_path
|
120
|
-
|
121
|
-
def run_query(self, query: str):
|
122
|
-
self._check_reconnection()
|
123
|
-
return self.cursor.execute(query).fetchall()
|
124
|
-
|
125
|
-
def read_pandas_from_query(self, query: str, parse_dates=None):
|
126
|
-
self._check_reconnection()
|
127
|
-
return pd.read_sql(
|
128
|
-
query,
|
129
|
-
self.conn,
|
130
|
-
parse_dates=parse_dates,
|
131
|
-
)
|
132
|
-
|
133
|
-
def _check_reconnection(self):
|
134
|
-
if self.expiration_time - time.time() < RECONNECT_THRESHOLD_SEC:
|
135
|
-
self.conn, self.expiration_time = self._init_connection()
|
136
|
-
self.cursor = self.conn.cursor()
|
137
|
-
|
138
|
-
def cleanup(self):
|
139
|
-
self._check_reconnection()
|
140
|
-
for join_table_spec in self.join_table_specs:
|
141
|
-
self.cursor.execute(
|
142
|
-
f"""DROP TABLE {join_table_spec.join_table_full_path.replace('"', '`')}"""
|
143
|
-
)
|
144
|
-
|
145
|
-
self.join_table_specs = []
|
146
|
-
|
147
|
-
s3 = self.conn.session.resource("s3")
|
148
|
-
bucket = s3.Bucket(self.bucket)
|
149
|
-
bucket.objects.filter(Prefix=self.staging_folder_prefix).delete()
|
150
|
-
|
151
|
-
@staticmethod
|
152
|
-
def get_quotes():
|
153
|
-
return '"'
|