Skip to content
Merged
Prev Previous commit
Next Next commit
Move redis too
Signed-off-by: Achal Shah <achals@gmail.com>
  • Loading branch information
achals committed Jun 16, 2021
commit 587b981ef001d9451d7ae68d54f73d81c1d7048d
44 changes: 27 additions & 17 deletions sdk/python/feast/infra/online_stores/helpers.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
import importlib
import struct
from typing import Any

import mmh3

from feast import errors
from feast.infra.online_stores.online_store import OnlineStore
from feast.protos.feast.storage.Redis_pb2 import RedisKeyV2 as RedisKeyProto
from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto
from feast.repo_config import OnlineStoreConfig, RedisOnlineStoreConfig


def get_online_store_from_config(
online_store_config: OnlineStoreConfig,
) -> OnlineStore:
def get_online_store_from_config(online_store_config: Any,) -> OnlineStore:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: trailing comma

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should the type of online_store_config be Any? Or FeastConfigBaseModel?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It has to be Any unfortunately - can't be FeastConfigBaseModel since pydantic doesn't understand or handle "subclass of type" as a type annotation.

"""Get the offline store from offline store config"""

if online_store_config.__repr_name__() == "SqliteOnlineStoreConfig":
from feast.infra.online_stores.sqlite import SqliteOnlineStore

return SqliteOnlineStore()
elif online_store_config.__repr_name__() == "DatastoreOnlineStoreConfig":
from feast.infra.online_stores.datastore import DatastoreOnlineStore

return DatastoreOnlineStore()
elif isinstance(online_store_config, RedisOnlineStoreConfig):
from feast.infra.online_stores.redis import RedisOnlineStore

return RedisOnlineStore()
raise ValueError(f"Unsupported online store config '{online_store_config}'")
module_name = online_store_config.__module__
qualified_name = type(online_store_config).__name__
store_class_name = qualified_name.replace("Config", "")
try:
module = importlib.import_module(module_name)
except Exception as e:
# The original exception can be anything - either module not found,
# or any other kind of error happening during the module import time.
# So we should include the original error as well in the stack trace.
raise errors.FeastModuleImportError(
module_name, module_type="OnlineStore"
) from e

# Try getting the provider class definition
try:
online_store_class = getattr(module, store_class_name)
except AttributeError:
# This can only be one type of error, when class_name attribute does not exist in the module
# So we don't have to include the original exception here
raise errors.FeastClassImportError(
module_name, store_class_name, class_type="OnlineStore"
) from None
return online_store_class()


def _redis_key(project: str, entity_key: EntityKeyProto):
Expand Down
24 changes: 23 additions & 1 deletion sdk/python/feast/infra/online_stores/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@
# limitations under the License.
import json
from datetime import datetime
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.protobuf.timestamp_pb2 import Timestamp
from pydantic import StrictStr
from pydantic.typing import Literal

from feast import Entity, FeatureTable, FeatureView, RepoConfig, utils
from feast.infra.online_stores.helpers import _mmh3, _redis_key
from feast.infra.online_stores.online_store import OnlineStore
from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto
from feast.protos.feast.types.Value_pb2 import Value as ValueProto
from feast.repo_config import RedisOnlineStoreConfig, RedisType
from feast.repo_config import FeastConfigBaseModel

try:
from redis import Redis
Expand All @@ -35,6 +38,25 @@
EX_SECONDS = 253402300799


class RedisType(str, Enum):
redis = "redis"
redis_cluster = "redis_cluster"


class RedisOnlineStoreConfig(FeastConfigBaseModel):
"""Online store config for Redis store"""

type: Literal["redis"] = "redis"
"""Online store type selector"""

redis_type: RedisType = RedisType.redis
"""Redis type: redis or redis_cluster"""

connection_string: StrictStr = "localhost:6379"
"""Connection string containing the host, port, and configuration parameters for Redis
format: host:port,parameter1,parameter2 eg. redis:6379,db=0 """


class RedisOnlineStore(OnlineStore):
_client: Optional[Union[Redis, RedisCluster]] = None

Expand Down
39 changes: 6 additions & 33 deletions sdk/python/feast/repo_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import importlib
from enum import Enum
from pathlib import Path
from typing import Any, TypeVar
from typing import Any

import yaml
from pydantic import BaseModel, StrictInt, StrictStr, ValidationError, root_validator
Expand All @@ -17,6 +16,7 @@
ONLINE_CONFIG_CLASS_FOR_TYPE = {
"sqlite": "feast.infra.online_stores.sqlite.SqliteOnlineStore",
"datastore": "feast.infra.online_stores.datastore.DatastoreOnlineStore",
"redis": "feast.infra.online_stores.redis.RedisOnlineStore",
}


Expand All @@ -36,31 +36,6 @@ class Config:
extra = "forbid"


OnlineT = TypeVar("OnlineT", bound=FeastConfigBaseModel)


class RedisType(str, Enum):
redis = "redis"
redis_cluster = "redis_cluster"


class RedisOnlineStoreConfig(FeastBaseModel):
"""Online store config for Redis store"""

type: Literal["redis"] = "redis"
"""Online store type selector"""

redis_type: RedisType = RedisType.redis
"""Redis type: redis or redis_cluster"""

connection_string: StrictStr = "localhost:6379"
"""Connection string containing the host, port, and configuration parameters for Redis
format: host:port,parameter1,parameter2 eg. redis:6379,db=0 """


OnlineStoreConfig = Union[RedisOnlineStoreConfig]


class FileOfflineStoreConfig(FeastBaseModel):
""" Offline store config for local (file-based) store """

Expand Down Expand Up @@ -144,6 +119,8 @@ def _validate_online_store_config(cls, values):
assert "provider" in values

# Set the default type
# This is only direct reference to a provider or online store that we should have
# for backwards compatibility.
if "type" not in values["online_store"]:
if values["provider"] == "local":
values["online_store"]["type"] = "sqlite"
Expand All @@ -154,13 +131,9 @@ def _validate_online_store_config(cls, values):

# Validate the dict to ensure one of the union types match
try:
if online_store_type == "redis":
RedisOnlineStoreConfig(**values["online_store"])
else:
online_config_class = get_online_config_from_type(online_store_type)
online_config_class(**values["online_store"])
online_config_class = get_online_config_from_type(online_store_type)
online_config_class(**values["online_store"])
except ValidationError as e:

raise ValidationError(
[ErrorWrapper(e, loc="online_store")], model=RepoConfig,
)
Expand Down
3 changes: 2 additions & 1 deletion sdk/python/tests/test_offline_online_store_consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
from feast.feature_store import FeatureStore
from feast.feature_view import FeatureView
from feast.infra.online_stores.datastore import DatastoreOnlineStoreConfig
from feast.infra.online_stores.redis import RedisOnlineStoreConfig, RedisType
from feast.infra.online_stores.sqlite import SqliteOnlineStoreConfig
from feast.repo_config import RedisOnlineStoreConfig, RedisType, RepoConfig
from feast.repo_config import RepoConfig
from feast.value_type import ValueType


Expand Down