diff --git a/pyproject.toml b/pyproject.toml index 661816f..5f7e73a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,8 @@ readme = "README.md" license = { text = "MIT" } requires-python = ">=3.10,<3.13" dependencies = [ - "langgraph>=1.2.14", - "langchain-core>=1.6.9", + "langgraph>=1.2.15", + "langchain-core>=1.6.11", "pandas>=2.3.3,<3.0", "pyyaml>=6.0.3", "quant-factors @ git+https://github.com/PureSaber/quant-factors.git@1c798bc737bc92fa5eca85dd0663dc608104f721", @@ -21,9 +21,9 @@ dependencies = [ ] [project.optional-dependencies] -llm = ["langchain-openai>=1.7.0"] +llm = ["langchain-openai>=1.7.2"] workspace = [] -dev = ["coverage>=7.16.2", "pytest>=9.1.1", "ruff>=0.16.10"] +dev = ["coverage>=7.16.2", "pytest>=9.1.1", "ruff>=0.17.1"] [project.scripts] quant-review = "quant_agent.cli:main" diff --git a/requirements.lock b/requirements.lock index 7ffe34a..e9bc249 100644 --- a/requirements.lock +++ b/requirements.lock @@ -71,7 +71,7 @@ jsonschema-rs==0.58.0 # via quant-data-kit jsonschema-specifications==2025.9.1 # via jsonschema -langchain-core==1.6.9 +langchain-core==1.6.11 # via # langchain-openai # langgraph @@ -79,15 +79,15 @@ langchain-core==1.6.9 # langgraph-prebuilt # langgraph-sdk # quant-agent (pyproject.toml) -langchain-openai==1.7.0 +langchain-openai==1.7.2 # via quant-agent (pyproject.toml) langchain-protocol==0.0.19 # via # langchain-core # langgraph-sdk -langgraph==1.2.14 +langgraph==1.2.15 # via quant-agent (pyproject.toml) -langgraph-checkpoint==4.2.0 +langgraph-checkpoint==4.3.0 # via # langgraph # langgraph-prebuilt @@ -95,7 +95,7 @@ langgraph-prebuilt==1.1.0 # via langgraph langgraph-sdk==0.4.6 # via langgraph -langsmith==0.11.2 +langsmith==0.14.12 # via langchain-core numpy==2.2.6 # via @@ -182,7 +182,7 @@ rpds-py==0.30.0 # via # jsonschema # referencing -ruff==0.16.10 +ruff==0.17.1 # via quant-agent (pyproject.toml) six==1.17.0 # via python-dateutil diff --git a/tests/test_declared_dependency_lock.py b/tests/test_declared_dependency_lock.py new file mode 100644 index 0000000..9e37e7e --- /dev/null +++ b/tests/test_declared_dependency_lock.py @@ -0,0 +1,56 @@ +"""The environment used by CI must satisfy declared runtime and development requirements.""" + +from pathlib import Path + +try: + import tomllib +except ModuleNotFoundError: + import tomli as tomllib + +from packaging.markers import default_environment +from packaging.requirements import Requirement +from packaging.utils import canonicalize_name + +ROOT = Path(__file__).resolve().parents[1] +LOCKS = ["requirements.lock"] +EXTRAS = ["dev", "llm"] + + +def test_locked_environment_satisfies_declared_requirements(): + metadata = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8")) + declared = list(metadata["project"].get("dependencies", [])) + declared.extend(metadata["build-system"]["requires"]) + for extra in EXTRAS: + declared.extend(metadata["project"]["optional-dependencies"][extra]) + for filename in LOCKS: + locked = [ + Requirement(line) + for line in (ROOT / filename).read_text(encoding="utf-8").splitlines() + if line and not line.startswith(("#", " ", "-")) + ] + for version in ("3.10", "3.11", "3.12"): + for platform in ("win32", "linux"): + environment = { + **default_environment(), + "python_version": version, + "python_full_version": version + ".0", + "sys_platform": platform, + } + active = { + canonicalize_name(item.name): item + for item in locked + if item.marker is None or item.marker.evaluate(environment) + } + for text in declared: + requirement = Requirement(text) + if requirement.url or ( + requirement.marker and not requirement.marker.evaluate(environment) + ): + continue + name = canonicalize_name(requirement.name) + assert name in active, f"{filename}: missing {name} on {version}/{platform}" + pins = [pin.version for pin in active[name].specifier if pin.operator == "=="] + assert len(pins) == 1, f"{filename}: {name} must have one exact version" + assert requirement.specifier.contains(pins[0]), ( + f"{filename}: {name}=={pins[0]} does not satisfy {requirement.specifier}" + )