mirror of
https://github.com/hwchase17/langchain.git
synced 2025-09-20 18:12:35 +00:00
refactor(cli): drop Python 3.9 (#32964)
This commit is contained in:
2
.github/scripts/check_diff.py
vendored
2
.github/scripts/check_diff.py
vendored
@@ -134,6 +134,8 @@ def _get_configs_for_single_dir(job: str, dir_: str) -> List[Dict[str, str]]:
|
||||
py_versions = ["3.9", "3.13"]
|
||||
elif dir_ == "libs/langchain_v1":
|
||||
py_versions = ["3.10", "3.13"]
|
||||
elif dir_ in {"libs/cli"}:
|
||||
py_versions = ["3.10", "3.13"]
|
||||
|
||||
elif dir_ == ".":
|
||||
# unable to install with 3.13 because tokenizers doesn't support 3.13 yet
|
||||
|
@@ -38,15 +38,16 @@ _e2e_test:
|
||||
mkdir .integration_test
|
||||
cd .integration_test && \
|
||||
python3 -m venv .venv && \
|
||||
pip install --upgrade poetry && \
|
||||
$(PYTHON) -m pip install --upgrade uv && \
|
||||
$(PYTHON) -m pip install -e .. && \
|
||||
$(PYTHON) -m langchain_cli.cli integration new --name parrot-link --name-class ParrotLink && \
|
||||
$(PYTHON) -m langchain_cli.cli integration new --name parrot-link --name-class ParrotLinkB --src=integration_template/chat_models.py --dst=langchain-parrot-link/langchain_parrot_link/chat_models_b.py && \
|
||||
$(PYTHON) -m langchain_cli.cli integration create-doc --name parrot-link --name-class ParrotLinkB --component-type ChatModel --destination-dir langchain-parrot-link/docs && \
|
||||
cd langchain-parrot-link && \
|
||||
poetry install --with lint,typing,test && \
|
||||
poetry run pip install -e ../../../standard-tests && \
|
||||
unset UV_FROZEN && \
|
||||
unset VIRTUAL_ENV && \
|
||||
uv sync && \
|
||||
uv add --editable ../../../standard-tests && \
|
||||
make format lint tests && \
|
||||
poetry install --with test_integration && \
|
||||
poetry run pip install -e ../../../core && \
|
||||
uv add --editable ../../../core && \
|
||||
make integration_test
|
||||
|
@@ -1,6 +1,8 @@
|
||||
"""LangChain CLI."""
|
||||
|
||||
from typing import Annotated, Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
|
||||
@@ -61,11 +63,11 @@ def _main(
|
||||
def serve(
|
||||
*,
|
||||
port: Annotated[
|
||||
Optional[int],
|
||||
int | None,
|
||||
typer.Option(help="The port to run the server on"),
|
||||
] = None,
|
||||
host: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
typer.Option(help="The host to run the server on"),
|
||||
] = None,
|
||||
) -> None:
|
||||
|
@@ -10,14 +10,14 @@ integration_test integration_tests: TEST_FILE = tests/integration_tests/
|
||||
|
||||
# unit tests are run with the --disable-socket flag to prevent network calls
|
||||
test tests:
|
||||
poetry run pytest --disable-socket --allow-unix-socket $(TEST_FILE)
|
||||
uv run pytest --disable-socket --allow-unix-socket $(TEST_FILE)
|
||||
|
||||
test_watch:
|
||||
poetry run ptw --snapshot-update --now . -- -vv $(TEST_FILE)
|
||||
uv run ptw --snapshot-update --now . -- -vv $(TEST_FILE)
|
||||
|
||||
# integration tests are run without the --disable-socket flag to allow network calls
|
||||
integration_test integration_tests:
|
||||
poetry run pytest $(TEST_FILE)
|
||||
uv run pytest $(TEST_FILE)
|
||||
|
||||
######################
|
||||
# LINTING AND FORMATTING
|
||||
@@ -33,22 +33,22 @@ lint_tests: PYTHON_FILES=tests
|
||||
lint_tests: MYPY_CACHE=.mypy_cache_test
|
||||
|
||||
lint lint_diff lint_package lint_tests:
|
||||
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff check $(PYTHON_FILES)
|
||||
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff
|
||||
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) && poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
|
||||
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check $(PYTHON_FILES)
|
||||
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
|
||||
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) && uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
|
||||
|
||||
format format_diff:
|
||||
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES)
|
||||
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --fix $(PYTHON_FILES)
|
||||
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES)
|
||||
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --fix $(PYTHON_FILES)
|
||||
|
||||
spell_check:
|
||||
poetry run codespell --toml pyproject.toml
|
||||
uv run codespell --toml pyproject.toml
|
||||
|
||||
spell_fix:
|
||||
poetry run codespell --toml pyproject.toml -w
|
||||
uv run codespell --toml pyproject.toml -w
|
||||
|
||||
check_imports: $(shell find __module_name__ -name '*.py')
|
||||
poetry run python ./scripts/check_imports.py $^
|
||||
uv run python ./scripts/check_imports.py $^
|
||||
|
||||
######################
|
||||
# HELP
|
||||
|
@@ -89,7 +89,7 @@ class Chat__ModuleName__(BaseChatModel):
|
||||
.. code-block:: python
|
||||
|
||||
for chunk in llm.stream(messages):
|
||||
print(chunk.text(), end="")
|
||||
print(chunk.text, end="")
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@@ -1,26 +1,38 @@
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
requires = ["pdm-backend"]
|
||||
build-backend = "pdm.backend"
|
||||
|
||||
[tool.poetry]
|
||||
[project]
|
||||
name = "__package_name__"
|
||||
version = "0.1.0"
|
||||
description = "An integration package connecting __ModuleName__ and LangChain"
|
||||
authors = []
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/langchain-ai/langchain"
|
||||
license = "MIT"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"langchain-core>=0.3.15",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
"Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/__package_name_short__"
|
||||
"Release Notes" = "https://github.com/langchain-ai/langchain/releases?q=tag%3A%22__package_name_short__%3D%3D0%22&expanded=true"
|
||||
"Repository" = "https://github.com/langchain-ai/langchain"
|
||||
|
||||
[tool.mypy]
|
||||
disallow_untyped_defs = "True"
|
||||
|
||||
[tool.poetry.urls]
|
||||
"Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/__package_name_short__"
|
||||
"Release Notes" = "https://github.com/langchain-ai/langchain/releases?q=tag%3A%22__package_name_short__%3D%3D0%22&expanded=true"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9,<4.0"
|
||||
langchain-core = "^0.3.15"
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
"pytest>=7.4.3",
|
||||
"pytest-asyncio>=0.23.2",
|
||||
"pytest-socket>=0.7.0",
|
||||
"pytest-watcher>=0.3.4",
|
||||
"langchain-tests>=0.3.5",
|
||||
"codespell>=2.2.6",
|
||||
"ruff>=0.5",
|
||||
"mypy>=1.10",
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I", "T201"]
|
||||
@@ -37,38 +49,3 @@ markers = [
|
||||
"compile: mark placeholder test used to compile integration tests without running them",
|
||||
]
|
||||
asyncio_mode = "auto"
|
||||
|
||||
[tool.poetry.group.test]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.codespell]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.test_integration]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.lint]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.dev]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
pytest = "^7.4.3"
|
||||
pytest-asyncio = "^0.23.2"
|
||||
pytest-socket = "^0.7.0"
|
||||
pytest-watcher = "^0.3.4"
|
||||
langchain-tests = "^0.3.5"
|
||||
|
||||
[tool.poetry.group.codespell.dependencies]
|
||||
codespell = "^2.2.6"
|
||||
|
||||
[tool.poetry.group.test_integration.dependencies]
|
||||
|
||||
[tool.poetry.group.lint.dependencies]
|
||||
ruff = "^0.5"
|
||||
|
||||
[tool.poetry.group.typing.dependencies]
|
||||
mypy = "^1.10"
|
||||
|
@@ -50,7 +50,6 @@ def new(
|
||||
typer.Option(
|
||||
"--pip/--no-pip",
|
||||
help="Pip install the template(s) as editable dependencies",
|
||||
is_flag=True,
|
||||
),
|
||||
] = None,
|
||||
noninteractive: Annotated[
|
||||
@@ -58,7 +57,6 @@ def new(
|
||||
typer.Option(
|
||||
"--non-interactive/--interactive",
|
||||
help="Don't prompt for any input",
|
||||
is_flag=True,
|
||||
),
|
||||
] = False,
|
||||
) -> None:
|
||||
@@ -154,7 +152,6 @@ def add(
|
||||
typer.Option(
|
||||
"--pip/--no-pip",
|
||||
help="Pip install the template(s) as editable dependencies",
|
||||
is_flag=True,
|
||||
prompt="Would you like to `pip install -e` the template(s)?",
|
||||
),
|
||||
],
|
||||
@@ -241,7 +238,7 @@ def add(
|
||||
try:
|
||||
add_dependencies_to_pyproject_toml(
|
||||
project_root / "pyproject.toml",
|
||||
zip(installed_destination_names, installed_destination_paths),
|
||||
zip(installed_destination_names, installed_destination_paths, strict=False),
|
||||
)
|
||||
except Exception:
|
||||
# Can fail if user modified/removed pyproject.toml
|
||||
@@ -279,11 +276,11 @@ def add(
|
||||
|
||||
imports = [
|
||||
f"from {e['module']} import {e['attr']} as {name}"
|
||||
for e, name in zip(installed_exports, chain_names)
|
||||
for e, name in zip(installed_exports, chain_names, strict=False)
|
||||
]
|
||||
routes = [
|
||||
f'add_routes(app, {name}, path="{path}")'
|
||||
for name, path in zip(chain_names, api_paths)
|
||||
for name, path in zip(chain_names, api_paths, strict=False)
|
||||
]
|
||||
|
||||
t = (
|
||||
|
@@ -1,5 +1,6 @@
|
||||
"""Develop integration packages for LangChain."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -125,12 +126,28 @@ def new(
|
||||
# replacements in files
|
||||
replace_glob(destination_dir, "**/*", cast("dict[str, str]", replacements))
|
||||
|
||||
# poetry install
|
||||
subprocess.run(
|
||||
["poetry", "install", "--with", "lint,test,typing,test_integration"], # noqa: S607
|
||||
cwd=destination_dir,
|
||||
check=True,
|
||||
)
|
||||
# dependency install
|
||||
try:
|
||||
# Use --no-progress to avoid tty issues in CI/test environments
|
||||
env = os.environ.copy()
|
||||
env.pop("UV_FROZEN", None)
|
||||
env.pop("VIRTUAL_ENV", None)
|
||||
subprocess.run(
|
||||
["uv", "sync", "--dev", "--no-progress"], # noqa: S607
|
||||
cwd=destination_dir,
|
||||
check=True,
|
||||
env=env,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
typer.echo(
|
||||
"uv is not installed. Skipping dependency installation; run "
|
||||
"`uv sync --dev` manually if needed.",
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
typer.echo(
|
||||
"Failed to install dependencies. You may need to run "
|
||||
"`uv sync --dev` manually in the package directory.",
|
||||
)
|
||||
else:
|
||||
# confirm src and dst are the same length
|
||||
if not src:
|
||||
@@ -166,7 +183,7 @@ def new(
|
||||
typer.echo(f"File {dst_path} exists.")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
for src_path, dst_path in zip(src_paths, dst_paths):
|
||||
for src_path, dst_path in zip(src_paths, dst_paths, strict=False):
|
||||
shutil.copy(src_path, dst_path)
|
||||
replace_file(dst_path, cast("dict[str, str]", replacements))
|
||||
|
||||
|
@@ -1,8 +1,10 @@
|
||||
"""Events utilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import http.client
|
||||
import json
|
||||
from typing import Any, Optional, TypedDict
|
||||
from typing import Any, TypedDict
|
||||
|
||||
import typer
|
||||
|
||||
@@ -18,10 +20,10 @@ class EventDict(TypedDict):
|
||||
"""
|
||||
|
||||
event: str
|
||||
properties: Optional[dict[str, Any]]
|
||||
properties: dict[str, Any] | None
|
||||
|
||||
|
||||
def create_events(events: list[EventDict]) -> Optional[dict[str, Any]]:
|
||||
def create_events(events: list[EventDict]) -> dict[str, Any] | None:
|
||||
"""Create events.
|
||||
|
||||
Args:
|
||||
|
@@ -1,12 +1,14 @@
|
||||
"""Git utilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import re
|
||||
import shutil
|
||||
from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, TypedDict
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from git import Repo
|
||||
|
||||
@@ -23,18 +25,18 @@ class DependencySource(TypedDict):
|
||||
"""Dependency source information."""
|
||||
|
||||
git: str
|
||||
ref: Optional[str]
|
||||
subdirectory: Optional[str]
|
||||
api_path: Optional[str]
|
||||
ref: str | None
|
||||
subdirectory: str | None
|
||||
api_path: str | None
|
||||
event_metadata: dict[str, Any]
|
||||
|
||||
|
||||
# use poetry dependency string format
|
||||
def parse_dependency_string(
|
||||
dep: Optional[str],
|
||||
repo: Optional[str],
|
||||
branch: Optional[str],
|
||||
api_path: Optional[str],
|
||||
dep: str | None,
|
||||
repo: str | None,
|
||||
branch: str | None,
|
||||
api_path: str | None,
|
||||
) -> DependencySource:
|
||||
"""Parse a dependency string into a DependencySource.
|
||||
|
||||
@@ -125,7 +127,7 @@ def parse_dependency_string(
|
||||
)
|
||||
|
||||
|
||||
def _list_arg_to_length(arg: Optional[list[str]], num: int) -> Sequence[Optional[str]]:
|
||||
def _list_arg_to_length(arg: list[str] | None, num: int) -> Sequence[str | None]:
|
||||
if not arg:
|
||||
return [None] * num
|
||||
if len(arg) == 1:
|
||||
@@ -137,7 +139,7 @@ def _list_arg_to_length(arg: Optional[list[str]], num: int) -> Sequence[Optional
|
||||
|
||||
|
||||
def parse_dependencies(
|
||||
dependencies: Optional[list[str]],
|
||||
dependencies: list[str] | None,
|
||||
repo: list[str],
|
||||
branch: list[str],
|
||||
api_path: list[str],
|
||||
@@ -180,17 +182,18 @@ def parse_dependencies(
|
||||
inner_branches = _list_arg_to_length(branch, num_deps)
|
||||
|
||||
return list(
|
||||
map(
|
||||
map( # type: ignore[call-overload]
|
||||
parse_dependency_string,
|
||||
inner_deps,
|
||||
inner_repos,
|
||||
inner_branches,
|
||||
inner_api_paths,
|
||||
strict=False,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _get_repo_path(gitstring: str, ref: Optional[str], repo_dir: Path) -> Path:
|
||||
def _get_repo_path(gitstring: str, ref: str | None, repo_dir: Path) -> Path:
|
||||
# only based on git for now
|
||||
ref_str = ref if ref is not None else ""
|
||||
hashed = hashlib.sha256((f"{gitstring}:{ref_str}").encode()).hexdigest()[:8]
|
||||
@@ -204,7 +207,7 @@ def _get_repo_path(gitstring: str, ref: Optional[str], repo_dir: Path) -> Path:
|
||||
return repo_dir / directory_name
|
||||
|
||||
|
||||
def update_repo(gitstring: str, ref: Optional[str], repo_dir: Path) -> Path:
|
||||
def update_repo(gitstring: str, ref: str | None, repo_dir: Path) -> Path:
|
||||
"""Update a git repository to the specified ref.
|
||||
|
||||
Tries to pull if the repo already exists, otherwise clones it.
|
||||
|
@@ -1,11 +1,12 @@
|
||||
"""GitHub utilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import http.client
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def list_packages(*, contains: Optional[str] = None) -> list[str]:
|
||||
def list_packages(*, contains: str | None = None) -> list[str]:
|
||||
"""List all packages in the langchain repository templates directory.
|
||||
|
||||
Args:
|
||||
|
@@ -1,12 +1,14 @@
|
||||
"""Packages utilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, TypedDict
|
||||
from typing import Any, TypedDict, cast
|
||||
|
||||
from tomlkit import load
|
||||
|
||||
|
||||
def get_package_root(cwd: Optional[Path] = None) -> Path:
|
||||
def get_package_root(cwd: Path | None = None) -> Path:
|
||||
"""Get package root directory.
|
||||
|
||||
Args:
|
||||
@@ -62,11 +64,12 @@ def get_langserve_export(filepath: Path) -> LangServeExport:
|
||||
KeyError: If the `pyproject.toml` file is missing required fields.
|
||||
"""
|
||||
with filepath.open() as f:
|
||||
data: dict[str, Any] = load(f)
|
||||
# tomlkit types aren't amazing - treat as Dict instead
|
||||
data = cast("dict[str, Any]", load(f))
|
||||
try:
|
||||
module = data["tool"]["langserve"]["export_module"]
|
||||
attr = data["tool"]["langserve"]["export_attr"]
|
||||
package_name = data["tool"]["poetry"]["name"]
|
||||
module = str(data["tool"]["langserve"]["export_module"])
|
||||
attr = str(data["tool"]["langserve"]["export_attr"])
|
||||
package_name = str(data["tool"]["poetry"]["name"])
|
||||
except KeyError as e:
|
||||
msg = "Invalid LangServe PyProject.toml"
|
||||
raise KeyError(msg) from e
|
||||
|
@@ -5,9 +5,9 @@ build-backend = "pdm.backend"
|
||||
[project]
|
||||
authors = [{ name = "Erick Friis", email = "erick@langchain.dev" }]
|
||||
license = { text = "MIT" }
|
||||
requires-python = ">=3.9"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"typer<1.0.0,>=0.9.0",
|
||||
"typer<1.0.0,>=0.17",
|
||||
"gitpython<4,>=3",
|
||||
"langserve[all]>=0.0.51",
|
||||
"uvicorn<1.0,>=0.23",
|
||||
@@ -80,6 +80,12 @@ pyupgrade.keep-runtime-typing = true
|
||||
"tests/**" = [ "D1", "DOC", "S", "SLF",]
|
||||
"scripts/**" = [ "INP", "S",]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "--strict-markers --strict-config --durations=5"
|
||||
markers = [
|
||||
"compile: mark placeholder test used to compile integration tests without running them",
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
plugins = ["pydantic.mypy"]
|
||||
strict = true
|
||||
|
@@ -1,9 +1,10 @@
|
||||
"""Script to generate migrations for the migration script."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pkgutil
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
@@ -52,7 +53,7 @@ def cli() -> None:
|
||||
def generic(
|
||||
pkg1: str,
|
||||
pkg2: str,
|
||||
output: Optional[str],
|
||||
output: str | None,
|
||||
filter_by_all: bool, # noqa: FBT001
|
||||
format_: str,
|
||||
) -> None:
|
||||
@@ -76,7 +77,7 @@ def generic(
|
||||
Path(output).write_text(dumped, encoding="utf-8")
|
||||
|
||||
|
||||
def handle_partner(pkg: str, output: Optional[str] = None) -> None:
|
||||
def handle_partner(pkg: str, output: str | None = None) -> None:
|
||||
"""Handle partner package migrations."""
|
||||
migrations = get_migrations_for_partner_package(pkg)
|
||||
# Run with python 3.9+
|
||||
|
@@ -52,7 +52,7 @@ class Folder:
|
||||
if len(self.files) != len(__value.files):
|
||||
return False
|
||||
|
||||
for self_file, other_file in zip(self.files, __value.files):
|
||||
for self_file, other_file in zip(self.files, __value.files, strict=False):
|
||||
if self_file != other_file:
|
||||
return False
|
||||
|
||||
|
@@ -14,7 +14,7 @@ pytest.importorskip("gritql")
|
||||
|
||||
|
||||
def find_issue(current: Folder, expected: Folder) -> str:
|
||||
for current_file, expected_file in zip(current.files, expected.files):
|
||||
for current_file, expected_file in zip(current.files, expected.files, strict=False):
|
||||
if current_file != expected_file:
|
||||
if current_file.name != expected_file.name:
|
||||
return (
|
||||
|
@@ -1,4 +1,6 @@
|
||||
from typing import Any, Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -13,10 +15,10 @@ 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[str, Any]] = None,
|
||||
git: str | None = None,
|
||||
ref: str | None = None,
|
||||
subdirectory: str | None = None,
|
||||
event_metadata: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
if dep["git"] != git:
|
||||
msg = f"Expected git to be {git} but got {dep['git']}"
|
||||
|
1120
libs/cli/uv.lock
generated
1120
libs/cli/uv.lock
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user