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 +11 -0
- eco_back/api/__init__.py +9 -0
- eco_back/api/client.py +278 -0
- eco_back/api/config.py +58 -0
- eco_back/api/registro.py +91 -0
- eco_back/database/__init__.py +9 -0
- eco_back/database/config.py +58 -0
- eco_back/database/connection.py +198 -0
- eco_back/database/models.py +49 -0
- eco_back/database/postgis.py +350 -0
- eco_back/database/repository.py +152 -0
- eco_back-0.1.0.dist-info/METADATA +191 -0
- eco_back-0.1.0.dist-info/RECORD +16 -0
- eco_back-0.1.0.dist-info/WHEEL +5 -0
- eco_back-0.1.0.dist-info/licenses/LICENSE +21 -0
- eco_back-0.1.0.dist-info/top_level.txt +1 -0
eco_back/__init__.py
ADDED
eco_back/api/__init__.py
ADDED
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
|
+
}
|
eco_back/api/registro.py
ADDED
|
@@ -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,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
|
+
}
|