Skip to content

Commit 7615bf4

Browse files
KludexDouweM
andauthored
Drop support for Python 3.9 (#2725)
Co-authored-by: Douwe Maan <[email protected]>
1 parent 7e0b4fc commit 7615bf4

File tree

104 files changed

+450
-1148
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+450
-1148
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ jobs:
137137
strategy:
138138
fail-fast: false
139139
matrix:
140-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
140+
python-version: ["3.10", "3.11", "3.12", "3.13"]
141141
install:
142142
- name: pydantic-ai-slim
143143
command: "--package pydantic-ai-slim"
@@ -169,7 +169,6 @@ jobs:
169169

170170
- name: store coverage files
171171
uses: actions/upload-artifact@v4
172-
if: matrix.python-version != '3.9'
173172
with:
174173
name: coverage-${{ matrix.python-version }}-${{ matrix.install.name }}
175174
path: .coverage

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ This is a uv workspace with multiple packages:
9191
- **VCR cassettes**: `tests/cassettes/` for recorded LLM API interactions
9292
- **Test models**: Use `TestModel` for deterministic testing
9393
- **Examples testing**: `tests/test_examples.py` validates all documentation examples
94-
- **Multi-version testing**: Python 3.9-3.13 support
94+
- **Multi-version testing**: Python 3.10-3.13 support
9595

9696
## Key Configuration Files
9797

Makefile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ install: .uv .pre-commit .deno ## Install the package, dependencies, and pre-com
1919

2020
.PHONY: install-all-python
2121
install-all-python: ## Install and synchronize an interpreter for every python version
22-
UV_PROJECT_ENVIRONMENT=.venv39 uv sync --python 3.9 --frozen --all-extras --all-packages --group lint --group docs
2322
UV_PROJECT_ENVIRONMENT=.venv310 uv sync --python 3.10 --frozen --all-extras --all-packages --group lint --group docs
2423
UV_PROJECT_ENVIRONMENT=.venv311 uv sync --python 3.11 --frozen --all-extras --all-packages --group lint --group docs
2524
UV_PROJECT_ENVIRONMENT=.venv312 uv sync --python 3.12 --frozen --all-extras --all-packages --group lint --group docs
@@ -60,13 +59,12 @@ typecheck-both: typecheck-pyright typecheck-mypy
6059

6160
.PHONY: test
6261
test: ## Run tests and collect coverage data
63-
uv run coverage run -m pytest -n auto --dist=loadgroup
62+
uv run coverage run -m pytest -n auto --dist=loadgroup --durations=20
6463
@uv run coverage combine
6564
@uv run coverage report
6665

6766
.PHONY: test-all-python
68-
test-all-python: ## Run tests on Python 3.9 to 3.13
69-
UV_PROJECT_ENVIRONMENT=.venv39 uv run --python 3.9 --all-extras --all-packages coverage run -p -m pytest
67+
test-all-python: ## Run tests on Python 3.10 to 3.13
7068
UV_PROJECT_ENVIRONMENT=.venv310 uv run --python 3.10 --all-extras --all-packages coverage run -p -m pytest
7169
UV_PROJECT_ENVIRONMENT=.venv311 uv run --python 3.11 --all-extras --all-packages coverage run -p -m pytest
7270
UV_PROJECT_ENVIRONMENT=.venv312 uv run --python 3.12 --all-extras --all-packages coverage run -p -m pytest

clai/pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ classifiers = [
2828
"Programming Language :: Python",
2929
"Programming Language :: Python :: 3",
3030
"Programming Language :: Python :: 3 :: Only",
31-
"Programming Language :: Python :: 3.9",
3231
"Programming Language :: Python :: 3.10",
3332
"Programming Language :: Python :: 3.11",
3433
"Programming Language :: Python :: 3.12",
@@ -44,7 +43,7 @@ classifiers = [
4443
"Topic :: Software Development :: Libraries :: Python Modules",
4544
"Topic :: Internet",
4645
]
47-
requires-python = ">=3.9"
46+
requires-python = ">=3.10"
4847

4948
[tool.hatch.metadata.hooks.uv-dynamic-versioning]
5049
dependencies = [

docs/install.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Pydantic AI is available on PyPI as [`pydantic-ai`](https://pypi.org/project/pyd
66
pip/uv-add pydantic-ai
77
```
88

9-
(Requires Python 3.9+)
9+
(Requires Python 3.10+)
1010

1111
This installs the `pydantic_ai` package, core dependencies, and libraries required to use all the models
1212
included in Pydantic AI. If you want to use a specific model, you can install the ["slim"](#slim-install) version of Pydantic AI.

examples/pydantic_ai_examples/chat_app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
import asyncio
1111
import json
1212
import sqlite3
13-
from collections.abc import AsyncIterator
13+
from collections.abc import AsyncIterator, Callable
1414
from concurrent.futures.thread import ThreadPoolExecutor
1515
from contextlib import asynccontextmanager
1616
from dataclasses import dataclass
1717
from datetime import datetime, timezone
1818
from functools import partial
1919
from pathlib import Path
20-
from typing import Annotated, Any, Callable, Literal, TypeVar
20+
from typing import Annotated, Any, Literal, TypeVar
2121

2222
import fastapi
2323
import logfire

examples/pydantic_ai_examples/sql_gen.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@
1616
from contextlib import asynccontextmanager
1717
from dataclasses import dataclass
1818
from datetime import date
19-
from typing import Annotated, Any, Union
19+
from typing import Annotated, Any, TypeAlias
2020

2121
import asyncpg
2222
import logfire
2323
from annotated_types import MinLen
2424
from devtools import debug
2525
from pydantic import BaseModel, Field
26-
from typing_extensions import TypeAlias
2726

2827
from pydantic_ai import Agent, ModelRetry, RunContext, format_as_xml
2928

@@ -91,7 +90,7 @@ class InvalidRequest(BaseModel):
9190
error_message: str
9291

9392

94-
Response: TypeAlias = Union[Success, InvalidRequest]
93+
Response: TypeAlias = Success | InvalidRequest
9594
agent = Agent[Deps, Response](
9695
'google-gla:gemini-1.5-flash',
9796
# Type ignore while we wait for PEP-0747, nonetheless unions will work fine everywhere else

examples/pyproject.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ classifiers = [
2828
"Programming Language :: Python",
2929
"Programming Language :: Python :: 3",
3030
"Programming Language :: Python :: 3 :: Only",
31-
"Programming Language :: Python :: 3.9",
3231
"Programming Language :: Python :: 3.10",
3332
"Programming Language :: Python :: 3.11",
3433
"Programming Language :: Python :: 3.12",
@@ -44,7 +43,7 @@ classifiers = [
4443
"Topic :: Software Development :: Libraries :: Python Modules",
4544
"Topic :: Internet",
4645
]
47-
requires-python = ">=3.9"
46+
requires-python = ">=3.10"
4847

4948
[tool.hatch.metadata.hooks.uv-dynamic-versioning]
5049
dependencies = [
@@ -57,8 +56,8 @@ dependencies = [
5756
"rich>=13.9.2",
5857
"uvicorn>=0.32.0",
5958
"devtools>=0.12.2",
60-
"gradio>=5.9.0; python_version>'3.9'",
61-
"mcp[cli]>=1.4.1; python_version >= '3.10'",
59+
"gradio>=5.9.0",
60+
"mcp[cli]>=1.4.1",
6261
"modal>=1.0.4",
6362
"duckdb>=1.3.2",
6463
"datasets>=4.0.0",

pydantic_ai_slim/pydantic_ai/_agent_graph.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
import dataclasses
55
import hashlib
66
from collections import defaultdict, deque
7-
from collections.abc import AsyncIterator, Awaitable, Iterator, Sequence
7+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Sequence
88
from contextlib import asynccontextmanager, contextmanager
99
from contextvars import ContextVar
1010
from dataclasses import field
11-
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, Union, cast
11+
from typing import TYPE_CHECKING, Any, Generic, Literal, TypeGuard, Union, cast
1212

1313
from opentelemetry.trace import Tracer
14-
from typing_extensions import TypeGuard, TypeVar, assert_never
14+
from typing_extensions import TypeVar, assert_never
1515

1616
from pydantic_ai._function_schema import _takes_ctx as is_takes_ctx # type: ignore
1717
from pydantic_ai._tool_manager import ToolManager
@@ -59,12 +59,12 @@
5959
_HistoryProcessorAsyncWithCtx = Callable[
6060
[RunContext[DepsT], list[_messages.ModelMessage]], Awaitable[list[_messages.ModelMessage]]
6161
]
62-
HistoryProcessor = Union[
63-
_HistoryProcessorSync,
64-
_HistoryProcessorAsync,
65-
_HistoryProcessorSyncWithCtx[DepsT],
66-
_HistoryProcessorAsyncWithCtx[DepsT],
67-
]
62+
HistoryProcessor = (
63+
_HistoryProcessorSync
64+
| _HistoryProcessorAsync
65+
| _HistoryProcessorSyncWithCtx[DepsT]
66+
| _HistoryProcessorAsyncWithCtx[DepsT]
67+
)
6868
"""A function that processes a list of model messages and returns a list of model messages.
6969
7070
Can optionally accept a `RunContext` as a parameter.
@@ -736,15 +736,15 @@ async def _call_function_tool(
736736

737737
if isinstance(tool_result, _messages.ToolReturn):
738738
if (
739-
isinstance(tool_result.return_value, _messages.MultiModalContentTypes)
739+
isinstance(tool_result.return_value, _messages.MultiModalContent)
740740
or isinstance(tool_result.return_value, list)
741741
and any(
742-
isinstance(content, _messages.MultiModalContentTypes)
742+
isinstance(content, _messages.MultiModalContent)
743743
for content in tool_result.return_value # type: ignore
744744
)
745745
):
746746
raise exceptions.UserError(
747-
f'The `return_value` of tool {tool_call.tool_name!r} contains invalid nested `MultiModalContentTypes` objects. '
747+
f'The `return_value` of tool {tool_call.tool_name!r} contains invalid nested `MultiModalContent` objects. '
748748
f'Please use `content` instead.'
749749
)
750750

@@ -765,7 +765,7 @@ def process_content(content: Any) -> Any:
765765
f'The return value of tool {tool_call.tool_name!r} contains invalid nested `ToolReturn` objects. '
766766
f'`ToolReturn` should be used directly.'
767767
)
768-
elif isinstance(content, _messages.MultiModalContentTypes):
768+
elif isinstance(content, _messages.MultiModalContent):
769769
if isinstance(content, _messages.BinaryContent):
770770
identifier = content.identifier or multi_modal_content_identifier(content.data)
771771
else:

pydantic_ai_slim/pydantic_ai/_function_schema.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
from __future__ import annotations as _annotations
77

8-
from collections.abc import Awaitable
8+
from collections.abc import Awaitable, Callable
99
from dataclasses import dataclass, field
1010
from inspect import Parameter, signature
11-
from typing import TYPE_CHECKING, Any, Callable, Union, cast
11+
from typing import TYPE_CHECKING, Any, Concatenate, cast, get_origin
1212

1313
from pydantic import ConfigDict
1414
from pydantic._internal import _decorators, _generate_schema, _typing_extra
@@ -17,7 +17,7 @@
1717
from pydantic.json_schema import GenerateJsonSchema
1818
from pydantic.plugin._schema_validator import create_schema_validator
1919
from pydantic_core import SchemaValidator, core_schema
20-
from typing_extensions import Concatenate, ParamSpec, TypeIs, TypeVar, get_origin
20+
from typing_extensions import ParamSpec, TypeIs, TypeVar
2121

2222
from ._griffe import doc_descriptions
2323
from ._run_context import RunContext
@@ -231,7 +231,7 @@ def function_schema( # noqa: C901
231231

232232
WithCtx = Callable[Concatenate[RunContext[Any], P], R]
233233
WithoutCtx = Callable[P, R]
234-
TargetFunc = Union[WithCtx[P, R], WithoutCtx[P, R]]
234+
TargetFunc = WithCtx[P, R] | WithoutCtx[P, R]
235235

236236

237237
def _takes_ctx(function: TargetFunc[P, R]) -> TypeIs[WithCtx[P, R]]:

0 commit comments

Comments
 (0)