Improve CLI ux (#11452)

Improve UX for cli
pull/10303/head
Eugene Yurtsev 11 months ago committed by GitHub
parent 9f85f7c543
commit d9018ae5f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,12 @@
"""A CLI for creating a new project with LangChain.""" """A CLI for creating a new project with LangChain."""
from pathlib import Path from pathlib import Path
from typing import Optional
from typing_extensions import Annotated
from langchain.cli.create_repo.base import create, is_poetry_installed
from langchain.cli.create_repo.pypi_name import is_name_taken, lint_name
from langchain.cli.create_repo.user_info import get_git_user_email, get_git_user_name
try: try:
import typer import typer
@ -9,45 +16,94 @@ except ImportError:
"You can install it with `pip install typer`." "You can install it with `pip install typer`."
) )
from typing_extensions import Annotated
from langchain.cli.create_repo.base import create, is_poetry_installed
from langchain.cli.create_repo.user_info import get_git_user_email, get_git_user_name
app = typer.Typer(no_args_is_help=False, add_completion=False) app = typer.Typer(no_args_is_help=False, add_completion=False)
AUTHOR_NAME_OPTION = typer.Option( def _select_project_name(suggested_project_name: str) -> str:
default_factory=get_git_user_name, """Help the user select a valid project name."""
prompt=True, while True:
help="If not specified, will be inferred from git config if possible. ", project_name = typer.prompt("Project Name", default=suggested_project_name)
)
AUTHOR_EMAIL_OPTION = typer.Option( project_name_diagnostics = lint_name(project_name)
default_factory=get_git_user_email, if project_name_diagnostics:
prompt=True, typer.echo(
help="If not specified, will be inferred from git config if possible. ", f"{typer.style('Warning:', fg=typer.colors.MAGENTA)}"
) f" The project name"
USE_POETRY_OPTION = typer.Option( f" {typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}"
default_factory=is_poetry_installed, f" is not valid.",
prompt=True, err=True,
help=( )
"Whether to use Poetry to manage the project. "
"If not specified, Poetry will be used if poetry is installed." for diagnostic in project_name_diagnostics:
), typer.echo(f" - {diagnostic}")
)
if typer.confirm(
"Select another name?",
default=True,
):
continue
if is_name_taken(project_name):
typer.echo(
f"{typer.style('Error:', fg=typer.colors.RED)}"
f" The project name"
f" {typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}"
f" is already taken on pypi",
err=True,
)
if typer.confirm(
"Select another name?",
default=True,
):
continue
# If we got here then the project name is valid and not taken
return project_name
#
#
@app.command() @app.command()
def new( def new(
project_directory: Annotated[ project_directory: Annotated[
Path, typer.Argument(help="The directory to create the project in.") Path, typer.Argument(help="The directory to create the project in.")
], ],
author_name: Annotated[str, AUTHOR_NAME_OPTION], author_name: Optional[str] = None,
author_email: Annotated[str, AUTHOR_EMAIL_OPTION], author_email: Optional[str] = None,
use_poetry: Annotated[bool, USE_POETRY_OPTION], use_poetry: Annotated[
Optional[bool], typer.Option(help="Specify whether to use Poetry or not.")
] = None,
) -> None: ) -> None:
"""Create a new project with LangChain.""" """Create a new project with LangChain."""
create(project_directory, author_name, author_email, use_poetry)
project_directory_path = Path(project_directory)
project_name_suggestion = project_directory_path.name.replace("-", "_")
project_name = _select_project_name(project_name_suggestion)
if not author_name:
author_name = typer.prompt("Author Name", default=get_git_user_name())
if not author_email:
author_email = typer.prompt("Author Email", default=get_git_user_email())
if use_poetry is None:
if is_poetry_installed():
typer.echo("🎉 Found Poetry installed. Project can be set up using poetry.")
use_poetry = typer.confirm("Use Poetry? (no to use pip)", default=True)
else:
typer.echo(" Could not find Poetry installed.")
use_pip = typer.confirm("Use Pip? (no to use poetry)", default=True)
use_poetry = not use_pip
if author_name is None:
raise typer.BadParameter("Author name is required")
if author_email is None:
raise typer.BadParameter("Author email is required")
create(project_directory, project_name, author_name, author_email, use_poetry)
if __name__ == "__main__": if __name__ == "__main__":

@ -9,7 +9,6 @@ from typing import List, Sequence
import typer import typer
import langchain import langchain
from langchain.cli.create_repo.pypi_name import is_name_taken, lint_name
class UnderscoreTemplate(string.Template): class UnderscoreTemplate(string.Template):
@ -145,7 +144,7 @@ def _pip_install(project_directory_path: Path) -> None:
def _init_git(project_directory_path: Path) -> None: def _init_git(project_directory_path: Path) -> None:
"""Initialize git repository.""" """Initialize git repository."""
typer.echo( typer.echo(
f"\n{typer.style('3.', bold=True, fg=typer.colors.GREEN)} Initializing git..." f"\n{typer.style('Initializing git...', bold=True, fg=typer.colors.GREEN)}"
) )
subprocess.run(["git", "init"], cwd=project_directory_path) subprocess.run(["git", "init"], cwd=project_directory_path)
@ -157,58 +156,12 @@ def _init_git(project_directory_path: Path) -> None:
) )
def _select_project_name(suggested_project_name: str) -> str:
"""Help the user select a valid project name."""
while True:
project_name = typer.prompt(
"Please choose a project name: ", default=suggested_project_name
)
project_name_diagnostics = lint_name(project_name)
if project_name_diagnostics:
typer.echo(
f"{typer.style('Error:', fg=typer.colors.RED)}"
f" The project name"
f" {typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}"
f" is not valid:",
err=True,
)
for diagnostic in project_name_diagnostics:
typer.echo(f" - {diagnostic}")
if typer.confirm(
"Would you like to choose another name? "
"Choose NO to proceed with existing name.",
default=True,
):
continue
if is_name_taken(project_name):
typer.echo(
f"{typer.style('Error:', fg=typer.colors.RED)}"
f" The project name"
f" {typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}"
f" is already taken on pypi",
err=True,
)
if typer.confirm(
"Would you like to choose another name? "
"Choose NO to proceed with existing name.",
default=True,
):
continue
# If we got here then the project name is valid and not taken
return project_name
# PUBLIC API # PUBLIC API
def create( def create(
project_directory: pathlib.Path, project_directory: pathlib.Path,
project_name: str,
author_name: str, author_name: str,
author_email: str, author_email: str,
use_poetry: bool, use_poetry: bool,
@ -217,27 +170,29 @@ def create(
Args: Args:
project_directory (str): The directory to create the project in. project_directory (str): The directory to create the project in.
project_name: The name of the project.
author_name (str): The name of the author. author_name (str): The name of the author.
author_email (str): The email of the author. author_email (str): The email of the author.
use_poetry (bool): Whether to use Poetry to manage the project. use_poetry (bool): Whether to use Poetry to manage the project.
""" """
project_directory_path = Path(project_directory) project_directory_path = Path(project_directory)
project_name_suggestion = project_directory_path.name.replace("-", "_")
project_name = _select_project_name(project_name_suggestion)
project_name_identifier = project_name project_name_identifier = project_name
resolved_path = project_directory_path.resolve() resolved_path = project_directory_path.resolve()
if not typer.confirm( if not typer.confirm(
f"\n{typer.style('>', bold=True, fg=typer.colors.GREEN)} " f"\n"
f"Creating new LangChain project " f"Creating a new LangChain project 🦜️🔗\n"
f"{typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}" f"Name: {typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}\n"
f" in" f"Path: {typer.style(resolved_path, fg=typer.colors.BRIGHT_CYAN)}\n"
f" {typer.style(resolved_path, fg=typer.colors.BRIGHT_CYAN)}", f"Project name: {typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}\n"
f"Author name: {typer.style(author_name, fg=typer.colors.BRIGHT_CYAN)}\n"
f"Author email: {typer.style(author_email, fg=typer.colors.BRIGHT_CYAN)}\n"
f"Use Poetry: {typer.style(str(use_poetry), fg=typer.colors.BRIGHT_CYAN)}\n"
"Continue?",
default=True, default=True,
): ):
typer.echo("OK! Canceling project creation.") typer.echo("Cancelled project creation. See you later! 👋")
raise typer.Exit(code=0) raise typer.Exit(code=0)
_create_project_dir( _create_project_dir(
@ -258,7 +213,7 @@ def create(
_init_git(project_directory_path) _init_git(project_directory_path)
typer.echo( typer.echo(
f"\n{typer.style('Done!', bold=True, fg=typer.colors.GREEN)}" f"\n{typer.style('Done!🙌', bold=True, fg=typer.colors.GREEN)}"
f" Your new LangChain project" f" Your new LangChain project"
f" {typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}" f" {typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}"
f" has been created in" f" has been created in"

Loading…
Cancel
Save