We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add validation for project name
**Is your feature request related to a problem? Please describe.**
A follow up to https://github.com/feast-dev/feast/pull/1752 - I think project name should definitely be validated early on. Otherwise could run into an error message (https://github.com/feast-dev/feast/pull/1752#issue-700598472) that is not obviously related to the project name.

**Describe the solution you'd like**
Validation upon feast apply that checks the project name. Can use the name validation function [here](https://github.com/feast-dev/feast/blob/a548c48927e6f6858d91a93cf356b43fe7c67aad/sdk/python/feast/repo_operations.py#L390).

**Describe alternatives you've considered**
There's probably more validation coverage to be done, and it could be done at a later time if there are higher priorities at the moment.

**Additional context**
@tedhtchang to take this simple addition on per https://github.com/feast-dev/feast/pull/1752#issuecomment-891339661


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sdk/python/feast/repo_config.py`
Content:
```
1 from pathlib import Path
2 from typing import Any
3 
4 import yaml
5 from pydantic import BaseModel, StrictInt, StrictStr, ValidationError, root_validator
6 from pydantic.error_wrappers import ErrorWrapper
7 from pydantic.typing import Dict, Optional, Union
8 
9 from feast.importer import get_class_from_type
10 from feast.usage import log_exceptions
11 
12 # These dict exists so that:
13 # - existing values for the online store type in featurestore.yaml files continue to work in a backwards compatible way
14 # - first party and third party implementations can use the same class loading code path.
15 ONLINE_STORE_CLASS_FOR_TYPE = {
16     "sqlite": "feast.infra.online_stores.sqlite.SqliteOnlineStore",
17     "datastore": "feast.infra.online_stores.datastore.DatastoreOnlineStore",
18     "redis": "feast.infra.online_stores.redis.RedisOnlineStore",
19     "dynamodb": "feast.infra.online_stores.dynamodb.DynamoDBOnlineStore",
20 }
21 
22 OFFLINE_STORE_CLASS_FOR_TYPE = {
23     "file": "feast.infra.offline_stores.file.FileOfflineStore",
24     "bigquery": "feast.infra.offline_stores.bigquery.BigQueryOfflineStore",
25     "redshift": "feast.infra.offline_stores.redshift.RedshiftOfflineStore",
26 }
27 
28 
29 class FeastBaseModel(BaseModel):
30     """ Feast Pydantic Configuration Class """
31 
32     class Config:
33         arbitrary_types_allowed = True
34         extra = "allow"
35 
36 
37 class FeastConfigBaseModel(BaseModel):
38     """ Feast Pydantic Configuration Class """
39 
40     class Config:
41         arbitrary_types_allowed = True
42         extra = "forbid"
43 
44 
45 class RegistryConfig(FeastBaseModel):
46     """ Metadata Store Configuration. Configuration that relates to reading from and writing to the Feast registry."""
47 
48     path: StrictStr
49     """ str: Path to metadata store. Can be a local path, or remote object storage path, e.g. a GCS URI """
50 
51     cache_ttl_seconds: StrictInt = 600
52     """int: The cache TTL is the amount of time registry state will be cached in memory. If this TTL is exceeded then
53      the registry will be refreshed when any feature store method asks for access to registry state. The TTL can be
54      set to infinity by setting TTL to 0 seconds, which means the cache will only be loaded once and will never
55      expire. Users can manually refresh the cache by calling feature_store.refresh_registry() """
56 
57 
58 class RepoConfig(FeastBaseModel):
59     """ Repo config. Typically loaded from `feature_store.yaml` """
60 
61     registry: Union[StrictStr, RegistryConfig] = "data/registry.db"
62     """ str: Path to metadata store. Can be a local path, or remote object storage path, e.g. a GCS URI """
63 
64     project: StrictStr
65     """ str: Feast project id. This can be any alphanumeric string up to 16 characters.
66         You can have multiple independent feature repositories deployed to the same cloud
67         provider account, as long as they have different project ids.
68     """
69 
70     provider: StrictStr
71     """ str: local or gcp or aws """
72 
73     online_store: Any
74     """ OnlineStoreConfig: Online store configuration (optional depending on provider) """
75 
76     offline_store: Any
77     """ OfflineStoreConfig: Offline store configuration (optional depending on provider) """
78 
79     repo_path: Optional[Path] = None
80 
81     def __init__(self, **data: Any):
82         super().__init__(**data)
83 
84         if isinstance(self.online_store, Dict):
85             self.online_store = get_online_config_from_type(self.online_store["type"])(
86                 **self.online_store
87             )
88         elif isinstance(self.online_store, str):
89             self.online_store = get_online_config_from_type(self.online_store)()
90 
91         if isinstance(self.offline_store, Dict):
92             self.offline_store = get_offline_config_from_type(
93                 self.offline_store["type"]
94             )(**self.offline_store)
95         elif isinstance(self.offline_store, str):
96             self.offline_store = get_offline_config_from_type(self.offline_store)()
97 
98     def get_registry_config(self):
99         if isinstance(self.registry, str):
100             return RegistryConfig(path=self.registry)
101         else:
102             return self.registry
103 
104     @root_validator(pre=True)
105     @log_exceptions
106     def _validate_online_store_config(cls, values):
107         # This method will validate whether the online store configurations are set correctly. This explicit validation
108         # is necessary because Pydantic Unions throw very verbose and cryptic exceptions. We also use this method to
109         # impute the default online store type based on the selected provider. For the time being this method should be
110         # considered tech debt until we can implement https://github.com/samuelcolvin/pydantic/issues/619 or a more
111         # granular configuration system
112 
113         # Set empty online_store config if it isn't set explicitly
114         if "online_store" not in values:
115             values["online_store"] = dict()
116 
117         # Skip if we aren't creating the configuration from a dict
118         if not isinstance(values["online_store"], Dict):
119             return values
120 
121         # Make sure that the provider configuration is set. We need it to set the defaults
122         assert "provider" in values
123 
124         # Set the default type
125         # This is only direct reference to a provider or online store that we should have
126         # for backwards compatibility.
127         if "type" not in values["online_store"]:
128             if values["provider"] == "local":
129                 values["online_store"]["type"] = "sqlite"
130             elif values["provider"] == "gcp":
131                 values["online_store"]["type"] = "datastore"
132             elif values["provider"] == "aws":
133                 values["online_store"]["type"] = "dynamodb"
134 
135         online_store_type = values["online_store"]["type"]
136 
137         # Validate the dict to ensure one of the union types match
138         try:
139             online_config_class = get_online_config_from_type(online_store_type)
140             online_config_class(**values["online_store"])
141         except ValidationError as e:
142             raise ValidationError(
143                 [ErrorWrapper(e, loc="online_store")], model=RepoConfig,
144             )
145 
146         return values
147 
148     @root_validator(pre=True)
149     def _validate_offline_store_config(cls, values):
150         # Set empty offline_store config if it isn't set explicitly
151         if "offline_store" not in values:
152             values["offline_store"] = dict()
153 
154         # Skip if we aren't creating the configuration from a dict
155         if not isinstance(values["offline_store"], Dict):
156             return values
157 
158         # Make sure that the provider configuration is set. We need it to set the defaults
159         assert "provider" in values
160 
161         # Set the default type
162         if "type" not in values["offline_store"]:
163             if values["provider"] == "local":
164                 values["offline_store"]["type"] = "file"
165             elif values["provider"] == "gcp":
166                 values["offline_store"]["type"] = "bigquery"
167             elif values["provider"] == "aws":
168                 values["offline_store"]["type"] = "redshift"
169 
170         offline_store_type = values["offline_store"]["type"]
171 
172         # Validate the dict to ensure one of the union types match
173         try:
174             offline_config_class = get_offline_config_from_type(offline_store_type)
175             offline_config_class(**values["offline_store"])
176         except ValidationError as e:
177             raise ValidationError(
178                 [ErrorWrapper(e, loc="offline_store")], model=RepoConfig,
179             )
180 
181         return values
182 
183 
184 class FeastConfigError(Exception):
185     def __init__(self, error_message, config_path):
186         self._error_message = error_message
187         self._config_path = config_path
188         super().__init__(self._error_message)
189 
190     def __str__(self) -> str:
191         return f"{self._error_message}\nat {self._config_path}"
192 
193     def __repr__(self) -> str:
194         return (
195             f"FeastConfigError({repr(self._error_message)}, {repr(self._config_path)})"
196         )
197 
198 
199 def get_data_source_class_from_type(data_source_type: str):
200     module_name, config_class_name = data_source_type.rsplit(".", 1)
201     return get_class_from_type(module_name, config_class_name, "Source")
202 
203 
204 def get_online_config_from_type(online_store_type: str):
205     if online_store_type in ONLINE_STORE_CLASS_FOR_TYPE:
206         online_store_type = ONLINE_STORE_CLASS_FOR_TYPE[online_store_type]
207     else:
208         assert online_store_type.endswith("OnlineStore")
209     module_name, online_store_class_type = online_store_type.rsplit(".", 1)
210     config_class_name = f"{online_store_class_type}Config"
211 
212     return get_class_from_type(module_name, config_class_name, config_class_name)
213 
214 
215 def get_offline_config_from_type(offline_store_type: str):
216     if offline_store_type in OFFLINE_STORE_CLASS_FOR_TYPE:
217         offline_store_type = OFFLINE_STORE_CLASS_FOR_TYPE[offline_store_type]
218     else:
219         assert offline_store_type.endswith("OfflineStore")
220     module_name, offline_store_class_type = offline_store_type.rsplit(".", 1)
221     config_class_name = f"{offline_store_class_type}Config"
222 
223     return get_class_from_type(module_name, config_class_name, config_class_name)
224 
225 
226 def load_repo_config(repo_path: Path) -> RepoConfig:
227     config_path = repo_path / "feature_store.yaml"
228 
229     with open(config_path) as f:
230         raw_config = yaml.safe_load(f)
231         try:
232             c = RepoConfig(**raw_config)
233             c.repo_path = repo_path
234             return c
235         except ValidationError as e:
236             raise FeastConfigError(e, config_path)
237
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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
@@ -2,7 +2,14 @@
 from typing import Any
 
 import yaml
-from pydantic import BaseModel, StrictInt, StrictStr, ValidationError, root_validator
+from pydantic import (
+    BaseModel,
+    StrictInt,
+    StrictStr,
+    ValidationError,
+    root_validator,
+    validator,
+)
 from pydantic.error_wrappers import ErrorWrapper
 from pydantic.typing import Dict, Optional, Union
 
@@ -180,6 +187,17 @@
 
         return values
 
+    @validator("project")
+    def _validate_project_name(cls, v):
+        from feast.repo_operations import is_valid_name
+
+        if not is_valid_name(v):
+            raise ValueError(
+                f"Project name, {v}, should only have "
+                f"alphanumerical values and underscores but not start with an underscore."
+            )
+        return v
+
 
 class FeastConfigError(Exception):
     def __init__(self, error_message, config_path):

{"golden_diff": "diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py\n--- a/sdk/python/feast/repo_config.py\n+++ b/sdk/python/feast/repo_config.py\n@@ -2,7 +2,14 @@\n from typing import Any\n \n import yaml\n-from pydantic import BaseModel, StrictInt, StrictStr, ValidationError, root_validator\n+from pydantic import (\n+    BaseModel,\n+    StrictInt,\n+    StrictStr,\n+    ValidationError,\n+    root_validator,\n+    validator,\n+)\n from pydantic.error_wrappers import ErrorWrapper\n from pydantic.typing import Dict, Optional, Union\n \n@@ -180,6 +187,17 @@\n \n         return values\n \n+    @validator(\"project\")\n+    def _validate_project_name(cls, v):\n+        from feast.repo_operations import is_valid_name\n+\n+        if not is_valid_name(v):\n+            raise ValueError(\n+                f\"Project name, {v}, should only have \"\n+                f\"alphanumerical values and underscores but not start with an underscore.\"\n+            )\n+        return v\n+\n \n class FeastConfigError(Exception):\n     def __init__(self, error_message, config_path):\n", "issue": "Add validation for project name\n**Is your feature request related to a problem? Please describe.**\r\nA follow up to https://github.com/feast-dev/feast/pull/1752 - I think project name should definitely be validated early on. Otherwise could run into an error message (https://github.com/feast-dev/feast/pull/1752#issue-700598472) that is not obviously related to the project name.\r\n\r\n**Describe the solution you'd like**\r\nValidation upon feast apply that checks the project name. Can use the name validation function [here](https://github.com/feast-dev/feast/blob/a548c48927e6f6858d91a93cf356b43fe7c67aad/sdk/python/feast/repo_operations.py#L390).\r\n\r\n**Describe alternatives you've considered**\r\nThere's probably more validation coverage to be done, and it could be done at a later time if there are higher priorities at the moment.\r\n\r\n**Additional context**\r\n@tedhtchang to take this simple addition on per https://github.com/feast-dev/feast/pull/1752#issuecomment-891339661\r\n\n", "before_files": [{"content": "from pathlib import Path\nfrom typing import Any\n\nimport yaml\nfrom pydantic import BaseModel, StrictInt, StrictStr, ValidationError, root_validator\nfrom pydantic.error_wrappers import ErrorWrapper\nfrom pydantic.typing import Dict, Optional, Union\n\nfrom feast.importer import get_class_from_type\nfrom feast.usage import log_exceptions\n\n# These dict exists so that:\n# - existing values for the online store type in featurestore.yaml files continue to work in a backwards compatible way\n# - first party and third party implementations can use the same class loading code path.\nONLINE_STORE_CLASS_FOR_TYPE = {\n    \"sqlite\": \"feast.infra.online_stores.sqlite.SqliteOnlineStore\",\n    \"datastore\": \"feast.infra.online_stores.datastore.DatastoreOnlineStore\",\n    \"redis\": \"feast.infra.online_stores.redis.RedisOnlineStore\",\n    \"dynamodb\": \"feast.infra.online_stores.dynamodb.DynamoDBOnlineStore\",\n}\n\nOFFLINE_STORE_CLASS_FOR_TYPE = {\n    \"file\": \"feast.infra.offline_stores.file.FileOfflineStore\",\n    \"bigquery\": \"feast.infra.offline_stores.bigquery.BigQueryOfflineStore\",\n    \"redshift\": \"feast.infra.offline_stores.redshift.RedshiftOfflineStore\",\n}\n\n\nclass FeastBaseModel(BaseModel):\n    \"\"\" Feast Pydantic Configuration Class \"\"\"\n\n    class Config:\n        arbitrary_types_allowed = True\n        extra = \"allow\"\n\n\nclass FeastConfigBaseModel(BaseModel):\n    \"\"\" Feast Pydantic Configuration Class \"\"\"\n\n    class Config:\n        arbitrary_types_allowed = True\n        extra = \"forbid\"\n\n\nclass RegistryConfig(FeastBaseModel):\n    \"\"\" Metadata Store Configuration. Configuration that relates to reading from and writing to the Feast registry.\"\"\"\n\n    path: StrictStr\n    \"\"\" str: Path to metadata store. Can be a local path, or remote object storage path, e.g. a GCS URI \"\"\"\n\n    cache_ttl_seconds: StrictInt = 600\n    \"\"\"int: The cache TTL is the amount of time registry state will be cached in memory. If this TTL is exceeded then\n     the registry will be refreshed when any feature store method asks for access to registry state. The TTL can be\n     set to infinity by setting TTL to 0 seconds, which means the cache will only be loaded once and will never\n     expire. Users can manually refresh the cache by calling feature_store.refresh_registry() \"\"\"\n\n\nclass RepoConfig(FeastBaseModel):\n    \"\"\" Repo config. Typically loaded from `feature_store.yaml` \"\"\"\n\n    registry: Union[StrictStr, RegistryConfig] = \"data/registry.db\"\n    \"\"\" str: Path to metadata store. Can be a local path, or remote object storage path, e.g. a GCS URI \"\"\"\n\n    project: StrictStr\n    \"\"\" str: Feast project id. This can be any alphanumeric string up to 16 characters.\n        You can have multiple independent feature repositories deployed to the same cloud\n        provider account, as long as they have different project ids.\n    \"\"\"\n\n    provider: StrictStr\n    \"\"\" str: local or gcp or aws \"\"\"\n\n    online_store: Any\n    \"\"\" OnlineStoreConfig: Online store configuration (optional depending on provider) \"\"\"\n\n    offline_store: Any\n    \"\"\" OfflineStoreConfig: Offline store configuration (optional depending on provider) \"\"\"\n\n    repo_path: Optional[Path] = None\n\n    def __init__(self, **data: Any):\n        super().__init__(**data)\n\n        if isinstance(self.online_store, Dict):\n            self.online_store = get_online_config_from_type(self.online_store[\"type\"])(\n                **self.online_store\n            )\n        elif isinstance(self.online_store, str):\n            self.online_store = get_online_config_from_type(self.online_store)()\n\n        if isinstance(self.offline_store, Dict):\n            self.offline_store = get_offline_config_from_type(\n                self.offline_store[\"type\"]\n            )(**self.offline_store)\n        elif isinstance(self.offline_store, str):\n            self.offline_store = get_offline_config_from_type(self.offline_store)()\n\n    def get_registry_config(self):\n        if isinstance(self.registry, str):\n            return RegistryConfig(path=self.registry)\n        else:\n            return self.registry\n\n    @root_validator(pre=True)\n    @log_exceptions\n    def _validate_online_store_config(cls, values):\n        # This method will validate whether the online store configurations are set correctly. This explicit validation\n        # is necessary because Pydantic Unions throw very verbose and cryptic exceptions. We also use this method to\n        # impute the default online store type based on the selected provider. For the time being this method should be\n        # considered tech debt until we can implement https://github.com/samuelcolvin/pydantic/issues/619 or a more\n        # granular configuration system\n\n        # Set empty online_store config if it isn't set explicitly\n        if \"online_store\" not in values:\n            values[\"online_store\"] = dict()\n\n        # Skip if we aren't creating the configuration from a dict\n        if not isinstance(values[\"online_store\"], Dict):\n            return values\n\n        # Make sure that the provider configuration is set. We need it to set the defaults\n        assert \"provider\" in values\n\n        # Set the default type\n        # This is only direct reference to a provider or online store that we should have\n        # for backwards compatibility.\n        if \"type\" not in values[\"online_store\"]:\n            if values[\"provider\"] == \"local\":\n                values[\"online_store\"][\"type\"] = \"sqlite\"\n            elif values[\"provider\"] == \"gcp\":\n                values[\"online_store\"][\"type\"] = \"datastore\"\n            elif values[\"provider\"] == \"aws\":\n                values[\"online_store\"][\"type\"] = \"dynamodb\"\n\n        online_store_type = values[\"online_store\"][\"type\"]\n\n        # Validate the dict to ensure one of the union types match\n        try:\n            online_config_class = get_online_config_from_type(online_store_type)\n            online_config_class(**values[\"online_store\"])\n        except ValidationError as e:\n            raise ValidationError(\n                [ErrorWrapper(e, loc=\"online_store\")], model=RepoConfig,\n            )\n\n        return values\n\n    @root_validator(pre=True)\n    def _validate_offline_store_config(cls, values):\n        # Set empty offline_store config if it isn't set explicitly\n        if \"offline_store\" not in values:\n            values[\"offline_store\"] = dict()\n\n        # Skip if we aren't creating the configuration from a dict\n        if not isinstance(values[\"offline_store\"], Dict):\n            return values\n\n        # Make sure that the provider configuration is set. We need it to set the defaults\n        assert \"provider\" in values\n\n        # Set the default type\n        if \"type\" not in values[\"offline_store\"]:\n            if values[\"provider\"] == \"local\":\n                values[\"offline_store\"][\"type\"] = \"file\"\n            elif values[\"provider\"] == \"gcp\":\n                values[\"offline_store\"][\"type\"] = \"bigquery\"\n            elif values[\"provider\"] == \"aws\":\n                values[\"offline_store\"][\"type\"] = \"redshift\"\n\n        offline_store_type = values[\"offline_store\"][\"type\"]\n\n        # Validate the dict to ensure one of the union types match\n        try:\n            offline_config_class = get_offline_config_from_type(offline_store_type)\n            offline_config_class(**values[\"offline_store\"])\n        except ValidationError as e:\n            raise ValidationError(\n                [ErrorWrapper(e, loc=\"offline_store\")], model=RepoConfig,\n            )\n\n        return values\n\n\nclass FeastConfigError(Exception):\n    def __init__(self, error_message, config_path):\n        self._error_message = error_message\n        self._config_path = config_path\n        super().__init__(self._error_message)\n\n    def __str__(self) -> str:\n        return f\"{self._error_message}\\nat {self._config_path}\"\n\n    def __repr__(self) -> str:\n        return (\n            f\"FeastConfigError({repr(self._error_message)}, {repr(self._config_path)})\"\n        )\n\n\ndef get_data_source_class_from_type(data_source_type: str):\n    module_name, config_class_name = data_source_type.rsplit(\".\", 1)\n    return get_class_from_type(module_name, config_class_name, \"Source\")\n\n\ndef get_online_config_from_type(online_store_type: str):\n    if online_store_type in ONLINE_STORE_CLASS_FOR_TYPE:\n        online_store_type = ONLINE_STORE_CLASS_FOR_TYPE[online_store_type]\n    else:\n        assert online_store_type.endswith(\"OnlineStore\")\n    module_name, online_store_class_type = online_store_type.rsplit(\".\", 1)\n    config_class_name = f\"{online_store_class_type}Config\"\n\n    return get_class_from_type(module_name, config_class_name, config_class_name)\n\n\ndef get_offline_config_from_type(offline_store_type: str):\n    if offline_store_type in OFFLINE_STORE_CLASS_FOR_TYPE:\n        offline_store_type = OFFLINE_STORE_CLASS_FOR_TYPE[offline_store_type]\n    else:\n        assert offline_store_type.endswith(\"OfflineStore\")\n    module_name, offline_store_class_type = offline_store_type.rsplit(\".\", 1)\n    config_class_name = f\"{offline_store_class_type}Config\"\n\n    return get_class_from_type(module_name, config_class_name, config_class_name)\n\n\ndef load_repo_config(repo_path: Path) -> RepoConfig:\n    config_path = repo_path / \"feature_store.yaml\"\n\n    with open(config_path) as f:\n        raw_config = yaml.safe_load(f)\n        try:\n            c = RepoConfig(**raw_config)\n            c.repo_path = repo_path\n            return c\n        except ValidationError as e:\n            raise FeastConfigError(e, config_path)\n", "path": "sdk/python/feast/repo_config.py"}], "after_files": [{"content": "from pathlib import Path\nfrom typing import Any\n\nimport yaml\nfrom pydantic import (\n    BaseModel,\n    StrictInt,\n    StrictStr,\n    ValidationError,\n    root_validator,\n    validator,\n)\nfrom pydantic.error_wrappers import ErrorWrapper\nfrom pydantic.typing import Dict, Optional, Union\n\nfrom feast.importer import get_class_from_type\nfrom feast.usage import log_exceptions\n\n# These dict exists so that:\n# - existing values for the online store type in featurestore.yaml files continue to work in a backwards compatible way\n# - first party and third party implementations can use the same class loading code path.\nONLINE_STORE_CLASS_FOR_TYPE = {\n    \"sqlite\": \"feast.infra.online_stores.sqlite.SqliteOnlineStore\",\n    \"datastore\": \"feast.infra.online_stores.datastore.DatastoreOnlineStore\",\n    \"redis\": \"feast.infra.online_stores.redis.RedisOnlineStore\",\n    \"dynamodb\": \"feast.infra.online_stores.dynamodb.DynamoDBOnlineStore\",\n}\n\nOFFLINE_STORE_CLASS_FOR_TYPE = {\n    \"file\": \"feast.infra.offline_stores.file.FileOfflineStore\",\n    \"bigquery\": \"feast.infra.offline_stores.bigquery.BigQueryOfflineStore\",\n    \"redshift\": \"feast.infra.offline_stores.redshift.RedshiftOfflineStore\",\n}\n\n\nclass FeastBaseModel(BaseModel):\n    \"\"\" Feast Pydantic Configuration Class \"\"\"\n\n    class Config:\n        arbitrary_types_allowed = True\n        extra = \"allow\"\n\n\nclass FeastConfigBaseModel(BaseModel):\n    \"\"\" Feast Pydantic Configuration Class \"\"\"\n\n    class Config:\n        arbitrary_types_allowed = True\n        extra = \"forbid\"\n\n\nclass RegistryConfig(FeastBaseModel):\n    \"\"\" Metadata Store Configuration. Configuration that relates to reading from and writing to the Feast registry.\"\"\"\n\n    path: StrictStr\n    \"\"\" str: Path to metadata store. Can be a local path, or remote object storage path, e.g. a GCS URI \"\"\"\n\n    cache_ttl_seconds: StrictInt = 600\n    \"\"\"int: The cache TTL is the amount of time registry state will be cached in memory. If this TTL is exceeded then\n     the registry will be refreshed when any feature store method asks for access to registry state. The TTL can be\n     set to infinity by setting TTL to 0 seconds, which means the cache will only be loaded once and will never\n     expire. Users can manually refresh the cache by calling feature_store.refresh_registry() \"\"\"\n\n\nclass RepoConfig(FeastBaseModel):\n    \"\"\" Repo config. Typically loaded from `feature_store.yaml` \"\"\"\n\n    registry: Union[StrictStr, RegistryConfig] = \"data/registry.db\"\n    \"\"\" str: Path to metadata store. Can be a local path, or remote object storage path, e.g. a GCS URI \"\"\"\n\n    project: StrictStr\n    \"\"\" str: Feast project id. This can be any alphanumeric string up to 16 characters.\n        You can have multiple independent feature repositories deployed to the same cloud\n        provider account, as long as they have different project ids.\n    \"\"\"\n\n    provider: StrictStr\n    \"\"\" str: local or gcp or aws \"\"\"\n\n    online_store: Any\n    \"\"\" OnlineStoreConfig: Online store configuration (optional depending on provider) \"\"\"\n\n    offline_store: Any\n    \"\"\" OfflineStoreConfig: Offline store configuration (optional depending on provider) \"\"\"\n\n    repo_path: Optional[Path] = None\n\n    def __init__(self, **data: Any):\n        super().__init__(**data)\n\n        if isinstance(self.online_store, Dict):\n            self.online_store = get_online_config_from_type(self.online_store[\"type\"])(\n                **self.online_store\n            )\n        elif isinstance(self.online_store, str):\n            self.online_store = get_online_config_from_type(self.online_store)()\n\n        if isinstance(self.offline_store, Dict):\n            self.offline_store = get_offline_config_from_type(\n                self.offline_store[\"type\"]\n            )(**self.offline_store)\n        elif isinstance(self.offline_store, str):\n            self.offline_store = get_offline_config_from_type(self.offline_store)()\n\n    def get_registry_config(self):\n        if isinstance(self.registry, str):\n            return RegistryConfig(path=self.registry)\n        else:\n            return self.registry\n\n    @root_validator(pre=True)\n    @log_exceptions\n    def _validate_online_store_config(cls, values):\n        # This method will validate whether the online store configurations are set correctly. This explicit validation\n        # is necessary because Pydantic Unions throw very verbose and cryptic exceptions. We also use this method to\n        # impute the default online store type based on the selected provider. For the time being this method should be\n        # considered tech debt until we can implement https://github.com/samuelcolvin/pydantic/issues/619 or a more\n        # granular configuration system\n\n        # Set empty online_store config if it isn't set explicitly\n        if \"online_store\" not in values:\n            values[\"online_store\"] = dict()\n\n        # Skip if we aren't creating the configuration from a dict\n        if not isinstance(values[\"online_store\"], Dict):\n            return values\n\n        # Make sure that the provider configuration is set. We need it to set the defaults\n        assert \"provider\" in values\n\n        # Set the default type\n        # This is only direct reference to a provider or online store that we should have\n        # for backwards compatibility.\n        if \"type\" not in values[\"online_store\"]:\n            if values[\"provider\"] == \"local\":\n                values[\"online_store\"][\"type\"] = \"sqlite\"\n            elif values[\"provider\"] == \"gcp\":\n                values[\"online_store\"][\"type\"] = \"datastore\"\n            elif values[\"provider\"] == \"aws\":\n                values[\"online_store\"][\"type\"] = \"dynamodb\"\n\n        online_store_type = values[\"online_store\"][\"type\"]\n\n        # Validate the dict to ensure one of the union types match\n        try:\n            online_config_class = get_online_config_from_type(online_store_type)\n            online_config_class(**values[\"online_store\"])\n        except ValidationError as e:\n            raise ValidationError(\n                [ErrorWrapper(e, loc=\"online_store\")], model=RepoConfig,\n            )\n\n        return values\n\n    @root_validator(pre=True)\n    def _validate_offline_store_config(cls, values):\n        # Set empty offline_store config if it isn't set explicitly\n        if \"offline_store\" not in values:\n            values[\"offline_store\"] = dict()\n\n        # Skip if we aren't creating the configuration from a dict\n        if not isinstance(values[\"offline_store\"], Dict):\n            return values\n\n        # Make sure that the provider configuration is set. We need it to set the defaults\n        assert \"provider\" in values\n\n        # Set the default type\n        if \"type\" not in values[\"offline_store\"]:\n            if values[\"provider\"] == \"local\":\n                values[\"offline_store\"][\"type\"] = \"file\"\n            elif values[\"provider\"] == \"gcp\":\n                values[\"offline_store\"][\"type\"] = \"bigquery\"\n            elif values[\"provider\"] == \"aws\":\n                values[\"offline_store\"][\"type\"] = \"redshift\"\n\n        offline_store_type = values[\"offline_store\"][\"type\"]\n\n        # Validate the dict to ensure one of the union types match\n        try:\n            offline_config_class = get_offline_config_from_type(offline_store_type)\n            offline_config_class(**values[\"offline_store\"])\n        except ValidationError as e:\n            raise ValidationError(\n                [ErrorWrapper(e, loc=\"offline_store\")], model=RepoConfig,\n            )\n\n        return values\n\n    @validator(\"project\")\n    def _validate_project_name(cls, v):\n        from feast.repo_operations import is_valid_name\n\n        if not is_valid_name(v):\n            raise ValueError(\n                f\"Project name, {v}, should only have \"\n                f\"alphanumerical values and underscores but not start with an underscore.\"\n            )\n        return v\n\n\nclass FeastConfigError(Exception):\n    def __init__(self, error_message, config_path):\n        self._error_message = error_message\n        self._config_path = config_path\n        super().__init__(self._error_message)\n\n    def __str__(self) -> str:\n        return f\"{self._error_message}\\nat {self._config_path}\"\n\n    def __repr__(self) -> str:\n        return (\n            f\"FeastConfigError({repr(self._error_message)}, {repr(self._config_path)})\"\n        )\n\n\ndef get_data_source_class_from_type(data_source_type: str):\n    module_name, config_class_name = data_source_type.rsplit(\".\", 1)\n    return get_class_from_type(module_name, config_class_name, \"Source\")\n\n\ndef get_online_config_from_type(online_store_type: str):\n    if online_store_type in ONLINE_STORE_CLASS_FOR_TYPE:\n        online_store_type = ONLINE_STORE_CLASS_FOR_TYPE[online_store_type]\n    else:\n        assert online_store_type.endswith(\"OnlineStore\")\n    module_name, online_store_class_type = online_store_type.rsplit(\".\", 1)\n    config_class_name = f\"{online_store_class_type}Config\"\n\n    return get_class_from_type(module_name, config_class_name, config_class_name)\n\n\ndef get_offline_config_from_type(offline_store_type: str):\n    if offline_store_type in OFFLINE_STORE_CLASS_FOR_TYPE:\n        offline_store_type = OFFLINE_STORE_CLASS_FOR_TYPE[offline_store_type]\n    else:\n        assert offline_store_type.endswith(\"OfflineStore\")\n    module_name, offline_store_class_type = offline_store_type.rsplit(\".\", 1)\n    config_class_name = f\"{offline_store_class_type}Config\"\n\n    return get_class_from_type(module_name, config_class_name, config_class_name)\n\n\ndef load_repo_config(repo_path: Path) -> RepoConfig:\n    config_path = repo_path / \"feature_store.yaml\"\n\n    with open(config_path) as f:\n        raw_config = yaml.safe_load(f)\n        try:\n            c = RepoConfig(**raw_config)\n            c.repo_path = repo_path\n            return c\n        except ValidationError as e:\n            raise FeastConfigError(e, config_path)\n", "path": "sdk/python/feast/repo_config.py"}]}