|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
| 3 | +import copy |
3 | 4 | import decimal |
4 | 5 | import re |
5 | 6 | from functools import partial |
6 | 7 | from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Optional, Pattern |
7 | 8 |
|
8 | 9 | import attrs |
| 10 | +import pydantic |
9 | 11 |
|
10 | 12 | from .. import errors, settings |
11 | 13 | from ..exception import FrictionlessException |
12 | 14 | from ..metadata import Metadata |
13 | 15 | from ..system import system |
| 16 | +from .field_descriptor import BooleanFieldDescriptor, FieldDescriptor |
14 | 17 |
|
15 | 18 | if TYPE_CHECKING: |
16 | 19 | from ..types import IDescriptor |
|
22 | 25 | class Field(Metadata): |
23 | 26 | """Field representation""" |
24 | 27 |
|
| 28 | + _descriptor: Optional[FieldDescriptor] = None |
| 29 | + |
25 | 30 | name: str |
26 | 31 | """ |
27 | 32 | A short url-usable (and preferably human-readable) name. |
@@ -50,9 +55,7 @@ class Field(Metadata): |
50 | 55 | For example: "default","array" etc. |
51 | 56 | """ |
52 | 57 |
|
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) |
56 | 59 | """ |
57 | 60 | List of string values to be set as missing values in the field. If any of string in missing values |
58 | 61 | is found in the field value then it is set as None. |
@@ -154,6 +157,8 @@ def cell_reader(cell: Any): |
154 | 157 | def create_value_reader(self) -> types.IValueReader: |
155 | 158 | # Create reader |
156 | 159 | def value_reader(cell: Any): |
| 160 | + if self._descriptor and isinstance(self._descriptor, BooleanFieldDescriptor): |
| 161 | + return self._descriptor.read_value(cell) |
157 | 162 | return cell |
158 | 163 |
|
159 | 164 | return value_reader |
@@ -192,6 +197,8 @@ def cell_writer(cell: Any, *, ignore_missing: bool = False): |
192 | 197 | def create_value_writer(self) -> types.IValueWriter: |
193 | 198 | # Create writer |
194 | 199 | def value_writer(cell: Any): |
| 200 | + if self._descriptor and isinstance(self._descriptor, BooleanFieldDescriptor): |
| 201 | + return self._descriptor.write_value(cell) |
195 | 202 | return str(cell) |
196 | 203 |
|
197 | 204 | return value_writer |
@@ -244,6 +251,39 @@ def metadata_transform(cls, descriptor: IDescriptor): |
244 | 251 | if format and isinstance(format, str) and format.startswith("fmt:"): |
245 | 252 | descriptor["format"] = format.replace("fmt:", "") |
246 | 253 |
|
| 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 | + |
247 | 287 | @classmethod |
248 | 288 | def metadata_validate(cls, descriptor: IDescriptor): # type: ignore |
249 | 289 | metadata_errors = list(super().metadata_validate(descriptor)) |
@@ -276,9 +316,7 @@ def metadata_validate(cls, descriptor: IDescriptor): # type: ignore |
276 | 316 | field.false_values = descriptor["falseValues"] |
277 | 317 | _, notes = field.read_cell(example) |
278 | 318 | 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' |
282 | 320 | yield errors.FieldError(note=note) |
283 | 321 |
|
284 | 322 | # Misleading |
|
0 commit comments