Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FSTORE-1328] Add list to feature store and richer repr drafts #1301

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
faa1add
Scaffold some structure for python SDK improved communication
vatj Apr 21, 2024
182ba48
Add welcome message on connection
vatj Apr 21, 2024
b443792
Minor adjustments
vatj Apr 21, 2024
4533b5c
Set styles like if I had a taste for these things
vatj Apr 21, 2024
c581a5a
Add api for get_all feature groups
vatj Apr 21, 2024
62f1a1b
Add base list and table printers
vatj Apr 21, 2024
d7e617c
Add api call for get_all feature views
vatj Apr 21, 2024
5616255
Add feature_store quicktour
vatj Apr 22, 2024
a7d59e6
Minor fix and clean ups
vatj Apr 22, 2024
651be27
Add user_messages file
vatj Apr 22, 2024
074562d
Update base tables for FGs
vatj Apr 22, 2024
9845179
Add methods to feature view engine
vatj Apr 22, 2024
b0b616a
Add alternate table style for fg list
vatj Apr 22, 2024
8e21bc1
More fixes
vatj Apr 22, 2024
57ddf87
Filter by latest version for feature view api
vatj Apr 22, 2024
868fbbb
Refactor + adapt feature view table
vatj Apr 22, 2024
06e7298
Refactoring
vatj Apr 22, 2024
bca6b74
Clean up
vatj Apr 22, 2024
d77b784
Basic FV info draft
vatj Apr 22, 2024
521695b
More table info for feature views
vatj Apr 23, 2024
e7184c9
More table info for feature views
vatj Apr 23, 2024
d912ef5
Minor adjustments
vatj Apr 23, 2024
4ad9032
Remove feature_view list limitation
vatj Apr 23, 2024
a08a7b9
Add to quicktour to manifest
vatj Apr 23, 2024
fed61b4
Split description code path for show_feature
vatj Apr 23, 2024
d2ce151
add output to markdown quicktour
vatj Apr 23, 2024
36fc1f3
Merge branch 'master' into FSTORE-1328-clean
vatj Apr 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions python/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
include ../README.md
include ../LICENSE
include hsfs/helpers/quicktours/feature_store.md
7 changes: 4 additions & 3 deletions python/hsfs/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
)
from hsfs.core.opensearch import OpenSearchClientSingleton
from hsfs.decorators import connected, not_connected
from requests.exceptions import ConnectionError
from hsfs.helpers import user_messages


AWS_DEFAULT_REGION = "default"
Expand Down Expand Up @@ -187,7 +187,9 @@ def get_feature_store(
"""
if not name:
name = client.get_instance()._project_name
return self._feature_store_api.get(util.append_feature_store_suffix(name))
fs = self._feature_store_api.get(util.append_feature_store_suffix(name))
user_messages.print_connected_to_feature_store_message(fs)
return fs

@not_connected
def connect(self) -> None:
Expand Down Expand Up @@ -264,7 +266,6 @@ def connect(self) -> None:
except (TypeError, ConnectionError):
self._connected = False
raise
print("Connected. Call `.close()` to terminate connection gracefully.")

def close(self) -> None:
"""Close a connection gracefully.
Expand Down
38 changes: 38 additions & 0 deletions python/hsfs/core/feature_group_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#
from __future__ import annotations

from typing import List, Union

from hsfs import client, feature_group, feature_group_commit
from hsfs.core import explicit_provenance, ingestion_job

Expand All @@ -24,6 +26,42 @@ class FeatureGroupApi:
ONDEMAND = "ondemand"
SPINE = "spine"

def get_all(
self,
feature_store_id: int,
with_features: bool = False,
) -> List[
Union[
feature_group.FeatureGroup,
feature_group.SpineGroup,
feature_group.ExternalFeatureGroup,
]
]:
"""Get a list of feature groups in a feature store.

:param feature_store_id: feature store id
:type feature_store_id: int
:param feature_group_type: type of the feature group to return
:type feature_group_type: string
:return: list of feature group metadata objects
:rtype: List[FeatureGroup]
"""
_client = client.get_instance()
path_params = [
"project",
_client._project_id,
"featurestores",
feature_store_id,
"featuregroups",
]
query_params = {}
if with_features:
query_params["expand"] = ["features"]

return feature_group.FeatureGroup.from_response_json(
_client._send_request("GET", path_params, query_params)
)

def save(self, feature_group_instance):
"""Save feature group metadata to the feature store.

Expand Down
64 changes: 64 additions & 0 deletions python/hsfs/core/feature_group_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
from __future__ import annotations

import warnings
from typing import List, Union

from hsfs import engine, util
from hsfs import feature_group as fg
from hsfs.client import exceptions
from hsfs.core import delta_engine, feature_group_base_engine, hudi_engine
from hsfs.core.deltastreamer_jobconf import DeltaStreamerJobConf
from hsfs.helpers.richer_repr import richer_feature_group


class FeatureGroupEngine(feature_group_base_engine.FeatureGroupBaseEngine):
Expand Down Expand Up @@ -388,3 +390,65 @@ def save_feature_group_metadata(
feature_group_id=feature_group.id,
)
)

def list_feature_groups(
self,
latest_version_only: bool,
online_enabled_only: bool,
spine_only: bool,
external_only: bool,
with_features: bool,
) -> List[fg.FeatureGroup, fg.ExternalFeatureGroup, fg.SpineGroup]:
fg_list = self._feature_group_api.get_all(
feature_store_id=self._feature_store_id,
with_features=with_features,
)

if online_enabled_only:
fg_list = [fgroup for fgroup in fg_list if fgroup.online_enabled]
if spine_only:
fg_list = [
fgroup for fgroup in fg_list if isinstance(fgroup, fg.SpineGroup)
]
if external_only:
fg_list = [
fgroup
for fgroup in fg_list
if isinstance(fgroup, fg.ExternalFeatureGroup)
]

if latest_version_only:
fg_list = [
fgroup
for fgroup in fg_list
if fgroup.version
== max(fg1.version for fg1 in fg_list if fg1.name == fgroup.name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be using filter() is better for readability instead of having a for loop again

]

return sorted(fg_list, key=lambda fgroup: fgroup.name)

def show_info(
self,
feature_group: Union[fg.FeatureGroup, fg.ExternalFeatureGroup, fg.SpineGroup],
show_features: bool = False,
):
richer_feature_group.build_and_print_info_fg_table(
feature_group, show_features=show_features
)

def show_all(
self,
latest_version_only: bool = True,
show_features: bool = False,
show_description: bool = False,
):
fgroup_list = self.list_feature_groups(
latest_version_only=latest_version_only,
online_enabled_only=False,
spine_only=False,
external_only=False,
with_features=show_features,
)
richer_feature_group.show_rich_table_feature_groups(
fgroup_list, show_features=show_features, show_description=show_description
)
17 changes: 16 additions & 1 deletion python/hsfs/core/feature_view_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#
from __future__ import annotations

from typing import List, Optional, Union
from typing import Any, Dict, List, Optional, Union

from hsfs import (
client,
Expand Down Expand Up @@ -55,6 +55,21 @@ def __init__(self, feature_store_id: int) -> None:
"featureview",
]

def get_all(
self, latest_version_only: bool = True, with_features: bool = False
) -> List[Dict[str, Any]]:
path = self._base_path
query_params = {}
if latest_version_only:
query_params["filter_by"] = "latest_version"
if with_features:
query_params["expand"] = ["features"]
return self._client._send_request(
"GET",
path_params=path,
query_params={"expand": ["features"]} if with_features else None,
)["items"]

def post(
self, feature_view_obj: feature_view.FeatureView
) -> feature_view.FeatureView:
Expand Down
46 changes: 45 additions & 1 deletion python/hsfs/core/feature_view_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@

import datetime
import warnings
from typing import Optional
from typing import Any, Dict, List, Optional

import humps
from hsfs import (
client,
engine,
Expand All @@ -39,6 +40,7 @@
training_dataset_engine,
transformation_function_engine,
)
from hsfs.helpers.richer_repr import richer_feature_view
from hsfs.training_dataset_split import TrainingDatasetSplit


Expand Down Expand Up @@ -923,3 +925,45 @@ def _check_if_exists_with_prefix(self, f_name, f_set):
)
else:
return f_name

def list_feature_views(
self, latest_version_only: bool = True, with_features: bool = False
) -> List[Dict[str, Any]]:
fv_list = [
humps.decamelize(fv_obj)
for fv_obj in self._feature_view_api.get_all(
latest_version_only=latest_version_only, with_features=with_features
)
]
if latest_version_only:
fv_list = [
fview
for fview in fv_list
if fview["version"]
== max(
fv1["version"] for fv1 in fv_list if fv1["name"] == fview["name"]
)
]

return sorted(fv_list, key=lambda fview: fview["name"])

def show_info(
self, feature_view_obj: feature_view.FeatureView, show_features: bool = True
) -> None:
richer_feature_view.build_and_print_info_fv_table(
feature_view_obj, show_features=show_features
)

def show_all(
self,
latest_version_only: bool = True,
show_features: bool = False,
show_description: bool = False,
):
fview_dicts = self.list_feature_views(
latest_version_only=latest_version_only,
with_features=show_features,
)
richer_feature_view.show_rich_table_feature_views(
fview_dicts, show_features, show_description
)
3 changes: 3 additions & 0 deletions python/hsfs/feature_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -3262,6 +3262,9 @@ def _is_time_travel_enabled(self) -> bool:
and self._time_travel_format.upper() == "HUDI"
)

def _show_info(self, show_features: bool = True) -> None:
self._feature_group_engine.show_info(self, show_features=show_features)

@property
def id(self) -> Optional[int]:
"""Feature group id."""
Expand Down
79 changes: 77 additions & 2 deletions python/hsfs/feature_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
)
from hsfs.decorators import typechecked
from hsfs.embedding import EmbeddingIndex
from hsfs.helpers import quicktours, verbose
from hsfs.statistics_config import StatisticsConfig
from hsfs.transformation_function import TransformationFunction

Expand Down Expand Up @@ -736,6 +737,10 @@ def get_or_create_feature_group(
self.id, name, version, feature_group_api.FeatureGroupApi.CACHED
)
feature_group_object.feature_store = self
if verbose.is_hsfs_verbose():
self._feature_group_engine.show_info(
feature_group_object, show_features=True
)
return feature_group_object
except exceptions.RestAPIError as e:
if (
Expand Down Expand Up @@ -764,6 +769,10 @@ def get_or_create_feature_group(
notification_topic_name=notification_topic_name,
)
feature_group_object.feature_store = self
if verbose.is_hsfs_verbose():
self._feature_group_engine.show_info(
feature_group_object, show_features=False
)
return feature_group_object
else:
raise e
Expand Down Expand Up @@ -1639,13 +1648,16 @@ def get_or_create_feature_view(
`FeatureView`: The feature view metadata object.
"""
try:
return self._feature_view_engine.get(name, version)
feature_view_object = self._feature_view_engine.get(name, version)
if verbose.is_hsfs_verbose():
self._feature_view_engine.show_info(feature_view_object)
return feature_view_object
except exceptions.RestAPIError as e:
if (
e.response.json().get("errorCode", "") == 270181
and e.response.status_code == 404
):
return self.create_feature_view(
feature_view_object = self.create_feature_view(
name=name,
query=query,
version=version,
Expand All @@ -1655,6 +1667,9 @@ def get_or_create_feature_view(
training_helper_columns=training_helper_columns or [],
transformation_functions=transformation_functions or {},
)
if verbose.is_hsfs_verbose():
self._feature_view_engine.show_info(feature_view_object)
return feature_view_object
else:
raise e

Expand Down Expand Up @@ -1728,6 +1743,66 @@ def get_feature_views(self, name: str) -> List["feature_view.FeatureView"]:
"""
return self._feature_view_engine.get(name)

def show_feature_groups(
self,
latest_only: bool = True,
show_features: bool = False,
show_description: bool = False,
) -> None:
"""Prints a list of all feature groups in the feature store.

!!! example
```python
# get feature store instance
fs = ...

# show all feature groups
fs.show_feature_groups()
```

# Arguments
latest_only: If `True` only the latest version of each feature group is shown, defaults to `True`.
show_features: If `True` also show the features of the feature groups, defaults to `False`.
show_description: If `True` also show the description of the feature groups, defaults to `False`.

# Returns
`None`
"""
self._feature_group_engine.show_all(
latest_only, show_features, show_description
)

def show_feature_views(
self,
latest_only: bool = True,
show_features: bool = False,
show_description: bool = False,
) -> None:
"""Prints a list of all feature views in the feature store.

!!! example
```python
# get feature store instance
fs = ...

# show all feature views
fs.show_feature_views()
```

# Arguments
latest_only: If `True` only the latest version of each feature view is shown, defaults to `True`.
show_features: If `True` also show the features of the feature views, defaults to `False`.
show_description: If `True` also show the description of the feature views, defaults to `False`.

# Returns
`None`
"""
self._feature_view_engine.show_all(latest_only, show_features, show_description)

def quicktour(self) -> None:
"""Prints a quick tour of the feature store API."""
quicktours.rich_print_quicktour("feature_store")

@property
def id(self) -> int:
"""Id of the feature store."""
Expand Down
Loading
Loading