patito 0.6.2__py3-none-any.whl → 0.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
patito/pydantic.py CHANGED
@@ -3,58 +3,46 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import itertools
6
- from collections.abc import Iterable
6
+ from collections.abc import Iterable, Mapping, Sequence
7
7
  from datetime import date, datetime, time, timedelta
8
- from functools import partial
9
8
  from inspect import getfullargspec
10
9
  from typing import (
11
10
  TYPE_CHECKING,
12
11
  Any,
13
12
  ClassVar,
14
- Dict,
15
- FrozenSet,
16
- Generic,
17
- List,
18
13
  Literal,
19
- Mapping,
20
14
  Optional,
21
- Sequence,
22
- Tuple,
23
- Type,
24
15
  TypeVar,
25
- Union,
26
16
  cast,
27
17
  get_args,
28
18
  )
19
+ from zoneinfo import ZoneInfo
29
20
 
30
21
  import polars as pl
31
22
  from polars.datatypes import DataType, DataTypeClass
32
23
  from pydantic import ( # noqa: F401
33
24
  BaseModel,
34
25
  create_model,
35
- field_serializer,
36
26
  fields,
37
27
  )
38
28
  from pydantic._internal._model_construction import (
39
29
  ModelMetaclass as PydanticModelMetaclass,
40
30
  )
41
- from zoneinfo import ZoneInfo
42
31
 
43
- from patito._pydantic.column_info import CI, ColumnInfo
32
+ from patito._pydantic.column_info import ColumnInfo
44
33
  from patito._pydantic.dtypes import (
45
34
  default_dtypes_for_model,
46
- dtype_from_string,
47
35
  is_optional,
48
36
  valid_dtypes_for_model,
49
37
  validate_annotation,
50
38
  validate_polars_dtype,
51
39
  )
52
40
  from patito._pydantic.schema import column_infos_for_model, schema_for_model
53
- from patito.polars import DataFrame, LazyFrame
41
+ from patito.polars import DataFrame, LazyFrame, ModelGenerator
54
42
  from patito.validators import validate
55
43
 
56
44
  try:
57
- import pandas as pd
45
+ import pandas as pd # type: ignore
58
46
 
59
47
  _PANDAS_AVAILABLE = True
60
48
  except ImportError:
@@ -68,16 +56,14 @@ if TYPE_CHECKING:
68
56
  ModelType = TypeVar("ModelType", bound="Model")
69
57
 
70
58
 
71
- class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
59
+ class ModelMetaclass(PydanticModelMetaclass):
72
60
  """Metaclass used by patito.Model.
73
61
 
74
62
  Responsible for setting any relevant model-dependent class properties.
75
63
  """
76
64
 
77
- column_info_class: ClassVar[Type[ColumnInfo]] = ColumnInfo
78
-
79
65
  if TYPE_CHECKING:
80
- model_fields: ClassVar[Dict[str, fields.FieldInfo]]
66
+ model_fields: ClassVar[dict[str, fields.FieldInfo]]
81
67
 
82
68
  def __init__(cls, name: str, bases: tuple, clsdict: dict, **kwargs) -> None:
83
69
  """Construct new patito model.
@@ -105,12 +91,12 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
105
91
  return super().__hash__()
106
92
 
107
93
  @property
108
- def column_infos(cls: Type[ModelType]) -> Mapping[str, ColumnInfo]:
94
+ def column_infos(cls: type[ModelType]) -> Mapping[str, ColumnInfo]:
109
95
  """Return column information for the model."""
110
96
  return column_infos_for_model(cls)
111
97
 
112
98
  @property
113
- def model_schema(cls: Type[ModelType]) -> Mapping[str, Mapping[str, Any]]:
99
+ def model_schema(cls: type[ModelType]) -> Mapping[str, Mapping[str, Any]]:
114
100
  """Return schema properties where definition references have been resolved.
115
101
 
116
102
  Returns:
@@ -126,7 +112,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
126
112
  return schema_for_model(cls)
127
113
 
128
114
  @property
129
- def columns(cls: Type[ModelType]) -> List[str]:
115
+ def columns(cls: type[ModelType]) -> list[str]:
130
116
  """Return the name of the dataframe columns specified by the fields of the model.
131
117
 
132
118
  Returns:
@@ -145,7 +131,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
145
131
  return list(cls.model_fields.keys())
146
132
 
147
133
  @property
148
- def dtypes(cls: Type[ModelType]) -> dict[str, DataTypeClass | DataType]:
134
+ def dtypes(cls: type[ModelType]) -> dict[str, DataTypeClass | DataType]:
149
135
  """Return the polars dtypes of the dataframe.
150
136
 
151
137
  Unless Field(dtype=...) is specified, the highest signed column dtype
@@ -169,8 +155,8 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
169
155
 
170
156
  @property
171
157
  def valid_dtypes(
172
- cls: Type[ModelType],
173
- ) -> Mapping[str, FrozenSet[DataTypeClass | DataType]]:
158
+ cls: type[ModelType],
159
+ ) -> Mapping[str, frozenset[DataTypeClass | DataType]]:
174
160
  """Return a list of polars dtypes which Patito considers valid for each field.
175
161
 
176
162
  The first item of each list is the default dtype chosen by Patito.
@@ -186,7 +172,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
186
172
  return valid_dtypes_for_model(cls)
187
173
 
188
174
  @property
189
- def defaults(cls: Type[ModelType]) -> dict[str, Any]:
175
+ def defaults(cls: type[ModelType]) -> dict[str, Any]:
190
176
  """Return default field values specified on the model.
191
177
 
192
178
  Returns:
@@ -211,7 +197,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
211
197
  }
212
198
 
213
199
  @property
214
- def non_nullable_columns(cls: Type[ModelType]) -> set[str]:
200
+ def non_nullable_columns(cls: type[ModelType]) -> set[str]:
215
201
  """Return names of those columns that are non-nullable in the schema.
216
202
 
217
203
  Returns:
@@ -235,12 +221,12 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
235
221
  for k in cls.columns
236
222
  if not (
237
223
  is_optional(cls.model_fields[k].annotation)
238
- or cls.model_fields[k].annotation == type(None)
224
+ or cls.model_fields[k].annotation is type(None)
239
225
  )
240
226
  )
241
227
 
242
228
  @property
243
- def nullable_columns(cls: Type[ModelType]) -> set[str]:
229
+ def nullable_columns(cls: type[ModelType]) -> set[str]:
244
230
  """Return names of those columns that are nullable in the schema.
245
231
 
246
232
  Returns:
@@ -262,7 +248,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
262
248
  return set(cls.columns) - cls.non_nullable_columns
263
249
 
264
250
  @property
265
- def unique_columns(cls: Type[ModelType]) -> set[str]:
251
+ def unique_columns(cls: type[ModelType]) -> set[str]:
266
252
  """Return columns with uniqueness constraint.
267
253
 
268
254
  Returns:
@@ -285,7 +271,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
285
271
  return {column for column in cls.columns if infos[column].unique}
286
272
 
287
273
  @property
288
- def derived_columns(cls: Type[ModelType]) -> set[str]:
274
+ def derived_columns(cls: type[ModelType]) -> set[str]:
289
275
  """Return set of columns which are derived from other columns."""
290
276
  infos = cls.column_infos
291
277
  return {
@@ -297,7 +283,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
297
283
  """Custom pydantic class for representing table schema and constructing rows."""
298
284
 
299
285
  @classmethod
300
- def validate_schema(cls: Type[ModelType]):
286
+ def validate_schema(cls: type[ModelType]):
301
287
  """Users should run this after defining or edit a model. We withhold the checks at model definition time to avoid expensive queries of the model schema."""
302
288
  for column in cls.columns:
303
289
  col_info = cls.column_infos[column]
@@ -311,8 +297,8 @@ class Model(BaseModel, metaclass=ModelMetaclass):
311
297
 
312
298
  @classmethod
313
299
  def from_row(
314
- cls: Type[ModelType],
315
- row: Union["pd.DataFrame", pl.DataFrame],
300
+ cls: type[ModelType],
301
+ row: pd.DataFrame | pl.DataFrame,
316
302
  validate: bool = True,
317
303
  ) -> ModelType:
318
304
  """Represent a single data frame row as a Patito model.
@@ -340,6 +326,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
340
326
  >>> df = pl.DataFrame(
341
327
  ... [["1", "product name", "1.22"]],
342
328
  ... schema=["product_id", "name", "price"],
329
+ ... orient="row",
343
330
  ... )
344
331
  >>> Product.from_row(df)
345
332
  Product(product_id=1, name='product name', price=1.22)
@@ -359,7 +346,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
359
346
 
360
347
  @classmethod
361
348
  def _from_polars(
362
- cls: Type[ModelType],
349
+ cls: type[ModelType],
363
350
  dataframe: pl.DataFrame,
364
351
  validate: bool = True,
365
352
  ) -> ModelType:
@@ -392,6 +379,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
392
379
  >>> df = pl.DataFrame(
393
380
  ... [["1", "product name", "1.22"]],
394
381
  ... schema=["product_id", "name", "price"],
382
+ ... orient="row",
395
383
  ... )
396
384
  >>> Product._from_polars(df)
397
385
  Product(product_id=1, name='product name', price=1.22)
@@ -419,13 +407,13 @@ class Model(BaseModel, metaclass=ModelMetaclass):
419
407
 
420
408
  @classmethod
421
409
  def validate(
422
- cls,
423
- dataframe: Union["pd.DataFrame", pl.DataFrame],
424
- columns: Optional[Sequence[str]] = None,
410
+ cls: type[ModelType],
411
+ dataframe: pd.DataFrame | pl.DataFrame,
412
+ columns: Sequence[str] | None = None,
425
413
  allow_missing_columns: bool = False,
426
414
  allow_superfluous_columns: bool = False,
427
- **kwargs,
428
- ) -> None:
415
+ drop_superfluous_columns: bool = False,
416
+ ) -> DataFrame[ModelType]:
429
417
  """Validate the schema and content of the given dataframe.
430
418
 
431
419
  Args:
@@ -434,10 +422,11 @@ class Model(BaseModel, metaclass=ModelMetaclass):
434
422
  of the dataframe will be validated.
435
423
  allow_missing_columns: If True, missing columns will not be considered an error.
436
424
  allow_superfluous_columns: If True, additional columns will not be considered an error.
437
- **kwargs: Additional keyword arguments to be passed to the validation
425
+ drop_superfluous_columns: If True, columns not present in the model will be
426
+ dropped from the resulting dataframe.
438
427
 
439
428
  Returns:
440
- ``None``:
429
+ DataFrame: A patito DataFrame containing the validated data.
441
430
 
442
431
  Raises:
443
432
  patito.exceptions.DataFrameValidationError: If the given dataframe does not match
@@ -479,15 +468,35 @@ class Model(BaseModel, metaclass=ModelMetaclass):
479
468
  columns=columns,
480
469
  allow_missing_columns=allow_missing_columns,
481
470
  allow_superfluous_columns=allow_superfluous_columns,
482
- **kwargs,
471
+ drop_superfluous_columns=drop_superfluous_columns,
483
472
  )
473
+ return cls.DataFrame(dataframe)
474
+
475
+ @classmethod
476
+ def iter_models(
477
+ cls: type[ModelType], dataframe: pd.DataFrame | pl.DataFrame
478
+ ) -> ModelGenerator[ModelType]:
479
+ """Validate the dataframe and iterate over the rows, yielding Patito models.
480
+
481
+ Args:
482
+ dataframe: Polars or pandas DataFrame to be validated.
483
+
484
+ Returns:
485
+ ListableIterator: An iterator of patito models over the validated data.
486
+
487
+ Raises:
488
+ patito.exceptions.DataFrameValidationError: If the given dataframe does not match
489
+ the given schema.
490
+
491
+ """
492
+ return cls.DataFrame(dataframe).iter_models()
484
493
 
485
494
  @classmethod
486
495
  def example_value( # noqa: C901
487
496
  cls,
488
- field: Optional[str] = None,
489
- properties: Optional[Dict[str, Any]] = None,
490
- ) -> Union[date, datetime, time, timedelta, float, int, str, None, Mapping, List]:
497
+ field: str | None = None,
498
+ properties: dict[str, Any] | None = None,
499
+ ) -> date | datetime | time | timedelta | float | int | str | None | Mapping | list:
491
500
  """Return a valid example value for the given model field.
492
501
 
493
502
  Args:
@@ -529,7 +538,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
529
538
  properties = cls._schema_properties()[field]
530
539
  info = cls.column_infos[field]
531
540
  else:
532
- info = cls.column_info_class()
541
+ info = ColumnInfo()
533
542
  properties = properties or {}
534
543
 
535
544
  if "type" in properties:
@@ -611,10 +620,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
611
620
  return date(year=1970, month=1, day=1)
612
621
  elif "format" in properties and properties["format"] == "date-time":
613
622
  if "column_info" in properties:
614
- dtype_str = properties["column_info"]["dtype"]
615
- dtype = dtype_from_string(dtype_str)
623
+ ci = ColumnInfo.model_validate_json(properties["column_info"])
624
+ dtype = ci.dtype
616
625
  if getattr(dtype, "time_zone", None) is not None:
617
- tzinfo = ZoneInfo(dtype.time_zone)
626
+ tzinfo = ZoneInfo(dtype.time_zone) # type: ignore
618
627
  else:
619
628
  tzinfo = None
620
629
  return datetime(year=1970, month=1, day=1, tzinfo=tzinfo)
@@ -650,7 +659,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
650
659
 
651
660
  @classmethod
652
661
  def example(
653
- cls: Type[ModelType],
662
+ cls: type[ModelType],
654
663
  **kwargs: Any, # noqa: ANN401
655
664
  ) -> ModelType:
656
665
  """Produce model instance with filled dummy data for all unspecified fields.
@@ -702,10 +711,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
702
711
 
703
712
  @classmethod
704
713
  def pandas_examples(
705
- cls: Type[ModelType],
706
- data: Union[dict, Iterable],
707
- columns: Optional[Iterable[str]] = None,
708
- ) -> "pd.DataFrame":
714
+ cls: type[ModelType],
715
+ data: dict | Iterable,
716
+ columns: Iterable[str] | None = None,
717
+ ) -> pd.DataFrame:
709
718
  """Generate dataframe with dummy data for all unspecified columns.
710
719
 
711
720
  Offers the same API as the pandas.DataFrame constructor.
@@ -772,10 +781,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
772
781
 
773
782
  @classmethod
774
783
  def examples(
775
- cls: Type[ModelType],
776
- data: Optional[Union[dict, Iterable]] = None,
777
- columns: Optional[Iterable[str]] = None,
778
- ) -> "patito.polars.DataFrame":
784
+ cls: type[ModelType],
785
+ data: dict | Iterable | None = None,
786
+ columns: Iterable[str] | None = None,
787
+ ) -> patito.polars.DataFrame:
779
788
  """Generate polars dataframe with dummy data for all unspecified columns.
780
789
 
781
790
  This constructor accepts the same data format as polars.DataFrame.
@@ -844,7 +853,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
844
853
  if wrong_columns:
845
854
  raise TypeError(f"{cls.__name__} does not contain fields {wrong_columns}!")
846
855
 
847
- series: List[Union[pl.Series, pl.Expr]] = []
856
+ series: list[pl.Series | pl.Expr] = []
848
857
  unique_series = []
849
858
  for column_name, dtype in cls.dtypes.items():
850
859
  if column_name not in kwargs:
@@ -872,10 +881,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
872
881
 
873
882
  @classmethod
874
883
  def join(
875
- cls: Type["Model"],
876
- other: Type["Model"],
884
+ cls: type[Model],
885
+ other: type[Model],
877
886
  how: Literal["inner", "left", "outer", "asof", "cross", "semi", "anti"],
878
- ) -> Type["Model"]:
887
+ ) -> type[Model]:
879
888
  """Dynamically create a new model compatible with an SQL Join operation.
880
889
 
881
890
  For instance, ``ModelA.join(ModelB, how="left")`` will create a model containing
@@ -920,7 +929,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
920
929
  if how in {"semi", "anti"}:
921
930
  return cls
922
931
 
923
- kwargs: Dict[str, Any] = {}
932
+ kwargs: dict[str, Any] = {}
924
933
  for model, nullable_methods in (
925
934
  (cls, {"outer"}),
926
935
  (other, {"left", "outer", "asof"}),
@@ -940,9 +949,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
940
949
  )
941
950
 
942
951
  @classmethod
943
- def select(
944
- cls: Type[ModelType], fields: Union[str, Iterable[str]]
945
- ) -> Type["Model"]:
952
+ def select(cls: type[ModelType], fields: str | Iterable[str]) -> type[Model]:
946
953
  """Create a new model consisting of only a subset of the model fields.
947
954
 
948
955
  Args:
@@ -984,7 +991,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
984
991
  )
985
992
 
986
993
  @classmethod
987
- def drop(cls: Type[ModelType], name: Union[str, Iterable[str]]) -> Type["Model"]:
994
+ def drop(cls: type[ModelType], name: str | Iterable[str]) -> type[Model]:
988
995
  """Return a new model where one or more fields are excluded.
989
996
 
990
997
  Args:
@@ -1023,7 +1030,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1023
1030
  )
1024
1031
 
1025
1032
  @classmethod
1026
- def prefix(cls: Type[ModelType], prefix: str) -> Type["Model"]:
1033
+ def prefix(cls: type[ModelType], prefix: str) -> type[Model]:
1027
1034
  """Return a new model where all field names have been prefixed.
1028
1035
 
1029
1036
  Args:
@@ -1049,7 +1056,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1049
1056
  )
1050
1057
 
1051
1058
  @classmethod
1052
- def suffix(cls: Type[ModelType], suffix: str) -> Type["Model"]:
1059
+ def suffix(cls: type[ModelType], suffix: str) -> type[Model]:
1053
1060
  """Return a new model where all field names have been suffixed.
1054
1061
 
1055
1062
  Args:
@@ -1076,7 +1083,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1076
1083
  )
1077
1084
 
1078
1085
  @classmethod
1079
- def rename(cls: Type[ModelType], mapping: Dict[str, str]) -> Type["Model"]:
1086
+ def rename(cls: type[ModelType], mapping: dict[str, str]) -> type[Model]:
1080
1087
  """Return a new model class where the specified fields have been renamed.
1081
1088
 
1082
1089
  Args:
@@ -1117,9 +1124,9 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1117
1124
 
1118
1125
  @classmethod
1119
1126
  def with_fields(
1120
- cls: Type[ModelType],
1127
+ cls: type[ModelType],
1121
1128
  **field_definitions: Any, # noqa: ANN401
1122
- ) -> Type["Model"]:
1129
+ ) -> type[Model]:
1123
1130
  """Return a new model class where the given fields have been added.
1124
1131
 
1125
1132
  Args:
@@ -1152,11 +1159,11 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1152
1159
  )
1153
1160
 
1154
1161
  @classmethod
1155
- def _schema_properties(cls: Type[ModelType]) -> Mapping[str, Any]:
1162
+ def _schema_properties(cls: type[ModelType]) -> Mapping[str, Any]:
1156
1163
  return cls.model_schema["properties"]
1157
1164
 
1158
1165
  @classmethod
1159
- def _update_dfn(cls, annotation: Any, schema: Dict[str, Any]) -> None:
1166
+ def _update_dfn(cls, annotation: Any, schema: dict[str, Any]) -> None:
1160
1167
  try:
1161
1168
  if issubclass(annotation, Model) and annotation.__name__ != cls.__name__:
1162
1169
  schema["$defs"][annotation.__name__] = annotation.model_schema
@@ -1165,10 +1172,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1165
1172
 
1166
1173
  @classmethod
1167
1174
  def _derive_model(
1168
- cls: Type[ModelType],
1175
+ cls: type[ModelType],
1169
1176
  model_name: str,
1170
- field_mapping: Dict[str, Any],
1171
- ) -> Type["Model"]:
1177
+ field_mapping: dict[str, Any],
1178
+ ) -> type[Model]:
1172
1179
  """Derive a new model with new field definitions.
1173
1180
 
1174
1181
  Args:
@@ -1200,7 +1207,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1200
1207
  field_type = Optional[field_type]
1201
1208
  new_fields[new_field_name] = (field_type, field_definition[1])
1202
1209
  return create_model( # type: ignore
1203
- __model_name=model_name,
1210
+ model_name,
1204
1211
  __base__=Model,
1205
1212
  **new_fields,
1206
1213
  )
@@ -1209,7 +1216,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1209
1216
  def _derive_field(
1210
1217
  field: fields.FieldInfo,
1211
1218
  make_nullable: bool = False,
1212
- ) -> Tuple[Type | None, fields.FieldInfo]:
1219
+ ) -> tuple[type | None, fields.FieldInfo]:
1213
1220
  field_type = field.annotation
1214
1221
  default = field.default
1215
1222
  extra_attrs = {
@@ -1241,8 +1248,8 @@ FIELD_KWARGS = getfullargspec(fields.Field)
1241
1248
  # Helper function for patito Field.
1242
1249
 
1243
1250
 
1244
- def FieldCI(
1245
- column_info: Type[ColumnInfo], *args: Any, **kwargs: Any
1251
+ def Field(
1252
+ *args: Any, **kwargs: Any
1246
1253
  ) -> Any: # annotate with Any to make the downstream type annotations happy
1247
1254
  """Annotate model field with additional type and validation information.
1248
1255
 
@@ -1254,6 +1261,7 @@ def FieldCI(
1254
1261
  can be read with the below examples.
1255
1262
 
1256
1263
  Args:
1264
+ allow_missing (bool): Column may be missing.
1257
1265
  column_info: (Type[ColumnInfo]): ColumnInfo object to pass args to.
1258
1266
  constraints (Union[polars.Expression, List[polars.Expression]): A single
1259
1267
  constraint or list of constraints, expressed as a polars expression objects.
@@ -1313,7 +1321,7 @@ def FieldCI(
1313
1321
  Polars dtype Int64 does not match model field type. (type=type_error.columndtype)
1314
1322
 
1315
1323
  """
1316
- ci = column_info(**kwargs)
1324
+ ci = ColumnInfo(**kwargs)
1317
1325
  for field in ci.model_fields_set:
1318
1326
  kwargs.pop(field)
1319
1327
  if kwargs.pop("modern_kwargs_only", True):
@@ -1322,11 +1330,12 @@ def FieldCI(
1322
1330
  raise ValueError(
1323
1331
  f"unexpected kwarg {kwarg}={kwargs[kwarg]}. Add modern_kwargs_only=False to ignore"
1324
1332
  )
1333
+ ci_json = ci.model_dump_json()
1334
+ existing_json_schema_extra = kwargs.pop("json_schema_extra", {})
1335
+ merged_json_schema_extra = {**existing_json_schema_extra, "column_info": ci_json}
1336
+
1325
1337
  return fields.Field(
1326
1338
  *args,
1327
- json_schema_extra={"column_info": ci},
1339
+ json_schema_extra=merged_json_schema_extra,
1328
1340
  **kwargs,
1329
1341
  )
1330
-
1331
-
1332
- Field = partial(FieldCI, column_info=ColumnInfo)