iatoolkit 0.71.4__py3-none-any.whl → 0.91.1__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.
Files changed (86) hide show
  1. iatoolkit/__init__.py +15 -5
  2. iatoolkit/base_company.py +4 -58
  3. iatoolkit/cli_commands.py +6 -7
  4. iatoolkit/common/exceptions.py +1 -0
  5. iatoolkit/common/routes.py +12 -28
  6. iatoolkit/common/util.py +7 -1
  7. iatoolkit/company_registry.py +50 -14
  8. iatoolkit/{iatoolkit.py → core.py} +54 -55
  9. iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
  10. iatoolkit/infra/llm_client.py +9 -5
  11. iatoolkit/locales/en.yaml +10 -2
  12. iatoolkit/locales/es.yaml +171 -162
  13. iatoolkit/repositories/database_manager.py +59 -14
  14. iatoolkit/repositories/llm_query_repo.py +34 -22
  15. iatoolkit/repositories/models.py +16 -18
  16. iatoolkit/repositories/profile_repo.py +5 -10
  17. iatoolkit/repositories/vs_repo.py +9 -4
  18. iatoolkit/services/auth_service.py +1 -1
  19. iatoolkit/services/branding_service.py +1 -1
  20. iatoolkit/services/company_context_service.py +19 -11
  21. iatoolkit/services/configuration_service.py +219 -46
  22. iatoolkit/services/dispatcher_service.py +31 -225
  23. iatoolkit/services/document_service.py +10 -1
  24. iatoolkit/services/embedding_service.py +9 -6
  25. iatoolkit/services/excel_service.py +50 -2
  26. iatoolkit/services/history_manager_service.py +189 -0
  27. iatoolkit/services/jwt_service.py +1 -1
  28. iatoolkit/services/language_service.py +8 -2
  29. iatoolkit/services/license_service.py +82 -0
  30. iatoolkit/services/mail_service.py +171 -25
  31. iatoolkit/services/profile_service.py +37 -32
  32. iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +110 -1
  33. iatoolkit/services/query_service.py +192 -191
  34. iatoolkit/services/sql_service.py +63 -12
  35. iatoolkit/services/tool_service.py +231 -0
  36. iatoolkit/services/user_feedback_service.py +18 -6
  37. iatoolkit/services/user_session_context_service.py +18 -0
  38. iatoolkit/static/images/iatoolkit_core.png +0 -0
  39. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  40. iatoolkit/static/js/chat_feedback_button.js +1 -1
  41. iatoolkit/static/js/chat_help_content.js +4 -4
  42. iatoolkit/static/js/chat_main.js +17 -5
  43. iatoolkit/static/js/chat_onboarding_button.js +1 -1
  44. iatoolkit/static/styles/chat_iatoolkit.css +1 -1
  45. iatoolkit/static/styles/chat_public.css +28 -0
  46. iatoolkit/static/styles/documents.css +598 -0
  47. iatoolkit/static/styles/landing_page.css +223 -7
  48. iatoolkit/system_prompts/__init__.py +0 -0
  49. iatoolkit/system_prompts/query_main.prompt +2 -1
  50. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  51. iatoolkit/templates/_company_header.html +30 -5
  52. iatoolkit/templates/_login_widget.html +3 -3
  53. iatoolkit/templates/chat.html +1 -1
  54. iatoolkit/templates/forgot_password.html +3 -2
  55. iatoolkit/templates/onboarding_shell.html +1 -1
  56. iatoolkit/templates/signup.html +3 -0
  57. iatoolkit/views/base_login_view.py +1 -1
  58. iatoolkit/views/change_password_view.py +1 -1
  59. iatoolkit/views/forgot_password_view.py +9 -4
  60. iatoolkit/views/history_api_view.py +3 -3
  61. iatoolkit/views/home_view.py +4 -2
  62. iatoolkit/views/init_context_api_view.py +1 -1
  63. iatoolkit/views/llmquery_api_view.py +4 -3
  64. iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +1 -1
  65. iatoolkit/views/login_view.py +17 -5
  66. iatoolkit/views/logout_api_view.py +10 -2
  67. iatoolkit/views/prompt_api_view.py +1 -1
  68. iatoolkit/views/root_redirect_view.py +22 -0
  69. iatoolkit/views/signup_view.py +12 -4
  70. iatoolkit/views/static_page_view.py +27 -0
  71. iatoolkit/views/verify_user_view.py +1 -1
  72. iatoolkit-0.91.1.dist-info/METADATA +268 -0
  73. iatoolkit-0.91.1.dist-info/RECORD +125 -0
  74. iatoolkit-0.91.1.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  75. iatoolkit/services/history_service.py +0 -37
  76. iatoolkit/templates/about.html +0 -13
  77. iatoolkit/templates/index.html +0 -145
  78. iatoolkit/templates/login_simulation.html +0 -45
  79. iatoolkit/views/external_login_view.py +0 -73
  80. iatoolkit/views/index_view.py +0 -14
  81. iatoolkit/views/login_simulation_view.py +0 -93
  82. iatoolkit-0.71.4.dist-info/METADATA +0 -276
  83. iatoolkit-0.71.4.dist-info/RECORD +0 -122
  84. {iatoolkit-0.71.4.dist-info → iatoolkit-0.91.1.dist-info}/WHEEL +0 -0
  85. {iatoolkit-0.71.4.dist-info → iatoolkit-0.91.1.dist-info}/licenses/LICENSE +0 -0
  86. {iatoolkit-0.71.4.dist-info → iatoolkit-0.91.1.dist-info}/top_level.txt +0 -0
iatoolkit/__init__.py CHANGED
@@ -1,14 +1,17 @@
1
- """
2
- IAToolkit Package
3
- """
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ __version__ = "0.91.1"
4
7
 
5
8
  # Expose main classes and functions at the top level of the package
6
9
 
7
10
  # main IAToolkit class
8
- from .iatoolkit import IAToolkit, create_app, current_iatoolkit
11
+ from iatoolkit.core import IAToolkit, create_app, current_iatoolkit
9
12
 
10
13
  # for registering the client companies
11
- from .company_registry import register_company
14
+ from .company_registry import register_company, set_company_registry
12
15
  from .base_company import BaseCompany
13
16
 
14
17
  # --- Services ---
@@ -18,12 +21,16 @@ from iatoolkit.services.search_service import SearchService
18
21
  from iatoolkit.services.sql_service import SqlService
19
22
  from iatoolkit.services.load_documents_service import LoadDocumentsService
20
23
  from iatoolkit.infra.call_service import CallServiceClient
24
+ from iatoolkit.services.profile_service import ProfileService
25
+ from iatoolkit.services.mail_service import MailService
26
+ from iatoolkit.repositories.models import Base as OrmModel
21
27
 
22
28
  __all__ = [
23
29
  'IAToolkit',
24
30
  'create_app',
25
31
  'current_iatoolkit',
26
32
  'register_company',
33
+ 'set_company_registry',
27
34
  'BaseCompany',
28
35
  'QueryService',
29
36
  'SqlService',
@@ -31,4 +38,7 @@ __all__ = [
31
38
  'SearchService',
32
39
  'LoadDocumentsService',
33
40
  'CallServiceClient',
41
+ 'ProfileService',
42
+ 'MailService',
43
+ 'OrmModel'
34
44
  ]
iatoolkit/base_company.py CHANGED
@@ -7,9 +7,8 @@
7
7
  from abc import ABC, abstractmethod
8
8
  from iatoolkit.repositories.profile_repo import ProfileRepo
9
9
  from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
10
- from iatoolkit.services.prompt_manager_service import PromptService
11
- from iatoolkit.repositories.models import Company, Function, PromptCategory
12
- from .iatoolkit import IAToolkit
10
+ from iatoolkit.repositories.models import Company
11
+ from iatoolkit.core import IAToolkit
13
12
 
14
13
 
15
14
  class BaseCompany(ABC):
@@ -18,74 +17,21 @@ class BaseCompany(ABC):
18
17
  injector = IAToolkit.get_instance().get_injector()
19
18
  self.profile_repo: ProfileRepo = injector.get(ProfileRepo)
20
19
  self.llm_query_repo: LLMQueryRepo = injector.get(LLMQueryRepo)
21
- self.prompt_service: PromptService = injector.get(PromptService)
22
20
  self.company: Company | None = None
23
- self.company_short_name: str
21
+ self.company_short_name = ''
24
22
 
25
- def _create_company(self,
26
- short_name: str,
27
- name: str,
28
- parameters: dict | None = None,
29
- ) -> Company:
30
- company_obj = Company(short_name=short_name,
31
- name=name,
32
- parameters=parameters)
33
- self.company = self.profile_repo.create_company(company_obj)
34
- return self.company
35
-
36
- def _create_function(self, function_name: str, description: str, params: dict, **kwargs):
37
- if not self.company:
38
- raise ValueError("La compañía debe estar definida antes de crear una función.")
39
-
40
- self.llm_query_repo.create_or_update_function(
41
- Function(
42
- company_id=self.company.id,
43
- name=function_name,
44
- description=description,
45
- parameters=params,
46
- system_function=False,
47
- **kwargs
48
- )
49
- )
50
-
51
- def _create_prompt_category(self, name: str, order: int) -> PromptCategory:
52
- if not self.company:
53
- raise ValueError("La compañía debe estar definida antes de crear una categoría.")
54
-
55
- return self.llm_query_repo.create_or_update_prompt_category(
56
- PromptCategory(name=name, order=order, company_id=self.company.id)
57
- )
58
-
59
- def _create_prompt(self, prompt_name: str, description: str, category: PromptCategory, order: int, **kwargs):
60
- if not self.company:
61
- raise ValueError("La compañía debe estar definida antes de crear un prompt.")
62
-
63
- self.prompt_service.create_prompt(
64
- prompt_name=prompt_name,
65
- description=description,
66
- order=order,
67
- company=self.company,
68
- category=category,
69
- **kwargs
70
- )
71
-
72
- @abstractmethod
73
- # get context specific for this company
74
- def get_user_info(self, user_identifier: str) -> dict:
75
- raise NotImplementedError("La subclase debe implementar el método get_user_info()")
76
23
 
77
24
  @abstractmethod
78
25
  # execute the specific action configured in the intent table
79
26
  def handle_request(self, tag: str, params: dict) -> dict:
80
27
  raise NotImplementedError("La subclase debe implementar el método handle_request()")
81
28
 
82
-
29
+ @abstractmethod
83
30
  def register_cli_commands(self, app):
84
31
  """
85
32
  optional method for a company definition of it's cli commands
86
33
  """
87
34
  pass
88
35
 
89
-
90
36
  def unsupported_operation(self, tag):
91
37
  raise NotImplementedError(f"La operación '{tag}' no está soportada por esta empresa.")
iatoolkit/cli_commands.py CHANGED
@@ -5,9 +5,10 @@
5
5
 
6
6
  import click
7
7
  import logging
8
- from .iatoolkit import IAToolkit
8
+ from iatoolkit.core import IAToolkit
9
9
  from iatoolkit.services.profile_service import ProfileService
10
10
 
11
+
11
12
  def register_core_commands(app):
12
13
  """Registra los comandos CLI del núcleo de IAToolkit."""
13
14
 
@@ -17,26 +18,24 @@ def register_core_commands(app):
17
18
  """⚙️ Genera una nueva API key para una compañía ya registrada."""
18
19
  try:
19
20
  profile_service = IAToolkit.get_instance().get_injector().get(ProfileService)
20
- click.echo(f"🔑 Generando API key para '{company_short_name}'...")
21
+ click.echo(f"🔑 Generating API-KEY for company: '{company_short_name}'...")
21
22
  result = profile_service.new_api_key(company_short_name)
22
23
 
23
24
  if 'error' in result:
24
25
  click.echo(f"❌ Error: {result['error']}")
25
- click.echo("👉 Asegúrate de que el nombre de la compañía es correcto y está registrada.")
26
+ click.echo("👉 Make sure the company is registered and valid.")
26
27
  else:
27
- click.echo("✅ ¡Configuración lista! Agrega esta variable a tu entorno:")
28
+ click.echo("✅ ¡Api-key is ready! add this variable to your environment:")
28
29
  click.echo(f"IATOOLKIT_API_KEY='{result['api-key']}'")
29
30
  except Exception as e:
30
31
  logging.exception(e)
31
- click.echo(f"❌ Ocurrió un error inesperado durante la configuración: {e}")
32
+ click.echo(f"❌ unexpectd error during the configuration: {e}")
32
33
 
33
34
  @app.cli.command("encrypt-key")
34
35
  @click.argument("key")
35
36
  def encrypt_llm_api_key(key: str):
36
37
  from iatoolkit.common.util import Utility
37
38
 
38
-
39
-
40
39
  util = IAToolkit.get_instance().get_injector().get(Utility)
41
40
  try:
42
41
  encrypt_key = util.encrypt_key(key)
@@ -37,6 +37,7 @@ class IAToolkitException(Exception):
37
37
  LOAD_DOCUMENT_ERROR = 25
38
38
  INVALID_USER = 26
39
39
  VECTOR_STORE_ERROR = 27
40
+ EMBEDDING_ERROR = 28
40
41
 
41
42
 
42
43
 
@@ -8,32 +8,31 @@ from flask import jsonify
8
8
 
9
9
 
10
10
  # this function register all the views
11
- def register_views(injector, app):
11
+ def register_views(app):
12
12
 
13
- from iatoolkit.views.index_view import IndexView
14
13
  from iatoolkit.views.init_context_api_view import InitContextApiView
15
14
  from iatoolkit.views.llmquery_api_view import LLMQueryApiView
16
15
  from iatoolkit.views.tasks_api_view import TaskApiView
17
16
  from iatoolkit.views.tasks_review_api_view import TaskReviewApiView
18
- from iatoolkit.views.login_simulation_view import LoginSimulationView
19
17
  from iatoolkit.views.signup_view import SignupView
20
18
  from iatoolkit.views.verify_user_view import VerifyAccountView
21
19
  from iatoolkit.views.forgot_password_view import ForgotPasswordView
22
20
  from iatoolkit.views.change_password_view import ChangePasswordView
23
- from iatoolkit.views.file_store_api_view import FileStoreApiView
21
+ from iatoolkit.views.load_document_api_view import LoadDocumentApiView
24
22
  from iatoolkit.views.user_feedback_api_view import UserFeedbackApiView
25
23
  from iatoolkit.views.prompt_api_view import PromptApiView
26
24
  from iatoolkit.views.history_api_view import HistoryApiView
27
25
  from iatoolkit.views.help_content_api_view import HelpContentApiView
28
- from iatoolkit.views.profile_api_view import UserLanguageApiView # <-- Importa la nueva vista
26
+ from iatoolkit.views.profile_api_view import UserLanguageApiView
29
27
  from iatoolkit.views.embedding_api_view import EmbeddingApiView
30
28
  from iatoolkit.views.login_view import LoginView, FinalizeContextView
31
- from iatoolkit.views.external_login_view import ExternalLoginView, RedeemTokenApiView
32
29
  from iatoolkit.views.logout_api_view import LogoutApiView
33
30
  from iatoolkit.views.home_view import HomeView
31
+ from iatoolkit.views.static_page_view import StaticPageView
32
+ from iatoolkit.views.root_redirect_view import RootRedirectView
34
33
 
35
- # iatoolkit home page
36
- app.add_url_rule('/', view_func=IndexView.as_view('index'))
34
+ # assign root '/' to our new redirect logic
35
+ app.add_url_rule('/home', view_func=RootRedirectView.as_view('root_redirect'))
37
36
 
38
37
  # company home view
39
38
  app.add_url_rule('/<company_short_name>/home', view_func=HomeView.as_view('home'))
@@ -41,10 +40,6 @@ def register_views(injector, app):
41
40
  # login for the iatoolkit integrated frontend
42
41
  app.add_url_rule('/<company_short_name>/login', view_func=LoginView.as_view('login'))
43
42
 
44
- # this is the login for external users
45
- app.add_url_rule('/<company_short_name>/external_login',
46
- view_func=ExternalLoginView.as_view('external_login'))
47
-
48
43
  # this endpoint is called when onboarding_shell finish the context load
49
44
  app.add_url_rule(
50
45
  '/<company_short_name>/finalize',
@@ -65,10 +60,6 @@ def register_views(injector, app):
65
60
  app.add_url_rule('/<company_short_name>/api/logout',
66
61
  view_func=LogoutApiView.as_view('logout'))
67
62
 
68
- # this endpoint is called by the JS for changing the token for a session
69
- app.add_url_rule('/<string:company_short_name>/api/redeem_token',
70
- view_func = RedeemTokenApiView.as_view('redeem_token'))
71
-
72
63
  # init (reset) the company context
73
64
  app.add_url_rule('/<company_short_name>/api/init-context',
74
65
  view_func=InitContextApiView.as_view('init-context'),
@@ -97,12 +88,16 @@ def register_views(injector, app):
97
88
  app.add_url_rule('/tasks/review/<int:task_id>', view_func=TaskReviewApiView.as_view('tasks-review'))
98
89
 
99
90
  # this endpoint is for upload documents into the vector store (api-key)
100
- app.add_url_rule('/api/load', view_func=FileStoreApiView.as_view('load_api'))
91
+ app.add_url_rule('/api/load-document', view_func=LoadDocumentApiView.as_view('load-document'), methods=['POST'])
101
92
 
102
93
  # this endpoint is for generating embeddings for a given text
103
94
  app.add_url_rule('/<company_short_name>/api/embedding',
104
95
  view_func=EmbeddingApiView.as_view('embedding_api'))
105
96
 
97
+ # static pages
98
+ # url: /pages/foundation o /pages/implementation_plan
99
+ static_view = StaticPageView.as_view('static_pages')
100
+ app.add_url_rule('/pages/<page_name>', view_func=static_view, methods=['GET'])
106
101
 
107
102
  @app.route('/download/<path:filename>')
108
103
  def download_file(filename):
@@ -125,21 +120,10 @@ def register_views(injector, app):
125
120
  except FileNotFoundError:
126
121
  abort(404)
127
122
 
128
- # login testing
129
- app.add_url_rule('/<company_short_name>/login_test',
130
- view_func=LoginSimulationView.as_view('login_test'))
131
-
132
- app.add_url_rule(
133
- '/about', # URL de la ruta
134
- view_func=lambda: render_template('about.html'))
135
123
 
136
124
  app.add_url_rule('/version', 'version',
137
125
  lambda: jsonify({"iatoolkit_version": current_app.config.get('VERSION', 'N/A')}))
138
126
 
139
127
 
140
- # hacer que la raíz '/' vaya al home de iatoolkit
141
- @app.route('/')
142
- def root_redirect():
143
- return redirect(url_for('index'))
144
128
 
145
129
 
iatoolkit/common/util.py CHANGED
@@ -6,6 +6,7 @@
6
6
  import logging
7
7
  from typing import List
8
8
  from iatoolkit.common.exceptions import IAToolkitException
9
+ from flask import request
9
10
  from injector import inject
10
11
  import os
11
12
  from jinja2 import Environment, FileSystemLoader
@@ -16,6 +17,7 @@ from cryptography.fernet import Fernet
16
17
  import base64
17
18
 
18
19
 
20
+
19
21
  class Utility:
20
22
  @inject
21
23
  def __init__(self):
@@ -97,6 +99,10 @@ class Utility:
97
99
  logging.exception(e)
98
100
  return None
99
101
 
102
+ def get_template_by_language(self, template_name: str, default_langueage: str = 'en') -> str:
103
+ # english is default
104
+ lang = request.args.get("lang", default_langueage)
105
+ return f'{template_name}_{lang}.html'
100
106
 
101
107
  def serialize(self, obj):
102
108
  if isinstance(obj, datetime) or isinstance(obj, date):
@@ -343,6 +349,6 @@ class Utility:
343
349
 
344
350
  def is_gemini_model(self, model: str) -> bool:
345
351
  gemini_models = [
346
- 'gemini', 'gemini-2.5-pro'
352
+ 'gemini', 'gemini-2.5-pro', 'gemini-3'
347
353
  ]
348
354
  return any(gemini_model in model.lower() for gemini_model in gemini_models)
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- from typing import Dict, Type, Any
6
+ from typing import Dict, Type, Any, Optional
7
7
  from .base_company import BaseCompany
8
8
  import logging
9
9
 
@@ -18,6 +18,30 @@ class CompanyRegistry:
18
18
  self._company_classes: Dict[str, Type[BaseCompany]] = {}
19
19
  self._company_instances: Dict[str, BaseCompany] = {}
20
20
 
21
+ def register(self, name: str, company_class: Type[BaseCompany]) -> None:
22
+ """
23
+ Registers a company in the registry.
24
+
25
+ COMMUNITY EDITION LIMITATION:
26
+ This base implementation enforces a strict single-tenant limit.
27
+ It raises a RuntimeError if a second company is registered.
28
+ """
29
+ if not issubclass(company_class, BaseCompany):
30
+ raise ValueError(f"The class {company_class.__name__} must be a subclass of BaseCompany")
31
+
32
+ company_key = name.lower()
33
+
34
+ # --- STRICT SINGLE-TENANT ENFORCEMENT ---
35
+ # If a company is already registered (and it's not an update to the same key)
36
+ if len(self._company_classes) > 0 and company_key not in self._company_classes:
37
+ logging.error(f"❌ Community Edition Restriction: Cannot register '{name}'. Limit reached (1).")
38
+ raise RuntimeError(
39
+ "IAToolkit Community Edition allows only one company instance. "
40
+ "Upgrade to IAToolkit Enterprise to enable multi-tenancy."
41
+ )
42
+
43
+ self._company_classes[company_key] = company_class
44
+ logging.info(f"Company registered: {name}")
21
45
 
22
46
  def instantiate_companies(self, injector) -> Dict[str, BaseCompany]:
23
47
  """
@@ -34,15 +58,16 @@ class CompanyRegistry:
34
58
 
35
59
  except Exception as e:
36
60
  logging.error(f"Error while creating company instance for {company_key}: {e}")
37
- logging.exception(e)
38
- raise
61
+ raise e
39
62
 
40
63
  return self._company_instances.copy()
41
64
 
42
65
  def get_all_company_instances(self) -> Dict[str, BaseCompany]:
43
- """Devuelve un diccionario con todas las instancias de empresas creadas."""
44
66
  return self._company_instances.copy()
45
67
 
68
+ def get_company_instance(self, company_name: str) -> Optional[BaseCompany]:
69
+ return self._company_instances.get(company_name.lower())
70
+
46
71
  def get_registered_companies(self) -> Dict[str, Type[BaseCompany]]:
47
72
  return self._company_classes.copy()
48
73
 
@@ -50,11 +75,30 @@ class CompanyRegistry:
50
75
  self._company_classes.clear()
51
76
  self._company_instances.clear()
52
77
 
78
+ # --- Singleton Management ---
53
79
 
54
- # global instance of the company registry
80
+ # Global instance (Default: Community Edition)
55
81
  _company_registry = CompanyRegistry()
56
82
 
57
83
 
84
+ def get_company_registry() -> CompanyRegistry:
85
+ """Get the global company registry instance."""
86
+ return _company_registry
87
+
88
+
89
+ def set_company_registry(registry: CompanyRegistry) -> None:
90
+ """
91
+ Sets the global company registry instance.
92
+ Use this to inject an Enterprise-compatible registry implementation.
93
+ """
94
+ global _company_registry
95
+ if not isinstance(registry, CompanyRegistry):
96
+ raise ValueError("Registry must inherit from CompanyRegistry")
97
+
98
+ _company_registry = registry
99
+ logging.info(f"✅ Company Registry implementation swapped: {type(registry).__name__}")
100
+
101
+
58
102
  def register_company(name: str, company_class: Type[BaseCompany]) -> None:
59
103
  """
60
104
  Public function to register a company.
@@ -63,13 +107,5 @@ def register_company(name: str, company_class: Type[BaseCompany]) -> None:
63
107
  name: Name of the company
64
108
  company_class: Class that inherits from BaseCompany
65
109
  """
66
- if not issubclass(company_class, BaseCompany):
67
- raise ValueError(f"La clase {company_class.__name__} debe heredar de BaseCompany")
68
-
69
- company_key = name.lower()
70
- _company_registry._company_classes[company_key] = company_class
110
+ _company_registry.register(name, company_class)
71
111
 
72
-
73
- def get_company_registry() -> CompanyRegistry:
74
- """get the global company registry instance"""
75
- return _company_registry