11from __future__ import annotations
22
3+ import copy
4+ import json
35from datetime import datetime
46from decimal import Decimal
57from pathlib import Path
2325)
2426CTE_F29_DATOS_OBJ_SCHEMA = read_json_schema (_CTE_F29_DATOS_OBJ_SCHEMA_PATH )
2527
28+ _CTE_F29_DATOS_OBJ_MISSING_KEY_FIXES_PATH = (
29+ Path (__file__ ).parent .parent .parent / 'data' / 'cte' / 'f29_datos_obj_missing_key_fixes.json'
30+ )
31+ CTE_F29_DATOS_OBJ_MISSING_KEY_FIXES : SiiCteF29DatosObjType = json .load (
32+ open (_CTE_F29_DATOS_OBJ_MISSING_KEY_FIXES_PATH )
33+ )
34+
2635
2736def parse_sii_cte_f29_datos_obj (
2837 datos_obj : SiiCteF29DatosObjType ,
29- schema_validator : Optional [Callable [[SiiCteF29DatosObjType ], None ]] = None ,
38+ schema_validator : Optional [Callable [[SiiCteF29DatosObjType ], SiiCteF29DatosObjType ]] = None ,
3039 campo_deserializer : Optional [Callable [[object , str ], object ]] = None ,
3140) -> CteForm29 :
3241 """
@@ -55,7 +64,7 @@ def parse_sii_cte_f29_datos_obj(
5564
5665def _parse_sii_cte_f29_datos_obj_to_dict (
5766 datos_obj : SiiCteF29DatosObjType ,
58- schema_validator : Callable [[SiiCteF29DatosObjType ], None ],
67+ schema_validator : Callable [[SiiCteF29DatosObjType ], SiiCteF29DatosObjType ],
5968 campo_deserializer : Callable [[object , str ], object ],
6069) -> Mapping [str , object ]:
6170 """
@@ -67,17 +76,17 @@ def _parse_sii_cte_f29_datos_obj_to_dict(
6776 :param campo_deserializer:
6877 :raises JsonSchemaValidationError: If schema validation fails.
6978 """
70- schema_validator (datos_obj )
79+ validated_datos_obj = schema_validator (datos_obj )
7180
7281 datos_obj_campos : Mapping [int , str ] = {
73- int (code ): str (value ) for code , value in datos_obj ['campos' ].items ()
82+ int (code ): str (value ) for code , value in validated_datos_obj ['campos' ].items ()
7483 }
75- datos_obj_extras : Mapping [str , object ] = datos_obj ['extras' ]
84+ datos_obj_extras : Mapping [str , object ] = validated_datos_obj ['extras' ]
7685 datos_obj_glosa : Mapping [int , str ] = { # noqa: F841
77- int (code ): str (value ) for code , value in datos_obj ['glosa' ].items ()
86+ int (code ): str (value ) for code , value in validated_datos_obj ['glosa' ].items ()
7887 }
7988 datos_obj_tipos : Mapping [int , str ] = {
80- int (code ): str (value ) for code , value in datos_obj ['tipos' ].items ()
89+ int (code ): str (value ) for code , value in validated_datos_obj ['tipos' ].items ()
8190 }
8291
8392 deserialized_datos_obj_campos = {
@@ -156,12 +165,14 @@ def cte_f29_datos_obj_campo_best_effort_deserializer(campo_value: object, tipo:
156165 return deserialized_value
157166
158167
159- def cte_f29_datos_schema_default_validator (datos_obj : SiiCteF29DatosObjType ) -> None :
168+ def cte_f29_datos_schema_default_validator (
169+ datos_obj : SiiCteF29DatosObjType ,
170+ ) -> SiiCteF29DatosObjType :
160171 """
161172 Validate the ``datos`` object against the schema.
162173
163174 :raises JsonSchemaValidationError: If schema validation fails.
164- :returns: ``None`` if schema validation passed.
175+ :returns: Validated ``datos`` object if schema validation passed.
165176 """
166177 try :
167178 jsonschema .validate (datos_obj , schema = CTE_F29_DATOS_OBJ_SCHEMA )
@@ -172,3 +183,82 @@ def cte_f29_datos_schema_default_validator(datos_obj: SiiCteF29DatosObjType) ->
172183 raise JsonSchemaValidationError ("The keys of 'campos' and 'tipos' are not exactly the same" )
173184 if datos_obj ['campos' ].keys () != datos_obj ['glosa' ].keys ():
174185 raise JsonSchemaValidationError ("The keys of 'campos' and 'tipos' are not exactly the same" )
186+
187+ return datos_obj
188+
189+
190+ def cte_f29_datos_schema_best_effort_validator (
191+ datos_obj : SiiCteF29DatosObjType ,
192+ ) -> SiiCteF29DatosObjType :
193+ """
194+ Validate the ``datos`` object against the schema.
195+
196+ If there are missing keys in the `tipos` or `glosa` dicts, it will try to get them
197+ from a list of default values.
198+
199+ :raises JsonSchemaValidationError: If schema validation fails.
200+ :returns: Validated ``datos`` object if schema validation passed.
201+ """
202+ try :
203+ validated_datos_obj = cte_f29_datos_schema_default_validator (datos_obj )
204+ except JsonSchemaValidationError as exc :
205+ if exc .__cause__ is jsonschema .exceptions .ValidationError :
206+ # We will not try to fix this kind of error.
207+ raise
208+ elif exc .__cause__ is None :
209+ # Let's try to fix this.
210+ new_datos_obj = try_fix_cte_f29_datos (datos_obj )
211+
212+ # Let's try again.
213+ cte_f29_datos_schema_default_validator (new_datos_obj )
214+ return new_datos_obj
215+ else :
216+ raise
217+ else :
218+ return validated_datos_obj
219+
220+
221+ def try_fix_cte_f29_datos (datos_obj : SiiCteF29DatosObjType ) -> SiiCteF29DatosObjType :
222+ """
223+ Try to fix the ``datos`` object.
224+
225+ If there are missing keys in the `tipos` or `glosa` dicts, it will try to get them
226+ from a list of default values.
227+
228+ :raises JsonSchemaValidationError: If an unfixable issue is found.
229+ :returns: A possibly fixed ``datos`` object.
230+ """
231+ new_datos_obj : Mapping [str , MutableMapping [str , object ]]
232+ new_datos_obj = copy .deepcopy (datos_obj ) # type: ignore[arg-type]
233+
234+ campos_tipos_keys_diff = datos_obj ['campos' ].keys () - datos_obj ['tipos' ].keys ()
235+ remaining_campos_tipos_diff = (
236+ campos_tipos_keys_diff - CTE_F29_DATOS_OBJ_MISSING_KEY_FIXES ['tipos' ].keys ()
237+ )
238+ if remaining_campos_tipos_diff :
239+ raise JsonSchemaValidationError (
240+ "The keys of 'campos' and 'tipos' differ for the following codes: "
241+ f"{ remaining_campos_tipos_diff } "
242+ )
243+ else :
244+ for missing_key in campos_tipos_keys_diff :
245+ new_datos_obj ['tipos' ][missing_key ] = CTE_F29_DATOS_OBJ_MISSING_KEY_FIXES ['tipos' ][
246+ missing_key
247+ ]
248+
249+ campos_glosa_keys_diff = datos_obj ['campos' ].keys () - datos_obj ['glosa' ].keys ()
250+ remaining_campos_glosa_diff = (
251+ campos_glosa_keys_diff - CTE_F29_DATOS_OBJ_MISSING_KEY_FIXES ['glosa' ].keys ()
252+ )
253+ if remaining_campos_glosa_diff :
254+ raise JsonSchemaValidationError (
255+ "The keys of 'campos' and 'glosa' differ for the following codes: "
256+ f"{ remaining_campos_glosa_diff } "
257+ )
258+ else :
259+ for missing_key in campos_glosa_keys_diff :
260+ new_datos_obj ['glosa' ][missing_key ] = CTE_F29_DATOS_OBJ_MISSING_KEY_FIXES ['glosa' ][
261+ missing_key
262+ ]
263+
264+ return new_datos_obj
0 commit comments