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/column_info.py +101 -46
- patito/_pydantic/dtypes/dtypes.py +19 -15
- patito/_pydantic/dtypes/utils.py +32 -28
- patito/_pydantic/repr.py +7 -15
- patito/_pydantic/schema.py +10 -9
- patito/exceptions.py +11 -16
- patito/polars.py +124 -65
- patito/pydantic.py +98 -89
- patito/validators.py +111 -71
- {patito-0.6.2.dist-info → patito-0.8.0.dist-info}/METADATA +6 -4
- patito-0.8.0.dist-info/RECORD +17 -0
- {patito-0.6.2.dist-info → patito-0.8.0.dist-info}/WHEEL +1 -1
- patito-0.6.2.dist-info/RECORD +0 -17
- {patito-0.6.2.dist-info → patito-0.8.0.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.
|
|
@@ -105,12 +91,12 @@ class ModelMetaclass(PydanticModelMetaclass, Generic[CI]):
|
|
|
105
91
|
return super().__hash__()
|
|
106
92
|
|
|
107
93
|
@property
|
|
108
|
-
def column_infos(cls:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
173
|
-
) -> Mapping[str,
|
|
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:
|
|
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:
|
|
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
|
|
224
|
+
or cls.model_fields[k].annotation is type(None)
|
|
239
225
|
)
|
|
240
226
|
)
|
|
241
227
|
|
|
242
228
|
@property
|
|
243
|
-
def nullable_columns(cls:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
315
|
-
row:
|
|
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:
|
|
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:
|
|
424
|
-
columns:
|
|
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
|
-
|
|
428
|
-
) ->
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
489
|
-
properties:
|
|
490
|
-
) ->
|
|
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 =
|
|
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
|
-
|
|
615
|
-
dtype =
|
|
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:
|
|
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:
|
|
706
|
-
data:
|
|
707
|
-
columns:
|
|
708
|
-
) ->
|
|
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:
|
|
776
|
-
data:
|
|
777
|
-
columns:
|
|
778
|
-
) ->
|
|
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:
|
|
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:
|
|
876
|
-
other:
|
|
884
|
+
cls: type[Model],
|
|
885
|
+
other: type[Model],
|
|
877
886
|
how: Literal["inner", "left", "outer", "asof", "cross", "semi", "anti"],
|
|
878
|
-
) ->
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
1127
|
+
cls: type[ModelType],
|
|
1121
1128
|
**field_definitions: Any, # noqa: ANN401
|
|
1122
|
-
) ->
|
|
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:
|
|
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:
|
|
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:
|
|
1175
|
+
cls: type[ModelType],
|
|
1169
1176
|
model_name: str,
|
|
1170
|
-
field_mapping:
|
|
1171
|
-
) ->
|
|
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
|
-
|
|
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
|
-
) ->
|
|
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
|
|
1245
|
-
|
|
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 =
|
|
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=
|
|
1339
|
+
json_schema_extra=merged_json_schema_extra,
|
|
1328
1340
|
**kwargs,
|
|
1329
1341
|
)
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
Field = partial(FieldCI, column_info=ColumnInfo)
|