Skip to content

Commit a0733ec

Browse files
KludexDouweMdsfacciniclaude
authored
Support Python 3.14 (#4138)
Co-authored-by: Douwe Maan <douwe@pydantic.dev> Co-authored-by: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a8df345 commit a0733ec

25 files changed

+471
-220
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,11 @@ jobs:
102102
&& 'depot-ubuntu-24.04-4'
103103
|| 'ubuntu-latest'
104104
}}
105-
timeout-minutes: 20
105+
timeout-minutes: 15
106106
strategy:
107107
fail-fast: false
108108
matrix:
109-
python-version: ["3.10", "3.11", "3.12", "3.13"]
109+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
110110
install:
111111
- name: pydantic-ai-slim
112112
command: "--package pydantic-ai-slim"
@@ -163,11 +163,11 @@ jobs:
163163
&& 'depot-ubuntu-24.04-4'
164164
|| 'ubuntu-latest'
165165
}}
166-
timeout-minutes: 20
166+
timeout-minutes: 15
167167
strategy:
168168
fail-fast: false
169169
matrix:
170-
python-version: ["3.10", "3.11", "3.12", "3.13"]
170+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
171171
env:
172172
CI: true
173173
COVERAGE_PROCESS_START: ./pyproject.toml
@@ -212,7 +212,7 @@ jobs:
212212
strategy:
213213
fail-fast: false
214214
matrix:
215-
python-version: ["3.11", "3.12", "3.13"]
215+
python-version: ["3.11", "3.12", "3.13", "3.14"]
216216
env:
217217
CI: true
218218
steps:

.python-version

Lines changed: 0 additions & 1 deletion
This file was deleted.

clai/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ classifiers = [
3232
"Programming Language :: Python :: 3.11",
3333
"Programming Language :: Python :: 3.12",
3434
"Programming Language :: Python :: 3.13",
35+
"Programming Language :: Python :: 3.14",
3536
"Intended Audience :: Developers",
3637
"Intended Audience :: Information Technology",
3738
"Intended Audience :: System Administrators",

docs/embeddings.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ export VOYAGE_API_KEY='your-api-key'
364364

365365
You can then use the model:
366366

367-
```python {title="voyageai_embeddings.py"}
367+
```python {title="voyageai_embeddings.py" max_py="3.13"}
368368
from pydantic_ai import Embedder
369369

370370
embedder = Embedder('voyageai:voyage-3.5')
@@ -384,7 +384,7 @@ See the [VoyageAI Embeddings documentation](https://docs.voyageai.com/docs/embed
384384

385385
VoyageAI models support additional settings via [`VoyageAIEmbeddingSettings`][pydantic_ai.embeddings.voyageai.VoyageAIEmbeddingSettings]:
386386

387-
```python {title="voyageai_settings.py"}
387+
```python {title="voyageai_settings.py" max_py="3.13"}
388388
from pydantic_ai import Embedder
389389
from pydantic_ai.embeddings.voyageai import VoyageAIEmbeddingSettings
390390

@@ -590,7 +590,7 @@ pip/uv-add "pydantic-ai-slim[sentence-transformers]"
590590

591591
#### Usage
592592

593-
```python {title="sentence_transformers_embeddings.py"}
593+
```python {title="sentence_transformers_embeddings.py" max_py="3.13"}
594594
from pydantic_ai import Embedder
595595

596596
# Model is downloaded from Hugging Face on first use
@@ -611,7 +611,7 @@ See the [Sentence-Transformers pretrained models](https://www.sbert.net/docs/sen
611611

612612
Control which device to use for inference:
613613

614-
```python {title="sentence_transformers_device.py"}
614+
```python {title="sentence_transformers_device.py" max_py="3.13"}
615615
from pydantic_ai import Embedder
616616
from pydantic_ai.embeddings.sentence_transformers import (
617617
SentenceTransformersEmbeddingSettings,
@@ -630,7 +630,7 @@ embedder = Embedder(
630630

631631
If you need more control over model initialization:
632632

633-
```python {title="sentence_transformers_instance.py"}
633+
```python {title="sentence_transformers_instance.py" max_py="3.13"}
634634
from sentence_transformers import SentenceTransformer
635635

636636
from pydantic_ai import Embedder

examples/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ classifiers = [
3232
"Programming Language :: Python :: 3.11",
3333
"Programming Language :: Python :: 3.12",
3434
"Programming Language :: Python :: 3.13",
35+
"Programming Language :: Python :: 3.14",
3536
"Intended Audience :: Developers",
3637
"Intended Audience :: Information Technology",
3738
"Intended Audience :: System Administrators",

pydantic_ai_slim/pydantic_ai/_function_schema.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from dataclasses import dataclass, field
1010
from functools import partial
1111
from inspect import Parameter, signature
12-
from typing import TYPE_CHECKING, Any, Concatenate, cast, get_origin
12+
from typing import TYPE_CHECKING, Any, Concatenate, Literal, cast, get_origin
1313

1414
from pydantic import ConfigDict
1515
from pydantic._internal import _decorators, _generate_schema, _typing_extra
@@ -184,10 +184,8 @@ def function_schema( # noqa: C901
184184
raise UserError(f'Error generating schema for {function.__qualname__}:\n {error_details}')
185185

186186
core_config = config_wrapper.core_config(None)
187-
# noinspection PyTypedDict
188-
core_config['extra_fields_behavior'] = 'allow' if var_kwargs_schema else 'forbid'
189187

190-
schema, single_arg_name = _build_schema(fields, var_kwargs_schema, gen_schema, core_config)
188+
schema, single_arg_name = _build_schema(fields, var_kwargs_schema, core_config)
191189
schema = gen_schema.clean_schema(schema)
192190
# noinspection PyUnresolvedReferences
193191
schema_validator = create_schema_validator(
@@ -269,15 +267,13 @@ def _takes_ctx(callable_obj: TargetCallable[P, R]) -> TypeIs[WithCtx[P, R]]: #
269267
def _build_schema(
270268
fields: dict[str, core_schema.TypedDictField],
271269
var_kwargs_schema: core_schema.CoreSchema | None,
272-
gen_schema: _generate_schema.GenerateSchema,
273270
core_config: core_schema.CoreConfig,
274271
) -> tuple[core_schema.CoreSchema, str | None]:
275272
"""Generate a typed dict schema for function parameters.
276273
277274
Args:
278275
fields: The fields to generate a typed dict schema for.
279276
var_kwargs_schema: The variable keyword arguments schema.
280-
gen_schema: The `GenerateSchema` instance.
281277
core_config: The core configuration.
282278
283279
Returns:
@@ -289,10 +285,12 @@ def _build_schema(
289285
if td_field['metadata']['is_model_like']: # type: ignore
290286
return td_field['schema'], name
291287

288+
extra_behavior: Literal['allow', 'forbid'] = 'allow' if var_kwargs_schema else 'forbid'
292289
td_schema = core_schema.typed_dict_schema(
293290
fields,
294291
config=core_config,
295-
extras_schema=gen_schema.generate_schema(var_kwargs_schema) if var_kwargs_schema else None,
292+
extra_behavior=extra_behavior,
293+
extras_schema=var_kwargs_schema,
296294
)
297295
return td_schema, None
298296

pydantic_ai_slim/pydantic_ai/embeddings/sentence_transformers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class SentenceTransformerEmbeddingModel(EmbeddingModel):
6666
for available models.
6767
6868
Example:
69-
```python
69+
```python {max_py="3.13"}
7070
from sentence_transformers import SentenceTransformer
7171
7272
from pydantic_ai.embeddings.sentence_transformers import (

pydantic_ai_slim/pydantic_ai/embeddings/voyageai.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class VoyageAIEmbeddingModel(EmbeddingModel):
9090
retrieval, with specialized models for code, finance, and legal domains.
9191
9292
Example:
93-
```python
93+
```python {max_py="3.13"}
9494
from pydantic_ai.embeddings.voyageai import VoyageAIEmbeddingModel
9595
9696
model = VoyageAIEmbeddingModel('voyage-3.5')

pydantic_ai_slim/pydantic_ai/format_prompt.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def _set_scalar_text(self, element: ElementTree.Element, value: Any) -> bool:
137137
if value is None:
138138
element.text = self.none_str
139139
elif isinstance(value, str):
140-
element.text = value
140+
element.text = value.value if isinstance(value, Enum) else value
141141
elif isinstance(value, bytes | bytearray):
142142
element.text = value.decode(errors='ignore')
143143
elif isinstance(value, bool | int | float | Enum):

pydantic_ai_slim/pydantic_ai/tools.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -236,21 +236,6 @@ class DeferredToolResults:
236236

237237

238238
class GenerateToolJsonSchema(GenerateJsonSchema):
239-
def typed_dict_schema(self, schema: core_schema.TypedDictSchema) -> JsonSchemaValue:
240-
json_schema = super().typed_dict_schema(schema)
241-
# Workaround for https://github.com/pydantic/pydantic/issues/12123
242-
if 'additionalProperties' not in json_schema: # pragma: no branch
243-
extra = schema.get('extra_behavior') or schema.get('config', {}).get('extra_fields_behavior')
244-
if extra == 'allow':
245-
extras_schema = schema.get('extras_schema', None)
246-
if extras_schema is not None:
247-
json_schema['additionalProperties'] = self.generate_inner(extras_schema) or True
248-
else:
249-
json_schema['additionalProperties'] = True # pragma: no cover
250-
elif extra == 'forbid':
251-
json_schema['additionalProperties'] = False
252-
return json_schema
253-
254239
def _named_required_fields_schema(self, named_required_fields: Sequence[tuple[str, bool, Any]]) -> JsonSchemaValue:
255240
# Remove largely-useless property titles
256241
s = super()._named_required_fields_schema(named_required_fields)

0 commit comments

Comments
 (0)