diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4e6faa8f97..0eedb9d5c0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,17 +56,14 @@ jobs: - run: uv run python -c 'import docs.plugins.main' - # Adding local symlinks gets nice source locations like - # pydantic_core/core_schema.py - # instead of - # .venv/lib/python3.10/site-packages/pydantic_core/core_schema.py + # Taken from docs-build.sh - name: prepare shortcuts for extra modules run: | ln -s .venv/lib/python*/site-packages/pydantic_core pydantic_core ln -s .venv/lib/python*/site-packages/pydantic_settings pydantic_settings ln -s .venv/lib/python*/site-packages/pydantic_extra_types pydantic_extra_types - - run: uv run mkdocs build + - run: PYTHONPATH="$PWD${PYTHONPATH:+:${PYTHONPATH}}" uv run mkdocs build test-memray: name: Test memray diff --git a/.github/workflows/docs-update.yml b/.github/workflows/docs-update.yml index abfc5d502c3..f90fc99fb40 100644 --- a/.github/workflows/docs-update.yml +++ b/.github/workflows/docs-update.yml @@ -77,10 +77,7 @@ jobs: - run: uv run python -c 'import docs.plugins.main' - # Adding local symlinks gets nice source locations like - # pydantic_core/core_schema.py - # instead of - # .venv/lib/python3.10/site-packages/pydantic_core/core_schema.py + # Taken from docs-build.sh - name: Prepare shortcuts for extra modules run: | ln -s .venv/lib/python*/site-packages/pydantic_core pydantic_core @@ -92,7 +89,7 @@ jobs: git config --global user.name "${{ github.actor }}" git config --global user.email "${{ github.actor }}@users.noreply.github.com" - - run: uv run mike deploy -b docs-site dev --push + - run: PYTHONPATH="$PWD${PYTHONPATH:+:${PYTHONPATH}}" uv run mike deploy -b docs-site dev --push if: github.ref == 'refs/heads/main' - if: github.ref == 'refs/heads/docs-update' || startsWith(github.ref, 'refs/tags/') @@ -102,7 +99,7 @@ jobs: version_file_path: 'pydantic/version.py' skip_env_check: true - - run: uv run mike deploy -b docs-site ${{ steps.check-version.outputs.VERSION_MAJOR_MINOR }} latest --update-aliases --push + - run: PYTHONPATH="$PWD${PYTHONPATH:+:${PYTHONPATH}}" uv run mike deploy -b docs-site ${{ steps.check-version.outputs.VERSION_MAJOR_MINOR }} latest --update-aliases --push if: ${{ (github.ref == 'refs/heads/docs-update' || startsWith(github.ref, 'refs/tags/')) && !fromJSON(steps.check-version.outputs.IS_PRERELEASE) }} env: PYDANTIC_VERSION: v${{ steps.check-version.outputs.VERSION }} diff --git a/CITATION.cff b/CITATION.cff index 926c024d2fc..2436b75e8a9 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -44,5 +44,5 @@ keywords: - hints - typing license: MIT -version: v2.11.2 -date-released: 2025-04-03 +version: v2.11.3 +date-released: 2025-04-08 diff --git a/HISTORY.md b/HISTORY.md index 490bba375a4..25550b7c5d3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,17 @@ +## v2.11.3 (2025-04-08) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.3) + +### What's Changed + +#### Packaging + +* Update V1 copy to v1.10.21 by @Viicos in [#11706](https://github.com/pydantic/pydantic/pull/11706) + +#### Fixes + +* Preserve field description when rebuilding model fields by @Viicos in [#11698](https://github.com/pydantic/pydantic/pull/11698) + ## v2.11.2 (2025-04-03) [GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.2) @@ -1927,6 +1941,28 @@ First pre-release of Pydantic V2! See [this post](https://docs.pydantic.dev/blog/pydantic-v2-alpha/) for more details. +## v1.10.21 (2025-01-10) + +* Fix compatibility with ForwardRef._evaluate and Python < 3.12.4 by @griels in https://github.com/pydantic/pydantic/pull/11232 + +## v1.10.20 (2025-01-07) + +This release provides proper support for Python 3.13, with (Cythonized) wheels published for this version. +As a consequence, Cython was updated from `0.29.x` to `3.0.x`. + +* General maintenance of CI and build ecosystem by @Viicos in https://github.com/pydantic/pydantic/pull/10847 + - Update Cython to `3.0.x`. + - Properly address Python 3.13 deprecation warnings. + - Migrate packaging to `pyproject.toml`, make use of PEP 517 build options. + - Use [`build`](https://pypi.org/project/build/) instead of direct `setup.py` invocations. + - Update various Github Actions versions. +* Replace outdated stpmex link in documentation by @jaredenorris in https://github.com/pydantic/pydantic/pull/10997 + +## v1.10.19 (2024-11-06) + +* Add warning when v2 model is nested in v1 model by @sydney-runkle in https://github.com/pydantic/pydantic/pull/10432 +* Fix deprecation warning in V1 `isinstance` check by @alicederyn in https://github.com/pydantic/pydantic/pull/10645 + ## v1.10.19 (2024-11-06) * Add warning when v2 model is nested in v1 model by @sydney-runkle in https://github.com/pydantic/pydantic/pull/10432 diff --git a/build-docs.sh b/build-docs.sh index 85cbba93d38..7b4b2d94dd0 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -16,8 +16,10 @@ python3 -m uv run python -c 'import docs.plugins.main' # pydantic_core/core_schema.py # instead of # .venv/lib/python3.10/site-packages/pydantic_core/core_schema.py +# See also: mkdocs.yml:mkdocstrings:handlers:python:paths: [.]: ln -s .venv/lib/python*/site-packages/pydantic_core pydantic_core ln -s .venv/lib/python*/site-packages/pydantic_settings pydantic_settings ln -s .venv/lib/python*/site-packages/pydantic_extra_types pydantic_extra_types - -python3 -m uv run --no-sync mkdocs build +# Put these at the front of PYTHONPATH (otherwise, symlinked +# entries will still have "Source code in .venv/lib/.../*.py ": +PYTHONPATH="$PWD${PYTHONPATH:+:${PYTHONPATH}}" python3 -m uv run --no-sync mkdocs build diff --git a/docs/concepts/fields.md b/docs/concepts/fields.md index dba44f6220b..f43f28b9351 100644 --- a/docs/concepts/fields.md +++ b/docs/concepts/fields.md @@ -937,6 +937,10 @@ print(Box.model_json_schema(mode='serialization')) """ ``` +1. If not specified, [`computed_field`][pydantic.fields.computed_field] will implicitly convert the method + to a [`property`][]. However, it is preferable to explicitly use the [`@property`][property] decorator + for type checking purposes. + Here's an example using the `model_dump` method with a computed field: ```python @@ -949,7 +953,7 @@ class Box(BaseModel): depth: float @computed_field - @property # (1)! + @property def volume(self) -> float: return self.width * self.height * self.depth @@ -959,10 +963,6 @@ print(b.model_dump()) #> {'width': 1.0, 'height': 2.0, 'depth': 3.0, 'volume': 6.0} ``` -1. If not specified, [`computed_field`][pydantic.fields.computed_field] will implicitly convert the method - to a [`property`][]. However, it is preferable to explicitly use the [`@property`][property] decorator - for type checking purposes. - As with regular fields, computed fields can be marked as being deprecated: ```python diff --git a/mkdocs.yml b/mkdocs.yml index c51497bbb84..9b4445ad028 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -244,7 +244,7 @@ plugins: - mkdocstrings: handlers: python: - paths: [.] + paths: [.] # see also: build-docs.sh options: members_order: source separate_signature: true diff --git a/pydantic/_internal/_fields.py b/pydantic/_internal/_fields.py index 65116067f6e..e9a7995c112 100644 --- a/pydantic/_internal/_fields.py +++ b/pydantic/_internal/_fields.py @@ -319,6 +319,7 @@ def rebuild_model_fields( if field_info._complete: rebuilt_fields[f_name] = field_info else: + existing_desc = field_info.description ann = _typing_extra.eval_type( field_info._original_annotation, *ns_resolver.types_namespace, @@ -326,11 +327,12 @@ def rebuild_model_fields( ann = _generics.replace_types(ann, typevars_map) if (assign := field_info._original_assignment) is PydanticUndefined: - rebuilt_fields[f_name] = FieldInfo_.from_annotation(ann, _source=AnnotationSource.CLASS) + new_field = FieldInfo_.from_annotation(ann, _source=AnnotationSource.CLASS) else: - rebuilt_fields[f_name] = FieldInfo_.from_annotated_attribute( - ann, assign, _source=AnnotationSource.CLASS - ) + new_field = FieldInfo_.from_annotated_attribute(ann, assign, _source=AnnotationSource.CLASS) + # The description might come from the docstring if `use_attribute_docstrings` was `True`: + new_field.description = new_field.description if new_field.description is not None else existing_desc + rebuilt_fields[f_name] = new_field return rebuilt_fields diff --git a/pydantic/v1/main.py b/pydantic/v1/main.py index 68af6f55444..8000967eafc 100644 --- a/pydantic/v1/main.py +++ b/pydantic/v1/main.py @@ -282,6 +282,12 @@ def is_untouched(v: Any) -> bool: cls = super().__new__(mcs, name, bases, new_namespace, **kwargs) # set __signature__ attr only for model class, but not for its instances cls.__signature__ = ClassAttribute('__signature__', generate_model_signature(cls.__init__, fields, config)) + + if not _is_base_model_class_defined: + # Cython does not understand the `if TYPE_CHECKING:` condition in the + # BaseModel's body (where annotations are set), so clear them manually: + getattr(cls, '__annotations__', {}).clear() + if resolve_forward_refs: cls.__try_update_forward_refs__() diff --git a/pydantic/v1/typing.py b/pydantic/v1/typing.py index 7dd341ce0cc..3038ccda4c8 100644 --- a/pydantic/v1/typing.py +++ b/pydantic/v1/typing.py @@ -58,7 +58,7 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: return type_._evaluate(globalns, localns) -else: +elif sys.version_info < (3, 12, 4): def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: # Even though it is the right signature for python 3.9, mypy complains with @@ -67,6 +67,13 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: # TypeError: ForwardRef._evaluate() missing 1 required keyword-only argument: 'recursive_guard' return cast(Any, type_)._evaluate(globalns, localns, recursive_guard=set()) +else: + + def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: + # Pydantic 1.x will not support PEP 695 syntax, but provide `type_params` to avoid + # warnings: + return cast(Any, type_)._evaluate(globalns, localns, type_params=(), recursive_guard=set()) + if sys.version_info < (3, 9): # Ensure we always get all the whole `Annotated` hint, not just the annotated type. diff --git a/pydantic/v1/utils.py b/pydantic/v1/utils.py index 0bd238ee44d..02543fd1370 100644 --- a/pydantic/v1/utils.py +++ b/pydantic/v1/utils.py @@ -159,7 +159,7 @@ def sequence_like(v: Any) -> bool: return isinstance(v, (list, tuple, set, frozenset, GeneratorType, deque)) -def validate_field_name(bases: List[Type['BaseModel']], field_name: str) -> None: +def validate_field_name(bases: Iterable[Type[Any]], field_name: str) -> None: """ Ensure that the field's name does not shadow an existing attribute of the model. """ @@ -708,6 +708,8 @@ def is_valid_field(name: str) -> bool: '__orig_bases__', '__orig_class__', '__qualname__', + '__firstlineno__', + '__static_attributes__', } diff --git a/pydantic/v1/version.py b/pydantic/v1/version.py index 47be0b7573a..c77cde126f3 100644 --- a/pydantic/v1/version.py +++ b/pydantic/v1/version.py @@ -1,6 +1,6 @@ __all__ = 'compiled', 'VERSION', 'version_info' -VERSION = '1.10.19' +VERSION = '1.10.21' try: import cython # type: ignore diff --git a/pydantic/version.py b/pydantic/version.py index d04d26fbd20..e09d751eca3 100644 --- a/pydantic/version.py +++ b/pydantic/version.py @@ -6,7 +6,7 @@ __all__ = 'VERSION', 'version_info' -VERSION = '2.11.2' +VERSION = '2.11.3' """The version of Pydantic.""" diff --git a/tests/test_fields.py b/tests/test_fields.py index 963d091578e..1a50ff2a45d 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -170,3 +170,21 @@ class Model(BaseModel): field: str = Field(coerce_numbers_to_str=True) assert Model(field=number).field == str(number) + + +def test_rebuild_model_fields_preserves_description() -> None: + """https://github.com/pydantic/pydantic/issues/11696""" + + class Model(BaseModel): + model_config = ConfigDict(use_attribute_docstrings=True) + + f: 'Int' + """test doc""" + + assert Model.model_fields['f'].description == 'test doc' + + Int = int + + Model.model_rebuild() + + assert Model.model_fields['f'].description == 'test doc'