Skip to content

Commit 265557f

Browse files
BooleanField independant of Metadata
1 parent 1d4866e commit 265557f

File tree

5 files changed

+445
-38
lines changed

5 files changed

+445
-38
lines changed

frictionless/detector/detector.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ def detect_schema(
329329

330330
# Handle name/empty
331331
for index, name in enumerate(names):
332-
names[index] = name or f"field{index+1}"
332+
names[index] = name or f"field{index + 1}"
333333

334334
# Deduplicate names
335335
if len(names) != len(set(names)):
@@ -360,10 +360,11 @@ def detect_schema(
360360
field.float_number = True # type: ignore
361361
elif field.type == "boolean":
362362
if self.field_true_values != settings.DEFAULT_TRUE_VALUES:
363-
field.true_values = self.field_true_values # type: ignore
363+
field._descriptor.true_values = self.field_true_values # type: ignore
364364
if self.field_false_values != settings.DEFAULT_FALSE_VALUES:
365-
field.false_values = self.field_false_values # type: ignore
365+
field._descriptor.false_values = self.field_false_values # type: ignore
366366
runner_fields.append(field)
367+
367368
for index, name in enumerate(names):
368369
runners.append([])
369370
for field in runner_fields:

frictionless/fields/boolean.py

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import Any, Dict, List
3+
from typing import List
44

55
import attrs
66

@@ -29,34 +29,6 @@ class BooleanField(Field):
2929
true values are ["false", "False", "FALSE", "0"].
3030
"""
3131

32-
# Read
33-
34-
def create_value_reader(self):
35-
# Create mapping
36-
mapping: Dict[str, bool] = {}
37-
for value in self.true_values:
38-
mapping[value] = True
39-
for value in self.false_values:
40-
mapping[value] = False
41-
42-
# Create reader
43-
def value_reader(cell: Any):
44-
if cell is True or cell is False:
45-
return cell
46-
if isinstance(cell, str):
47-
return mapping.get(cell)
48-
49-
return value_reader
50-
51-
# Write
52-
53-
def create_value_writer(self):
54-
# Create writer
55-
def value_writer(cell: Any):
56-
return self.true_values[0] if cell else self.false_values[0]
57-
58-
return value_writer
59-
6032
# Metadata
6133

6234
metadata_profile_patch = {

frictionless/schema/field.py

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
from __future__ import annotations
22

3+
import copy
34
import decimal
45
import re
56
from functools import partial
67
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Optional, Pattern
78

89
import attrs
10+
import pydantic
911

1012
from .. import errors, settings
1113
from ..exception import FrictionlessException
1214
from ..metadata import Metadata
1315
from ..system import system
16+
from .field_descriptor import BooleanFieldDescriptor, FieldDescriptor
1417

1518
if TYPE_CHECKING:
1619
from ..types import IDescriptor
@@ -22,6 +25,8 @@
2225
class Field(Metadata):
2326
"""Field representation"""
2427

28+
_descriptor: Optional[FieldDescriptor] = None
29+
2530
name: str
2631
"""
2732
A short url-usable (and preferably human-readable) name.
@@ -50,9 +55,7 @@ class Field(Metadata):
5055
For example: "default","array" etc.
5156
"""
5257

53-
missing_values: List[str] = attrs.field(
54-
factory=settings.DEFAULT_MISSING_VALUES.copy
55-
)
58+
missing_values: List[str] = attrs.field(factory=settings.DEFAULT_MISSING_VALUES.copy)
5659
"""
5760
List of string values to be set as missing values in the field. If any of string in missing values
5861
is found in the field value then it is set as None.
@@ -154,6 +157,8 @@ def cell_reader(cell: Any):
154157
def create_value_reader(self) -> types.IValueReader:
155158
# Create reader
156159
def value_reader(cell: Any):
160+
if self._descriptor and isinstance(self._descriptor, BooleanFieldDescriptor):
161+
return self._descriptor.read_value(cell)
157162
return cell
158163

159164
return value_reader
@@ -192,6 +197,8 @@ def cell_writer(cell: Any, *, ignore_missing: bool = False):
192197
def create_value_writer(self) -> types.IValueWriter:
193198
# Create writer
194199
def value_writer(cell: Any):
200+
if self._descriptor and isinstance(self._descriptor, BooleanFieldDescriptor):
201+
return self._descriptor.write_value(cell)
195202
return str(cell)
196203

197204
return value_writer
@@ -244,6 +251,39 @@ def metadata_transform(cls, descriptor: IDescriptor):
244251
if format and isinstance(format, str) and format.startswith("fmt:"):
245252
descriptor["format"] = format.replace("fmt:", "")
246253

254+
@classmethod
255+
def metadata_import(
256+
cls,
257+
descriptor: IDescriptor,
258+
*,
259+
with_basepath: bool = False,
260+
**options: Any,
261+
) -> "Field":
262+
descriptor_copy = copy.deepcopy(descriptor)
263+
field = super().metadata_import(
264+
descriptor,
265+
with_basepath=with_basepath,
266+
)
267+
268+
if field.type == "boolean":
269+
try:
270+
field._descriptor = BooleanFieldDescriptor.model_validate(descriptor_copy)
271+
except pydantic.ValidationError as ve:
272+
error = errors.SchemaError(note=str(ve))
273+
raise FrictionlessException(error)
274+
275+
return field
276+
277+
def to_descriptor(self, *, validate: bool = False) -> IDescriptor:
278+
if self._descriptor and isinstance(self._descriptor, BooleanFieldDescriptor):
279+
descr = self._descriptor.model_dump(exclude_none=True, exclude_unset=True)
280+
## Temporarily, Field properties have priority over
281+
## Field._descriptor properties
282+
descr = {**descr, **super().to_descriptor(validate=validate)}
283+
return descr
284+
else:
285+
return super().to_descriptor(validate=validate)
286+
247287
@classmethod
248288
def metadata_validate(cls, descriptor: IDescriptor): # type: ignore
249289
metadata_errors = list(super().metadata_validate(descriptor))
@@ -276,9 +316,7 @@ def metadata_validate(cls, descriptor: IDescriptor): # type: ignore
276316
field.false_values = descriptor["falseValues"]
277317
_, notes = field.read_cell(example)
278318
if notes is not None:
279-
note = (
280-
f'example value "{example}" for field "{field.name}" is not valid'
281-
)
319+
note = f'example value "{example}" for field "{field.name}" is not valid'
282320
yield errors.FieldError(note=note)
283321

284322
# Misleading
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""field_constraints.py provide pydantic Models for constraints"""
2+
3+
from typing import Any, Dict, Generic, List, Optional, TypeVar, Union
4+
5+
import pydantic
6+
7+
T = TypeVar("T")
8+
9+
10+
class BaseConstraints(pydantic.BaseModel, Generic[T]):
11+
required: Optional[bool] = None
12+
unique: Optional[bool] = None
13+
enum: Optional[List[Union[str, T]]] = None
14+
15+
16+
class CollectionConstraints(BaseConstraints[str]):
17+
minLength: Optional[int] = None
18+
maxLength: Optional[int] = None
19+
20+
21+
class JSONConstraints(CollectionConstraints):
22+
jsonSchema: Optional[Dict[str, Any]] = None
23+
24+
25+
class StringConstraints(CollectionConstraints):
26+
pattern: Optional[str] = None
27+
28+
29+
class ValueConstraints(BaseConstraints[T], Generic[T]):
30+
minimum: Optional[Union[str, T]] = None
31+
maximum: Optional[Union[str, T]] = None
32+
exclusiveMinimum: Optional[Union[str, T]] = None
33+
exclusiveMaximum: Optional[Union[str, T]] = None

0 commit comments

Comments
 (0)