mirror of
https://github.com/hwchase17/langchain.git
synced 2025-09-01 11:02:37 +00:00
cli pyproject updating (#12945)
`langchain app add` and `langchain app remove` will now keep the dependencies list updated. --------- Co-authored-by: Nuno Campos <nuno@boringbits.io>
This commit is contained in:
@@ -6,9 +6,8 @@ from typing import Sequence
|
||||
|
||||
from fastapi import FastAPI
|
||||
from langserve import add_routes
|
||||
from langserve.packages import get_langserve_export
|
||||
|
||||
from langchain_cli.utils.packages import get_package_root
|
||||
from langchain_cli.utils.packages import get_langserve_export, get_package_root
|
||||
|
||||
|
||||
def create_demo_server(
|
||||
|
@@ -9,7 +9,6 @@ from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import typer
|
||||
from langserve.packages import get_langserve_export
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from langchain_cli.utils.events import create_events
|
||||
@@ -19,7 +18,15 @@ from langchain_cli.utils.git import (
|
||||
parse_dependencies,
|
||||
update_repo,
|
||||
)
|
||||
from langchain_cli.utils.packages import get_package_root
|
||||
from langchain_cli.utils.packages import (
|
||||
LangServeExport,
|
||||
get_langserve_export,
|
||||
get_package_root,
|
||||
)
|
||||
from langchain_cli.utils.pyproject import (
|
||||
add_dependencies_to_pyproject_toml,
|
||||
remove_dependencies_from_pyproject_toml,
|
||||
)
|
||||
|
||||
REPO_DIR = Path(typer.get_app_dir("langchain")) / "git_repos"
|
||||
|
||||
@@ -98,7 +105,8 @@ def add(
|
||||
grouped[key_tup] = lst
|
||||
|
||||
installed_destination_paths: List[Path] = []
|
||||
installed_exports: List[Dict] = []
|
||||
installed_destination_names: List[str] = []
|
||||
installed_exports: List[LangServeExport] = []
|
||||
|
||||
for (git, ref), group_deps in grouped.items():
|
||||
if len(group_deps) == 1:
|
||||
@@ -131,23 +139,38 @@ def add(
|
||||
copy_repo(source_path, destination_path)
|
||||
typer.echo(f" - Downloaded {dep['subdirectory']} to {inner_api_path}")
|
||||
installed_destination_paths.append(destination_path)
|
||||
installed_destination_names.append(inner_api_path)
|
||||
installed_exports.append(langserve_export)
|
||||
|
||||
if len(installed_destination_paths) == 0:
|
||||
typer.echo("No packages installed. Exiting.")
|
||||
return
|
||||
|
||||
cwd = Path.cwd()
|
||||
installed_desination_strs = [
|
||||
str(p.relative_to(cwd)) for p in installed_destination_paths
|
||||
]
|
||||
cmd = ["pip", "install", "-e"] + installed_desination_strs
|
||||
cmd_str = " \\\n ".join(installed_desination_strs)
|
||||
install_str = f"To install:\n\npip install -e \\\n {cmd_str}"
|
||||
typer.echo(install_str)
|
||||
try:
|
||||
add_dependencies_to_pyproject_toml(
|
||||
project_root / "pyproject.toml",
|
||||
zip(installed_destination_names, installed_destination_paths),
|
||||
)
|
||||
except Exception:
|
||||
# Can fail if user modified/removed pyproject.toml
|
||||
typer.echo("Failed to add dependencies to pyproject.toml, continuing...")
|
||||
|
||||
if typer.confirm("Run it?"):
|
||||
subprocess.run(cmd, cwd=cwd)
|
||||
try:
|
||||
cwd = Path.cwd()
|
||||
installed_destination_strs = [
|
||||
str(p.relative_to(cwd)) for p in installed_destination_paths
|
||||
]
|
||||
except ValueError:
|
||||
# Can fail if the cwd is not a parent of the package
|
||||
typer.echo("Failed to print install command, continuing...")
|
||||
else:
|
||||
cmd = ["pip", "install", "-e"] + installed_destination_strs
|
||||
cmd_str = " \\\n ".join(installed_destination_strs)
|
||||
install_str = f"To install:\n\npip install -e \\\n {cmd_str}"
|
||||
typer.echo(install_str)
|
||||
|
||||
if typer.confirm("Run it?"):
|
||||
subprocess.run(cmd, cwd=cwd)
|
||||
|
||||
if typer.confirm("\nGenerate route code for these packages?", default=True):
|
||||
chain_names = []
|
||||
@@ -187,20 +210,43 @@ def add(
|
||||
@app_cli.command()
|
||||
def remove(
|
||||
api_paths: Annotated[List[str], typer.Argument(help="The API paths to remove")],
|
||||
*,
|
||||
project_dir: Annotated[
|
||||
Optional[Path], typer.Option(help="The project directory")
|
||||
] = None,
|
||||
):
|
||||
"""
|
||||
Removes the specified package from the current LangServe app.
|
||||
"""
|
||||
for api_path in api_paths:
|
||||
package_dir = Path.cwd() / "packages" / api_path
|
||||
if not package_dir.exists():
|
||||
typer.echo(f"Endpoint {api_path} does not exist. Skipping...")
|
||||
continue
|
||||
pyproject = package_dir / "pyproject.toml"
|
||||
langserve_export = get_langserve_export(pyproject)
|
||||
typer.echo(f"Removing {langserve_export['package_name']}...")
|
||||
|
||||
shutil.rmtree(package_dir)
|
||||
project_root = get_package_root(project_dir)
|
||||
|
||||
project_pyproject = project_root / "pyproject.toml"
|
||||
|
||||
package_root = project_root / "packages"
|
||||
|
||||
remove_deps: List[str] = []
|
||||
|
||||
for api_path in api_paths:
|
||||
package_dir = package_root / api_path
|
||||
if not package_dir.exists():
|
||||
typer.echo(f"Package {api_path} does not exist. Skipping...")
|
||||
continue
|
||||
try:
|
||||
pyproject = package_dir / "pyproject.toml"
|
||||
langserve_export = get_langserve_export(pyproject)
|
||||
typer.echo(f"Removing {langserve_export['package_name']}...")
|
||||
|
||||
shutil.rmtree(package_dir)
|
||||
remove_deps.append(api_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
remove_dependencies_from_pyproject_toml(project_pyproject, remove_deps)
|
||||
except Exception:
|
||||
# Can fail if user modified/removed pyproject.toml
|
||||
typer.echo("Failed to remove dependencies from pyproject.toml.")
|
||||
|
||||
|
||||
@app_cli.command()
|
||||
|
@@ -9,10 +9,9 @@ from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import typer
|
||||
from langserve.packages import get_langserve_export
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from langchain_cli.utils.packages import get_package_root
|
||||
from langchain_cli.utils.packages import get_langserve_export, get_package_root
|
||||
|
||||
package_cli = typer.Typer(no_args_is_help=True, add_completion=False)
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
[tool.poetry]
|
||||
name = "langservehub-template"
|
||||
name = "__app_name__"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
@@ -7,16 +7,12 @@ readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
sse-starlette = "^1.6.5"
|
||||
tomli-w = "^1.0.0"
|
||||
uvicorn = "^0.23.2"
|
||||
fastapi = "^0.103.2"
|
||||
langserve = ">=0.0.16"
|
||||
langserve = {extras = ["server"], version = ">=0.0.22"}
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
uvicorn = "^0.23.2"
|
||||
pygithub = "^2.1.1"
|
||||
|
||||
langchain-cli = ">=0.0.15"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
@@ -1,5 +1,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional, Set
|
||||
from typing import Any, Dict, Optional, Set, TypedDict
|
||||
|
||||
from tomlkit import load
|
||||
|
||||
|
||||
def get_package_root(cwd: Optional[Path] = None) -> Path:
|
||||
@@ -14,3 +16,30 @@ def get_package_root(cwd: Optional[Path] = None) -> Path:
|
||||
return package_root
|
||||
package_root = package_root.parent
|
||||
raise FileNotFoundError("No pyproject.toml found")
|
||||
|
||||
|
||||
class LangServeExport(TypedDict):
|
||||
"""
|
||||
Fields from pyproject.toml that are relevant to LangServe
|
||||
|
||||
Attributes:
|
||||
module: The module to import from, tool.langserve.export_module
|
||||
attr: The attribute to import from the module, tool.langserve.export_attr
|
||||
package_name: The name of the package, tool.poetry.name
|
||||
"""
|
||||
|
||||
module: str
|
||||
attr: str
|
||||
package_name: str
|
||||
|
||||
|
||||
def get_langserve_export(filepath: Path) -> LangServeExport:
|
||||
with open(filepath) as f:
|
||||
data: Dict[str, Any] = load(f)
|
||||
try:
|
||||
module = data["tool"]["langserve"]["export_module"]
|
||||
attr = data["tool"]["langserve"]["export_attr"]
|
||||
package_name = data["tool"]["poetry"]["name"]
|
||||
except KeyError as e:
|
||||
raise KeyError("Invalid LangServe PyProject.toml") from e
|
||||
return LangServeExport(module=module, attr=attr, package_name=package_name)
|
||||
|
45
libs/cli/langchain_cli/utils/pyproject.py
Normal file
45
libs/cli/langchain_cli/utils/pyproject.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Iterable
|
||||
|
||||
from tomlkit import dump, inline_table, load
|
||||
from tomlkit.items import InlineTable
|
||||
|
||||
|
||||
def _get_dep_inline_table(path: Path) -> InlineTable:
|
||||
dep = inline_table()
|
||||
dep.update({"path": str(path), "develop": True})
|
||||
return dep
|
||||
|
||||
|
||||
def add_dependencies_to_pyproject_toml(
|
||||
pyproject_toml: Path, local_editable_dependencies: Iterable[tuple[str, Path]]
|
||||
) -> None:
|
||||
"""Add dependencies to pyproject.toml."""
|
||||
with open(pyproject_toml, encoding="utf-8") as f:
|
||||
# tomlkit types aren't amazing - treat as Dict instead
|
||||
pyproject: Dict[str, Any] = load(f)
|
||||
pyproject["tool"]["poetry"]["dependencies"].update(
|
||||
{
|
||||
name: _get_dep_inline_table(loc.relative_to(pyproject_toml.parent))
|
||||
for name, loc in local_editable_dependencies
|
||||
}
|
||||
)
|
||||
with open(pyproject_toml, "w", encoding="utf-8") as f:
|
||||
dump(pyproject, f)
|
||||
|
||||
|
||||
def remove_dependencies_from_pyproject_toml(
|
||||
pyproject_toml: Path, local_editable_dependencies: Iterable[str]
|
||||
) -> None:
|
||||
"""Remove dependencies from pyproject.toml."""
|
||||
with open(pyproject_toml, encoding="utf-8") as f:
|
||||
pyproject: Dict[str, Any] = load(f)
|
||||
# tomlkit types aren't amazing - treat as Dict instead
|
||||
dependencies = pyproject["tool"]["poetry"]["dependencies"]
|
||||
for name in local_editable_dependencies:
|
||||
try:
|
||||
del dependencies[name]
|
||||
except KeyError:
|
||||
pass
|
||||
with open(pyproject_toml, "w", encoding="utf-8") as f:
|
||||
dump(pyproject, f)
|
Reference in New Issue
Block a user