Skip to content

Commit b3d58f6

Browse files
Add feature server configuration for AWS lambda (feast-dev#1865)
* Add feature server configuration for AWS lambda Signed-off-by: Felix Wang <wangfelix98@gmail.com> * Rename app.py to config.py and add defaults to config Signed-off-by: Felix Wang <wangfelix98@gmail.com> * Change asserts to if/raise checks Signed-off-by: Felix Wang <wangfelix98@gmail.com> * Always generate feature server config Signed-off-by: Felix Wang <wangfelix98@gmail.com> * Make feature server config optional Signed-off-by: Felix Wang <wangfelix98@gmail.com> * Small fix Signed-off-by: Felix Wang <wangfelix98@gmail.com>
1 parent af95bf0 commit b3d58f6

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

sdk/python/feast/errors.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,25 @@ def __init__(self, provider_name):
9393
super().__init__(f"Provider '{provider_name}' is not implemented")
9494

9595

96+
class FeastProviderNotSetError(Exception):
97+
def __init__(self):
98+
super().__init__("Provider is not set, but is required")
99+
100+
101+
class FeastFeatureServerTypeSetError(Exception):
102+
def __init__(self, feature_server_type: str):
103+
super().__init__(
104+
f"Feature server type was set to {feature_server_type}, but the type should be determined by the provider"
105+
)
106+
107+
108+
class FeastFeatureServerTypeInvalidError(Exception):
109+
def __init__(self, feature_server_type: str):
110+
super().__init__(
111+
f"Feature server type was set to {feature_server_type}, but this type is invalid"
112+
)
113+
114+
96115
class FeastModuleImportError(Exception):
97116
def __init__(self, module_name: str, module_type: str):
98117
super().__init__(f"Could not import {module_type} module '{module_name}'")
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from pydantic import StrictBool, StrictStr
2+
from pydantic.typing import Literal
3+
4+
from feast.repo_config import FeastConfigBaseModel
5+
6+
7+
class AwsLambdaFeatureServerConfig(FeastConfigBaseModel):
8+
"""Feature server config for AWS Lambda."""
9+
10+
type: Literal["aws_lambda"] = "aws_lambda"
11+
"""Feature server type selector."""
12+
13+
enabled: StrictBool = False
14+
"""Whether the feature server should be launched."""
15+
16+
public: StrictBool = True
17+
"""Whether the endpoint should be publicly accessible."""
18+
19+
auth: Literal["none", "api-key"] = "none"
20+
"""Authentication method for the endpoint."""
21+
22+
execution_role_name: StrictStr
23+
"""The execution role for the AWS Lambda function."""

sdk/python/feast/repo_config.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
from pydantic.error_wrappers import ErrorWrapper
1414
from pydantic.typing import Dict, Optional, Union
1515

16+
from feast.errors import (
17+
FeastFeatureServerTypeInvalidError,
18+
FeastFeatureServerTypeSetError,
19+
FeastProviderNotSetError,
20+
)
1621
from feast.importer import get_class_from_type
1722
from feast.usage import log_exceptions
1823

@@ -32,6 +37,10 @@
3237
"redshift": "feast.infra.offline_stores.redshift.RedshiftOfflineStore",
3338
}
3439

40+
FEATURE_SERVER_CONFIG_CLASS_FOR_TYPE = {
41+
"aws_lambda": "feast.infra.feature_servers.aws_lambda.config.AwsLambdaFeatureServerConfig",
42+
}
43+
3544

3645
class FeastBaseModel(BaseModel):
3746
""" Feast Pydantic Configuration Class """
@@ -86,6 +95,9 @@ class RepoConfig(FeastBaseModel):
8695
offline_store: Any
8796
""" OfflineStoreConfig: Offline store configuration (optional depending on provider) """
8897

98+
feature_server: Optional[Any]
99+
""" FeatureServerConfig: Feature server configuration (optional depending on provider) """
100+
89101
repo_path: Optional[Path] = None
90102

91103
def __init__(self, **data: Any):
@@ -105,6 +117,11 @@ def __init__(self, **data: Any):
105117
elif isinstance(self.offline_store, str):
106118
self.offline_store = get_offline_config_from_type(self.offline_store)()
107119

120+
if isinstance(self.feature_server, Dict):
121+
self.feature_server = get_feature_server_config_from_type(
122+
self.feature_server["type"]
123+
)(**self.feature_server)
124+
108125
def get_registry_config(self):
109126
if isinstance(self.registry, str):
110127
return RegistryConfig(path=self.registry)
@@ -190,6 +207,43 @@ def _validate_offline_store_config(cls, values):
190207

191208
return values
192209

210+
@root_validator(pre=True)
211+
def _validate_feature_server_config(cls, values):
212+
# Having no feature server is the default.
213+
if "feature_server" not in values:
214+
return values
215+
216+
# Skip if we aren't creating the configuration from a dict
217+
if not isinstance(values["feature_server"], Dict):
218+
return values
219+
220+
# Make sure that the provider configuration is set. We need it to set the defaults
221+
if "provider" not in values:
222+
raise FeastProviderNotSetError()
223+
224+
# Make sure that the type is not set, since we will set it based on the provider.
225+
if "type" in values["feature_server"]:
226+
raise FeastFeatureServerTypeSetError(values["feature_server"]["type"])
227+
228+
# Set the default type. We only support AWS Lambda for now.
229+
if values["provider"] == "aws":
230+
values["feature_server"]["type"] = "aws_lambda"
231+
232+
feature_server_type = values["feature_server"]["type"]
233+
234+
# Validate the dict to ensure one of the union types match
235+
try:
236+
feature_server_config_class = get_feature_server_config_from_type(
237+
feature_server_type
238+
)
239+
feature_server_config_class(**values["feature_server"])
240+
except ValidationError as e:
241+
raise ValidationError(
242+
[ErrorWrapper(e, loc="feature_server")], model=RepoConfig,
243+
)
244+
245+
return values
246+
193247
@validator("project")
194248
def _validate_project_name(cls, v):
195249
from feast.repo_operations import is_valid_name
@@ -244,6 +298,16 @@ def get_offline_config_from_type(offline_store_type: str):
244298
return get_class_from_type(module_name, config_class_name, config_class_name)
245299

246300

301+
def get_feature_server_config_from_type(feature_server_type: str):
302+
# We do not support custom feature servers right now.
303+
if feature_server_type not in FEATURE_SERVER_CONFIG_CLASS_FOR_TYPE:
304+
raise FeastFeatureServerTypeInvalidError(feature_server_type)
305+
306+
feature_server_type = FEATURE_SERVER_CONFIG_CLASS_FOR_TYPE[feature_server_type]
307+
module_name, config_class_name = feature_server_type.rsplit(".", 1)
308+
return get_class_from_type(module_name, config_class_name, config_class_name)
309+
310+
247311
def load_repo_config(repo_path: Path) -> RepoConfig:
248312
config_path = repo_path / "feature_store.yaml"
249313

0 commit comments

Comments
 (0)