Пример
pydantic 2.10.6: Preserve AnyUrl and HttpUrl types during model_dump and dump_python serialization instead of coercing to str, fixed in Pydantic v2.10.6
Проверенный пример — pypi pydantic 2.10.6: Preserve AnyUrl and HttpUrl types during model_dump and dump_python serialization instead of coercing to str…
sha256:39e10d5ed6b42f8dff5b5058de1c88dc65201cb0b044894581fdada51cd99e7e
Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла.
Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди.
MIT-0
Свидетельства выполнения
Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.
- Основа свидетельства
- Подписанный контракт пройден
- Квитанции проверки
- 2
- Ключи подписи, собравшие его
- 2
Заявленная среда
python linux x64 python python pip
Среды запусков проверки
| Окружение | Контракт | Этапы | Запуск |
|---|---|---|---|
| python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-16 |
| python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-18 |
Кейс
HOW- Цель
- Preserve AnyUrl and HttpUrl types during model_dump and dump_python serialization instead of coercing to str, fixed in Pydantic v2.10.6
- Пакеты
- Символы
-
- pydantic.AnyUrl
- pydantic.HttpUrl
- pydantic.BaseModel.model_dump
- pydantic.TypeAdapter.dump_python
- Окружение
- python
- Создан
- 2026-08-16T05:33:52Z
Контракт
- assert model_dump() retains AnyUrl instances with .host and .path attributes instead of coercing to str
- assert model_dump(mode='python') retains HttpUrl instances with .port and .scheme attributes
- assert model_dump() retains AnyUrl instances within Union[AnyUrl, int] fields
- assert TypeAdapter(AnyUrl).dump_python() returns AnyUrl instance rather than str
- assert TypeAdapter(Union[AnyUrl, int]).dump_python() returns AnyUrl instance rather than str
Файлы
- NOTES.md
- csx.json
- requirements.lock
- requirements.txt
- src/url_models.py
- test/contract.py
Исходный код
# CodeSampleX Notes: Pydantic v2.10.6 URL Python Serialization Bugfix
### 1. `search_known_solution` Query & Existing Samples Found
Before authoring this sample, `search_known_solution` was invoked for `pydantic` validation/serialization/bugfix queries. The search returned existing related samples:
- `sha256:e42c3b1ea5735bfe91f5529c093002f4401eff5fcd584ff24883134788d26c35`: *"Avoid silent validation failures, broken V1 imports, and order assumptions across Pydantic V2 validator modes"*
- `sha256:3ea42df47255426affc822fffa2fe475b71a881239b1db84e637d566bf47ae93`: *"Replace pydantic v1 parse_obj/dict with the v2 model_validate/model_dump API"*
- `sha256:f5541d3ce0eda3656543ec05a42e65a6c79c2fcfe44febb312569405335bdce1`: *"Pin pydantic-settings source precedence..."*
To provide distinct, verified bugfix coverage, this sample targets the URL serialization regression fix in patch release `v2.10.6`.
---
### 2. The Release & Notes Citation
- **Target Release**: Pydantic [`v2.10.6`](https://github.com/pydantic/pydantic/releases/tag/v2.10.6) (Released January 23, 2025).
- **Changelog Entry**:
> *"Fix url python serialization by @sydney-runkle in [#11331](https://github.com/pydantic/pydantic/pull/11331)"*
- **Originating PRs & Issues**: PR [#11331](https://github.com/pydantic/pydantic/pull/11331) (backport of PR [#11330](https://github.com/pydantic/pydantic/pull/11330)), which resolved Issue [#11248](https://github.com/pydantic/pydantic/issues/11248) (*"Field AnyUrl cast to str when model_dump"*).
---
### 3. Behaviour Before (v2.10.5) vs Behaviour After (v2.10.6)
#### Broken Behaviour (v2.10.5)
In `v2.10.5`, PR [#11233](https://github.com/pydantic/pydantic/pull/11233) intended to fix URL serialization in unions, but inadvertently changed the core serializer for `_BaseUrl` subclasses.
When invoking `model.model_dump()` (defaulting to `mode='python'`), `model.model_dump(mode='python')`, or `TypeAdapter(AnyUrl).dump_python()`, any `AnyUrl` / `HttpUrl` object was coerced directly to a standard Python `str` rather than preserving the URL object instance.
```python
# v2.10.5 (Broken):
model = ServiceEndpoint(endpoint="https://api.example.com/v1/events")
dumped = model.model_dump()
type(dumped["endpoint"]) # -> <class 'str'> (BUG: coerced to string in python mode)
isinstance(dumped["endpoint"], AnyUrl) # -> False
dumped["endpoint"].host # -> AttributeError: 'str' object has no attribute 'host'
```
#### Fixed Behaviour (v2.10.6+)
In `v2.10.6`, PR #11331 restored the proper conditional serialization for Python mode:
- `model.model_dump()` / `model.model_dump(mode='python')` and `TypeAdapter.dump_python()` preserve `AnyUrl` and `HttpUrl` instances intact.
- Concrete attributes such as `.host`, `.port`, `.scheme`, and `.path` remain accessible on dumped dictionaries.
- String serialization is strictly applied when `mode='json'` is explicitly requested (`model.model_dump(mode='json')` or `model.model_dump_json()`).
```python
# v2.10.6 (Fixed):
model = ServiceEndpoint(endpoint="https://api.example.com/v1/events")
dumped = model.model_dump()
type(dumped["endpoint"]) # -> <class 'pydantic.networks.AnyUrl'>
isinstance(dumped["endpoint"], AnyUrl) # -> True
dumped["endpoint"].host # -> "api.example.com"
```
---
### 4. How the Broken Version Fails
1. **Loud Failures (`AttributeError`)**:
Downstream application code expecting `model_dump()` to return rich URL objects (e.g. accessing `dumped["endpoint"].host` or `dumped["webhook"].port`) crashes immediately at runtime with:
`AttributeError: 'str' object has no attribute 'host'`.
2. **Silent Failure with Green Build**:
If test suites or callers only check for string representations or truthiness (e.g., `assert str(dumped["endpoint"]) == "https://example.com/..."`), tests pass green while the actual object type is silently corrupted from `AnyUrl` to `str`, breaking type guards and type invariants throughout the application.
{"case":{"caseId":"case:sha256:9c1a06b37d096020760805524b324db781944c244f5f534d84bb34610078cc06","contract":["assert model_dump() retains AnyUrl instances with .host and .path attributes instead of coercing to str","assert model_dump(mode='python') retains HttpUrl instances with .port and .scheme attributes","assert model_dump() retains AnyUrl instances within Union[AnyUrl, int] fields","assert TypeAdapter(AnyUrl).dump_python() returns AnyUrl instance rather than str","assert TypeAdapter(Union[AnyUrl, int]).dump_python() returns AnyUrl instance rather than str"],"goal":"Preserve AnyUrl and HttpUrl types during model_dump and dump_python serialization instead of coercing to str, fixed in Pydantic v2.10.6","kind":"HOW","packages":["pkg:pypi/pydantic@2.10.6"],"schemaVersion":1,"symbols":["pydantic.AnyUrl","pydantic.HttpUrl","pydantic.BaseModel.model_dump","pydantic.TypeAdapter.dump_python"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/pydantic@2.10.6"],"schemaVersion":1,"symbols":["pydantic.AnyUrl","pydantic.HttpUrl","pydantic.BaseModel.model_dump","pydantic.TypeAdapter.dump_python"],"verifierAdapter":"python@1"}
annotated-types==0.8.0
pydantic==2.10.6
pydantic-core==2.27.2
typing-extensions==4.12.2
annotated-types==0.8.0
pydantic==2.10.6
pydantic-core==2.27.2
typing-extensions==4.12.2
from typing import Union
from pydantic import BaseModel, AnyUrl, HttpUrl
class ServiceEndpoint(BaseModel):
endpoint: AnyUrl
webhook: HttpUrl
fallback: Union[AnyUrl, int]
import sys
from pathlib import Path
# Add project root to sys.path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from typing import Union
from pydantic import AnyUrl, HttpUrl, TypeAdapter
from src.url_models import ServiceEndpoint
def test_model_dump_preserves_anyurl_instance():
# In Pydantic 2.10.5 (broken by #11233), model_dump() coerced AnyUrl to str.
# Fixed in 2.10.6 (#11331 / #11330 / #11248): model_dump() preserves AnyUrl object.
model = ServiceEndpoint(
endpoint="https://api.example.com/v1/events",
webhook="http://hooks.example.net:8080/notify",
fallback=100,
)
dumped = model.model_dump()
# Broken version (2.10.5) returned str; fixed version (2.10.6+) returns AnyUrl
assert isinstance(dumped["endpoint"], AnyUrl), (
f"Expected AnyUrl instance in model_dump(), got {type(dumped['endpoint'])}"
)
assert not isinstance(dumped["endpoint"], str), (
"AnyUrl must not be coerced to str in python mode model_dump()"
)
assert dumped["endpoint"].host == "api.example.com"
assert dumped["endpoint"].path == "/v1/events"
def test_model_dump_mode_python_preserves_httpurl_attributes():
# In 2.10.5, model_dump(mode='python') coerced HttpUrl to str, breaking property access.
# Fixed in 2.10.6: HttpUrl is preserved with .port, .scheme, .host intact.
model = ServiceEndpoint(
endpoint="https://api.example.com",
webhook="http://hooks.example.net:8080/notify",
fallback=100,
)
dumped = model.model_dump(mode="python")
assert isinstance(dumped["webhook"], HttpUrl), (
f"Expected HttpUrl instance in model_dump(mode='python'), got {type(dumped['webhook'])}"
)
assert not isinstance(dumped["webhook"], str), (
"HttpUrl must not be coerced to str in python mode"
)
assert dumped["webhook"].port == 8080
assert dumped["webhook"].scheme == "http"
assert dumped["webhook"].host == "hooks.example.net"
def test_model_dump_preserves_url_in_union_field():
# In 2.10.5, AnyUrl in Union[AnyUrl, int] was coerced to str.
# Fixed in 2.10.6: AnyUrl in Union fields is preserved as AnyUrl.
model = ServiceEndpoint(
endpoint="https://api.example.com",
webhook="http://hooks.example.net:8080/notify",
fallback="https://backup.example.com/status",
)
dumped = model.model_dump()
assert isinstance(dumped["fallback"], AnyUrl), (
f"Expected AnyUrl instance in union field, got {type(dumped['fallback'])}"
)
assert not isinstance(dumped["fallback"], str), (
"Union URL field must not be coerced to str"
)
assert dumped["fallback"].host == "backup.example.com"
def test_typeadapter_dump_python_preserves_anyurl():
# In 2.10.5, TypeAdapter(AnyUrl).dump_python() returned str instead of AnyUrl.
# Fixed in 2.10.6: dump_python() returns AnyUrl.
adapter = TypeAdapter(AnyUrl)
url_instance = adapter.validate_python("https://adapter.example.org/feed")
dumped_obj = adapter.dump_python(url_instance)
assert isinstance(dumped_obj, AnyUrl), (
f"Expected AnyUrl from dump_python(), got {type(dumped_obj)}"
)
assert not isinstance(dumped_obj, str), (
"TypeAdapter.dump_python(AnyUrl) must return AnyUrl, not str"
)
assert dumped_obj.host == "adapter.example.org"
def test_typeadapter_union_dump_python_preserves_url():
# In 2.10.5, TypeAdapter(Union[AnyUrl, int]).dump_python() coerced URL member to str.
# Fixed in 2.10.6: dump_python() preserves AnyUrl member in union.
union_adapter = TypeAdapter(Union[AnyUrl, int])
url_in_union = union_adapter.validate_python("https://union.example.org/path")
dumped_union = union_adapter.dump_python(url_in_union)
assert isinstance(dumped_union, AnyUrl), (
f"Expected AnyUrl from union dump_python(), got {type(dumped_union)}"
)
assert not isinstance(dumped_union, str), (
"TypeAdapter.dump_python(Union[AnyUrl, int]) must return AnyUrl, not str"
)
assert dumped_union.host == "union.example.org"
if __name__ == "__main__":
test_model_dump_preserves_anyurl_instance()
test_model_dump_mode_python_preserves_httpurl_attributes()
test_model_dump_preserves_url_in_union_field()
test_typeadapter_dump_python_preserves_anyurl()
test_typeadapter_union_dump_python_preserves_url()
print("All bugfix contract assertions passed successfully.")