cli.py 11 KB

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