solace-agent-mesh 1.3.1__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.
- solace_agent_mesh/agent/protocol/event_handlers.py +91 -0
- solace_agent_mesh/agent/sac/app.py +2 -0
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/483cef9a.03d5dceb.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{main.1c79039d.js → main.4adc477a.js} +2 -2
- solace_agent_mesh/assets/docs/assets/js/{runtime~main.858117b7.js → runtime~main.cf0229ea.js} +1 -1
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
- solace_agent_mesh/assets/docs/lunr-index-1757704179464.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1757704179464.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{main-C1k9E0aC.js → main-DjoMeldu.js} +8 -8
- solace_agent_mesh/client/webui/frontend/static/index.html +1 -1
- solace_agent_mesh/common/a2a/__init__.py +4 -0
- solace_agent_mesh/common/a2a/protocol.py +20 -0
- solace_agent_mesh/common/sac/sam_component_base.py +29 -9
- solace_agent_mesh/common/sam_events/__init__.py +9 -0
- solace_agent_mesh/common/sam_events/event_service.py +207 -0
- solace_agent_mesh/gateway/http_sse/alembic/env.py +1 -1
- solace_agent_mesh/gateway/http_sse/component.py +45 -35
- solace_agent_mesh/gateway/http_sse/dependencies.py +123 -60
- solace_agent_mesh/gateway/http_sse/main.py +20 -33
- solace_agent_mesh/gateway/http_sse/repository/__init__.py +37 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +9 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/message.py +41 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/session.py +45 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +16 -0
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +64 -0
- solace_agent_mesh/gateway/http_sse/repository/message_repository.py +78 -0
- solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +9 -0
- solace_agent_mesh/gateway/http_sse/repository/models/base.py +7 -0
- solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +27 -0
- solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +27 -0
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +139 -0
- solace_agent_mesh/gateway/http_sse/routers/config.py +1 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +20 -0
- solace_agent_mesh/gateway/http_sse/{api → routers}/dto/requests/session_requests.py +1 -8
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +16 -0
- solace_agent_mesh/gateway/http_sse/{api → routers}/dto/responses/session_responses.py +3 -30
- solace_agent_mesh/gateway/http_sse/{api/controllers/session_controller.py → routers/sessions.py} +20 -77
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +42 -49
- solace_agent_mesh/gateway/http_sse/{api/controllers/user_controller.py → routers/users.py} +1 -1
- solace_agent_mesh/gateway/http_sse/services/session_service.py +245 -0
- solace_agent_mesh/gateway/http_sse/session_manager.py +0 -3
- solace_agent_mesh/gateway/http_sse/shared/enums.py +0 -5
- {solace_agent_mesh-1.3.1.dist-info → solace_agent_mesh-1.3.2.dist-info}/METADATA +1 -1
- {solace_agent_mesh-1.3.1.dist-info → solace_agent_mesh-1.3.2.dist-info}/RECORD +90 -98
- solace_agent_mesh/assets/docs/assets/js/483cef9a.4e972867.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1757531604543.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1757531604543.json +0 -1
- solace_agent_mesh/gateway/http_sse/ARCHITECTURE_GUIDE.md +0 -676
- solace_agent_mesh/gateway/http_sse/api/__init__.py +0 -11
- solace_agent_mesh/gateway/http_sse/api/controllers/__init__.py +0 -9
- solace_agent_mesh/gateway/http_sse/api/controllers/task_controller.py +0 -279
- solace_agent_mesh/gateway/http_sse/api/dto/requests/__init__.py +0 -37
- solace_agent_mesh/gateway/http_sse/api/dto/requests/task_requests.py +0 -66
- solace_agent_mesh/gateway/http_sse/api/dto/responses/__init__.py +0 -43
- solace_agent_mesh/gateway/http_sse/api/dto/responses/task_responses.py +0 -74
- solace_agent_mesh/gateway/http_sse/application/__init__.py +0 -3
- solace_agent_mesh/gateway/http_sse/application/services/__init__.py +0 -3
- solace_agent_mesh/gateway/http_sse/application/services/session_service.py +0 -135
- solace_agent_mesh/gateway/http_sse/domain/entities/__init__.py +0 -3
- solace_agent_mesh/gateway/http_sse/domain/entities/session.py +0 -90
- solace_agent_mesh/gateway/http_sse/domain/repositories/__init__.py +0 -3
- solace_agent_mesh/gateway/http_sse/domain/repositories/session_repository.py +0 -54
- solace_agent_mesh/gateway/http_sse/infrastructure/__init__.py +0 -4
- solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/__init__.py +0 -3
- solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/container.py +0 -123
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/__init__.py +0 -4
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_persistence_service.py +0 -16
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_service.py +0 -119
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/models.py +0 -31
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence_service.py +0 -12
- solace_agent_mesh/gateway/http_sse/infrastructure/repositories/__init__.py +0 -3
- solace_agent_mesh/gateway/http_sse/infrastructure/repositories/session_repository.py +0 -174
- /solace_agent_mesh/assets/docs/assets/js/{main.1c79039d.js.LICENSE.txt → main.4adc477a.js.LICENSE.txt} +0 -0
- /solace_agent_mesh/gateway/http_sse/{api → routers}/dto/__init__.py +0 -0
- {solace_agent_mesh-1.3.1.dist-info → solace_agent_mesh-1.3.2.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.3.1.dist-info → solace_agent_mesh-1.3.2.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.3.1.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"]
|