11from typing import Type , TypeVar
22from pydantic import BaseModel
33import requests
4- from fiscalapi .models .common_models import ApiResponse , FiscalApiSettings
4+ from fiscalapi .models .common_models import ApiResponse , FiscalApiSettings , ValidationFailure
55
66T = TypeVar ('T' , bound = BaseModel )
77
88class BaseService :
9- """
10- Clase base que agrupa lógica repetida en los servicios,
11- como la construcción de URLs, cabeceras y manejo de responses.
12- """
13-
149 def __init__ (self , settings : FiscalApiSettings ):
1510 self .settings = settings
1611 self .api_version = settings .api_version
1712 self .base_url = settings .api_url
1813 self .api_key = settings .api_key
1914
2015 def _get_headers (self ) -> dict :
21- """
22- Construye las cabeceras http necesarias.
23- """
2416 return {
2517 "Content-Type" : "application/json" ,
2618 "X-TENANT-KEY" : self .settings .tenant ,
@@ -29,43 +21,77 @@ def _get_headers(self) -> dict:
2921 }
3022
3123 def _request (self , method : str , endpoint : str , ** kwargs ) -> requests .Response :
32- """
33- Realiza una llamada HTTP con la librería requests.
34- """
3524 url = f"{ self .base_url } /api/{ self .api_version } /{ endpoint } "
3625 headers = self ._get_headers ()
3726
38- # Unir los headers definidos por el usuario con los headers por defecto.
3927 if "headers" in kwargs :
40- headers .update (kwargs ["headers" ])
41- del kwargs ["headers" ]
28+ headers .update (kwargs .pop ("headers" ))
29+
30+ # Disable certificate validation (for development only!)
31+ kwargs .setdefault ("verify" , False )
32+
33+ return requests .request (method = method , url = url , headers = headers , ** kwargs )
4234
43- response = requests .request ( method = method , url = url , headers = headers , ** kwargs )
44- response . raise_for_status () # Levanta excepciones para errores HTTP
45- return response
35+ def _process_response ( self , response : requests .Response , response_model : Type [ T ]) -> ApiResponse [ T ]:
36+ status_code = response . status_code
37+ raw_content = response . text
4638
47- def _process_response (self , response : requests .Response , response_model : Type [BaseModel ]) -> ApiResponse :
48- """
49- Procesa y valida la respuesta de la API.
50- """
5139 try :
5240 response_data = response .json ()
41+ except ValueError :
42+ return ApiResponse [T ](
43+ succeeded = False ,
44+ http_status_code = status_code ,
45+ message = "Error processing server response" ,
46+ details = raw_content ,
47+ data = None
48+ )
5349
54- # Procesar el campo `data` utilizando los modelos con alias
50+ if 200 <= status_code < 300 :
5551 if "data" in response_data and response_data ["data" ] is not None :
5652 response_data ["data" ] = response_model .model_validate (response_data ["data" ])
53+ return ApiResponse [T ].model_validate (response_data )
5754
58- return ApiResponse .model_validate (response_data )
59- except Exception as e :
60- print (f"Error al procesar la respuesta: { e } " )
61- print (f"Response data: { response .json ()} " )
62- raise
55+ try :
56+ generic_error = ApiResponse [object ].model_validate (response_data )
57+ except Exception :
58+ return ApiResponse [T ](
59+ succeeded = False ,
60+ http_status_code = status_code ,
61+ message = "Error processing server error response" ,
62+ details = raw_content ,
63+ data = None
64+ )
6365
66+ if status_code == 400 and isinstance (response_data .get ("data" ), list ):
67+ try :
68+ failures = [ValidationFailure .model_validate (item ) for item in response_data ["data" ]]
69+ if failures :
70+ details_str = "; " .join (f"{ f .propertyName } : { f .errorMessage } " for f in failures )
71+ return ApiResponse [T ](
72+ succeeded = False ,
73+ http_status_code = 400 ,
74+ message = generic_error .message ,
75+ details = details_str ,
76+ data = None
77+ )
78+ except Exception :
79+ pass
6480
81+ return ApiResponse [T ](
82+ succeeded = False ,
83+ http_status_code = status_code ,
84+ message = generic_error .message or f"HTTP Error { status_code } " ,
85+ details = generic_error .details or raw_content ,
86+ data = None
87+ )
6588
6689 def send_request (self , method : str , endpoint : str , response_model : Type [T ], ** kwargs ) -> ApiResponse [T ]:
67- """
68- Envía una solicitud HTTP y devuelve la respuesta deserializada en un ApiResponse.
69- """
90+ payload = kwargs .pop ("payload" , None )
91+ if payload is not None and isinstance (payload , BaseModel ):
92+ # Excluir propiedades con valor None
93+ kwargs ["json" ] = payload .model_dump (mode = "json" , by_alias = True , exclude_none = True )
94+
95+ print ("Payload Request:" , kwargs .get ("json" ))
7096 response = self ._request (method , endpoint , ** kwargs )
7197 return self ._process_response (response , response_model )
0 commit comments