From c03a7387d7d0173ab6842055729d3793e6f58a3e Mon Sep 17 00:00:00 2001 From: Samuel Villegas Date: Tue, 31 Jan 2023 13:42:16 -0300 Subject: [PATCH 1/2] chore(deps): Bump marshmallow from 2.21.0 to 3.19.0 Bumps [marshmallow](https://github.com/marshmallow-code/marshmallow) from 2.21.0 to 3.19.0. - [Release notes](https://marshmallow.readthedocs.io/en/stable/changelog.html) - [Commits](https://github.com/marshmallow-code/marshmallow/compare/2.21.0...3.19.0) - [Upgrading guide](https://marshmallow.readthedocs.io/en/stable/upgrading.html#upgrading-to-3-0) Ref: https://cordada.aha.io/features/COMPCLDATA-201 --- updated-dependencies: - dependency-name: marshmallow - dependency-type: direct:production - update-type: version-update:semver-major ... --- requirements-dev.txt | 9 ++++----- requirements.in | 2 +- requirements.txt | 4 +++- setup.py | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0070b039..e693f7a4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -73,8 +73,10 @@ mypy-extensions==0.4.3 # via # black # mypy -packaging==20.4 - # via tox +packaging==23.0 + # via + # -c requirements.txt + # tox pathspec==0.9.0 # via black pkginfo==1.8.3 @@ -95,8 +97,6 @@ pyflakes==2.4.0 # via flake8 pygments==2.13.0 # via readme-renderer -pyparsing==3.0.9 - # via packaging readme-renderer==35.0 # via twine requests==2.25.1 @@ -111,7 +111,6 @@ secretstorage==3.3.3 six==1.16.0 # via # bleach - # packaging # tox # virtualenv toml==0.10.2 diff --git a/requirements.in b/requirements.in index f8a5e396..30db7fa5 100644 --- a/requirements.in +++ b/requirements.in @@ -12,7 +12,7 @@ djangorestframework>=3.10.3,<3.15 importlib-metadata==1.6.0 jsonschema==4.17.3 lxml==4.9.2 -marshmallow==2.21.0 +marshmallow==3.19.0 pydantic==1.10.4 pyOpenSSL==23.0.0 pytz==2022.7.1 diff --git a/requirements.txt b/requirements.txt index c0b2c330..c2df4346 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,8 +37,10 @@ lxml==4.9.2 # via # -r requirements.in # signxml -marshmallow==2.21.0 +marshmallow==3.19.0 # via -r requirements.in +packaging==23.0 + # via marshmallow pkgutil-resolve-name==1.3.10 # via jsonschema pycparser==2.20 diff --git a/setup.py b/setup.py index b9a26902..22039c43 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ def get_version(*file_paths: Sequence[str]) -> str: 'defusedxml>=0.6.0,<1', 'jsonschema>=3.1.1', 'lxml>=4.6.5,<5', - 'marshmallow>=2.19.2,<3', + 'marshmallow>=3,<4', 'pydantic>=1.6.2,!=1.7.*,!=1.8.*,!=1.9.*', 'pyOpenSSL>=22.0.0', 'pytz>=2019.3', From 6ff9db71da5e8d0d081cfcceb3ebad2f45a8c8c1 Mon Sep 17 00:00:00 2001 From: Samuel Villegas Date: Tue, 31 Jan 2023 13:42:29 -0300 Subject: [PATCH 2/2] chore: Update code to work on `marshmallow 3.19.0` Changes were done by following the guide: https://marshmallow.readthedocs.io/en/stable/upgrading.html#upgrading-to-3-0 * Added `**kwargs` param to `_deserialize` and `serialize` functions * Changed `load_from` to `data_key` param in Schemas * Updated test to work handle error properly * Replaced deprecated `Field.fail` for `Field.make_error` Ref: https://cordada.aha.io/features/COMPCLDATA-201 --- cl_sii/extras/mm_fields.py | 85 +++--- cl_sii/libs/mm_utils.py | 38 +-- cl_sii/libs/rows_processing.py | 26 +- cl_sii/rcv/parse_csv.py | 96 +++---- mypy.ini | 3 - tests/test_extras_mm_fields.py | 486 +++++++++++++++++---------------- 6 files changed, 373 insertions(+), 361 deletions(-) diff --git a/cl_sii/extras/mm_fields.py b/cl_sii/extras/mm_fields.py index 840d83b9..a85bcf56 100644 --- a/cl_sii/extras/mm_fields.py +++ b/cl_sii/extras/mm_fields.py @@ -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 @@ -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]: @@ -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 @@ -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]: @@ -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 @@ -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]: @@ -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 @@ -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]: @@ -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 diff --git a/cl_sii/libs/mm_utils.py b/cl_sii/libs/mm_utils.py index 3af0ab0f..67c3126a 100644 --- a/cl_sii/libs/mm_utils.py +++ b/cl_sii/libs/mm_utils.py @@ -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 @@ -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) @@ -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 @@ -98,11 +96,13 @@ 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 @@ -110,29 +110,31 @@ def _serialize(self, value: date, attr: str, obj: object) -> Union[str, 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 diff --git a/cl_sii/libs/rows_processing.py b/cl_sii/libs/rows_processing.py index 631f4749..082d4395 100644 --- a/cl_sii/libs/rows_processing.py +++ b/cl_sii/libs/rows_processing.py @@ -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 diff --git a/cl_sii/rcv/parse_csv.py b/cl_sii/rcv/parse_csv.py index 27ea6ab4..e17f0176 100644 --- a/cl_sii/rcv/parse_csv.py +++ b/cl_sii/rcv/parse_csv.py @@ -7,7 +7,7 @@ import csv import logging from datetime import date, datetime -from typing import Callable, Dict, Iterable, Optional, Sequence, Tuple +from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Tuple import marshmallow @@ -507,7 +507,7 @@ def parse_rcv_compra_pendiente_csv_file( class _RcvCsvRowSchemaBase(marshmallow.Schema): @marshmallow.validates_schema(pass_original=True) - def validate_schema(self, data: dict, original_data: dict) -> None: + def validate_schema(self, data: dict, original_data: dict, **kwargs: Any) -> None: mm_utils.validate_no_unexpected_input_fields(self, data, original_data) # @marshmallow.validates('field_x') @@ -524,37 +524,34 @@ class RcvVentaCsvRowSchema(_RcvCsvRowSchemaBase): FIELD_FECHA_ACUSE_DT_TZ = SII_OFFICIAL_TZ FIELD_FECHA_RECLAMO_DT_TZ = SII_OFFICIAL_TZ - class Meta: - strict = True - ########################################################################### # basic fields ########################################################################### tipo_docto = mm_fields.RcvTipoDoctoField( required=True, - load_from='Tipo Doc', + data_key='Tipo Doc', ) folio = marshmallow.fields.Integer( required=True, - load_from='Folio', + data_key='Folio', ) fecha_emision_date = mm_utils.CustomMarshmallowDateField( format='%d/%m/%Y', # e.g. '22/10/2018' required=True, - load_from='Fecha Docto', + data_key='Fecha Docto', ) receptor_rut = mm_fields.RutField( required=True, - load_from='Rut cliente', + data_key='Rut cliente', ) monto_total = marshmallow.fields.Integer( required=True, - load_from='Monto total', + data_key='Monto total', ) receptor_razon_social = marshmallow.fields.String( required=True, - load_from='Razon Social', + data_key='Razon Social', ) ########################################################################### @@ -572,23 +569,23 @@ class Meta: fecha_recepcion_dt = marshmallow.fields.DateTime( format='%d/%m/%Y %H:%M:%S', # e.g. '23/10/2018 01:54:13' required=True, - load_from='Fecha Recepcion', + data_key='Fecha Recepcion', ) fecha_acuse_dt = marshmallow.fields.DateTime( format='%d/%m/%Y %H:%M:%S', # e.g. '23/10/2018 01:54:13' required=False, allow_none=True, - load_from='Fecha Acuse Recibo', + data_key='Fecha Acuse Recibo', ) fecha_reclamo_dt = marshmallow.fields.DateTime( format='%d/%m/%Y %H:%M:%S', # e.g. '23/10/2018 01:54:13' required=False, allow_none=True, - load_from='Fecha Reclamo', + data_key='Fecha Reclamo', ) @marshmallow.pre_load - def preprocess(self, in_data: dict) -> dict: + def preprocess(self, in_data: dict, **kwargs: Any) -> dict: # note: required fields checks are run later on automatically thus we may not assume that # values of required fields (`required=True`) exist. @@ -606,7 +603,7 @@ def preprocess(self, in_data: dict) -> dict: return in_data @marshmallow.post_load - def postprocess(self, data: dict) -> dict: + def postprocess(self, data: dict, **kwargs: Any) -> dict: # >>> data['fecha_recepcion_dt'].isoformat() # '2018-10-23T01:54:13' data['fecha_recepcion_dt'] = tz_utils.convert_naive_dt_to_tz_aware( @@ -673,37 +670,34 @@ class RcvCompraRegistroCsvRowSchema(_RcvCsvRowSchemaBase): FIELD_FECHA_RECEPCION_DT_TZ = SII_OFFICIAL_TZ FIELD_FECHA_ACUSE_DT_TZ = SII_OFFICIAL_TZ - class Meta: - strict = True - ########################################################################### # basic fields ########################################################################### emisor_rut = mm_fields.RutField( required=True, - load_from='RUT Proveedor', + data_key='RUT Proveedor', ) tipo_docto = mm_fields.RcvTipoDoctoField( required=True, - load_from='Tipo Doc', + data_key='Tipo Doc', ) folio = marshmallow.fields.Integer( required=True, - load_from='Folio', + data_key='Folio', ) fecha_emision_date = mm_utils.CustomMarshmallowDateField( format='%d/%m/%Y', # e.g. '22/10/2018' required=True, - load_from='Fecha Docto', + data_key='Fecha Docto', ) monto_total = marshmallow.fields.Integer( required=True, - load_from='Monto Total', + data_key='Monto Total', ) emisor_razon_social = marshmallow.fields.String( required=True, - load_from='Razon Social', + data_key='Razon Social', ) ########################################################################### @@ -721,17 +715,17 @@ class Meta: fecha_recepcion_dt = marshmallow.fields.DateTime( format='%d/%m/%Y %H:%M:%S', # e.g. '23/10/2018 01:54:13' required=True, - load_from='Fecha Recepcion', + data_key='Fecha Recepcion', ) fecha_acuse_dt = marshmallow.fields.DateTime( format='%d/%m/%Y %H:%M:%S', # e.g. '23/10/2018 01:54:13' required=True, allow_none=True, - load_from='Fecha Acuse', + data_key='Fecha Acuse', ) @marshmallow.pre_load - def preprocess(self, in_data: dict) -> dict: + def preprocess(self, in_data: dict, **kwargs: Any) -> dict: # note: required fields checks are run later on automatically thus we may not assume that # values of required fields (`required=True`) exist. @@ -746,7 +740,7 @@ def preprocess(self, in_data: dict) -> dict: return in_data @marshmallow.post_load - def postprocess(self, data: dict) -> dict: + def postprocess(self, data: dict, **kwargs: Any) -> dict: # >>> data['fecha_recepcion_dt'].isoformat() # '2018-10-23T01:54:13' data['fecha_recepcion_dt'] = tz_utils.convert_naive_dt_to_tz_aware( @@ -840,37 +834,34 @@ class RcvCompraReclamadoCsvRowSchema(_RcvCsvRowSchemaBase): FIELD_FECHA_RECEPCION_DT_TZ = SII_OFFICIAL_TZ FIELD_FECHA_RECLAMO_DT_TZ = SII_OFFICIAL_TZ - class Meta: - strict = True - ########################################################################### # basic fields ########################################################################### emisor_rut = mm_fields.RutField( required=True, - load_from='RUT Proveedor', + data_key='RUT Proveedor', ) tipo_docto = mm_fields.RcvTipoDoctoField( required=True, - load_from='Tipo Doc', + data_key='Tipo Doc', ) folio = marshmallow.fields.Integer( required=True, - load_from='Folio', + data_key='Folio', ) fecha_emision_date = mm_utils.CustomMarshmallowDateField( format='%d/%m/%Y', # e.g. '22/10/2018' required=True, - load_from='Fecha Docto', + data_key='Fecha Docto', ) monto_total = marshmallow.fields.Integer( required=True, - load_from='Monto Total', + data_key='Monto Total', ) emisor_razon_social = marshmallow.fields.String( required=True, - load_from='Razon Social', + data_key='Razon Social', ) ########################################################################### @@ -888,7 +879,7 @@ class Meta: fecha_recepcion_dt = marshmallow.fields.DateTime( format='%d/%m/%Y %H:%M:%S', # e.g. '23/10/2018 01:54:13' required=True, - load_from='Fecha Recepcion', + data_key='Fecha Recepcion', ) fecha_reclamo_dt = marshmallow.fields.DateTime( # note: for some reason the rows with 'tipo_docto' equal to @@ -897,11 +888,11 @@ class Meta: format='%d/%m/%Y %H:%M:%S', # e.g. '23/10/2018 01:54:13' required=False, allow_none=True, - load_from='Fecha Reclamo', + data_key='Fecha Reclamo', ) @marshmallow.pre_load - def preprocess(self, in_data: dict) -> dict: + def preprocess(self, in_data: dict, **kwargs: Any) -> dict: # note: required fields checks are run later on automatically thus we may not assume that # values of required fields (`required=True`) exist. @@ -919,7 +910,7 @@ def preprocess(self, in_data: dict) -> dict: return in_data @marshmallow.post_load - def postprocess(self, data: dict) -> dict: + def postprocess(self, data: dict, **kwargs: Any) -> dict: # >>> data['fecha_recepcion_dt'].isoformat() # '2018-10-23T01:54:13' data['fecha_recepcion_dt'] = tz_utils.convert_naive_dt_to_tz_aware( @@ -980,37 +971,34 @@ class RcvCompraPendienteCsvRowSchema(_RcvCsvRowSchemaBase): FIELD_FECHA_RECEPCION_DT_TZ = SII_OFFICIAL_TZ FIELD_FECHA_ACUSE_DT_TZ = SII_OFFICIAL_TZ - class Meta: - strict = True - ########################################################################### # basic fields ########################################################################### emisor_rut = mm_fields.RutField( required=True, - load_from='RUT Proveedor', + data_key='RUT Proveedor', ) tipo_docto = mm_fields.RcvTipoDoctoField( required=True, - load_from='Tipo Doc', + data_key='Tipo Doc', ) folio = marshmallow.fields.Integer( required=True, - load_from='Folio', + data_key='Folio', ) fecha_emision_date = mm_utils.CustomMarshmallowDateField( format='%d/%m/%Y', # e.g. '22/10/2018' required=True, - load_from='Fecha Docto', + data_key='Fecha Docto', ) monto_total = marshmallow.fields.Integer( required=True, - load_from='Monto Total', + data_key='Monto Total', ) emisor_razon_social = marshmallow.fields.String( required=True, - load_from='Razon Social', + data_key='Razon Social', ) ########################################################################### @@ -1028,11 +1016,11 @@ class Meta: fecha_recepcion_dt = marshmallow.fields.DateTime( format='%d/%m/%Y %H:%M:%S', # e.g. '23/10/2018 01:54:13' required=True, - load_from='Fecha Recepcion', + data_key='Fecha Recepcion', ) @marshmallow.pre_load - def preprocess(self, in_data: dict) -> dict: + def preprocess(self, in_data: dict, **kwargs: Any) -> dict: # note: required fields checks are run later on automatically thus we may not assume that # values of required fields (`required=True`) exist. @@ -1047,7 +1035,7 @@ def preprocess(self, in_data: dict) -> dict: return in_data @marshmallow.post_load - def postprocess(self, data: dict) -> dict: + def postprocess(self, data: dict, **kwargs: Any) -> dict: # >>> data['fecha_recepcion_dt'].isoformat() # '2018-10-23T01:54:13' data['fecha_recepcion_dt'] = tz_utils.convert_naive_dt_to_tz_aware( diff --git a/mypy.ini b/mypy.ini index d5a433e6..d551da5f 100644 --- a/mypy.ini +++ b/mypy.ini @@ -28,9 +28,6 @@ ignore_missing_imports = True [mypy-lxml.*] ignore_missing_imports = True -[mypy-marshmallow.*] -ignore_missing_imports = True - [mypy-rest_framework.*] ignore_missing_imports = True diff --git a/tests/test_extras_mm_fields.py b/tests/test_extras_mm_fields.py index 489cdc6f..23b58333 100644 --- a/tests/test_extras_mm_fields.py +++ b/tests/test_extras_mm_fields.py @@ -26,25 +26,28 @@ class MyBadObj: def __init__(self, some_field: int) -> None: self.some_field = some_field - class MyMmSchema(marshmallow.Schema): - class Meta: - strict = False + class DumpMyMmSchema(marshmallow.Schema): + emisor_rut = RutField( + required=True, + ) + other_field = marshmallow.fields.Integer( + required=False, + ) + class LoadMyMmSchema(marshmallow.Schema): emisor_rut = RutField( required=True, - load_from='RUT of Emisor', + data_key='RUT of Emisor', ) other_field = marshmallow.fields.Integer( required=False, ) class MyMmSchemaStrict(marshmallow.Schema): - class Meta: - strict = True emisor_rut = RutField( required=True, - load_from='RUT of Emisor', + data_key='RUT of Emisor', ) other_field = marshmallow.fields.Integer( required=False, @@ -52,108 +55,107 @@ class Meta: self.MyObj = MyObj self.MyBadObj = MyBadObj - self.MyMmSchema = MyMmSchema + self.LoadMyMmSchema = LoadMyMmSchema + self.DumpMyMmSchema = DumpMyMmSchema self.MyMmSchemaStrict = MyMmSchemaStrict def test_load_ok_valid(self) -> None: - schema = self.MyMmSchema() + schema = self.LoadMyMmSchema() data_valid_1 = {'RUT of Emisor': '1-1'} data_valid_2 = {'RUT of Emisor': Rut('1-1')} data_valid_3 = {'RUT of Emisor': ' 1.111.111-k \t '} result = schema.load(data_valid_1) - self.assertDictEqual(dict(result.data), {'emisor_rut': Rut('1-1')}) - self.assertDictEqual(dict(result.errors), {}) + self.assertDictEqual(dict(result), {'emisor_rut': Rut('1-1')}) result = schema.load(data_valid_2) - self.assertDictEqual(dict(result.data), {'emisor_rut': Rut('1-1')}) - self.assertDictEqual(dict(result.errors), {}) + self.assertDictEqual(dict(result), {'emisor_rut': Rut('1-1')}) result = schema.load(data_valid_3) - self.assertDictEqual(dict(result.data), {'emisor_rut': Rut('1111111-K')}) - self.assertDictEqual(dict(result.errors), {}) + self.assertDictEqual(dict(result), {'emisor_rut': Rut('1111111-K')}) def test_dump_ok_valid(self) -> None: - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() obj_valid_1 = self.MyObj(emisor_rut=Rut('1-1')) obj_valid_2 = self.MyObj(emisor_rut=None) - data, errors = schema.dump(obj_valid_1) + data = schema.dump(obj_valid_1) self.assertDictEqual(data, {'emisor_rut': '1-1', 'other_field': None}) - self.assertDictEqual(errors, {}) - data, errors = schema.dump(obj_valid_2) + data = schema.dump(obj_valid_2) self.assertDictEqual(data, {'emisor_rut': None, 'other_field': None}) - self.assertDictEqual(errors, {}) def test_dump_ok_strange(self) -> None: # If the class of the object to be dumped has attributes that do not match at all the - # fields of the schema, there are no errors! Even if the schema has `strict = True` set. + # fields of the schema, there are no errors! - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() schema_strict = self.MyMmSchemaStrict() obj_valid_1 = self.MyBadObj(some_field=123) obj_valid_2 = self.MyBadObj(some_field=None) - data, errors = schema.dump(obj_valid_1) - self.assertEqual((data, errors), ({}, {})) + data = schema.dump(obj_valid_1) + self.assertEqual(data, {}) - data, errors = schema_strict.dump(obj_valid_1) - self.assertEqual((data, errors), ({}, {})) + data = schema_strict.dump(obj_valid_1) + self.assertEqual(data, {}) - data, errors = schema.dump(obj_valid_2) - self.assertEqual((data, errors), ({}, {})) + data = schema.dump(obj_valid_2) + self.assertEqual(data, {}) - data, errors = schema_strict.dump(obj_valid_2) - self.assertEqual((data, errors), ({}, {})) + data = schema_strict.dump(obj_valid_2) + self.assertEqual(data, {}) def test_load_fail(self) -> None: - schema = self.MyMmSchema() + schema = self.LoadMyMmSchema() data_invalid_1 = {'RUT of Emisor': '123123123123'} data_invalid_2 = {'RUT of Emisor': 123} data_invalid_3 = {'RUT of Emisor': None} data_invalid_4 = {} - result = schema.load(data_invalid_1) - self.assertDictEqual(dict(result.data), {}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_1) self.assertDictEqual( - dict(result.errors), {'RUT of Emisor': ['Not a syntactically valid RUT.']} + cm.exception.messages, {'RUT of Emisor': ['Not a syntactically valid RUT.']} ) - result = schema.load(data_invalid_2) - self.assertDictEqual(dict(result.data), {}) - self.assertDictEqual(dict(result.errors), {'RUT of Emisor': ['Invalid type.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_2) + self.assertDictEqual(cm.exception.messages, {'RUT of Emisor': ['Invalid type.']}) - result = schema.load(data_invalid_3) - self.assertDictEqual(dict(result.data), {}) - self.assertDictEqual(dict(result.errors), {'RUT of Emisor': ['Field may not be null.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_3) + self.assertDictEqual(cm.exception.messages, {'RUT of Emisor': ['Field may not be null.']}) - result = schema.load(data_invalid_4) - self.assertDictEqual(dict(result.data), {}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_4) self.assertDictEqual( - dict(result.errors), {'RUT of Emisor': ['Missing data for required field.']} + cm.exception.messages, {'RUT of Emisor': ['Missing data for required field.']} ) def test_dump_fail(self) -> None: - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() obj_invalid_1 = self.MyObj(emisor_rut=20) obj_invalid_2 = self.MyObj(emisor_rut='123123123123') obj_invalid_3 = self.MyObj(emisor_rut='') - data, errors = schema.dump(obj_invalid_1) - self.assertDictEqual(errors, {'emisor_rut': ['Invalid type.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_1) + self.assertEqual(cm.exception.messages, ['Invalid type.']) - data, errors = schema.dump(obj_invalid_2) - self.assertDictEqual(errors, {'emisor_rut': ['Not a syntactically valid RUT.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_2) + self.assertEqual(cm.exception.messages, ['Not a syntactically valid RUT.']) - data, errors = schema.dump(obj_invalid_3) - self.assertDictEqual(errors, {'emisor_rut': ['Not a syntactically valid RUT.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_3) + self.assertEqual(cm.exception.messages, ['Not a syntactically valid RUT.']) class TipoDteFieldTest(unittest.TestCase): @@ -167,25 +169,27 @@ class MyBadObj: def __init__(self, some_field: int) -> None: self.some_field = some_field - class MyMmSchema(marshmallow.Schema): - class Meta: - strict = False + class LoadMyMmSchema(marshmallow.Schema): + tipo_dte = TipoDteField( + required=True, + data_key='source field name', + ) + other_field = marshmallow.fields.Integer( + required=False, + ) + class DumpMyMmSchema(marshmallow.Schema): tipo_dte = TipoDteField( required=True, - load_from='source field name', ) other_field = marshmallow.fields.Integer( required=False, ) class MyMmSchemaStrict(marshmallow.Schema): - class Meta: - strict = True - tipo_dte = TipoDteField( required=True, - load_from='source field name', + data_key='source field name', ) other_field = marshmallow.fields.Integer( required=False, @@ -193,93 +197,93 @@ class Meta: self.MyObj = MyObj self.MyBadObj = MyBadObj - self.MyMmSchema = MyMmSchema + self.LoadMyMmSchema = LoadMyMmSchema + self.DumpMyMmSchema = DumpMyMmSchema self.MyMmSchemaStrict = MyMmSchemaStrict def test_load_ok_valid(self) -> None: - schema = self.MyMmSchema() + schema = self.LoadMyMmSchema() data_valid_1 = {'source field name': 33} data_valid_2 = {'source field name': TipoDte(33)} data_valid_3 = {'source field name': ' 33 \t '} - result = schema.load(data_valid_1) - self.assertDictEqual(dict(result.data), {'tipo_dte': TipoDte(33)}) - self.assertDictEqual(dict(result.errors), {}) + data = schema.load(data_valid_1) + self.assertDictEqual(data, {'tipo_dte': TipoDte(33)}) - result = schema.load(data_valid_2) - self.assertDictEqual(dict(result.data), {'tipo_dte': TipoDte(33)}) - self.assertDictEqual(dict(result.errors), {}) + data = schema.load(data_valid_2) + self.assertDictEqual(data, {'tipo_dte': TipoDte(33)}) - result = schema.load(data_valid_3) - self.assertDictEqual(dict(result.data), {'tipo_dte': TipoDte(33)}) - self.assertDictEqual(dict(result.errors), {}) + data = schema.load(data_valid_3) + self.assertDictEqual(data, {'tipo_dte': TipoDte(33)}) def test_dump_ok_valid(self) -> None: - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() obj_valid_1 = self.MyObj(tipo_dte=TipoDte(33)) obj_valid_2 = self.MyObj(tipo_dte=None) - data, errors = schema.dump(obj_valid_1) + data = schema.dump(obj_valid_1) self.assertDictEqual(data, {'tipo_dte': 33, 'other_field': None}) - self.assertDictEqual(errors, {}) - data, errors = schema.dump(obj_valid_2) + data = schema.dump(obj_valid_2) self.assertDictEqual(data, {'tipo_dte': None, 'other_field': None}) - self.assertDictEqual(errors, {}) def test_dump_ok_strange(self) -> None: # If the class of the object to be dumped has attributes that do not match at all the - # fields of the schema, there are no errors! Even if the schema has `strict = True` set. + # fields of the schema, there are no errors! - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() schema_strict = self.MyMmSchemaStrict() obj_valid_1 = self.MyBadObj(some_field=123) obj_valid_2 = self.MyBadObj(some_field=None) - data, errors = schema.dump(obj_valid_1) - self.assertEqual((data, errors), ({}, {})) + data = schema.dump(obj_valid_1) + self.assertEqual(data, {}) - data, errors = schema_strict.dump(obj_valid_1) - self.assertEqual((data, errors), ({}, {})) + data = schema_strict.dump(obj_valid_1) + self.assertEqual(data, {}) - data, errors = schema.dump(obj_valid_2) - self.assertEqual((data, errors), ({}, {})) + data = schema.dump(obj_valid_2) + self.assertEqual(data, {}) - data, errors = schema_strict.dump(obj_valid_2) - self.assertEqual((data, errors), ({}, {})) + data = schema_strict.dump(obj_valid_2) + self.assertEqual(data, {}) def test_load_fail(self) -> None: - schema = self.MyMmSchema() + schema = self.LoadMyMmSchema() data_invalid_1 = {'source field name': '123'} data_invalid_2 = {'source field name': True} data_invalid_3 = {'source field name': None} data_invalid_4 = {} - result = schema.load(data_invalid_1) - self.assertDictEqual(dict(result.data), {}) - self.assertDictEqual(dict(result.errors), {'source field name': ['Not a valid Tipo DTE.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_1) + self.assertDictEqual( + cm.exception.messages, {'source field name': ['Not a valid Tipo DTE.']} + ) - result = schema.load(data_invalid_2) - self.assertDictEqual(dict(result.data), {}) - self.assertDictEqual(dict(result.errors), {'source field name': ['Invalid type.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_2) + self.assertDictEqual(cm.exception.messages, {'source field name': ['Invalid type.']}) - result = schema.load(data_invalid_3) - self.assertDictEqual(dict(result.data), {}) - self.assertDictEqual(dict(result.errors), {'source field name': ['Field may not be null.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_3) + self.assertDictEqual( + cm.exception.messages, {'source field name': ['Field may not be null.']} + ) - result = schema.load(data_invalid_4) - self.assertDictEqual(dict(result.data), {}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_4) self.assertDictEqual( - dict(result.errors), {'source field name': ['Missing data for required field.']} + cm.exception.messages, {'source field name': ['Missing data for required field.']} ) def test_dump_fail(self) -> None: - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() obj_invalid_1 = self.MyObj(tipo_dte=100) obj_invalid_2 = self.MyObj(tipo_dte=True) @@ -287,16 +291,25 @@ def test_dump_fail(self) -> None: obj_invalid_4 = self.MyObj(tipo_dte='') obj_invalid_5 = self.MyObj(tipo_dte=date(2018, 12, 23)) - data, errors = schema.dump(obj_invalid_1) - self.assertDictEqual(errors, {'tipo_dte': ['Not a valid Tipo DTE.']}) - data, errors = schema.dump(obj_invalid_2) - self.assertDictEqual(errors, {'tipo_dte': ['Invalid type.']}) - data, errors = schema.dump(obj_invalid_3) - self.assertDictEqual(errors, {'tipo_dte': ['Invalid type.']}) - data, errors = schema.dump(obj_invalid_4) - self.assertDictEqual(errors, {'tipo_dte': ['Invalid type.']}) - data, errors = schema.dump(obj_invalid_5) - self.assertDictEqual(errors, {'tipo_dte': ['Invalid type.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_1) + self.assertEqual(cm.exception.messages, ['Not a valid Tipo DTE.']) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_2) + self.assertEqual(cm.exception.messages, ['Invalid type.']) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_3) + self.assertEqual(cm.exception.messages, ['Invalid type.']) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_4) + self.assertEqual(cm.exception.messages, ['Invalid type.']) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_5) + self.assertEqual(cm.exception.messages, ['Invalid type.']) class RcvTipoDoctoFieldTest(unittest.TestCase): @@ -310,25 +323,27 @@ class MyBadObj: def __init__(self, some_field: int) -> None: self.some_field = some_field - class MyMmSchema(marshmallow.Schema): - class Meta: - strict = False + class DumpMyMmSchema(marshmallow.Schema): + tipo_docto = RcvTipoDoctoField( + required=True, + ) + other_field = marshmallow.fields.Integer( + required=False, + ) + class LoadMyMmSchema(marshmallow.Schema): tipo_docto = RcvTipoDoctoField( required=True, - load_from='source field name', + data_key='source field name', ) other_field = marshmallow.fields.Integer( required=False, ) class MyMmSchemaStrict(marshmallow.Schema): - class Meta: - strict = True - tipo_docto = RcvTipoDoctoField( required=True, - load_from='source field name', + data_key='source field name', ) other_field = marshmallow.fields.Integer( required=False, @@ -336,95 +351,93 @@ class Meta: self.MyObj = MyObj self.MyBadObj = MyBadObj - self.MyMmSchema = MyMmSchema + self.LoadMyMmSchema = LoadMyMmSchema + self.DumpMyMmSchema = DumpMyMmSchema self.MyMmSchemaStrict = MyMmSchemaStrict def test_load_ok_valid(self) -> None: - schema = self.MyMmSchema() + schema = self.LoadMyMmSchema() data_valid_1 = {'source field name': 33} data_valid_2 = {'source field name': RcvTipoDocto(33)} data_valid_3 = {'source field name': ' 33 \t '} result = schema.load(data_valid_1) - self.assertDictEqual(dict(result.data), {'tipo_docto': RcvTipoDocto(33)}) - self.assertDictEqual(dict(result.errors), {}) + self.assertDictEqual(dict(result), {'tipo_docto': RcvTipoDocto(33)}) result = schema.load(data_valid_2) - self.assertDictEqual(dict(result.data), {'tipo_docto': RcvTipoDocto(33)}) - self.assertDictEqual(dict(result.errors), {}) + self.assertDictEqual(dict(result), {'tipo_docto': RcvTipoDocto(33)}) result = schema.load(data_valid_3) - self.assertDictEqual(dict(result.data), {'tipo_docto': RcvTipoDocto(33)}) - self.assertDictEqual(dict(result.errors), {}) + self.assertDictEqual(dict(result), {'tipo_docto': RcvTipoDocto(33)}) def test_dump_ok_valid(self) -> None: - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() obj_valid_1 = self.MyObj(tipo_docto=RcvTipoDocto(33)) obj_valid_2 = self.MyObj(tipo_docto=None) - data, errors = schema.dump(obj_valid_1) + data = schema.dump(obj_valid_1) self.assertDictEqual(data, {'tipo_docto': 33, 'other_field': None}) - self.assertDictEqual(errors, {}) - data, errors = schema.dump(obj_valid_2) + data = schema.dump(obj_valid_2) self.assertDictEqual(data, {'tipo_docto': None, 'other_field': None}) - self.assertDictEqual(errors, {}) def test_dump_ok_strange(self) -> None: # If the class of the object to be dumped has attributes that do not match at all the - # fields of the schema, there are no errors! Even if the schema has `strict = True` set. + # fields of the schema, there are no errors! - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() schema_strict = self.MyMmSchemaStrict() obj_valid_1 = self.MyBadObj(some_field=123) obj_valid_2 = self.MyBadObj(some_field=None) - data, errors = schema.dump(obj_valid_1) - self.assertEqual((data, errors), ({}, {})) + data = schema.dump(obj_valid_1) + self.assertEqual(data, {}) - data, errors = schema_strict.dump(obj_valid_1) - self.assertEqual((data, errors), ({}, {})) + data = schema_strict.dump(obj_valid_1) + self.assertEqual(data, {}) - data, errors = schema.dump(obj_valid_2) - self.assertEqual((data, errors), ({}, {})) + data = schema.dump(obj_valid_2) + self.assertEqual(data, {}) - data, errors = schema_strict.dump(obj_valid_2) - self.assertEqual((data, errors), ({}, {})) + data = schema_strict.dump(obj_valid_2) + self.assertEqual(data, {}) def test_load_fail(self) -> None: - schema = self.MyMmSchema() + schema = self.LoadMyMmSchema() data_invalid_1 = {'source field name': '123'} data_invalid_2 = {'source field name': True} data_invalid_3 = {'source field name': None} data_invalid_4 = {} - result = schema.load(data_invalid_1) - self.assertDictEqual(dict(result.data), {}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_1) self.assertDictEqual( - dict(result.errors), {'source field name': ["Not a valid RCV's Tipo de Documento."]} + cm.exception.messages, {'source field name': ["Not a valid RCV's Tipo de Documento."]} ) - result = schema.load(data_invalid_2) - self.assertDictEqual(dict(result.data), {}) - self.assertDictEqual(dict(result.errors), {'source field name': ['Invalid type.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_2) + self.assertDictEqual(cm.exception.messages, {'source field name': ['Invalid type.']}) - result = schema.load(data_invalid_3) - self.assertDictEqual(dict(result.data), {}) - self.assertDictEqual(dict(result.errors), {'source field name': ['Field may not be null.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_3) + self.assertDictEqual( + cm.exception.messages, {'source field name': ['Field may not be null.']} + ) - result = schema.load(data_invalid_4) - self.assertDictEqual(dict(result.data), {}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_4) self.assertDictEqual( - dict(result.errors), {'source field name': ['Missing data for required field.']} + cm.exception.messages, {'source field name': ['Missing data for required field.']} ) def test_dump_fail(self) -> None: - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() obj_invalid_1 = self.MyObj(tipo_docto=100) obj_invalid_2 = self.MyObj(tipo_docto=True) @@ -432,16 +445,25 @@ def test_dump_fail(self) -> None: obj_invalid_4 = self.MyObj(tipo_docto='') obj_invalid_5 = self.MyObj(tipo_docto=date(2018, 12, 23)) - data, errors = schema.dump(obj_invalid_1) - self.assertDictEqual(errors, {'tipo_docto': ["Not a valid RCV's Tipo de Documento."]}) - data, errors = schema.dump(obj_invalid_2) - self.assertDictEqual(errors, {'tipo_docto': ['Invalid type.']}) - data, errors = schema.dump(obj_invalid_3) - self.assertDictEqual(errors, {'tipo_docto': ['Invalid type.']}) - data, errors = schema.dump(obj_invalid_4) - self.assertDictEqual(errors, {'tipo_docto': ['Invalid type.']}) - data, errors = schema.dump(obj_invalid_5) - self.assertDictEqual(errors, {'tipo_docto': ['Invalid type.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_1) + self.assertEqual(cm.exception.messages, ["Not a valid RCV's Tipo de Documento."]) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_2) + self.assertEqual(cm.exception.messages, ['Invalid type.']) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_3) + self.assertEqual(cm.exception.messages, ['Invalid type.']) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_4) + self.assertEqual(cm.exception.messages, ['Invalid type.']) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_5) + self.assertEqual(cm.exception.messages, ['Invalid type.']) class RcvPeriodoTributarioFieldTest(unittest.TestCase): @@ -459,25 +481,27 @@ class MyBadObj: def __init__(self, some_field: int) -> None: self.some_field = some_field - class MyMmSchema(marshmallow.Schema): - class Meta: - strict = False + class DumpMyMmSchema(marshmallow.Schema): + periodo_tributario = RcvPeriodoTributarioField( + required=True, + ) + other_field = marshmallow.fields.Integer( + required=False, + ) + class LoadMyMmSchema(marshmallow.Schema): periodo_tributario = RcvPeriodoTributarioField( required=True, - load_from='source field name', + data_key='source field name', ) other_field = marshmallow.fields.Integer( required=False, ) class MyMmSchemaStrict(marshmallow.Schema): - class Meta: - strict = True - periodo_tributario = RcvPeriodoTributarioField( required=True, - load_from='source field name', + data_key='source field name', ) other_field = marshmallow.fields.Integer( required=False, @@ -485,11 +509,12 @@ class Meta: self.MyObj = MyObj self.MyBadObj = MyBadObj - self.MyMmSchema = MyMmSchema + self.DumpMyMmSchema = DumpMyMmSchema + self.LoadMyMmSchema = LoadMyMmSchema self.MyMmSchemaStrict = MyMmSchemaStrict def test_load_ok_valid(self) -> None: - schema = self.MyMmSchema() + schema = self.LoadMyMmSchema() data_valid_1 = {'source field name': '2019-12'} data_valid_2 = {'source field name': RcvPeriodoTributario(year=2019, month=12)} @@ -498,75 +523,68 @@ def test_load_ok_valid(self) -> None: result = schema.load(data_valid_1) self.assertEqual( - dict(result.data), + dict(result), {'periodo_tributario': RcvPeriodoTributario(year=2019, month=12)}, ) - self.assertEqual(dict(result.errors), {}) result = schema.load(data_valid_2) self.assertEqual( - dict(result.data), + dict(result), {'periodo_tributario': RcvPeriodoTributario(year=2019, month=12)}, ) - self.assertEqual(dict(result.errors), {}) result = schema.load(data_valid_3) self.assertEqual( - dict(result.data), + dict(result), {'periodo_tributario': RcvPeriodoTributario(year=2019, month=9)}, ) - self.assertEqual(dict(result.errors), {}) result = schema.load(data_valid_4) self.assertEqual( - dict(result.data), + dict(result), {'periodo_tributario': RcvPeriodoTributario(year=2019, month=9)}, ) - self.assertEqual(dict(result.errors), {}) def test_dump_ok_valid(self) -> None: - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() obj_valid_1 = self.MyObj(periodo_tributario=RcvPeriodoTributario(year=2019, month=12)) obj_valid_2 = self.MyObj(periodo_tributario=RcvPeriodoTributario(year=2019, month=9)) obj_valid_3 = self.MyObj(periodo_tributario=None) - data, errors = schema.dump(obj_valid_1) + data = schema.dump(obj_valid_1) self.assertEqual(data, {'periodo_tributario': '2019-12', 'other_field': None}) - self.assertEqual(errors, {}) - data, errors = schema.dump(obj_valid_2) + data = schema.dump(obj_valid_2) self.assertEqual(data, {'periodo_tributario': '2019-09', 'other_field': None}) - self.assertEqual(errors, {}) - data, errors = schema.dump(obj_valid_3) + data = schema.dump(obj_valid_3) self.assertEqual(data, {'periodo_tributario': None, 'other_field': None}) - self.assertEqual(errors, {}) def test_dump_ok_strange(self) -> None: # If the class of the object to be dumped has attributes that do not match at all the - # fields of the schema, there are no errors! Even if the schema has `strict = True` set. + # fields of the schema, there are no errors! - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() schema_strict = self.MyMmSchemaStrict() obj_valid_1 = self.MyBadObj(some_field=123) obj_valid_2 = self.MyBadObj(some_field=None) - data, errors = schema.dump(obj_valid_1) - self.assertEqual((data, errors), ({}, {})) + data = schema.dump(obj_valid_1) + self.assertEqual(data, {}) - data, errors = schema_strict.dump(obj_valid_1) - self.assertEqual((data, errors), ({}, {})) + data = schema_strict.dump(obj_valid_1) + self.assertEqual(data, {}) - data, errors = schema.dump(obj_valid_2) - self.assertEqual((data, errors), ({}, {})) + data = schema.dump(obj_valid_2) + self.assertEqual(data, {}) - data, errors = schema_strict.dump(obj_valid_2) - self.assertEqual((data, errors), ({}, {})) + data = schema_strict.dump(obj_valid_2) + self.assertEqual(data, {}) def test_load_fail(self) -> None: - schema = self.MyMmSchema() + schema = self.LoadMyMmSchema() data_invalid_1 = {'source field name': '2019-12-01'} data_invalid_2 = {'source field name': 201912} @@ -574,37 +592,34 @@ def test_load_fail(self) -> None: data_invalid_4 = {'source field name': None} data_invalid_5 = {} - result = schema.load(data_invalid_1) - self.assertEqual(dict(result.data), {}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_1) self.assertEqual( - dict(result.errors), - {'source field name': ["Not a valid RCV Periodo Tributario."]}, + cm.exception.messages, {'source field name': ['Not a valid RCV Periodo Tributario.']} ) - result = schema.load(data_invalid_2) - self.assertEqual(dict(result.data), {}) - self.assertEqual(dict(result.errors), {'source field name': ['Invalid type.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_2) + self.assertEqual(cm.exception.messages, {'source field name': ['Invalid type.']}) - result = schema.load(data_invalid_3) - self.assertEqual(dict(result.data), {}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_3) self.assertEqual( - dict(result.errors), - {'source field name': ["Not a valid RCV Periodo Tributario."]}, + cm.exception.messages, {'source field name': ["Not a valid RCV Periodo Tributario."]} ) - result = schema.load(data_invalid_4) - self.assertEqual(dict(result.data), {}) - self.assertEqual(dict(result.errors), {'source field name': ['Field may not be null.']}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_4) + self.assertEqual(cm.exception.messages, {'source field name': ['Field may not be null.']}) - result = schema.load(data_invalid_5) - self.assertEqual(dict(result.data), {}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.load(data_invalid_5) self.assertEqual( - dict(result.errors), - {'source field name': ['Missing data for required field.']}, + cm.exception.messages, {'source field name': ['Missing data for required field.']} ) def test_dump_fail(self) -> None: - schema = self.MyMmSchema() + schema = self.DumpMyMmSchema() obj_invalid_1 = self.MyObj(periodo_tributario='2019-12-01') obj_invalid_2 = self.MyObj(periodo_tributario=date(2019, 12, 1)) @@ -613,15 +628,26 @@ def test_dump_fail(self) -> None: obj_invalid_5 = self.MyObj(periodo_tributario=201912) obj_invalid_6 = self.MyObj(periodo_tributario=' 2019-12-01') - data, errors = schema.dump(obj_invalid_1) - self.assertEqual(errors, {'periodo_tributario': ["Not a valid RCV Periodo Tributario."]}) - data, errors = schema.dump(obj_invalid_2) - self.assertEqual(errors, {'periodo_tributario': ['Invalid type.']}) - data, errors = schema.dump(obj_invalid_3) - self.assertEqual(errors, {'periodo_tributario': ['Invalid type.']}) - data, errors = schema.dump(obj_invalid_4) - self.assertEqual(errors, {'periodo_tributario': ["Not a valid RCV Periodo Tributario."]}) - data, errors = schema.dump(obj_invalid_5) - self.assertEqual(errors, {'periodo_tributario': ['Invalid type.']}) - data, errors = schema.dump(obj_invalid_6) - self.assertEqual(errors, {'periodo_tributario': ["Not a valid RCV Periodo Tributario."]}) + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_1) + self.assertEqual(cm.exception.messages, ["Not a valid RCV Periodo Tributario."]) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_2) + self.assertEqual(cm.exception.messages, ['Invalid type.']) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_3) + self.assertEqual(cm.exception.messages, ['Invalid type.']) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_4) + self.assertEqual(cm.exception.messages, ["Not a valid RCV Periodo Tributario."]) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_5) + self.assertEqual(cm.exception.messages, ['Invalid type.']) + + with self.assertRaises(marshmallow.ValidationError) as cm: + schema.dump(obj_invalid_6) + self.assertEqual(cm.exception.messages, ["Not a valid RCV Periodo Tributario."])