mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
build: promote anthropic, postgres, console, tls to core dependencies
The Anthropic SDK provider was the lone first-class provider gated behind an optional extra, while OpenAI ships in core and Google rides the OpenAI-compatible path. Fold anthropic, psycopg (postgres), croniter (console), and lacme (tls) into the base dependency set so a default `pip install turnstone` yields a complete single- or multi-node deployment; only the Discord/Slack channel gateways stay optional. - pyproject: four extras → base deps; `all` is now discord+slack; drop the redundant croniter from the `test` extra; regenerate uv.lock. - ci: the postgres test job installs `.[test]` (psycopg is base now). - providers: `_ensure_anthropic` becomes a thin SDK accessor for `create_client`; drop the now-redundant eager import-guard calls from the streaming/completion hot path (anthropic is always present). - bootstrap: import anthropic directly. - tests/docs: drop the anthropic importorskips and stale extra-install hints.
This commit is contained in:
@@ -82,7 +82,7 @@ jobs:
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version: "24"
|
||||
- run: pip install -e ".[test,postgres]"
|
||||
- run: pip install -e ".[test]"
|
||||
- run: pytest tests/ -m "not live" --storage-backend=postgresql -q
|
||||
env:
|
||||
TURNSTONE_TEST_PG_URL: postgresql+psycopg://postgres:postgres@localhost:5432/turnstone_test
|
||||
|
||||
@@ -51,14 +51,12 @@ turnstone --base-url http://localhost:8000/v1
|
||||
turnstone-server --port 8080 --base-url http://localhost:8000/v1
|
||||
|
||||
# Cluster dashboard
|
||||
pip install turnstone[console]
|
||||
turnstone-console --port 8090
|
||||
```
|
||||
|
||||
For PostgreSQL (recommended for production):
|
||||
|
||||
```bash
|
||||
pip install turnstone[postgres]
|
||||
export TURNSTONE_DB_BACKEND=postgresql
|
||||
export TURNSTONE_DB_URL="postgresql+psycopg://user:pass@localhost:5432/turnstone"
|
||||
turnstone-server --port 8080 --base-url http://localhost:8000/v1
|
||||
@@ -161,7 +159,7 @@ UML diagrams in [`docs/diagrams/`](docs/diagrams/):
|
||||
|
||||
- Python 3.11+
|
||||
- An OpenAI-compatible API endpoint, Anthropic API key, or Google Gemini API key
|
||||
- Optional: PostgreSQL (`pip install turnstone[postgres]`), Anthropic (`pip install turnstone[anthropic]`)
|
||||
- Optional: Discord / Slack channel integrations (`pip install turnstone[discord,slack]`)
|
||||
- [Git LFS](https://git-lfs.com/) for cloning (diagram PNGs)
|
||||
|
||||
## Community
|
||||
|
||||
@@ -652,9 +652,8 @@ display). Automatic prompt caching is enabled via top-level `cache_control:
|
||||
cacheable block and advances it as conversations grow (90% input cost
|
||||
reduction on cache hits, 1.25x write on first turn). Cache metrics
|
||||
(`cache_creation_input_tokens`, `cache_read_input_tokens`) are extracted from
|
||||
both streaming and non-streaming responses. The `anthropic` SDK is imported
|
||||
lazily so it remains an optional dependency (`pip install
|
||||
turnstone[anthropic]`).
|
||||
both streaming and non-streaming responses. The `anthropic` SDK is a core
|
||||
dependency — the Anthropic provider is first-class alongside OpenAI.
|
||||
|
||||
**GoogleProvider** (`_google.py`): extends `OpenAIChatCompletionsProvider` for
|
||||
the Gemini `/v1beta/openai/` endpoint. Uses a single default
|
||||
|
||||
+6
-6
@@ -23,6 +23,7 @@ classifiers = [
|
||||
]
|
||||
dependencies = [
|
||||
"openai>=2.37",
|
||||
"anthropic>=0.39",
|
||||
"httpx>=0.28",
|
||||
"mcp>=1.27",
|
||||
"starlette>=1.0.1", # PYSEC-2026-161: host-header path-injection in URL reconstruction (auth-bypass on apps comparing reconstructed URL paths)
|
||||
@@ -32,10 +33,13 @@ dependencies = [
|
||||
"pydantic>=2.0",
|
||||
"sqlalchemy>=2.0",
|
||||
"alembic>=1.14",
|
||||
"psycopg[binary]>=3.2",
|
||||
"croniter>=3.0",
|
||||
"structlog>=24.1",
|
||||
"PyJWT>=2.8",
|
||||
"bcrypt>=4.0",
|
||||
"cryptography>=42",
|
||||
"lacme>=1.0.5",
|
||||
"python-frontmatter>=1.0",
|
||||
]
|
||||
|
||||
@@ -45,15 +49,11 @@ Repository = "https://github.com/turnstonelabs/turnstone"
|
||||
Issues = "https://github.com/turnstonelabs/turnstone/issues"
|
||||
|
||||
[project.optional-dependencies]
|
||||
test = ["pytest>=9.0", "pytest-cov>=6.0", "croniter>=3.0", "slack-bolt>=1.18", "aiohttp>=3.9"]
|
||||
test = ["pytest>=9.0", "pytest-cov>=6.0", "slack-bolt>=1.18", "aiohttp>=3.9"]
|
||||
dev = ["ruff>=0.9", "mypy>=1.14"]
|
||||
console = ["croniter>=3.0"]
|
||||
anthropic = ["anthropic>=0.39"]
|
||||
postgres = ["psycopg[binary]>=3.2"]
|
||||
discord = ["discord.py>=2.4"]
|
||||
tls = ["lacme>=1.0.5"]
|
||||
slack = ["slack-bolt>=1.18", "aiohttp>=3.9"]
|
||||
all = ["turnstone[console,anthropic,postgres,discord,tls,slack]"]
|
||||
all = ["turnstone[discord,slack]"]
|
||||
|
||||
[project.scripts]
|
||||
turnstone = "turnstone.cli:main"
|
||||
|
||||
@@ -26,8 +26,6 @@ from types import SimpleNamespace
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from tests._session_helpers import make_session as _make_session
|
||||
|
||||
|
||||
@@ -300,7 +298,6 @@ class TestSessionToWireBoundaryIntegration:
|
||||
the resolver pre-set to *replay_flag*. Returns the kwargs
|
||||
dict that reached the (mocked) Anthropic SDK boundary.
|
||||
"""
|
||||
pytest.importorskip("anthropic")
|
||||
from turnstone.core.providers._anthropic import AnthropicProvider
|
||||
|
||||
session = _make_session()
|
||||
@@ -322,7 +319,7 @@ class TestSessionToWireBoundaryIntegration:
|
||||
model_alias="claude-opus-4-7",
|
||||
)
|
||||
# Iterate the stream to drain the (empty) generator and ensure
|
||||
# _ensure_anthropic / convert / build_kwargs all ran.
|
||||
# convert / build_kwargs all ran.
|
||||
list(stream)
|
||||
return captured
|
||||
|
||||
@@ -386,7 +383,6 @@ class TestSessionToWireBoundaryIntegration:
|
||||
# supports_reasoning_replay=False. AND-gate at the resolver
|
||||
# blocks replay, so the strip predicate fires at the wire and
|
||||
# the thinking block does NOT reach the SDK boundary.
|
||||
pytest.importorskip("anthropic")
|
||||
from turnstone.core.providers._anthropic import AnthropicProvider
|
||||
from turnstone.core.providers._protocol import ModelCapabilities
|
||||
|
||||
|
||||
@@ -976,12 +976,7 @@ def _setup_openai() -> tuple[str, Any, str]:
|
||||
|
||||
|
||||
def _setup_anthropic() -> tuple[str, Any, str]:
|
||||
try:
|
||||
import anthropic
|
||||
except ImportError:
|
||||
print(f"\n {RED}The 'anthropic' package is not installed.{RESET}")
|
||||
print(f" Install it with: {CYAN}pip install turnstone[anthropic]{RESET}")
|
||||
sys.exit(1)
|
||||
import anthropic
|
||||
|
||||
api_key = _prompt_api_key("ANTHROPIC_API_KEY", "Enter your Anthropic API key:")
|
||||
model = _prompt_model("anthropic")
|
||||
|
||||
@@ -29,16 +29,14 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _ensure_anthropic() -> Any:
|
||||
"""Lazy import anthropic SDK, raising helpful error if not installed."""
|
||||
try:
|
||||
import anthropic # noqa: PLC0415
|
||||
"""Return the ``anthropic`` SDK module (a core dependency).
|
||||
|
||||
return anthropic
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"The 'anthropic' package is required for Anthropic provider. "
|
||||
"Install it with: pip install 'turnstone[anthropic]'"
|
||||
) from None
|
||||
Kept as a thin accessor so ``create_client`` (and tests) can obtain the
|
||||
SDK via a single seam without a module-top import.
|
||||
"""
|
||||
import anthropic # noqa: PLC0415
|
||||
|
||||
return anthropic
|
||||
|
||||
|
||||
# -- message format helpers --------------------------------------------------
|
||||
@@ -700,7 +698,6 @@ class AnthropicProvider:
|
||||
resolve_attachments: Callable[[list[str]], dict[str, dict[str, Any]]] | None = None,
|
||||
) -> Iterator[StreamChunk]:
|
||||
messages = materialize_attachments(messages, resolve_attachments)
|
||||
_ensure_anthropic()
|
||||
caps = capabilities or self.get_capabilities(model)
|
||||
system_prompt, converted_msgs = self._convert_messages(
|
||||
messages,
|
||||
@@ -914,7 +911,6 @@ class AnthropicProvider:
|
||||
resolve_attachments: Callable[[list[str]], dict[str, dict[str, Any]]] | None = None,
|
||||
) -> CompletionResult:
|
||||
messages = materialize_attachments(messages, resolve_attachments)
|
||||
_ensure_anthropic()
|
||||
caps = capabilities or self.get_capabilities(model)
|
||||
system_prompt, converted_msgs = self._convert_messages(
|
||||
messages,
|
||||
|
||||
@@ -2311,12 +2311,16 @@ version = "1.6.0a10"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "alembic" },
|
||||
{ name = "anthropic" },
|
||||
{ name = "bcrypt" },
|
||||
{ name = "croniter" },
|
||||
{ name = "cryptography" },
|
||||
{ name = "httpx" },
|
||||
{ name = "httpx-sse" },
|
||||
{ name = "lacme" },
|
||||
{ name = "mcp" },
|
||||
{ name = "openai" },
|
||||
{ name = "psycopg", extra = ["binary"] },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyjwt" },
|
||||
{ name = "python-frontmatter" },
|
||||
@@ -2330,19 +2334,9 @@ dependencies = [
|
||||
[package.optional-dependencies]
|
||||
all = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "anthropic" },
|
||||
{ name = "croniter" },
|
||||
{ name = "discord-py" },
|
||||
{ name = "lacme" },
|
||||
{ name = "psycopg", extra = ["binary"] },
|
||||
{ name = "slack-bolt" },
|
||||
]
|
||||
anthropic = [
|
||||
{ name = "anthropic" },
|
||||
]
|
||||
console = [
|
||||
{ name = "croniter" },
|
||||
]
|
||||
dev = [
|
||||
{ name = "mypy" },
|
||||
{ name = "ruff" },
|
||||
@@ -2350,42 +2344,34 @@ dev = [
|
||||
discord = [
|
||||
{ name = "discord-py" },
|
||||
]
|
||||
postgres = [
|
||||
{ name = "psycopg", extra = ["binary"] },
|
||||
]
|
||||
slack = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "slack-bolt" },
|
||||
]
|
||||
test = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "croniter" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "slack-bolt" },
|
||||
]
|
||||
tls = [
|
||||
{ name = "lacme" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "aiohttp", marker = "extra == 'slack'", specifier = ">=3.9" },
|
||||
{ name = "aiohttp", marker = "extra == 'test'", specifier = ">=3.9" },
|
||||
{ name = "alembic", specifier = ">=1.14" },
|
||||
{ name = "anthropic", marker = "extra == 'anthropic'", specifier = ">=0.39" },
|
||||
{ name = "anthropic", specifier = ">=0.39" },
|
||||
{ name = "bcrypt", specifier = ">=4.0" },
|
||||
{ name = "croniter", marker = "extra == 'console'", specifier = ">=3.0" },
|
||||
{ name = "croniter", marker = "extra == 'test'", specifier = ">=3.0" },
|
||||
{ name = "croniter", specifier = ">=3.0" },
|
||||
{ name = "cryptography", specifier = ">=42" },
|
||||
{ name = "discord-py", marker = "extra == 'discord'", specifier = ">=2.4" },
|
||||
{ name = "httpx", specifier = ">=0.28" },
|
||||
{ name = "httpx-sse", specifier = ">=0.4" },
|
||||
{ name = "lacme", marker = "extra == 'tls'", specifier = ">=1.0.5" },
|
||||
{ name = "lacme", specifier = ">=1.0.5" },
|
||||
{ name = "mcp", specifier = ">=1.27" },
|
||||
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.14" },
|
||||
{ name = "openai", specifier = ">=2.37" },
|
||||
{ name = "psycopg", extras = ["binary"], marker = "extra == 'postgres'", specifier = ">=3.2" },
|
||||
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2" },
|
||||
{ name = "pydantic", specifier = ">=2.0" },
|
||||
{ name = "pyjwt", specifier = ">=2.8" },
|
||||
{ name = "pytest", marker = "extra == 'test'", specifier = ">=9.0" },
|
||||
@@ -2398,10 +2384,10 @@ requires-dist = [
|
||||
{ name = "sse-starlette", specifier = ">=2.0" },
|
||||
{ name = "starlette", specifier = ">=1.0.1" },
|
||||
{ name = "structlog", specifier = ">=24.1" },
|
||||
{ name = "turnstone", extras = ["console", "anthropic", "postgres", "discord", "tls", "slack"], marker = "extra == 'all'" },
|
||||
{ name = "turnstone", extras = ["discord", "slack"], marker = "extra == 'all'" },
|
||||
{ name = "uvicorn", specifier = ">=0.34" },
|
||||
]
|
||||
provides-extras = ["test", "dev", "console", "anthropic", "postgres", "discord", "tls", "slack", "all"]
|
||||
provides-extras = ["test", "dev", "discord", "slack", "all"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
|
||||
Reference in New Issue
Block a user