cli.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. import json
  2. import os
  3. import re
  4. import shutil
  5. import subprocess
  6. import click
  7. import pkg_resources
  8. from rich.console import Console
  9. from embedchain.telemetry.posthog import AnonymousTelemetry
  10. console = Console()
  11. @click.group()
  12. def cli():
  13. pass
  14. anonymous_telemetry = AnonymousTelemetry()
  15. def get_pkg_path_from_name(template: str):
  16. try:
  17. # Determine the installation location of the embedchain package
  18. package_path = pkg_resources.resource_filename("embedchain", "")
  19. except ImportError:
  20. console.print("❌ [bold red]Failed to locate the 'embedchain' package. Is it installed?[/bold red]")
  21. return
  22. # Construct the source path from the embedchain package
  23. src_path = os.path.join(package_path, "deployment", template)
  24. if not os.path.exists(src_path):
  25. console.print(f"❌ [bold red]Template '{template}' not found.[/bold red]")
  26. return
  27. return src_path
  28. def setup_fly_io_app(extra_args):
  29. fly_launch_command = ["fly", "launch", "--region", "sjc", "--no-deploy"] + list(extra_args)
  30. try:
  31. console.print(f"🚀 [bold cyan]Running: {' '.join(fly_launch_command)}[/bold cyan]")
  32. shutil.move(".env.example", ".env")
  33. subprocess.run(fly_launch_command, check=True)
  34. console.print("✅ [bold green]'fly launch' executed successfully.[/bold green]")
  35. except subprocess.CalledProcessError as e:
  36. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  37. except FileNotFoundError:
  38. console.print(
  39. "❌ [bold red]'fly' command not found. Please ensure Fly CLI is installed and in your PATH.[/bold red]"
  40. )
  41. def setup_modal_com_app(extra_args):
  42. modal_setup_file = os.path.join(os.path.expanduser("~"), ".modal.toml")
  43. if os.path.exists(modal_setup_file):
  44. console.print(
  45. """✅ [bold green]Modal setup already done. You can now install the dependencies by doing \n
  46. `pip install -r requirements.txt`[/bold green]"""
  47. )
  48. else:
  49. modal_setup_cmd = ["modal", "setup"] + list(extra_args)
  50. console.print(f"🚀 [bold cyan]Running: {' '.join(modal_setup_cmd)}[/bold cyan]")
  51. subprocess.run(modal_setup_cmd, check=True)
  52. shutil.move(".env.example", ".env")
  53. console.print(
  54. """Great! Now you can install the dependencies by doing: \n
  55. `pip install -r requirements.txt`\n
  56. \n
  57. To run your app locally:\n
  58. `ec dev`
  59. """
  60. )
  61. def setup_render_com_app():
  62. render_setup_file = os.path.join(os.path.expanduser("~"), ".render/config.yaml")
  63. if os.path.exists(render_setup_file):
  64. console.print(
  65. """✅ [bold green]Render setup already done. You can now install the dependencies by doing \n
  66. `pip install -r requirements.txt`[/bold green]"""
  67. )
  68. else:
  69. render_setup_cmd = ["render", "config", "init"]
  70. console.print(f"🚀 [bold cyan]Running: {' '.join(render_setup_cmd)}[/bold cyan]")
  71. subprocess.run(render_setup_cmd, check=True)
  72. shutil.move(".env.example", ".env")
  73. console.print(
  74. """Great! Now you can install the dependencies by doing: \n
  75. `pip install -r requirements.txt`\n
  76. \n
  77. To run your app locally:\n
  78. `ec dev`
  79. """
  80. )
  81. def setup_streamlit_io_app():
  82. # nothing needs to be done here
  83. console.print("Great! Now you can install the dependencies by doing `pip install -r requirements.txt`")
  84. def setup_gradio_app():
  85. # nothing needs to be done here
  86. console.print("Great! Now you can install the dependencies by doing `pip install -r requirements.txt`")
  87. def setup_hf_app():
  88. subprocess.run(["pip", "install", "huggingface_hub[cli]"], check=True)
  89. hf_setup_file = os.path.join(os.path.expanduser("~"), ".cache/huggingface/token")
  90. if os.path.exists(hf_setup_file):
  91. console.print(
  92. """✅ [bold green]HuggingFace setup already done. You can now install the dependencies by doing \n
  93. `pip install -r requirements.txt`[/bold green]"""
  94. )
  95. else:
  96. console.print(
  97. """🚀 [cyan]Running: huggingface-cli login \n
  98. Please provide a [bold]WRITE[/bold] token so that we can directly deploy\n
  99. your apps from the terminal.[/cyan]
  100. """
  101. )
  102. subprocess.run(["huggingface-cli", "login"], check=True)
  103. console.print("Great! Now you can install the dependencies by doing `pip install -r requirements.txt`")
  104. @cli.command()
  105. @click.option("--template", default="fly.io", help="The template to use.")
  106. @click.argument("extra_args", nargs=-1, type=click.UNPROCESSED)
  107. def create(template, extra_args):
  108. anonymous_telemetry.capture(event_name="ec_create", properties={"template_used": template})
  109. template_dir = template
  110. if "/" in template_dir:
  111. template_dir = template.split("/")[1]
  112. src_path = get_pkg_path_from_name(template_dir)
  113. shutil.copytree(src_path, os.getcwd(), dirs_exist_ok=True)
  114. console.print(f"✅ [bold green]Successfully created app from template '{template}'.[/bold green]")
  115. if template == "fly.io":
  116. setup_fly_io_app(extra_args)
  117. elif template == "modal.com":
  118. setup_modal_com_app(extra_args)
  119. elif template == "render.com":
  120. setup_render_com_app()
  121. elif template == "streamlit.io":
  122. setup_streamlit_io_app()
  123. elif template == "gradio.app":
  124. setup_gradio_app()
  125. elif template == "hf/gradio.app" or template == "hf/streamlit.app":
  126. setup_hf_app()
  127. else:
  128. raise ValueError(f"Unknown template '{template}'.")
  129. embedchain_config = {"provider": template}
  130. with open("embedchain.json", "w") as file:
  131. json.dump(embedchain_config, file, indent=4)
  132. console.print(
  133. f"🎉 [green]All done! Successfully created `embedchain.json` with '{template}' as provider.[/green]"
  134. )
  135. def run_dev_fly_io(debug, host, port):
  136. uvicorn_command = ["uvicorn", "app:app"]
  137. if debug:
  138. uvicorn_command.append("--reload")
  139. uvicorn_command.extend(["--host", host, "--port", str(port)])
  140. try:
  141. console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(uvicorn_command)}[/bold cyan]")
  142. subprocess.run(uvicorn_command, check=True)
  143. except subprocess.CalledProcessError as e:
  144. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  145. except KeyboardInterrupt:
  146. console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]")
  147. def run_dev_modal_com():
  148. modal_run_cmd = ["modal", "serve", "app"]
  149. try:
  150. console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(modal_run_cmd)}[/bold cyan]")
  151. subprocess.run(modal_run_cmd, check=True)
  152. except subprocess.CalledProcessError as e:
  153. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  154. except KeyboardInterrupt:
  155. console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]")
  156. def run_dev_streamlit_io():
  157. streamlit_run_cmd = ["streamlit", "run", "app.py"]
  158. try:
  159. console.print(f"🚀 [bold cyan]Running Streamlit app with command: {' '.join(streamlit_run_cmd)}[/bold cyan]")
  160. subprocess.run(streamlit_run_cmd, check=True)
  161. except subprocess.CalledProcessError as e:
  162. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  163. except KeyboardInterrupt:
  164. console.print("\n🛑 [bold yellow]Streamlit server stopped[/bold yellow]")
  165. def run_dev_render_com(debug, host, port):
  166. uvicorn_command = ["uvicorn", "app:app"]
  167. if debug:
  168. uvicorn_command.append("--reload")
  169. uvicorn_command.extend(["--host", host, "--port", str(port)])
  170. try:
  171. console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(uvicorn_command)}[/bold cyan]")
  172. subprocess.run(uvicorn_command, check=True)
  173. except subprocess.CalledProcessError as e:
  174. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  175. except KeyboardInterrupt:
  176. console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]")
  177. def run_dev_gradio():
  178. gradio_run_cmd = ["gradio", "app.py"]
  179. try:
  180. console.print(f"🚀 [bold cyan]Running Gradio app with command: {' '.join(gradio_run_cmd)}[/bold cyan]")
  181. subprocess.run(gradio_run_cmd, check=True)
  182. except subprocess.CalledProcessError as e:
  183. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  184. except KeyboardInterrupt:
  185. console.print("\n🛑 [bold yellow]Gradio server stopped[/bold yellow]")
  186. @cli.command()
  187. @click.option("--debug", is_flag=True, help="Enable or disable debug mode.")
  188. @click.option("--host", default="127.0.0.1", help="The host address to run the FastAPI app on.")
  189. @click.option("--port", default=8000, help="The port to run the FastAPI app on.")
  190. def dev(debug, host, port):
  191. template = ""
  192. with open("embedchain.json", "r") as file:
  193. embedchain_config = json.load(file)
  194. template = embedchain_config["provider"]
  195. anonymous_telemetry.capture(event_name="ec_dev", properties={"template_used": template})
  196. if template == "fly.io":
  197. run_dev_fly_io(debug, host, port)
  198. elif template == "modal.com":
  199. run_dev_modal_com()
  200. elif template == "render.com":
  201. run_dev_render_com(debug, host, port)
  202. elif template == "streamlit.io" or template == "hf/streamlit.app":
  203. run_dev_streamlit_io()
  204. elif template == "gradio.app" or template == "hf/gradio.app":
  205. run_dev_gradio()
  206. else:
  207. raise ValueError(f"Unknown template '{template}'.")
  208. def read_env_file(env_file_path):
  209. """
  210. Reads an environment file and returns a dictionary of key-value pairs.
  211. Args:
  212. env_file_path (str): The path to the .env file.
  213. Returns:
  214. dict: Dictionary of environment variables.
  215. """
  216. env_vars = {}
  217. with open(env_file_path, "r") as file:
  218. for line in file:
  219. # Ignore comments and empty lines
  220. if line.strip() and not line.strip().startswith("#"):
  221. # Assume each line is in the format KEY=VALUE
  222. key_value_match = re.match(r"(\w+)=(.*)", line.strip())
  223. if key_value_match:
  224. key, value = key_value_match.groups()
  225. env_vars[key] = value
  226. return env_vars
  227. def deploy_fly():
  228. app_name = ""
  229. with open("fly.toml", "r") as file:
  230. for line in file:
  231. if line.strip().startswith("app ="):
  232. app_name = line.split("=")[1].strip().strip('"')
  233. if not app_name:
  234. console.print("❌ [bold red]App name not found in fly.toml[/bold red]")
  235. return
  236. env_vars = read_env_file(".env")
  237. secrets_command = ["flyctl", "secrets", "set", "-a", app_name] + [f"{k}={v}" for k, v in env_vars.items()]
  238. deploy_command = ["fly", "deploy"]
  239. try:
  240. # Set secrets
  241. console.print(f"🔐 [bold cyan]Setting secrets for {app_name}[/bold cyan]")
  242. subprocess.run(secrets_command, check=True)
  243. # Deploy application
  244. console.print(f"🚀 [bold cyan]Running: {' '.join(deploy_command)}[/bold cyan]")
  245. subprocess.run(deploy_command, check=True)
  246. console.print("✅ [bold green]'fly deploy' executed successfully.[/bold green]")
  247. except subprocess.CalledProcessError as e:
  248. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  249. except FileNotFoundError:
  250. console.print(
  251. "❌ [bold red]'fly' command not found. Please ensure Fly CLI is installed and in your PATH.[/bold red]"
  252. )
  253. def deploy_modal():
  254. modal_deploy_cmd = ["modal", "deploy", "app"]
  255. try:
  256. console.print(f"🚀 [bold cyan]Running: {' '.join(modal_deploy_cmd)}[/bold cyan]")
  257. subprocess.run(modal_deploy_cmd, check=True)
  258. console.print("✅ [bold green]'modal deploy' executed successfully.[/bold green]")
  259. except subprocess.CalledProcessError as e:
  260. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  261. except FileNotFoundError:
  262. console.print(
  263. "❌ [bold red]'modal' command not found. Please ensure Modal CLI is installed and in your PATH.[/bold red]"
  264. )
  265. def deploy_streamlit():
  266. streamlit_deploy_cmd = ["streamlit", "run", "app.py"]
  267. try:
  268. console.print(f"🚀 [bold cyan]Running: {' '.join(streamlit_deploy_cmd)}[/bold cyan]")
  269. console.print(
  270. """\n\n✅ [bold yellow]To deploy a streamlit app, you can directly it from the UI.\n
  271. Click on the 'Deploy' button on the top right corner of the app.\n
  272. For more information, please refer to https://docs.embedchain.ai/deployment/streamlit_io
  273. [/bold yellow]
  274. \n\n"""
  275. )
  276. subprocess.run(streamlit_deploy_cmd, check=True)
  277. except subprocess.CalledProcessError as e:
  278. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  279. except FileNotFoundError:
  280. console.print(
  281. """❌ [bold red]'streamlit' command not found.\n
  282. Please ensure Streamlit CLI is installed and in your PATH.[/bold red]"""
  283. )
  284. def deploy_render():
  285. render_deploy_cmd = ["render", "blueprint", "launch"]
  286. try:
  287. console.print(f"🚀 [bold cyan]Running: {' '.join(render_deploy_cmd)}[/bold cyan]")
  288. subprocess.run(render_deploy_cmd, check=True)
  289. console.print("✅ [bold green]'render blueprint launch' executed successfully.[/bold green]")
  290. except subprocess.CalledProcessError as e:
  291. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  292. except FileNotFoundError:
  293. console.print(
  294. "❌ [bold red]'render' command not found. Please ensure Render CLI is installed and in your PATH.[/bold red]" # noqa:E501
  295. )
  296. def deploy_gradio_app():
  297. gradio_deploy_cmd = ["gradio", "deploy"]
  298. try:
  299. console.print(f"🚀 [bold cyan]Running: {' '.join(gradio_deploy_cmd)}[/bold cyan]")
  300. subprocess.run(gradio_deploy_cmd, check=True)
  301. console.print("✅ [bold green]'gradio deploy' executed successfully.[/bold green]")
  302. except subprocess.CalledProcessError as e:
  303. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  304. except FileNotFoundError:
  305. console.print(
  306. "❌ [bold red]'gradio' command not found. Please ensure Gradio CLI is installed and in your PATH.[/bold red]" # noqa:E501
  307. )
  308. def deploy_hf_spaces(ec_app_name):
  309. if not ec_app_name:
  310. console.print("❌ [bold red]'name' not found in embedchain.json[/bold red]")
  311. return
  312. hf_spaces_deploy_cmd = ["huggingface-cli", "upload", ec_app_name, ".", ".", "--repo-type=space"]
  313. try:
  314. console.print(f"🚀 [bold cyan]Running: {' '.join(hf_spaces_deploy_cmd)}[/bold cyan]")
  315. subprocess.run(hf_spaces_deploy_cmd, check=True)
  316. console.print("✅ [bold green]'huggingface-cli upload' executed successfully.[/bold green]")
  317. except subprocess.CalledProcessError as e:
  318. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  319. @cli.command()
  320. def deploy():
  321. # Check for platform-specific files
  322. template = ""
  323. ec_app_name = ""
  324. with open("embedchain.json", "r") as file:
  325. embedchain_config = json.load(file)
  326. ec_app_name = embedchain_config["name"] if "name" in embedchain_config else None
  327. template = embedchain_config["provider"]
  328. anonymous_telemetry.capture(event_name="ec_deploy", properties={"template_used": template})
  329. if template == "fly.io":
  330. deploy_fly()
  331. elif template == "modal.com":
  332. deploy_modal()
  333. elif template == "render.com":
  334. deploy_render()
  335. elif template == "streamlit.io":
  336. deploy_streamlit()
  337. elif template == "gradio.app":
  338. deploy_gradio_app()
  339. elif template.startswith("hf/"):
  340. deploy_hf_spaces(ec_app_name)
  341. else:
  342. console.print("❌ [bold red]No recognized deployment platform found.[/bold red]")