solace-agent-mesh 1.3.0__py3-none-any.whl → 1.3.2__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 (145) hide show
  1. solace_agent_mesh/agent/adk/setup.py +141 -34
  2. solace_agent_mesh/agent/protocol/event_handlers.py +91 -0
  3. solace_agent_mesh/agent/sac/app.py +3 -2
  4. solace_agent_mesh/agent/tools/__init__.py +1 -0
  5. solace_agent_mesh/agent/tools/dynamic_tool.py +362 -0
  6. solace_agent_mesh/assets/docs/404.html +3 -3
  7. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.d97b8e94.js +1 -0
  8. solace_agent_mesh/assets/docs/assets/js/483cef9a.03d5dceb.js +1 -0
  9. solace_agent_mesh/assets/docs/assets/js/55f47984.cf3781c4.js +1 -0
  10. solace_agent_mesh/assets/docs/assets/js/664b740a.1b744a32.js +1 -0
  11. solace_agent_mesh/assets/docs/assets/js/75384d09.c193a8f0.js +1 -0
  12. solace_agent_mesh/assets/docs/assets/js/9a09e75d.d6607c56.js +1 -0
  13. solace_agent_mesh/assets/docs/assets/js/aba87c2f.071e2d94.js +1 -0
  14. solace_agent_mesh/assets/docs/assets/js/ae0e903d.4d8dda10.js +1 -0
  15. solace_agent_mesh/assets/docs/assets/js/c835a94d.146e3186.js +1 -0
  16. solace_agent_mesh/assets/docs/assets/js/f284c35a.7334119c.js +1 -0
  17. solace_agent_mesh/assets/docs/assets/js/main.4adc477a.js +2 -0
  18. solace_agent_mesh/assets/docs/assets/js/runtime~main.cf0229ea.js +1 -0
  19. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +29 -0
  20. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +25 -0
  21. solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html } +6 -6
  22. solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html } +6 -6
  23. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +19 -27
  24. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
  25. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
  26. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
  27. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
  28. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
  29. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
  30. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
  31. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
  32. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
  33. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +4 -4
  34. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
  35. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
  36. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
  37. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  38. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +4 -4
  39. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +5 -4
  52. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
  53. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +63 -0
  54. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
  55. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
  56. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
  57. solace_agent_mesh/assets/docs/lunr-index-1757704179464.json +1 -0
  58. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  59. solace_agent_mesh/assets/docs/search-doc-1757704179464.json +1 -0
  60. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  61. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  62. solace_agent_mesh/cli/__init__.py +1 -1
  63. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-vY5eu2lI.js → authCallback-CAX9u8a7.js} +1 -1
  64. solace_agent_mesh/client/webui/frontend/static/assets/{client-BeBkzgWW.js → client-DXU9SPI5.js} +1 -1
  65. solace_agent_mesh/client/webui/frontend/static/assets/{main-Bjys1KQs.js → main-DjoMeldu.js} +26 -26
  66. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-CE0AeXyK.js → vendor-B0BEKoAR.js} +69 -74
  67. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  68. solace_agent_mesh/client/webui/frontend/static/index.html +3 -3
  69. solace_agent_mesh/common/a2a/__init__.py +4 -0
  70. solace_agent_mesh/common/a2a/protocol.py +20 -0
  71. solace_agent_mesh/common/sac/sam_component_base.py +29 -9
  72. solace_agent_mesh/common/sam_events/__init__.py +9 -0
  73. solace_agent_mesh/common/sam_events/event_service.py +207 -0
  74. solace_agent_mesh/gateway/http_sse/alembic/env.py +1 -1
  75. solace_agent_mesh/gateway/http_sse/component.py +45 -35
  76. solace_agent_mesh/gateway/http_sse/dependencies.py +129 -66
  77. solace_agent_mesh/gateway/http_sse/main.py +22 -35
  78. solace_agent_mesh/gateway/http_sse/repository/__init__.py +37 -0
  79. solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +9 -0
  80. solace_agent_mesh/gateway/http_sse/repository/entities/message.py +41 -0
  81. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +45 -0
  82. solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +16 -0
  83. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +64 -0
  84. solace_agent_mesh/gateway/http_sse/repository/message_repository.py +78 -0
  85. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +9 -0
  86. solace_agent_mesh/gateway/http_sse/repository/models/base.py +7 -0
  87. solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +27 -0
  88. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +27 -0
  89. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +139 -0
  90. solace_agent_mesh/gateway/http_sse/routers/{agents.py → agent_cards.py} +7 -7
  91. solace_agent_mesh/gateway/http_sse/routers/config.py +1 -0
  92. solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +20 -0
  93. solace_agent_mesh/gateway/http_sse/{api → routers}/dto/requests/session_requests.py +1 -8
  94. solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +16 -0
  95. solace_agent_mesh/gateway/http_sse/{api → routers}/dto/responses/session_responses.py +3 -30
  96. solace_agent_mesh/gateway/http_sse/{api/controllers/session_controller.py → routers/sessions.py} +20 -77
  97. solace_agent_mesh/gateway/http_sse/routers/tasks.py +42 -49
  98. solace_agent_mesh/gateway/http_sse/{api/controllers/user_controller.py → routers/users.py} +1 -1
  99. solace_agent_mesh/gateway/http_sse/services/{agent_service.py → agent_card_service.py} +19 -19
  100. solace_agent_mesh/gateway/http_sse/services/session_service.py +245 -0
  101. solace_agent_mesh/gateway/http_sse/session_manager.py +0 -3
  102. solace_agent_mesh/gateway/http_sse/shared/enums.py +0 -5
  103. {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.2.dist-info}/METADATA +1 -1
  104. {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.2.dist-info}/RECORD +109 -110
  105. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.8ccb9901.js +0 -1
  106. solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +0 -1
  107. solace_agent_mesh/assets/docs/assets/js/6e0db977.39a79ca9.js +0 -1
  108. solace_agent_mesh/assets/docs/assets/js/75384d09.bf78fbdb.js +0 -1
  109. solace_agent_mesh/assets/docs/assets/js/90dd9cf6.88f385ea.js +0 -1
  110. solace_agent_mesh/assets/docs/assets/js/aba87c2f.76376d7c.js +0 -1
  111. solace_agent_mesh/assets/docs/assets/js/f284c35a.fb68323a.js +0 -1
  112. solace_agent_mesh/assets/docs/assets/js/main.08d30374.js +0 -2
  113. solace_agent_mesh/assets/docs/assets/js/runtime~main.458efb1d.js +0 -1
  114. solace_agent_mesh/assets/docs/lunr-index-1757433031159.json +0 -1
  115. solace_agent_mesh/assets/docs/search-doc-1757433031159.json +0 -1
  116. solace_agent_mesh/gateway/http_sse/ARCHITECTURE_GUIDE.md +0 -676
  117. solace_agent_mesh/gateway/http_sse/api/__init__.py +0 -11
  118. solace_agent_mesh/gateway/http_sse/api/controllers/__init__.py +0 -9
  119. solace_agent_mesh/gateway/http_sse/api/controllers/task_controller.py +0 -279
  120. solace_agent_mesh/gateway/http_sse/api/dto/requests/__init__.py +0 -37
  121. solace_agent_mesh/gateway/http_sse/api/dto/requests/task_requests.py +0 -66
  122. solace_agent_mesh/gateway/http_sse/api/dto/responses/__init__.py +0 -43
  123. solace_agent_mesh/gateway/http_sse/api/dto/responses/task_responses.py +0 -74
  124. solace_agent_mesh/gateway/http_sse/application/__init__.py +0 -3
  125. solace_agent_mesh/gateway/http_sse/application/services/__init__.py +0 -3
  126. solace_agent_mesh/gateway/http_sse/application/services/session_service.py +0 -135
  127. solace_agent_mesh/gateway/http_sse/domain/entities/__init__.py +0 -3
  128. solace_agent_mesh/gateway/http_sse/domain/entities/session.py +0 -90
  129. solace_agent_mesh/gateway/http_sse/domain/repositories/__init__.py +0 -3
  130. solace_agent_mesh/gateway/http_sse/domain/repositories/session_repository.py +0 -54
  131. solace_agent_mesh/gateway/http_sse/infrastructure/__init__.py +0 -4
  132. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/__init__.py +0 -3
  133. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/container.py +0 -123
  134. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/__init__.py +0 -4
  135. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_persistence_service.py +0 -16
  136. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_service.py +0 -119
  137. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/models.py +0 -31
  138. solace_agent_mesh/gateway/http_sse/infrastructure/persistence_service.py +0 -12
  139. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/__init__.py +0 -3
  140. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/session_repository.py +0 -174
  141. /solace_agent_mesh/assets/docs/assets/js/{main.08d30374.js.LICENSE.txt → main.4adc477a.js.LICENSE.txt} +0 -0
  142. /solace_agent_mesh/gateway/http_sse/{api → routers}/dto/__init__.py +0 -0
  143. {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.2.dist-info}/WHEEL +0 -0
  144. {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.2.dist-info}/entry_points.txt +0 -0
  145. {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,676 +0,0 @@
1
- # Clean Architecture Development Guide
2
-
3
- This guide explains how to implement new features in the Solace Agent Mesh HTTP SSE Gateway following clean architecture principles.
4
-
5
- ## 🏗️ Architecture Overview
6
-
7
- The codebase follows **Clean Architecture** with clear separation of concerns:
8
-
9
- ```
10
- src/solace_agent_mesh/gateway/http_sse/
11
- ├── domain/ # Business logic and entities
12
- ├── application/ # Use cases and services
13
- ├── infrastructure/ # External concerns (database, etc.)
14
- ├── api/ # Controllers and DTOs
15
- └── shared/ # Common utilities
16
- ```
17
-
18
- **Dependency Flow**: `API → Application → Domain ← Infrastructure`
19
-
20
- ## 📋 Step-by-Step Implementation Guide
21
-
22
- ### Step 1: Define Domain Entity
23
-
24
- **Location**: `domain/entities/`
25
-
26
- Create your business entity with validation and behavior:
27
-
28
- ```python
29
- # domain/entities/user.py
30
- from datetime import datetime, timezone
31
- from pydantic import BaseModel
32
- from ...shared.types import UserId
33
- from ...shared.enums import UserStatus
34
-
35
- class User(BaseModel):
36
- id: UserId
37
- email: str
38
- name: str
39
- status: UserStatus = UserStatus.ACTIVE
40
- created_at: datetime
41
- updated_at: datetime | None = None
42
-
43
- def update_name(self, new_name: str) -> None:
44
- if not new_name or len(new_name.strip()) == 0:
45
- raise ValueError("User name cannot be empty")
46
-
47
- self.name = new_name.strip()
48
- self.updated_at = datetime.now(timezone.utc)
49
-
50
- def activate(self) -> None:
51
- self.status = UserStatus.ACTIVE
52
- self.updated_at = datetime.now(timezone.utc)
53
-
54
- def deactivate(self) -> None:
55
- self.status = UserStatus.INACTIVE
56
- self.updated_at = datetime.now(timezone.utc)
57
- ```
58
-
59
- **Update** `domain/entities/__init__.py`:
60
- ```python
61
- from .session import Session, Message, SessionHistory
62
- from .user import User
63
-
64
- __all__ = ["Session", "Message", "SessionHistory", "User"]
65
- ```
66
-
67
- ### Step 2: Define Repository Interface
68
-
69
- **Location**: `domain/repositories/`
70
-
71
- Define what operations your entity needs (interface only):
72
-
73
- ```python
74
- # domain/repositories/user_repository.py
75
- from abc import ABC, abstractmethod
76
- from typing import List, Optional
77
-
78
- from ...shared.types import PaginationInfo, UserId
79
- from ..entities.user import User
80
-
81
- class IUserRepository(ABC):
82
- @abstractmethod
83
- def get_by_id(self, user_id: UserId) -> Optional[User]:
84
- pass
85
-
86
- @abstractmethod
87
- def get_by_email(self, email: str) -> Optional[User]:
88
- pass
89
-
90
- @abstractmethod
91
- def get_all(self, pagination: Optional[PaginationInfo] = None) -> List[User]:
92
- pass
93
-
94
- @abstractmethod
95
- def create(self, user: User) -> User:
96
- pass
97
-
98
- @abstractmethod
99
- def update(self, user: User) -> Optional[User]:
100
- pass
101
-
102
- @abstractmethod
103
- def delete(self, user_id: UserId) -> bool:
104
- pass
105
- ```
106
-
107
- **Update** `domain/repositories/__init__.py`:
108
- ```python
109
- from .session_repository import ISessionRepository, IMessageRepository
110
- from .user_repository import IUserRepository
111
-
112
- __all__ = ["ISessionRepository", "IMessageRepository", "IUserRepository"]
113
- ```
114
-
115
- ### Step 3: Create Application Service
116
-
117
- **Location**: `application/services/`
118
-
119
- Implement your business use cases:
120
-
121
- ```python
122
- # application/services/user_service.py
123
- import uuid
124
- from datetime import datetime, timezone
125
- from typing import List, Optional
126
-
127
- from ...domain.entities.user import User
128
- from ...domain.repositories.user_repository import IUserRepository
129
- from ...shared.enums import UserStatus
130
- from ...shared.types import PaginationInfo, UserId
131
-
132
- class UserService:
133
- def __init__(self, user_repository: IUserRepository):
134
- self.user_repository = user_repository
135
-
136
- def get_user_by_id(self, user_id: UserId) -> Optional[User]:
137
- return self.user_repository.get_by_id(user_id)
138
-
139
- def get_user_by_email(self, email: str) -> Optional[User]:
140
- return self.user_repository.get_by_email(email)
141
-
142
- def get_all_users(self, pagination: Optional[PaginationInfo] = None) -> List[User]:
143
- return self.user_repository.get_all(pagination)
144
-
145
- def create_user(self, email: str, name: str, user_id: Optional[str] = None) -> User:
146
- if not email or not email.strip():
147
- raise ValueError("Email cannot be empty")
148
-
149
- if not name or not name.strip():
150
- raise ValueError("Name cannot be empty")
151
-
152
- # Check if user already exists
153
- existing_user = self.user_repository.get_by_email(email)
154
- if existing_user:
155
- raise ValueError(f"User with email {email} already exists")
156
-
157
- if not user_id:
158
- user_id = str(uuid.uuid4())
159
-
160
- now = datetime.now(timezone.utc)
161
- user = User(
162
- id=user_id,
163
- email=email.strip().lower(),
164
- name=name.strip(),
165
- status=UserStatus.ACTIVE,
166
- created_at=now,
167
- updated_at=now,
168
- )
169
-
170
- return self.user_repository.create(user)
171
-
172
- def update_user_name(self, user_id: UserId, name: str) -> Optional[User]:
173
- user = self.user_repository.get_by_id(user_id)
174
- if not user:
175
- return None
176
-
177
- user.update_name(name)
178
- return self.user_repository.update(user)
179
-
180
- def deactivate_user(self, user_id: UserId) -> Optional[User]:
181
- user = self.user_repository.get_by_id(user_id)
182
- if not user:
183
- return None
184
-
185
- user.deactivate()
186
- return self.user_repository.update(user)
187
- ```
188
-
189
- **Update** `application/services/__init__.py`:
190
- ```python
191
- from .session_service import SessionService
192
- from .user_service import UserService
193
-
194
- __all__ = ["SessionService", "UserService"]
195
- ```
196
-
197
- ### Step 4: Create Database Model
198
-
199
- **Location**: `infrastructure/persistence/models.py`
200
-
201
- Add your SQLAlchemy model to the existing file:
202
-
203
- ```python
204
- # Add to existing infrastructure/persistence/models.py
205
- class UserModel(Base):
206
- __tablename__ = "users"
207
- id = Column(String, primary_key=True)
208
- email = Column(String, nullable=False, unique=True)
209
- name = Column(String, nullable=False)
210
- status = Column(String(20), nullable=False)
211
- created_at = Column(DateTime, default=func.now())
212
- updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
213
- ```
214
-
215
- **Update** `infrastructure/persistence/__init__.py`:
216
- ```python
217
- from .database_service import DatabaseService
218
- from .models import Base, SessionModel, MessageModel, UserModel
219
-
220
- __all__ = ["DatabaseService", "Base", "SessionModel", "MessageModel", "UserModel"]
221
- ```
222
-
223
- ### Step 5: Implement Repository
224
-
225
- **Location**: `infrastructure/repositories/`
226
-
227
- Implement the actual database operations:
228
-
229
- ```python
230
- # infrastructure/repositories/user_repository.py
231
- from typing import List, Optional
232
-
233
- from ...domain.entities.user import User
234
- from ...domain.repositories.user_repository import IUserRepository
235
- from ...shared.enums import UserStatus
236
- from ...shared.types import PaginationInfo, UserId
237
- from ..persistence.database_service import DatabaseService
238
- from ..persistence.models import UserModel
239
-
240
- class UserRepository(IUserRepository):
241
- def __init__(self, db_service: DatabaseService):
242
- self.db_service = db_service
243
-
244
- def get_by_id(self, user_id: UserId) -> Optional[User]:
245
- with self.db_service.read_only_session() as session:
246
- model = session.query(UserModel).filter(UserModel.id == user_id).first()
247
- return self._model_to_entity(model) if model else None
248
-
249
- def get_by_email(self, email: str) -> Optional[User]:
250
- with self.db_service.read_only_session() as session:
251
- model = session.query(UserModel).filter(UserModel.email == email.lower()).first()
252
- return self._model_to_entity(model) if model else None
253
-
254
- def get_all(self, pagination: Optional[PaginationInfo] = None) -> List[User]:
255
- with self.db_service.read_only_session() as session:
256
- query = session.query(UserModel)
257
-
258
- if pagination:
259
- offset = (pagination.page - 1) * pagination.page_size
260
- query = query.offset(offset).limit(pagination.page_size)
261
-
262
- models = query.order_by(UserModel.created_at.desc()).all()
263
- return [self._model_to_entity(model) for model in models]
264
-
265
- def create(self, user_entity: User) -> User:
266
- with self.db_service.session_scope() as session:
267
- model = UserModel(
268
- id=user_entity.id,
269
- email=user_entity.email,
270
- name=user_entity.name,
271
- status=user_entity.status.value,
272
- created_at=user_entity.created_at,
273
- updated_at=user_entity.updated_at,
274
- )
275
- session.add(model)
276
- session.flush()
277
- session.refresh(model)
278
- return self._model_to_entity(model)
279
-
280
- def update(self, user_entity: User) -> Optional[User]:
281
- with self.db_service.session_scope() as session:
282
- model = session.query(UserModel).filter(UserModel.id == user_entity.id).first()
283
-
284
- if not model:
285
- return None
286
-
287
- model.email = user_entity.email
288
- model.name = user_entity.name
289
- model.status = user_entity.status.value
290
- model.updated_at = user_entity.updated_at
291
-
292
- session.flush()
293
- session.refresh(model)
294
- return self._model_to_entity(model)
295
-
296
- def delete(self, user_id: UserId) -> bool:
297
- with self.db_service.session_scope() as session:
298
- model = session.query(UserModel).filter(UserModel.id == user_id).first()
299
-
300
- if not model:
301
- return False
302
-
303
- session.delete(model)
304
- return True
305
-
306
- def _model_to_entity(self, model: UserModel) -> User:
307
- return User(
308
- id=model.id,
309
- email=model.email,
310
- name=model.name,
311
- status=UserStatus(model.status),
312
- created_at=model.created_at,
313
- updated_at=model.updated_at,
314
- )
315
- ```
316
-
317
- **Update** `infrastructure/repositories/__init__.py`:
318
- ```python
319
- from .session_repository import SessionRepository, MessageRepository
320
- from .user_repository import UserRepository
321
-
322
- __all__ = ["SessionRepository", "MessageRepository", "UserRepository"]
323
- ```
324
-
325
- ### Step 6: Update Dependency Injection
326
-
327
- **Location**: `infrastructure/dependency_injection/container.py`
328
-
329
- Add your services to the DI container:
330
-
331
- ```python
332
- # Add these imports to the existing file
333
- from ...application.services.user_service import UserService
334
- from ...domain.repositories.user_repository import IUserRepository
335
- from ...infrastructure.repositories.user_repository import UserRepository
336
-
337
- # In the _setup_dependencies method, add:
338
- def _setup_dependencies(self) -> None:
339
- if self.has_database:
340
- # ... existing code ...
341
-
342
- # Add user repository
343
- user_repository = UserRepository(database_service)
344
- self.container.register_singleton(IUserRepository, user_repository)
345
-
346
- # Add user service factory
347
- def user_service_factory():
348
- return UserService(user_repository)
349
-
350
- self.container.register_factory(UserService, user_service_factory)
351
-
352
- # Add getter method
353
- def get_user_service(self) -> UserService | None:
354
- if not self.has_database:
355
- return None
356
- return self.container.get(UserService)
357
- ```
358
-
359
- ### Step 7: Create API DTOs
360
-
361
- **Location**: `api/dto/requests/` and `api/dto/responses/`
362
-
363
- Create request DTOs:
364
-
365
- ```python
366
- # api/dto/requests/user_requests.py
367
- from pydantic import BaseModel
368
- from typing import Optional
369
-
370
- from ....shared.types import PaginationInfo, UserId
371
-
372
- class GetUserRequest(BaseModel):
373
- user_id: UserId
374
-
375
- class GetUsersRequest(BaseModel):
376
- pagination: Optional[PaginationInfo] = None
377
-
378
- class CreateUserRequest(BaseModel):
379
- email: str
380
- name: str
381
-
382
- class UpdateUserRequest(BaseModel):
383
- user_id: UserId
384
- name: str
385
- ```
386
-
387
- Create response DTOs:
388
-
389
- ```python
390
- # api/dto/responses/user_responses.py
391
- from datetime import datetime
392
- from pydantic import BaseModel
393
- from typing import List, Optional
394
-
395
- from ....shared.enums import UserStatus
396
- from ....shared.types import PaginationInfo, UserId
397
-
398
- class UserResponse(BaseModel):
399
- id: UserId
400
- email: str
401
- name: str
402
- status: UserStatus
403
- created_at: datetime
404
- updated_at: Optional[datetime]
405
-
406
- class UserListResponse(BaseModel):
407
- users: List[UserResponse]
408
- total_count: int
409
- pagination: Optional[PaginationInfo] = None
410
- ```
411
-
412
- ### Step 8: Create API Controller
413
-
414
- **Location**: `api/controllers/`
415
-
416
- Implement your REST endpoints:
417
-
418
- ```python
419
- # api/controllers/user_controller.py
420
- from fastapi import APIRouter, Body, Depends, HTTPException, status
421
- from solace_ai_connector.common.log import log
422
-
423
- from ...application.services.user_service import UserService
424
- from ...dependencies import get_user_service
425
- from ...shared.auth_utils import get_current_user
426
- from ..dto.requests.user_requests import (
427
- CreateUserRequest,
428
- GetUserRequest,
429
- GetUsersRequest,
430
- UpdateUserRequest,
431
- )
432
- from ..dto.responses.user_responses import UserListResponse, UserResponse
433
-
434
- router = APIRouter()
435
-
436
- @router.get("/users", response_model=UserListResponse)
437
- async def get_all_users(
438
- user: dict = Depends(get_current_user),
439
- user_service: UserService = Depends(get_user_service),
440
- ):
441
- try:
442
- request_dto = GetUsersRequest()
443
- users = user_service.get_all_users(pagination=request_dto.pagination)
444
-
445
- user_responses = [
446
- UserResponse(
447
- id=user.id,
448
- email=user.email,
449
- name=user.name,
450
- status=user.status,
451
- created_at=user.created_at,
452
- updated_at=user.updated_at,
453
- )
454
- for user in users
455
- ]
456
-
457
- return UserListResponse(
458
- users=user_responses,
459
- total_count=len(user_responses),
460
- pagination=request_dto.pagination,
461
- )
462
-
463
- except Exception as e:
464
- log.error("Error fetching users: %s", e)
465
- raise HTTPException(
466
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
467
- detail="Failed to retrieve users",
468
- )
469
-
470
- @router.get("/users/{user_id}", response_model=UserResponse)
471
- async def get_user(
472
- user_id: str,
473
- current_user: dict = Depends(get_current_user),
474
- user_service: UserService = Depends(get_user_service),
475
- ):
476
- try:
477
- user = user_service.get_user_by_id(user_id)
478
- if not user:
479
- raise HTTPException(
480
- status_code=status.HTTP_404_NOT_FOUND,
481
- detail="User not found",
482
- )
483
-
484
- return UserResponse(
485
- id=user.id,
486
- email=user.email,
487
- name=user.name,
488
- status=user.status,
489
- created_at=user.created_at,
490
- updated_at=user.updated_at,
491
- )
492
-
493
- except HTTPException:
494
- raise
495
- except Exception as e:
496
- log.error("Error fetching user %s: %s", user_id, e)
497
- raise HTTPException(
498
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
499
- detail="Failed to retrieve user",
500
- )
501
-
502
- @router.post("/users", response_model=UserResponse)
503
- async def create_user(
504
- request: CreateUserRequest,
505
- current_user: dict = Depends(get_current_user),
506
- user_service: UserService = Depends(get_user_service),
507
- ):
508
- try:
509
- user = user_service.create_user(
510
- email=request.email,
511
- name=request.name,
512
- )
513
-
514
- return UserResponse(
515
- id=user.id,
516
- email=user.email,
517
- name=user.name,
518
- status=user.status,
519
- created_at=user.created_at,
520
- updated_at=user.updated_at,
521
- )
522
-
523
- except ValueError as e:
524
- raise HTTPException(
525
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
526
- detail=str(e),
527
- )
528
- except Exception as e:
529
- log.error("Error creating user: %s", e)
530
- raise HTTPException(
531
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
532
- detail="Failed to create user",
533
- )
534
-
535
- @router.patch("/users/{user_id}", response_model=UserResponse)
536
- async def update_user(
537
- user_id: str,
538
- name: str = Body(..., embed=True),
539
- current_user: dict = Depends(get_current_user),
540
- user_service: UserService = Depends(get_user_service),
541
- ):
542
- try:
543
- updated_user = user_service.update_user_name(user_id, name)
544
- if not updated_user:
545
- raise HTTPException(
546
- status_code=status.HTTP_404_NOT_FOUND,
547
- detail="User not found",
548
- )
549
-
550
- return UserResponse(
551
- id=updated_user.id,
552
- email=updated_user.email,
553
- name=updated_user.name,
554
- status=updated_user.status,
555
- created_at=updated_user.created_at,
556
- updated_at=updated_user.updated_at,
557
- )
558
-
559
- except ValueError as e:
560
- raise HTTPException(
561
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
562
- detail=str(e),
563
- )
564
- except HTTPException:
565
- raise
566
- except Exception as e:
567
- log.error("Error updating user %s: %s", user_id, e)
568
- raise HTTPException(
569
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
570
- detail="Failed to update user",
571
- )
572
- ```
573
-
574
- ### Step 9: Update Dependencies
575
-
576
- **Location**: `dependencies.py`
577
-
578
- Add your service dependency:
579
-
580
- ```python
581
- # Add to dependencies.py
582
- from .application.services.user_service import UserService
583
-
584
- def get_user_service(
585
- component: "WebUIBackendComponent" = Depends(get_sac_component),
586
- ) -> UserService:
587
- if (
588
- hasattr(component, "persistence_service")
589
- and component.persistence_service is not None
590
- ):
591
- container = component.persistence_service.container
592
- return container.get_user_service()
593
- else:
594
- raise HTTPException(
595
- status_code=status.HTTP_501_NOT_IMPLEMENTED,
596
- detail="User management requires database configuration.",
597
- )
598
- ```
599
-
600
- ### Step 10: Register Routes
601
-
602
- **Location**: `main.py`
603
-
604
- Add your controller to the FastAPI app:
605
-
606
- ```python
607
- # Add to main.py imports
608
- from .api.controllers.user_controller import router as user_router
609
-
610
- # Add to the router registration section
611
- app.include_router(
612
- user_router, prefix=f"{api_prefix}/users", tags=["Users"]
613
- )
614
- ```
615
-
616
- ### Step 11: Create Database Migration
617
-
618
- **Location**: `alembic/versions/`
619
-
620
- ```bash
621
- # Run this command to generate migration
622
- alembic revision --autogenerate -m "add_users_table"
623
- ```
624
-
625
- ## 🧪 Testing Your Implementation
626
-
627
- Test your new API endpoints:
628
-
629
- ```python
630
- # Test script
631
- import requests
632
-
633
- # Test creating a user
634
- response = requests.post("http://localhost:8080/api/v1/users", json={
635
- "email": "test@example.com",
636
- "name": "Test User"
637
- })
638
- print("Create user:", response.json())
639
-
640
- # Test getting users
641
- response = requests.get("http://localhost:8080/api/v1/users")
642
- print("Get users:", response.json())
643
- ```
644
-
645
- ## ✅ Architecture Checklist
646
-
647
- When implementing new features, ensure:
648
-
649
- - [ ] **Domain Entity**: Pure business logic, no external dependencies
650
- - [ ] **Repository Interface**: Abstract contracts in domain layer
651
- - [ ] **Application Service**: Orchestrates domain entities and repositories
652
- - [ ] **Infrastructure Repository**: Implements domain interfaces
653
- - [ ] **Database Models**: SQLAlchemy models for persistence
654
- - [ ] **API DTOs**: Request/response models for API layer
655
- - [ ] **API Controller**: REST endpoints using application services
656
- - [ ] **Dependency Injection**: All services registered in DI container
657
- - [ ] **Database Migration**: Alembic migration for schema changes
658
-
659
- ## 🚫 Common Pitfalls to Avoid
660
-
661
- 1. **Don't** import infrastructure in domain layer
662
- 2. **Don't** import application services in domain layer
663
- 3. **Don't** put business logic in controllers
664
- 4. **Don't** put database queries in application services
665
- 5. **Don't** forget to register services in DI container
666
- 6. **Don't** skip validation in domain entities
667
- 7. **Don't** expose database models directly in API responses
668
-
669
- ## 📚 Additional Resources
670
-
671
- - **Existing Examples**: Study `Session` implementation for reference
672
- - **Clean Architecture**: Follow the dependency rule strictly
673
- - **Testing**: Create unit tests by mocking repository interfaces
674
- - **Error Handling**: Use domain exceptions and proper HTTP status codes
675
-
676
- This guide ensures your new features follow the established clean architecture patterns and maintain consistency with the existing codebase.
@@ -1,11 +0,0 @@
1
- """
2
- API Layer - Presentation Tier
3
-
4
- This package contains the API layer components including controllers, DTOs,
5
- middleware, and validators for handling HTTP requests and responses.
6
- """
7
-
8
- from . import controllers
9
- from . import dto
10
-
11
- __all__ = ["controllers", "dto"]
@@ -1,9 +0,0 @@
1
- """
2
- API controllers for handling HTTP requests.
3
- """
4
-
5
- from .session_controller import router as session_router
6
- from .user_controller import router as user_router
7
- from .task_controller import router as task_router
8
-
9
- __all__ = ["session_router", "user_router", "task_router"]