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/column_info.py +100 -45
- patito/_pydantic/dtypes/dtypes.py +44 -36
- patito/_pydantic/dtypes/utils.py +30 -27
- patito/_pydantic/repr.py +7 -15
- patito/_pydantic/schema.py +10 -9
- patito/exceptions.py +11 -16
- patito/polars.py +191 -118
- patito/pydantic.py +108 -95
- patito/validators.py +111 -71
- {patito-0.7.0.dist-info → patito-0.8.2.dist-info}/METADATA +4 -3
- patito-0.8.2.dist-info/RECORD +17 -0
- {patito-0.7.0.dist-info → patito-0.8.2.dist-info}/WHEEL +1 -1
- patito-0.7.0.dist-info/RECORD +0 -17
- {patito-0.7.0.dist-info → patito-0.8.2.dist-info}/LICENSE +0 -0
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
|
|
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
|
|
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[
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
model
|
|
79
|
+
NewDataFrame = type(
|
|
80
|
+
f"{cls.__name__}DataFrame",
|
|
81
|
+
(DataFrame,),
|
|
82
|
+
{"model": cls},
|
|
97
83
|
)
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
173
|
-
) -> Mapping[str,
|
|
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:
|
|
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:
|
|
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
|
|
228
|
+
or cls.model_fields[k].annotation is type(None)
|
|
239
229
|
)
|
|
240
230
|
)
|
|
241
231
|
|
|
242
232
|
@property
|
|
243
|
-
def nullable_columns(cls:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
315
|
-
row:
|
|
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:
|
|
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:
|
|
424
|
-
columns:
|
|
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
|
-
|
|
428
|
-
) ->
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
489
|
-
properties:
|
|
490
|
-
) ->
|
|
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 =
|
|
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
|
-
|
|
615
|
-
dtype =
|
|
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:
|
|
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:
|
|
706
|
-
data:
|
|
707
|
-
columns:
|
|
708
|
-
) ->
|
|
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:
|
|
776
|
-
data:
|
|
777
|
-
columns:
|
|
778
|
-
) ->
|
|
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:
|
|
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:
|
|
876
|
-
other:
|
|
888
|
+
cls: type[Model],
|
|
889
|
+
other: type[Model],
|
|
877
890
|
how: Literal["inner", "left", "outer", "asof", "cross", "semi", "anti"],
|
|
878
|
-
) ->
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
1131
|
+
cls: type[ModelType],
|
|
1121
1132
|
**field_definitions: Any, # noqa: ANN401
|
|
1122
|
-
) ->
|
|
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:
|
|
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:
|
|
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:
|
|
1179
|
+
cls: type[ModelType],
|
|
1169
1180
|
model_name: str,
|
|
1170
|
-
field_mapping:
|
|
1171
|
-
) ->
|
|
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
|
-
) ->
|
|
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
|
|
1245
|
-
|
|
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 =
|
|
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=
|
|
1343
|
+
json_schema_extra=merged_json_schema_extra,
|
|
1328
1344
|
**kwargs,
|
|
1329
1345
|
)
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
Field = partial(FieldCI, column_info=ColumnInfo)
|