Improve CLI ux (#11452)

Improve UX for cli
pull/10303/head
Eugene Yurtsev 10 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."""
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:
import typer
@ -9,45 +16,94 @@ except ImportError:
"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)
AUTHOR_NAME_OPTION = typer.Option(
default_factory=get_git_user_name,
prompt=True,
help="If not specified, will be inferred from git config if possible. ",
)
AUTHOR_EMAIL_OPTION = typer.Option(
default_factory=get_git_user_email,
prompt=True,
help="If not specified, will be inferred from git config if possible. ",
)
USE_POETRY_OPTION = typer.Option(
default_factory=is_poetry_installed,
prompt=True,
help=(
"Whether to use Poetry to manage the project. "
"If not specified, Poetry will be used if poetry is installed."
),
)
def _select_project_name(suggested_project_name: str) -> str:
"""Help the user select a valid project name."""
while True:
project_name = typer.prompt("Project Name", default=suggested_project_name)
project_name_diagnostics = lint_name(project_name)
if project_name_diagnostics:
typer.echo(
f"{typer.style('Warning:', fg=typer.colors.MAGENTA)}"
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(
"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()
def new(
project_directory: Annotated[
Path, typer.Argument(help="The directory to create the project in.")
],
author_name: Annotated[str, AUTHOR_NAME_OPTION],
author_email: Annotated[str, AUTHOR_EMAIL_OPTION],
use_poetry: Annotated[bool, USE_POETRY_OPTION],
author_name: Optional[str] = None,
author_email: Optional[str] = None,
use_poetry: Annotated[
Optional[bool], typer.Option(help="Specify whether to use Poetry or not.")
] = None,
) -> None:
"""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__":

@ -9,7 +9,6 @@ from typing import List, Sequence
import typer
import langchain
from langchain.cli.create_repo.pypi_name import is_name_taken, lint_name
class UnderscoreTemplate(string.Template):
@ -145,7 +144,7 @@ def _pip_install(project_directory_path: Path) -> None:
def _init_git(project_directory_path: Path) -> None:
"""Initialize git repository."""
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)
@ -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
def create(
project_directory: pathlib.Path,
project_name: str,
author_name: str,
author_email: str,
use_poetry: bool,
@ -217,27 +170,29 @@ def create(
Args:
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_email (str): The email of the author.
use_poetry (bool): Whether to use Poetry to manage the project.
"""
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
resolved_path = project_directory_path.resolve()
if not typer.confirm(
f"\n{typer.style('>', bold=True, fg=typer.colors.GREEN)} "
f"Creating new LangChain project "
f"{typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}"
f" in"
f" {typer.style(resolved_path, fg=typer.colors.BRIGHT_CYAN)}",
f"\n"
f"Creating a new LangChain project 🦜️🔗\n"
f"Name: {typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}\n"
f"Path: {typer.style(resolved_path, fg=typer.colors.BRIGHT_CYAN)}\n"
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,
):
typer.echo("OK! Canceling project creation.")
typer.echo("Cancelled project creation. See you later! 👋")
raise typer.Exit(code=0)
_create_project_dir(
@ -258,7 +213,7 @@ def create(
_init_git(project_directory_path)
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" {typer.style(project_name, fg=typer.colors.BRIGHT_CYAN)}"
f" has been created in"

Loading…
Cancel
Save