Content-Length: 1017656 | pFad | https://github.com/googleapis/python-aiplatform/commit/f2233ceebef523fe7ed9e3a93a3c94a109e8e448

84 feat: Add FeatureMonitor to FeatureGroup in Vertex AI SDK · googleapis/python-aiplatform@f2233ce · GitHub
Skip to content

Commit f2233ce

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat: Add FeatureMonitor to FeatureGroup in Vertex AI SDK
PiperOrigin-RevId: 696595950
1 parent fde1b96 commit f2233ce

File tree

9 files changed

+304
-0
lines changed

9 files changed

+304
-0
lines changed

google/cloud/aiplatform/compat/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,9 @@
226226
types.explanation_metadata = types.explanation_metadata_v1
227227
types.feature = types.feature_v1
228228
types.feature_group = types.feature_group_v1
229+
# TODO(b/293184410): Temporary code. Switch to v1 once v1 is available.
230+
types.feature_monitor = types.feature_monitor_v1beta1
231+
types.feature_monitor_job = types.feature_monitor_job_v1beta1
229232
types.feature_monitoring_stats = types.feature_monitoring_stats_v1
230233
types.feature_online_store = types.feature_online_store_v1
231234
types.feature_online_store_admin_service = (

google/cloud/aiplatform/utils/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,12 @@ def parse_feature_path(path: str) -> Dict[str, str]:
763763
)
764764

765765

766+
class FeatureRegistryClientV1Beta1WithOverride(FeatureRegistryClientWithOverride):
767+
"""Adds function override for v1beta1 client classes to support new Feature Store."""
768+
769+
_default_version = compat.V1BETA1
770+
771+
766772
class FeaturestoreClientWithOverride(ClientWithOverride):
767773
_is_temporary = True
768774
_default_version = compat.DEFAULT_VERSION

tests/unit/vertexai/conftest.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
from google.cloud.aiplatform.compat.services import (
3535
feature_registry_service_client,
3636
)
37+
from google.cloud.aiplatform_v1beta1.services.feature_registry_service import (
38+
FeatureRegistryServiceClient,
39+
)
3740
from google.cloud.aiplatform.compat.services import job_service_client
3841
from google.cloud.aiplatform.compat.types import (
3942
custom_job as gca_custom_job_compat,
@@ -59,6 +62,7 @@
5962
_TEST_FG1,
6063
_TEST_FG1_F1,
6164
_TEST_FG1_F2,
65+
_TEST_FG1_FM1,
6266
_TEST_FV1,
6367
_TEST_OPTIMIZED_EMBEDDING_FV,
6468
_TEST_OPTIMIZED_FV1,
@@ -67,6 +71,7 @@
6771
)
6872
import pytest
6973

74+
7075
_TEST_PROJECT = "test-project"
7176
_TEST_PROJECT_NUMBER = "12345678"
7277
_TEST_LOCATION = "us-central1"
@@ -519,3 +524,13 @@ def get_feature_with_version_column_mock():
519524
) as get_fg_mock:
520525
get_fg_mock.return_value = _TEST_FG1_F2
521526
yield get_fg_mock
527+
528+
529+
@pytest.fixture
530+
def get_feature_monitor_mock():
531+
with patch.object(
532+
FeatureRegistryServiceClient,
533+
"get_feature_monitor",
534+
) as get_fg_mock:
535+
get_fg_mock.return_value = _TEST_FG1_FM1
536+
yield get_fg_mock

tests/unit/vertexai/feature_store_constants.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,15 @@
352352
)
353353

354354
_TEST_FG1_FEATURE_LIST = [_TEST_FG1_F1, _TEST_FG1_F2]
355+
356+
_TEST_FG1_FM1_ID = "my_fg1_fm1"
357+
_TEST_FG1_FM1_PATH = (
358+
f"{_TEST_PARENT}/featureGroups/{_TEST_FG1_ID}/featureMonitors/{_TEST_FG1_FM1_ID}"
359+
)
360+
_TEST_FG1_FM1_DESCRIPTION = "My feature monitor 1 in feature group 1"
361+
_TEST_FG1_FM1_LABELS = {"my_fg1_feature_monitor": "fm1"}
362+
_TEST_FG1_FM1 = types.feature_monitor.FeatureMonitor(
363+
name=_TEST_FG1_FM1_PATH,
364+
description=_TEST_FG1_FM1_DESCRIPTION,
365+
labels=_TEST_FG1_FM1_LABELS,
366+
)
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright 2024 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
import re
19+
from typing import Dict
20+
21+
from google.cloud import aiplatform
22+
from google.cloud.aiplatform import base
23+
from feature_store_constants import _TEST_FG1_FM1_DESCRIPTION
24+
from feature_store_constants import _TEST_FG1_FM1_ID
25+
from feature_store_constants import _TEST_FG1_FM1_LABELS
26+
from feature_store_constants import _TEST_FG1_FM1_PATH
27+
from feature_store_constants import _TEST_FG1_ID
28+
from feature_store_constants import _TEST_LOCATION
29+
from feature_store_constants import _TEST_PROJECT
30+
from vertexai.resources.preview import FeatureMonitor
31+
import pytest
32+
33+
34+
pytestmark = pytest.mark.usefixtures("google_auth_mock")
35+
36+
37+
def feature_monitor_eq(
38+
feature_monitor_to_check: FeatureMonitor,
39+
name: str,
40+
resource_name: str,
41+
project: str,
42+
location: str,
43+
description: str,
44+
labels: Dict[str, str],
45+
):
46+
"""Check if a Feature Monitor has the appropriate values set."""
47+
assert feature_monitor_to_check.name == name
48+
assert feature_monitor_to_check.resource_name == resource_name
49+
assert feature_monitor_to_check.project == project
50+
assert feature_monitor_to_check.location == location
51+
assert feature_monitor_to_check.description == description
52+
assert feature_monitor_to_check.labels == labels
53+
54+
55+
def test_init_with_feature_monitor_id_and_no_fg_id_raises_error():
56+
aiplatform.init(project=_TEST_PROJECT, location=_TEST_LOCATION)
57+
58+
with pytest.raises(
59+
ValueError,
60+
match=re.escape(
61+
"Since feature monitor 'my_fg1_fm1' is not provided as a path, please"
62+
" specify feature_group_id."
63+
),
64+
):
65+
FeatureMonitor(_TEST_FG1_FM1_ID)
66+
67+
68+
def test_init_with_feature_monitor_path_and_fg_id_raises_error():
69+
aiplatform.init(project=_TEST_PROJECT, location=_TEST_LOCATION)
70+
71+
with pytest.raises(
72+
ValueError,
73+
match=re.escape(
74+
"Since feature monitor 'projects/test-project/locations/us-central1/"
75+
"featureGroups/my_fg1/featureMonitors/my_fg1_fm1' is provided as a "
76+
"path, feature_group_id should not be specified."
77+
),
78+
):
79+
FeatureMonitor(_TEST_FG1_FM1_PATH, feature_group_id=_TEST_FG1_ID)
80+
81+
82+
def test_init_with_feature_monitor_id(get_feature_monitor_mock):
83+
aiplatform.init(project=_TEST_PROJECT, location=_TEST_LOCATION)
84+
85+
feature_monitor = FeatureMonitor(_TEST_FG1_FM1_ID, feature_group_id=_TEST_FG1_ID)
86+
87+
get_feature_monitor_mock.assert_called_once_with(
88+
name=_TEST_FG1_FM1_PATH,
89+
retry=base._DEFAULT_RETRY,
90+
)
91+
92+
feature_monitor_eq(
93+
feature_monitor,
94+
name=_TEST_FG1_FM1_ID,
95+
resource_name=_TEST_FG1_FM1_PATH,
96+
project=_TEST_PROJECT,
97+
location=_TEST_LOCATION,
98+
description=_TEST_FG1_FM1_DESCRIPTION,
99+
labels=_TEST_FG1_FM1_LABELS,
100+
)
101+
102+
103+
def test_init_with_feature_path(get_feature_monitor_mock):
104+
aiplatform.init(project=_TEST_PROJECT, location=_TEST_LOCATION)
105+
106+
feature_monitor = FeatureMonitor(_TEST_FG1_FM1_PATH)
107+
108+
get_feature_monitor_mock.assert_called_once_with(
109+
name=_TEST_FG1_FM1_PATH,
110+
retry=base._DEFAULT_RETRY,
111+
)
112+
113+
feature_monitor_eq(
114+
feature_monitor,
115+
name=_TEST_FG1_FM1_ID,
116+
resource_name=_TEST_FG1_FM1_PATH,
117+
project=_TEST_PROJECT,
118+
location=_TEST_LOCATION,
119+
description=_TEST_FG1_FM1_DESCRIPTION,
120+
labels=_TEST_FG1_FM1_LABELS,
121+
)

vertexai/resources/preview/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
Feature,
4141
FeatureGroup,
4242
FeatureGroupBigQuerySource,
43+
FeatureMonitor,
4344
FeatureOnlineStore,
4445
FeatureOnlineStoreType,
4546
FeatureView,
@@ -70,6 +71,7 @@
7071
"Feature",
7172
"FeatureGroup",
7273
"FeatureGroupBigQuerySource",
74+
"FeatureMonitor",
7375
"FeatureOnlineStoreType",
7476
"FeatureOnlineStore",
7577
"FeatureView",

vertexai/resources/preview/feature_store/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
FeatureGroup,
2525
)
2626

27+
from vertexai.resources.preview.feature_store.feature_monitor import (
28+
FeatureMonitor,
29+
)
30+
2731
from vertexai.resources.preview.feature_store.feature_online_store import (
2832
FeatureOnlineStore,
2933
FeatureOnlineStoreType,
@@ -48,6 +52,7 @@
4852
Feature,
4953
FeatureGroup,
5054
FeatureGroupBigQuerySource,
55+
FeatureMonitor,
5156
FeatureOnlineStoreType,
5257
FeatureOnlineStore,
5358
FeatureView,

vertexai/resources/preview/feature_store/feature_group.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
from vertexai.resources.preview.feature_store import (
3737
Feature,
3838
)
39+
from vertexai.resources.preview.feature_store.feature_monitor import (
40+
FeatureMonitor,
41+
)
3942

4043

4144
_LOGGER = base.Logger(__name__)
@@ -398,6 +401,33 @@ def list_features(
398401
credentials=credentials,
399402
)
400403

404+
def get_feature_monitor(
405+
self,
406+
feature_monitor_id: str,
407+
credentials: Optional[auth_credentials.Credentials] = None,
408+
) -> FeatureMonitor:
409+
"""Retrieves an existing feature monitor.
410+
411+
Args:
412+
feature_monitor_id: The ID of the feature monitor.
413+
credentials:
414+
Custom credentials to use to retrieve the feature monitor under this
415+
feature group. The order of which credentials are used is as
416+
follows: (1) this parameter (2) credentials passed to FeatureGroup
417+
constructor (3) credentials set in aiplatform.init.
418+
419+
Returns:
420+
FeatureMonitor - the Feature Monitor resource object under this
421+
feature group.
422+
"""
423+
credentials = (
424+
credentials or self.credentials or initializer.global_config.credentials
425+
)
426+
return FeatureMonitor(
427+
f"{self.resource_name}/featureMonitors/{feature_monitor_id}",
428+
credentials=credentials,
429+
)
430+
401431
@property
402432
def source(self) -> FeatureGroupBigQuerySource:
403433
return FeatureGroupBigQuerySource(
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright 2024 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
import re
19+
from typing import Optional
20+
from google.auth import credentials as auth_credentials
21+
from google.cloud.aiplatform import base
22+
from google.cloud.aiplatform import utils
23+
from google.cloud.aiplatform.compat.types import (
24+
feature_monitor_v1beta1 as gca_feature_monitor,
25+
)
26+
27+
28+
class FeatureMonitor(base.VertexAiResourceNounWithFutureManager):
29+
"""Class for managing Feature Monitor resources."""
30+
31+
client_class = utils.FeatureRegistryClientV1Beta1WithOverride
32+
33+
_resource_noun = "feature_monitors"
34+
_getter_method = "get_feature_monitor"
35+
_list_method = "list_feature_monitors"
36+
_delete_method = "delete_feature_monitors"
37+
_parse_resource_name_method = "parse_feature_monitor_path"
38+
_format_resource_name_method = "feature_monitor_path"
39+
_gca_resource: gca_feature_monitor.FeatureMonitor
40+
41+
def __init__(
42+
self,
43+
name: str,
44+
feature_group_id: Optional[str] = None,
45+
project: Optional[str] = None,
46+
location: Optional[str] = None,
47+
credentials: Optional[auth_credentials.Credentials] = None,
48+
):
49+
"""Retrieves an existing managed feature.
50+
51+
Args:
52+
name:
53+
The resource name
54+
(`projects/.../locations/.../featureGroups/.../featureMonitors/...`) or
55+
ID.
56+
feature_group_id:
57+
The feature group ID. Must be passed in if name is an ID and not
58+
a resource path.
59+
project:
60+
Project to retrieve feature from. If not set, the project set in
61+
aiplatform.init will be used.
62+
location:
63+
Location to retrieve feature from. If not set, the location set
64+
in aiplatform.init will be used.
65+
credentials:
66+
Custom credentials to use to retrieve this feature. Overrides
67+
credentials set in aiplatform.init.
68+
"""
69+
70+
super().__init__(
71+
project=project,
72+
location=location,
73+
credentials=credentials,
74+
resource_name=name,
75+
)
76+
77+
if re.fullmatch(
78+
r"projects/.+/locations/.+/featureGroups/.+/featureMonitors/.+",
79+
name,
80+
):
81+
if feature_group_id:
82+
raise ValueError(
83+
f"Since feature monitor '{name}' is provided as a path, feature_group_id should not be specified."
84+
)
85+
feature_monitor = name
86+
else:
87+
from .feature_group import FeatureGroup
88+
89+
# Construct the feature path using feature group ID if only the
90+
# feature group ID is provided.
91+
if not feature_group_id:
92+
raise ValueError(
93+
f"Since feature monitor '{name}' is not provided as a path, please specify feature_group_id."
94+
)
95+
96+
feature_group_path = utils.full_resource_name(
97+
resource_name=feature_group_id,
98+
resource_noun=FeatureGroup._resource_noun,
99+
parse_resource_name_method=FeatureGroup._parse_resource_name,
100+
format_resource_name_method=FeatureGroup._format_resource_name,
101+
)
102+
103+
feature_monitor = f"{feature_group_path}/featureMonitors/{name}"
104+
105+
self._gca_resource = self._get_gca_resource(resource_name=feature_monitor)
106+
107+
@property
108+
def description(self) -> str:
109+
"""The description of the feature monitor."""
110+
return self._gca_resource.description

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/googleapis/python-aiplatform/commit/f2233ceebef523fe7ed9e3a93a3c94a109e8e448

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy