Repository: feast-dev/feast

Issue:
Better and more explicit Pydantic error messages
**Is your feature request related to a problem? Please describe.**
The Pydantic errors can be very annoying to debug when the error messages are not explicit or (as often seen) completely missing. I provide two specific examples bellow but many more can be found throughout the codebase.

**Describe the solution you'd like**
Always include explicit error messages to all Pydating validation checks.

**Additional context**

A couple of examples:

```pycon
>>> from feast import RepoConfig
>>> repo_config = RepoConfig(
...     project="foo",
... )
Traceback (most recent call last):
  File "my_script.py", line 2, in <module>
    repo_config = RepoConfig(
  File ".venv/lib/python3.9/site-packages/feast/repo_config.py", line 124, in __init__
    super().__init__(**data)
  File "pydantic/main.py", line 331, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for RepoConfig
__root__
   (type=assertion_error)
```

👆 In this example, the `feast.errors.FeastProviderNotSetError` exception should be raised instead of a blank AssertionError. The error message here would be explicit (i.e. `"Provider is not set, but is required"`)

```pycon
>>> from feast import RepoConfig
>>> repo_config = RepoConfig(
...     project="foo",
...     provider="local",
...     offline_store=dict(
...         type="my.custom.offline_store.CustomStore"
...     ),
... )
Traceback (most recent call last):
  File "my_script.py", line 2, in <module>
    repo_config = RepoConfig(
  File ".venv/lib/python3.9/site-packages/feast/repo_config.py", line 124, in __init__
    super().__init__(**data)
  File "pydantic/main.py", line 331, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for RepoConfig
__root__
   (type=assertion_error)
```

👆 Just like in the other example, it is impossible to see what the issue here is. In this case, the `get_offline_config_from_type` function should be raising an explicit exception with a `"Offline store types should end with 'OfflineStore', got {offline_store_type} instead."` message instead of an empty AssertionError. This way it would be immediately clear what the issue was.



Discussion:
Agreed @tpvasconcelos , we should definitely have better errors here. This should easy to do, we have a bunch of naked assert statements in `feast/repo_config.py` and just adding an error message there would hugely improve user experience. 

Would you be willing to take this on?

Resolving patch:
diff --git a/sdk/python/feast/errors.py b/sdk/python/feast/errors.py
--- a/sdk/python/feast/errors.py
+++ b/sdk/python/feast/errors.py
@@ -197,6 +197,13 @@ def __init__(
         )
 
 
+class FeastOfflineStoreInvalidName(Exception):
+    def __init__(self, offline_store_class_name: str):
+        super().__init__(
+            f"Offline Store Class '{offline_store_class_name}' should end with the string `OfflineStore`.'"
+        )
+
+
 class FeastOnlineStoreInvalidName(Exception):
     def __init__(self, online_store_class_name: str):
         super().__init__(
diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py
--- a/sdk/python/feast/repo_config.py
+++ b/sdk/python/feast/repo_config.py
@@ -21,6 +21,8 @@
 from feast.errors import (
     FeastFeatureServerTypeInvalidError,
     FeastFeatureServerTypeSetError,
+    FeastOfflineStoreInvalidName,
+    FeastOnlineStoreInvalidName,
     FeastProviderNotSetError,
 )
 from feast.importer import import_class
@@ -278,7 +280,8 @@ def _validate_online_store_config(cls, values):
             return values
 
         # Make sure that the provider configuration is set. We need it to set the defaults
-        assert "provider" in values
+        if "provider" not in values:
+            raise FeastProviderNotSetError()
 
         # Set the default type
         # This is only direct reference to a provider or online store that we should have
@@ -315,7 +318,8 @@ def _validate_offline_store_config(cls, values):
             return values
 
         # Make sure that the provider configuration is set. We need it to set the defaults
-        assert "provider" in values
+        if "provider" not in values:
+            raise FeastProviderNotSetError()
 
         # Set the default type
         if "type" not in values["offline_store"]:
@@ -455,8 +459,8 @@ def get_batch_engine_config_from_type(batch_engine_type: str):
 def get_online_config_from_type(online_store_type: str):
     if online_store_type in ONLINE_STORE_CLASS_FOR_TYPE:
         online_store_type = ONLINE_STORE_CLASS_FOR_TYPE[online_store_type]
-    else:
-        assert online_store_type.endswith("OnlineStore")
+    elif not online_store_type.endswith("OnlineStore"):
+        raise FeastOnlineStoreInvalidName(online_store_type)
     module_name, online_store_class_type = online_store_type.rsplit(".", 1)
     config_class_name = f"{online_store_class_type}Config"
 
@@ -466,8 +470,8 @@ def get_online_config_from_type(online_store_type: str):
 def get_offline_config_from_type(offline_store_type: str):
     if offline_store_type in OFFLINE_STORE_CLASS_FOR_TYPE:
         offline_store_type = OFFLINE_STORE_CLASS_FOR_TYPE[offline_store_type]
-    else:
-        assert offline_store_type.endswith("OfflineStore")
+    elif not offline_store_type.endswith("OfflineStore"):
+        raise FeastOfflineStoreInvalidName(offline_store_type)
     module_name, offline_store_class_type = offline_store_type.rsplit(".", 1)
     config_class_name = f"{offline_store_class_type}Config"
 


Test patch: