mirror of
https://github.com/hwchase17/langchain.git
synced 2025-08-29 06:23:20 +00:00
parent
07c2649753
commit
47070b8314
3
libs/cli/.gitignore
vendored
Normal file
3
libs/cli/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
dist
|
||||||
|
__pycache__
|
||||||
|
.venv
|
213
libs/cli/DOCS.md
Normal file
213
libs/cli/DOCS.md
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
# `langchain`
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain [OPTIONS] COMMAND [ARGS]...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--help`: Show this message and exit.
|
||||||
|
|
||||||
|
**Commands**:
|
||||||
|
|
||||||
|
* `hub`: Manage installable hub packages.
|
||||||
|
* `serve`: Manage LangServe application projects.
|
||||||
|
* `start`: Start the LangServe instance, whether it's...
|
||||||
|
|
||||||
|
## `langchain hub`
|
||||||
|
|
||||||
|
Manage installable hub packages.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain hub [OPTIONS] COMMAND [ARGS]...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--help`: Show this message and exit.
|
||||||
|
|
||||||
|
**Commands**:
|
||||||
|
|
||||||
|
* `new`: Creates a new hub package.
|
||||||
|
* `start`: Starts a demo LangServe instance for this...
|
||||||
|
|
||||||
|
### `langchain hub new`
|
||||||
|
|
||||||
|
Creates a new hub package.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain hub new [OPTIONS] NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
* `NAME`: [required]
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--help`: Show this message and exit.
|
||||||
|
|
||||||
|
### `langchain hub start`
|
||||||
|
|
||||||
|
Starts a demo LangServe instance for this hub package.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain hub start [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--port INTEGER`
|
||||||
|
* `--host TEXT`
|
||||||
|
* `--help`: Show this message and exit.
|
||||||
|
|
||||||
|
## `langchain serve`
|
||||||
|
|
||||||
|
Manage LangServe application projects.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain serve [OPTIONS] COMMAND [ARGS]...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--help`: Show this message and exit.
|
||||||
|
|
||||||
|
**Commands**:
|
||||||
|
|
||||||
|
* `add`: Adds the specified package to the current...
|
||||||
|
* `install`
|
||||||
|
* `list`: Lists all packages in the current...
|
||||||
|
* `new`: Create a new LangServe application.
|
||||||
|
* `remove`: Removes the specified package from the...
|
||||||
|
* `start`: Starts the LangServe instance.
|
||||||
|
|
||||||
|
### `langchain serve add`
|
||||||
|
|
||||||
|
Adds the specified package to the current LangServe instance.
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
langchain serve add simple-pirate
|
||||||
|
langchain serve add git+ssh://git@github.com/efriis/simple-pirate.git
|
||||||
|
langchain serve add git+https://github.com/efriis/hub.git#devbranch#subdirectory=mypackage
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain serve add [OPTIONS] DEPENDENCIES...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
* `DEPENDENCIES...`: [required]
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--api-path TEXT`
|
||||||
|
* `--project-dir PATH`
|
||||||
|
* `--help`: Show this message and exit.
|
||||||
|
|
||||||
|
### `langchain serve install`
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain serve install [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--help`: Show this message and exit.
|
||||||
|
|
||||||
|
### `langchain serve list`
|
||||||
|
|
||||||
|
Lists all packages in the current LangServe instance.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain serve list [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--help`: Show this message and exit.
|
||||||
|
|
||||||
|
### `langchain serve new`
|
||||||
|
|
||||||
|
Create a new LangServe application.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain serve new [OPTIONS] NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
* `NAME`: [required]
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--package TEXT`
|
||||||
|
* `--help`: Show this message and exit.
|
||||||
|
|
||||||
|
### `langchain serve remove`
|
||||||
|
|
||||||
|
Removes the specified package from the current LangServe instance.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain serve remove [OPTIONS] API_PATHS...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
* `API_PATHS...`: [required]
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--help`: Show this message and exit.
|
||||||
|
|
||||||
|
### `langchain serve start`
|
||||||
|
|
||||||
|
Starts the LangServe instance.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain serve start [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--port INTEGER`
|
||||||
|
* `--host TEXT`
|
||||||
|
* `--help`: Show this message and exit.
|
||||||
|
|
||||||
|
## `langchain start`
|
||||||
|
|
||||||
|
Start the LangServe instance, whether it's a hub package or a serve project.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ langchain start [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
* `--port INTEGER`
|
||||||
|
* `--host TEXT`
|
||||||
|
* `--help`: Show this message and exit.
|
43
libs/cli/README.md
Normal file
43
libs/cli/README.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# langchain-cli
|
||||||
|
|
||||||
|
Install CLI
|
||||||
|
|
||||||
|
`pip install -U --pre langchain-cli`
|
||||||
|
|
||||||
|
Create new langchain app
|
||||||
|
|
||||||
|
`langchain serve new my-app`
|
||||||
|
|
||||||
|
Go into app
|
||||||
|
|
||||||
|
`cd my-app`
|
||||||
|
|
||||||
|
Install a package
|
||||||
|
|
||||||
|
`langchain serve add extraction-summary`
|
||||||
|
|
||||||
|
(Activate virtualenv)
|
||||||
|
|
||||||
|
Install langserve
|
||||||
|
|
||||||
|
`pip install "langserve[all]"`
|
||||||
|
|
||||||
|
Install the langchain package
|
||||||
|
|
||||||
|
`pip install -e packages/extraction-summary`
|
||||||
|
|
||||||
|
Edit `app/server.py` to add that package to the routes
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from langserve import add_routes
|
||||||
|
from extraction_summary.chain import chain
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
add_routes(app, chain)
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the app
|
||||||
|
|
||||||
|
`python app/server.py`
|
0
libs/cli/langchain_cli/__init__.py
Normal file
0
libs/cli/langchain_cli/__init__.py
Normal file
36
libs/cli/langchain_cli/cli.py
Normal file
36
libs/cli/langchain_cli/cli.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import typer
|
||||||
|
import subprocess
|
||||||
|
from typing import Optional
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
from langchain_cli.namespaces import hub
|
||||||
|
from langchain_cli.namespaces import serve
|
||||||
|
|
||||||
|
app = typer.Typer(no_args_is_help=True, add_completion=False)
|
||||||
|
app.add_typer(hub.hub, name="hub", help=hub.__doc__)
|
||||||
|
app.add_typer(serve.serve, name="serve", help=serve.__doc__)
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def start(
|
||||||
|
*,
|
||||||
|
port: Annotated[
|
||||||
|
Optional[int], typer.Option(help="The port to run the server on")
|
||||||
|
] = None,
|
||||||
|
host: Annotated[
|
||||||
|
Optional[str], typer.Option(help="The host to run the server on")
|
||||||
|
] = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Start the LangServe instance, whether it's a hub package or a serve project.
|
||||||
|
"""
|
||||||
|
cmd = ["poetry", "run", "poe", "start"]
|
||||||
|
if port is not None:
|
||||||
|
cmd += ["--port", str(port)]
|
||||||
|
if host is not None:
|
||||||
|
cmd += ["--host", host]
|
||||||
|
subprocess.run(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app()
|
2
libs/cli/langchain_cli/constants.py
Normal file
2
libs/cli/langchain_cli/constants.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
DEFAULT_GIT_REPO = "https://github.com/langchain-ai/langchain.git"
|
||||||
|
DEFAULT_GIT_SUBDIRECTORY = "templates"
|
17
libs/cli/langchain_cli/dev_scripts.py
Normal file
17
libs/cli/langchain_cli/dev_scripts.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
Development Scripts for Hub Packages
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from langserve.packages import add_package_route
|
||||||
|
from langchain_cli.utils.packages import get_package_root
|
||||||
|
|
||||||
|
|
||||||
|
def create_demo_server():
|
||||||
|
"""
|
||||||
|
Creates a demo server for the current hub package.
|
||||||
|
"""
|
||||||
|
app = FastAPI()
|
||||||
|
package_root = get_package_root()
|
||||||
|
add_package_route(app, package_root, "")
|
||||||
|
return app
|
0
libs/cli/langchain_cli/namespaces/__init__.py
Normal file
0
libs/cli/langchain_cli/namespaces/__init__.py
Normal file
89
libs/cli/langchain_cli/namespaces/hub.py
Normal file
89
libs/cli/langchain_cli/namespaces/hub.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
"""
|
||||||
|
Manage installable hub packages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import typer
|
||||||
|
from typing import Optional
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
|
||||||
|
hub = typer.Typer(no_args_is_help=True, add_completion=False)
|
||||||
|
|
||||||
|
|
||||||
|
@hub.command()
|
||||||
|
def new(
|
||||||
|
name: Annotated[str, typer.Argument(help="The name of the folder to create")],
|
||||||
|
with_poetry: Annotated[
|
||||||
|
bool,
|
||||||
|
typer.Option(
|
||||||
|
"--with-poetry/--no-poetry", help="Don't run poetry install"
|
||||||
|
),
|
||||||
|
] = False,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Creates a new hub package.
|
||||||
|
"""
|
||||||
|
computed_name = name if name != "." else Path.cwd().name
|
||||||
|
destination_dir = Path.cwd() / name if name != "." else Path.cwd()
|
||||||
|
|
||||||
|
# copy over template from ../package_template
|
||||||
|
project_template_dir = Path(__file__).parent.parent.parent / "package_template"
|
||||||
|
shutil.copytree(project_template_dir, destination_dir, dirs_exist_ok=name == ".")
|
||||||
|
|
||||||
|
package_name_split = computed_name.split("/")
|
||||||
|
package_name_last = (
|
||||||
|
package_name_split[-2]
|
||||||
|
if len(package_name_split) > 1 and package_name_split[-1] == ""
|
||||||
|
else package_name_split[-1]
|
||||||
|
)
|
||||||
|
default_package_name = re.sub(
|
||||||
|
r"[^a-zA-Z0-9_]",
|
||||||
|
"_",
|
||||||
|
package_name_last,
|
||||||
|
)
|
||||||
|
|
||||||
|
# replace template strings
|
||||||
|
pyproject = destination_dir / "pyproject.toml"
|
||||||
|
pyproject_contents = pyproject.read_text()
|
||||||
|
pyproject.write_text(
|
||||||
|
pyproject_contents.replace("__package_name__", default_package_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
# move module folder
|
||||||
|
package_dir = destination_dir / default_package_name
|
||||||
|
shutil.move(destination_dir / "package_template", package_dir)
|
||||||
|
|
||||||
|
# replace readme
|
||||||
|
readme = destination_dir / "README.md"
|
||||||
|
readme_contents = readme.read_text()
|
||||||
|
readme.write_text(
|
||||||
|
readme_contents.replace("__package_name_last__", package_name_last)
|
||||||
|
)
|
||||||
|
|
||||||
|
# poetry install
|
||||||
|
if with_poetry:
|
||||||
|
subprocess.run(["poetry", "install"], cwd=destination_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@hub.command()
|
||||||
|
def start(
|
||||||
|
*,
|
||||||
|
port: Annotated[
|
||||||
|
Optional[int], typer.Option(help="The port to run the server on")
|
||||||
|
] = None,
|
||||||
|
host: Annotated[
|
||||||
|
Optional[str], typer.Option(help="The host to run the server on")
|
||||||
|
] = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Starts a demo LangServe instance for this hub package.
|
||||||
|
"""
|
||||||
|
cmd = ["poetry", "run", "poe", "start"]
|
||||||
|
if port is not None:
|
||||||
|
cmd += ["--port", str(port)]
|
||||||
|
if host is not None:
|
||||||
|
cmd += ["--host", host]
|
||||||
|
subprocess.run(cmd)
|
218
libs/cli/langchain_cli/namespaces/serve.py
Normal file
218
libs/cli/langchain_cli/namespaces/serve.py
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
"""
|
||||||
|
Manage LangServe application projects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import typer
|
||||||
|
from typing import Optional, List
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from langchain_cli.utils.git import copy_repo, update_repo
|
||||||
|
from langchain_cli.utils.packages import get_package_root
|
||||||
|
from langchain_cli.utils.events import create_events
|
||||||
|
from langserve.packages import list_packages, get_langserve_export
|
||||||
|
import tomli
|
||||||
|
|
||||||
|
REPO_DIR = Path(typer.get_app_dir("langchain")) / "git_repos"
|
||||||
|
|
||||||
|
serve = typer.Typer(no_args_is_help=True, add_completion=False)
|
||||||
|
|
||||||
|
|
||||||
|
@serve.command()
|
||||||
|
def new(
|
||||||
|
name: Annotated[str, typer.Argument(help="The name of the folder to create")],
|
||||||
|
*,
|
||||||
|
package: Annotated[
|
||||||
|
Optional[List[str]],
|
||||||
|
typer.Option(help="Packages to seed the project with"),
|
||||||
|
] = None,
|
||||||
|
with_poetry: Annotated[
|
||||||
|
bool,
|
||||||
|
typer.Option(
|
||||||
|
"--with-poetry/--no-poetry", help="Run poetry install"
|
||||||
|
),
|
||||||
|
] = False,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a new LangServe application.
|
||||||
|
"""
|
||||||
|
# copy over template from ../project_template
|
||||||
|
project_template_dir = Path(__file__).parent.parent.parent / "project_template"
|
||||||
|
destination_dir = Path.cwd() / name if name != "." else Path.cwd()
|
||||||
|
shutil.copytree(project_template_dir, destination_dir, dirs_exist_ok=name == ".")
|
||||||
|
|
||||||
|
# poetry install
|
||||||
|
if with_poetry:
|
||||||
|
subprocess.run(["poetry", "install"], cwd=destination_dir)
|
||||||
|
|
||||||
|
# add packages if specified
|
||||||
|
if package is not None and len(package) > 0:
|
||||||
|
add(package, project_dir=destination_dir, with_poetry=with_poetry)
|
||||||
|
|
||||||
|
|
||||||
|
@serve.command()
|
||||||
|
def install():
|
||||||
|
package_root = get_package_root() / "packages"
|
||||||
|
for package_path in list_packages(package_root):
|
||||||
|
try:
|
||||||
|
pyproject_path = package_path / "pyproject.toml"
|
||||||
|
langserve_export = get_langserve_export(pyproject_path)
|
||||||
|
typer.echo(f"Installing {langserve_export['package_name']}...")
|
||||||
|
subprocess.run(["poetry", "add", "--editable", package_path])
|
||||||
|
except Exception as e:
|
||||||
|
typer.echo(f"Skipping installing {package_path} due to error: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
@serve.command()
|
||||||
|
def add(
|
||||||
|
dependencies: Annotated[
|
||||||
|
Optional[List[str]], typer.Argument(help="The dependency to add")
|
||||||
|
] = None,
|
||||||
|
*,
|
||||||
|
api_path: Annotated[List[str], typer.Option(help="API paths to add")] = [],
|
||||||
|
project_dir: Annotated[
|
||||||
|
Optional[Path], typer.Option(help="The project directory")
|
||||||
|
] = None,
|
||||||
|
repo: Annotated[
|
||||||
|
List[str], typer.Option(help="Shorthand for installing a GitHub Repo")
|
||||||
|
] = [],
|
||||||
|
with_poetry: Annotated[
|
||||||
|
bool,
|
||||||
|
typer.Option(
|
||||||
|
"--with-poetry/--no-poetry", help="Run poetry install"
|
||||||
|
),
|
||||||
|
] = False,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Adds the specified package to the current LangServe instance.
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
langchain serve add simple-pirate
|
||||||
|
langchain serve add git+ssh://git@github.com/efriis/simple-pirate.git
|
||||||
|
langchain serve add git+https://github.com/efriis/hub.git#devbranch#subdirectory=mypackage
|
||||||
|
"""
|
||||||
|
project_root = get_package_root(project_dir)
|
||||||
|
|
||||||
|
if dependencies is None:
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
# cannot have both repo and dependencies
|
||||||
|
if len(repo) != 0:
|
||||||
|
if len(dependencies) != 0:
|
||||||
|
raise typer.BadParameter(
|
||||||
|
"Cannot specify both repo and dependencies. Please specify one or the other."
|
||||||
|
)
|
||||||
|
dependencies = [f"git+https://github.com/{r}" for r in repo]
|
||||||
|
|
||||||
|
if len(api_path) != 0 and len(api_path) != len(dependencies):
|
||||||
|
raise typer.BadParameter(
|
||||||
|
"The number of API paths must match the number of dependencies."
|
||||||
|
)
|
||||||
|
|
||||||
|
# get installed packages from pyproject.toml
|
||||||
|
root_pyproject_path = project_root / "pyproject.toml"
|
||||||
|
with open(root_pyproject_path, "rb") as pyproject_file:
|
||||||
|
pyproject = tomli.load(pyproject_file)
|
||||||
|
installed_packages = (
|
||||||
|
pyproject.get("tool", {}).get("poetry", {}).get("dependencies", {})
|
||||||
|
)
|
||||||
|
installed_names = set(installed_packages.keys())
|
||||||
|
|
||||||
|
package_dir = project_root / "packages"
|
||||||
|
|
||||||
|
create_events(
|
||||||
|
[{"event": "serve add", "properties": {"package": d}} for d in dependencies]
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, dependency in enumerate(dependencies):
|
||||||
|
# update repo
|
||||||
|
typer.echo(f"Adding {dependency}...")
|
||||||
|
source_path = update_repo(dependency, REPO_DIR)
|
||||||
|
pyproject_path = source_path / "pyproject.toml"
|
||||||
|
langserve_export = get_langserve_export(pyproject_path)
|
||||||
|
|
||||||
|
# detect name conflict
|
||||||
|
if langserve_export["package_name"] in installed_names:
|
||||||
|
typer.echo(
|
||||||
|
f"Package with name {langserve_export['package_name']} already installed. Skipping...",
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
inner_api_path = (
|
||||||
|
api_path[i] if len(api_path) != 0 else langserve_export["package_name"]
|
||||||
|
)
|
||||||
|
destination_path = package_dir / inner_api_path
|
||||||
|
if destination_path.exists():
|
||||||
|
typer.echo(
|
||||||
|
f"Endpoint {langserve_export['package_name']} already exists. Skipping...",
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
copy_repo(source_path, destination_path)
|
||||||
|
# poetry install
|
||||||
|
if with_poetry:
|
||||||
|
subprocess.run(
|
||||||
|
["poetry", "add", "--editable", destination_path], cwd=project_root
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@serve.command()
|
||||||
|
def remove(
|
||||||
|
api_paths: Annotated[List[str], typer.Argument(help="The API paths to remove")],
|
||||||
|
with_poetry: Annotated[
|
||||||
|
bool,
|
||||||
|
typer.Option(
|
||||||
|
"--with_poetry/--no-poetry", help="Don't run poetry remove"
|
||||||
|
),
|
||||||
|
] = False,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Removes the specified package from the current LangServe instance.
|
||||||
|
"""
|
||||||
|
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']}...")
|
||||||
|
if with_poetry:
|
||||||
|
subprocess.run(["poetry", "remove", langserve_export["package_name"]])
|
||||||
|
shutil.rmtree(package_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@serve.command()
|
||||||
|
def list():
|
||||||
|
"""
|
||||||
|
Lists all packages in the current LangServe instance.
|
||||||
|
"""
|
||||||
|
package_root = get_package_root() / "packages"
|
||||||
|
for package_path in list_packages(package_root):
|
||||||
|
relative = package_path.relative_to(package_root)
|
||||||
|
pyproject_path = package_path / "pyproject.toml"
|
||||||
|
langserve_export = get_langserve_export(pyproject_path)
|
||||||
|
typer.echo(
|
||||||
|
f"{relative}: ({langserve_export['module']}.{langserve_export['attr']})"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@serve.command()
|
||||||
|
def start(
|
||||||
|
*,
|
||||||
|
port: Annotated[
|
||||||
|
Optional[int], typer.Option(help="The port to run the server on")
|
||||||
|
] = None,
|
||||||
|
host: Annotated[
|
||||||
|
Optional[str], typer.Option(help="The host to run the server on")
|
||||||
|
] = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Starts the LangServe instance.
|
||||||
|
"""
|
||||||
|
cmd = ["poetry", "run", "poe", "start"]
|
||||||
|
if port is not None:
|
||||||
|
cmd += ["--port", str(port)]
|
||||||
|
if host is not None:
|
||||||
|
cmd += ["--host", host]
|
||||||
|
subprocess.run(cmd)
|
0
libs/cli/langchain_cli/utils/__init__.py
Normal file
0
libs/cli/langchain_cli/utils/__init__.py
Normal file
52
libs/cli/langchain_cli/utils/events.py
Normal file
52
libs/cli/langchain_cli/utils/events.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import urllib3
|
||||||
|
import json
|
||||||
|
from typing import List, Dict, Any, Optional, TypedDict
|
||||||
|
|
||||||
|
WRITE_KEY = "310apTK0HUFl4AOv"
|
||||||
|
|
||||||
|
|
||||||
|
class EventDict(TypedDict):
|
||||||
|
event: str
|
||||||
|
properties: Optional[Dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
|
def create_event(event: EventDict) -> None:
|
||||||
|
"""
|
||||||
|
Creates a new event with the given type and payload.
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"write_key": WRITE_KEY,
|
||||||
|
"event": event["event"],
|
||||||
|
"properties": event.get("properties"),
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
urllib3.request(
|
||||||
|
"POST",
|
||||||
|
"https://app.firstpartyhq.com/events/v1/track",
|
||||||
|
body=json.dumps(data),
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def create_events(events: List[EventDict]) -> None:
|
||||||
|
data = {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"write_key": WRITE_KEY,
|
||||||
|
"event": event["event"],
|
||||||
|
"properties": event.get("properties"),
|
||||||
|
}
|
||||||
|
for event in events
|
||||||
|
]
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
urllib3.request(
|
||||||
|
"POST",
|
||||||
|
"https://app.firstpartyhq.com/events/v1/track/bulk",
|
||||||
|
body=json.dumps(data),
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
130
libs/cli/langchain_cli/utils/git.py
Normal file
130
libs/cli/langchain_cli/utils/git.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
from typing import Optional, TypedDict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
import re
|
||||||
|
from langchain_cli.constants import DEFAULT_GIT_REPO, DEFAULT_GIT_SUBDIRECTORY
|
||||||
|
import hashlib
|
||||||
|
from git import Repo
|
||||||
|
|
||||||
|
|
||||||
|
class DependencySource(TypedDict):
|
||||||
|
git: str
|
||||||
|
ref: Optional[str]
|
||||||
|
subdirectory: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_main_branch(repo: Repo) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Get the name of the main branch of a git repo.
|
||||||
|
From https://stackoverflow.com/questions/69651536/how-to-get-master-main-branch-from-gitpython
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# replace "origin" with your remote name if differs
|
||||||
|
show_result = repo.git.remote("show", "origin")
|
||||||
|
|
||||||
|
# The show_result contains a wall of text in the language that
|
||||||
|
# is set by your locales. Now you can use regex to extract the
|
||||||
|
# default branch name, but if your language is different
|
||||||
|
# from english, you need to adjust this regex pattern.
|
||||||
|
|
||||||
|
matches = re.search(r"\s*HEAD branch:\s*(.*)", show_result)
|
||||||
|
if matches:
|
||||||
|
default_branch = matches.group(1)
|
||||||
|
return default_branch
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# fallback to main/master
|
||||||
|
if "main" in repo.heads:
|
||||||
|
return "main"
|
||||||
|
if "master" in repo.heads:
|
||||||
|
return "master"
|
||||||
|
|
||||||
|
raise ValueError("Could not find main branch")
|
||||||
|
|
||||||
|
|
||||||
|
# use poetry dependency string format
|
||||||
|
def _parse_dependency_string(package_string: str) -> DependencySource:
|
||||||
|
if package_string.startswith("git+"):
|
||||||
|
# remove git+
|
||||||
|
remaining = package_string[4:]
|
||||||
|
# split main string from params
|
||||||
|
gitstring, *params = remaining.split("#")
|
||||||
|
# parse params
|
||||||
|
params_dict = {}
|
||||||
|
for param in params:
|
||||||
|
if not param:
|
||||||
|
# ignore empty entries
|
||||||
|
continue
|
||||||
|
if "=" in param:
|
||||||
|
key, value = param.split("=")
|
||||||
|
if key in params_dict:
|
||||||
|
raise ValueError(
|
||||||
|
f"Duplicate parameter {key} in dependency string {package_string}"
|
||||||
|
)
|
||||||
|
params_dict[key] = value
|
||||||
|
else:
|
||||||
|
if "ref" in params_dict:
|
||||||
|
raise ValueError(
|
||||||
|
f"Duplicate parameter ref in dependency string {package_string}"
|
||||||
|
)
|
||||||
|
params_dict["ref"] = param
|
||||||
|
return DependencySource(
|
||||||
|
git=gitstring,
|
||||||
|
ref=params_dict.get("ref"),
|
||||||
|
subdirectory=params_dict.get("subdirectory"),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif package_string.startswith("https://"):
|
||||||
|
raise NotImplementedError("url dependencies are not supported yet")
|
||||||
|
else:
|
||||||
|
# it's a default git repo dependency
|
||||||
|
gitstring = DEFAULT_GIT_REPO
|
||||||
|
subdirectory = str(Path(DEFAULT_GIT_SUBDIRECTORY) / package_string)
|
||||||
|
return DependencySource(git=gitstring, ref=None, subdirectory=subdirectory)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_repo_path(dependency: DependencySource, repo_dir: Path) -> Path:
|
||||||
|
# only based on git for now
|
||||||
|
gitstring = dependency["git"]
|
||||||
|
hashed = hashlib.sha256(gitstring.encode("utf-8")).hexdigest()[:8]
|
||||||
|
|
||||||
|
removed_protocol = gitstring.split("://")[-1]
|
||||||
|
removed_basename = re.split(r"[/:]", removed_protocol, 1)[-1]
|
||||||
|
removed_extras = removed_basename.split("#")[0]
|
||||||
|
foldername = re.sub(r"[^a-zA-Z0-9_]", "_", removed_extras)
|
||||||
|
|
||||||
|
directory_name = f"{foldername}_{hashed}"
|
||||||
|
return repo_dir / directory_name
|
||||||
|
|
||||||
|
|
||||||
|
def update_repo(gitpath: str, repo_dir: Path) -> Path:
|
||||||
|
# see if path already saved
|
||||||
|
dependency = _parse_dependency_string(gitpath)
|
||||||
|
repo_path = _get_repo_path(dependency, repo_dir)
|
||||||
|
if not repo_path.exists():
|
||||||
|
repo = Repo.clone_from(dependency["git"], repo_path)
|
||||||
|
else:
|
||||||
|
repo = Repo(repo_path)
|
||||||
|
|
||||||
|
# pull it
|
||||||
|
ref = dependency.get("ref") if dependency.get("ref") else _get_main_branch(repo)
|
||||||
|
repo.git.checkout(ref)
|
||||||
|
|
||||||
|
repo.git.pull()
|
||||||
|
|
||||||
|
return (
|
||||||
|
repo_path
|
||||||
|
if dependency["subdirectory"] is None
|
||||||
|
else repo_path / dependency["subdirectory"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_repo(
|
||||||
|
source: Path,
|
||||||
|
destination: Path,
|
||||||
|
) -> None:
|
||||||
|
def ignore_func(_, files):
|
||||||
|
return [f for f in files if f == ".git"]
|
||||||
|
|
||||||
|
shutil.copytree(source, destination, ignore=ignore_func)
|
16
libs/cli/langchain_cli/utils/packages.py
Normal file
16
libs/cli/langchain_cli/utils/packages.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Set, Optional
|
||||||
|
|
||||||
|
|
||||||
|
def get_package_root(cwd: Optional[Path] = None) -> Path:
|
||||||
|
# traverse path for routes to host (any directory holding a pyproject.toml file)
|
||||||
|
package_root = Path.cwd() if cwd is None else cwd
|
||||||
|
visited: Set[Path] = set()
|
||||||
|
while package_root not in visited:
|
||||||
|
visited.add(package_root)
|
||||||
|
|
||||||
|
pyproject_path = package_root / "pyproject.toml"
|
||||||
|
if pyproject_path.exists():
|
||||||
|
return package_root
|
||||||
|
package_root = package_root.parent
|
||||||
|
raise FileNotFoundError("No pyproject.toml found")
|
21
libs/cli/package_template/LICENSE
Normal file
21
libs/cli/package_template/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 LangChain, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
1
libs/cli/package_template/README.md
Normal file
1
libs/cli/package_template/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# __package_name_last__
|
18
libs/cli/package_template/package_template/chain.py
Normal file
18
libs/cli/package_template/package_template/chain.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from langchain.prompts import ChatPromptTemplate
|
||||||
|
from langchain.chat_models import ChatOpenAI
|
||||||
|
|
||||||
|
|
||||||
|
_prompt = ChatPromptTemplate.from_messages(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"system",
|
||||||
|
"You are a helpful assistant who speaks like a pirate",
|
||||||
|
),
|
||||||
|
("human", "{text}"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
_model = ChatOpenAI()
|
||||||
|
|
||||||
|
# if you update this, you MUST also update ../pyproject.toml
|
||||||
|
# with the new `tool.langserve.export_attr`
|
||||||
|
chain = _prompt | _model
|
1452
libs/cli/package_template/poetry.lock
generated
Normal file
1452
libs/cli/package_template/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
libs/cli/package_template/pyproject.toml
Normal file
32
libs/cli/package_template/pyproject.toml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "__package_name__"
|
||||||
|
version = "0.0.1"
|
||||||
|
description = ""
|
||||||
|
authors = []
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = ">=3.8.1,<4.0"
|
||||||
|
langchain = ">=0.0.313, <0.1"
|
||||||
|
openai = "^0.28.1"
|
||||||
|
fastapi = "^0.104.0"
|
||||||
|
sse-starlette = "^1.6.5"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
langchain-cli = {git = "https://github.com/langchain-ai/langchain.git", rev = "erick/cli", subdirectory = "libs/cli"}
|
||||||
|
poethepoet = "^0.24.1"
|
||||||
|
|
||||||
|
[tool.langserve]
|
||||||
|
export_module = "__package_name__.chain"
|
||||||
|
export_attr = "chain"
|
||||||
|
|
||||||
|
[tool.poe.tasks.start]
|
||||||
|
cmd="uvicorn langchain_cli.dev_scripts:create_demo_server --reload --port $port --host $host"
|
||||||
|
args = [
|
||||||
|
{name = "port", help = "port to run on", default = "8000"},
|
||||||
|
{name = "host", help = "host to run on", default = "127.0.0.1"}
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
0
libs/cli/package_template/tests/__init__.py
Normal file
0
libs/cli/package_template/tests/__init__.py
Normal file
1491
libs/cli/poetry.lock
generated
Normal file
1491
libs/cli/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
libs/cli/project_template/README.md
Normal file
32
libs/cli/project_template/README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# LangServeHub Project Template
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Install the LangChain CLI if you haven't yet
|
||||||
|
```bash
|
||||||
|
pip install --user --upgrade git+https://github.com/pingpong-templates/cli.git
|
||||||
|
```
|
||||||
|
And install this package's dependencies
|
||||||
|
```bash
|
||||||
|
poetry install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding packages
|
||||||
|
```bash
|
||||||
|
# if you have problems with `poe`, try `poetry run poe`
|
||||||
|
|
||||||
|
# adding packages from https://github.com/pingpong-templates/hub
|
||||||
|
langchain serve add simple-pirate
|
||||||
|
|
||||||
|
# adding custom GitHub repo packages
|
||||||
|
langchain serve add git+https://github.com/hwchase17/chain-of-verification
|
||||||
|
|
||||||
|
# with a custom api mount point (defaults to `/{package_name}`)
|
||||||
|
poe add simple-translator --api_path=/my/custom/path/translator
|
||||||
|
```
|
||||||
|
|
||||||
|
## Removing packages
|
||||||
|
|
||||||
|
Note: you remove packages by their api path
|
||||||
|
```bash
|
||||||
|
langchain serve remove pirate
|
||||||
|
```
|
0
libs/cli/project_template/app/__init__.py
Normal file
0
libs/cli/project_template/app/__init__.py
Normal file
12
libs/cli/project_template/app/server.py
Normal file
12
libs/cli/project_template/app/server.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from langserve import add_routes
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
# Edit this to add the chain you want to add
|
||||||
|
add_routes(app, NotImplemented)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8001)
|
0
libs/cli/project_template/packages/README.md
Normal file
0
libs/cli/project_template/packages/README.md
Normal file
1570
libs/cli/project_template/poetry.lock
generated
Normal file
1570
libs/cli/project_template/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
libs/cli/project_template/pyproject.toml
Normal file
31
libs/cli/project_template/pyproject.toml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "langservehub-template"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["Your Name <you@example.com>"]
|
||||||
|
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 = {git = "https://github.com/langchain-ai/langserve"}
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
poethepoet = "^0.24.1"
|
||||||
|
uvicorn = "^0.23.2"
|
||||||
|
pygithub = "^2.1.1"
|
||||||
|
|
||||||
|
[tool.poe.tasks.start]
|
||||||
|
cmd="uvicorn app.server:app --reload --port $port --host $host"
|
||||||
|
args = [
|
||||||
|
{name = "port", help = "port to run on", default = "8000"},
|
||||||
|
{name = "host", help = "host to run on", default = "127.0.0.1"}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
38
libs/cli/pyproject.toml
Normal file
38
libs/cli/pyproject.toml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "langchain-cli"
|
||||||
|
version = "0.0.1rc2"
|
||||||
|
description = "CLI for interacting with LangChain"
|
||||||
|
authors = ["Erick Friis <erick@langchain.dev>"]
|
||||||
|
readme = "README.md"
|
||||||
|
include = [
|
||||||
|
{path="project_template/**/*", format="wheel"},
|
||||||
|
{path="package_template/**/*", format="wheel"}
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = ">=3.8.1,<4.0"
|
||||||
|
typer = {extras = ["all"], version = "^0.9.0"}
|
||||||
|
tomli = "^2.0.1"
|
||||||
|
gitpython = "^3.1.40"
|
||||||
|
langserve = "^0.0.16"
|
||||||
|
fastapi = "^0.104.0"
|
||||||
|
uvicorn = "^0.23.2"
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
langchain = "langchain_cli.cli:app"
|
||||||
|
langchain-cli = "langchain_cli.cli:app"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
poethepoet = "^0.24.1"
|
||||||
|
pytest = "^7.4.2"
|
||||||
|
pytest-watch = "^4.2.0"
|
||||||
|
|
||||||
|
[tool.poe.tasks]
|
||||||
|
test = "poetry run pytest"
|
||||||
|
watch = "poetry run ptw"
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
0
libs/cli/tests/__init__.py
Normal file
0
libs/cli/tests/__init__.py
Normal file
32
libs/cli/tests/test_utils.py
Normal file
32
libs/cli/tests/test_utils.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from langchain_cli.utils.git import _parse_dependency_string, DependencySource
|
||||||
|
from langchain_cli.constants import DEFAULT_GIT_REPO, DEFAULT_GIT_SUBDIRECTORY
|
||||||
|
|
||||||
|
|
||||||
|
def test_dependency_string() -> None:
|
||||||
|
assert _parse_dependency_string(
|
||||||
|
"git+ssh://git@github.com/efriis/myrepo.git"
|
||||||
|
) == DependencySource(
|
||||||
|
git="ssh://git@github.com/efriis/myrepo.git",
|
||||||
|
ref=None,
|
||||||
|
subdirectory=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert _parse_dependency_string(
|
||||||
|
"git+https://github.com/efriis/myrepo.git#subdirectory=src"
|
||||||
|
) == DependencySource(
|
||||||
|
git="https://github.com/efriis/myrepo.git",
|
||||||
|
subdirectory="src",
|
||||||
|
ref=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert _parse_dependency_string(
|
||||||
|
"git+ssh://git@github.com:efriis/myrepo.git#develop"
|
||||||
|
) == DependencySource(
|
||||||
|
git="ssh://git@github.com:efriis/myrepo.git", ref="develop", subdirectory=None
|
||||||
|
)
|
||||||
|
|
||||||
|
assert _parse_dependency_string("simple-pirate") == DependencySource(
|
||||||
|
git=DEFAULT_GIT_REPO,
|
||||||
|
subdirectory=f"{DEFAULT_GIT_SUBDIRECTORY}/simple-pirate",
|
||||||
|
ref=None,
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user