solace-agent-mesh 1.4.6__py3-none-any.whl → 1.4.8__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 solace-agent-mesh might be problematic. Click here for more details.

Files changed (139) hide show
  1. solace_agent_mesh/agent/adk/runner.py +24 -8
  2. solace_agent_mesh/agent/sac/component.py +13 -1
  3. solace_agent_mesh/assets/docs/404.html +3 -3
  4. solace_agent_mesh/assets/docs/assets/js/{0e682baa.da822665.js → 0e682baa.d054e1d8.js} +1 -1
  5. solace_agent_mesh/assets/docs/assets/js/166ab619.e27886d9.js +1 -0
  6. solace_agent_mesh/assets/docs/assets/js/{1c6e87d2.43771adc.js → 1c6e87d2.e056b7e0.js} +1 -1
  7. solace_agent_mesh/assets/docs/assets/js/453a82a6.3c6bb61d.js +1 -0
  8. solace_agent_mesh/assets/docs/assets/js/483cef9a.4736f2d8.js +1 -0
  9. solace_agent_mesh/assets/docs/assets/js/{4c2787c2.fc6804f2.js → 4c2787c2.c1290a40.js} +1 -1
  10. solace_agent_mesh/assets/docs/assets/js/{5b4258a4.dff11eca.js → 5b4258a4.fdfd2325.js} +1 -1
  11. solace_agent_mesh/assets/docs/assets/js/{75384d09.abdf9cf9.js → 75384d09.c19e8b51.js} +1 -1
  12. solace_agent_mesh/assets/docs/assets/js/85387663.be2bc838.js +1 -0
  13. solace_agent_mesh/assets/docs/assets/js/945fb41e.16e00776.js +1 -0
  14. solace_agent_mesh/assets/docs/assets/js/a12a4955.25fbed32.js +1 -0
  15. solace_agent_mesh/assets/docs/assets/js/a3a92b25.af35e313.js +1 -0
  16. solace_agent_mesh/assets/docs/assets/js/ae0e903d.5fe5203f.js +1 -0
  17. solace_agent_mesh/assets/docs/assets/js/bac0be12.17de4316.js +1 -0
  18. solace_agent_mesh/assets/docs/assets/js/cee5d587.47904f5e.js +1 -0
  19. solace_agent_mesh/assets/docs/assets/js/d6a81ee7.829198f1.js +1 -0
  20. solace_agent_mesh/assets/docs/assets/js/f284c35a.ed8dd236.js +1 -0
  21. solace_agent_mesh/assets/docs/assets/js/f897a61a.126663fe.js +1 -0
  22. solace_agent_mesh/assets/docs/assets/js/{fbfa3e75.aca209c9.js → fbfa3e75.e144b16c.js} +1 -1
  23. solace_agent_mesh/assets/docs/assets/js/main.86924c42.js +2 -0
  24. solace_agent_mesh/assets/docs/assets/js/runtime~main.0d2ff2b6.js +1 -0
  25. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +4 -4
  26. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/rbac-setup-guilde/index.html +201 -0
  27. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +4 -4
  28. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +4 -4
  29. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +4 -4
  30. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +4 -4
  31. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +7 -7
  32. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
  33. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
  34. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
  35. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
  36. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +5 -5
  37. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +5 -5
  38. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +6 -6
  39. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +7 -7
  40. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +12 -13
  41. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/litellm_models/index.html +49 -0
  42. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +6 -6
  44. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +6 -6
  45. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +8 -8
  48. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +5 -5
  49. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +7 -7
  51. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +5 -5
  52. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
  53. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  54. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
  55. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
  56. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
  57. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
  58. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
  59. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +4 -4
  60. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
  61. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +4 -4
  62. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
  63. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +6 -6
  64. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +7 -7
  65. solace_agent_mesh/assets/docs/lunr-index-1759246102819.json +1 -0
  66. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  67. solace_agent_mesh/assets/docs/search-doc-1759246102819.json +1 -0
  68. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  69. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  70. solace_agent_mesh/cli/__init__.py +1 -1
  71. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +6 -3
  72. solace_agent_mesh/cli/commands/init_cmd/__init__.py +3 -3
  73. solace_agent_mesh/cli/commands/init_cmd/broker_step.py +1 -1
  74. solace_agent_mesh/cli/commands/init_cmd/env_step.py +1 -1
  75. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +4 -4
  76. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +12 -1
  77. solace_agent_mesh/cli/commands/plugin_cmd/install_cmd.py +5 -5
  78. solace_agent_mesh/client/webui/frontend/static/assets/main-B0PHV3hm.js +339 -0
  79. solace_agent_mesh/client/webui/frontend/static/index.html +1 -1
  80. solace_agent_mesh/config_portal/backend/common.py +1 -1
  81. solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-bFMKlzKf.js → _index-BNuqpWDc.js} +1 -1
  82. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-89db7c30.js → manifest-44d62be6.js} +1 -1
  83. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  84. solace_agent_mesh/gateway/http_sse/alembic/versions/20250916_f6e7d8c9b0a1_convert_timestamps_to_epoch_and_align_columns.py +112 -42
  85. solace_agent_mesh/gateway/http_sse/app.py +0 -28
  86. solace_agent_mesh/gateway/http_sse/component.py +21 -15
  87. solace_agent_mesh/gateway/http_sse/dependencies.py +84 -88
  88. solace_agent_mesh/gateway/http_sse/main.py +37 -15
  89. solace_agent_mesh/gateway/http_sse/repository/entities/message.py +3 -1
  90. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +3 -1
  91. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +5 -0
  92. solace_agent_mesh/gateway/http_sse/repository/message_repository.py +25 -23
  93. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +12 -4
  94. solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +19 -1
  95. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +19 -1
  96. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +46 -42
  97. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +199 -59
  98. solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +1 -6
  99. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +3 -17
  100. solace_agent_mesh/gateway/http_sse/routers/people.py +4 -37
  101. solace_agent_mesh/gateway/http_sse/routers/sessions.py +33 -68
  102. solace_agent_mesh/gateway/http_sse/routers/tasks.py +54 -28
  103. solace_agent_mesh/gateway/http_sse/services/session_service.py +60 -28
  104. solace_agent_mesh/gateway/http_sse/shared/__init__.py +122 -1
  105. solace_agent_mesh/gateway/http_sse/shared/base_repository.py +278 -0
  106. solace_agent_mesh/gateway/http_sse/shared/database_exceptions.py +274 -0
  107. solace_agent_mesh/gateway/http_sse/shared/database_helpers.py +43 -0
  108. solace_agent_mesh/gateway/http_sse/shared/error_dto.py +107 -0
  109. solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +192 -0
  110. solace_agent_mesh/gateway/http_sse/shared/exceptions.py +192 -0
  111. solace_agent_mesh/gateway/http_sse/shared/pagination.py +138 -0
  112. solace_agent_mesh/gateway/http_sse/shared/response_utils.py +134 -0
  113. solace_agent_mesh/gateway/http_sse/shared/utils.py +22 -0
  114. solace_agent_mesh/templates/plugin_agent_config_template.yaml +1 -1
  115. solace_agent_mesh/templates/plugin_custom_config_template.yaml +1 -1
  116. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +1 -1
  117. solace_agent_mesh/templates/shared_config.yaml +1 -1
  118. {solace_agent_mesh-1.4.6.dist-info → solace_agent_mesh-1.4.8.dist-info}/METADATA +34 -35
  119. {solace_agent_mesh-1.4.6.dist-info → solace_agent_mesh-1.4.8.dist-info}/RECORD +123 -110
  120. solace_agent_mesh/assets/docs/assets/js/166ab619.e8f3a7c7.js +0 -1
  121. solace_agent_mesh/assets/docs/assets/js/483cef9a.8d318c2f.js +0 -1
  122. solace_agent_mesh/assets/docs/assets/js/85387663.6bf41934.js +0 -1
  123. solace_agent_mesh/assets/docs/assets/js/945fb41e.abf2be91.js +0 -1
  124. solace_agent_mesh/assets/docs/assets/js/a3a92b25.1d029b81.js +0 -1
  125. solace_agent_mesh/assets/docs/assets/js/ae0e903d.c786e887.js +0 -1
  126. solace_agent_mesh/assets/docs/assets/js/bac0be12.27ee2c26.js +0 -1
  127. solace_agent_mesh/assets/docs/assets/js/beecea0d.8bbd852c.js +0 -1
  128. solace_agent_mesh/assets/docs/assets/js/cee5d587.f1e1ca86.js +0 -1
  129. solace_agent_mesh/assets/docs/assets/js/f284c35a.2b2f5048.js +0 -1
  130. solace_agent_mesh/assets/docs/assets/js/f897a61a.bc634a3e.js +0 -1
  131. solace_agent_mesh/assets/docs/assets/js/main.72d74e33.js +0 -2
  132. solace_agent_mesh/assets/docs/assets/js/runtime~main.3dcfaf51.js +0 -1
  133. solace_agent_mesh/assets/docs/lunr-index-1758893005563.json +0 -1
  134. solace_agent_mesh/assets/docs/search-doc-1758893005563.json +0 -1
  135. solace_agent_mesh/client/webui/frontend/static/assets/main-BKIoiLSu.js +0 -339
  136. /solace_agent_mesh/assets/docs/assets/js/{main.72d74e33.js.LICENSE.txt → main.86924c42.js.LICENSE.txt} +0 -0
  137. {solace_agent_mesh-1.4.6.dist-info → solace_agent_mesh-1.4.8.dist-info}/WHEEL +0 -0
  138. {solace_agent_mesh-1.4.6.dist-info → solace_agent_mesh-1.4.8.dist-info}/entry_points.txt +0 -0
  139. {solace_agent_mesh-1.4.6.dist-info → solace_agent_mesh-1.4.8.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,8 @@
1
1
  """
2
2
  Shared Utilities and Constants
3
3
 
4
- Contains common utilities, constants, enums, and types used across layers.
4
+ Contains common utilities, constants, enums, types, and exception handling
5
+ used across layers.
5
6
  """
6
7
 
7
8
  from .auth_utils import get_current_user
@@ -14,7 +15,71 @@ from .timestamp_utils import (
14
15
  validate_epoch_ms,
15
16
  )
16
17
 
18
+ # Exception handling exports
19
+ from .exceptions import (
20
+ WebUIBackendException,
21
+ ValidationError,
22
+ EntityNotFoundError,
23
+ EntityAlreadyExistsError,
24
+ BusinessRuleViolationError,
25
+ ConfigurationError,
26
+ DataIntegrityError,
27
+ ExternalServiceError,
28
+ EntityOperation,
29
+ ValidationErrorBuilder,
30
+ )
31
+ from .error_dto import EventErrorDTO
32
+ from .exception_handlers import (
33
+ register_exception_handlers,
34
+ create_error_response,
35
+ validation_error_handler,
36
+ entity_not_found_handler,
37
+ entity_already_exists_handler,
38
+ business_rule_violation_handler,
39
+ configuration_error_handler,
40
+ data_integrity_error_handler,
41
+ external_service_error_handler,
42
+ webui_backend_exception_handler,
43
+ )
44
+
45
+ # Repository base classes
46
+ from .base_repository import (
47
+ BaseRepository,
48
+ PaginatedRepository,
49
+ ValidationMixin,
50
+ )
51
+
52
+ # Pagination utilities
53
+ from .pagination import (
54
+ PaginationParams,
55
+ PaginatedResponse,
56
+ DataResponse,
57
+ )
58
+
59
+ # Database utilities
60
+ from .database_exceptions import (
61
+ DatabaseExceptionHandler,
62
+ DatabaseErrorDecorator,
63
+ handle_database_errors,
64
+ )
65
+ # Database helpers (SimpleJSON type only - transaction management removed)
66
+ from .database_helpers import SimpleJSON
67
+
68
+ # Generic utilities
69
+ from .utils import generate_uuid, to_snake_case, to_pascal_case
70
+
71
+ # Response utilities
72
+ from .response_utils import (
73
+ create_data_response,
74
+ create_paginated_response,
75
+ create_empty_data_response,
76
+ create_success_response,
77
+ create_list_response,
78
+ StandardResponseMixin,
79
+ )
80
+
17
81
  __all__ = [
82
+ # Utilities
18
83
  "get_current_user",
19
84
  "now_epoch_ms",
20
85
  "epoch_ms_to_iso8601",
@@ -22,4 +87,60 @@ __all__ = [
22
87
  "datetime_to_epoch_ms",
23
88
  "epoch_ms_to_datetime",
24
89
  "validate_epoch_ms",
90
+
91
+ # Exception classes
92
+ "WebUIBackendException",
93
+ "ValidationError",
94
+ "EntityNotFoundError",
95
+ "EntityAlreadyExistsError",
96
+ "BusinessRuleViolationError",
97
+ "ConfigurationError",
98
+ "DataIntegrityError",
99
+ "ExternalServiceError",
100
+ "EntityOperation",
101
+ "ValidationErrorBuilder",
102
+
103
+ # Error response DTO
104
+ "EventErrorDTO",
105
+
106
+ # Exception handlers
107
+ "register_exception_handlers",
108
+ "create_error_response",
109
+ "validation_error_handler",
110
+ "entity_not_found_handler",
111
+ "entity_already_exists_handler",
112
+ "business_rule_violation_handler",
113
+ "configuration_error_handler",
114
+ "data_integrity_error_handler",
115
+ "external_service_error_handler",
116
+ "webui_backend_exception_handler",
117
+
118
+ # Repository base classes
119
+ "BaseRepository",
120
+ "PaginatedRepository",
121
+ "ValidationMixin",
122
+
123
+ # Pagination utilities
124
+ "PaginationParams",
125
+ "PaginatedResponse",
126
+ "DataResponse",
127
+
128
+ # Database utilities
129
+ "DatabaseExceptionHandler",
130
+ "DatabaseErrorDecorator",
131
+ "handle_database_errors",
132
+ # Database transaction functions removed - use FastAPI dependency injection
133
+
134
+ # Generic utilities
135
+ "generate_uuid",
136
+ "to_snake_case",
137
+ "to_pascal_case",
138
+
139
+ # Response utilities
140
+ "create_data_response",
141
+ "create_paginated_response",
142
+ "create_empty_data_response",
143
+ "create_success_response",
144
+ "create_list_response",
145
+ "StandardResponseMixin",
25
146
  ]
@@ -0,0 +1,278 @@
1
+ """
2
+ Base repository classes with proper transaction management.
3
+
4
+ This module provides base classes for repositories that follow FastAPI best practices
5
+ for database session management and transaction handling.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Dict, Any, List, Optional, TypeVar, Generic, Type
10
+ from sqlalchemy.orm import Session
11
+ from sqlalchemy.exc import NoResultFound
12
+ from .exceptions import EntityNotFoundError, ValidationError
13
+ from .types import PaginationInfo
14
+
15
+
16
+ T = TypeVar('T')
17
+ ModelType = TypeVar('ModelType')
18
+ EntityType = TypeVar('EntityType')
19
+
20
+
21
+ class BaseRepository(ABC, Generic[ModelType, EntityType]):
22
+ """
23
+ Abstract base class for repositories with common database operations.
24
+
25
+ This base class provides common patterns for database operations
26
+ without manual transaction management, following the principle that
27
+ transactions should be handled at the service/API layer.
28
+ """
29
+
30
+ def __init__(self, model_class: Type[ModelType], entity_class: Type[EntityType]):
31
+ """
32
+ Initialize repository with model and entity classes.
33
+
34
+ Args:
35
+ model_class: SQLAlchemy model class
36
+ entity_class: Pydantic entity class
37
+ """
38
+ self.model_class = model_class
39
+ self.entity_class = entity_class
40
+
41
+ @property
42
+ @abstractmethod
43
+ def entity_name(self) -> str:
44
+ """Return the entity name for error messages."""
45
+ pass
46
+
47
+ def create(self, session: Session, create_data: Dict[str, Any]) -> EntityType:
48
+ """
49
+ Create a new entity.
50
+
51
+ Args:
52
+ session: Database session (managed externally)
53
+ create_data: Data for creating the entity
54
+
55
+ Returns:
56
+ Created entity
57
+
58
+ Note:
59
+ This method does NOT commit the transaction.
60
+ Commit/rollback is handled by the service layer.
61
+ """
62
+ model_instance = self.model_class(**create_data)
63
+
64
+ session.add(model_instance)
65
+ session.flush() # Flush to get generated IDs
66
+ session.refresh(model_instance)
67
+
68
+ entity = self.entity_class.model_validate(model_instance)
69
+
70
+ return entity
71
+
72
+ def get_by_id(self, session: Session, entity_id: Any) -> EntityType:
73
+ """
74
+ Get entity by ID.
75
+
76
+ Args:
77
+ session: Database session
78
+ entity_id: Entity identifier
79
+
80
+ Returns:
81
+ Entity instance
82
+
83
+ Raises:
84
+ EntityNotFoundError: If entity not found
85
+ """
86
+ model_instance = session.query(self.model_class).filter(
87
+ self.model_class.id == str(entity_id)
88
+ ).first()
89
+
90
+ if not model_instance:
91
+ raise EntityNotFoundError(self.entity_name, entity_id)
92
+
93
+ return self.entity_class.model_validate(model_instance)
94
+
95
+ def get_all(self, session: Session, limit: Optional[int] = None, offset: Optional[int] = None) -> List[EntityType]:
96
+ """
97
+ Get all entities with optional pagination.
98
+
99
+ Args:
100
+ session: Database session
101
+ limit: Maximum number of results
102
+ offset: Number of results to skip
103
+
104
+ Returns:
105
+ List of entities
106
+ """
107
+ query = session.query(self.model_class)
108
+
109
+ if offset:
110
+ query = query.offset(offset)
111
+ if limit:
112
+ query = query.limit(limit)
113
+
114
+ model_instances = query.all()
115
+ return [self.entity_class.model_validate(instance) for instance in model_instances]
116
+
117
+ def update(self, session: Session, entity_id: Any, update_data: Dict[str, Any]) -> EntityType:
118
+ """
119
+ Update an entity.
120
+
121
+ Args:
122
+ session: Database session
123
+ entity_id: Entity identifier
124
+ update_data: Data to update
125
+
126
+ Returns:
127
+ Updated entity
128
+
129
+ Raises:
130
+ EntityNotFoundError: If entity not found
131
+ """
132
+ model_instance = session.query(self.model_class).filter(
133
+ self.model_class.id == str(entity_id)
134
+ ).first()
135
+
136
+ if not model_instance:
137
+ raise EntityNotFoundError(self.entity_name, entity_id)
138
+
139
+ for key, value in update_data.items():
140
+ if value is not None and hasattr(model_instance, key):
141
+ setattr(model_instance, key, value)
142
+
143
+ session.flush() # Flush to validate constraints
144
+ session.refresh(model_instance)
145
+
146
+ entity = self.entity_class.model_validate(model_instance)
147
+
148
+ return entity
149
+
150
+ def delete(self, session: Session, entity_id: Any) -> None:
151
+ """
152
+ Delete an entity.
153
+
154
+ Args:
155
+ session: Database session
156
+ entity_id: Entity identifier
157
+
158
+ Raises:
159
+ EntityNotFoundError: If entity not found
160
+ """
161
+ model_instance = session.query(self.model_class).filter(
162
+ self.model_class.id == str(entity_id)
163
+ ).first()
164
+
165
+ if not model_instance:
166
+ raise EntityNotFoundError(self.entity_name, entity_id)
167
+
168
+ session.delete(model_instance)
169
+ session.flush() # Flush to validate constraints
170
+
171
+ def exists(self, session: Session, entity_id: Any) -> bool:
172
+ """
173
+ Check if an entity exists.
174
+
175
+ Args:
176
+ session: Database session
177
+ entity_id: Entity identifier
178
+
179
+ Returns:
180
+ True if entity exists, False otherwise
181
+ """
182
+ count = session.query(self.model_class).filter(
183
+ self.model_class.id == str(entity_id)
184
+ ).count()
185
+
186
+ return count > 0
187
+
188
+ def count(self, session: Session) -> int:
189
+ """
190
+ Get total count of entities.
191
+
192
+ Args:
193
+ session: Database session
194
+
195
+ Returns:
196
+ Total number of entities
197
+ """
198
+ return session.query(self.model_class).count()
199
+
200
+
201
+ class PaginatedRepository(BaseRepository[ModelType, EntityType]):
202
+ """
203
+ Base repository with enhanced pagination support.
204
+ """
205
+
206
+ def get_paginated(self, session: Session, page_number: int, page_size: int) -> tuple[List[EntityType], int]:
207
+ """
208
+ Get paginated results.
209
+
210
+ Args:
211
+ session: Database session
212
+ page_number: Page number (1-based)
213
+ page_size: Number of items per page
214
+
215
+ Returns:
216
+ Tuple of (entities, total_count)
217
+ """
218
+ offset = (page_number - 1) * page_size
219
+
220
+ total_count = self.count(session)
221
+
222
+ entities = self.get_all(session, limit=page_size, offset=offset)
223
+
224
+ return entities, total_count
225
+
226
+ def get_paginated_with_info(self, session: Session, pagination: PaginationInfo) -> tuple[List[EntityType], PaginationInfo]:
227
+ """
228
+ Get paginated results with complete pagination info.
229
+
230
+ Args:
231
+ session: Database session
232
+ pagination: Pagination parameters
233
+
234
+ Returns:
235
+ Tuple of (entities, updated_pagination_info)
236
+ """
237
+ entities, total_count = self.get_paginated(session, pagination.page, pagination.page_size)
238
+
239
+ updated_pagination = PaginationInfo(
240
+ page=pagination.page,
241
+ page_size=pagination.page_size,
242
+ total_items=total_count,
243
+ total_pages=(total_count + pagination.page_size - 1) // pagination.page_size,
244
+ has_next=pagination.page * pagination.page_size < total_count,
245
+ has_previous=pagination.page > 1
246
+ )
247
+
248
+ return entities, updated_pagination
249
+
250
+
251
+ class ValidationMixin:
252
+ """
253
+ Mixin for repositories that need validation logic.
254
+ """
255
+
256
+ def validate_create_data(self, create_data: Dict[str, Any]) -> None:
257
+ """
258
+ Validate data before creation.
259
+
260
+ Args:
261
+ create_data: Data to validate
262
+
263
+ Raises:
264
+ ValidationError: If validation fails
265
+ """
266
+ pass
267
+
268
+ def validate_update_data(self, update_data: Dict[str, Any]) -> None:
269
+ """
270
+ Validate data before update.
271
+
272
+ Args:
273
+ update_data: Data to validate
274
+
275
+ Raises:
276
+ ValidationError: If validation fails
277
+ """
278
+ pass
@@ -0,0 +1,274 @@
1
+ """
2
+ Database Exception Handling
3
+
4
+ Provides centralized handling for converting SQLAlchemy exceptions to domain exceptions.
5
+ This handler translates low-level database errors into meaningful business exceptions
6
+ that can be properly handled by the API layer.
7
+ """
8
+
9
+ from typing import Optional, Dict, List
10
+ from sqlalchemy.exc import IntegrityError, SQLAlchemyError, OperationalError, DatabaseError
11
+ from .exceptions import ValidationError, DataIntegrityError, EntityAlreadyExistsError
12
+
13
+
14
+ class DatabaseExceptionHandler:
15
+ """
16
+ Centralized handler for converting SQLAlchemy exceptions to domain exceptions.
17
+
18
+ This handler translates low-level database errors into meaningful business exceptions
19
+ that can be properly handled by the API layer.
20
+ """
21
+
22
+ @staticmethod
23
+ def handle_integrity_error(e: IntegrityError, entity_type: str = "Resource") -> ValidationError:
24
+ """
25
+ Convert SQLAlchemy integrity constraint violations to domain exceptions.
26
+
27
+ Args:
28
+ e: The SQLAlchemy IntegrityError
29
+ entity_type: The type of entity being operated on
30
+
31
+ Returns:
32
+ ValidationError with appropriate message and validation details
33
+ """
34
+ # Try to get the most descriptive error message for constraint matching
35
+ full_message = str(e)
36
+ orig_message = str(e.orig) if hasattr(e, 'orig') and e.orig else ""
37
+
38
+ # Use the message that contains constraint information
39
+ if any(keyword in full_message for keyword in ['constraint', 'UNIQUE', 'NULL', 'CHECK', 'FOREIGN']):
40
+ # Extract the most specific constraint line from the full message
41
+ lines = full_message.split('\n')
42
+
43
+ # Prioritize SQL lines that contain actual constraint details
44
+ for line in lines:
45
+ if '[SQL:' in line and any(keyword in line for keyword in ['UNIQUE', 'NULL', 'CHECK', 'FOREIGN']):
46
+ # Extract just the SQL part without the [SQL: ] wrapper
47
+ if '[SQL:' in line and ']' in line:
48
+ error_message = line.split('[SQL:')[1].split(']')[0].strip()
49
+ break
50
+ error_message = line.strip()
51
+ break
52
+ else:
53
+ # Fallback to any line with constraint keywords
54
+ for line in lines:
55
+ if any(keyword in line for keyword in ['constraint', 'UNIQUE', 'NULL', 'CHECK', 'FOREIGN']):
56
+ error_message = line.strip()
57
+ break
58
+ else:
59
+ error_message = full_message
60
+ else:
61
+ # Fallback to orig if no constraint info in main message
62
+ error_message = orig_message or full_message
63
+
64
+ # Handle UNIQUE constraint violations and concurrent modifications
65
+ if ("UNIQUE constraint failed" in error_message or
66
+ "duplicate key value" in error_message.lower() or
67
+ "concurrent modification" in error_message.lower() or
68
+ "row was updated or deleted by another transaction" in error_message.lower()):
69
+ field_name = DatabaseExceptionHandler._extract_field_from_unique_error(error_message)
70
+ return EntityAlreadyExistsError(
71
+ entity_type=entity_type,
72
+ identifier=field_name,
73
+ value="provided value"
74
+ )
75
+
76
+ # Handle NOT NULL constraint violations
77
+ if "NOT NULL constraint failed" in error_message or "null value in column" in error_message.lower():
78
+ field_name = DatabaseExceptionHandler._extract_field_from_null_error(error_message)
79
+ return ValidationError(
80
+ message="Required field is missing",
81
+ validation_details={field_name: ["This field is required"]},
82
+ entity_type=entity_type
83
+ )
84
+
85
+ # Handle CHECK constraint violations
86
+ if "CHECK constraint failed" in error_message or "check constraint" in error_message.lower():
87
+ constraint_name = DatabaseExceptionHandler._extract_constraint_name(error_message)
88
+ field_name = DatabaseExceptionHandler._map_constraint_to_field(constraint_name)
89
+ return ValidationError(
90
+ message="Field validation failed",
91
+ validation_details={field_name: ["Value violates validation rules"]},
92
+ entity_type=entity_type
93
+ )
94
+
95
+ # Handle FOREIGN KEY constraint violations
96
+ if "FOREIGN KEY constraint failed" in error_message or "foreign key constraint" in error_message.lower():
97
+ return ValidationError(
98
+ message="Referenced resource does not exist",
99
+ validation_details={"reference": ["Invalid reference to related resource"]},
100
+ entity_type=entity_type
101
+ )
102
+
103
+ # Generic integrity error fallback
104
+ return DataIntegrityError(
105
+ constraint="integrity_violation",
106
+ message=f"Database constraint violation: {error_message}"
107
+ )
108
+
109
+ @staticmethod
110
+ def handle_operational_error(e: OperationalError, entity_type: str = "Resource") -> DataIntegrityError:
111
+ """
112
+ Handle SQLAlchemy operational errors (connection issues, timeouts, etc.).
113
+
114
+ Args:
115
+ e: The SQLAlchemy OperationalError
116
+ entity_type: The type of entity being operated on
117
+
118
+ Returns:
119
+ DataIntegrityError with appropriate message
120
+ """
121
+ error_message = str(e.orig) if hasattr(e, 'orig') else str(e)
122
+
123
+ if "timeout" in error_message.lower():
124
+ return DataIntegrityError(
125
+ constraint="timeout",
126
+ message="Database operation timed out. Please try again."
127
+ )
128
+
129
+ if "connection" in error_message.lower():
130
+ return DataIntegrityError(
131
+ constraint="connection_error",
132
+ message="Database connection failed. Please try again later."
133
+ )
134
+
135
+ return DataIntegrityError(
136
+ constraint="operational_error",
137
+ message="Database operation failed. Please contact support if this persists."
138
+ )
139
+
140
+ @staticmethod
141
+ def handle_database_error(e: DatabaseError, entity_type: str = "Resource") -> DataIntegrityError:
142
+ """
143
+ Handle general SQLAlchemy database errors.
144
+
145
+ Args:
146
+ e: The SQLAlchemy DatabaseError
147
+ entity_type: The type of entity being operated on
148
+
149
+ Returns:
150
+ DataIntegrityError with appropriate message
151
+ """
152
+ error_message = str(e.orig) if hasattr(e, 'orig') else str(e)
153
+
154
+ return DataIntegrityError(
155
+ constraint="database_error",
156
+ message="A database error occurred. Please contact support."
157
+ )
158
+
159
+ @staticmethod
160
+ def handle_sqlalchemy_error(e: SQLAlchemyError, entity_type: str = "Resource") -> DataIntegrityError:
161
+ """
162
+ Handle any other SQLAlchemy errors not caught by specific handlers.
163
+
164
+ Args:
165
+ e: The SQLAlchemy error
166
+ entity_type: The type of entity being operated on
167
+
168
+ Returns:
169
+ DataIntegrityError with generic message
170
+ """
171
+ error_message = str(e)
172
+
173
+ return DataIntegrityError(
174
+ constraint="sqlalchemy_error",
175
+ message="An unexpected database error occurred. Please contact support."
176
+ )
177
+
178
+ @staticmethod
179
+ def _extract_field_from_unique_error(error_message: str) -> str:
180
+ """Extract field name from UNIQUE constraint error message."""
181
+ # Try to extract field name from common formats
182
+ if "agents.name" in error_message:
183
+ return "name"
184
+ elif "agents.id" in error_message:
185
+ return "id"
186
+ elif "." in error_message:
187
+ # Extract field after table.field pattern
188
+ parts = error_message.split(".")
189
+ if len(parts) > 1:
190
+ field_part = parts[1].split()[0] # Get first word after dot
191
+ return field_part.strip('()"\'')
192
+
193
+ return "field" # Generic fallback
194
+
195
+ @staticmethod
196
+ def _extract_field_from_null_error(error_message: str) -> str:
197
+ """Extract field name from NOT NULL constraint error message."""
198
+ # Common patterns: "NOT NULL constraint failed: agents.name"
199
+ if ":" in error_message:
200
+ field_part = error_message.split(":")[-1].strip()
201
+ if "." in field_part:
202
+ return field_part.split(".")[-1]
203
+ return field_part
204
+
205
+ return "field" # Generic fallback
206
+
207
+ @staticmethod
208
+ def _extract_constraint_name(error_message: str) -> str:
209
+ """Extract constraint name from CHECK constraint error message."""
210
+ # Common patterns: "CHECK constraint failed: check_name_length"
211
+ if ":" in error_message:
212
+ constraint_part = error_message.split(":")[-1].strip()
213
+ return constraint_part
214
+
215
+ return "unknown_constraint"
216
+
217
+ @staticmethod
218
+ def _map_constraint_to_field(constraint_name: str) -> str:
219
+ """Map database constraint names to user-friendly field names."""
220
+ constraint_field_map = {
221
+ "check_name_length": "name",
222
+ "check_description_length": "description",
223
+ "check_system_prompt_length": "systemPrompt",
224
+ "check_created_by_length": "createdBy",
225
+ "check_updated_by_length": "updatedBy",
226
+ "check_id_length": "id"
227
+ }
228
+
229
+ return constraint_field_map.get(constraint_name, "field")
230
+
231
+
232
+ class DatabaseErrorDecorator:
233
+ """
234
+ Decorator class for wrapping repository methods with database exception handling.
235
+ """
236
+
237
+ def __init__(self, entity_type: str = "Resource"):
238
+ self.entity_type = entity_type
239
+
240
+ def __call__(self, func):
241
+ """
242
+ Decorator that wraps repository methods with database exception handling.
243
+
244
+ Args:
245
+ func: The repository method to wrap
246
+
247
+ Returns:
248
+ Wrapped function with exception handling
249
+ """
250
+ def wrapper(*args, **kwargs):
251
+ try:
252
+ return func(*args, **kwargs)
253
+ except IntegrityError as e:
254
+ raise DatabaseExceptionHandler.handle_integrity_error(e, self.entity_type)
255
+ except OperationalError as e:
256
+ raise DatabaseExceptionHandler.handle_operational_error(e, self.entity_type)
257
+ except DatabaseError as e:
258
+ raise DatabaseExceptionHandler.handle_database_error(e, self.entity_type)
259
+ except SQLAlchemyError as e:
260
+ raise DatabaseExceptionHandler.handle_sqlalchemy_error(e, self.entity_type)
261
+
262
+ return wrapper
263
+
264
+
265
+ def handle_database_errors(entity_type: str = "Resource"):
266
+ """
267
+ Convenience decorator for database exception handling.
268
+
269
+ Usage:
270
+ @handle_database_errors("Agent")
271
+ def create_agent(self, ...):
272
+ # Repository method implementation
273
+ """
274
+ return DatabaseErrorDecorator(entity_type)