88from pathlib import Path
99from pydantic import BaseModel , Field , model_validator , create_model , BeforeValidator
1010
11- _complex_type_validator = BeforeValidator (lambda x : (x .real ,x .imag ) if isinstance (x ,complex ) else x )
11+ _complex_type_validator = BeforeValidator (
12+ lambda x : (x .real , x .imag ) if isinstance (x , complex ) else x
13+ )
1214
13- ComplexType = Annotated [
14- tuple [float ,float ],
15- _complex_type_validator
16- ]
15+ ComplexType = Annotated [tuple [float , float ], _complex_type_validator ]
16+
17+ NullableComplexType = Annotated [tuple [float , float ] | None , _complex_type_validator ]
1718
18- NullableComplexType = Annotated [
19- tuple [float ,float ] | None ,
20- _complex_type_validator
21- ]
2219
2320class FileFormat (StrEnum ):
2421 """Define known file formats for autogeneration of schemae."""
@@ -27,25 +24,25 @@ class FileFormat(StrEnum):
2724 JSON = "json"
2825 JSONL = "jsonl"
2926
27+
3028class SchemaGenerator (BaseModel ):
3129 """Automatically infer a dataset schema and create a pydantic model from it."""
3230
33- file_name : str | Path = Field (
34- description = "The path to the dataset."
35- )
31+ file_name : str | Path = Field (description = "The path to the dataset." )
3632
37- fmt : FileFormat | None = Field (
38- None , description = "The dataset file format. If no format is provided, it will be inferred."
33+ fmt : FileFormat | None = Field (
34+ None ,
35+ description = "The dataset file format. If no format is provided, it will be inferred." ,
3936 )
4037
4138 @model_validator (mode = "before" )
42- def check_format (cls , config : dict [str ,Any ]) -> dict [str ,Any ]:
43-
44- if isinstance (fp := config ["file_name" ],str ):
39+ def check_format (cls , config : dict [str , Any ]) -> dict [str , Any ]:
40+
41+ if isinstance (fp := config ["file_name" ], str ):
4542 config ["file_name" ] = Path (fp ).resolve ()
46-
43+
4744 if config .get ("fmt" ):
48- if isinstance (config ["fmt" ],str ):
45+ if isinstance (config ["fmt" ], str ):
4946 if config ["fmt" ] in FileFormat .__members__ :
5047 config ["fmt" ] = FileFormat [config ["fmt" ]]
5148 else :
@@ -58,7 +55,9 @@ def check_format(cls, config : dict[str,Any]) -> dict[str,Any]:
5855 else :
5956 try :
6057 config ["fmt" ] = next (
61- file_fmt for file_fmt in FileFormat if file_fmt .value in config ["file_name" ].name
58+ file_fmt
59+ for file_fmt in FileFormat
60+ if file_fmt .value in config ["file_name" ].name
6261 )
6362 except StopIteration :
6463 raise ValueError (
@@ -67,17 +66,17 @@ def check_format(cls, config : dict[str,Any]) -> dict[str,Any]:
6766 return config
6867
6968 @staticmethod
70- def _cast_dtype (dtype , assume_nullable : bool = True ):
69+ def _cast_dtype (dtype , assume_nullable : bool = True ):
7170 """Cast input dtype to parquet-friendly dtypes.
7271
73- Accounts for difficulties de-serializing datetimes
72+ Accounts for difficulties de-serializing datetimes
7473 and complex numbers.
7574
7675 Assumes all fields are nullable by default.
7776 """
78- vname = getattr (dtype ,"name" ,str (dtype )).lower ()
77+ vname = getattr (dtype , "name" , str (dtype )).lower ()
7978
80- if any (spec_type in vname for spec_type in ("datetime" ,"complex" )):
79+ if any (spec_type in vname for spec_type in ("datetime" , "complex" )):
8180 if "datetime" in vname :
8281 return NullableDateTimeType if assume_nullable else DateTimeType
8382 elif "complex" in vname :
@@ -88,7 +87,7 @@ def _cast_dtype(dtype, assume_nullable : bool = True):
8887 inferred_type = float
8988 elif "int" in vname :
9089 inferred_type = int
91-
90+
9291 return inferred_type | None if assume_nullable else inferred_type
9392
9493 @property
@@ -97,30 +96,34 @@ def pydantic_schema(self) -> Type[BaseModel]:
9796
9897 if self .fmt == "csv" :
9998 data = pd .read_csv (self .file_name )
100-
101- elif self .fmt in {"json" ,"jsonl" }:
99+
100+ elif self .fmt in {"json" , "jsonl" }:
102101 # we exclude the "table" case for `orient` since the user
103102 # presumably already knows what the schema is.
104- for orient in ("columns" ,"index" ,"records" ,"split" ,"values" ):
103+ for orient in ("columns" , "index" , "records" , "split" , "values" ):
105104 try :
106- data = pd .read_json (self .file_name , orient = orient , lines = self .fmt == "jsonl" )
105+ data = pd .read_json (
106+ self .file_name , orient = orient , lines = self .fmt == "jsonl"
107+ )
107108 break
108109 except Exception as exc :
109110 continue
110111 else :
111112 raise ValueError (
112113 f"Could not load { self .fmt .value } data, please check manually."
113114 )
114-
115+
115116 model_fields = {
116- col_name : (
117+ col_name : (
117118 self ._cast_dtype (data .dtypes [col_name ]),
118- Field (default = None ,)
119+ Field (
120+ default = None ,
121+ ),
119122 )
120123 for col_name in data .columns
121124 }
122125
123126 return create_model (
124127 f"{ self .file_name .name .split ("." ,1 )[0 ]} " ,
125128 ** model_fields ,
126- )
129+ )
0 commit comments