diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index dc306471dbd3f..404d3820cad02 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -127,7 +127,7 @@ Other enhancements - Many read/to_* functions, such as :meth:`DataFrame.to_pickle` and :func:`read_csv`, support forwarding compression arguments to lzma.LZMAFile (:issue:`52979`) - Performance improvement in :func:`concat` with homogeneous ``np.float64`` or ``np.float32`` dtypes (:issue:`52685`) - Performance improvement in :meth:`DataFrame.filter` when ``items`` is given (:issue:`52941`) -- +- Support Pydantic V2 protocol for :class:`Series` (:issue:`54034`) .. --------------------------------------------------------------------------- .. _whatsnew_210.notable_bug_fixes: diff --git a/environment.yml b/environment.yml index 8e3c3a26ffc0f..3b62561403ad0 100644 --- a/environment.yml +++ b/environment.yml @@ -45,6 +45,7 @@ dependencies: - py - psycopg2>=2.9.3 - pyarrow>=7.0.0 + - pydantic>=2.0 - pymysql>=1.0.2 - pyreadstat>=1.1.5 - pytables>=3.7.0 diff --git a/pandas/core/series.py b/pandas/core/series.py index 2fc926d7e43d1..eddbf7c072bfb 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -19,6 +19,7 @@ Union, cast, overload, + Type ) import warnings import weakref @@ -37,6 +38,7 @@ ) from pandas._libs.lib import is_range_indexer from pandas.compat import PYPY +from pandas.compat._optional import import_optional_dependency from pandas.compat.numpy import function as nv from pandas.errors import ( ChainedAssignmentError, @@ -148,6 +150,10 @@ import pandas.plotting if TYPE_CHECKING: + import pydantic_core + from pydantic import GetCoreSchemaHandler + from pydantic_core.core_schema import CoreSchema + from pandas._libs.internals import BlockValuesRefs from pandas._typing import ( AggFuncType, @@ -6215,3 +6221,15 @@ def cumsum(self, axis: Axis | None = None, skipna: bool = True, *args, **kwargs) @doc(make_doc("cumprod", 1)) def cumprod(self, axis: Axis | None = None, skipna: bool = True, *args, **kwargs): return NDFrame.cumprod(self, axis, skipna, *args, **kwargs) + + @classmethod + def __get_pydantic_core_schema__(cls, source_type: Type[Any], handler: GetCoreSchemaHandler) -> CoreSchema: + pyd_core = cast("pydantic_core", import_optional_dependency("pydantic_core")) + core_schema = pyd_core.core_schema + + return core_schema.union_schema( + [ + core_schema.is_instance_schema(Series), + core_schema.no_info_plain_validator_function(Series) + ] + ) diff --git a/pandas/tests/test_downstream.py b/pandas/tests/test_downstream.py index 7354e313e24f4..b3c3f595e7fed 100644 --- a/pandas/tests/test_downstream.py +++ b/pandas/tests/test_downstream.py @@ -274,3 +274,23 @@ def __radd__(self, other): assert right.__add__(left) is NotImplemented assert right + left is left + + +@td.skip_if_no("pydantic") +@td.skip_if_no("pydantic_core") +@pytest.mark.parametrize("series", [ + Series([1, 2, 3]), + Series(np.array([1, 2, 3])), + Series({'a': 1, 'b': 2, 'c': 3}), + Series(3), +]) +def test_pydantic_protocol(series: Series) -> None: + from pydantic import BaseModel + + class Model(BaseModel): + series: Series + + + model = Model(series=series) + assert model.model_dump() == {} + assert model.model_json_schema() == {}