mirror of
https://github.com/pydantic/pydantic-ai.git
synced 2026-10-11 21:59:51 +00:00
Support Python 3.15 (#10064)
Co-authored-by: Marcelo Trylesinski <[email protected]>
This commit is contained in:
co-authored by
Marcelo Trylesinski
parent
86d8f65865
commit
4c6fc3ce0a
@@ -28,7 +28,8 @@ MIN_BASELINE_SAMPLES = 10
|
||||
# Must stay above the number of tracked jobs so a full run renders every row: `no_baseline` sorts
|
||||
# into the truncated tail with `normal`, and every newly-minted signature starts there. Today's
|
||||
# `ci.yml` matrices give 4 pythons x (5 installs + 2 all-extras shards + 2 lowest-versions
|
||||
# shards) + 4 x 2 durable-exec + 3.11's third all-extras shard = 45.
|
||||
# shards) + 4 x 2 durable-exec + 3.11's third all-extras shard + 3.15's 5 installs, 2 all-extras
|
||||
# shards, and locked durable-exec leg = 53.
|
||||
REPORT_ROW_LIMIT = 60
|
||||
WARNING_MIN_SECONDS = 60
|
||||
SLOW_THRESHOLD_MULTIPLIER = 1.25
|
||||
|
||||
@@ -21,7 +21,11 @@ pydantic-core = false
|
||||
pydantic-ai-harness = false
|
||||
logfire-api = false
|
||||
cassetter = false
|
||||
aiohttp = "2026-10-05T17:43:49Z"
|
||||
listentome = false
|
||||
asyncpg = "2026-10-06T20:32:41Z"
|
||||
fastavro = "2026-10-05T22:24:00Z"
|
||||
rpds-py = "2026-10-04T16:32:38Z"
|
||||
logfire = false
|
||||
pydantic-settings = false
|
||||
pydantic-monty-client = false
|
||||
@@ -1136,6 +1140,7 @@ requires-dist = [
|
||||
{ name = "absurd-sdk", marker = "extra == 'absurd'", specifier = ">=0.5.0,<1" },
|
||||
{ name = "agent-client-protocol", marker = "extra == 'acp'", specifier = ">=0.11,<0.12" },
|
||||
{ name = "aws-durable-execution-sdk-python", marker = "python_full_version >= '3.11' and extra == 'aws-lambda'", specifier = ">=1.7.0,<2" },
|
||||
{ name = "beartype", marker = "python_full_version >= '3.15' and extra == 'stackone'", specifier = ">=0.23.0rc2" },
|
||||
{ name = "browser-use", marker = "python_full_version >= '3.11' and extra == 'browser-use'", specifier = ">=0.13.6,<0.14" },
|
||||
{ name = "e2b", marker = "extra == 'e2b'", specifier = ">=2.48.0" },
|
||||
{ name = "exa-py", marker = "extra == 'exa'", specifier = ">=2.16.0" },
|
||||
@@ -1177,7 +1182,8 @@ requires-dist = [
|
||||
{ name = "ripgrep", marker = "sys_platform != 'android' and extra == 'coder'", specifier = "==14.1.0" },
|
||||
{ name = "sprites-py", marker = "extra == 'sprites'", specifier = ">=0.7.0,<0.8" },
|
||||
{ name = "stackone-defender", marker = "python_full_version >= '3.11' and extra == 'prompt-injection-defender'", specifier = ">=0.7.3,<0.8" },
|
||||
{ name = "stackone-defender", extras = ["onnx"], marker = "python_full_version >= '3.11' and extra == 'prompt-injection-defender-ml'", specifier = ">=0.7.3,<0.8" },
|
||||
{ name = "stackone-defender", marker = "python_full_version >= '3.11' and extra == 'prompt-injection-defender-ml'", specifier = ">=0.7.3,<0.8" },
|
||||
{ name = "stackone-defender", extras = ["onnx"], marker = "python_full_version >= '3.11' and python_full_version < '3.15' and extra == 'prompt-injection-defender-ml'", specifier = ">=0.7.3,<0.8" },
|
||||
{ name = "youdotcom", marker = "extra == 'youdotcom'", specifier = ">=3.1.2,<3.2" },
|
||||
]
|
||||
provides-extras = ["absurd", "acp", "anthropic", "aws-lambda", "browser-use", "cli", "code-mode", "codemode", "coder", "day-ai", "dbos", "dynamic-workflow", "e2b", "exa", "github", "google-workspace", "grain", "linear", "logfire", "logfire-mcp", "modal", "mongodb", "notion", "ordinal", "playwright", "posthog", "prompt-injection-defender", "prompt-injection-defender-ml", "pylon", "researcher", "skills", "slack", "sprites", "stackone", "temporal", "youdotcom"]
|
||||
|
||||
@@ -505,7 +505,7 @@ jobs:
|
||||
# `REPORT_ROW_LIMIT` in `.github/scripts/ci_duration.py` must stay above, or the duration
|
||||
# report truncates. That constant's comment enumerates all four.
|
||||
# `all-extras` is the slowest install shape -- it runs sharded in `test-all-extras` instead.
|
||||
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
||||
python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]
|
||||
install:
|
||||
- name: pydantic-ai-slim
|
||||
command: "--package pydantic-ai-slim"
|
||||
@@ -656,13 +656,17 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
mkdir test-durations
|
||||
for python in 3.11 3.12 3.13 3.14; do
|
||||
for python in 3.11 3.12 3.13 3.14 3.15; do
|
||||
all_extras_shards=2
|
||||
if [[ "$python" == 3.11 ]]; then all_extras_shards=3; fi
|
||||
python3 .github/scripts/shard_durations.py fetch --prefix "test-durations-$python-all-extras-" \
|
||||
--shards "$all_extras_shards" --output "test-durations/$python-all-extras.json"
|
||||
python3 .github/scripts/shard_durations.py fetch --prefix "test-durations-$python-lowest-versions-" \
|
||||
--shards 2 --output "test-durations/$python-lowest-versions.json"
|
||||
# No 3.15 lowest-versions shards exist to fetch: like `test-durable-exec`'s `lowest` leg, they
|
||||
# would compile our declared minimums from source on 3.15.
|
||||
if [[ "$python" != 3.15 ]]; then
|
||||
python3 .github/scripts/shard_durations.py fetch --prefix "test-durations-$python-lowest-versions-" \
|
||||
--shards 2 --output "test-durations/$python-lowest-versions.json"
|
||||
fi
|
||||
done
|
||||
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
@@ -684,7 +688,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
||||
python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]
|
||||
shard: [1, 2]
|
||||
include:
|
||||
- python-version: "3.11"
|
||||
@@ -746,9 +750,12 @@ jobs:
|
||||
# Cache only the immutable model used by `TestSentenceTransformers`. The old
|
||||
# shared HF cache could be populated first by a matrix job that never downloaded
|
||||
# this model, permanently turning an exact cache hit into a cold model cache.
|
||||
# The `sentence-transformers` extra only installs below Python 3.14 (its marker in
|
||||
# `pydantic_ai_slim/pyproject.toml`), so 3.14 and 3.15 skip these three steps: there,
|
||||
# the download would fail its import and spend its retries before giving up.
|
||||
- name: restore sentence-transformers test model
|
||||
id: sentence-transformers-cache
|
||||
if: matrix.python-version != '3.14'
|
||||
if: matrix.python-version != '3.14' && matrix.python-version != '3.15'
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.cache/huggingface/hub/models--sentence-transformers-testing--stsb-bert-tiny-safetensors
|
||||
@@ -767,7 +774,7 @@ jobs:
|
||||
- name: pre-download sentence-transformers test model
|
||||
id: sentence-transformers-download
|
||||
if: >-
|
||||
matrix.python-version != '3.14'
|
||||
matrix.python-version != '3.14' && matrix.python-version != '3.15'
|
||||
&& steps.sentence-transformers-cache.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
@@ -784,7 +791,7 @@ jobs:
|
||||
# A failed non-fatal download must not become an immutable exact cache hit.
|
||||
- name: save sentence-transformers test model
|
||||
if: >-
|
||||
matrix.python-version != '3.14'
|
||||
matrix.python-version != '3.14' && matrix.python-version != '3.15'
|
||||
&& steps.sentence-transformers-cache.outputs.cache-hit != 'true'
|
||||
&& steps.sentence-transformers-download.outcome == 'success'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
@@ -895,6 +902,12 @@ jobs:
|
||||
matrix:
|
||||
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
||||
resolution: [locked, lowest]
|
||||
# 3.15 runs the `locked` leg only: `lowest` installs our declared minimums, and 13 of them
|
||||
# have no 3.15 wheel (pydantic-core 2.41, numpy 2.3, tiktoken 0.12, ...), so the leg would
|
||||
# compile them from source past this job's timeout.
|
||||
include:
|
||||
- python-version: "3.15"
|
||||
resolution: locked
|
||||
env:
|
||||
CI: true
|
||||
# Python 3.11 and 3.14 cover version endpoints; both 3.12 resolutions cover Temporal dependency branches.
|
||||
@@ -994,6 +1007,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# No 3.15: our declared minimums (pydantic-core 2.41, numpy 2.3, tiktoken 0.12, ...) have no 3.15 wheels.
|
||||
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
||||
shard: [1, 2]
|
||||
env:
|
||||
@@ -1150,6 +1164,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# No 3.15: the examples need duckdb and datasets, which are skipped there (see `examples/pyproject.toml`).
|
||||
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
||||
env:
|
||||
CI: true
|
||||
|
||||
@@ -65,7 +65,7 @@ concurrency:
|
||||
env:
|
||||
COLUMNS: 150
|
||||
# `ci.yml` sets this at workflow level, which does not cross the `workflow_call` boundary
|
||||
# (#7847), so the called workflow needs its own copy. Inert while this matrix is 3.11/3.14 --
|
||||
# (#7847), so the called workflow needs its own copy. Inert while this matrix is 3.11/3.15 --
|
||||
# both managed downloads -- but it stops a future matrix entry silently resolving to a
|
||||
# system interpreter.
|
||||
UV_PYTHON_PREFERENCE: only-managed
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
matrix:
|
||||
# The endpoints of `ci.yml`'s own `test` matrix. Keep these in step with it: an
|
||||
# endpoint left behind here silently stops asking the question on that interpreter.
|
||||
python-version: ['3.11', '3.14']
|
||||
python-version: ['3.11', '3.15']
|
||||
env:
|
||||
CI: true
|
||||
# `BLOCKBUSTER_ENABLED` as on `ci.yml`'s all-extras shards, whose time budget can't absorb its
|
||||
|
||||
@@ -106,7 +106,7 @@ The repo contains a `uv` workspace defining multiple Python packages:
|
||||
|
||||
The project uses:
|
||||
|
||||
- [`uv`](https://docs.astral.sh/uv/getting-started/installation/), supporting Python 3.11 through 3.14
|
||||
- [`uv`](https://docs.astral.sh/uv/getting-started/installation/), supporting Python 3.11 through 3.15
|
||||
- Install all dependencies with `make install`
|
||||
- `pre-commit`, can be installed with `uv tool install pre-commit`
|
||||
- `ruff` via `make lint` and `make format`
|
||||
|
||||
@@ -19,6 +19,7 @@ install-all-python: ## Install and synchronize an interpreter for every python v
|
||||
UV_PROJECT_ENVIRONMENT=.venv312 uv sync --python 3.12 --frozen --all-extras --no-extra mcp-tasks --all-packages --group lint
|
||||
UV_PROJECT_ENVIRONMENT=.venv313 uv sync --python 3.13 --frozen --all-extras --no-extra mcp-tasks --all-packages --group lint
|
||||
UV_PROJECT_ENVIRONMENT=.venv314 uv sync --python 3.14 --frozen --all-extras --no-extra mcp-tasks --all-packages --group lint
|
||||
UV_PROJECT_ENVIRONMENT=.venv315 uv sync --python 3.15 --frozen --all-extras --no-extra mcp-tasks --all-packages --group lint
|
||||
|
||||
.PHONY: sync
|
||||
sync: .uv ## Update local packages and uv.lock
|
||||
@@ -73,11 +74,12 @@ test: ## Run tests without coverage (fast, for local dev)
|
||||
COLUMNS=150 $(if $(PYTEST_PYTHON),UV_PROJECT_ENVIRONMENT=.venv$(subst .,,$(PYTEST_PYTHON))) uv run $(if $(PYTEST_PYTHON),--python $(PYTEST_PYTHON)) pytest -n auto --dist=loadgroup --durations=20
|
||||
|
||||
.PHONY: test-all-python
|
||||
test-all-python: ## Run tests on Python 3.11 to 3.14
|
||||
test-all-python: ## Run tests on Python 3.11 to 3.15
|
||||
COLUMNS=150 UV_PROJECT_ENVIRONMENT=.venv311 uv run --python 3.11 --all-extras --no-extra mcp-tasks --all-packages coverage run -p -m pytest
|
||||
COLUMNS=150 UV_PROJECT_ENVIRONMENT=.venv312 uv run --python 3.12 --all-extras --no-extra mcp-tasks --all-packages coverage run -p -m pytest
|
||||
COLUMNS=150 UV_PROJECT_ENVIRONMENT=.venv313 uv run --python 3.13 --all-extras --no-extra mcp-tasks --all-packages coverage run -p -m pytest
|
||||
COLUMNS=150 UV_PROJECT_ENVIRONMENT=.venv314 uv run --python 3.14 --all-extras --no-extra mcp-tasks --all-packages coverage run -p -m pytest
|
||||
COLUMNS=150 UV_PROJECT_ENVIRONMENT=.venv315 uv run --python 3.15 --all-extras --no-extra mcp-tasks --all-packages coverage run -p -m pytest
|
||||
@uv run coverage combine
|
||||
@uv run coverage report
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Programming Language :: Python :: 3.15",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: System Administrators",
|
||||
|
||||
@@ -67,7 +67,8 @@ replaces the rejected result before the model sees it.
|
||||
- `block_high_risk`: ask the built-in defense to reject detected high or critical
|
||||
risk results. The default is report-only.
|
||||
- `semantic_detection`: add local ML classification beyond known patterns. This
|
||||
requires the `prompt-injection-defender-ml` extra.
|
||||
requires the `prompt-injection-defender-ml` extra. On Python 3.15 that extra
|
||||
installs pattern detection only, until ONNX Runtime publishes Python 3.15 wheels.
|
||||
- `tool_filter`: classify all tools, selected tool names, or tools accepted by a
|
||||
[`ToolSelector`][pydantic_ai.tools.ToolSelector].
|
||||
- `on_detection`: run a sync or async callback for each flagged verdict. A
|
||||
|
||||
@@ -62,8 +62,13 @@ dependencies = [
|
||||
# support (#6661) so the workspace lock can fork onto SDK v2 for the `mcp-tasks` extra.
|
||||
"mcp[cli]>=1.28.1",
|
||||
"modal>=1.0.4",
|
||||
"duckdb>=1.4.2",
|
||||
"datasets>=4.0.0",
|
||||
# duckdb has no cp315 wheels, and a source build outlasts the CI timeout, so the 3.15 all-extras install
|
||||
# skips it. The examples don't support 3.15 until then (no classifier, no 3.15 `test-examples` cell).
|
||||
# `datasets` is only used by the same example (`data_analyst.py`), and on 3.15 it would pull pyarrow and
|
||||
# xxhash bumps into the lock for nothing, so it's skipped there too.
|
||||
# Markers removable once duckdb ships cp315 wheels (tracked in #9455): https://pypi.org/project/duckdb/#files
|
||||
"duckdb>=1.4.2; python_version < '3.15'",
|
||||
"datasets>=4.0.0; python_version < '3.15'",
|
||||
"pandas>=2.3.3",
|
||||
# Needed by the interactive cancel_and_resume example. Declaring it here also pulls prompt_toolkit
|
||||
# into the lowest-versions test env, which un-skips the CLI tests; 3.0.29 is the first release whose
|
||||
|
||||
@@ -34,6 +34,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Programming Language :: Python :: 3.15",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: System Administrators",
|
||||
|
||||
@@ -32,6 +32,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Programming Language :: Python :: 3.15",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: System Administrators",
|
||||
|
||||
@@ -32,6 +32,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Programming Language :: Python :: 3.15",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: System Administrators",
|
||||
|
||||
@@ -35,6 +35,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Programming Language :: Python :: 3.15",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Operating System :: OS Independent",
|
||||
@@ -220,6 +221,16 @@ openai = "2026-10-06T21:31:20Z"
|
||||
# google-genai 2.25.0 is the `google-genai` floor: it sends Gemini blob fields as `mime_type`/`file_uri`.
|
||||
# TODO(adtyavrdhn): Remove after 2026-09-29 17:24 UTC.
|
||||
google-genai = "2026-09-22T17:23:02Z"
|
||||
# First releases with cp315 wheels, so Python 3.15 CI installs them instead of compiling from source.
|
||||
# rpds-py 2026.9.1 is its first cp315 wheel built against the frozen 3.15 ABI (3.15.0rc1); earlier ones
|
||||
# were built against betas, which broke greenlet's on 3.15.0.
|
||||
# Each admits that release's artifacts through one second after its last upload.
|
||||
# TODO(adtyavrdhn): Remove after 2026-10-13 20:33 UTC, when the last of these (asyncpg) leaves the cooldown,
|
||||
# then run `uv lock` and `uv lock --script .github/scripts/pydantic-ai-runner`.
|
||||
aiohttp = "2026-10-05T17:43:49Z"
|
||||
asyncpg = "2026-10-06T20:32:41Z"
|
||||
fastavro = "2026-10-05T22:24:00Z"
|
||||
rpds-py = "2026-10-04T16:32:38Z"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
@@ -258,6 +269,11 @@ dev = [
|
||||
# `[mcp]` extra ships `fastmcp-slim[client]` which lacks server support, so devs need the full
|
||||
# `fastmcp` package alongside, tracking the same range as the `[mcp]` extra (#6661).
|
||||
"fastmcp>=3.3.0,<5",
|
||||
# FastMCP 3 pulls in `beartype` 0.22 (via `py-key-value-aio`), which imports `typing.no_type_check_decorator`
|
||||
# (removed in 3.15).
|
||||
# Removable once beartype 0.23.0 final is released (tracked in #9455):
|
||||
# https://github.com/beartype/beartype/issues/618
|
||||
"beartype>=0.23.0rc2; python_version >= '3.15'",
|
||||
# The MCP SDK v2 wire types as a standalone pure-Pydantic distribution, so the tests can check
|
||||
# the compat readers' field-name pairs against the real v2 models while running against v1.
|
||||
"mcp-types>=2.0.0",
|
||||
@@ -535,6 +551,10 @@ filterwarnings = [
|
||||
"ignore:UnsupportedFieldAttributeWarning:UserWarning",
|
||||
# coolname (prefect dep) uses codecs.open(), deprecated in Python 3.14
|
||||
"ignore:codecs.open\\(\\) is deprecated:DeprecationWarning",
|
||||
# Python 3.15 deprecates `isinstance()` against protocols without `@runtime_checkable`, which
|
||||
# `py-key-value-aio`'s `AsyncKeyValue` (via FastMCP) lacks; this suite's `error` filter would fail on it.
|
||||
# Removable once `py-key-value-aio` decorates its protocols (tracked in #9455).
|
||||
"ignore:<class 'key_value\\.aio\\..*'> isn't explicitly decorated with @runtime_checkable:DeprecationWarning",
|
||||
# outlines' llamacpp backend calls llama-cpp-python's `Llama.from_pretrained`, which forwards
|
||||
# `local_dir_use_symlinks` to `hf_hub_download`; huggingface-hub >= 1.0 deprecated (and ignores)
|
||||
# that arg and warns about it. The arg is a no-op and the call site isn't ours to change.
|
||||
|
||||
@@ -79,7 +79,8 @@ replaces the rejected result before the model sees it.
|
||||
- `block_high_risk`: ask the built-in defense to reject detected high or critical
|
||||
risk results. The default is report-only.
|
||||
- `semantic_detection`: add local ML classification beyond known patterns. This
|
||||
requires the `prompt-injection-defender-ml` extra.
|
||||
requires the `prompt-injection-defender-ml` extra. On Python 3.15 that extra
|
||||
installs pattern detection only, until ONNX Runtime publishes Python 3.15 wheels.
|
||||
- `tool_filter`: classify all tools, selected tool names, or tools accepted by a
|
||||
[`ToolSelector`](https://pydantic.dev/docs/ai/api/pydantic-ai/tools/#pydantic_ai.tools.ToolSelector).
|
||||
- `on_detection`: run a sync or async callback for each flagged verdict. A
|
||||
|
||||
+10
-2
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import KW_ONLY, dataclass, field
|
||||
from typing import Any
|
||||
@@ -169,9 +170,16 @@ class PromptInjectionDefender(AbstractCapability[AgentDepsT]):
|
||||
self._defense = self.defense
|
||||
else:
|
||||
if self.semantic_detection and importlib.util.find_spec('onnxruntime') is None:
|
||||
# The ML extra skips ONNX Runtime on 3.15, which it has no wheels for yet (tracked in #9455).
|
||||
# A conditional expression rather than an `if`, so branch coverage doesn't need one interpreter
|
||||
# to take both arms of this version check.
|
||||
remedy = (
|
||||
'ONNX Runtime has no Python 3.15 wheels yet, so semantic detection needs Python 3.14 or earlier.'
|
||||
if sys.version_info >= (3, 15)
|
||||
else 'Install it with: uv add "pydantic-ai-harness[prompt-injection-defender-ml]"'
|
||||
)
|
||||
raise UserError(
|
||||
'PromptInjectionDefender requires ONNX Runtime when `semantic_detection=True`. '
|
||||
'Install it with: uv add "pydantic-ai-harness[prompt-injection-defender-ml]"'
|
||||
f'PromptInjectionDefender requires ONNX Runtime when `semantic_detection=True`. {remedy}'
|
||||
)
|
||||
# Construct `PromptDefense` directly; the `create_prompt_defense` factory is untyped and fails pyright strict.
|
||||
self._defense = PromptDefense(
|
||||
|
||||
@@ -37,6 +37,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Programming Language :: Python :: 3.15",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
@@ -71,14 +72,23 @@ mongodb = ["pymongo>=4.17.0"]
|
||||
skills = ["pyyaml>=6.0.2"]
|
||||
browser-use = ["browser-use>=0.13.6,<0.14 ; python_full_version >= '3.11'"]
|
||||
playwright = ["idna>=3.4", "playwright>=1.61.0"]
|
||||
stackone = ["fastmcp>=3.4.5,<4", "pydantic-ai-slim[mcp]=={{ major }}.{{ minor }}.{{ patch }}{% if distance %}.dev{{ distance }}+{{ commit }}{% endif %}"]
|
||||
# FastMCP 3 resolves `beartype` 0.22 (via `py-key-value-aio`), which imports `typing.no_type_check_decorator` (removed in 3.15).
|
||||
# The beartype line is removable once beartype 0.23.0 final is released (tracked in #9455):
|
||||
# https://github.com/beartype/beartype/issues/618
|
||||
stackone = ["fastmcp>=3.4.5,<4", "beartype>=0.23.0rc2 ; python_version >= '3.15'", "pydantic-ai-slim[mcp]=={{ major }}.{{ minor }}.{{ patch }}{% if distance %}.dev{{ distance }}+{{ commit }}{% endif %}"]
|
||||
ordinal = ["pydantic-ai-slim[mcp]=={{ major }}.{{ minor }}.{{ patch }}{% if distance %}.dev{{ distance }}+{{ commit }}{% endif %}"]
|
||||
grain = ["pydantic-ai-slim[mcp]=={{ major }}.{{ minor }}.{{ patch }}{% if distance %}.dev{{ distance }}+{{ commit }}{% endif %}"]
|
||||
day-ai = ["pydantic-ai-slim[mcp]=={{ major }}.{{ minor }}.{{ patch }}{% if distance %}.dev{{ distance }}+{{ commit }}{% endif %}"]
|
||||
posthog = ["pydantic-ai-slim[mcp]=={{ major }}.{{ minor }}.{{ patch }}{% if distance %}.dev{{ distance }}+{{ commit }}{% endif %}"]
|
||||
pylon = ["pydantic-ai-slim[mcp]=={{ major }}.{{ minor }}.{{ patch }}{% if distance %}.dev{{ distance }}+{{ commit }}{% endif %}"]
|
||||
prompt-injection-defender = ["stackone-defender>=0.7.3,<0.8 ; python_full_version >= '3.11'"]
|
||||
prompt-injection-defender-ml = ["stackone-defender[onnx]>=0.7.3,<0.8 ; python_full_version >= '3.11'"]
|
||||
# The base requirement keeps pattern detection installed on 3.15, where onnxruntime has no wheels yet.
|
||||
# Fold back into one `[onnx]` line once onnxruntime ships cp315 wheels (tracked in #9455):
|
||||
# https://pypi.org/project/onnxruntime/#files
|
||||
prompt-injection-defender-ml = [
|
||||
"stackone-defender>=0.7.3,<0.8 ; python_full_version >= '3.11'",
|
||||
"stackone-defender[onnx]>=0.7.3,<0.8 ; python_full_version >= '3.11' and python_version < '3.15'",
|
||||
]
|
||||
github = ["pydantic-ai-slim[mcp]=={{ major }}.{{ minor }}.{{ patch }}{% if distance %}.dev{{ distance }}+{{ commit }}{% endif %}"]
|
||||
slack = ["pydantic-ai-slim[mcp]=={{ major }}.{{ minor }}.{{ patch }}{% if distance %}.dev{{ distance }}+{{ commit }}{% endif %}"]
|
||||
coder = ["ripgrep==14.1.0 ; sys_platform != 'android'"]
|
||||
|
||||
@@ -66,6 +66,7 @@ def _shown(screen: int) -> bytes:
|
||||
|
||||
|
||||
@posix_only
|
||||
@pytest.mark.subprocess(reason='a child process must inherit the relayed stderr, which is the behavior under test')
|
||||
def test_relay_keeps_fork_notices_off_the_terminal(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(stderr_relay.sys, 'platform', 'darwin')
|
||||
child = f'import os; os.write(2, b"child error\\n"); os.write(2, {_NOTICE!r}); os.write(2, b"child done\\n")'
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
@@ -69,13 +70,24 @@ def test_defense_with_semantic_detection_raises() -> None:
|
||||
PromptInjectionDefender(_observe(), semantic_detection=True)
|
||||
|
||||
|
||||
def test_semantic_detection_without_onnx_raises(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
('version_info', 'remedy'),
|
||||
[
|
||||
((3, 14), 'uv add "pydantic-ai-harness[prompt-injection-defender-ml]"'),
|
||||
((3, 15), 'needs Python 3.14 or earlier'),
|
||||
],
|
||||
)
|
||||
def test_semantic_detection_without_onnx_raises(
|
||||
monkeypatch: pytest.MonkeyPatch, version_info: tuple[int, int], remedy: str
|
||||
) -> None:
|
||||
def no_spec(name: str) -> None:
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(importlib.util, 'find_spec', no_spec)
|
||||
with pytest.raises(UserError, match='ONNX Runtime'):
|
||||
monkeypatch.setattr(sys, 'version_info', version_info)
|
||||
with pytest.raises(UserError, match='ONNX Runtime') as exc_info:
|
||||
PromptInjectionDefender(semantic_detection=True)
|
||||
assert remedy in str(exc_info.value)
|
||||
|
||||
|
||||
@requires_onnx
|
||||
|
||||
@@ -665,6 +665,12 @@ def test_the_fork_handler_hands_back_a_lock_nobody_holds():
|
||||
|
||||
|
||||
@pytest.mark.skipif(not hasattr(os, 'fork'), reason='fork is POSIX-only')
|
||||
# Forking while another thread holds the lock is the scenario under test. Python 3.12+ warns about forking a
|
||||
# multithreaded process; from 3.15, `os.fork()` raises that warning under this suite's `error` filter instead
|
||||
# of swallowing it.
|
||||
@pytest.mark.filterwarnings(
|
||||
'ignore:This process .* is multi-threaded, use of fork\\(\\) may lead to deadlocks:DeprecationWarning'
|
||||
)
|
||||
def test_a_banner_does_not_hang_a_process_forked_mid_claim():
|
||||
"""`fork` clones one thread, so a lock another was holding is held forever in the child.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user