patito 0.7.0__py3-none-any.whl → 0.8.2__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.
@@ -90,27 +76,31 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
90
76
 
91
77
  """
92
78
  super().__init__(name, bases, clsdict, **kwargs)
93
- # Add a custom subclass of patito.DataFrame to the model class,
94
- # where .set_model() has been implicitly set.
95
- cls.DataFrame = DataFrame._construct_dataframe_model_class(
96
- model=cls, # type: ignore
79
+ NewDataFrame = type(
80
+ f"{cls.__name__}DataFrame",
81
+ (DataFrame,),
82
+ {"model": cls},
97
83
  )
98
- # Similarly for LazyFrame
99
- cls.LazyFrame = LazyFrame._construct_lazyframe_model_class(
100
- model=cls, # type: ignore
84
+ cls.DataFrame: type[DataFrame[cls]] = NewDataFrame # type: ignore
85
+
86
+ NewLazyFrame = type(
87
+ f"{cls.__name__}LazyFrame",
88
+ (LazyFrame,),
89
+ {"model": cls},
101
90
  )
91
+ cls.LazyFrame: type[LazyFrame[cls]] = NewLazyFrame # type: ignore
102
92
 
103
93
  def __hash__(self) -> int:
104
94
  """Return hash of the model class."""
105
95
  return super().__hash__()
106
96
 
107
97
  @property
108
- def column_infos(cls: Type[ModelType]) -> Mapping[str, ColumnInfo]:
98
+ def column_infos(cls: type[Model]) -> Mapping[str, ColumnInfo]:
109
99
  """Return column information for the model."""
110
100
  return column_infos_for_model(cls)
111
101
 
112
102
  @property
113
- def model_schema(cls: Type[ModelType]) -> Mapping[str, Mapping[str, Any]]:
103
+ def model_schema(cls: type[Model]) -> Mapping[str, Mapping[str, Any]]:
114
104
  """Return schema properties where definition references have been resolved.
115
105
 
116
106
  Returns:
@@ -126,7 +116,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
126
116
  return schema_for_model(cls)
127
117
 
128
118
  @property
129
- def columns(cls: Type[ModelType]) -> List[str]:
119
+ def columns(cls: type[Model]) -> list[str]:
130
120
  """Return the name of the dataframe columns specified by the fields of the model.
131
121
 
132
122
  Returns:
@@ -145,7 +135,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
145
135
  return list(cls.model_fields.keys())
146
136
 
147
137
  @property
148
- def dtypes(cls: Type[ModelType]) -> dict[str, DataTypeClass | DataType]:
138
+ def dtypes(cls: type[Model]) -> dict[str, DataTypeClass | DataType]:
149
139
  """Return the polars dtypes of the dataframe.
150
140
 
151
141
  Unless Field(dtype=...) is specified, the highest signed column dtype
@@ -169,8 +159,8 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
169
159
 
170
160
  @property
171
161
  def valid_dtypes(
172
- cls: Type[ModelType],
173
- ) -> Mapping[str, FrozenSet[DataTypeClass | DataType]]:
162
+ cls: type[Model],
163
+ ) -> Mapping[str, frozenset[DataTypeClass | DataType]]:
174
164
  """Return a list of polars dtypes which Patito considers valid for each field.
175
165
 
176
166
  The first item of each list is the default dtype chosen by Patito.
@@ -186,7 +176,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
186
176
  return valid_dtypes_for_model(cls)
187
177
 
188
178
  @property
189
- def defaults(cls: Type[ModelType]) -> dict[str, Any]:
179
+ def defaults(cls: type[Model]) -> dict[str, Any]:
190
180
  """Return default field values specified on the model.
191
181
 
192
182
  Returns:
@@ -211,7 +201,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
211
201
  }
212
202
 
213
203
  @property
214
- def non_nullable_columns(cls: Type[ModelType]) -> set[str]:
204
+ def non_nullable_columns(cls: type[Model]) -> set[str]:
215
205
  """Return names of those columns that are non-nullable in the schema.
216
206
 
217
207
  Returns:
@@ -235,12 +225,12 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
235
225
  for k in cls.columns
236
226
  if not (
237
227
  is_optional(cls.model_fields[k].annotation)
238
- or cls.model_fields[k].annotation == type(None)
228
+ or cls.model_fields[k].annotation is type(None)
239
229
  )
240
230
  )
241
231
 
242
232
  @property
243
- def nullable_columns(cls: Type[ModelType]) -> set[str]:
233
+ def nullable_columns(cls: type[Model]) -> set[str]:
244
234
  """Return names of those columns that are nullable in the schema.
245
235
 
246
236
  Returns:
@@ -262,7 +252,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
262
252
  return set(cls.columns) - cls.non_nullable_columns
263
253
 
264
254
  @property
265
- def unique_columns(cls: Type[ModelType]) -> set[str]:
255
+ def unique_columns(cls: type[Model]) -> set[str]:
266
256
  """Return columns with uniqueness constraint.
267
257
 
268
258
  Returns:
@@ -285,7 +275,7 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
285
275
  return {column for column in cls.columns if infos[column].unique}
286
276
 
287
277
  @property
288
- def derived_columns(cls: Type[ModelType]) -> set[str]:
278
+ def derived_columns(cls: type[Model]) -> set[str]:
289
279
  """Return set of columns which are derived from other columns."""
290
280
  infos = cls.column_infos
291
281
  return {
@@ -297,7 +287,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
297
287
  """Custom pydantic class for representing table schema and constructing rows."""
298
288
 
299
289
  @classmethod
300
- def validate_schema(cls: Type[ModelType]):
290
+ def validate_schema(cls: type[ModelType]):
301
291
  """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
292
  for column in cls.columns:
303
293
  col_info = cls.column_infos[column]
@@ -311,8 +301,8 @@ class Model(BaseModel, metaclass=ModelMetaclass):
311
301
 
312
302
  @classmethod
313
303
  def from_row(
314
- cls: Type[ModelType],
315
- row: Union["pd.DataFrame", pl.DataFrame],
304
+ cls: type[ModelType],
305
+ row: pd.DataFrame | pl.DataFrame,
316
306
  validate: bool = True,
317
307
  ) -> ModelType:
318
308
  """Represent a single data frame row as a Patito model.
@@ -340,6 +330,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
340
330
  >>> df = pl.DataFrame(
341
331
  ... [["1", "product name", "1.22"]],
342
332
  ... schema=["product_id", "name", "price"],
333
+ ... orient="row",
343
334
  ... )
344
335
  >>> Product.from_row(df)
345
336
  Product(product_id=1, name='product name', price=1.22)
@@ -359,7 +350,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
359
350
 
360
351
  @classmethod
361
352
  def _from_polars(
362
- cls: Type[ModelType],
353
+ cls: type[ModelType],
363
354
  dataframe: pl.DataFrame,
364
355
  validate: bool = True,
365
356
  ) -> ModelType:
@@ -392,6 +383,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
392
383
  >>> df = pl.DataFrame(
393
384
  ... [["1", "product name", "1.22"]],
394
385
  ... schema=["product_id", "name", "price"],
386
+ ... orient="row",
395
387
  ... )
396
388
  >>> Product._from_polars(df)
397
389
  Product(product_id=1, name='product name', price=1.22)
@@ -419,13 +411,13 @@ class Model(BaseModel, metaclass=ModelMetaclass):
419
411
 
420
412
  @classmethod
421
413
  def validate(
422
- cls,
423
- dataframe: Union["pd.DataFrame", pl.DataFrame],
424
- columns: Optional[Sequence[str]] = None,
414
+ cls: type[ModelType],
415
+ dataframe: pd.DataFrame | pl.DataFrame,
416
+ columns: Sequence[str] | None = None,
425
417
  allow_missing_columns: bool = False,
426
418
  allow_superfluous_columns: bool = False,
427
- **kwargs,
428
- ) -> None:
419
+ drop_superfluous_columns: bool = False,
420
+ ) -> DataFrame[ModelType]:
429
421
  """Validate the schema and content of the given dataframe.
430
422
 
431
423
  Args:
@@ -434,10 +426,11 @@ class Model(BaseModel, metaclass=ModelMetaclass):
434
426
  of the dataframe will be validated.
435
427
  allow_missing_columns: If True, missing columns will not be considered an error.
436
428
  allow_superfluous_columns: If True, additional columns will not be considered an error.
437
- **kwargs: Additional keyword arguments to be passed to the validation
429
+ drop_superfluous_columns: If True, columns not present in the model will be
430
+ dropped from the resulting dataframe.
438
431
 
439
432
  Returns:
440
- ``None``:
433
+ DataFrame: A patito DataFrame containing the validated data.
441
434
 
442
435
  Raises:
443
436
  patito.exceptions.DataFrameValidationError: If the given dataframe does not match
@@ -479,15 +472,35 @@ class Model(BaseModel, metaclass=ModelMetaclass):
479
472
  columns=columns,
480
473
  allow_missing_columns=allow_missing_columns,
481
474
  allow_superfluous_columns=allow_superfluous_columns,
482
- **kwargs,
475
+ drop_superfluous_columns=drop_superfluous_columns,
483
476
  )
477
+ return cls.DataFrame(dataframe)
478
+
479
+ @classmethod
480
+ def iter_models(
481
+ cls: type[ModelType], dataframe: pd.DataFrame | pl.DataFrame
482
+ ) -> ModelGenerator[ModelType]:
483
+ """Validate the dataframe and iterate over the rows, yielding Patito models.
484
+
485
+ Args:
486
+ dataframe: Polars or pandas DataFrame to be validated.
487
+
488
+ Returns:
489
+ ListableIterator: An iterator of patito models over the validated data.
490
+
491
+ Raises:
492
+ patito.exceptions.DataFrameValidationError: If the given dataframe does not match
493
+ the given schema.
494
+
495
+ """
496
+ return cls.DataFrame(dataframe).iter_models()
484
497
 
485
498
  @classmethod
486
499
  def example_value( # noqa: C901
487
500
  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]:
501
+ field: str | None = None,
502
+ properties: dict[str, Any] | None = None,
503
+ ) -> date | datetime | time | timedelta | float | int | str | None | Mapping | list:
491
504
  """Return a valid example value for the given model field.
492
505
 
493
506
  Args:
@@ -529,7 +542,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
529
542
  properties = cls._schema_properties()[field]
530
543
  info = cls.column_infos[field]
531
544
  else:
532
- info = cls.column_info_class()
545
+ info = ColumnInfo()
533
546
  properties = properties or {}
534
547
 
535
548
  if "type" in properties:
@@ -611,10 +624,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
611
624
  return date(year=1970, month=1, day=1)
612
625
  elif "format" in properties and properties["format"] == "date-time":
613
626
  if "column_info" in properties:
614
- dtype_str = properties["column_info"]["dtype"]
615
- dtype = dtype_from_string(dtype_str)
627
+ ci = ColumnInfo.model_validate_json(properties["column_info"])
628
+ dtype = ci.dtype
616
629
  if getattr(dtype, "time_zone", None) is not None:
617
- tzinfo = ZoneInfo(dtype.time_zone)
630
+ tzinfo = ZoneInfo(dtype.time_zone) # type: ignore
618
631
  else:
619
632
  tzinfo = None
620
633
  return datetime(year=1970, month=1, day=1, tzinfo=tzinfo)
@@ -650,7 +663,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
650
663
 
651
664
  @classmethod
652
665
  def example(
653
- cls: Type[ModelType],
666
+ cls: type[ModelType],
654
667
  **kwargs: Any, # noqa: ANN401
655
668
  ) -> ModelType:
656
669
  """Produce model instance with filled dummy data for all unspecified fields.
@@ -702,10 +715,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
702
715
 
703
716
  @classmethod
704
717
  def pandas_examples(
705
- cls: Type[ModelType],
706
- data: Union[dict, Iterable],
707
- columns: Optional[Iterable[str]] = None,
708
- ) -> "pd.DataFrame":
718
+ cls: type[ModelType],
719
+ data: dict | Iterable,
720
+ columns: Iterable[str] | None = None,
721
+ ) -> pd.DataFrame:
709
722
  """Generate dataframe with dummy data for all unspecified columns.
710
723
 
711
724
  Offers the same API as the pandas.DataFrame constructor.
@@ -772,10 +785,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
772
785
 
773
786
  @classmethod
774
787
  def examples(
775
- cls: Type[ModelType],
776
- data: Optional[Union[dict, Iterable]] = None,
777
- columns: Optional[Iterable[str]] = None,
778
- ) -> "patito.polars.DataFrame":
788
+ cls: type[ModelType],
789
+ data: dict | Iterable | None = None,
790
+ columns: Iterable[str] | None = None,
791
+ ) -> patito.polars.DataFrame:
779
792
  """Generate polars dataframe with dummy data for all unspecified columns.
780
793
 
781
794
  This constructor accepts the same data format as polars.DataFrame.
@@ -844,7 +857,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
844
857
  if wrong_columns:
845
858
  raise TypeError(f"{cls.__name__} does not contain fields {wrong_columns}!")
846
859
 
847
- series: List[Union[pl.Series, pl.Expr]] = []
860
+ series: list[pl.Series | pl.Expr] = []
848
861
  unique_series = []
849
862
  for column_name, dtype in cls.dtypes.items():
850
863
  if column_name not in kwargs:
@@ -872,10 +885,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
872
885
 
873
886
  @classmethod
874
887
  def join(
875
- cls: Type["Model"],
876
- other: Type["Model"],
888
+ cls: type[Model],
889
+ other: type[Model],
877
890
  how: Literal["inner", "left", "outer", "asof", "cross", "semi", "anti"],
878
- ) -> Type["Model"]:
891
+ ) -> type[Model]:
879
892
  """Dynamically create a new model compatible with an SQL Join operation.
880
893
 
881
894
  For instance, ``ModelA.join(ModelB, how="left")`` will create a model containing
@@ -920,7 +933,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
920
933
  if how in {"semi", "anti"}:
921
934
  return cls
922
935
 
923
- kwargs: Dict[str, Any] = {}
936
+ kwargs: dict[str, Any] = {}
924
937
  for model, nullable_methods in (
925
938
  (cls, {"outer"}),
926
939
  (other, {"left", "outer", "asof"}),
@@ -940,9 +953,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
940
953
  )
941
954
 
942
955
  @classmethod
943
- def select(
944
- cls: Type[ModelType], fields: Union[str, Iterable[str]]
945
- ) -> Type["Model"]:
956
+ def select(cls: type[ModelType], fields: str | Iterable[str]) -> type[Model]:
946
957
  """Create a new model consisting of only a subset of the model fields.
947
958
 
948
959
  Args:
@@ -984,7 +995,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
984
995
  )
985
996
 
986
997
  @classmethod
987
- def drop(cls: Type[ModelType], name: Union[str, Iterable[str]]) -> Type["Model"]:
998
+ def drop(cls: type[ModelType], name: str | Iterable[str]) -> type[Model]:
988
999
  """Return a new model where one or more fields are excluded.
989
1000
 
990
1001
  Args:
@@ -1023,7 +1034,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1023
1034
  )
1024
1035
 
1025
1036
  @classmethod
1026
- def prefix(cls: Type[ModelType], prefix: str) -> Type["Model"]:
1037
+ def prefix(cls: type[ModelType], prefix: str) -> type[Model]:
1027
1038
  """Return a new model where all field names have been prefixed.
1028
1039
 
1029
1040
  Args:
@@ -1049,7 +1060,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1049
1060
  )
1050
1061
 
1051
1062
  @classmethod
1052
- def suffix(cls: Type[ModelType], suffix: str) -> Type["Model"]:
1063
+ def suffix(cls: type[ModelType], suffix: str) -> type[Model]:
1053
1064
  """Return a new model where all field names have been suffixed.
1054
1065
 
1055
1066
  Args:
@@ -1076,7 +1087,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1076
1087
  )
1077
1088
 
1078
1089
  @classmethod
1079
- def rename(cls: Type[ModelType], mapping: Dict[str, str]) -> Type["Model"]:
1090
+ def rename(cls: type[ModelType], mapping: dict[str, str]) -> type[Model]:
1080
1091
  """Return a new model class where the specified fields have been renamed.
1081
1092
 
1082
1093
  Args:
@@ -1117,9 +1128,9 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1117
1128
 
1118
1129
  @classmethod
1119
1130
  def with_fields(
1120
- cls: Type[ModelType],
1131
+ cls: type[ModelType],
1121
1132
  **field_definitions: Any, # noqa: ANN401
1122
- ) -> Type["Model"]:
1133
+ ) -> type[Model]:
1123
1134
  """Return a new model class where the given fields have been added.
1124
1135
 
1125
1136
  Args:
@@ -1152,11 +1163,11 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1152
1163
  )
1153
1164
 
1154
1165
  @classmethod
1155
- def _schema_properties(cls: Type[ModelType]) -> Mapping[str, Any]:
1166
+ def _schema_properties(cls: type[ModelType]) -> Mapping[str, Any]:
1156
1167
  return cls.model_schema["properties"]
1157
1168
 
1158
1169
  @classmethod
1159
- def _update_dfn(cls, annotation: Any, schema: Dict[str, Any]) -> None:
1170
+ def _update_dfn(cls, annotation: Any, schema: dict[str, Any]) -> None:
1160
1171
  try:
1161
1172
  if issubclass(annotation, Model) and annotation.__name__ != cls.__name__:
1162
1173
  schema["$defs"][annotation.__name__] = annotation.model_schema
@@ -1165,10 +1176,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1165
1176
 
1166
1177
  @classmethod
1167
1178
  def _derive_model(
1168
- cls: Type[ModelType],
1179
+ cls: type[ModelType],
1169
1180
  model_name: str,
1170
- field_mapping: Dict[str, Any],
1171
- ) -> Type["Model"]:
1181
+ field_mapping: dict[str, Any],
1182
+ ) -> type[Model]:
1172
1183
  """Derive a new model with new field definitions.
1173
1184
 
1174
1185
  Args:
@@ -1209,7 +1220,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
1209
1220
  def _derive_field(
1210
1221
  field: fields.FieldInfo,
1211
1222
  make_nullable: bool = False,
1212
- ) -> Tuple[Type | None, fields.FieldInfo]:
1223
+ ) -> tuple[type | None, fields.FieldInfo]:
1213
1224
  field_type = field.annotation
1214
1225
  default = field.default
1215
1226
  extra_attrs = {
@@ -1241,8 +1252,8 @@ FIELD_KWARGS = getfullargspec(fields.Field)
1241
1252
  # Helper function for patito Field.
1242
1253
 
1243
1254
 
1244
- def FieldCI(
1245
- column_info: Type[ColumnInfo], *args: Any, **kwargs: Any
1255
+ def Field(
1256
+ *args: Any, **kwargs: Any
1246
1257
  ) -> Any: # annotate with Any to make the downstream type annotations happy
1247
1258
  """Annotate model field with additional type and validation information.
1248
1259
 
@@ -1254,6 +1265,7 @@ def FieldCI(
1254
1265
  can be read with the below examples.
1255
1266
 
1256
1267
  Args:
1268
+ allow_missing (bool): Column may be missing.
1257
1269
  column_info: (Type[ColumnInfo]): ColumnInfo object to pass args to.
1258
1270
  constraints (Union[polars.Expression, List[polars.Expression]): A single
1259
1271
  constraint or list of constraints, expressed as a polars expression objects.
@@ -1313,7 +1325,7 @@ def FieldCI(
1313
1325
  Polars dtype Int64 does not match model field type. (type=type_error.columndtype)
1314
1326
 
1315
1327
  """
1316
- ci = column_info(**kwargs)
1328
+ ci = ColumnInfo(**kwargs)
1317
1329
  for field in ci.model_fields_set:
1318
1330
  kwargs.pop(field)
1319
1331
  if kwargs.pop("modern_kwargs_only", True):
@@ -1322,11 +1334,12 @@ def FieldCI(
1322
1334
  raise ValueError(
1323
1335
  f"unexpected kwarg {kwarg}={kwargs[kwarg]}. Add modern_kwargs_only=False to ignore"
1324
1336
  )
1337
+ ci_json = ci.model_dump_json()
1338
+ existing_json_schema_extra = kwargs.pop("json_schema_extra", {})
1339
+ merged_json_schema_extra = {**existing_json_schema_extra, "column_info": ci_json}
1340
+
1325
1341
  return fields.Field(
1326
1342
  *args,
1327
- json_schema_extra={"column_info": ci},
1343
+ json_schema_extra=merged_json_schema_extra,
1328
1344
  **kwargs,
1329
1345
  )
1330
-
1331
-
1332
- Field = partial(FieldCI, column_info=ColumnInfo)