Skip to content

Commit cf76857

Browse files
committed
⭐️ Comming soon pages
1 parent 1ca3dcf commit cf76857

File tree

12 files changed

+973
-14
lines changed

12 files changed

+973
-14
lines changed

agent_market_server/crud.py

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
"""
2+
CRUD operations for Agent Market Server
3+
"""
4+
import logging
5+
from typing import List, Dict, Any, Optional, Tuple
6+
7+
from sqlalchemy import func
8+
from sqlalchemy.orm import Session, joinedload
9+
10+
from database import get_db_session, as_dict
11+
from models import MarketAgent, MarketTool, MarketMcpServer, MarketAgentToolRel, MarketAgentMcpRel
12+
13+
logger = logging.getLogger("crud")
14+
15+
16+
def get_agent_list(
17+
page: int = 1,
18+
page_size: int = 20,
19+
enabled_only: bool = True
20+
) -> Tuple[List[Dict[str, Any]], int]:
21+
"""
22+
Get paginated list of agents
23+
24+
Args:
25+
page: Page number (starts from 1)
26+
page_size: Number of items per page
27+
enabled_only: If True, only return enabled agents
28+
29+
Returns:
30+
Tuple of (agent_list, total_count)
31+
"""
32+
with get_db_session() as session:
33+
query = session.query(MarketAgent).filter(MarketAgent.delete_flag != 'Y')
34+
35+
if enabled_only:
36+
query = query.filter(MarketAgent.enabled == True)
37+
38+
# Get total count
39+
total = query.count()
40+
41+
# Apply pagination
42+
offset = (page - 1) * page_size
43+
agents = query.order_by(MarketAgent.create_time.desc()).offset(offset).limit(page_size).all()
44+
45+
# Convert to list format with basic info
46+
agent_list = []
47+
for agent in agents:
48+
agent_list.append({
49+
"agent_id": agent.agent_id,
50+
"name": agent.name,
51+
"display_name": agent.display_name,
52+
"description": agent.description,
53+
"logo_url": agent.logo_url,
54+
"enabled": agent.enabled,
55+
"create_time": agent.create_time.isoformat() if agent.create_time else None
56+
})
57+
58+
return agent_list, total
59+
60+
61+
def get_agent_detail(agent_id: int) -> Optional[Dict[str, Any]]:
62+
"""
63+
Get detailed information for a specific agent
64+
65+
Args:
66+
agent_id: Agent ID
67+
68+
Returns:
69+
Dictionary with agent details including tools and MCP servers
70+
"""
71+
with get_db_session() as session:
72+
agent = session.query(MarketAgent).options(
73+
joinedload(MarketAgent.tools).joinedload(MarketAgentToolRel.tool),
74+
joinedload(MarketAgent.mcps).joinedload(MarketAgentMcpRel.mcp)
75+
).filter(
76+
MarketAgent.agent_id == agent_id,
77+
MarketAgent.delete_flag != 'Y'
78+
).first()
79+
80+
if not agent:
81+
return None
82+
83+
# Build agent info dict
84+
agent_info = as_dict(agent)
85+
86+
# Add tools information
87+
tools = []
88+
for rel in agent.tools:
89+
if rel.delete_flag != 'Y' and rel.tool.delete_flag != 'Y':
90+
tool_dict = as_dict(rel.tool)
91+
# Map tool_metadata back to metadata for API compatibility
92+
if 'tool_metadata' in tool_dict:
93+
tool_dict['metadata'] = tool_dict.pop('tool_metadata')
94+
tools.append(tool_dict)
95+
96+
agent_info['tools'] = tools
97+
98+
# Add MCP servers information
99+
mcp_info = []
100+
for rel in agent.mcps:
101+
if rel.delete_flag != 'Y' and rel.mcp.delete_flag != 'Y':
102+
mcp_dict = {
103+
"mcp_server_name": rel.mcp.mcp_server_name,
104+
"mcp_url": rel.mcp.mcp_url
105+
}
106+
mcp_info.append(mcp_dict)
107+
108+
agent_info['mcp_info'] = mcp_info
109+
110+
# Add managed_agents (empty list for compatibility)
111+
agent_info['managed_agents'] = []
112+
113+
return agent_info
114+
115+
116+
def create_agent_with_relations(agent_data: Dict[str, Any]) -> int:
117+
"""
118+
Create a new agent with tools and MCP server relationships
119+
120+
Args:
121+
agent_data: Dictionary containing agent information, tools, and mcp_info
122+
123+
Returns:
124+
Created agent ID
125+
"""
126+
with get_db_session() as session:
127+
# Extract tools and mcp_info
128+
tools_data = agent_data.pop('tools', [])
129+
mcp_info_data = agent_data.pop('mcp_info', [])
130+
agent_data.pop('managed_agents', None) # Remove if exists
131+
132+
# Create agent
133+
agent = MarketAgent(**agent_data)
134+
agent.delete_flag = 'N'
135+
session.add(agent)
136+
session.flush()
137+
138+
agent_id = agent.agent_id
139+
140+
# Create tools and relationships
141+
for tool_data in tools_data:
142+
# Check if tool already exists by name and class_name
143+
existing_tool = session.query(MarketTool).filter(
144+
MarketTool.name == tool_data['name'],
145+
MarketTool.class_name == tool_data['class_name'],
146+
MarketTool.delete_flag != 'Y'
147+
).first()
148+
149+
if existing_tool:
150+
tool_id = existing_tool.tool_id
151+
else:
152+
# Create new tool
153+
tool = MarketTool(
154+
name=tool_data['name'],
155+
class_name=tool_data['class_name'],
156+
description=tool_data.get('description'),
157+
inputs=tool_data.get('inputs'),
158+
output_type=tool_data.get('output_type'),
159+
source=tool_data.get('source'),
160+
usage=tool_data.get('usage'),
161+
params=tool_data.get('params'),
162+
tool_metadata=tool_data.get('metadata'),
163+
delete_flag='N'
164+
)
165+
session.add(tool)
166+
session.flush()
167+
tool_id = tool.tool_id
168+
169+
# Create relationship
170+
rel = MarketAgentToolRel(
171+
agent_id=agent_id,
172+
tool_id=tool_id,
173+
delete_flag='N'
174+
)
175+
session.add(rel)
176+
177+
# Create MCP servers and relationships
178+
for mcp_data in mcp_info_data:
179+
# Check if MCP server already exists by name and URL
180+
existing_mcp = session.query(MarketMcpServer).filter(
181+
MarketMcpServer.mcp_server_name == mcp_data['mcp_server_name'],
182+
MarketMcpServer.mcp_url == mcp_data['mcp_url'],
183+
MarketMcpServer.delete_flag != 'Y'
184+
).first()
185+
186+
if existing_mcp:
187+
mcp_id = existing_mcp.mcp_id
188+
else:
189+
# Create new MCP server
190+
mcp = MarketMcpServer(
191+
mcp_server_name=mcp_data['mcp_server_name'],
192+
mcp_url=mcp_data['mcp_url'],
193+
delete_flag='N'
194+
)
195+
session.add(mcp)
196+
session.flush()
197+
mcp_id = mcp.mcp_id
198+
199+
# Create relationship
200+
rel = MarketAgentMcpRel(
201+
agent_id=agent_id,
202+
mcp_id=mcp_id,
203+
delete_flag='N'
204+
)
205+
session.add(rel)
206+
207+
session.commit()
208+
return agent_id
209+
210+
211+
def delete_agent(agent_id: int) -> bool:
212+
"""
213+
Soft delete an agent
214+
215+
Args:
216+
agent_id: Agent ID to delete
217+
218+
Returns:
219+
True if successful, False otherwise
220+
"""
221+
with get_db_session() as session:
222+
agent = session.query(MarketAgent).filter(
223+
MarketAgent.agent_id == agent_id
224+
).first()
225+
226+
if not agent:
227+
return False
228+
229+
agent.delete_flag = 'Y'
230+
session.commit()
231+
return True
232+

agent_market_server/database.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""
2+
Database client and connection management for Agent Market Server
3+
"""
4+
import logging
5+
from contextlib import contextmanager
6+
from datetime import datetime, date
7+
from typing import Optional, Any
8+
from urllib.parse import quote_plus
9+
10+
import yaml
11+
from sqlalchemy import create_engine
12+
from sqlalchemy.orm import sessionmaker, class_mapper
13+
from sqlalchemy.sql.schema import MetaData
14+
15+
from models import Base
16+
17+
logger = logging.getLogger("database")
18+
19+
20+
class DatabaseConfig:
21+
"""Database configuration loader"""
22+
23+
def __init__(self, config_path: str = "config.yaml"):
24+
with open(config_path, 'r') as f:
25+
config = yaml.safe_load(f)
26+
db_config = config['database']
27+
28+
self.host = db_config['host']
29+
self.port = db_config['port']
30+
self.user = db_config['user']
31+
self.password = db_config['password']
32+
self.database = db_config['database']
33+
self.schema = db_config.get('schema', 'agent_market')
34+
self.pool_size = db_config.get('pool_size', 10)
35+
self.pool_timeout = db_config.get('pool_timeout', 30)
36+
37+
def get_connection_url(self) -> str:
38+
"""Get SQLAlchemy connection URL"""
39+
# URL encode password to handle special characters like @, :, /, etc.
40+
encoded_password = quote_plus(self.password)
41+
return f"postgresql://{self.user}:{encoded_password}@{self.host}:{self.port}/{self.database}"
42+
43+
44+
class DatabaseClient:
45+
"""PostgreSQL database client singleton"""
46+
_instance: Optional['DatabaseClient'] = None
47+
_engine = None
48+
_session_maker = None
49+
50+
def __new__(cls, config_path: str = "config.yaml"):
51+
if cls._instance is None:
52+
cls._instance = super(DatabaseClient, cls).__new__(cls)
53+
return cls._instance
54+
55+
def __init__(self, config_path: str = "config.yaml"):
56+
if self._engine is None:
57+
self.config = DatabaseConfig(config_path)
58+
self._initialize_engine()
59+
60+
def _initialize_engine(self):
61+
"""Initialize SQLAlchemy engine and session maker"""
62+
self._engine = create_engine(
63+
self.config.get_connection_url(),
64+
pool_size=self.config.pool_size,
65+
pool_pre_ping=True,
66+
pool_timeout=self.config.pool_timeout,
67+
echo=False
68+
)
69+
self._session_maker = sessionmaker(bind=self._engine)
70+
logger.info("Database engine initialized successfully")
71+
72+
@property
73+
def engine(self):
74+
"""Get SQLAlchemy engine"""
75+
return self._engine
76+
77+
@property
78+
def session_maker(self):
79+
"""Get session maker"""
80+
return self._session_maker
81+
82+
def create_tables(self):
83+
"""Create all tables defined in models"""
84+
Base.metadata.create_all(self._engine)
85+
logger.info("Database tables created successfully")
86+
87+
88+
# Global database client instance
89+
db_client = DatabaseClient()
90+
91+
92+
@contextmanager
93+
def get_db_session(db_session=None):
94+
"""
95+
Provide a transactional scope around a series of operations.
96+
97+
Args:
98+
db_session: Optional existing session to use
99+
100+
Yields:
101+
SQLAlchemy session object
102+
"""
103+
session = db_client.session_maker() if db_session is None else db_session
104+
try:
105+
yield session
106+
if db_session is None:
107+
session.commit()
108+
except Exception as e:
109+
if db_session is None:
110+
session.rollback()
111+
logger.error(f"Database operation failed: {str(e)}")
112+
raise e
113+
finally:
114+
if db_session is None:
115+
session.close()
116+
117+
118+
def as_dict(obj):
119+
"""
120+
Convert SQLAlchemy model instance to dictionary
121+
122+
Args:
123+
obj: SQLAlchemy model instance
124+
125+
Returns:
126+
Dictionary representation of the model with serializable values
127+
"""
128+
if hasattr(obj, '__table__'):
129+
result = {}
130+
mapper = class_mapper(obj.__class__)
131+
for c in mapper.columns:
132+
key = c.key
133+
try:
134+
value = getattr(obj, key)
135+
# Skip SQLAlchemy internal objects like MetaData
136+
if isinstance(value, MetaData):
137+
continue
138+
# Convert datetime objects to ISO format strings
139+
if isinstance(value, (datetime, date)):
140+
result[key] = value.isoformat() if value else None
141+
else:
142+
result[key] = value
143+
except AttributeError:
144+
# Skip if attribute doesn't exist
145+
continue
146+
return result
147+
return dict(obj._mapping)
148+
149+
150+
def filter_property(data: dict, model_class):
151+
"""
152+
Filter dictionary to only include keys that match model columns
153+
154+
Args:
155+
data: Dictionary to filter
156+
model_class: SQLAlchemy model class
157+
158+
Returns:
159+
Filtered dictionary with only valid column keys
160+
"""
161+
model_fields = model_class.__table__.columns.keys()
162+
return {key: value for key, value in data.items() if key in model_fields}
163+

0 commit comments

Comments
 (0)