Skip to content

Commit

Permalink
Merge pull request #426 from fyntex/task/COMPCLDATA-201-update-marshm…
Browse files Browse the repository at this point in the history
…allow-to-major-version-3

chore(deps): Bump marshmallow from 2.21.0 to 3.19.0
  • Loading branch information
svillegas-cdd committed Feb 8, 2023
2 parents 871426b + 6ff9db7 commit ea5f5e6
Show file tree
Hide file tree
Showing 10 changed files with 382 additions and 369 deletions.
85 changes: 54 additions & 31 deletions cl_sii/extras/mm_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
(for serializers)
"""
from __future__ import annotations


try:
import marshmallow
except ImportError as exc: # pragma: no cover
raise ImportError("Package 'marshmallow' is required to use this module.") from exc

import datetime
from typing import Optional
from typing import Any, Mapping, Optional

import marshmallow.fields

Expand Down Expand Up @@ -46,13 +49,18 @@ class RutField(marshmallow.fields.Field):

default_error_messages = {
'invalid': 'Not a syntactically valid RUT.',
'type': 'Invalid type.',
}

def _serialize(self, value: Optional[object], attr: str, obj: object) -> Optional[str]:
def _serialize(
self, value: Optional[object], attr: str | None, obj: object, **kwargs: Any
) -> Optional[str]:
validated = self._validated(value)
return validated.canonical if validated is not None else None

def _deserialize(self, value: str, attr: str, data: dict) -> Optional[Rut]:
def _deserialize(
self, value: str, attr: str | None, data: Mapping[str, Any] | None, **kwargs: Any
) -> Optional[Rut]:
return self._validated(value)

def _validated(self, value: Optional[object]) -> Optional[Rut]:
Expand All @@ -61,10 +69,10 @@ def _validated(self, value: Optional[object]) -> Optional[Rut]:
else:
try:
validated = Rut(value, validate_dv=False) # type: ignore
except TypeError:
self.fail('type')
except ValueError:
self.fail('invalid')
except TypeError as exc:
raise self.make_error('type') from exc
except ValueError as exc:
raise self.make_error('invalid') from exc
return validated


Expand All @@ -89,13 +97,18 @@ class TipoDteField(marshmallow.fields.Field):

default_error_messages = {
'invalid': 'Not a valid Tipo DTE.',
'type': 'Invalid type.',
}

def _serialize(self, value: Optional[object], attr: str, obj: object) -> Optional[int]:
def _serialize(
self, value: Optional[object], attr: str | None, obj: object, **kwargs: Any
) -> Optional[int]:
validated: Optional[TipoDte] = self._validated(value)
return validated.value if validated is not None else None

def _deserialize(self, value: object, attr: str, data: dict) -> Optional[TipoDte]:
def _deserialize(
self, value: object, attr: str | None, data: Mapping[str, Any] | None, **kwargs: Any
) -> Optional[TipoDte]:
return self._validated(value)

def _validated(self, value: Optional[object]) -> Optional[TipoDte]:
Expand All @@ -104,21 +117,21 @@ def _validated(self, value: Optional[object]) -> Optional[TipoDte]:
else:
if isinstance(value, bool):
# is value is bool, `isinstance(value, int)` is True and `int(value)` works!
self.fail('type')
raise self.make_error('type')
try:
value = int(value) # type: ignore
except ValueError:
except ValueError as exc:
# `int('x')` raises 'ValueError', not 'TypeError'
self.fail('type')
except TypeError:
raise self.make_error('type') from exc
except TypeError as exc:
# `int(date(2018, 10, 10))` raises 'TypeError', unlike `int('x')`
self.fail('type')
raise self.make_error('type') from exc

try:
validated = TipoDte(value) # type: ignore
except ValueError:
except ValueError as exc:
# TipoDte('x') raises 'ValueError', not 'TypeError'
self.fail('invalid')
raise self.make_error('invalid') from exc
return validated


Expand All @@ -142,13 +155,18 @@ class RcvTipoDoctoField(marshmallow.fields.Field):

default_error_messages = {
'invalid': "Not a valid RCV's Tipo de Documento.",
'type': "Invalid type.",
}

def _serialize(self, value: Optional[object], attr: str, obj: object) -> Optional[int]:
def _serialize(
self, value: Optional[object], attr: str | None, obj: object, **kwargs: Any
) -> Optional[int]:
validated: Optional[RcvTipoDocto] = self._validated(value)
return validated.value if validated is not None else None

def _deserialize(self, value: object, attr: str, data: dict) -> Optional[RcvTipoDocto]:
def _deserialize(
self, value: object, attr: str | None, data: Mapping[str, Any] | None, **kwargs: Any
) -> Optional[RcvTipoDocto]:
return self._validated(value)

def _validated(self, value: Optional[object]) -> Optional[RcvTipoDocto]:
Expand All @@ -157,21 +175,21 @@ def _validated(self, value: Optional[object]) -> Optional[RcvTipoDocto]:
else:
if isinstance(value, bool):
# is value is bool, `isinstance(value, int)` is True and `int(value)` works!
self.fail('type')
raise self.make_error('type')
try:
value = int(value) # type: ignore
except ValueError:
except ValueError as exc:
# `int('x')` raises 'ValueError', not 'TypeError'
self.fail('type')
except TypeError:
raise self.make_error('type') from exc
except TypeError as exc:
# `int(date(2018, 10, 10))` raises 'TypeError', unlike `int('x')`
self.fail('type')
raise self.make_error('type') from exc

try:
validated = RcvTipoDocto(value) # type: ignore
except ValueError:
except ValueError as exc:
# RcvTipoDocto('x') raises 'ValueError', not 'TypeError'
self.fail('invalid')
raise self.make_error('invalid') from exc
return validated


Expand All @@ -186,14 +204,19 @@ class RcvPeriodoTributarioField(marshmallow.fields.Field):

default_error_messages = {
'invalid': "Not a valid RCV Periodo Tributario.",
'type': "Invalid type.",
}
_string_format = '%Y-%m' # Example: '2019-12'

def _serialize(self, value: Optional[object], attr: str, obj: object) -> Optional[str]:
def _serialize(
self, value: Optional[object], attr: str | None, obj: object, **kwargs: Any
) -> Optional[str]:
validated: Optional[RcvPeriodoTributario] = self._validated(value)
return validated.as_date().strftime(self._string_format) if validated is not None else None

def _deserialize(self, value: object, attr: str, data: dict) -> Optional[RcvPeriodoTributario]:
def _deserialize(
self, value: object, attr: str | None, data: Mapping[str, Any] | None, **kwargs: Any
) -> Optional[RcvPeriodoTributario]:
return self._validated(value)

def _validated(self, value: Optional[object]) -> Optional[RcvPeriodoTributario]:
Expand All @@ -203,10 +226,10 @@ def _validated(self, value: Optional[object]) -> Optional[RcvPeriodoTributario]:
try:
value = datetime.datetime.strptime(value, self._string_format) # type: ignore
value = value.date()
except ValueError:
self.fail('invalid')
except TypeError:
self.fail('type')
except ValueError as exc:
raise self.make_error('invalid') from exc
except TypeError as exc:
raise self.make_error('type') from exc

validated = RcvPeriodoTributario.from_date(value) # type: ignore

Expand Down
38 changes: 20 additions & 18 deletions cl_sii/libs/mm_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from datetime import date, datetime
from typing import Any, Optional, Union
from typing import Any, Mapping, Optional, Union

import marshmallow
import marshmallow.fields
Expand All @@ -22,10 +24,6 @@ def validate_no_unexpected_input_fields(
Usage::
class MySchema(marshmallow.Schema):
class Meta:
strict = True
folio = marshmallow.fields.Integer()
@marshmallow.validates_schema(pass_original=True)
Expand All @@ -36,7 +34,7 @@ def validate_schema(self, data: dict, original_data: dict) -> None:
# Original inspiration from
# https://marshmallow.readthedocs.io/en/2.x-line/extending.html#validating-original-input-data
fields_name_or_load_from = {
field.name if field.load_from is None else field.load_from
field.name if field.data_key is None else field.data_key
for field_key, field in schema.fields.items()
}
unexpected_input_fields = set(original_data) - fields_name_or_load_from
Expand Down Expand Up @@ -98,41 +96,45 @@ def __init__(self, format: Optional[str] = None, **kwargs: Any) -> None:
# TODO: for 'marshmallow 3', rename 'dateformat' to 'datetimeformat'.
self.dateformat = format

def _add_to_schema(self, field_name: str, schema: marshmallow.Schema) -> None:
super()._add_to_schema(field_name, schema)
def _bind_to_schema(self, field_name: str, schema: marshmallow.Schema) -> None:
super()._bind_to_schema(field_name, schema)
self.dateformat = self.dateformat or schema.opts.dateformat

def _serialize(self, value: date, attr: str, obj: object) -> Union[str, None]:
def _serialize(
self, value: date, attr: str | None, obj: object, **kwargs: Any
) -> Union[str, None]:
if value is None:
return None
self.dateformat = self.dateformat or self.DEFAULT_FORMAT
format_func = self.DATEFORMAT_SERIALIZATION_FUNCS.get(self.dateformat, None)
if format_func:
try:
date_str = format_func(value)
except (AttributeError, ValueError):
self.fail('format', input=value)
except (AttributeError, ValueError) as exc:
raise self.make_error('format', input=value) from exc
else:
date_str = value.strftime(self.dateformat)

return date_str

def _deserialize(self, value: str, attr: str, data: dict) -> date:
def _deserialize(
self, value: str, attr: str | None, data: Mapping[str, Any] | None, **kwargs: Any
) -> date:
if not value: # Falsy values, e.g. '', None, [] are not valid
self.fail('invalid')
raise self.make_error('invalid')
self.dateformat = self.dateformat or self.DEFAULT_FORMAT
func = self.DATEFORMAT_DESERIALIZATION_FUNCS.get(self.dateformat)
if func:
try:
date_value = func(value) # type: date
except (TypeError, AttributeError, ValueError):
self.fail('invalid')
except (TypeError, AttributeError, ValueError) as exc:
raise self.make_error('invalid') from exc
elif self.dateformat:
try:
date_value = datetime.strptime(value, self.dateformat).date()
except (TypeError, AttributeError, ValueError):
self.fail('invalid')
except (TypeError, AttributeError, ValueError) as exc:
raise self.make_error('invalid') from exc
else:
self.fail('invalid')
raise self.make_error('invalid')

return date_value
26 changes: 1 addition & 25 deletions cl_sii/libs/rows_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,36 +122,12 @@ def rows_mm_deserialization_iterator(
row_data.pop(_field_name, None)

try:
mm_result: marshmallow.UnmarshalResult = row_schema.load(row_data)
deserialized_row_data: dict = mm_result.data
deserialized_row_data: dict = row_schema.load(row_data)
raised_validation_errors: dict = {}
returned_validation_errors: dict = mm_result.errors
except marshmallow.ValidationError as exc:
deserialized_row_data = {}
raised_validation_errors = dict(exc.normalized_messages())
returned_validation_errors = {}

validation_errors = raised_validation_errors
if returned_validation_errors:
if row_schema.strict:
# 'marshmallow.schema.BaseSchema':
# > :param bool strict: If `True`, raise errors if invalid data are passed in
# > instead of failing silently and storing the errors.
logger.error(
"Marshmallow schema is 'strict' but validation errors were returned by "
"method 'load' ('UnmarshalResult.errors') instead of being raised. "
"Errors: %s",
repr(returned_validation_errors),
)
if raised_validation_errors:
logger.fatal(
"Programming error: either returned or raised validation errors "
"(depending on 'strict') but never both. "
"Returned errors: %s. Raised errors: %s",
repr(returned_validation_errors),
repr(raised_validation_errors),
)

validation_errors.update(returned_validation_errors)

yield row_ix, row_data, deserialized_row_data, validation_errors

0 comments on commit ea5f5e6

Please sign in to comment.