iatoolkit 0.7.4__py3-none-any.whl → 0.7.6__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.

Potentially problematic release.


This version of iatoolkit might be problematic. Click here for more details.

Files changed (57) hide show
  1. common/__init__.py +0 -0
  2. common/auth.py +200 -0
  3. common/exceptions.py +46 -0
  4. common/routes.py +86 -0
  5. common/session_manager.py +25 -0
  6. common/util.py +358 -0
  7. iatoolkit/iatoolkit.py +3 -3
  8. {iatoolkit-0.7.4.dist-info → iatoolkit-0.7.6.dist-info}/METADATA +1 -1
  9. iatoolkit-0.7.6.dist-info/RECORD +80 -0
  10. iatoolkit-0.7.6.dist-info/top_level.txt +6 -0
  11. infra/__init__.py +5 -0
  12. infra/call_service.py +140 -0
  13. infra/connectors/__init__.py +5 -0
  14. infra/connectors/file_connector.py +17 -0
  15. infra/connectors/file_connector_factory.py +57 -0
  16. infra/connectors/google_cloud_storage_connector.py +53 -0
  17. infra/connectors/google_drive_connector.py +68 -0
  18. infra/connectors/local_file_connector.py +46 -0
  19. infra/connectors/s3_connector.py +33 -0
  20. infra/gemini_adapter.py +356 -0
  21. infra/google_chat_app.py +57 -0
  22. infra/llm_client.py +430 -0
  23. infra/llm_proxy.py +139 -0
  24. infra/llm_response.py +40 -0
  25. infra/mail_app.py +145 -0
  26. infra/openai_adapter.py +90 -0
  27. infra/redis_session_manager.py +76 -0
  28. repositories/__init__.py +5 -0
  29. repositories/database_manager.py +95 -0
  30. repositories/document_repo.py +33 -0
  31. repositories/llm_query_repo.py +91 -0
  32. repositories/models.py +309 -0
  33. repositories/profile_repo.py +118 -0
  34. repositories/tasks_repo.py +52 -0
  35. repositories/vs_repo.py +139 -0
  36. views/__init__.py +5 -0
  37. views/change_password_view.py +91 -0
  38. views/chat_token_request_view.py +98 -0
  39. views/chat_view.py +51 -0
  40. views/download_file_view.py +58 -0
  41. views/external_chat_login_view.py +88 -0
  42. views/external_login_view.py +40 -0
  43. views/file_store_view.py +58 -0
  44. views/forgot_password_view.py +64 -0
  45. views/history_view.py +57 -0
  46. views/home_view.py +34 -0
  47. views/llmquery_view.py +65 -0
  48. views/login_view.py +60 -0
  49. views/prompt_view.py +37 -0
  50. views/signup_view.py +87 -0
  51. views/tasks_review_view.py +83 -0
  52. views/tasks_view.py +98 -0
  53. views/user_feedback_view.py +74 -0
  54. views/verify_user_view.py +55 -0
  55. iatoolkit-0.7.4.dist-info/RECORD +0 -30
  56. iatoolkit-0.7.4.dist-info/top_level.txt +0 -2
  57. {iatoolkit-0.7.4.dist-info → iatoolkit-0.7.6.dist-info}/WHEEL +0 -0
common/util.py ADDED
@@ -0,0 +1,358 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ import logging
7
+ from typing import List
8
+ from common.exceptions import IAToolkitException
9
+ from injector import inject
10
+ import os
11
+ from jinja2 import Environment, FileSystemLoader
12
+ from common.session_manager import SessionManager
13
+ from datetime import datetime, date
14
+ from decimal import Decimal
15
+ import yaml
16
+ from cryptography.fernet import Fernet
17
+ import base64
18
+
19
+
20
+ class Utility:
21
+ @inject
22
+ def __init__(self):
23
+ self.encryption_key = os.getenv('FERNET_KEY')
24
+
25
+ @staticmethod
26
+ def resolve_user_identifier(external_user_id: str = None, local_user_id: int = 0) -> tuple[str, bool]:
27
+ """
28
+ Resuelve un identificador único de usuario desde external_user_id o local_user_id.
29
+
30
+ Lógica:
31
+ - Si external_user_id existe y no está vacío: usar external_user_id
32
+ - Si no, y local_user_id > 0: obtener email de la sesión actual y retornarlo como ID
33
+ - Si ninguno: retornar string vacío
34
+
35
+ """
36
+ if external_user_id and external_user_id.strip():
37
+ return external_user_id.strip(), False
38
+ elif local_user_id and local_user_id > 0:
39
+ # get the user information from the session
40
+ user_data = SessionManager.get('user')
41
+ if user_data:
42
+ return user_data.get('email', ''), True
43
+
44
+ return "", False
45
+
46
+ def render_prompt_from_template(self,
47
+ template_pathname: str,
48
+ query: str = None,
49
+ client_data: dict = {},
50
+ **kwargs) -> str:
51
+
52
+ try:
53
+ # Normalizar la ruta para que funcione en cualquier SO
54
+ template_pathname = os.path.abspath(template_pathname)
55
+ template_dir = os.path.dirname(template_pathname)
56
+ template_file = os.path.basename(template_pathname)
57
+
58
+ env = Environment(loader=FileSystemLoader(template_dir))
59
+ template = env.get_template(template_file)
60
+
61
+ kwargs["query"] = query
62
+
63
+ # add all the keys in client_data to kwargs
64
+ kwargs.update(client_data)
65
+
66
+ # render my dynamic prompt
67
+ prompt = template.render(**kwargs)
68
+ return prompt
69
+ except Exception as e:
70
+ logging.exception(e)
71
+ raise IAToolkitException(IAToolkitException.ErrorType.TEMPLATE_ERROR,
72
+ f'No se pudo renderizar el template: {template_pathname}, error: {str(e)}') from e
73
+
74
+ def render_prompt_from_string(self,
75
+ template_string: str,
76
+ searchpath: str | list[str] = None,
77
+ query: str = None,
78
+ client_data: dict = {},
79
+ **kwargs) -> str:
80
+ """
81
+ Renderiza un prompt a partir de un string de plantilla Jinja2.
82
+
83
+ :param template_string: El string que contiene la plantilla Jinja2.
84
+ :param searchpath: Una ruta o lista de rutas a directorios para buscar plantillas incluidas (con {% include %}).
85
+ :param query: El query principal a pasar a la plantilla.
86
+ :param client_data: Un diccionario con datos adicionales para la plantilla.
87
+ :param kwargs: Argumentos adicionales para la plantilla.
88
+ :return: El prompt renderizado como un string.
89
+ """
90
+ try:
91
+ # Si se proporciona un searchpath, se usa un FileSystemLoader para permitir includes.
92
+ if searchpath:
93
+ loader = FileSystemLoader(searchpath)
94
+ else:
95
+ loader = None # Sin loader, no se pueden incluir plantillas desde archivos.
96
+
97
+ env = Environment(loader=loader)
98
+ template = env.from_string(template_string)
99
+
100
+ kwargs["query"] = query
101
+ kwargs.update(client_data)
102
+
103
+ prompt = template.render(**kwargs)
104
+ return prompt
105
+ except Exception as e:
106
+ logging.exception(e)
107
+ raise IAToolkitException(IAToolkitException.ErrorType.TEMPLATE_ERROR,
108
+ f'No se pudo renderizar el template desde el string, error: {str(e)}') from e
109
+
110
+
111
+ def serialize(self, obj):
112
+ if isinstance(obj, datetime) or isinstance(obj, date):
113
+ return obj.isoformat()
114
+ elif isinstance(obj, Decimal):
115
+ return float(obj)
116
+ elif isinstance(obj, bytes):
117
+ return obj.decode('utf-8')
118
+ else:
119
+ raise TypeError(f"Type {type(obj)} not serializable")
120
+
121
+ def encrypt_key(self, key: str) -> str:
122
+ if not self.encryption_key:
123
+ raise IAToolkitException(IAToolkitException.ErrorType.CRYPT_ERROR,
124
+ 'No se pudo obtener variable de ambiente para encriptar')
125
+
126
+ if not key:
127
+ raise IAToolkitException(IAToolkitException.ErrorType.CRYPT_ERROR,
128
+ 'falta la clave a encriptar')
129
+ try:
130
+ cipher_suite = Fernet(self.encryption_key.encode('utf-8'))
131
+
132
+ encrypted_key = cipher_suite.encrypt(key.encode('utf-8'))
133
+ encrypted_key_str = base64.urlsafe_b64encode(encrypted_key).decode('utf-8')
134
+
135
+ return encrypted_key_str
136
+ except Exception as e:
137
+ raise IAToolkitException(IAToolkitException.ErrorType.CRYPT_ERROR,
138
+ f'No se pudo encriptar la clave: {str(e)}') from e
139
+
140
+ def decrypt_key(self, encrypted_key: str) -> str:
141
+ if not self.encryption_key:
142
+ raise IAToolkitException(IAToolkitException.ErrorType.CRYPT_ERROR,
143
+ 'No se pudo obtener variable de ambiente para desencriptar')
144
+ if not encrypted_key:
145
+ raise IAToolkitException(IAToolkitException.ErrorType.CRYPT_ERROR,
146
+ 'falta la clave a encriptar')
147
+
148
+ try:
149
+ # transform to bytes first
150
+ encrypted_data_from_storage_bytes = base64.urlsafe_b64decode(encrypted_key.encode('utf-8'))
151
+
152
+ cipher_suite = Fernet(self.encryption_key.encode('utf-8'))
153
+ decrypted_key_bytes = cipher_suite.decrypt(encrypted_data_from_storage_bytes)
154
+ return decrypted_key_bytes.decode('utf-8')
155
+ except Exception as e:
156
+ raise IAToolkitException(IAToolkitException.ErrorType.CRYPT_ERROR,
157
+ f'No se pudo desencriptar la clave: {str(e)}') from e
158
+
159
+ def load_schema_from_yaml(self, file_path):
160
+ with open(file_path, 'r', encoding='utf-8') as f:
161
+ schema = yaml.safe_load(f)
162
+ return schema
163
+
164
+ def generate_context_for_schema(self, entity_name: str, schema_file: str = None, schema: dict = {}) -> str:
165
+ if not schema_file and not schema:
166
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
167
+ f'No se pudo obtener schema de la entidad: {entity_name}')
168
+
169
+ try:
170
+ if schema_file:
171
+ schema = self.load_schema_from_yaml(schema_file)
172
+ table_schema = self.generate_schema_table(schema)
173
+ return table_schema
174
+ except Exception as e:
175
+ logging.exception(e)
176
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
177
+ f'No se pudo leer el schema de la entidad: {entity_name}') from e
178
+
179
+ def generate_schema_table(self, schema: dict) -> str:
180
+ """
181
+ Genera una descripción detallada y formateada en Markdown de un esquema.
182
+ Esta función está diseñada para manejar el formato específico de nuestros
183
+ archivos YAML, donde el esquema se define bajo una única clave raíz.
184
+ """
185
+ if not schema or not isinstance(schema, dict):
186
+ return ""
187
+
188
+ # Asumimos que el YAML tiene una única clave raíz que nombra a la entidad.
189
+ if len(schema) == 1:
190
+ root_name = list(schema.keys())[0]
191
+ root_details = schema[root_name]
192
+
193
+ if isinstance(root_details, dict):
194
+ # Las claves de metadatos describen el objeto en sí, no sus propiedades hijas.
195
+ METADATA_KEYS = ['description', 'type', 'format', 'items', 'properties']
196
+
197
+ # Las propiedades son las claves restantes en el diccionario.
198
+ properties = {
199
+ k: v for k, v in root_details.items() if k not in METADATA_KEYS
200
+ }
201
+
202
+ # La descripción del objeto raíz.
203
+ root_description = root_details.get('description', '')
204
+
205
+ # Formatea las propiedades extraídas usando la función auxiliar recursiva.
206
+ formatted_properties = self._format_json_schema(properties, 0)
207
+
208
+ # Construcción del resultado final, incluyendo el nombre del objeto raíz.
209
+ output_parts = [f"\n\n### Objeto: `{root_name}`"]
210
+ if root_description:
211
+ # Limpia la descripción para que se muestre bien
212
+ cleaned_description = '\n'.join(line.strip() for line in root_description.strip().split('\n'))
213
+ output_parts.append(f"{cleaned_description}")
214
+
215
+ if formatted_properties:
216
+ output_parts.append(f"**Campos del objeto `{root_name}`:**\n{formatted_properties}")
217
+
218
+ return "\n".join(output_parts)
219
+
220
+ # Si el esquema (como tender_schema.yaml) no tiene un objeto raíz,
221
+ # se formatea directamente como una lista de propiedades.
222
+ return self._format_json_schema(schema, 0)
223
+
224
+ def _format_json_schema(self, properties: dict, indent_level: int) -> str:
225
+ """
226
+ Formatea de manera recursiva las propiedades de un esquema JSON/YAML.
227
+ """
228
+ output = []
229
+ indent_str = ' ' * indent_level
230
+
231
+ for name, details in properties.items():
232
+ if not isinstance(details, dict):
233
+ continue
234
+
235
+ description = details.get('description', '')
236
+ data_type = details.get('type', 'any')
237
+ output.append(f"{indent_str}- **`{name.lower()}`** ({data_type}): {description}")
238
+
239
+ child_indent_str = ' ' * (indent_level + 1)
240
+
241
+ # Manejo de 'oneOf' para mostrar valores constantes
242
+ if 'oneOf' in details:
243
+ for item in details['oneOf']:
244
+ if 'const' in item:
245
+ const_desc = item.get('description', '')
246
+ output.append(f"{child_indent_str}- `{item['const']}`: {const_desc}")
247
+
248
+ # Manejo de 'items' para arrays
249
+ if 'items' in details:
250
+ items_details = details.get('items', {})
251
+ if isinstance(items_details, dict):
252
+ item_description = items_details.get('description')
253
+ if item_description:
254
+ # Limpiamos y añadimos la descripción del item
255
+ cleaned_description = '\n'.join(
256
+ f"{line.strip()}" for line in item_description.strip().split('\n')
257
+ )
258
+ output.append(
259
+ f"{child_indent_str}*Descripción de los elementos del array:*\n{child_indent_str}{cleaned_description}")
260
+
261
+ if 'properties' in items_details:
262
+ nested_properties = self._format_json_schema(items_details['properties'], indent_level + 1)
263
+ output.append(nested_properties)
264
+
265
+ # Manejo de 'properties' para objetos anidados estándar
266
+ if 'properties' in details:
267
+ nested_properties = self._format_json_schema(details['properties'], indent_level + 1)
268
+ output.append(nested_properties)
269
+
270
+ elif 'additionalProperties' in details and 'properties' in details.get('additionalProperties', {}):
271
+ # Imprime un marcador de posición para la clave dinámica.
272
+ output.append(
273
+ f"{child_indent_str}- **[*]** (object): Las claves de este objeto son dinámicas (ej. un ID).")
274
+ # Procesa las propiedades del objeto anidado.
275
+ nested_properties = self._format_json_schema(details['additionalProperties']['properties'],
276
+ indent_level + 2)
277
+ output.append(nested_properties)
278
+
279
+ return '\n'.join(output)
280
+
281
+ def load_markdown_context(self, filepath: str) -> str:
282
+ with open(filepath, 'r', encoding='utf-8') as f:
283
+ return f.read()
284
+
285
+ @classmethod
286
+ def _get_verifier(self, rut: int):
287
+ value = 11 - sum([int(a) * int(b) for a, b in zip(str(rut).zfill(8), '32765432')]) % 11
288
+ return {10: 'K', 11: '0'}.get(value, str(value))
289
+
290
+ def validate_rut(self, rut_str):
291
+ if not rut_str or not isinstance(rut_str, str):
292
+ return False
293
+
294
+ rut_str = rut_str.strip().replace('.', '').upper()
295
+ parts = rut_str.split('-')
296
+ if not len(parts) == 2:
297
+ return False
298
+
299
+ try:
300
+ rut = int(parts[0])
301
+ except ValueError:
302
+ return False
303
+
304
+ if rut < 1000000:
305
+ return False
306
+
307
+ if not len(parts[1]) == 1:
308
+ return False
309
+
310
+ digit = parts[1].upper()
311
+ return digit == self._get_verifier(rut)
312
+
313
+ def get_files_by_extension(self, directory: str, extension: str, return_extension: bool = False) -> List[str]:
314
+ try:
315
+ # Normalizar la extensión (agregar punto si no lo tiene)
316
+ if not extension.startswith('.'):
317
+ extension = '.' + extension
318
+
319
+ # Verificar que el directorio existe
320
+ if not os.path.exists(directory):
321
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
322
+ f'El directorio no existe: {directory}')
323
+
324
+ if not os.path.isdir(directory):
325
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
326
+ f'La ruta no es un directorio: {directory}')
327
+
328
+ # Buscar archivos con la extensión especificada
329
+ files = []
330
+ for filename in os.listdir(directory):
331
+ file_path = os.path.join(directory, filename)
332
+ if os.path.isfile(file_path) and filename.endswith(extension):
333
+ if return_extension:
334
+ files.append(filename)
335
+ else:
336
+ name_without_extension = os.path.splitext(filename)[0]
337
+ files.append(name_without_extension)
338
+
339
+ return sorted(files) # Retornar lista ordenada alfabéticamente
340
+
341
+ except IAToolkitException:
342
+ raise
343
+ except Exception as e:
344
+ logging.exception(e)
345
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
346
+ f'Error al buscar archivos en el directorio {directory}: {str(e)}') from e
347
+
348
+ def is_openai_model(self, model: str) -> bool:
349
+ openai_models = [
350
+ 'gpt-5', 'gpt'
351
+ ]
352
+ return any(openai_model in model.lower() for openai_model in openai_models)
353
+
354
+ def is_gemini_model(self, model: str) -> bool:
355
+ gemini_models = [
356
+ 'gemini', 'gemini-2.5-pro'
357
+ ]
358
+ return any(gemini_model in model.lower() for gemini_model in gemini_models)
iatoolkit/iatoolkit.py CHANGED
@@ -8,10 +8,7 @@ from flask_session import Session
8
8
  from flask_injector import FlaskInjector
9
9
  from flask_bcrypt import Bcrypt
10
10
  from flask_cors import CORS
11
- from common.auth import IAuthentication
12
- from common.util import Utility
13
11
  from common.exceptions import IAToolkitException
14
- from common.session_manager import SessionManager
15
12
  from urllib.parse import urlparse
16
13
  import redis
17
14
  import logging
@@ -294,6 +291,8 @@ class IAToolkit:
294
291
  from infra.llm_proxy import LLMProxy
295
292
  from infra.google_chat_app import GoogleChatApp
296
293
  from infra.mail_app import MailApp
294
+ from common.auth import IAuthentication
295
+ from common.util import Utility
297
296
 
298
297
  binder.bind(LLMProxy, to=LLMProxy, scope=singleton)
299
298
  binder.bind(llmClient, to=llmClient, scope=singleton)
@@ -352,6 +351,7 @@ class IAToolkit:
352
351
  # Configura context processors para templates
353
352
  @self.app.context_processor
354
353
  def inject_globals():
354
+ from common.session_manager import SessionManager
355
355
  return {
356
356
  'url_for': url_for,
357
357
  'iatoolkit_version': self.version,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 0.7.4
3
+ Version: 0.7.6
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -0,0 +1,80 @@
1
+ common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ common/auth.py,sha256=kKBvZoIm8RPpPFZ6KEm1oowHJfJwSO0mf10Yac8SG9k,8456
3
+ common/exceptions.py,sha256=EXx40n5htp7UiOM6P1xfJ9U6NMcADqm62dlFaKz7ICU,1154
4
+ common/routes.py,sha256=_1ywiXorwEeJ8IAamQwSxU9z8jlAAL_U64tMb01-zFg,4188
5
+ common/session_manager.py,sha256=7D_RuJs60w-1zDr3fOGEz9JW7IZlSXuUHgUT87CzaUo,472
6
+ common/util.py,sha256=5zw1wL_FdPBibGJbQAl1kozgTjUpsOZlPqT3tbWsHp4,15515
7
+ iatoolkit/__init__.py,sha256=GkFxAQHKPifz4Kd8M73Rc8TWRVIxjxkl1N0nsPvb_sU,1743
8
+ iatoolkit/base_company.py,sha256=WmD4o0qFC1n5DW5eRsRsuNfaGot9nxGFcJe3LmibSuE,4259
9
+ iatoolkit/cli_commands.py,sha256=BGuThg19eoSssrBJIqzBNaWpMmyy7u4yRUz0JA7d-vc,2270
10
+ iatoolkit/company_registry.py,sha256=tduqt3oV8iDX_IB1eA7KIgvIxE4edTcy-3qZIXh3Lzw,2549
11
+ iatoolkit/iatoolkit.py,sha256=bbwLkLS_xZGNS0Dki3zX73RhAYMS32IshXDIdr_RVXg,15776
12
+ iatoolkit/system_prompts/format_styles.prompt,sha256=MSMe1qvR3cF_0IbFshn8R0z6Wx6VCHQq1p37rpu5wwk,3576
13
+ iatoolkit/system_prompts/query_main.prompt,sha256=w_9ybgWgiQH4V_RbAXqsvz0M7oOuiyhxcwf-D0CgfA4,3017
14
+ iatoolkit/system_prompts/sql_rules.prompt,sha256=y4nURVnb9AyFwt-lrbMNBHHtZlhk6kC9grYoOhRnrJo,59174
15
+ infra/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
16
+ infra/call_service.py,sha256=170qCNJb_9caUiEi8PprQaqSUS3Rj7XmU917J2-m62I,4919
17
+ infra/gemini_adapter.py,sha256=MLesq8Oyx_Jd6-cSA_41zC1UtDM6CMino054NEybOJM,15031
18
+ infra/google_chat_app.py,sha256=XHyFQyX92ztpJQLvNx4mKPjbR2x7_15t_dcpVqW25ZQ,1927
19
+ infra/llm_client.py,sha256=cN1uaZxOP7H8LkmFpSpQMesBH3Pg1LKJuent4r1yovg,18548
20
+ infra/llm_proxy.py,sha256=qBird4-k3Z8ABECvnWXbljXCQWx2q6RTmylJZQLDk4U,5684
21
+ infra/llm_response.py,sha256=YUUQPBHzmP3Ce6-t0kKMRIpowvh7de1odSoefEByIvI,904
22
+ infra/mail_app.py,sha256=eWSOz5uWf-KmZoUJbYFPad-0uWP2LekINAamDYbxYpg,6093
23
+ infra/openai_adapter.py,sha256=roPrp-DJWcHHhOXdUttsgwfhJpMhHNy1RpkdzVKQMFo,3510
24
+ infra/redis_session_manager.py,sha256=Dkwii8fns0_Vd9hm9GmotmrKtSCxVps868U5sqj7FAE,2278
25
+ infra/connectors/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
26
+ infra/connectors/file_connector.py,sha256=HOjRTFd-WfDOcFyvHncAhnGNZuFgChIwC-P6osPo9ZM,352
27
+ infra/connectors/file_connector_factory.py,sha256=xa4hphbeNVt0l4nn_mNbBT31lgn-OMlIQ8TtXpgN42M,2068
28
+ infra/connectors/google_cloud_storage_connector.py,sha256=5KDZlT7ndgkqFOC3SebrmSQpP2zSb3KnSBVXWWt7Alg,2040
29
+ infra/connectors/google_drive_connector.py,sha256=MyRECNWvLf5j-YvNwokcwihRS1WhZnBDUjx8tSup2L0,2527
30
+ infra/connectors/local_file_connector.py,sha256=FO9_HFGKRE_GQqOjyWuIHyNFXYUgro39CxxqEcXUq1c,1719
31
+ infra/connectors/s3_connector.py,sha256=M91gWnOgyCYIsoB0xka2oSaOTL4gDgjfjHwZi20jgnw,1113
32
+ repositories/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
33
+ repositories/database_manager.py,sha256=UWOZhbUjNQV0yoL9xevuAnxV2OuBkMaxu5nqaqMuMQE,3098
34
+ repositories/document_repo.py,sha256=dq41jj5hBOd6mRKdaSRMhyiHk12ukX43njVyLuuGl44,1094
35
+ repositories/llm_query_repo.py,sha256=NF8Ybye_sMVvzeQGa_5MGJup0AFvl_P1Z0oCsr2Wz0Y,3592
36
+ repositories/models.py,sha256=RJH0dtq2VIDJFaFDufV05NUnNW4WtNpdTjhONtfLyCw,13179
37
+ repositories/profile_repo.py,sha256=JWbClu9SM18koUceLQdeuT5DA37h6ij5aeK-nwoRgIk,4042
38
+ repositories/tasks_repo.py,sha256=cCg4uz8ou4tkx7yzmktEV4lfQjQBU-RylcqUof2dytY,1725
39
+ repositories/vs_repo.py,sha256=PP6iPf0rt64rMyvqg-ffJk__sLbT2wgxx3dta3o8Vuo,5270
40
+ services/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
41
+ services/benchmark_service.py,sha256=_ruKh9YzrTLtR0ZKrRNxqJQW0HdbwWuFz1gfLzJ9owA,5850
42
+ services/dispatcher_service.py,sha256=y2J-TfrkoCcsOESr3E6E3q5bleJfRibT4ONxs9AxUSg,13959
43
+ services/document_service.py,sha256=8MsJz0pihM9E9Z92PrPqDgQnpMAmpFrbogXr5HscMWM,5926
44
+ services/excel_service.py,sha256=ATPaeAvkLwQAkPZ3AKIUpO73RVyRg0D8c6i37_mcql0,3559
45
+ services/file_processor_service.py,sha256=98yWYF7nIq1nm7nh6IzMmTKaOMTIeqCFWYwXVtV-ZJI,4102
46
+ services/history_service.py,sha256=j0QCqcIIyw7DBy3GrZrEZNk0I4m-uuRoG5g0Z2RCcOE,1586
47
+ services/jwt_service.py,sha256=YoZ9h7_o9xBko-arNQv4MbcwnxoSWVNj4VbZmMo_QGY,3908
48
+ services/load_documents_service.py,sha256=UGfomYz7seWFXawbDuk2t6CyoEr1vggR8vmrCUAeLBg,7190
49
+ services/mail_service.py,sha256=_67pctxZO46DHnWBip51ayuYtWd4bEoS1kg29ACO7_I,2162
50
+ services/profile_service.py,sha256=vTK9TvH_2AFdqgL3sGjhck9LyLGIbvdC2USoaRx82G8,17561
51
+ services/prompt_manager_service.py,sha256=7SMC6N_T4BP4m5-ZNYAL3Y2YWHwl1bytXSgnEqu5bWI,8301
52
+ services/query_service.py,sha256=gvUnq0Vpn4gv5ycQk8-fklh7NDFIXpO1Vt1lT2ugO6Q,15283
53
+ services/search_service.py,sha256=bB3FWFxJi1iYsOdBxyu9tzIO406nQxcyeQzEowpmpjY,1803
54
+ services/sql_service.py,sha256=s84K1ADlvMtum949wgMh8jsmqlOUeL-m_SWfAM4Wsv4,2141
55
+ services/tasks_service.py,sha256=1DdbERlAxIkCpGEylnHDKC-KAsXRJugbaRSzRbPfL58,6790
56
+ services/user_feedback_service.py,sha256=_LeNBYz4hHFapXfYTQVfkkD34gE8j2UeKnyOZ8H0nWo,2442
57
+ services/user_session_context_service.py,sha256=GluNSgqP6W_hFke4oslSnfGnU_b-ph28BHH6jf3EIm0,3797
58
+ views/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
59
+ views/change_password_view.py,sha256=KY6mf25Vc-rJn8pTLuJlkIHB7j9_NI0UoxIWod32qpM,3930
60
+ views/chat_token_request_view.py,sha256=7Bgf7rOVdgo4u4nU4lZWk87wi_D9_-sYxB9zBuL8TcA,4388
61
+ views/chat_view.py,sha256=XvKJApeJPU9uoH0XpHusehNbge3dDwwr72iMcuBveBY,2048
62
+ views/download_file_view.py,sha256=AIHLURyPi7w0k_Rs_fqz0yOp1Hav8K73AC5Bxx4vzf4,1980
63
+ views/external_chat_login_view.py,sha256=pz8EPmTSrYHpr3WbfYYBbPvSKaDJ9taOro4bMqTAZZE,3732
64
+ views/external_login_view.py,sha256=2NPzdxIqw1zvRHcsKOVvL896mE9B4GCUI6gBW7LsZCU,1335
65
+ views/file_store_view.py,sha256=591Kc2QR7SdxMrcJcL1ciMEwRopCpqYbmODL4XZ2SU8,1913
66
+ views/forgot_password_view.py,sha256=7W2VP3nZlUdgGLv-zc7Pj5wa7OsT3K66t6fkntrSPzI,2705
67
+ views/history_view.py,sha256=nkvV6Dg6SrVWZsWQJaCpy7D9VygwXDQF5FVanhzHdeo,2044
68
+ views/home_view.py,sha256=lQvDYUJ1X-xu0GuJcZJwF9iW74d834_q8fCkLgqo6j4,1238
69
+ views/llmquery_view.py,sha256=_CsbtFQFRuGCe50TWJDtTSUyaM3kMMwkn54kVuxMEQw,2412
70
+ views/login_view.py,sha256=s2FQPo8EuJCWPoC2hs1Wyqh8rY6Dc_5HmiL9b9GLhho,2322
71
+ views/prompt_view.py,sha256=ARuZ64EYjBhMAClUvjeFcr7zcgZUt2rEktbUvqpiE2U,1237
72
+ views/signup_view.py,sha256=Jn2F0gGBDmyUYt4vo0fw2UMHoJblau-Be62zNAMas50,3795
73
+ views/tasks_review_view.py,sha256=nvxdUJowfpgrJk0m8quxYg4iw5BZOhZhpDpPt5-wsDI,3368
74
+ views/tasks_view.py,sha256=TWW48wzcJzqIPat1kSRx41Et92-wLr-wCkgypWYZ7dk,4090
75
+ views/user_feedback_view.py,sha256=inZoNl5YnEq9_2FXOVvzKymXuSPELLmi9zPNG3zQxEQ,2632
76
+ views/verify_user_view.py,sha256=0hRKIudox7KjxZDH973trUhw4fZtc7eiLz6R44QbIG8,2341
77
+ iatoolkit-0.7.6.dist-info/METADATA,sha256=ewI228_gn4LwbqX0Dmd39uey9MCnkMRxpsL6Dcs5ujg,9300
78
+ iatoolkit-0.7.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
79
+ iatoolkit-0.7.6.dist-info/top_level.txt,sha256=ZXCST2AUsVPIeMiz49ZFn7WEfIiO4d8BfWY9QzUqP0s,51
80
+ iatoolkit-0.7.6.dist-info/RECORD,,
@@ -0,0 +1,6 @@
1
+ common
2
+ iatoolkit
3
+ infra
4
+ repositories
5
+ services
6
+ views
infra/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
infra/call_service.py ADDED
@@ -0,0 +1,140 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from common.exceptions import IAToolkitException
7
+ from injector import inject
8
+ # call_service.py
9
+ import requests
10
+ from typing import Optional, Dict, Any, Tuple, Union
11
+ from requests import RequestException
12
+
13
+ class CallServiceClient:
14
+ @inject
15
+ def __init__(self):
16
+ self.headers = {'Content-Type': 'application/json'}
17
+
18
+ def _merge_headers(self, extra: Optional[Dict[str, str]]) -> Dict[str, str]:
19
+ if not extra:
20
+ return dict(self.headers)
21
+ merged = dict(self.headers)
22
+ merged.update(extra)
23
+ return merged
24
+
25
+ def _normalize_timeout(self, timeout: Union[int, float, Tuple[int, int], Tuple[float, float]]) -> Tuple[float, float]:
26
+ # Si pasan un solo número → (connect, read) = (10, timeout)
27
+ if isinstance(timeout, (int, float)):
28
+ return (10, float(timeout))
29
+ return (float(timeout[0]), float(timeout[1]))
30
+
31
+ def _deserialize_response(self, response) -> Tuple[Any, int]:
32
+ try:
33
+ return response.json(), response.status_code
34
+ except ValueError:
35
+ # No es JSON → devolver texto
36
+ return response.text, response.status_code
37
+
38
+ def get(
39
+ self,
40
+ endpoint: str,
41
+ params: Optional[Dict[str, Any]] = None,
42
+ headers: Optional[Dict[str, str]] = None,
43
+ timeout: Union[int, float, Tuple[int, int]] = 10
44
+ ):
45
+ try:
46
+ response = requests.get(
47
+ endpoint,
48
+ params=params,
49
+ headers=self._merge_headers(headers),
50
+ timeout=self._normalize_timeout(timeout)
51
+ )
52
+ except RequestException as e:
53
+ raise IAToolkitException(IAToolkitException.ErrorType.REQUEST_ERROR, str(e))
54
+ return self._deserialize_response(response)
55
+
56
+ def post(
57
+ self,
58
+ endpoint: str,
59
+ json_dict: Optional[Dict[str, Any]] = None,
60
+ params: Optional[Dict[str, Any]] = None,
61
+ headers: Optional[Dict[str, str]] = None,
62
+ timeout: Union[int, float, Tuple[int, int]] = 10
63
+ ):
64
+ try:
65
+ response = requests.post(
66
+ endpoint,
67
+ params=params,
68
+ json=json_dict,
69
+ headers=self._merge_headers(headers),
70
+ timeout=self._normalize_timeout(timeout)
71
+ )
72
+ except RequestException as e:
73
+ raise IAToolkitException(IAToolkitException.ErrorType.REQUEST_ERROR, str(e))
74
+ return self._deserialize_response(response)
75
+
76
+ def put(
77
+ self,
78
+ endpoint: str,
79
+ json_dict: Optional[Dict[str, Any]] = None,
80
+ params: Optional[Dict[str, Any]] = None,
81
+ headers: Optional[Dict[str, str]] = None,
82
+ timeout: Union[int, float, Tuple[int, int]] = 10
83
+ ):
84
+ try:
85
+ response = requests.put(
86
+ endpoint,
87
+ params=params,
88
+ json=json_dict,
89
+ headers=self._merge_headers(headers),
90
+ timeout=self._normalize_timeout(timeout)
91
+ )
92
+ except RequestException as e:
93
+ raise IAToolkitException(IAToolkitException.ErrorType.REQUEST_ERROR, str(e))
94
+ return self._deserialize_response(response)
95
+
96
+ def delete(
97
+ self,
98
+ endpoint: str,
99
+ json_dict: Optional[Dict[str, Any]] = None,
100
+ params: Optional[Dict[str, Any]] = None,
101
+ headers: Optional[Dict[str, str]] = None,
102
+ timeout: Union[int, float, Tuple[int, int]] = 10
103
+ ):
104
+ try:
105
+ response = requests.delete(
106
+ endpoint,
107
+ params=params,
108
+ json=json_dict,
109
+ headers=self._merge_headers(headers),
110
+ timeout=self._normalize_timeout(timeout)
111
+ )
112
+ except RequestException as e:
113
+ raise IAToolkitException(IAToolkitException.ErrorType.REQUEST_ERROR, str(e))
114
+ return self._deserialize_response(response)
115
+
116
+ def post_files(
117
+ self,
118
+ endpoint: str,
119
+ data: Dict[str, Any],
120
+ params: Optional[Dict[str, Any]] = None,
121
+ headers: Optional[Dict[str, str]] = None,
122
+ timeout: Union[int, float, Tuple[int, int]] = 10
123
+ ):
124
+ # Para multipart/form-data no imponemos Content-Type por defecto
125
+ merged_headers = dict(self.headers)
126
+ merged_headers.pop('Content-Type', None)
127
+ if headers:
128
+ merged_headers.update(headers)
129
+
130
+ try:
131
+ response = requests.post(
132
+ endpoint,
133
+ params=params,
134
+ files=data,
135
+ headers=merged_headers,
136
+ timeout=self._normalize_timeout(timeout)
137
+ )
138
+ except RequestException as e:
139
+ raise IAToolkitException(IAToolkitException.ErrorType.REQUEST_ERROR, str(e))
140
+ return self._deserialize_response(response)
@@ -0,0 +1,5 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
@@ -0,0 +1,17 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from abc import ABC, abstractmethod
7
+ from typing import List
8
+
9
+
10
+ class FileConnector(ABC):
11
+ @abstractmethod
12
+ def list_files(self) -> List[str]:
13
+ pass
14
+
15
+ @abstractmethod
16
+ def get_file_content(self, file_path: str) -> bytes:
17
+ pass