iatoolkit 0.108.1__tar.gz → 1.21.0__tar.gz

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.
Files changed (154) hide show
  1. {iatoolkit-0.108.1/src/iatoolkit.egg-info → iatoolkit-1.21.0}/PKG-INFO +1 -1
  2. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/__init__.py +5 -5
  3. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/cli_commands.py +16 -0
  4. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/common/interfaces/database_provider.py +13 -8
  5. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/common/routes.py +58 -6
  6. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/common/util.py +19 -114
  7. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/core.py +5 -2
  8. iatoolkit-1.21.0/src/iatoolkit/infra/connectors/file_connector.py +28 -0
  9. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/connectors/file_connector_factory.py +23 -27
  10. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +14 -0
  11. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/connectors/google_drive_connector.py +32 -1
  12. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/connectors/local_file_connector.py +22 -0
  13. iatoolkit-1.21.0/src/iatoolkit/infra/connectors/s3_connector.py +61 -0
  14. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/llm_providers/deepseek_adapter.py +17 -1
  15. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/llm_providers/gemini_adapter.py +117 -18
  16. iatoolkit-1.21.0/src/iatoolkit/infra/llm_providers/openai_adapter.py +219 -0
  17. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/llm_response.py +13 -0
  18. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/locales/en.yaml +235 -3
  19. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/locales/es.yaml +233 -5
  20. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/repositories/database_manager.py +27 -47
  21. iatoolkit-1.21.0/src/iatoolkit/repositories/document_repo.py +72 -0
  22. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/repositories/llm_query_repo.py +53 -19
  23. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/repositories/models.py +109 -5
  24. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/repositories/profile_repo.py +3 -4
  25. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/repositories/vs_repo.py +23 -25
  26. iatoolkit-1.21.0/src/iatoolkit/services/company_context_service.py +397 -0
  27. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/configuration_service.py +272 -76
  28. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/dispatcher_service.py +1 -4
  29. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/file_processor_service.py +0 -8
  30. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/i18n_service.py +7 -0
  31. iatoolkit-1.21.0/src/iatoolkit/services/ingestor_service.py +297 -0
  32. iatoolkit-1.21.0/src/iatoolkit/services/knowledge_base_service.py +479 -0
  33. iatoolkit-1.21.0/src/iatoolkit/services/language_service.py +131 -0
  34. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/llm_client_service.py +61 -2
  35. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/prompt_service.py +248 -170
  36. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/query_service.py +49 -20
  37. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/sql_service.py +17 -0
  38. iatoolkit-1.21.0/src/iatoolkit/services/storage_service.py +184 -0
  39. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/tool_service.py +3 -2
  40. iatoolkit-1.21.0/src/iatoolkit/static/js/chat_filepond.js +210 -0
  41. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/js/chat_main.js +105 -52
  42. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/styles/chat_iatoolkit.css +96 -0
  43. iatoolkit-1.21.0/src/iatoolkit/system_prompts/query_main.prompt +59 -0
  44. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/templates/chat.html +18 -8
  45. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/base_login_view.py +1 -1
  46. iatoolkit-1.21.0/src/iatoolkit/views/categories_api_view.py +111 -0
  47. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/chat_view.py +1 -1
  48. iatoolkit-1.21.0/src/iatoolkit/views/configuration_api_view.py +163 -0
  49. iatoolkit-1.21.0/src/iatoolkit/views/ingestion_api_view.py +100 -0
  50. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/load_document_api_view.py +14 -10
  51. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/login_view.py +1 -1
  52. iatoolkit-1.21.0/src/iatoolkit/views/prompt_api_view.py +118 -0
  53. iatoolkit-1.21.0/src/iatoolkit/views/rag_api_view.py +216 -0
  54. {iatoolkit-0.108.1 → iatoolkit-1.21.0/src/iatoolkit.egg-info}/PKG-INFO +1 -1
  55. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit.egg-info/SOURCES.txt +7 -3
  56. iatoolkit-0.108.1/src/iatoolkit/infra/connectors/file_connector.py +0 -17
  57. iatoolkit-0.108.1/src/iatoolkit/infra/connectors/s3_connector.py +0 -33
  58. iatoolkit-0.108.1/src/iatoolkit/infra/llm_providers/openai_adapter.py +0 -124
  59. iatoolkit-0.108.1/src/iatoolkit/repositories/document_repo.py +0 -33
  60. iatoolkit-0.108.1/src/iatoolkit/services/company_context_service.py +0 -212
  61. iatoolkit-0.108.1/src/iatoolkit/services/language_service.py +0 -89
  62. iatoolkit-0.108.1/src/iatoolkit/services/load_documents_service.py +0 -174
  63. iatoolkit-0.108.1/src/iatoolkit/services/search_service.py +0 -55
  64. iatoolkit-0.108.1/src/iatoolkit/static/js/chat_filepond.js +0 -85
  65. iatoolkit-0.108.1/src/iatoolkit/system_prompts/query_main.prompt +0 -74
  66. iatoolkit-0.108.1/src/iatoolkit/views/load_company_configuration_api_view.py +0 -49
  67. iatoolkit-0.108.1/src/iatoolkit/views/prompt_api_view.py +0 -37
  68. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/LICENSE +0 -0
  69. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/LICENSE_COMMUNITY.md +0 -0
  70. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/pyproject.toml +0 -0
  71. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/readme.md +0 -0
  72. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/requirements.txt +0 -0
  73. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/setup.cfg +0 -0
  74. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/base_company.py +0 -0
  75. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/common/__init__.py +0 -0
  76. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/common/exceptions.py +0 -0
  77. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/common/interfaces/__init__.py +0 -0
  78. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/common/interfaces/asset_storage.py +0 -0
  79. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/common/model_registry.py +0 -0
  80. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/common/session_manager.py +0 -0
  81. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/company_registry.py +0 -0
  82. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/__init__.py +0 -0
  83. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/brevo_mail_app.py +0 -0
  84. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/call_service.py +0 -0
  85. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/connectors/__init__.py +0 -0
  86. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/google_chat_app.py +0 -0
  87. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/llm_providers/__init__.py +0 -0
  88. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/llm_proxy.py +0 -0
  89. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/infra/redis_session_manager.py +0 -0
  90. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/repositories/__init__.py +0 -0
  91. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/repositories/filesystem_asset_repository.py +0 -0
  92. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/__init__.py +0 -0
  93. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/auth_service.py +0 -0
  94. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/benchmark_service.py +0 -0
  95. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/branding_service.py +0 -0
  96. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/document_service.py +0 -0
  97. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/embedding_service.py +0 -0
  98. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/excel_service.py +0 -0
  99. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/history_manager_service.py +0 -0
  100. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/jwt_service.py +0 -0
  101. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/license_service.py +0 -0
  102. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/mail_service.py +0 -0
  103. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/profile_service.py +0 -0
  104. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/user_feedback_service.py +0 -0
  105. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/services/user_session_context_service.py +0 -0
  106. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/images/fernando.jpeg +0 -0
  107. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/images/iatoolkit_core.png +0 -0
  108. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/images/iatoolkit_logo.png +0 -0
  109. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
  110. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/js/chat_help_content.js +0 -0
  111. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/js/chat_history_button.js +0 -0
  112. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
  113. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/js/chat_model_selector.js +0 -0
  114. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
  115. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
  116. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/js/chat_reload_button.js +0 -0
  117. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/styles/chat_modal.css +0 -0
  118. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/styles/chat_public.css +0 -0
  119. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/styles/documents.css +0 -0
  120. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/styles/landing_page.css +0 -0
  121. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/styles/llm_output.css +0 -0
  122. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/static/styles/onboarding.css +0 -0
  123. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/system_prompts/__init__.py +0 -0
  124. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/system_prompts/format_styles.prompt +0 -0
  125. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/system_prompts/sql_rules.prompt +0 -0
  126. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/templates/_company_header.html +0 -0
  127. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/templates/_login_widget.html +0 -0
  128. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/templates/base.html +0 -0
  129. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/templates/change_password.html +0 -0
  130. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/templates/chat_modals.html +0 -0
  131. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/templates/error.html +0 -0
  132. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/templates/forgot_password.html +0 -0
  133. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/templates/onboarding_shell.html +0 -0
  134. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/templates/signup.html +0 -0
  135. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/__init__.py +0 -0
  136. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/change_password_view.py +0 -0
  137. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/embedding_api_view.py +0 -0
  138. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/forgot_password_view.py +0 -0
  139. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/help_content_api_view.py +0 -0
  140. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/history_api_view.py +0 -0
  141. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/home_view.py +0 -0
  142. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/init_context_api_view.py +0 -0
  143. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/llmquery_api_view.py +0 -0
  144. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/logout_api_view.py +0 -0
  145. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/profile_api_view.py +0 -0
  146. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/root_redirect_view.py +0 -0
  147. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/signup_view.py +0 -0
  148. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/static_page_view.py +0 -0
  149. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
  150. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/users_api_view.py +0 -0
  151. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit/views/verify_user_view.py +0 -0
  152. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
  153. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit.egg-info/requires.txt +0 -0
  154. {iatoolkit-0.108.1 → iatoolkit-1.21.0}/src/iatoolkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 0.108.1
3
+ Version: 1.21.0
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- __version__ = "0.108.1"
6
+ __version__ = "1.21.0"
7
7
 
8
8
  # Expose main classes and functions at the top level of the package
9
9
 
@@ -17,9 +17,9 @@ from .base_company import BaseCompany
17
17
  # --- Services ---
18
18
  from iatoolkit.services.query_service import QueryService
19
19
  from iatoolkit.services.document_service import DocumentService
20
- from iatoolkit.services.search_service import SearchService
20
+ from iatoolkit.services.knowledge_base_service import KnowledgeBaseService
21
21
  from iatoolkit.services.sql_service import SqlService
22
- from iatoolkit.services.load_documents_service import LoadDocumentsService
22
+ from iatoolkit.services.ingestor_service import IngestorService
23
23
  from iatoolkit.infra.call_service import CallServiceClient
24
24
  from iatoolkit.services.profile_service import ProfileService
25
25
  from iatoolkit.services.mail_service import MailService
@@ -36,8 +36,8 @@ __all__ = [
36
36
  'QueryService',
37
37
  'SqlService',
38
38
  'DocumentService',
39
- 'SearchService',
40
- 'LoadDocumentsService',
39
+ 'KnowledgeBaseService',
40
+ 'IngestorService',
41
41
  'CallServiceClient',
42
42
  'ProfileService',
43
43
  'MailService',
@@ -7,6 +7,7 @@ import click
7
7
  import logging
8
8
  from iatoolkit.core import IAToolkit
9
9
  from iatoolkit.services.profile_service import ProfileService
10
+ from iatoolkit.services.prompt_service import PromptService
10
11
 
11
12
 
12
13
  def register_core_commands(app):
@@ -32,6 +33,21 @@ def register_core_commands(app):
32
33
  logging.exception(e)
33
34
  click.echo(f"❌ unexpectd error during the configuration: {e}")
34
35
 
36
+ @app.cli.command("init-company")
37
+ @click.argument("company_short_name")
38
+ def init_company(company_short_name: str):
39
+ """⚙️ Bootstrap a new company."""
40
+ try:
41
+ prompt_service = IAToolkit.get_instance().get_injector().get(PromptService)
42
+ click.echo(f"🔑 Bootstrap company: '{company_short_name}'...")
43
+ result = prompt_service.register_system_prompts(company_short_name)
44
+
45
+ if result:
46
+ click.echo("✅ System prompts registered successfully!")
47
+ except Exception as e:
48
+ logging.exception(e)
49
+ click.echo(f"❌ unexpected error during the configuration: {e}")
50
+
35
51
  @app.cli.command("encrypt-key")
36
52
  @click.argument("key")
37
53
  def encrypt_llm_api_key(key: str):
@@ -9,14 +9,19 @@ class DatabaseProvider(abc.ABC):
9
9
 
10
10
  # --- Schema Methods ---
11
11
  @abc.abstractmethod
12
- def get_all_table_names(self) -> List[str]:
13
- pass
14
-
15
- @abc.abstractmethod
16
- def get_table_description(self,
17
- table_name: str,
18
- schema_object_name: str | None = None,
19
- exclude_columns: List[str] | None = None) -> str:
12
+ def get_database_structure(self) -> dict:
13
+ """
14
+ Returns the structure of the database (tables, columns, types)
15
+ Format:
16
+ {
17
+ "table_name": {
18
+ "columns": [
19
+ {"name": "col1", "type": "VARCHAR", "nullable": True, "pk": True},
20
+ ...
21
+ ]
22
+ }
23
+ }
24
+ """
20
25
  pass
21
26
 
22
27
  # --- Execution Methods ---
@@ -24,13 +24,16 @@ def register_views(app):
24
24
  from iatoolkit.views.profile_api_view import UserLanguageApiView
25
25
  from iatoolkit.views.embedding_api_view import EmbeddingApiView
26
26
  from iatoolkit.views.login_view import LoginView, FinalizeContextView
27
- from iatoolkit.views.load_company_configuration_api_view import LoadCompanyConfigurationApiView
27
+ from iatoolkit.views.configuration_api_view import ConfigurationApiView, ValidateConfigurationApiView
28
28
  from iatoolkit.views.logout_api_view import LogoutApiView
29
29
  from iatoolkit.views.home_view import HomeView
30
30
  from iatoolkit.views.chat_view import ChatView
31
31
  from iatoolkit.views.static_page_view import StaticPageView
32
32
  from iatoolkit.views.root_redirect_view import RootRedirectView
33
33
  from iatoolkit.views.users_api_view import UsersApiView
34
+ from iatoolkit.views.rag_api_view import RagApiView
35
+ from iatoolkit.views.categories_api_view import CategoriesApiView
36
+ from iatoolkit.views.ingestion_api_view import IngestionApiView
34
37
 
35
38
  # assign root '/' to our new redirect logic
36
39
  app.add_url_rule('/home', view_func=RootRedirectView.as_view('root_redirect'))
@@ -84,24 +87,73 @@ def register_views(app):
84
87
  # can be used also for executing iatoolkit prompts
85
88
  app.add_url_rule('/<company_short_name>/api/llm_query', view_func=LLMQueryApiView.as_view('llm_query_api'))
86
89
 
87
- # open the promt directory
88
- app.add_url_rule('/<company_short_name>/api/prompts', view_func=PromptApiView.as_view('prompt'))
89
-
90
+ # Categories Endpoint
91
+ app.add_url_rule('/<company_short_name>/api/categories',
92
+ view_func=CategoriesApiView.as_view('categories_api'),
93
+ methods=['GET', 'POST'])
94
+
95
+ # open the promt directory and specific prompt management
96
+ prompt_view = PromptApiView.as_view('prompt')
97
+ app.add_url_rule('/<company_short_name>/api/prompts',
98
+ view_func=prompt_view,
99
+ methods=['GET', 'POST'],
100
+ defaults={'prompt_name': None})
101
+
102
+ app.add_url_rule('/<company_short_name>/api/prompts/<prompt_name>',
103
+ view_func=prompt_view,
104
+ methods=['GET', 'POST','PUT', 'DELETE'])
90
105
  # toolbar buttons
91
106
  app.add_url_rule('/<company_short_name>/api/feedback', view_func=UserFeedbackApiView.as_view('feedback'))
92
107
  app.add_url_rule('/<company_short_name>/api/history', view_func=HistoryApiView.as_view('history'))
93
108
  app.add_url_rule('/<company_short_name>/api/help-content', view_func=HelpContentApiView.as_view('help-content'))
94
109
 
110
+ # --- RAG API Routes ---
111
+ rag_view = RagApiView.as_view('rag_api')
112
+
113
+ # 1. List Files (POST for filters)
114
+ app.add_url_rule('/api/rag/<company_short_name>/files',
115
+ view_func=rag_view,
116
+ methods=['POST'],
117
+ defaults={'action': 'list_files'})
118
+
119
+ # 2. Delete File
120
+ app.add_url_rule('/api/rag/<company_short_name>/files/<int:document_id>',
121
+ view_func=rag_view,
122
+ methods=['DELETE'],
123
+ defaults={'action': 'delete_file'})
124
+
125
+ # 3. Search Lab
126
+ app.add_url_rule('/api/rag/<company_short_name>/search',
127
+ view_func=rag_view,
128
+ methods=['POST'],
129
+ defaults={'action': 'search'})
130
+
131
+ # 4. Get File Content (View/Download)
132
+ app.add_url_rule('/api/rag/<company_short_name>/files/<int:document_id>/content',
133
+ view_func=rag_view,
134
+ methods=['GET'],
135
+ defaults={'action': 'get_file_content'})
136
+
95
137
  # this endpoint is for upload documents into the vector store (api-key)
96
138
  app.add_url_rule('/api/load-document', view_func=LoadDocumentApiView.as_view('load-document'), methods=['POST'])
97
139
 
140
+ # Document ingestion
141
+ ingestion_view = IngestionApiView.as_view('ingestion_api')
142
+ app.add_url_rule('/<company_short_name>/api/ingestion-sources', view_func=ingestion_view, methods=['GET', 'POST'])
143
+ app.add_url_rule('/<company_short_name>/api/ingestion-sources/<int:source_id>/<action>', view_func=ingestion_view, methods=['POST'])
144
+
98
145
  # this endpoint is for generating embeddings for a given text
99
146
  app.add_url_rule('/<company_short_name>/api/embedding',
100
147
  view_func=EmbeddingApiView.as_view('embedding_api'))
101
148
 
102
149
  # company configuration
103
- app.add_url_rule('/<company_short_name>/api/load_configuration',
104
- view_func=LoadCompanyConfigurationApiView.as_view('load-configuration'))
150
+ app.add_url_rule('/<company_short_name>/api/configuration',
151
+ view_func=ConfigurationApiView.as_view('configuration'),
152
+ methods=['GET', 'POST', 'PATCH'],)
153
+
154
+ app.add_url_rule('/<company_short_name>/api/configuration/validate',
155
+ view_func=ValidateConfigurationApiView.as_view('configuration-validate'),
156
+ methods=['GET'])
105
157
 
106
158
  # static pages
107
159
  # url: /pages/foundation o /pages/implementation_plan
@@ -162,128 +162,33 @@ class Utility:
162
162
  Parses a YAML string into a dictionary securely.
163
163
  """
164
164
  try:
165
+ if not yaml_content:
166
+ return {}
167
+
168
+ # Normalizar tabulaciones que rompen YAML
165
169
  yaml_content = yaml_content.replace('\t', ' ')
166
- return yaml.safe_load(yaml_content) or {}
170
+
171
+ loaded = yaml.safe_load(yaml_content)
172
+ # Asegurar que siempre retornamos un dict, incluso si el YAML es una lista o escalar
173
+ return loaded if isinstance(loaded, dict) else {}
167
174
  except yaml.YAMLError as e:
168
175
  logging.error(f"Error parsing YAML string: {e}")
169
176
  return {}
170
177
 
171
- def generate_context_for_schema(self, entity_name: str, schema_file: str = None, schema: dict = {}) -> str:
172
- if not schema_file and not schema:
173
- raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
174
- f'No se pudo obtener schema de la entidad: {entity_name}')
175
-
176
- try:
177
- if schema_file:
178
- schema = self.load_schema_from_yaml(schema_file)
179
- table_schema = self.generate_schema_table(schema)
180
- return table_schema
181
- except Exception as e:
182
- logging.exception(e)
183
- raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
184
- f'No se pudo leer el schema de la entidad: {entity_name}') from e
185
-
186
- def generate_schema_table(self, schema: dict) -> str:
178
+ def dump_yaml_to_string(self, config: dict) -> str:
187
179
  """
188
- Genera una descripción detallada y formateada en Markdown de un esquema.
189
- Esta función está diseñada para manejar el formato específico de nuestros
190
- archivos YAML, donde el esquema se define bajo una única clave raíz.
180
+ Dumps a dictionary into a YAML formatted string.
181
+ It uses default flow style False to ensure block format (readable YAML).
191
182
  """
192
- if not schema or not isinstance(schema, dict):
193
- return ""
194
-
195
- # Asumimos que el YAML tiene una única clave raíz que nombra a la entidad.
196
- if len(schema) == 1:
197
- root_name = list(schema.keys())[0]
198
- root_details = schema[root_name]
199
-
200
- if isinstance(root_details, dict):
201
- # Las claves de metadatos describen el objeto en sí, no sus propiedades hijas.
202
- METADATA_KEYS = ['description', 'type', 'format', 'items', 'properties']
203
-
204
- # Las propiedades son las claves restantes en el diccionario.
205
- properties = {
206
- k: v for k, v in root_details.items() if k not in METADATA_KEYS
207
- }
208
-
209
- # La descripción del objeto raíz.
210
- root_description = root_details.get('description', '')
211
-
212
- # Formatea las propiedades extraídas usando la función auxiliar recursiva.
213
- formatted_properties = self._format_json_schema(properties, 0)
214
-
215
- # Construcción del resultado final, incluyendo el nombre del objeto raíz.
216
- output_parts = [f"\n\n### Objeto: `{root_name}`"]
217
- if root_description:
218
- # Limpia la descripción para que se muestre bien
219
- cleaned_description = '\n'.join(line.strip() for line in root_description.strip().split('\n'))
220
- output_parts.append(f"{cleaned_description}")
221
-
222
- if formatted_properties:
223
- output_parts.append(f"**Campos del objeto `{root_name}`:**\n{formatted_properties}")
224
-
225
- return "\n".join(output_parts)
226
-
227
- # Si el esquema (como tender_schema.yaml) no tiene un objeto raíz,
228
- # se formatea directamente como una lista de propiedades.
229
- return self._format_json_schema(schema, 0)
183
+ try:
184
+ # default_flow_style=False ensures lists and dicts are expanded (not inline like JSON)
185
+ # allow_unicode=True ensures characters like accents are preserved
186
+ return yaml.safe_dump(config, default_flow_style=False, allow_unicode=True, sort_keys=False)
187
+ except yaml.YAMLError as e:
188
+ logging.error(f"Error dumping YAML to string: {e}")
189
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
190
+ f"Failed to generate YAML: {e}")
230
191
 
231
- def _format_json_schema(self, properties: dict, indent_level: int) -> str:
232
- """
233
- Formatea de manera recursiva las propiedades de un esquema JSON/YAML.
234
- """
235
- output = []
236
- indent_str = ' ' * indent_level
237
-
238
- for name, details in properties.items():
239
- if not isinstance(details, dict):
240
- continue
241
-
242
- description = details.get('description', '')
243
- data_type = details.get('type', 'any')
244
- output.append(f"{indent_str}- **`{name.lower()}`** ({data_type}): {description}")
245
-
246
- child_indent_str = ' ' * (indent_level + 1)
247
-
248
- # Manejo de 'oneOf' para mostrar valores constantes
249
- if 'oneOf' in details:
250
- for item in details['oneOf']:
251
- if 'const' in item:
252
- const_desc = item.get('description', '')
253
- output.append(f"{child_indent_str}- `{item['const']}`: {const_desc}")
254
-
255
- # Manejo de 'items' para arrays
256
- if 'items' in details:
257
- items_details = details.get('items', {})
258
- if isinstance(items_details, dict):
259
- item_description = items_details.get('description')
260
- if item_description:
261
- # Limpiamos y añadimos la descripción del item
262
- cleaned_description = '\n'.join(
263
- f"{line.strip()}" for line in item_description.strip().split('\n')
264
- )
265
- output.append(
266
- f"{child_indent_str}*Descripción de los elementos del array:*\n{child_indent_str}{cleaned_description}")
267
-
268
- if 'properties' in items_details:
269
- nested_properties = self._format_json_schema(items_details['properties'], indent_level + 1)
270
- output.append(nested_properties)
271
-
272
- # Manejo de 'properties' para objetos anidados estándar
273
- if 'properties' in details:
274
- nested_properties = self._format_json_schema(details['properties'], indent_level + 1)
275
- output.append(nested_properties)
276
-
277
- elif 'additionalProperties' in details and 'properties' in details.get('additionalProperties', {}):
278
- # Imprime un marcador de posición para la clave dinámica.
279
- output.append(
280
- f"{child_indent_str}- **[*]** (object): Las claves de este objeto son dinámicas (ej. un ID).")
281
- # Procesa las propiedades del objeto anidado.
282
- nested_properties = self._format_json_schema(details['additionalProperties']['properties'],
283
- indent_level + 2)
284
- output.append(nested_properties)
285
-
286
- return '\n'.join(output)
287
192
 
288
193
  def load_markdown_context(self, filepath: str) -> str:
289
194
  with open(filepath, 'r', encoding='utf-8') as f:
@@ -58,6 +58,7 @@ class IAToolkit:
58
58
  self._injector = Injector() # init empty injector
59
59
  self.version = IATOOLKIT_VERSION
60
60
  self.license = "Community Edition"
61
+ self.is_community = True
61
62
 
62
63
  @classmethod
63
64
  def get_instance(cls) -> 'IAToolkit':
@@ -321,7 +322,7 @@ class IAToolkit:
321
322
  from iatoolkit.services.prompt_service import PromptService
322
323
  from iatoolkit.services.excel_service import ExcelService
323
324
  from iatoolkit.services.mail_service import MailService
324
- from iatoolkit.services.load_documents_service import LoadDocumentsService
325
+ from iatoolkit.services.ingestor_service import IngestorService
325
326
  from iatoolkit.services.profile_service import ProfileService
326
327
  from iatoolkit.services.jwt_service import JWTService
327
328
  from iatoolkit.services.dispatcher_service import Dispatcher
@@ -335,6 +336,7 @@ class IAToolkit:
335
336
  from iatoolkit.services.llm_client_service import llmClient
336
337
  from iatoolkit.services.auth_service import AuthService
337
338
  from iatoolkit.services.sql_service import SqlService
339
+ from iatoolkit.services.knowledge_base_service import KnowledgeBaseService
338
340
 
339
341
  binder.bind(QueryService, to=QueryService)
340
342
  binder.bind(BenchmarkService, to=BenchmarkService)
@@ -342,7 +344,7 @@ class IAToolkit:
342
344
  binder.bind(PromptService, to=PromptService)
343
345
  binder.bind(ExcelService, to=ExcelService)
344
346
  binder.bind(MailService, to=MailService)
345
- binder.bind(LoadDocumentsService, to=LoadDocumentsService)
347
+ binder.bind(IngestorService, to=IngestorService)
346
348
  binder.bind(ProfileService, to=ProfileService)
347
349
  binder.bind(JWTService, to=JWTService)
348
350
  binder.bind(Dispatcher, to=Dispatcher)
@@ -356,6 +358,7 @@ class IAToolkit:
356
358
  binder.bind(llmClient, to=llmClient)
357
359
  binder.bind(AuthService, to=AuthService)
358
360
  binder.bind(SqlService, to=SqlService)
361
+ binder.bind(KnowledgeBaseService, to=KnowledgeBaseService)
359
362
 
360
363
  def _bind_infrastructure(self, binder: Binder):
361
364
  from iatoolkit.infra.llm_proxy import LLMProxy
@@ -0,0 +1,28 @@
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, Optional
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
18
+
19
+ @abstractmethod
20
+ def delete_file(self, file_path: str) -> None:
21
+ pass
22
+
23
+ @abstractmethod
24
+ def upload_file(self, file_path: str, content: bytes, content_type: str = None) -> None:
25
+ pass
26
+
27
+ def generate_presigned_url(self, file_path: str, expiration: int = 3600) -> Optional[str]:
28
+ return None
@@ -1,29 +1,19 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
1
+ # ... existing code ...
2
+ import os
3
+ from typing import Dict
6
4
  from iatoolkit.infra.connectors.file_connector import FileConnector
7
5
  from iatoolkit.infra.connectors.local_file_connector import LocalFileConnector
8
6
  from iatoolkit.infra.connectors.s3_connector import S3Connector
9
- from iatoolkit.infra.connectors.google_drive_connector import GoogleDriveConnector
10
7
  from iatoolkit.infra.connectors.google_cloud_storage_connector import GoogleCloudStorageConnector
11
- from typing import Dict
12
- import os
13
-
8
+ from iatoolkit.infra.connectors.google_drive_connector import GoogleDriveConnector
14
9
 
15
10
  class FileConnectorFactory:
16
11
  @staticmethod
17
12
  def create(config: Dict) -> FileConnector:
18
13
  """
19
- Configuración esperada:
20
- {
21
- "type": "local" | "s3" | "gdrive" | "gcs",
22
- "path": "/ruta/local", # solo para local
23
- "bucket": "mi-bucket", "prefix": "datos/", "auth": {...}, # solo para S3
24
- "folder_id": "xxxxxxx", # solo para Google Drive
25
- "bucket": "mi-bucket", "service_account": "/ruta/service_account.json" # solo para GCS
26
- }
14
+ Crea un conector basado en un diccionario de configuración.
15
+ Permite pasar credenciales explícitas en 'auth' o 'service_account_path',
16
+ o dejar que el conector use sus defaults.
27
17
  """
28
18
  connector_type = config.get('type')
29
19
 
@@ -31,27 +21,33 @@ class FileConnectorFactory:
31
21
  return LocalFileConnector(config['path'])
32
22
 
33
23
  elif connector_type == 's3':
34
- auth = {
35
- 'aws_access_key_id': os.getenv('AWS_ACCESS_KEY_ID'),
36
- 'aws_secret_access_key': os.getenv('AWS_SECRET_ACCESS_KEY'),
37
- 'region_name': os.getenv('AWS_REGION', 'us-east-1')
38
- }
24
+ # Permite inyectar auth ya resuelto, o usar defaults de entorno
25
+ auth = config.get('auth')
26
+ if not auth:
27
+ auth = {
28
+ 'aws_access_key_id': os.getenv('AWS_ACCESS_KEY_ID'),
29
+ 'aws_secret_access_key': os.getenv('AWS_SECRET_ACCESS_KEY'),
30
+ 'region_name': os.getenv('AWS_REGION', 'us-east-1')
31
+ }
39
32
 
40
33
  return S3Connector(
41
34
  bucket=config['bucket'],
42
35
  prefix=config.get('prefix', ''),
36
+ folder=config.get('folder', ''),
43
37
  auth=auth
44
38
  )
45
39
 
46
40
  elif connector_type == 'gdrive':
47
- return GoogleDriveConnector(config['folder_id'],
48
- 'service_account.json')
41
+ return GoogleDriveConnector(
42
+ folder_id=config['folder_id'],
43
+ service_account_path=config.get('service_account', 'service_account.json')
44
+ )
49
45
 
50
- elif connector_type == 'gcs':
46
+ elif connector_type in ['gcs', 'google_cloud_storage']:
51
47
  return GoogleCloudStorageConnector(
52
48
  bucket_name=config['bucket'],
53
- service_account_path=config.get('service_account', 'service_account.json')
49
+ service_account_path=config.get('service_account_path', 'service_account.json')
54
50
  )
55
51
 
56
52
  else:
57
- raise ValueError(f"Unknown connector type: {connector_type}")
53
+ raise ValueError(f"Unknown connector type: {connector_type}")
@@ -51,3 +51,17 @@ class GoogleCloudStorageConnector(FileConnector):
51
51
  file_buffer = blob.download_as_bytes() # Descarga el contenido como bytes
52
52
 
53
53
  return file_buffer
54
+
55
+ def delete_file(self, file_path: str) -> None:
56
+ """
57
+ Elimina un archivo del bucket dado su path.
58
+ """
59
+ blob = self.bucket.blob(file_path)
60
+ blob.delete()
61
+
62
+ def upload_file(self, file_path: str, content: bytes, content_type: str = None) -> None:
63
+ """
64
+ Sube un archivo al bucket.
65
+ """
66
+ blob = self.bucket.blob(file_path)
67
+ blob.upload_from_string(content, content_type=content_type)
@@ -5,7 +5,7 @@
5
5
 
6
6
  from iatoolkit.infra.connectors.file_connector import FileConnector
7
7
  from googleapiclient.discovery import build
8
- from googleapiclient.http import MediaIoBaseDownload
8
+ from googleapiclient.http import MediaIoBaseDownload, MediaIoBaseUpload
9
9
  from google.oauth2.service_account import Credentials
10
10
  import io
11
11
  from typing import List
@@ -66,3 +66,34 @@ class GoogleDriveConnector(FileConnector):
66
66
  status, done = downloader.next_chunk()
67
67
 
68
68
  return file_buffer.getvalue()
69
+
70
+ def upload_file(self, file_path: str, content: bytes, content_type: str = None) -> None:
71
+ """
72
+ Sube un archivo a Google Drive.
73
+ Nota: 'file_path' en este contexto se interpreta como el nombre del archivo,
74
+ ya que GDrive usa IDs para carpetas. El archivo se subirá a la carpeta configurada (self.folder_id).
75
+ """
76
+ file_metadata = {
77
+ 'name': file_path, # Usamos file_path como nombre
78
+ 'parents': [self.folder_id]
79
+ }
80
+
81
+ media = MediaIoBaseUpload(io.BytesIO(content), mimetype=content_type, resumable=True)
82
+
83
+ self.drive_service.files().create(
84
+ body=file_metadata,
85
+ media_body=media,
86
+ fields='id'
87
+ ).execute()
88
+
89
+ def delete_file(self, file_path: str) -> None:
90
+ """
91
+ Elimina un archivo de Google Drive.
92
+ Nota: 'file_path' aquí DEBE ser el ID del archivo (fileId), no su nombre.
93
+ """
94
+ try:
95
+ self.drive_service.files().delete(fileId=file_path).execute()
96
+ except Exception:
97
+ # Si falla (ej: no existe), podríamos loguear o ignorar según diseño.
98
+ # Aquí asumimos propagación de error o manejo silencioso si no crítico.
99
+ pass
@@ -44,3 +44,25 @@ class LocalFileConnector(FileConnector):
44
44
  except Exception as e:
45
45
  raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
46
46
  f"Error leyendo el archivo {file_path}: {e}")
47
+
48
+ def upload_file(self, file_path: str, content: bytes, content_type: str = None) -> None:
49
+ # Nota: file_path debe ser relativo al directorio raíz configurado
50
+ full_path = os.path.join(self.directory, file_path)
51
+
52
+ # Asegurar que el directorio destino existe
53
+ try:
54
+ os.makedirs(os.path.dirname(full_path), exist_ok=True)
55
+ with open(full_path, 'wb') as f:
56
+ f.write(content)
57
+ except Exception as e:
58
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
59
+ f"Error escribiendo el archivo {file_path}: {e}")
60
+
61
+ def delete_file(self, file_path: str) -> None:
62
+ full_path = os.path.join(self.directory, file_path)
63
+ try:
64
+ if os.path.exists(full_path):
65
+ os.remove(full_path)
66
+ except Exception as e:
67
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
68
+ f"Error eliminando el archivo {file_path}: {e}")
@@ -0,0 +1,61 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ import boto3
7
+ from iatoolkit.infra.connectors.file_connector import FileConnector
8
+ from typing import List
9
+
10
+
11
+ class S3Connector(FileConnector):
12
+ def __init__(self, bucket: str, prefix: str, folder: str, auth: dict):
13
+ self.bucket = bucket
14
+ self.prefix = prefix
15
+ self.folder = folder
16
+ self.s3 = boto3.client('s3', **auth)
17
+
18
+ def list_files(self) -> List[dict]:
19
+ # list all the files as dictionaries, with keys: 'path', 'name' y 'metadata'.
20
+ prefix = f'{self.prefix}/{self.folder}/'
21
+ response = self.s3.list_objects_v2(Bucket=self.bucket, Prefix=prefix)
22
+ files = response.get('Contents', [])
23
+
24
+ return [
25
+ {
26
+ "path": obj['Key'], # s3 key
27
+ "name": obj['Key'].split('/')[-1], # filename
28
+ "metadata": {"size": obj.get('Size'), "last_modified": obj.get('LastModified')}
29
+ }
30
+ for obj in files
31
+ ]
32
+
33
+ def get_file_content(self, file_path: str) -> bytes:
34
+ response = self.s3.get_object(Bucket=self.bucket, Key=file_path)
35
+ return response['Body'].read()
36
+
37
+ def delete_file(self, file_path: str) -> None:
38
+ self.s3.delete_object(Bucket=self.bucket, Key=file_path)
39
+
40
+ def upload_file(self, file_path: str, content: bytes, content_type: str = None) -> None:
41
+ # If the path doesn't start with the prefix, add it (optional, depends on your logic)'
42
+ # Assuming file_path is either a full path or relative to the root of the bucket for flexibility
43
+ full_path = file_path
44
+
45
+ extra_args = {}
46
+ if content_type:
47
+ extra_args['ContentType'] = content_type
48
+
49
+ self.s3.put_object(
50
+ Bucket=self.bucket,
51
+ Key=full_path,
52
+ Body=content,
53
+ **extra_args
54
+ )
55
+
56
+ def generate_presigned_url(self, file_path: str, expiration: int = 3600) -> str:
57
+ return self.s3.generate_presigned_url(
58
+ 'get_object',
59
+ Params={'Bucket': self.bucket, 'Key': file_path},
60
+ ExpiresIn=expiration
61
+ )