Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions frictionless/detector/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def detect_schema(

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

# Deduplicate names
if len(names) != len(set(names)):
Expand Down Expand Up @@ -360,10 +360,11 @@ def detect_schema(
field.float_number = True # type: ignore
elif field.type == "boolean":
if self.field_true_values != settings.DEFAULT_TRUE_VALUES:
field.true_values = self.field_true_values # type: ignore
field._descriptor.true_values = self.field_true_values # type: ignore
if self.field_false_values != settings.DEFAULT_FALSE_VALUES:
field.false_values = self.field_false_values # type: ignore
field._descriptor.false_values = self.field_false_values # type: ignore
runner_fields.append(field)

for index, name in enumerate(names):
runners.append([])
for field in runner_fields:
Expand Down
30 changes: 1 addition & 29 deletions frictionless/fields/boolean.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import Any, Dict, List
from typing import List

import attrs

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

# Read

def create_value_reader(self):
# Create mapping
mapping: Dict[str, bool] = {}
for value in self.true_values:
mapping[value] = True
for value in self.false_values:
mapping[value] = False

# Create reader
def value_reader(cell: Any):
if cell is True or cell is False:
return cell
if isinstance(cell, str):
return mapping.get(cell)

return value_reader

# Write

def create_value_writer(self):
# Create writer
def value_writer(cell: Any):
return self.true_values[0] if cell else self.false_values[0]

return value_writer

# Metadata

metadata_profile_patch = {
Expand Down
50 changes: 44 additions & 6 deletions frictionless/schema/field.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
from __future__ import annotations

import copy
import decimal
import re
from functools import partial
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Optional, Pattern

import attrs
import pydantic

from .. import errors, settings
from ..exception import FrictionlessException
from ..metadata import Metadata
from ..system import system
from .field_descriptor import BooleanFieldDescriptor, FieldDescriptor

if TYPE_CHECKING:
from ..types import IDescriptor
Expand All @@ -22,6 +25,8 @@
class Field(Metadata):
"""Field representation"""

_descriptor: Optional[FieldDescriptor] = None

name: str
"""
A short url-usable (and preferably human-readable) name.
Expand Down Expand Up @@ -50,9 +55,7 @@ class Field(Metadata):
For example: "default","array" etc.
"""

missing_values: List[str] = attrs.field(
factory=settings.DEFAULT_MISSING_VALUES.copy
)
missing_values: List[str] = attrs.field(factory=settings.DEFAULT_MISSING_VALUES.copy)
"""
List of string values to be set as missing values in the field. If any of string in missing values
is found in the field value then it is set as None.
Expand Down Expand Up @@ -154,6 +157,8 @@ def cell_reader(cell: Any):
def create_value_reader(self) -> types.IValueReader:
# Create reader
def value_reader(cell: Any):
if self._descriptor and isinstance(self._descriptor, BooleanFieldDescriptor):
return self._descriptor.read_value(cell)
return cell

return value_reader
Expand Down Expand Up @@ -192,6 +197,8 @@ def cell_writer(cell: Any, *, ignore_missing: bool = False):
def create_value_writer(self) -> types.IValueWriter:
# Create writer
def value_writer(cell: Any):
if self._descriptor and isinstance(self._descriptor, BooleanFieldDescriptor):
return self._descriptor.write_value(cell)
return str(cell)

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

@classmethod
def metadata_import(
cls,
descriptor: IDescriptor,
*,
with_basepath: bool = False,
**options: Any,
) -> "Field":
descriptor_copy = copy.deepcopy(descriptor)
field = super().metadata_import(
descriptor,
with_basepath=with_basepath,
)

if field.type == "boolean":
try:
field._descriptor = BooleanFieldDescriptor.model_validate(descriptor_copy)
except pydantic.ValidationError as ve:
error = errors.SchemaError(note=str(ve))
raise FrictionlessException(error)

return field

def to_descriptor(self, *, validate: bool = False) -> IDescriptor:
if self._descriptor and isinstance(self._descriptor, BooleanFieldDescriptor):
descr = self._descriptor.model_dump(exclude_none=True, exclude_unset=True)
## Temporarily, Field properties have priority over
## Field._descriptor properties
descr = {**descr, **super().to_descriptor(validate=validate)}
return descr
else:
return super().to_descriptor(validate=validate)

@classmethod
def metadata_validate(cls, descriptor: IDescriptor): # type: ignore
metadata_errors = list(super().metadata_validate(descriptor))
Expand Down Expand Up @@ -276,9 +316,7 @@ def metadata_validate(cls, descriptor: IDescriptor): # type: ignore
field.false_values = descriptor["falseValues"]
_, notes = field.read_cell(example)
if notes is not None:
note = (
f'example value "{example}" for field "{field.name}" is not valid'
)
note = f'example value "{example}" for field "{field.name}" is not valid'
yield errors.FieldError(note=note)

# Misleading
Expand Down
33 changes: 33 additions & 0 deletions frictionless/schema/field_constraints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""field_constraints.py provide pydantic Models for constraints"""

from typing import Any, Dict, Generic, List, Optional, TypeVar, Union

import pydantic

T = TypeVar("T")


class BaseConstraints(pydantic.BaseModel, Generic[T]):
required: Optional[bool] = None
unique: Optional[bool] = None
enum: Optional[List[Union[str, T]]] = None


class CollectionConstraints(BaseConstraints[str]):
minLength: Optional[int] = None
maxLength: Optional[int] = None


class JSONConstraints(CollectionConstraints):
jsonSchema: Optional[Dict[str, Any]] = None


class StringConstraints(CollectionConstraints):
pattern: Optional[str] = None


class ValueConstraints(BaseConstraints[T], Generic[T]):
minimum: Optional[Union[str, T]] = None
maximum: Optional[Union[str, T]] = None
exclusiveMinimum: Optional[Union[str, T]] = None
exclusiveMaximum: Optional[Union[str, T]] = None
Loading
Loading