We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
feast cli feature-sets describe broken
## Expected Behavior 
`feast feature-sets describe <name>` cannot be used because it doesn't allow the user to set a project, nor does it default to any value.

## Steps to reproduce
Call `feast feature-sets describe <some_feature_set>`

### Specifications

- Version: 0.5.0

## Possible Solution

The method calls `fs = feast_client.get_feature_set(name=name)`. Since no project is provided to `get_feature_set`, a default project needs to be set in the client. 

Either 
1. Allow users to pass feature set ids with projects specified (`project/feature_set`) or
2. Allow users to set a default project.

The method should fall back to a default project (`default`) should one not be provided.


--- 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/cli.py`
Content:
```
1 # Copyright 2019 The Feast Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     https://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import json
16 import logging
17 import sys
18 
19 import click
20 import pkg_resources
21 import yaml
22 
23 from feast.client import Client
24 from feast.config import Config
25 from feast.core.IngestionJob_pb2 import IngestionJobStatus
26 from feast.feature_set import FeatureSet, FeatureSetRef
27 from feast.loaders.yaml import yaml_loader
28 
29 _logger = logging.getLogger(__name__)
30 
31 _common_options = [
32     click.option("--core-url", help="Set Feast core URL to connect to"),
33     click.option("--serving-url", help="Set Feast serving URL to connect to"),
34 ]
35 
36 
37 def common_options(func):
38     """
39     Options that are available for most CLI commands
40     """
41     for option in reversed(_common_options):
42         func = option(func)
43     return func
44 
45 
46 @click.group()
47 def cli():
48     pass
49 
50 
51 @cli.command()
52 @click.option(
53     "--client-only", "-c", is_flag=True, help="Print only the version of the CLI"
54 )
55 @common_options
56 def version(client_only: bool, **kwargs):
57     """
58     Displays version and connectivity information
59     """
60 
61     try:
62         feast_versions_dict = {
63             "sdk": {"version": str(pkg_resources.get_distribution("feast"))}
64         }
65 
66         if not client_only:
67             feast_client = Client(**kwargs)
68             feast_versions_dict.update(feast_client.version())
69 
70         print(json.dumps(feast_versions_dict))
71     except Exception as e:
72         _logger.error("Error initializing backend store")
73         _logger.exception(e)
74         sys.exit(1)
75 
76 
77 @cli.group()
78 def config():
79     """
80     View and edit Feast properties
81     """
82     pass
83 
84 
85 @config.command(name="list")
86 def config_list():
87     """
88     List Feast properties for the currently active configuration
89     """
90     try:
91         print(Config())
92     except Exception as e:
93         _logger.error("Error occurred when reading Feast configuration file")
94         _logger.exception(e)
95         sys.exit(1)
96 
97 
98 @config.command(name="set")
99 @click.argument("prop")
100 @click.argument("value")
101 def config_set(prop, value):
102     """
103     Set a Feast properties for the currently active configuration
104     """
105     try:
106         conf = Config()
107         conf.set(option=prop.strip(), value=value.strip())
108         conf.save()
109     except Exception as e:
110         _logger.error("Error in reading config file")
111         _logger.exception(e)
112         sys.exit(1)
113 
114 
115 @cli.group(name="feature-sets")
116 def feature_set():
117     """
118     Create and manage feature sets
119     """
120     pass
121 
122 
123 @feature_set.command(name="list")
124 def feature_set_list():
125     """
126     List all feature sets
127     """
128     feast_client = Client()  # type: Client
129 
130     table = []
131     for fs in feast_client.list_feature_sets(project="*", name="*"):
132         table.append([fs.name, repr(fs)])
133 
134     from tabulate import tabulate
135 
136     print(tabulate(table, headers=["NAME", "REFERENCE"], tablefmt="plain"))
137 
138 
139 @feature_set.command("apply")
140 # TODO: add project option to overwrite project setting.
141 @click.option(
142     "--filename",
143     "-f",
144     help="Path to a feature set configuration file that will be applied",
145     type=click.Path(exists=True),
146 )
147 def feature_set_create(filename):
148     """
149     Create or update a feature set
150     """
151 
152     feature_sets = [FeatureSet.from_dict(fs_dict) for fs_dict in yaml_loader(filename)]
153     feast_client = Client()  # type: Client
154     feast_client.apply(feature_sets)
155 
156 
157 @feature_set.command("describe")
158 @click.argument("name", type=click.STRING)
159 def feature_set_describe(name: str):
160     """
161     Describe a feature set
162     """
163     feast_client = Client()  # type: Client
164     fs = feast_client.get_feature_set(name=name)
165     if not fs:
166         print(f'Feature set with name "{name}" could not be found')
167         return
168 
169     print(yaml.dump(yaml.safe_load(str(fs)), default_flow_style=False, sort_keys=False))
170 
171 
172 @cli.group(name="projects")
173 def project():
174     """
175     Create and manage projects
176     """
177     pass
178 
179 
180 @project.command(name="create")
181 @click.argument("name", type=click.STRING)
182 def project_create(name: str):
183     """
184     Create a project
185     """
186     feast_client = Client()  # type: Client
187     feast_client.create_project(name)
188 
189 
190 @project.command(name="archive")
191 @click.argument("name", type=click.STRING)
192 def project_archive(name: str):
193     """
194     Archive a project
195     """
196     feast_client = Client()  # type: Client
197     feast_client.archive_project(name)
198 
199 
200 @project.command(name="list")
201 def project_list():
202     """
203     List all projects
204     """
205     feast_client = Client()  # type: Client
206 
207     table = []
208     for project in feast_client.list_projects():
209         table.append([project])
210 
211     from tabulate import tabulate
212 
213     print(tabulate(table, headers=["NAME"], tablefmt="plain"))
214 
215 
216 @cli.group(name="ingest-jobs")
217 def ingest_job():
218     """
219     Manage ingestion jobs
220     """
221     pass
222 
223 
224 @ingest_job.command("list")
225 @click.option("--job-id", "-i", help="Show only ingestion jobs with the given job id")
226 @click.option(
227     "--feature-set-ref",
228     "-f",
229     help="Show only ingestion job targeting the feature set with the given reference",
230 )
231 @click.option(
232     "--store-name",
233     "-s",
234     help="List only ingestion job that ingest into feast store with given name",
235 )
236 # TODO: types
237 def ingest_job_list(job_id, feature_set_ref, store_name):
238     """
239     List ingestion jobs
240     """
241     # parse feature set reference
242     if feature_set_ref is not None:
243         feature_set_ref = FeatureSetRef.from_str(feature_set_ref)
244 
245     # pull & render ingestion jobs as a table
246     feast_client = Client()
247     table = []
248     for ingest_job in feast_client.list_ingest_jobs(
249         job_id=job_id, feature_set_ref=feature_set_ref, store_name=store_name
250     ):
251         table.append([ingest_job.id, IngestionJobStatus.Name(ingest_job.status)])
252 
253     from tabulate import tabulate
254 
255     print(tabulate(table, headers=["ID", "STATUS"], tablefmt="plain"))
256 
257 
258 @ingest_job.command("describe")
259 @click.argument("job_id")
260 def ingest_job_describe(job_id: str):
261     """
262     Describe the ingestion job with the given id.
263     """
264     # find ingestion job for id
265     feast_client = Client()
266     jobs = feast_client.list_ingest_jobs(job_id=job_id)
267     if len(jobs) < 1:
268         print(f"Ingestion Job with id {job_id} could not be found")
269         sys.exit(1)
270     job = jobs[0]
271 
272     # pretty render ingestion job as yaml
273     print(
274         yaml.dump(yaml.safe_load(str(job)), default_flow_style=False, sort_keys=False)
275     )
276 
277 
278 @ingest_job.command("stop")
279 @click.option(
280     "--wait", "-w", is_flag=True, help="Wait for the ingestion job to fully stop."
281 )
282 @click.option(
283     "--timeout",
284     "-t",
285     default=600,
286     help="Timeout in seconds to wait for the job to stop.",
287 )
288 @click.argument("job_id")
289 def ingest_job_stop(wait: bool, timeout: int, job_id: str):
290     """
291     Stop ingestion job for id.
292     """
293     # find ingestion job for id
294     feast_client = Client()
295     jobs = feast_client.list_ingest_jobs(job_id=job_id)
296     if len(jobs) < 1:
297         print(f"Ingestion Job with id {job_id} could not be found")
298         sys.exit(1)
299     job = jobs[0]
300 
301     feast_client.stop_ingest_job(job)
302 
303     # wait for ingestion job to stop
304     if wait:
305         job.wait(IngestionJobStatus.ABORTED, timeout=timeout)
306 
307 
308 @ingest_job.command("restart")
309 @click.argument("job_id")
310 def ingest_job_restart(job_id: str):
311     """
312     Restart job for id.
313     Waits for the job to fully restart.
314     """
315     # find ingestion job for id
316     feast_client = Client()
317     jobs = feast_client.list_ingest_jobs(job_id=job_id)
318     if len(jobs) < 1:
319         print(f"Ingestion Job with id {job_id} could not be found")
320         sys.exit(1)
321     job = jobs[0]
322 
323     feast_client.restart_ingest_job(job)
324 
325 
326 @cli.command()
327 @click.option(
328     "--name", "-n", help="Feature set name to ingest data into", required=True
329 )
330 @click.option(
331     "--filename",
332     "-f",
333     help="Path to file to be ingested",
334     type=click.Path(exists=True),
335     required=True,
336 )
337 @click.option(
338     "--file-type",
339     "-t",
340     type=click.Choice(["CSV"], case_sensitive=False),
341     help="Type of file to ingest. Defaults to CSV.",
342 )
343 def ingest(name, filename, file_type):
344     """
345     Ingest feature data into a feature set
346     """
347 
348     feast_client = Client()  # type: Client
349     feature_set = feast_client.get_feature_set(name=name)
350     feature_set.ingest_file(file_path=filename)
351 
352 
353 if __name__ == "__main__":
354     cli()
355
```
--- 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/cli.py b/sdk/python/feast/cli.py
--- a/sdk/python/feast/cli.py
+++ b/sdk/python/feast/cli.py
@@ -156,12 +156,20 @@
 
 @feature_set.command("describe")
 @click.argument("name", type=click.STRING)
-def feature_set_describe(name: str):
+@click.option(
+    "--project",
+    "-p",
+    help="Project that feature set belongs to",
+    type=click.STRING,
+    default="default",
+)
+def feature_set_describe(name: str, project: str):
     """
     Describe a feature set
     """
     feast_client = Client()  # type: Client
-    fs = feast_client.get_feature_set(name=name)
+    fs = feast_client.get_feature_set(name=name, project=project)
+
     if not fs:
         print(f'Feature set with name "{name}" could not be found')
         return

{"golden_diff": "diff --git a/sdk/python/feast/cli.py b/sdk/python/feast/cli.py\n--- a/sdk/python/feast/cli.py\n+++ b/sdk/python/feast/cli.py\n@@ -156,12 +156,20 @@\n \n @feature_set.command(\"describe\")\n @click.argument(\"name\", type=click.STRING)\n-def feature_set_describe(name: str):\n+@click.option(\n+    \"--project\",\n+    \"-p\",\n+    help=\"Project that feature set belongs to\",\n+    type=click.STRING,\n+    default=\"default\",\n+)\n+def feature_set_describe(name: str, project: str):\n     \"\"\"\n     Describe a feature set\n     \"\"\"\n     feast_client = Client()  # type: Client\n-    fs = feast_client.get_feature_set(name=name)\n+    fs = feast_client.get_feature_set(name=name, project=project)\n+\n     if not fs:\n         print(f'Feature set with name \"{name}\" could not be found')\n         return\n", "issue": "feast cli feature-sets describe broken\n## Expected Behavior \r\n`feast feature-sets describe <name>` cannot be used because it doesn't allow the user to set a project, nor does it default to any value.\r\n\r\n## Steps to reproduce\r\nCall `feast feature-sets describe <some_feature_set>`\r\n\r\n### Specifications\r\n\r\n- Version: 0.5.0\r\n\r\n## Possible Solution\r\n\r\nThe method calls `fs = feast_client.get_feature_set(name=name)`. Since no project is provided to `get_feature_set`, a default project needs to be set in the client. \r\n\r\nEither \r\n1. Allow users to pass feature set ids with projects specified (`project/feature_set`) or\r\n2. Allow users to set a default project.\r\n\r\nThe method should fall back to a default project (`default`) should one not be provided.\r\n\n", "before_files": [{"content": "# Copyright 2019 The Feast Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport logging\nimport sys\n\nimport click\nimport pkg_resources\nimport yaml\n\nfrom feast.client import Client\nfrom feast.config import Config\nfrom feast.core.IngestionJob_pb2 import IngestionJobStatus\nfrom feast.feature_set import FeatureSet, FeatureSetRef\nfrom feast.loaders.yaml import yaml_loader\n\n_logger = logging.getLogger(__name__)\n\n_common_options = [\n    click.option(\"--core-url\", help=\"Set Feast core URL to connect to\"),\n    click.option(\"--serving-url\", help=\"Set Feast serving URL to connect to\"),\n]\n\n\ndef common_options(func):\n    \"\"\"\n    Options that are available for most CLI commands\n    \"\"\"\n    for option in reversed(_common_options):\n        func = option(func)\n    return func\n\n\n@click.group()\ndef cli():\n    pass\n\n\n@cli.command()\n@click.option(\n    \"--client-only\", \"-c\", is_flag=True, help=\"Print only the version of the CLI\"\n)\n@common_options\ndef version(client_only: bool, **kwargs):\n    \"\"\"\n    Displays version and connectivity information\n    \"\"\"\n\n    try:\n        feast_versions_dict = {\n            \"sdk\": {\"version\": str(pkg_resources.get_distribution(\"feast\"))}\n        }\n\n        if not client_only:\n            feast_client = Client(**kwargs)\n            feast_versions_dict.update(feast_client.version())\n\n        print(json.dumps(feast_versions_dict))\n    except Exception as e:\n        _logger.error(\"Error initializing backend store\")\n        _logger.exception(e)\n        sys.exit(1)\n\n\n@cli.group()\ndef config():\n    \"\"\"\n    View and edit Feast properties\n    \"\"\"\n    pass\n\n\n@config.command(name=\"list\")\ndef config_list():\n    \"\"\"\n    List Feast properties for the currently active configuration\n    \"\"\"\n    try:\n        print(Config())\n    except Exception as e:\n        _logger.error(\"Error occurred when reading Feast configuration file\")\n        _logger.exception(e)\n        sys.exit(1)\n\n\n@config.command(name=\"set\")\n@click.argument(\"prop\")\n@click.argument(\"value\")\ndef config_set(prop, value):\n    \"\"\"\n    Set a Feast properties for the currently active configuration\n    \"\"\"\n    try:\n        conf = Config()\n        conf.set(option=prop.strip(), value=value.strip())\n        conf.save()\n    except Exception as e:\n        _logger.error(\"Error in reading config file\")\n        _logger.exception(e)\n        sys.exit(1)\n\n\n@cli.group(name=\"feature-sets\")\ndef feature_set():\n    \"\"\"\n    Create and manage feature sets\n    \"\"\"\n    pass\n\n\n@feature_set.command(name=\"list\")\ndef feature_set_list():\n    \"\"\"\n    List all feature sets\n    \"\"\"\n    feast_client = Client()  # type: Client\n\n    table = []\n    for fs in feast_client.list_feature_sets(project=\"*\", name=\"*\"):\n        table.append([fs.name, repr(fs)])\n\n    from tabulate import tabulate\n\n    print(tabulate(table, headers=[\"NAME\", \"REFERENCE\"], tablefmt=\"plain\"))\n\n\n@feature_set.command(\"apply\")\n# TODO: add project option to overwrite project setting.\n@click.option(\n    \"--filename\",\n    \"-f\",\n    help=\"Path to a feature set configuration file that will be applied\",\n    type=click.Path(exists=True),\n)\ndef feature_set_create(filename):\n    \"\"\"\n    Create or update a feature set\n    \"\"\"\n\n    feature_sets = [FeatureSet.from_dict(fs_dict) for fs_dict in yaml_loader(filename)]\n    feast_client = Client()  # type: Client\n    feast_client.apply(feature_sets)\n\n\n@feature_set.command(\"describe\")\n@click.argument(\"name\", type=click.STRING)\ndef feature_set_describe(name: str):\n    \"\"\"\n    Describe a feature set\n    \"\"\"\n    feast_client = Client()  # type: Client\n    fs = feast_client.get_feature_set(name=name)\n    if not fs:\n        print(f'Feature set with name \"{name}\" could not be found')\n        return\n\n    print(yaml.dump(yaml.safe_load(str(fs)), default_flow_style=False, sort_keys=False))\n\n\n@cli.group(name=\"projects\")\ndef project():\n    \"\"\"\n    Create and manage projects\n    \"\"\"\n    pass\n\n\n@project.command(name=\"create\")\n@click.argument(\"name\", type=click.STRING)\ndef project_create(name: str):\n    \"\"\"\n    Create a project\n    \"\"\"\n    feast_client = Client()  # type: Client\n    feast_client.create_project(name)\n\n\n@project.command(name=\"archive\")\n@click.argument(\"name\", type=click.STRING)\ndef project_archive(name: str):\n    \"\"\"\n    Archive a project\n    \"\"\"\n    feast_client = Client()  # type: Client\n    feast_client.archive_project(name)\n\n\n@project.command(name=\"list\")\ndef project_list():\n    \"\"\"\n    List all projects\n    \"\"\"\n    feast_client = Client()  # type: Client\n\n    table = []\n    for project in feast_client.list_projects():\n        table.append([project])\n\n    from tabulate import tabulate\n\n    print(tabulate(table, headers=[\"NAME\"], tablefmt=\"plain\"))\n\n\n@cli.group(name=\"ingest-jobs\")\ndef ingest_job():\n    \"\"\"\n    Manage ingestion jobs\n    \"\"\"\n    pass\n\n\n@ingest_job.command(\"list\")\n@click.option(\"--job-id\", \"-i\", help=\"Show only ingestion jobs with the given job id\")\n@click.option(\n    \"--feature-set-ref\",\n    \"-f\",\n    help=\"Show only ingestion job targeting the feature set with the given reference\",\n)\n@click.option(\n    \"--store-name\",\n    \"-s\",\n    help=\"List only ingestion job that ingest into feast store with given name\",\n)\n# TODO: types\ndef ingest_job_list(job_id, feature_set_ref, store_name):\n    \"\"\"\n    List ingestion jobs\n    \"\"\"\n    # parse feature set reference\n    if feature_set_ref is not None:\n        feature_set_ref = FeatureSetRef.from_str(feature_set_ref)\n\n    # pull & render ingestion jobs as a table\n    feast_client = Client()\n    table = []\n    for ingest_job in feast_client.list_ingest_jobs(\n        job_id=job_id, feature_set_ref=feature_set_ref, store_name=store_name\n    ):\n        table.append([ingest_job.id, IngestionJobStatus.Name(ingest_job.status)])\n\n    from tabulate import tabulate\n\n    print(tabulate(table, headers=[\"ID\", \"STATUS\"], tablefmt=\"plain\"))\n\n\n@ingest_job.command(\"describe\")\n@click.argument(\"job_id\")\ndef ingest_job_describe(job_id: str):\n    \"\"\"\n    Describe the ingestion job with the given id.\n    \"\"\"\n    # find ingestion job for id\n    feast_client = Client()\n    jobs = feast_client.list_ingest_jobs(job_id=job_id)\n    if len(jobs) < 1:\n        print(f\"Ingestion Job with id {job_id} could not be found\")\n        sys.exit(1)\n    job = jobs[0]\n\n    # pretty render ingestion job as yaml\n    print(\n        yaml.dump(yaml.safe_load(str(job)), default_flow_style=False, sort_keys=False)\n    )\n\n\n@ingest_job.command(\"stop\")\n@click.option(\n    \"--wait\", \"-w\", is_flag=True, help=\"Wait for the ingestion job to fully stop.\"\n)\n@click.option(\n    \"--timeout\",\n    \"-t\",\n    default=600,\n    help=\"Timeout in seconds to wait for the job to stop.\",\n)\n@click.argument(\"job_id\")\ndef ingest_job_stop(wait: bool, timeout: int, job_id: str):\n    \"\"\"\n    Stop ingestion job for id.\n    \"\"\"\n    # find ingestion job for id\n    feast_client = Client()\n    jobs = feast_client.list_ingest_jobs(job_id=job_id)\n    if len(jobs) < 1:\n        print(f\"Ingestion Job with id {job_id} could not be found\")\n        sys.exit(1)\n    job = jobs[0]\n\n    feast_client.stop_ingest_job(job)\n\n    # wait for ingestion job to stop\n    if wait:\n        job.wait(IngestionJobStatus.ABORTED, timeout=timeout)\n\n\n@ingest_job.command(\"restart\")\n@click.argument(\"job_id\")\ndef ingest_job_restart(job_id: str):\n    \"\"\"\n    Restart job for id.\n    Waits for the job to fully restart.\n    \"\"\"\n    # find ingestion job for id\n    feast_client = Client()\n    jobs = feast_client.list_ingest_jobs(job_id=job_id)\n    if len(jobs) < 1:\n        print(f\"Ingestion Job with id {job_id} could not be found\")\n        sys.exit(1)\n    job = jobs[0]\n\n    feast_client.restart_ingest_job(job)\n\n\n@cli.command()\n@click.option(\n    \"--name\", \"-n\", help=\"Feature set name to ingest data into\", required=True\n)\n@click.option(\n    \"--filename\",\n    \"-f\",\n    help=\"Path to file to be ingested\",\n    type=click.Path(exists=True),\n    required=True,\n)\n@click.option(\n    \"--file-type\",\n    \"-t\",\n    type=click.Choice([\"CSV\"], case_sensitive=False),\n    help=\"Type of file to ingest. Defaults to CSV.\",\n)\ndef ingest(name, filename, file_type):\n    \"\"\"\n    Ingest feature data into a feature set\n    \"\"\"\n\n    feast_client = Client()  # type: Client\n    feature_set = feast_client.get_feature_set(name=name)\n    feature_set.ingest_file(file_path=filename)\n\n\nif __name__ == \"__main__\":\n    cli()\n", "path": "sdk/python/feast/cli.py"}], "after_files": [{"content": "# Copyright 2019 The Feast Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport logging\nimport sys\n\nimport click\nimport pkg_resources\nimport yaml\n\nfrom feast.client import Client\nfrom feast.config import Config\nfrom feast.core.IngestionJob_pb2 import IngestionJobStatus\nfrom feast.feature_set import FeatureSet, FeatureSetRef\nfrom feast.loaders.yaml import yaml_loader\n\n_logger = logging.getLogger(__name__)\n\n_common_options = [\n    click.option(\"--core-url\", help=\"Set Feast core URL to connect to\"),\n    click.option(\"--serving-url\", help=\"Set Feast serving URL to connect to\"),\n]\n\n\ndef common_options(func):\n    \"\"\"\n    Options that are available for most CLI commands\n    \"\"\"\n    for option in reversed(_common_options):\n        func = option(func)\n    return func\n\n\n@click.group()\ndef cli():\n    pass\n\n\n@cli.command()\n@click.option(\n    \"--client-only\", \"-c\", is_flag=True, help=\"Print only the version of the CLI\"\n)\n@common_options\ndef version(client_only: bool, **kwargs):\n    \"\"\"\n    Displays version and connectivity information\n    \"\"\"\n\n    try:\n        feast_versions_dict = {\n            \"sdk\": {\"version\": str(pkg_resources.get_distribution(\"feast\"))}\n        }\n\n        if not client_only:\n            feast_client = Client(**kwargs)\n            feast_versions_dict.update(feast_client.version())\n\n        print(json.dumps(feast_versions_dict))\n    except Exception as e:\n        _logger.error(\"Error initializing backend store\")\n        _logger.exception(e)\n        sys.exit(1)\n\n\n@cli.group()\ndef config():\n    \"\"\"\n    View and edit Feast properties\n    \"\"\"\n    pass\n\n\n@config.command(name=\"list\")\ndef config_list():\n    \"\"\"\n    List Feast properties for the currently active configuration\n    \"\"\"\n    try:\n        print(Config())\n    except Exception as e:\n        _logger.error(\"Error occurred when reading Feast configuration file\")\n        _logger.exception(e)\n        sys.exit(1)\n\n\n@config.command(name=\"set\")\n@click.argument(\"prop\")\n@click.argument(\"value\")\ndef config_set(prop, value):\n    \"\"\"\n    Set a Feast properties for the currently active configuration\n    \"\"\"\n    try:\n        conf = Config()\n        conf.set(option=prop.strip(), value=value.strip())\n        conf.save()\n    except Exception as e:\n        _logger.error(\"Error in reading config file\")\n        _logger.exception(e)\n        sys.exit(1)\n\n\n@cli.group(name=\"feature-sets\")\ndef feature_set():\n    \"\"\"\n    Create and manage feature sets\n    \"\"\"\n    pass\n\n\n@feature_set.command(name=\"list\")\ndef feature_set_list():\n    \"\"\"\n    List all feature sets\n    \"\"\"\n    feast_client = Client()  # type: Client\n\n    table = []\n    for fs in feast_client.list_feature_sets(project=\"*\", name=\"*\"):\n        table.append([fs.name, repr(fs)])\n\n    from tabulate import tabulate\n\n    print(tabulate(table, headers=[\"NAME\", \"REFERENCE\"], tablefmt=\"plain\"))\n\n\n@feature_set.command(\"apply\")\n# TODO: add project option to overwrite project setting.\n@click.option(\n    \"--filename\",\n    \"-f\",\n    help=\"Path to a feature set configuration file that will be applied\",\n    type=click.Path(exists=True),\n)\ndef feature_set_create(filename):\n    \"\"\"\n    Create or update a feature set\n    \"\"\"\n\n    feature_sets = [FeatureSet.from_dict(fs_dict) for fs_dict in yaml_loader(filename)]\n    feast_client = Client()  # type: Client\n    feast_client.apply(feature_sets)\n\n\n@feature_set.command(\"describe\")\n@click.argument(\"name\", type=click.STRING)\n@click.option(\n    \"--project\",\n    \"-p\",\n    help=\"Project that feature set belongs to\",\n    type=click.STRING,\n    default=\"default\",\n)\ndef feature_set_describe(name: str, project: str):\n    \"\"\"\n    Describe a feature set\n    \"\"\"\n    feast_client = Client()  # type: Client\n    fs = feast_client.get_feature_set(name=name, project=project)\n\n    if not fs:\n        print(f'Feature set with name \"{name}\" could not be found')\n        return\n\n    print(yaml.dump(yaml.safe_load(str(fs)), default_flow_style=False, sort_keys=False))\n\n\n@cli.group(name=\"projects\")\ndef project():\n    \"\"\"\n    Create and manage projects\n    \"\"\"\n    pass\n\n\n@project.command(name=\"create\")\n@click.argument(\"name\", type=click.STRING)\ndef project_create(name: str):\n    \"\"\"\n    Create a project\n    \"\"\"\n    feast_client = Client()  # type: Client\n    feast_client.create_project(name)\n\n\n@project.command(name=\"archive\")\n@click.argument(\"name\", type=click.STRING)\ndef project_archive(name: str):\n    \"\"\"\n    Archive a project\n    \"\"\"\n    feast_client = Client()  # type: Client\n    feast_client.archive_project(name)\n\n\n@project.command(name=\"list\")\ndef project_list():\n    \"\"\"\n    List all projects\n    \"\"\"\n    feast_client = Client()  # type: Client\n\n    table = []\n    for project in feast_client.list_projects():\n        table.append([project])\n\n    from tabulate import tabulate\n\n    print(tabulate(table, headers=[\"NAME\"], tablefmt=\"plain\"))\n\n\n@cli.group(name=\"ingest-jobs\")\ndef ingest_job():\n    \"\"\"\n    Manage ingestion jobs\n    \"\"\"\n    pass\n\n\n@ingest_job.command(\"list\")\n@click.option(\"--job-id\", \"-i\", help=\"Show only ingestion jobs with the given job id\")\n@click.option(\n    \"--feature-set-ref\",\n    \"-f\",\n    help=\"Show only ingestion job targeting the feature set with the given reference\",\n)\n@click.option(\n    \"--store-name\",\n    \"-s\",\n    help=\"List only ingestion job that ingest into feast store with given name\",\n)\n# TODO: types\ndef ingest_job_list(job_id, feature_set_ref, store_name):\n    \"\"\"\n    List ingestion jobs\n    \"\"\"\n    # parse feature set reference\n    if feature_set_ref is not None:\n        feature_set_ref = FeatureSetRef.from_str(feature_set_ref)\n\n    # pull & render ingestion jobs as a table\n    feast_client = Client()\n    table = []\n    for ingest_job in feast_client.list_ingest_jobs(\n        job_id=job_id, feature_set_ref=feature_set_ref, store_name=store_name\n    ):\n        table.append([ingest_job.id, IngestionJobStatus.Name(ingest_job.status)])\n\n    from tabulate import tabulate\n\n    print(tabulate(table, headers=[\"ID\", \"STATUS\"], tablefmt=\"plain\"))\n\n\n@ingest_job.command(\"describe\")\n@click.argument(\"job_id\")\ndef ingest_job_describe(job_id: str):\n    \"\"\"\n    Describe the ingestion job with the given id.\n    \"\"\"\n    # find ingestion job for id\n    feast_client = Client()\n    jobs = feast_client.list_ingest_jobs(job_id=job_id)\n    if len(jobs) < 1:\n        print(f\"Ingestion Job with id {job_id} could not be found\")\n        sys.exit(1)\n    job = jobs[0]\n\n    # pretty render ingestion job as yaml\n    print(\n        yaml.dump(yaml.safe_load(str(job)), default_flow_style=False, sort_keys=False)\n    )\n\n\n@ingest_job.command(\"stop\")\n@click.option(\n    \"--wait\", \"-w\", is_flag=True, help=\"Wait for the ingestion job to fully stop.\"\n)\n@click.option(\n    \"--timeout\",\n    \"-t\",\n    default=600,\n    help=\"Timeout in seconds to wait for the job to stop.\",\n)\n@click.argument(\"job_id\")\ndef ingest_job_stop(wait: bool, timeout: int, job_id: str):\n    \"\"\"\n    Stop ingestion job for id.\n    \"\"\"\n    # find ingestion job for id\n    feast_client = Client()\n    jobs = feast_client.list_ingest_jobs(job_id=job_id)\n    if len(jobs) < 1:\n        print(f\"Ingestion Job with id {job_id} could not be found\")\n        sys.exit(1)\n    job = jobs[0]\n\n    feast_client.stop_ingest_job(job)\n\n    # wait for ingestion job to stop\n    if wait:\n        job.wait(IngestionJobStatus.ABORTED, timeout=timeout)\n\n\n@ingest_job.command(\"restart\")\n@click.argument(\"job_id\")\ndef ingest_job_restart(job_id: str):\n    \"\"\"\n    Restart job for id.\n    Waits for the job to fully restart.\n    \"\"\"\n    # find ingestion job for id\n    feast_client = Client()\n    jobs = feast_client.list_ingest_jobs(job_id=job_id)\n    if len(jobs) < 1:\n        print(f\"Ingestion Job with id {job_id} could not be found\")\n        sys.exit(1)\n    job = jobs[0]\n\n    feast_client.restart_ingest_job(job)\n\n\n@cli.command()\n@click.option(\n    \"--name\", \"-n\", help=\"Feature set name to ingest data into\", required=True\n)\n@click.option(\n    \"--filename\",\n    \"-f\",\n    help=\"Path to file to be ingested\",\n    type=click.Path(exists=True),\n    required=True,\n)\n@click.option(\n    \"--file-type\",\n    \"-t\",\n    type=click.Choice([\"CSV\"], case_sensitive=False),\n    help=\"Type of file to ingest. Defaults to CSV.\",\n)\ndef ingest(name, filename, file_type):\n    \"\"\"\n    Ingest feature data into a feature set\n    \"\"\"\n\n    feast_client = Client()  # type: Client\n    feature_set = feast_client.get_feature_set(name=name)\n    feature_set.ingest_file(file_path=filename)\n\n\nif __name__ == \"__main__\":\n    cli()\n", "path": "sdk/python/feast/cli.py"}]}