mirror of
https://github.com/hwchase17/langchain.git
synced 2025-08-01 09:04:03 +00:00
cli improvements (#12465)
Features - add multiple repos by their branch/repo - generate `pip install` commands and `add_route()` code  Optimizations: - group installs by repo/branch to avoid duplicate cloning
This commit is contained in:
parent
5545de0466
commit
9adaa78c65
@ -5,17 +5,17 @@ Manage LangServe application projects.
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import tomli
|
|
||||||
import typer
|
import typer
|
||||||
from langserve.packages import get_langserve_export, list_packages
|
from langserve.packages import get_langserve_export, list_packages
|
||||||
from typing_extensions import Annotated
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
from langchain_cli.utils.events import create_events
|
from langchain_cli.utils.events import create_events
|
||||||
from langchain_cli.utils.git import (
|
from langchain_cli.utils.git import (
|
||||||
|
DependencySource,
|
||||||
copy_repo,
|
copy_repo,
|
||||||
parse_dependency_string,
|
parse_dependencies,
|
||||||
update_repo,
|
update_repo,
|
||||||
)
|
)
|
||||||
from langchain_cli.utils.packages import get_package_root
|
from langchain_cli.utils.packages import get_package_root
|
||||||
@ -79,7 +79,10 @@ def add(
|
|||||||
Optional[Path], typer.Option(help="The project directory")
|
Optional[Path], typer.Option(help="The project directory")
|
||||||
] = None,
|
] = None,
|
||||||
repo: Annotated[
|
repo: Annotated[
|
||||||
List[str], typer.Option(help="Shorthand for installing a GitHub Repo")
|
List[str], typer.Option(help="Install deps from a specific github repo instead")
|
||||||
|
] = [],
|
||||||
|
branch: Annotated[
|
||||||
|
List[str], typer.Option(help="Install deps from a specific branch")
|
||||||
] = [],
|
] = [],
|
||||||
with_poetry: Annotated[
|
with_poetry: Annotated[
|
||||||
bool,
|
bool,
|
||||||
@ -94,80 +97,112 @@ def add(
|
|||||||
langchain serve add git+ssh://git@github.com/efriis/simple-pirate.git
|
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
|
langchain serve add git+https://github.com/efriis/hub.git#devbranch#subdirectory=mypackage
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
parsed_deps = parse_dependencies(dependencies, repo, branch, api_path)
|
||||||
|
|
||||||
project_root = get_package_root(project_dir)
|
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"
|
package_dir = project_root / "packages"
|
||||||
|
|
||||||
create_events(
|
create_events(
|
||||||
[{"event": "serve add", "properties": {"package": d}} for d in dependencies]
|
[{"event": "serve add", "properties": dict(parsed_dep=d)} for d in parsed_deps]
|
||||||
)
|
)
|
||||||
|
|
||||||
for i, dependency in enumerate(dependencies):
|
# group by repo/ref
|
||||||
# update repo
|
grouped: Dict[Tuple[str, Optional[str]], List[DependencySource]] = {}
|
||||||
typer.echo(f"Adding {dependency}...")
|
for dep in parsed_deps:
|
||||||
dep = parse_dependency_string(dependency)
|
key_tup = (dep["git"], dep["ref"])
|
||||||
source_repo_path = update_repo(dep["git"], dep["ref"], REPO_DIR)
|
lst = grouped.get(key_tup, [])
|
||||||
source_path = (
|
lst.append(dep)
|
||||||
source_repo_path / dep["subdirectory"]
|
grouped[key_tup] = lst
|
||||||
if dep["subdirectory"]
|
|
||||||
else source_repo_path
|
|
||||||
)
|
|
||||||
pyproject_path = source_path / "pyproject.toml"
|
|
||||||
if not pyproject_path.exists():
|
|
||||||
typer.echo(f"Could not find {pyproject_path}")
|
|
||||||
continue
|
|
||||||
langserve_export = get_langserve_export(pyproject_path)
|
|
||||||
|
|
||||||
# detect name conflict
|
installed_destination_paths: List[Path] = []
|
||||||
if langserve_export["package_name"] in installed_names:
|
installed_exports: List[Dict] = []
|
||||||
typer.echo(
|
|
||||||
f"Package with name {langserve_export['package_name']} already "
|
|
||||||
"installed. Skipping...",
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
inner_api_path = (
|
for (git, ref), group_deps in grouped.items():
|
||||||
api_path[i] if len(api_path) != 0 else langserve_export["package_name"]
|
if len(group_deps) == 1:
|
||||||
|
typer.echo(f"Adding {git}@{ref}...")
|
||||||
|
else:
|
||||||
|
typer.echo(f"Adding {len(group_deps)} dependencies from {git}@{ref}")
|
||||||
|
source_repo_path = update_repo(git, ref, REPO_DIR)
|
||||||
|
|
||||||
|
for dep in group_deps:
|
||||||
|
source_path = (
|
||||||
|
source_repo_path / dep["subdirectory"]
|
||||||
|
if dep["subdirectory"]
|
||||||
|
else source_repo_path
|
||||||
|
)
|
||||||
|
pyproject_path = source_path / "pyproject.toml"
|
||||||
|
if not pyproject_path.exists():
|
||||||
|
typer.echo(f"Could not find {pyproject_path}")
|
||||||
|
continue
|
||||||
|
langserve_export = get_langserve_export(pyproject_path)
|
||||||
|
|
||||||
|
# default path to package_name
|
||||||
|
inner_api_path = dep["api_path"] or langserve_export["package_name"]
|
||||||
|
|
||||||
|
destination_path = package_dir / inner_api_path
|
||||||
|
if destination_path.exists():
|
||||||
|
typer.echo(
|
||||||
|
f"Folder {str(inner_api_path)} already exists. " "Skipping...",
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
copy_repo(source_path, destination_path)
|
||||||
|
typer.echo(f" - Downloaded {dep['subdirectory']} to {inner_api_path}")
|
||||||
|
installed_destination_paths.append(destination_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
|
||||||
|
]
|
||||||
|
|
||||||
|
if with_poetry:
|
||||||
|
subprocess.run(
|
||||||
|
["poetry", "add", "--editable"] + installed_desination_strs,
|
||||||
|
cwd=cwd,
|
||||||
)
|
)
|
||||||
destination_path = package_dir / inner_api_path
|
else:
|
||||||
if destination_path.exists():
|
cmd = ["pip", "install", "-e"] + installed_desination_strs
|
||||||
typer.echo(
|
cmd_str = " \\\n ".join(installed_desination_strs)
|
||||||
f"Endpoint {langserve_export['package_name']} already exists. "
|
install_str = f"To install:\n\npip install -e \\\n {cmd_str}"
|
||||||
"Skipping...",
|
typer.echo(install_str)
|
||||||
)
|
|
||||||
continue
|
if typer.confirm("Run it?"):
|
||||||
copy_repo(source_path, destination_path)
|
subprocess.run(cmd, cwd=cwd)
|
||||||
# poetry install
|
if typer.confirm("\nGenerate route code for these packages?", default=True):
|
||||||
if with_poetry:
|
chain_names = []
|
||||||
subprocess.run(
|
for e in installed_exports:
|
||||||
["poetry", "add", "--editable", destination_path], cwd=project_root
|
original_candidate = f'{e["package_name"].replace("-", "_")}_chain'
|
||||||
)
|
candidate = original_candidate
|
||||||
|
i = 2
|
||||||
|
while candidate in chain_names:
|
||||||
|
candidate = original_candidate + "_" + str(i)
|
||||||
|
i += 1
|
||||||
|
chain_names.append(candidate)
|
||||||
|
|
||||||
|
api_paths = [
|
||||||
|
str(Path("/") / path.relative_to(package_dir))
|
||||||
|
for path in installed_destination_paths
|
||||||
|
]
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
f"from {e['module']} import {e['attr']} as {name}"
|
||||||
|
for e, name in zip(installed_exports, chain_names)
|
||||||
|
]
|
||||||
|
routes = [
|
||||||
|
f'add_routes(app, {name}, path="{path}")'
|
||||||
|
for name, path in zip(chain_names, api_paths)
|
||||||
|
]
|
||||||
|
|
||||||
|
lines = (
|
||||||
|
["", "Great! Add the following to your app:", ""] + imports + [""] + routes
|
||||||
|
)
|
||||||
|
typer.echo("\n".join(lines))
|
||||||
|
|
||||||
|
|
||||||
@serve.command()
|
@serve.command()
|
||||||
|
@ -2,7 +2,7 @@ import hashlib
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, TypedDict
|
from typing import Dict, List, Optional, Sequence, TypedDict
|
||||||
|
|
||||||
from git import Repo
|
from git import Repo
|
||||||
|
|
||||||
@ -17,13 +17,25 @@ class DependencySource(TypedDict):
|
|||||||
git: str
|
git: str
|
||||||
ref: Optional[str]
|
ref: Optional[str]
|
||||||
subdirectory: Optional[str]
|
subdirectory: Optional[str]
|
||||||
|
api_path: Optional[str]
|
||||||
|
event_metadata: Dict
|
||||||
|
|
||||||
|
|
||||||
# use poetry dependency string format
|
# use poetry dependency string format
|
||||||
def parse_dependency_string(package_string: str) -> DependencySource:
|
def parse_dependency_string(
|
||||||
if package_string.startswith("git+"):
|
dep: Optional[str],
|
||||||
|
repo: Optional[str],
|
||||||
|
branch: Optional[str],
|
||||||
|
api_path: Optional[str],
|
||||||
|
) -> DependencySource:
|
||||||
|
if dep is not None and dep.startswith("git+"):
|
||||||
|
if repo is not None or branch is not None:
|
||||||
|
raise ValueError(
|
||||||
|
"If a dependency starts with git+, you cannot manually specify "
|
||||||
|
"a repo or branch."
|
||||||
|
)
|
||||||
# remove git+
|
# remove git+
|
||||||
gitstring = package_string[4:]
|
gitstring = dep[4:]
|
||||||
subdirectory = None
|
subdirectory = None
|
||||||
ref = None
|
ref = None
|
||||||
# first check for #subdirectory= on the end
|
# first check for #subdirectory= on the end
|
||||||
@ -62,16 +74,77 @@ def parse_dependency_string(package_string: str) -> DependencySource:
|
|||||||
git=gitstring,
|
git=gitstring,
|
||||||
ref=ref,
|
ref=ref,
|
||||||
subdirectory=subdirectory,
|
subdirectory=subdirectory,
|
||||||
|
api_path=api_path,
|
||||||
|
event_metadata={"dependency_string": dep},
|
||||||
)
|
)
|
||||||
|
|
||||||
elif package_string.startswith("https://"):
|
elif dep is not None and dep.startswith("https://"):
|
||||||
raise NotImplementedError("url dependencies are not supported yet")
|
raise ValueError("Only git dependencies are supported")
|
||||||
else:
|
else:
|
||||||
# it's a default git repo dependency
|
# if repo is none, use default, including subdirectory
|
||||||
subdirectory = str(Path(DEFAULT_GIT_SUBDIRECTORY) / package_string)
|
base_subdir = Path(DEFAULT_GIT_SUBDIRECTORY) if repo is None else Path()
|
||||||
return DependencySource(
|
subdir = str(base_subdir / dep) if dep is not None else None
|
||||||
git=DEFAULT_GIT_REPO, ref=DEFAULT_GIT_REF, subdirectory=subdirectory
|
gitstring = (
|
||||||
|
DEFAULT_GIT_REPO
|
||||||
|
if repo is None
|
||||||
|
else f"https://github.com/{repo.strip('/')}.git"
|
||||||
)
|
)
|
||||||
|
ref = DEFAULT_GIT_REF if branch is None else branch
|
||||||
|
# it's a default git repo dependency
|
||||||
|
return DependencySource(
|
||||||
|
git=gitstring,
|
||||||
|
ref=ref,
|
||||||
|
subdirectory=subdir,
|
||||||
|
api_path=api_path,
|
||||||
|
event_metadata={
|
||||||
|
"dependency_string": dep,
|
||||||
|
"used_repo_flag": repo is not None,
|
||||||
|
"used_branch_flag": branch is not None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _list_arg_to_length(arg: Optional[List[str]], num: int) -> Sequence[Optional[str]]:
|
||||||
|
if not arg:
|
||||||
|
return [None] * num
|
||||||
|
elif len(arg) == 1:
|
||||||
|
return arg * num
|
||||||
|
elif len(arg) == num:
|
||||||
|
return arg
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Argument must be of length 1 or {num}")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_dependencies(
|
||||||
|
dependencies: Optional[List[str]],
|
||||||
|
repo: List[str],
|
||||||
|
branch: List[str],
|
||||||
|
api_path: List[str],
|
||||||
|
) -> List[DependencySource]:
|
||||||
|
num_deps = max(
|
||||||
|
len(dependencies) if dependencies is not None else 0, len(repo), len(branch)
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
(dependencies and len(dependencies) != num_deps)
|
||||||
|
or (api_path and len(api_path) != num_deps)
|
||||||
|
or (repo and len(repo) not in [1, num_deps])
|
||||||
|
or (branch and len(branch) not in [1, num_deps])
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
"Number of defined repos/branches/api_paths did not match the "
|
||||||
|
"number of dependencies."
|
||||||
|
)
|
||||||
|
inner_deps = _list_arg_to_length(dependencies, num_deps)
|
||||||
|
inner_api_paths = _list_arg_to_length(api_path, num_deps)
|
||||||
|
inner_repos = _list_arg_to_length(repo, num_deps)
|
||||||
|
inner_branches = _list_arg_to_length(branch, num_deps)
|
||||||
|
|
||||||
|
return [
|
||||||
|
parse_dependency_string(iter_dep, iter_repo, iter_branch, iter_api_path)
|
||||||
|
for iter_dep, iter_repo, iter_branch, iter_api_path in zip(
|
||||||
|
inner_deps, inner_repos, inner_branches, inner_api_paths
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _get_repo_path(gitstring: str, repo_dir: Path) -> Path:
|
def _get_repo_path(gitstring: str, repo_dir: Path) -> Path:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langchain-cli"
|
name = "langchain-cli"
|
||||||
version = "0.0.7"
|
version = "0.0.8"
|
||||||
description = "CLI for interacting with LangChain"
|
description = "CLI for interacting with LangChain"
|
||||||
authors = ["Erick Friis <erick@langchain.dev>"]
|
authors = ["Erick Friis <erick@langchain.dev>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from langchain_cli.constants import (
|
from langchain_cli.constants import (
|
||||||
@ -8,44 +10,74 @@ from langchain_cli.constants import (
|
|||||||
from langchain_cli.utils.git import DependencySource, parse_dependency_string
|
from langchain_cli.utils.git import DependencySource, parse_dependency_string
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_dependency_equals(
|
||||||
|
dep: DependencySource,
|
||||||
|
*,
|
||||||
|
git: Optional[str] = None,
|
||||||
|
ref: Optional[str] = None,
|
||||||
|
subdirectory: Optional[str] = None,
|
||||||
|
event_metadata: Optional[Dict] = None,
|
||||||
|
) -> None:
|
||||||
|
assert dep["git"] == git
|
||||||
|
assert dep["ref"] == ref
|
||||||
|
assert dep["subdirectory"] == subdirectory
|
||||||
|
if event_metadata is not None:
|
||||||
|
assert dep["event_metadata"] == event_metadata
|
||||||
|
|
||||||
|
|
||||||
def test_dependency_string() -> None:
|
def test_dependency_string() -> None:
|
||||||
assert parse_dependency_string(
|
_assert_dependency_equals(
|
||||||
"git+ssh://git@github.com/efriis/myrepo.git"
|
parse_dependency_string(
|
||||||
) == DependencySource(
|
"git+ssh://git@github.com/efriis/myrepo.git", None, None, None
|
||||||
|
),
|
||||||
git="ssh://git@github.com/efriis/myrepo.git",
|
git="ssh://git@github.com/efriis/myrepo.git",
|
||||||
ref=None,
|
ref=None,
|
||||||
subdirectory=None,
|
subdirectory=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert parse_dependency_string(
|
_assert_dependency_equals(
|
||||||
"git+https://github.com/efriis/myrepo.git#subdirectory=src"
|
parse_dependency_string(
|
||||||
) == DependencySource(
|
"git+https://github.com/efriis/myrepo.git#subdirectory=src",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
git="https://github.com/efriis/myrepo.git",
|
git="https://github.com/efriis/myrepo.git",
|
||||||
subdirectory="src",
|
subdirectory="src",
|
||||||
ref=None,
|
ref=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert parse_dependency_string(
|
_assert_dependency_equals(
|
||||||
"git+ssh://git@github.com:efriis/myrepo.git#develop"
|
parse_dependency_string(
|
||||||
) == DependencySource(
|
"git+ssh://git@github.com:efriis/myrepo.git#develop", None, None, None
|
||||||
git="ssh://git@github.com:efriis/myrepo.git", ref="develop", subdirectory=None
|
),
|
||||||
|
git="ssh://git@github.com:efriis/myrepo.git",
|
||||||
|
ref="develop",
|
||||||
|
subdirectory=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# also support a slash in ssh
|
# also support a slash in ssh
|
||||||
assert parse_dependency_string(
|
_assert_dependency_equals(
|
||||||
"git+ssh://git@github.com/efriis/myrepo.git#develop"
|
parse_dependency_string(
|
||||||
) == DependencySource(
|
"git+ssh://git@github.com/efriis/myrepo.git#develop", None, None, None
|
||||||
git="ssh://git@github.com/efriis/myrepo.git", ref="develop", subdirectory=None
|
),
|
||||||
|
git="ssh://git@github.com/efriis/myrepo.git",
|
||||||
|
ref="develop",
|
||||||
|
subdirectory=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# looks like poetry supports both an @ and a #
|
# looks like poetry supports both an @ and a #
|
||||||
assert parse_dependency_string(
|
_assert_dependency_equals(
|
||||||
"git+ssh://git@github.com:efriis/myrepo.git@develop"
|
parse_dependency_string(
|
||||||
) == DependencySource(
|
"git+ssh://git@github.com:efriis/myrepo.git@develop", None, None, None
|
||||||
git="ssh://git@github.com:efriis/myrepo.git", ref="develop", subdirectory=None
|
),
|
||||||
|
git="ssh://git@github.com:efriis/myrepo.git",
|
||||||
|
ref="develop",
|
||||||
|
subdirectory=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert parse_dependency_string("simple-pirate") == DependencySource(
|
_assert_dependency_equals(
|
||||||
|
parse_dependency_string("simple-pirate", None, None, None),
|
||||||
git=DEFAULT_GIT_REPO,
|
git=DEFAULT_GIT_REPO,
|
||||||
subdirectory=f"{DEFAULT_GIT_SUBDIRECTORY}/simple-pirate",
|
subdirectory=f"{DEFAULT_GIT_SUBDIRECTORY}/simple-pirate",
|
||||||
ref=DEFAULT_GIT_REF,
|
ref=DEFAULT_GIT_REF,
|
||||||
@ -53,9 +85,13 @@ def test_dependency_string() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_dependency_string_both() -> None:
|
def test_dependency_string_both() -> None:
|
||||||
assert parse_dependency_string(
|
_assert_dependency_equals(
|
||||||
"git+https://github.com/efriis/myrepo.git@branch#subdirectory=src"
|
parse_dependency_string(
|
||||||
) == DependencySource(
|
"git+https://github.com/efriis/myrepo.git@branch#subdirectory=src",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
git="https://github.com/efriis/myrepo.git",
|
git="https://github.com/efriis/myrepo.git",
|
||||||
subdirectory="src",
|
subdirectory="src",
|
||||||
ref="branch",
|
ref="branch",
|
||||||
@ -66,7 +102,10 @@ def test_dependency_string_invalids() -> None:
|
|||||||
# expect error for wrong order
|
# expect error for wrong order
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
parse_dependency_string(
|
parse_dependency_string(
|
||||||
"git+https://github.com/efriis/myrepo.git#subdirectory=src@branch"
|
"git+https://github.com/efriis/myrepo.git#subdirectory=src@branch",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
# expect error for @subdirectory
|
# expect error for @subdirectory
|
||||||
|
|
||||||
@ -77,16 +116,21 @@ def test_dependency_string_edge_case() -> None:
|
|||||||
# this could be a ssh dep with user=a, and default ref
|
# this could be a ssh dep with user=a, and default ref
|
||||||
# or a ssh dep at a with ref=b.
|
# or a ssh dep at a with ref=b.
|
||||||
# in this case, assume the first case (be greedy with the '@')
|
# in this case, assume the first case (be greedy with the '@')
|
||||||
assert parse_dependency_string("git+ssh://a@b") == DependencySource(
|
_assert_dependency_equals(
|
||||||
|
parse_dependency_string("git+ssh://a@b", None, None, None),
|
||||||
git="ssh://a@b",
|
git="ssh://a@b",
|
||||||
subdirectory=None,
|
subdirectory=None,
|
||||||
ref=None,
|
ref=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# weird one that is actually valid
|
# weird one that is actually valid
|
||||||
assert parse_dependency_string(
|
_assert_dependency_equals(
|
||||||
"git+https://github.com/efriis/myrepo.git@subdirectory=src"
|
parse_dependency_string(
|
||||||
) == DependencySource(
|
"git+https://github.com/efriis/myrepo.git@subdirectory=src",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
git="https://github.com/efriis/myrepo.git",
|
git="https://github.com/efriis/myrepo.git",
|
||||||
subdirectory=None,
|
subdirectory=None,
|
||||||
ref="subdirectory=src",
|
ref="subdirectory=src",
|
||||||
|
Loading…
Reference in New Issue
Block a user