class RcvCsvRowSchema(marshmallow.Schema): EXPECTED_INPUT_FIELDS = tuple(_RCV_CSV_EXPECTED_FIELD_NAMES) + (_CSV_ROW_DICT_EXTRA_FIELDS_KEY, ) # type: ignore # noqa: E501 FIELD_FECHA_RECEPCION_DATETIME_TZ = tz_utils.TZ_CL_SANTIAGO class Meta: strict = True emisor_rut = mm_fields.RutField( required=True, load_from='RUT Proveedor', ) tipo_dte = marshmallow.fields.Integer( required=True, load_from='Tipo Doc', ) folio = marshmallow.fields.Integer( required=True, load_from='Folio', ) fecha_emision_date = mm_utils.CustomMarshmallowDateField( format='%d/%m/%Y', # e.g. '22/10/2018' required=True, load_from='Fecha Docto', ) fecha_recepcion_datetime = marshmallow.fields.DateTime( format='%d/%m/%Y %H:%M:%S', # e.g. '23/10/2018 01:54:13' required=True, load_from='Fecha Recepcion', ) # note: this field value is set using data passed in the schema context. receptor_rut = mm_fields.RutField( required=True, ) monto_total = marshmallow.fields.Integer( required=True, load_from='Monto Total', ) @marshmallow.pre_load def preprocess(self, in_data: dict) -> dict: # note: required fields checks are run later on automatically thus we may not assume that # values of required fields (`required=True`) exist. # Set field value only if it was not in the input data. in_data.setdefault('receptor_rut', self.context['receptor_rut']) return in_data @marshmallow.post_load def postprocess(self, data: dict) -> dict: # >>> data['fecha_recepcion_datetime'].isoformat() # '2018-10-23T01:54:13' data['fecha_recepcion_datetime'] = tz_utils.convert_naive_dt_to_tz_aware( dt=data['fecha_recepcion_datetime'], tz=self.FIELD_FECHA_RECEPCION_DATETIME_TZ) # >>> data['fecha_recepcion_datetime'].isoformat() # '2018-10-23T01:54:13-03:00' # >>> data['fecha_recepcion_datetime'].astimezone(pytz.UTC).isoformat() # '2018-10-23T04:54:13+00:00' # note: to express this value in another timezone (but the value does not change), do # `datetime_obj.astimezone(pytz.timezone('some timezone'))` return data @marshmallow.validates_schema(pass_original=True) def validate_schema(self, data: dict, original_data: dict) -> None: # Fail validation if there was an unexpected input field. unexpected_input_fields = ( set(original_data) - set(self.fields) - set(self.EXPECTED_INPUT_FIELDS) ) if unexpected_input_fields: raise marshmallow.ValidationError( 'Unexpected input field', field_names=list(unexpected_input_fields)) # @marshmallow.validates('field_x') # def validate_field_x(self, value): # pass ########################################################################### # non-marshmallow-related methods ########################################################################### def deserialize_csv_row(self, row: OrderedDict) -> dict: try: result = self.load(row) # type: marshmallow.UnmarshalResult except marshmallow.ValidationError as exc: exc_msg = "Validation errors during deserialization." validation_error_msgs = dict(exc.normalized_messages()) raise ValueError(exc_msg, validation_error_msgs) from exc result_data = result.data # type: dict result_errors = result.errors # type: dict if result_errors: raise Exception("Deserialization errors: %s", result_errors) return result_data
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', ) tipo_docto = mm_fields.RcvTipoDoctoField( required=True, load_from='Tipo Doc', ) folio = marshmallow.fields.Integer( required=True, load_from='Folio', ) fecha_emision_date = mm_utils.CustomMarshmallowDateField( format='%d/%m/%Y', # e.g. '22/10/2018' required=True, load_from='Fecha Docto', ) monto_total = marshmallow.fields.Integer( required=True, load_from='Monto Total', ) emisor_razon_social = marshmallow.fields.String( required=True, load_from='Razon Social', ) ########################################################################### # fields whose value is set using data passed in the schema context ########################################################################### receptor_rut = mm_fields.RutField( required=True, ) receptor_razon_social = marshmallow.fields.String( required=True, ) ########################################################################### # extra fields: not included in the returned struct ########################################################################### 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', ) @marshmallow.pre_load def preprocess(self, in_data: dict) -> dict: # note: required fields checks are run later on automatically thus we may not assume that # values of required fields (`required=True`) exist. # Set field value only if it was not in the input data. in_data.setdefault('receptor_rut', self.context['receptor_rut']) in_data.setdefault('receptor_razon_social', self.context['receptor_razon_social']) # Fix missing/default values. if 'Fecha Acuse' in in_data: if in_data['Fecha Acuse'] == '': in_data['Fecha Acuse'] = None return in_data @marshmallow.post_load def postprocess(self, data: dict) -> dict: # >>> data['fecha_recepcion_dt'].isoformat() # '2018-10-23T01:54:13' data['fecha_recepcion_dt'] = tz_utils.convert_naive_dt_to_tz_aware( dt=data['fecha_recepcion_dt'], tz=self.FIELD_FECHA_RECEPCION_DT_TZ) # >>> data['fecha_recepcion_dt'].isoformat() # '2018-10-23T01:54:13-03:00' # >>> data['fecha_recepcion_dt'].astimezone(pytz.UTC).isoformat() # '2018-10-23T04:54:13+00:00' # note: to express this value in another timezone (but the value does not change), do # `dt_obj.astimezone(pytz.timezone('some timezone'))` return data def to_detalle_entry(self, data: dict) -> RcPendienteDetalleEntry: try: emisor_rut: Rut = data['emisor_rut'] # type: ignore tipo_docto = data['tipo_docto'] # type: ignore folio: int = data['folio'] # type: ignore fecha_emision_date: date = data['fecha_emision_date'] # type: ignore receptor_rut: Rut = data['receptor_rut'] # type: ignore monto_total: int = data['monto_total'] # type: ignore emisor_razon_social: str = data['emisor_razon_social'] # type: ignore receptor_razon_social: str = data['receptor_razon_social'] # type: ignore fecha_recepcion_dt: datetime = data['fecha_recepcion_dt'] # type: ignore except KeyError as exc: raise ValueError("Programming error: a referenced field is missing.") from exc try: detalle_entry = RcPendienteDetalleEntry( emisor_rut=emisor_rut, tipo_docto=tipo_docto, folio=folio, fecha_emision_date=fecha_emision_date, receptor_rut=receptor_rut, monto_total=monto_total, emisor_razon_social=emisor_razon_social, receptor_razon_social=receptor_razon_social, fecha_recepcion_dt=fecha_recepcion_dt, ) except (TypeError, ValueError): raise return detalle_entry