eco-back 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
eco_back/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """
2
+ eco-back - Librería Python
3
+ """
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ # Módulos principales
8
+ from . import database
9
+ from . import api
10
+
11
+ __all__ = ["database", "api", "__version__"]
@@ -0,0 +1,9 @@
1
+ """
2
+ Módulo de cliente API para eco-back
3
+ """
4
+
5
+ from .config import APIConfig
6
+ from .client import APIClient
7
+ from .registro import Consecutivo
8
+
9
+ __all__ = ["APIConfig", "APIClient", "Consecutivo"]
eco_back/api/client.py ADDED
@@ -0,0 +1,278 @@
1
+ """
2
+ Cliente HTTP para consumir APIs
3
+ """
4
+ from typing import Optional, Dict, Any, Union
5
+ import logging
6
+
7
+ try:
8
+ import requests
9
+ from requests.exceptions import RequestException, Timeout, HTTPError
10
+ REQUESTS_AVAILABLE = True
11
+ except ImportError:
12
+ REQUESTS_AVAILABLE = False
13
+
14
+ from .config import APIConfig
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class APIClient:
20
+ """
21
+ Cliente HTTP genérico para consumir APIs REST
22
+
23
+ Ejemplo:
24
+ >>> from eco_back.api import APIConfig, APIClient
25
+ >>>
26
+ >>> config = APIConfig(base_url="https://api.ejemplo.com")
27
+ >>> client = APIClient(config)
28
+ >>>
29
+ >>> # GET request
30
+ >>> data = client.get("/users/1")
31
+ >>>
32
+ >>> # POST request
33
+ >>> response = client.post("/users", data={"name": "Juan"})
34
+ """
35
+
36
+ def __init__(self, config: APIConfig):
37
+ """
38
+ Inicializa el cliente API
39
+
40
+ Args:
41
+ config: Configuración del API
42
+
43
+ Raises:
44
+ ImportError: Si requests no está instalado
45
+ """
46
+ if not REQUESTS_AVAILABLE:
47
+ raise ImportError(
48
+ "requests no está instalado. Instálalo con: pip install requests"
49
+ )
50
+
51
+ self.config = config
52
+ self.session = requests.Session()
53
+
54
+ # Configurar headers por defecto
55
+ if self.config.headers:
56
+ self.session.headers.update(self.config.headers)
57
+
58
+ def _build_url(self, endpoint: str) -> str:
59
+ """
60
+ Construye la URL completa
61
+
62
+ Args:
63
+ endpoint: Endpoint del API
64
+
65
+ Returns:
66
+ URL completa
67
+ """
68
+ # Asegurar que el endpoint empiece con /
69
+ if not endpoint.startswith('/'):
70
+ endpoint = f'/{endpoint}'
71
+
72
+ return f'{self.config.base_url}{endpoint}'
73
+
74
+ def _handle_response(
75
+ self,
76
+ response: requests.Response,
77
+ raise_for_status: bool = True
78
+ ) -> Optional[Union[Dict[str, Any], list, str]]:
79
+ """
80
+ Procesa la respuesta HTTP
81
+
82
+ Args:
83
+ response: Respuesta HTTP
84
+ raise_for_status: Si debe lanzar excepción en errores HTTP
85
+
86
+ Returns:
87
+ Datos parseados de la respuesta o None
88
+ """
89
+ if raise_for_status:
90
+ try:
91
+ response.raise_for_status()
92
+ except HTTPError as e:
93
+ logger.error(f"Error HTTP {response.status_code}: {e}")
94
+ raise
95
+
96
+ if response.status_code == 204: # No Content
97
+ return None
98
+
99
+ try:
100
+ return response.json()
101
+ except ValueError:
102
+ # Si no es JSON, retornar el texto
103
+ return response.text
104
+
105
+ def get(
106
+ self,
107
+ endpoint: str,
108
+ params: Optional[Dict[str, Any]] = None,
109
+ headers: Optional[Dict[str, str]] = None,
110
+ timeout: Optional[int] = None,
111
+ raise_for_status: bool = True
112
+ ) -> Optional[Union[Dict[str, Any], list, str]]:
113
+ """
114
+ Realiza una petición GET
115
+
116
+ Args:
117
+ endpoint: Endpoint del API
118
+ params: Parámetros query string
119
+ headers: Headers adicionales
120
+ timeout: Tiempo de espera (usa el default si no se especifica)
121
+ raise_for_status: Si debe lanzar excepción en errores HTTP
122
+
123
+ Returns:
124
+ Respuesta parseada
125
+
126
+ Raises:
127
+ RequestException: Si hay error en la petición
128
+ """
129
+ url = self._build_url(endpoint)
130
+ timeout = timeout or self.config.timeout
131
+
132
+ try:
133
+ logger.debug(f"GET {url}")
134
+ response = self.session.get(
135
+ url,
136
+ params=params,
137
+ headers=headers,
138
+ timeout=timeout,
139
+ verify=self.config.verify_ssl
140
+ )
141
+ return self._handle_response(response, raise_for_status)
142
+ except Timeout:
143
+ logger.error(f"Timeout al hacer GET a {url}")
144
+ raise
145
+ except RequestException as e:
146
+ logger.error(f"Error en GET {url}: {e}")
147
+ raise
148
+
149
+ def post(
150
+ self,
151
+ endpoint: str,
152
+ data: Optional[Dict[str, Any]] = None,
153
+ json: Optional[Dict[str, Any]] = None,
154
+ headers: Optional[Dict[str, str]] = None,
155
+ timeout: Optional[int] = None,
156
+ raise_for_status: bool = True
157
+ ) -> Optional[Union[Dict[str, Any], list, str]]:
158
+ """
159
+ Realiza una petición POST
160
+
161
+ Args:
162
+ endpoint: Endpoint del API
163
+ data: Datos form-encoded
164
+ json: Datos JSON
165
+ headers: Headers adicionales
166
+ timeout: Tiempo de espera
167
+ raise_for_status: Si debe lanzar excepción en errores HTTP
168
+
169
+ Returns:
170
+ Respuesta parseada
171
+ """
172
+ url = self._build_url(endpoint)
173
+ timeout = timeout or self.config.timeout
174
+
175
+ try:
176
+ logger.debug(f"POST {url}")
177
+ response = self.session.post(
178
+ url,
179
+ data=data,
180
+ json=json,
181
+ headers=headers,
182
+ timeout=timeout,
183
+ verify=self.config.verify_ssl
184
+ )
185
+ return self._handle_response(response, raise_for_status)
186
+ except RequestException as e:
187
+ logger.error(f"Error en POST {url}: {e}")
188
+ raise
189
+
190
+ def put(
191
+ self,
192
+ endpoint: str,
193
+ data: Optional[Dict[str, Any]] = None,
194
+ json: Optional[Dict[str, Any]] = None,
195
+ headers: Optional[Dict[str, str]] = None,
196
+ timeout: Optional[int] = None,
197
+ raise_for_status: bool = True
198
+ ) -> Optional[Union[Dict[str, Any], list, str]]:
199
+ """
200
+ Realiza una petición PUT
201
+
202
+ Args:
203
+ endpoint: Endpoint del API
204
+ data: Datos form-encoded
205
+ json: Datos JSON
206
+ headers: Headers adicionales
207
+ timeout: Tiempo de espera
208
+ raise_for_status: Si debe lanzar excepción en errores HTTP
209
+
210
+ Returns:
211
+ Respuesta parseada
212
+ """
213
+ url = self._build_url(endpoint)
214
+ timeout = timeout or self.config.timeout
215
+
216
+ try:
217
+ logger.debug(f"PUT {url}")
218
+ response = self.session.put(
219
+ url,
220
+ data=data,
221
+ json=json,
222
+ headers=headers,
223
+ timeout=timeout,
224
+ verify=self.config.verify_ssl
225
+ )
226
+ return self._handle_response(response, raise_for_status)
227
+ except RequestException as e:
228
+ logger.error(f"Error en PUT {url}: {e}")
229
+ raise
230
+
231
+ def delete(
232
+ self,
233
+ endpoint: str,
234
+ headers: Optional[Dict[str, str]] = None,
235
+ timeout: Optional[int] = None,
236
+ raise_for_status: bool = True
237
+ ) -> Optional[Union[Dict[str, Any], list, str]]:
238
+ """
239
+ Realiza una petición DELETE
240
+
241
+ Args:
242
+ endpoint: Endpoint del API
243
+ headers: Headers adicionales
244
+ timeout: Tiempo de espera
245
+ raise_for_status: Si debe lanzar excepción en errores HTTP
246
+
247
+ Returns:
248
+ Respuesta parseada
249
+ """
250
+ url = self._build_url(endpoint)
251
+ timeout = timeout or self.config.timeout
252
+
253
+ try:
254
+ logger.debug(f"DELETE {url}")
255
+ response = self.session.delete(
256
+ url,
257
+ headers=headers,
258
+ timeout=timeout,
259
+ verify=self.config.verify_ssl
260
+ )
261
+ return self._handle_response(response, raise_for_status)
262
+ except RequestException as e:
263
+ logger.error(f"Error en DELETE {url}: {e}")
264
+ raise
265
+
266
+ def close(self):
267
+ """Cierra la sesión"""
268
+ if self.session:
269
+ self.session.close()
270
+
271
+ def __enter__(self):
272
+ """Context manager entry"""
273
+ return self
274
+
275
+ def __exit__(self, exc_type, exc_val, exc_tb):
276
+ """Context manager exit"""
277
+ self.close()
278
+ return False
eco_back/api/config.py ADDED
@@ -0,0 +1,58 @@
1
+ """
2
+ Configuración para clientes API
3
+ """
4
+ from dataclasses import dataclass
5
+ from typing import Optional, Dict
6
+
7
+
8
+ @dataclass
9
+ class APIConfig:
10
+ """
11
+ Configuración para el cliente API
12
+
13
+ Args:
14
+ base_url: URL base del API
15
+ timeout: Tiempo de espera para las peticiones en segundos
16
+ headers: Headers personalizados para las peticiones
17
+ verify_ssl: Verificar certificados SSL
18
+ """
19
+ base_url: str
20
+ timeout: int = 30
21
+ headers: Optional[Dict[str, str]] = None
22
+ verify_ssl: bool = True
23
+
24
+ def __post_init__(self):
25
+ """Inicialización después de crear la instancia"""
26
+ if self.headers is None:
27
+ self.headers = {}
28
+
29
+ # Asegurar que la URL base no termine con /
30
+ if self.base_url.endswith('/'):
31
+ self.base_url = self.base_url[:-1]
32
+
33
+ @classmethod
34
+ def from_dict(cls, config: dict) -> "APIConfig":
35
+ """
36
+ Crea una instancia de APIConfig desde un diccionario
37
+
38
+ Args:
39
+ config: Diccionario con la configuración
40
+
41
+ Returns:
42
+ Instancia de APIConfig
43
+ """
44
+ return cls(**config)
45
+
46
+ def to_dict(self) -> dict:
47
+ """
48
+ Convierte la configuración a diccionario
49
+
50
+ Returns:
51
+ Diccionario con la configuración
52
+ """
53
+ return {
54
+ "base_url": self.base_url,
55
+ "timeout": self.timeout,
56
+ "headers": self.headers,
57
+ "verify_ssl": self.verify_ssl,
58
+ }
@@ -0,0 +1,91 @@
1
+ """
2
+ Cliente para operaciones de registro y consecutivos UNP
3
+ """
4
+ from typing import Optional
5
+ import logging
6
+
7
+ from .client import APIClient
8
+ from .config import APIConfig
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class Consecutivo:
14
+ """
15
+ Cliente para obtener consecutivos de la API UNP
16
+
17
+ Maneja operaciones relacionadas con la obtención de consecutivos.
18
+
19
+ Ejemplo:
20
+ >>> from eco_back.api import Consecutivo
21
+ >>>
22
+ >>> client = Consecutivo(base_url="https://api.unp.com")
23
+ >>> numero = client.obtener(origen=1)
24
+ >>> print(numero)
25
+ """
26
+
27
+ def __init__(self, base_url: str, timeout: int = 30, verify_ssl: bool = True):
28
+ """
29
+ Inicializa el cliente de Consecutivos
30
+
31
+ Args:
32
+ base_url: URL base del API UNP
33
+ timeout: Tiempo de espera para las peticiones
34
+ verify_ssl: Verificar certificados SSL
35
+ """
36
+ config = APIConfig(
37
+ base_url=base_url,
38
+ timeout=timeout,
39
+ verify_ssl=verify_ssl
40
+ )
41
+ self.client = APIClient(config)
42
+
43
+ def obtener(self, origen: int = 1) -> Optional[int]:
44
+ """
45
+ Obtiene un consecutivo desde la API UNP
46
+
47
+ Args:
48
+ origen: Identificador del origen (default: 1)
49
+
50
+ Returns:
51
+ Número consecutivo o None si hay error
52
+
53
+ Example:
54
+ >>> client = Consecutivo(base_url="https://api.unp.com")
55
+ >>> numero = client.obtener(origen=1)
56
+ >>> if numero:
57
+ ... print(f"Consecutivo obtenido: {numero}")
58
+ """
59
+ try:
60
+ endpoint = f'api-auth/users/consecutivos/{origen}/'
61
+ response = self.client.get(endpoint, raise_for_status=False)
62
+
63
+ if response and isinstance(response, dict):
64
+ consecutivo = response.get('consecutivo')
65
+ logger.info(f"Consecutivo obtenido: {consecutivo} para origen {origen}")
66
+ return consecutivo
67
+ else:
68
+ logger.warning(f"No se pudo obtener consecutivo para origen {origen}")
69
+ return None
70
+
71
+ except Exception as e:
72
+ logger.error(f"Error al obtener consecutivo: {e}")
73
+ return None
74
+
75
+ # Alias para compatibilidad
76
+ def obtener_consecutivo(self, origen: int = 1) -> Optional[int]:
77
+ """Alias de obtener() para compatibilidad"""
78
+ return self.obtener(origen)
79
+
80
+ def close(self):
81
+ """Cierra el cliente"""
82
+ self.client.close()
83
+
84
+ def __enter__(self):
85
+ """Context manager entry"""
86
+ return self
87
+
88
+ def __exit__(self, exc_type, exc_val, exc_tb):
89
+ """Context manager exit"""
90
+ self.close()
91
+ return False
@@ -0,0 +1,9 @@
1
+ """
2
+ Módulo de gestión de base de datos para eco-back
3
+ """
4
+
5
+ from .connection import DatabaseConnection
6
+ from .config import DatabaseConfig
7
+ from .postgis import PostGISHelper
8
+
9
+ __all__ = ["DatabaseConnection", "DatabaseConfig", "PostGISHelper"]
@@ -0,0 +1,58 @@
1
+ """
2
+ Configuración de base de datos
3
+ """
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+
8
+ @dataclass
9
+ class DatabaseConfig:
10
+ """
11
+ Configuración para la conexión a la base de datos
12
+
13
+ Args:
14
+ host: Dirección del servidor de base de datos
15
+ port: Puerto de conexión
16
+ database: Nombre de la base de datos
17
+ user: Usuario de la base de datos
18
+ password: Contraseña del usuario
19
+ charset: Conjunto de caracteres (default: utf8mb4)
20
+ connect_timeout: Tiempo de espera para la conexión en segundos
21
+ """
22
+ host: str
23
+ port: int
24
+ database: str
25
+ user: str
26
+ password: str
27
+ charset: str = "utf8mb4"
28
+ connect_timeout: int = 10
29
+
30
+ @classmethod
31
+ def from_dict(cls, config: dict) -> "DatabaseConfig":
32
+ """
33
+ Crea una instancia de DatabaseConfig desde un diccionario
34
+
35
+ Args:
36
+ config: Diccionario con la configuración
37
+
38
+ Returns:
39
+ Instancia de DatabaseConfig
40
+ """
41
+ return cls(**config)
42
+
43
+ def to_dict(self) -> dict:
44
+ """
45
+ Convierte la configuración a diccionario
46
+
47
+ Returns:
48
+ Diccionario con la configuración
49
+ """
50
+ return {
51
+ "host": self.host,
52
+ "port": self.port,
53
+ "database": self.database,
54
+ "user": self.user,
55
+ "password": self.password,
56
+ "charset": self.charset,
57
+ "connect_timeout": self.connect_timeout,
58
+ }