solace-agent-mesh 1.4.7__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 (117) hide show
  1. solace_agent_mesh/agent/sac/component.py +2 -3
  2. solace_agent_mesh/assets/docs/404.html +3 -3
  3. solace_agent_mesh/assets/docs/assets/js/{0e682baa.da822665.js → 0e682baa.d054e1d8.js} +1 -1
  4. solace_agent_mesh/assets/docs/assets/js/166ab619.e27886d9.js +1 -0
  5. solace_agent_mesh/assets/docs/assets/js/453a82a6.3c6bb61d.js +1 -0
  6. solace_agent_mesh/assets/docs/assets/js/{75384d09.1e7d7cb7.js → 75384d09.c19e8b51.js} +1 -1
  7. solace_agent_mesh/assets/docs/assets/js/a3a92b25.af35e313.js +1 -0
  8. solace_agent_mesh/assets/docs/assets/js/{bac0be12.bf0181cf.js → bac0be12.17de4316.js} +1 -1
  9. solace_agent_mesh/assets/docs/assets/js/d6a81ee7.829198f1.js +1 -0
  10. solace_agent_mesh/assets/docs/assets/js/f284c35a.ed8dd236.js +1 -0
  11. solace_agent_mesh/assets/docs/assets/js/{main.11f9f9f3.js → main.86924c42.js} +2 -2
  12. solace_agent_mesh/assets/docs/assets/js/runtime~main.0d2ff2b6.js +1 -0
  13. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +4 -4
  14. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/rbac-setup-guilde/index.html +4 -4
  15. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +4 -4
  16. 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
  17. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +4 -4
  18. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +4 -4
  19. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
  20. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
  21. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
  22. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
  23. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
  24. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
  25. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
  26. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
  27. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
  28. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +11 -12
  29. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/litellm_models/index.html +49 -0
  30. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
  31. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
  32. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
  33. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  34. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +4 -4
  35. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +5 -5
  36. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +5 -5
  37. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
  38. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +6 -6
  39. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
  52. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
  53. solace_agent_mesh/assets/docs/lunr-index-1759246102819.json +1 -0
  54. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  55. solace_agent_mesh/assets/docs/search-doc-1759246102819.json +1 -0
  56. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  57. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  58. solace_agent_mesh/cli/__init__.py +1 -1
  59. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +6 -3
  60. solace_agent_mesh/cli/commands/init_cmd/__init__.py +3 -3
  61. solace_agent_mesh/cli/commands/init_cmd/broker_step.py +1 -1
  62. solace_agent_mesh/cli/commands/init_cmd/env_step.py +1 -1
  63. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +4 -4
  64. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +12 -1
  65. solace_agent_mesh/cli/commands/plugin_cmd/install_cmd.py +5 -5
  66. solace_agent_mesh/client/webui/frontend/static/assets/main-B0PHV3hm.js +339 -0
  67. solace_agent_mesh/client/webui/frontend/static/index.html +1 -1
  68. solace_agent_mesh/config_portal/backend/common.py +1 -1
  69. solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-bFMKlzKf.js → _index-BNuqpWDc.js} +1 -1
  70. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-89db7c30.js → manifest-44d62be6.js} +1 -1
  71. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  72. solace_agent_mesh/gateway/http_sse/component.py +21 -15
  73. solace_agent_mesh/gateway/http_sse/dependencies.py +84 -88
  74. solace_agent_mesh/gateway/http_sse/main.py +8 -2
  75. solace_agent_mesh/gateway/http_sse/repository/entities/message.py +3 -1
  76. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +3 -1
  77. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +5 -0
  78. solace_agent_mesh/gateway/http_sse/repository/message_repository.py +25 -23
  79. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +12 -4
  80. solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +19 -1
  81. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +19 -1
  82. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +46 -42
  83. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +199 -59
  84. solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +1 -6
  85. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +3 -17
  86. solace_agent_mesh/gateway/http_sse/routers/people.py +4 -37
  87. solace_agent_mesh/gateway/http_sse/routers/sessions.py +33 -68
  88. solace_agent_mesh/gateway/http_sse/routers/tasks.py +54 -28
  89. solace_agent_mesh/gateway/http_sse/services/session_service.py +60 -28
  90. solace_agent_mesh/gateway/http_sse/shared/__init__.py +122 -1
  91. solace_agent_mesh/gateway/http_sse/shared/base_repository.py +278 -0
  92. solace_agent_mesh/gateway/http_sse/shared/database_exceptions.py +274 -0
  93. solace_agent_mesh/gateway/http_sse/shared/database_helpers.py +43 -0
  94. solace_agent_mesh/gateway/http_sse/shared/error_dto.py +107 -0
  95. solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +192 -0
  96. solace_agent_mesh/gateway/http_sse/shared/exceptions.py +192 -0
  97. solace_agent_mesh/gateway/http_sse/shared/pagination.py +138 -0
  98. solace_agent_mesh/gateway/http_sse/shared/response_utils.py +134 -0
  99. solace_agent_mesh/gateway/http_sse/shared/utils.py +22 -0
  100. solace_agent_mesh/templates/plugin_agent_config_template.yaml +1 -1
  101. solace_agent_mesh/templates/plugin_custom_config_template.yaml +1 -1
  102. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +1 -1
  103. solace_agent_mesh/templates/shared_config.yaml +1 -1
  104. {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/METADATA +34 -35
  105. {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/RECORD +109 -98
  106. solace_agent_mesh/assets/docs/assets/js/166ab619.bdddc63a.js +0 -1
  107. solace_agent_mesh/assets/docs/assets/js/a3a92b25.6def8980.js +0 -1
  108. solace_agent_mesh/assets/docs/assets/js/beecea0d.ce915979.js +0 -1
  109. solace_agent_mesh/assets/docs/assets/js/f284c35a.525933db.js +0 -1
  110. solace_agent_mesh/assets/docs/assets/js/runtime~main.5922bcf0.js +0 -1
  111. solace_agent_mesh/assets/docs/lunr-index-1759151175744.json +0 -1
  112. solace_agent_mesh/assets/docs/search-doc-1759151175744.json +0 -1
  113. solace_agent_mesh/client/webui/frontend/static/assets/main-BKIoiLSu.js +0 -339
  114. /solace_agent_mesh/assets/docs/assets/js/{main.11f9f9f3.js.LICENSE.txt → main.86924c42.js.LICENSE.txt} +0 -0
  115. {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/WHEEL +0 -0
  116. {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/entry_points.txt +0 -0
  117. {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,43 @@
1
+ """
2
+ Database Helper Functions
3
+
4
+ Provides database utility functions and custom types.
5
+ Separated from dependencies.py to avoid circular imports.
6
+ """
7
+
8
+ import json
9
+ from sqlalchemy import Text, TypeDecorator
10
+ from .exceptions import DataIntegrityError
11
+
12
+
13
+ class SimpleJSON(TypeDecorator):
14
+ """Simple JSON type using Text storage for all databases."""
15
+
16
+ impl = Text
17
+ cache_ok = True
18
+
19
+ def process_bind_param(self, value, dialect):
20
+ """Convert Python object to JSON string for storage."""
21
+ if value is not None:
22
+ return json.dumps(value, default=self._json_serializer, ensure_ascii=False)
23
+ return value
24
+
25
+ def process_result_value(self, value, dialect):
26
+ """Convert JSON string back to Python object."""
27
+ if value is not None and isinstance(value, str):
28
+ try:
29
+ return json.loads(value)
30
+ except (ValueError, TypeError, json.JSONDecodeError) as e:
31
+ raise DataIntegrityError("json_parsing", f"Invalid JSON data in database: {value}") from e
32
+ return value
33
+
34
+ @staticmethod
35
+ def _json_serializer(obj):
36
+ """Custom JSON serializer for complex objects."""
37
+ if model_dump := getattr(obj, 'model_dump', None):
38
+ return model_dump()
39
+ elif dict_method := getattr(obj, 'dict', None):
40
+ return dict_method()
41
+ elif obj_dict := getattr(obj, '__dict__', None):
42
+ return obj_dict
43
+ raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
@@ -0,0 +1,107 @@
1
+ """
2
+ Standardized error response DTOs for HTTP APIs.
3
+
4
+ This module provides consistent error response formats that can be used
5
+ across any web application for uniform error handling.
6
+ """
7
+
8
+ from typing import Dict, List, Optional
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class EventErrorDTO(BaseModel):
13
+ """
14
+ Simplified and standardized error response format.
15
+
16
+ This provides consistent error responses across all endpoints with:
17
+ - message: Human-readable error description
18
+ - validationDetails: Field-level validation errors (optional)
19
+
20
+ Examples:
21
+ 404 Not Found:
22
+ {
23
+ "message": "Could not find User with id: 123",
24
+ "validationDetails": null
25
+ }
26
+
27
+ 422 Validation Error:
28
+ {
29
+ "message": "Invalid input data",
30
+ "validationDetails": {
31
+ "email": ["Invalid email format"],
32
+ "name": ["Name is required"]
33
+ }
34
+ }
35
+ """
36
+ message: str = Field(..., description="Human-readable error message")
37
+ validationDetails: Optional[Dict[str, List[str]]] = Field(
38
+ default=None,
39
+ description="Field-level validation errors"
40
+ )
41
+
42
+ @classmethod
43
+ def create(
44
+ cls,
45
+ message: str,
46
+ validation_details: Optional[Dict[str, List[str]]] = None
47
+ ) -> "EventErrorDTO":
48
+ """
49
+ Create a new EventErrorDTO.
50
+
51
+ Args:
52
+ message: Human-readable error message
53
+ validation_details: Optional field-level validation errors
54
+
55
+ Returns:
56
+ EventErrorDTO instance
57
+ """
58
+ return cls(
59
+ message=message,
60
+ validationDetails=validation_details
61
+ )
62
+
63
+ @classmethod
64
+ def not_found(cls, entity_type: str, entity_id: str) -> "EventErrorDTO":
65
+ """
66
+ Create a 404 Not Found error with standardized message format.
67
+
68
+ Args:
69
+ entity_type: The type of entity (e.g., "User", "Product", "Order")
70
+ entity_id: The ID that was not found
71
+
72
+ Returns:
73
+ EventErrorDTO with 404 message format
74
+
75
+ Example:
76
+ EventErrorDTO.not_found("User", "123")
77
+ # Returns: {"message": "Could not find User with id: 123", "validationDetails": null}
78
+ """
79
+ message = f"Could not find {entity_type} with id: {entity_id}"
80
+ return cls.create(message=message)
81
+
82
+ @classmethod
83
+ def validation_error(
84
+ cls,
85
+ message: str,
86
+ validation_details: Dict[str, List[str]]
87
+ ) -> "EventErrorDTO":
88
+ """
89
+ Create a validation error with field-level details.
90
+
91
+ Args:
92
+ message: Main validation error message
93
+ validation_details: Dict mapping field names to error lists
94
+
95
+ Returns:
96
+ EventErrorDTO with validation details
97
+
98
+ Example:
99
+ EventErrorDTO.validation_error(
100
+ "Invalid user data",
101
+ {"email": ["Invalid format"], "age": ["Must be positive"]}
102
+ )
103
+ """
104
+ return cls.create(
105
+ message=message,
106
+ validation_details=validation_details
107
+ )
@@ -0,0 +1,192 @@
1
+ """
2
+ Generic FastAPI exception handlers for consistent HTTP error responses.
3
+
4
+ This module provides FastAPI exception handlers that convert domain exceptions
5
+ into appropriate HTTP responses with consistent formatting. These handlers
6
+ can be used by any FastAPI application for uniform error handling.
7
+ """
8
+
9
+ from fastapi import HTTPException, Request, status
10
+ from fastapi.responses import JSONResponse
11
+ from fastapi.exceptions import RequestValidationError
12
+ from pydantic import ValidationError as PydanticValidationError
13
+ from starlette.exceptions import HTTPException as StarletteHTTPException
14
+
15
+ from .exceptions import (
16
+ WebUIBackendException,
17
+ ValidationError,
18
+ EntityNotFoundError,
19
+ EntityAlreadyExistsError,
20
+ BusinessRuleViolationError,
21
+ ConfigurationError,
22
+ DataIntegrityError,
23
+ ExternalServiceError,
24
+ )
25
+ from .error_dto import EventErrorDTO
26
+
27
+
28
+ def create_error_response(
29
+ status_code: int, message: str, validation_details: dict = None
30
+ ) -> JSONResponse:
31
+ """Create standardized error response using EventErrorDTO format."""
32
+ if validation_details:
33
+ error_dto = EventErrorDTO.validation_error(message, validation_details)
34
+ else:
35
+ error_dto = EventErrorDTO.create(message)
36
+
37
+ return JSONResponse(status_code=status_code, content=error_dto.model_dump())
38
+
39
+
40
+ async def validation_error_handler(
41
+ request: Request, exc: ValidationError
42
+ ) -> JSONResponse:
43
+ """Handle domain validation errors - 400 Bad Request."""
44
+ if exc.validation_details:
45
+ # Validation errors with field details
46
+ error_dto = EventErrorDTO.validation_error(exc.message, exc.validation_details)
47
+ else:
48
+ # General bad request
49
+ error_dto = EventErrorDTO.create("bad request" if not exc.message else exc.message)
50
+
51
+ return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST, content=error_dto.model_dump())
52
+
53
+
54
+ async def entity_not_found_handler(
55
+ request: Request, exc: EntityNotFoundError
56
+ ) -> JSONResponse:
57
+ """Handle entity not found errors - 404 Not Found."""
58
+ # Format: "Could not find applicationDomain with id: some-invalid-id"
59
+ message = f"Could not find {exc.entity_type} with id: {exc.entity_id}"
60
+ error_dto = EventErrorDTO.create(message)
61
+ return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content=error_dto.model_dump())
62
+
63
+
64
+ async def entity_already_exists_handler(
65
+ request: Request, exc: EntityAlreadyExistsError
66
+ ) -> JSONResponse:
67
+ """Handle entity already exists errors - 409 Conflict."""
68
+ error_dto = EventErrorDTO.create(exc.message)
69
+ return JSONResponse(status_code=status.HTTP_409_CONFLICT, content=error_dto.model_dump())
70
+
71
+
72
+ async def business_rule_violation_handler(
73
+ request: Request, exc: BusinessRuleViolationError
74
+ ) -> JSONResponse:
75
+ """Handle business rule violations - 400 Bad Request."""
76
+ error_dto = EventErrorDTO.create(exc.message)
77
+ return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST, content=error_dto.model_dump())
78
+
79
+
80
+ async def configuration_error_handler(
81
+ request: Request, exc: ConfigurationError
82
+ ) -> JSONResponse:
83
+ """Handle configuration errors - 500 Internal Server Error."""
84
+ error_dto = EventErrorDTO.create("An unexpected server error occurred.")
85
+ return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=error_dto.model_dump())
86
+
87
+
88
+ async def data_integrity_error_handler(
89
+ request: Request, exc: DataIntegrityError
90
+ ) -> JSONResponse:
91
+ """Handle data integrity errors - 400 Bad Request."""
92
+ # Format: "An entity of type applicationDomain was passed in an invalid format"
93
+ message = f"An entity of type {exc.entity_type} was passed in an invalid format" if hasattr(exc, 'entity_type') else "bad request"
94
+ error_dto = EventErrorDTO.create(message)
95
+ return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST, content=error_dto.model_dump())
96
+
97
+
98
+ async def external_service_error_handler(
99
+ request: Request, exc: ExternalServiceError
100
+ ) -> JSONResponse:
101
+ """Handle external service errors - 503 Service Unavailable."""
102
+ error_dto = EventErrorDTO.create("Service is unavailable.")
103
+ return JSONResponse(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, content=error_dto.model_dump())
104
+
105
+
106
+ async def webui_backend_exception_handler(
107
+ request: Request, exc: WebUIBackendException
108
+ ) -> JSONResponse:
109
+ """Handle generic WebUI backend exceptions - 500 Internal Server Error."""
110
+ error_dto = EventErrorDTO.create("An unexpected server error occurred.")
111
+ return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=error_dto.model_dump())
112
+
113
+
114
+ async def http_exception_handler(
115
+ request: Request, exc: HTTPException
116
+ ) -> JSONResponse:
117
+ """Handle FastAPI HTTPExceptions with standardized format."""
118
+ # Map common HTTP status codes to standard messages
119
+ message_map = {
120
+ 400: "bad request",
121
+ 401: "An authentication error occurred. Try logging out and in again.",
122
+ 403: "You do not have permissions to perform this operation",
123
+ 404: f"Resource not found with path {request.url.path}",
124
+ 405: f"Request method '{request.method}' is not supported",
125
+ 406: "Unacceptable Content-type.",
126
+ 429: "Rate limit exceeded message here",
127
+ 500: "An unexpected server error occurred.",
128
+ 501: "Not Implemented",
129
+ 503: "Service is unavailable.",
130
+ }
131
+
132
+ message = message_map.get(exc.status_code, exc.detail)
133
+ error_dto = EventErrorDTO.create(message)
134
+ return JSONResponse(status_code=exc.status_code, content=error_dto.model_dump())
135
+
136
+
137
+ async def request_validation_exception_handler(
138
+ request: Request, exc: RequestValidationError
139
+ ) -> JSONResponse:
140
+ """Handle FastAPI request validation errors - 400 Bad Request."""
141
+ # Convert Pydantic validation errors to our format
142
+ validation_details = {}
143
+ for error in exc.errors():
144
+ field_path = ".".join(str(x) for x in error["loc"] if x != "body")
145
+ if field_path not in validation_details:
146
+ validation_details[field_path] = []
147
+ validation_details[field_path].append(error["msg"])
148
+
149
+ if validation_details:
150
+ # Field-specific validation errors
151
+ message = "body must not be empty" if not validation_details else "Validation error"
152
+ error_dto = EventErrorDTO.validation_error(message, validation_details)
153
+ else:
154
+ # General bad request
155
+ error_dto = EventErrorDTO.create("bad request")
156
+
157
+ return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST, content=error_dto.model_dump())
158
+
159
+
160
+ def register_exception_handlers(app):
161
+ """
162
+ Register all exception handlers with a FastAPI app.
163
+
164
+ This function registers all the generic exception handlers, providing
165
+ consistent error responses across the entire application.
166
+
167
+ Args:
168
+ app: FastAPI application instance
169
+
170
+ Example:
171
+ from fastapi import FastAPI
172
+ from solace_agent_mesh.gateway.http_sse.shared.exception_handlers import register_exception_handlers
173
+
174
+ app = FastAPI()
175
+ register_exception_handlers(app)
176
+ """
177
+ # Domain exception handlers
178
+ app.add_exception_handler(ValidationError, validation_error_handler)
179
+ app.add_exception_handler(EntityNotFoundError, entity_not_found_handler)
180
+ app.add_exception_handler(EntityAlreadyExistsError, entity_already_exists_handler)
181
+ app.add_exception_handler(
182
+ BusinessRuleViolationError, business_rule_violation_handler
183
+ )
184
+ app.add_exception_handler(ConfigurationError, configuration_error_handler)
185
+ app.add_exception_handler(DataIntegrityError, data_integrity_error_handler)
186
+ app.add_exception_handler(ExternalServiceError, external_service_error_handler)
187
+ app.add_exception_handler(WebUIBackendException, webui_backend_exception_handler)
188
+
189
+ # FastAPI built-in exception handlers
190
+ app.add_exception_handler(HTTPException, http_exception_handler)
191
+ app.add_exception_handler(StarletteHTTPException, http_exception_handler)
192
+ app.add_exception_handler(RequestValidationError, request_validation_exception_handler)
@@ -0,0 +1,192 @@
1
+ """
2
+ Generic web exceptions for HTTP/REST APIs.
3
+
4
+ This module provides a comprehensive set of generic exception classes
5
+ that can be used by any web application for consistent error handling.
6
+ """
7
+
8
+ from typing import Any, Dict, List, Optional
9
+ from enum import Enum
10
+
11
+
12
+ class EntityOperation(Enum):
13
+ """Operations that can be performed on entities."""
14
+ CREATE = "create"
15
+ UPDATE = "update"
16
+ DELETE = "delete"
17
+ READ = "read"
18
+
19
+
20
+ class WebUIBackendException(Exception):
21
+ """Base exception for all web UI backend errors."""
22
+
23
+ def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
24
+ self.message = message
25
+ self.details = details or {}
26
+ super().__init__(self.message)
27
+
28
+
29
+ class ValidationError(WebUIBackendException):
30
+ """
31
+ Exception for validation errors with field-level details.
32
+
33
+ This exception supports both simple validation messages and detailed
34
+ field-level validation errors with user-friendly message formatting.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ message: str,
40
+ validation_details: Optional[Dict[str, List[str]]] = None,
41
+ entity_type: Optional[str] = None,
42
+ entity_identifier: Optional[str] = None
43
+ ):
44
+ self.validation_details = validation_details or {}
45
+ self.entity_type = entity_type
46
+ self.entity_identifier = entity_identifier
47
+ super().__init__(message)
48
+
49
+ def get_user_friendly_message(self, operation: EntityOperation) -> str:
50
+ """Generate a user-friendly error message based on context."""
51
+ if not self.entity_identifier or not self.entity_type:
52
+ return self.message
53
+
54
+ if self.validation_details:
55
+ field = next(iter(self.validation_details.keys()))
56
+ error_msg = self.validation_details[field][0]
57
+ return f"Unable to {operation.value} {self.entity_type} {self.entity_identifier} because of a problem with the {field}: {error_msg.lower()}"
58
+
59
+ if self.message:
60
+ return f"Unable to {operation.value} {self.entity_type} {self.entity_identifier}: {self.message.lower()}"
61
+
62
+ return self.message
63
+
64
+ @classmethod
65
+ def builder(cls):
66
+ """Create a ValidationErrorBuilder for fluent error construction."""
67
+ return ValidationErrorBuilder()
68
+
69
+
70
+ class ValidationErrorBuilder:
71
+ """Builder for constructing ValidationError instances with fluent API."""
72
+
73
+ def __init__(self):
74
+ self._message = ""
75
+ self._validation_details = {}
76
+ self._entity_type = None
77
+ self._entity_identifier = None
78
+
79
+ def message(self, message: str):
80
+ """Set the main error message."""
81
+ self._message = message
82
+ return self
83
+
84
+ def formatted_message(self, pattern: str, *args):
85
+ """Set a formatted error message."""
86
+ self._message = pattern.format(*args)
87
+ return self
88
+
89
+ def validation_detail(self, field: str, errors: List[str]):
90
+ """Add validation details for a specific field."""
91
+ self._validation_details[field] = errors
92
+ return self
93
+
94
+ def formatted_validation_detail(self, field: str, pattern: str, *args):
95
+ """Add a formatted validation detail for a field."""
96
+ error_msg = pattern.format(*args)
97
+ self._validation_details[field] = [error_msg]
98
+ return self
99
+
100
+ def entity_type(self, entity_type: str):
101
+ """Set the entity type for context."""
102
+ self._entity_type = entity_type
103
+ return self
104
+
105
+ def entity_identifier(self, identifier: str):
106
+ """Set the entity identifier for context."""
107
+ self._entity_identifier = identifier
108
+ return self
109
+
110
+ def build(self) -> ValidationError:
111
+ """Build the ValidationError instance."""
112
+ return ValidationError(
113
+ self._message,
114
+ self._validation_details,
115
+ self._entity_type,
116
+ self._entity_identifier
117
+ )
118
+
119
+
120
+ class EntityNotFoundError(WebUIBackendException):
121
+ """
122
+ Generic exception for when an entity is not found.
123
+
124
+ This replaces all specific "NotFound" exceptions with a single generic one.
125
+ Format: "Could not find {entity_type} with id: {entity_id}"
126
+ """
127
+
128
+ def __init__(self, entity_type: str, entity_id: str):
129
+ self.entity_type = entity_type
130
+ self.entity_id = str(entity_id)
131
+ message = f"Could not find {entity_type} with id: {self.entity_id}"
132
+ details = {"entity_type": entity_type, "entity_id": self.entity_id}
133
+ super().__init__(message, details)
134
+
135
+
136
+ class EntityAlreadyExistsError(WebUIBackendException):
137
+ """Exception for when an entity already exists."""
138
+
139
+ def __init__(self, entity_type: str, identifier: str, value: Any = None):
140
+ self.entity_type = entity_type
141
+ self.identifier = identifier
142
+ self.value = str(value) if value is not None else None
143
+
144
+ if value is not None:
145
+ message = f"{entity_type} with {identifier} '{self.value}' already exists"
146
+ else:
147
+ message = f"{entity_type} already exists"
148
+
149
+ details = {"entity_type": entity_type, "identifier": identifier}
150
+ if self.value:
151
+ details["value"] = self.value
152
+
153
+ super().__init__(message, details)
154
+
155
+
156
+ class BusinessRuleViolationError(WebUIBackendException):
157
+ """Exception for business rule violations."""
158
+
159
+ def __init__(self, rule: str, message: str):
160
+ self.rule = rule
161
+ details = {"rule": rule}
162
+ super().__init__(message, details)
163
+
164
+
165
+ class ConfigurationError(WebUIBackendException):
166
+ """Exception for configuration-related errors."""
167
+
168
+ def __init__(self, component: str, message: str):
169
+ self.component = component
170
+ details = {"component": component}
171
+ super().__init__(message, details)
172
+
173
+
174
+ class DataIntegrityError(WebUIBackendException):
175
+ """Exception for data integrity violations."""
176
+
177
+ def __init__(self, constraint: str, message: str):
178
+ self.constraint = constraint
179
+ details = {"constraint": constraint}
180
+ super().__init__(message, details)
181
+
182
+
183
+ class ExternalServiceError(WebUIBackendException):
184
+ """Exception for external service communication errors."""
185
+
186
+ def __init__(self, service: str, message: str, status_code: Optional[int] = None):
187
+ self.service = service
188
+ self.status_code = status_code
189
+ details = {"service": service}
190
+ if status_code:
191
+ details["status_code"] = status_code
192
+ super().__init__(message, details)
@@ -0,0 +1,138 @@
1
+ """
2
+ Pagination utilities for API responses.
3
+
4
+ Provides standard pagination patterns for consistent API responses across the application.
5
+
6
+ Default pagination settings:
7
+ - Page number: 1
8
+ - Page size: 20
9
+ - Max page size: 100
10
+ """
11
+
12
+ from pydantic import BaseModel, Field
13
+ from typing import TypeVar, Generic
14
+
15
+ T = TypeVar("T")
16
+
17
+ DEFAULT_PAGE_NUMBER = 1
18
+ DEFAULT_PAGE_SIZE = 20
19
+ MAX_PAGE_SIZE = 100
20
+
21
+
22
+ class PaginationParams(BaseModel):
23
+ """
24
+ Request parameters for pagination with sensible defaults.
25
+
26
+ Defaults:
27
+ - page_number: 1
28
+ - page_size: 20
29
+ """
30
+ page_number: int = Field(default=DEFAULT_PAGE_NUMBER, ge=1, alias="pageNumber")
31
+ page_size: int = Field(default=DEFAULT_PAGE_SIZE, ge=1, le=MAX_PAGE_SIZE, alias="pageSize")
32
+
33
+ @property
34
+ def offset(self) -> int:
35
+ """Calculate the offset for database queries."""
36
+ return (self.page_number - 1) * self.page_size
37
+
38
+ model_config = {"populate_by_name": True}
39
+
40
+
41
+ def get_pagination_or_default(pagination: PaginationParams | None = None) -> PaginationParams:
42
+ """
43
+ Get pagination parameters or return defaults if None.
44
+
45
+ This helper ensures all paginated endpoints use the same defaults:
46
+ - page_number: 1
47
+ - page_size: 20
48
+
49
+ Args:
50
+ pagination: Optional pagination parameters
51
+
52
+ Returns:
53
+ PaginationParams with defaults if None provided
54
+
55
+ Example:
56
+ pagination = get_pagination_or_default(request_pagination)
57
+ # Always returns valid PaginationParams with defaults if None
58
+ """
59
+ if pagination is None:
60
+ return PaginationParams()
61
+ return pagination
62
+
63
+
64
+ class PaginationMeta(BaseModel):
65
+ """Pagination metadata for API responses."""
66
+ page_number: int = Field(..., alias="pageNumber")
67
+ count: int
68
+ page_size: int = Field(..., alias="pageSize")
69
+ next_page: int | None = Field(..., alias="nextPage")
70
+ total_pages: int = Field(..., alias="totalPages")
71
+
72
+ model_config = {"populate_by_name": True}
73
+
74
+
75
+ class Meta(BaseModel):
76
+ """Metadata container for API responses."""
77
+ pagination: PaginationMeta
78
+
79
+
80
+ class PaginatedResponse(BaseModel, Generic[T]):
81
+ """Generic paginated response with data and metadata."""
82
+ data: list[T]
83
+ meta: Meta
84
+
85
+ @classmethod
86
+ def create(
87
+ cls, data: list[T], total_count: int, pagination: PaginationParams
88
+ ) -> "PaginatedResponse[T]":
89
+ """
90
+ Create a paginated response from data and pagination parameters.
91
+
92
+ Args:
93
+ data: List of items for current page
94
+ total_count: Total number of items across all pages
95
+ pagination: Pagination parameters used for the request
96
+
97
+ Returns:
98
+ PaginatedResponse with data and calculated metadata
99
+ """
100
+ total_pages = (total_count + pagination.page_size - 1) // pagination.page_size
101
+ next_page = pagination.page_number + 1 if pagination.page_number < total_pages else None
102
+
103
+ pagination_meta = PaginationMeta(
104
+ page_number=pagination.page_number,
105
+ count=total_count,
106
+ page_size=pagination.page_size,
107
+ next_page=next_page,
108
+ total_pages=total_pages,
109
+ )
110
+
111
+ return cls(
112
+ data=data,
113
+ meta=Meta(pagination=pagination_meta)
114
+ )
115
+
116
+ model_config = {"populate_by_name": True}
117
+
118
+
119
+ class DataResponse(BaseModel, Generic[T]):
120
+ """Simple data response wrapper."""
121
+ data: T
122
+
123
+ @classmethod
124
+ def create(cls, data: T) -> "DataResponse[T]":
125
+ """Create a data response from data."""
126
+ return cls(data=data)
127
+
128
+
129
+ __all__ = [
130
+ "PaginationParams",
131
+ "PaginationMeta",
132
+ "PaginatedResponse",
133
+ "DataResponse",
134
+ "get_pagination_or_default",
135
+ "DEFAULT_PAGE_NUMBER",
136
+ "DEFAULT_PAGE_SIZE",
137
+ "MAX_PAGE_SIZE",
138
+ ]