[build-system] requires = ["setuptools>=68", "wheel"] build-backend = "setuptools.build_meta" [project] name = "chainweaver" dynamic = ["version"] description = "Analyze repeated AI-agent tool paths and turn reviewed candidates into governed deterministic capabilities." readme = "README.md" license = { file = "LICENSE" } requires-python = ">=3.10" keywords = [ "mcp", "agents", "orchestration", "deterministic", "workflow", "llm", "tools", "pydantic", "flow", "weaver-stack", ] classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed", ] # Dependency-constraint policy (issue #236): library-grade specifiers — lower # bounds only (``>=``), set to the lowest version the test suite actually passes # on, and no speculative upper-bound caps. The ``floor-deps`` CI job installs # these minimums (``uv pip install --resolution lowest-direct``) and runs the # full suite, so every floor below is proven, not guessed. See CONTRIBUTING.md. dependencies = [ "deepdiff>=9.0", "packaging>=21.0", "pydantic>=2.11", "tenacity>=8.0", "typer>=0.24", ] [project.scripts] chainweaver = "chainweaver.cli:main" [project.entry-points.pytest11] chainweaver = "pytest_chainweaver" [project.optional-dependencies] yaml = [ "pyyaml>=6.0", ] otel = [ "opentelemetry-api>=1.20", ] contrib = [ # The six contrib tools shipped today have no extra runtime deps; # this extra is reserved so future additions needing lightweight # extras can be added without breaking the base install. ] langchain = [ # ``langchain-core>=0.3`` uses Pydantic v2, matching our base dep. # Earlier 0.1/0.2 releases pulled in Pydantic v1 and would conflict. "langchain-core>=0.3", ] llamaindex = [ "llama-index-core>=0.10", ] langgraph = [ # LangGraph node recipe (issue #205). Builds on ``langchain-core`` (our # existing Pydantic-v2-compatible dep); the recipe itself runs offline. "langgraph>=0.2", ] openai-agents = [ # OpenAI Agents SDK tool recipe (issue #206). The recipe wraps a flow as a # ``FunctionTool`` and validates it in a dry-run — no API key required. "openai-agents>=0.1", ] llm-anthropic = [ # Optional Anthropic adapter for the offline proposers (issue #368). The base # package never imports this SDK; only chainweaver.integrations.llm_anthropic does. "anthropic>=0.40", ] llm-openai = [ # Optional OpenAI / OpenAI-compatible adapter for the offline proposers # (issue #368). Also covers local runtimes via ``base_url``. "openai>=1.40", ] mcp = [ # ``FlowServer`` (outbound) runs on the standalone ``fastmcp`` package # (issue #243); the inbound ``MCPToolAdapter`` still imports # ``mcp.ClientSession`` from the official SDK, so both are required. "mcp>=1.0", "fastmcp>=3.4", ] weaver-stack = [ # Real Weaver Stack interop (issue #233). ``weaver-contracts`` is the # published distribution of the weaver-spec shared contract consumed by # chainweaver.integrations.weaver_spec / contextweaver / agent_kernel # (issues #91, #106, #89, #107). Pinned to the 0.x line — the contract # promises no breaking changes within a major version. "weaver-contracts>=0.6,<1.0", ] # Property-based testing extra (issue #143). Pulls the Hypothesis stack the # property suite needs — including hypothesis-jsonschema, which main's suite # uses to derive strategies from the Pydantic JSON schemas. Enable via # ``pip install 'chainweaver[test]'``. test = [ "hypothesis>=6.150", "hypothesis-jsonschema>=0.23", ] docs = [ "mkdocs>=1.6", "mkdocs-material>=9.5", "mkdocstrings[python]>=0.26", "pymdown-extensions>=10.7", ] # Every user-facing integration extra at once (issue #550). CI installs this # alongside the ``dev`` dependency group, so the composition CI exercises is # named once here rather than repeated in each workflow, CONTRIBUTING and the # floor job — three hand-maintained copies of a list drift, and this one had # already grown a fourth as the old ``dev`` extra. Deliberately excludes # ``llm-anthropic`` / ``llm-openai``: those pull real provider SDKs and the # evals workflow adds the one it needs explicitly. integrations = [ "chainweaver[yaml,otel,contrib,langchain,llamaindex,langgraph,openai-agents,mcp,weaver-stack,test]", ] [project.urls] "Homepage" = "https://github.com/dgenio/ChainWeaver" "Documentation" = "https://chainweaver.readthedocs.io/" "Source" = "https://github.com/dgenio/ChainWeaver" "Changelog" = "https://github.com/dgenio/ChainWeaver/blob/main/CHANGELOG.md" "Issues" = "https://github.com/dgenio/ChainWeaver/issues" "Bug Tracker" = "https://github.com/dgenio/ChainWeaver/issues" # --------------------------------------------------------------------------- # Maintainer-only tooling (PEP 735 dependency groups, issue #550). # # Nothing here is published in the wheel's metadata: a downstream consumer of # ChainWeaver never needs a linter, a type checker or a notebook kernel, and # advertising them as an extra invited ``pip install chainweaver[dev]`` in # environments that only wanted the library. User-facing capability stays in # ``[project.optional-dependencies]`` above. # # Install with ``pip install -e ".[integrations]" --group dev`` (pip >= 25.1) # or ``uv pip install -e ".[integrations]" --group dev``. # --------------------------------------------------------------------------- [dependency-groups] # The pytest runners alone. The free-threaded 3.14t lane installs exactly # these (see the comment on that job in ``.github/workflows/ci.yml``); before # issue #550 it hand-copied the list into the workflow, and that copy had # already drifted — it never gained ``pytest-timeout`` when issue #543 added # it, so the lane most likely to hang was the one lane running unbounded. test-runners = [ "pytest>=7.0", "pytest-cov>=4.0", "pytest-asyncio>=0.23", # Per-test hang ceiling (issue #543): a blocked Event.wait() must fail # attributably instead of burning the runner's global timeout. "pytest-timeout>=2.3", ] dev = [ { include-group = "test-runners" }, "ruff>=0.8", "mypy>=1.0", "types-pyyaml>=6.0", "griffe>=2.0", # Notebook execution gate (issue #229): nbmake runs notebooks/ in CI so the # "Open in Colab" quickstart cannot silently rot; ipykernel is the kernel it # executes against. "nbmake>=1.5", "ipykernel>=6.29", # The library depends on ``opentelemetry-api`` only (the ``otel`` extra). # The SDK is how the tests *observe* the spans it emits, so it is # maintainer tooling, not a consumer dependency. "opentelemetry-sdk>=1.20", ] [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] python_classes = ["Test*"] python_functions = ["test_*"] # Bare-name imports inside tests/ (e.g. `from helpers import ...`, # `from strategies import ...`) resolve because pytest prepends these # directories to `sys.path` before collection. pythonpath = ["tests", "tests/property", "."] addopts = "--cov=chainweaver --cov-report=term-missing --cov-fail-under=80" asyncio_mode = "auto" markers = [ "conformance: weaver-spec conformance gate (issue #91); run with -m conformance", "property: Hypothesis property-based determinism test (also run via `pytest -m property`)", ] # Per-test ceiling (issue #543). A give-up bound for a hang, NOT a performance # budget: #341 moved the concurrency tests from sleeps to events, so a signal # that never fires no longer trips a timing assertion -- it burns the runner's # global timeout and reports nothing useful. This turns that into a named # failure with a traceback. # # Choosing 60s. The relationship worth keeping true: # * slowest healthy test, measured at multiplier 1: 4.34s # * tests/helpers.py BARRIER_TIMEOUT_S give-up bound: 5.00s # -- fixed, and deliberately NOT scaled by the multiplier # * scaled() offsets are sub-second, so even # CHAINWEAVER_TEST_TIMING_MULTIPLIER=10 adds only single-digit seconds # 60s therefore leaves >12x headroom over the slowest healthy test and clears # BARRIER_TIMEOUT_S by an order of magnitude, so it cannot become a flake # source itself. Raise it here if a genuinely slow lane ever approaches it -- # do not scatter per-test overrides, and do not lower it to police speed. timeout = 60 # thread, not signal: a test blocked in a C-level call is not interruptible by # SIGALRM, and those are exactly the hangs this is meant to catch. timeout_method = "thread" [tool.ruff] target-version = "py310" line-length = 99 [tool.ruff.lint] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # pyflakes "I", # isort "UP", # pyupgrade "B", # flake8-bugbear "SIM", # flake8-simplify "RUF", # Ruff-specific rules ] [tool.ruff.lint.isort] known-first-party = ["chainweaver"] [tool.ruff.format] quote-style = "double" skip-magic-trailing-comma = false [tool.mypy] python_version = "3.10" strict = true [[tool.mypy.overrides]] module = "tests.*" disallow_untyped_defs = false [[tool.mypy.overrides]] # weaver-contracts (issue #233) ships no py.typed marker yet; treat it as # untyped rather than failing the type check on the optional integration. # Cover both the top-level package and its submodules. module = ["weaver_contracts", "weaver_contracts.*"] ignore_missing_imports = true [[tool.mypy.overrides]] # Optional provider SDKs for the llm-anthropic / llm-openai adapters (issue #368). # They are not part of the base or .[integrations] install, so the type check # must not fail when they are absent; the adapters import them lazily behind a # guard. module = ["anthropic", "anthropic.*", "openai", "openai.*"] ignore_missing_imports = true [tool.setuptools.packages.find] where = ["."] include = ["chainweaver*"] # The pytest plugin (``pytest_chainweaver``) lives at the repo root so # that pytest's ``pytest11`` entry-point loader does not transitively # import ``chainweaver`` before ``pytest-cov`` can start coverage # tracking. Without this, every import-time statement in the # ChainWeaver package would be counted as "missed" and the project- # wide coverage percentage would collapse from ~94 % to ~64 %. See # the module docstring of ``pytest_chainweaver.py`` for details. [tool.setuptools] py-modules = ["pytest_chainweaver"] [tool.setuptools.dynamic] version = { attr = "chainweaver.__version__" }