cli.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  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. @cli.command()
  85. @click.option("--template", default="fly.io", help="The template to use.")
  86. @click.argument("extra_args", nargs=-1, type=click.UNPROCESSED)
  87. def create(template, extra_args):
  88. anonymous_telemetry.capture(event_name="ec_create", properties={"template_used": template})
  89. src_path = get_pkg_path_from_name(template)
  90. shutil.copytree(src_path, os.getcwd(), dirs_exist_ok=True)
  91. console.print(f"✅ [bold green]Successfully created app from template '{template}'.[/bold green]")
  92. if template == "fly.io":
  93. setup_fly_io_app(extra_args)
  94. elif template == "modal.com":
  95. setup_modal_com_app(extra_args)
  96. elif template == "render.com":
  97. setup_render_com_app()
  98. elif template == "streamlit.io":
  99. setup_streamlit_io_app()
  100. else:
  101. raise ValueError(f"Unknown template '{template}'.")
  102. embedchain_config = {"provider": template}
  103. with open("embedchain.json", "w") as file:
  104. json.dump(embedchain_config, file, indent=4)
  105. console.print(
  106. f"🎉 [green]All done! Successfully created `embedchain.json` with '{template}' as provider.[/green]"
  107. )
  108. def run_dev_fly_io(debug, host, port):
  109. uvicorn_command = ["uvicorn", "app:app"]
  110. if debug:
  111. uvicorn_command.append("--reload")
  112. uvicorn_command.extend(["--host", host, "--port", str(port)])
  113. try:
  114. console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(uvicorn_command)}[/bold cyan]")
  115. subprocess.run(uvicorn_command, check=True)
  116. except subprocess.CalledProcessError as e:
  117. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  118. except KeyboardInterrupt:
  119. console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]")
  120. def run_dev_modal_com():
  121. modal_run_cmd = ["modal", "serve", "app"]
  122. try:
  123. console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(modal_run_cmd)}[/bold cyan]")
  124. subprocess.run(modal_run_cmd, check=True)
  125. except subprocess.CalledProcessError as e:
  126. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  127. except KeyboardInterrupt:
  128. console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]")
  129. def run_dev_streamlit_io():
  130. streamlit_run_cmd = ["streamlit", "run", "app.py"]
  131. try:
  132. console.print(f"🚀 [bold cyan]Running Streamlit app with command: {' '.join(streamlit_run_cmd)}[/bold cyan]")
  133. subprocess.run(streamlit_run_cmd, check=True)
  134. except subprocess.CalledProcessError as e:
  135. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  136. except KeyboardInterrupt:
  137. console.print("\n🛑 [bold yellow]Streamlit server stopped[/bold yellow]")
  138. def run_dev_render_com(debug, host, port):
  139. uvicorn_command = ["uvicorn", "app:app"]
  140. if debug:
  141. uvicorn_command.append("--reload")
  142. uvicorn_command.extend(["--host", host, "--port", str(port)])
  143. try:
  144. console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(uvicorn_command)}[/bold cyan]")
  145. subprocess.run(uvicorn_command, check=True)
  146. except subprocess.CalledProcessError as e:
  147. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  148. except KeyboardInterrupt:
  149. console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]")
  150. @cli.command()
  151. @click.option("--debug", is_flag=True, help="Enable or disable debug mode.")
  152. @click.option("--host", default="127.0.0.1", help="The host address to run the FastAPI app on.")
  153. @click.option("--port", default=8000, help="The port to run the FastAPI app on.")
  154. def dev(debug, host, port):
  155. template = ""
  156. with open("embedchain.json", "r") as file:
  157. embedchain_config = json.load(file)
  158. template = embedchain_config["provider"]
  159. anonymous_telemetry.capture(event_name="ec_dev", properties={"template_used": template})
  160. if template == "fly.io":
  161. run_dev_fly_io(debug, host, port)
  162. elif template == "modal.com":
  163. run_dev_modal_com()
  164. elif template == "render.com":
  165. run_dev_render_com(debug, host, port)
  166. elif template == "streamlit.io":
  167. run_dev_streamlit_io()
  168. else:
  169. raise ValueError(f"Unknown template '{template}'.")
  170. def read_env_file(env_file_path):
  171. """
  172. Reads an environment file and returns a dictionary of key-value pairs.
  173. Args:
  174. env_file_path (str): The path to the .env file.
  175. Returns:
  176. dict: Dictionary of environment variables.
  177. """
  178. env_vars = {}
  179. with open(env_file_path, "r") as file:
  180. for line in file:
  181. # Ignore comments and empty lines
  182. if line.strip() and not line.strip().startswith("#"):
  183. # Assume each line is in the format KEY=VALUE
  184. key_value_match = re.match(r"(\w+)=(.*)", line.strip())
  185. if key_value_match:
  186. key, value = key_value_match.groups()
  187. env_vars[key] = value
  188. return env_vars
  189. def deploy_fly():
  190. app_name = ""
  191. with open("fly.toml", "r") as file:
  192. for line in file:
  193. if line.strip().startswith("app ="):
  194. app_name = line.split("=")[1].strip().strip('"')
  195. if not app_name:
  196. console.print("❌ [bold red]App name not found in fly.toml[/bold red]")
  197. return
  198. env_vars = read_env_file(".env")
  199. secrets_command = ["flyctl", "secrets", "set", "-a", app_name] + [f"{k}={v}" for k, v in env_vars.items()]
  200. deploy_command = ["fly", "deploy"]
  201. try:
  202. # Set secrets
  203. console.print(f"🔐 [bold cyan]Setting secrets for {app_name}[/bold cyan]")
  204. subprocess.run(secrets_command, check=True)
  205. # Deploy application
  206. console.print(f"🚀 [bold cyan]Running: {' '.join(deploy_command)}[/bold cyan]")
  207. subprocess.run(deploy_command, check=True)
  208. console.print("✅ [bold green]'fly deploy' executed successfully.[/bold green]")
  209. except subprocess.CalledProcessError as e:
  210. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  211. except FileNotFoundError:
  212. console.print(
  213. "❌ [bold red]'fly' command not found. Please ensure Fly CLI is installed and in your PATH.[/bold red]"
  214. )
  215. def deploy_modal():
  216. modal_deploy_cmd = ["modal", "deploy", "app"]
  217. try:
  218. console.print(f"🚀 [bold cyan]Running: {' '.join(modal_deploy_cmd)}[/bold cyan]")
  219. subprocess.run(modal_deploy_cmd, check=True)
  220. console.print("✅ [bold green]'modal deploy' executed successfully.[/bold green]")
  221. except subprocess.CalledProcessError as e:
  222. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  223. except FileNotFoundError:
  224. console.print(
  225. "❌ [bold red]'modal' command not found. Please ensure Modal CLI is installed and in your PATH.[/bold red]"
  226. )
  227. def deploy_streamlit():
  228. streamlit_deploy_cmd = ["streamlit", "run", "app.py"]
  229. try:
  230. console.print(f"🚀 [bold cyan]Running: {' '.join(streamlit_deploy_cmd)}[/bold cyan]")
  231. console.print(
  232. """\n\n✅ [bold yellow]To deploy a streamlit app, you can directly it from the UI.\n
  233. Click on the 'Deploy' button on the top right corner of the app.\n
  234. For more information, please refer to https://docs.embedchain.ai/deployment/streamlit_io
  235. [/bold yellow]
  236. \n\n"""
  237. )
  238. subprocess.run(streamlit_deploy_cmd, check=True)
  239. except subprocess.CalledProcessError as e:
  240. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  241. except FileNotFoundError:
  242. console.print(
  243. """❌ [bold red]'streamlit' command not found.\n
  244. Please ensure Streamlit CLI is installed and in your PATH.[/bold red]"""
  245. )
  246. def deploy_render():
  247. render_deploy_cmd = ["render", "blueprint", "launch"]
  248. try:
  249. console.print(f"🚀 [bold cyan]Running: {' '.join(render_deploy_cmd)}[/bold cyan]")
  250. subprocess.run(render_deploy_cmd, check=True)
  251. console.print("✅ [bold green]'render blueprint launch' executed successfully.[/bold green]")
  252. except subprocess.CalledProcessError as e:
  253. console.print(f"❌ [bold red]An error occurred: {e}[/bold red]")
  254. except FileNotFoundError:
  255. console.print(
  256. "❌ [bold red]'render' command not found. Please ensure Render CLI is installed and in your PATH.[/bold red]"
  257. )
  258. @cli.command()
  259. def deploy():
  260. # Check for platform-specific files
  261. template = ""
  262. with open("embedchain.json", "r") as file:
  263. embedchain_config = json.load(file)
  264. template = embedchain_config["provider"]
  265. anonymous_telemetry.capture(event_name="ec_deploy", properties={"template_used": template})
  266. if template == "fly.io":
  267. deploy_fly()
  268. elif template == "modal.com":
  269. deploy_modal()
  270. elif template == "render.com":
  271. deploy_render()
  272. elif template == "streamlit.io":
  273. deploy_streamlit()
  274. else:
  275. console.print("❌ [bold red]No recognized deployment platform found.[/bold red]")