ci:add version
Browse files- app.py +6 -3
- postgre_mcp_server.py +17 -17
app.py
CHANGED
|
@@ -13,7 +13,7 @@ import logging
|
|
| 13 |
|
| 14 |
# Load environment variables
|
| 15 |
load_dotenv()
|
| 16 |
-
|
| 17 |
|
| 18 |
# ======================================= Load DB configs
|
| 19 |
def load_db_configs():
|
|
@@ -120,6 +120,8 @@ with gr.Blocks(css=custom_css, theme=xtheme) as demo:
|
|
| 120 |
"""
|
| 121 |
<h1 style='text-align: center; margin-bottom: 1rem'>Talk to Your Data</h1>
|
| 122 |
<p style='text-align: center'>Ask questions about your database, analyze and visualize data.</p>
|
|
|
|
|
|
|
| 123 |
"""
|
| 124 |
)
|
| 125 |
with gr.Row(elem_classes="container"):
|
|
@@ -130,7 +132,8 @@ with gr.Blocks(css=custom_css, theme=xtheme) as demo:
|
|
| 130 |
height=1000,
|
| 131 |
show_label=False,
|
| 132 |
elem_classes="chat-container",
|
| 133 |
-
render_markdown=True
|
|
|
|
| 134 |
),
|
| 135 |
textbox=gr.Textbox(
|
| 136 |
placeholder="Type your questions here...",
|
|
@@ -163,7 +166,7 @@ with gr.Blocks(css=custom_css, theme=xtheme) as demo:
|
|
| 163 |
- π₯ Total number of customers
|
| 164 |
- π Top 10 customers by ticket count
|
| 165 |
- π Ticket count by status and visualize
|
| 166 |
-
- π Average ticket
|
| 167 |
- π§Ή Clear memory : `/clear-cache`
|
| 168 |
""")
|
| 169 |
|
|
|
|
| 13 |
|
| 14 |
# Load environment variables
|
| 15 |
load_dotenv()
|
| 16 |
+
VERSION = "0.0.1"
|
| 17 |
|
| 18 |
# ======================================= Load DB configs
|
| 19 |
def load_db_configs():
|
|
|
|
| 120 |
"""
|
| 121 |
<h1 style='text-align: center; margin-bottom: 1rem'>Talk to Your Data</h1>
|
| 122 |
<p style='text-align: center'>Ask questions about your database, analyze and visualize data.</p>
|
| 123 |
+
<p style='text-align: center; color: #666; font-size: 0.9em; margin-top: -0.5rem'>Version: {}</p>
|
| 124 |
+
""".format(VERSION)
|
| 125 |
"""
|
| 126 |
)
|
| 127 |
with gr.Row(elem_classes="container"):
|
|
|
|
| 132 |
height=1000,
|
| 133 |
show_label=False,
|
| 134 |
elem_classes="chat-container",
|
| 135 |
+
render_markdown=True,
|
| 136 |
+
type="messages"
|
| 137 |
),
|
| 138 |
textbox=gr.Textbox(
|
| 139 |
placeholder="Type your questions here...",
|
|
|
|
| 166 |
- π₯ Total number of customers
|
| 167 |
- π Top 10 customers by ticket count
|
| 168 |
- π Ticket count by status and visualize
|
| 169 |
+
- π Average ticket reopened
|
| 170 |
- π§Ή Clear memory : `/clear-cache`
|
| 171 |
""")
|
| 172 |
|
postgre_mcp_server.py
CHANGED
|
@@ -41,7 +41,7 @@ class DbContext:
|
|
| 41 |
async def db_lifespan(server: FastMCP) -> AsyncIterator[DbContext]:
|
| 42 |
"""Manage database connection lifecycle"""
|
| 43 |
dsn = os.environ["DB_URL"]
|
| 44 |
-
|
| 45 |
|
| 46 |
pool = await asyncpg.create_pool(
|
| 47 |
dsn,
|
|
@@ -52,7 +52,7 @@ async def db_lifespan(server: FastMCP) -> AsyncIterator[DbContext]:
|
|
| 52 |
command_timeout=300,
|
| 53 |
)
|
| 54 |
try:
|
| 55 |
-
yield DbContext(pool=pool, schema=
|
| 56 |
finally:
|
| 57 |
# Clean up
|
| 58 |
await pool.close()
|
|
@@ -248,9 +248,9 @@ async def execute_query(
|
|
| 248 |
|
| 249 |
|
| 250 |
# Database helper functions
|
| 251 |
-
async def get_all_tables(pool,
|
| 252 |
"""Get all tables from the database"""
|
| 253 |
-
print(f"schema: {
|
| 254 |
async with pool.acquire() as conn:
|
| 255 |
result = await conn.fetch("""
|
| 256 |
SELECT c.relname AS table_name
|
|
@@ -265,12 +265,12 @@ async def get_all_tables(pool, schema):
|
|
| 265 |
AND n.nspname = $1
|
| 266 |
AND c.relname NOT LIKE 'pg_%'
|
| 267 |
ORDER BY c.relname;
|
| 268 |
-
""",
|
| 269 |
|
| 270 |
return result
|
| 271 |
|
| 272 |
|
| 273 |
-
async def get_table_schema_info(pool,
|
| 274 |
"""Get schema information for a specific table"""
|
| 275 |
async with pool.acquire() as conn:
|
| 276 |
columns = await conn.fetch("""
|
|
@@ -284,7 +284,7 @@ async def get_table_schema_info(pool, schema, table_name):
|
|
| 284 |
WHERE table_schema = $1
|
| 285 |
AND table_name = $2
|
| 286 |
ORDER BY ordinal_position;
|
| 287 |
-
""",
|
| 288 |
|
| 289 |
return columns
|
| 290 |
|
|
@@ -325,13 +325,13 @@ async def list_tables() -> str:
|
|
| 325 |
async def get_table_schema(table_name: str) -> str:
|
| 326 |
"""Get schema information for a specific table"""
|
| 327 |
try:
|
| 328 |
-
|
| 329 |
|
| 330 |
async with db_lifespan(mcp) as db_ctx:
|
| 331 |
-
columns = await get_table_schema_info(db_ctx.pool,
|
| 332 |
|
| 333 |
if not columns:
|
| 334 |
-
return f"Table '{table_name}' not found in {
|
| 335 |
|
| 336 |
return format_table_schema(table_name, columns)
|
| 337 |
except asyncpg.exceptions.PostgresError as e:
|
|
@@ -348,7 +348,7 @@ def get_foreign_keys(table_name: str) -> str:
|
|
| 348 |
table_name: The name of the table to get foreign keys from
|
| 349 |
schema: The schema name (defaults to 'public')
|
| 350 |
"""
|
| 351 |
-
|
| 352 |
|
| 353 |
sql = """
|
| 354 |
SELECT
|
|
@@ -366,19 +366,19 @@ def get_foreign_keys(table_name: str) -> str:
|
|
| 366 |
JOIN information_schema.constraint_column_usage ccu
|
| 367 |
ON rc.unique_constraint_name = ccu.constraint_name
|
| 368 |
WHERE tc.constraint_type = 'FOREIGN KEY'
|
| 369 |
-
AND tc.table_schema = {
|
| 370 |
AND tc.table_name = {table_name}
|
| 371 |
ORDER BY tc.constraint_name, kcu.ordinal_position
|
| 372 |
"""
|
| 373 |
|
| 374 |
-
return execute_query(sql, (
|
| 375 |
|
| 376 |
|
| 377 |
@mcp.tool(description="Fetches and formats the schema details for all tables in the configured database schema.")
|
| 378 |
async def get_all_schemas() -> str:
|
| 379 |
"""Get schema information for all tables in the database"""
|
| 380 |
try:
|
| 381 |
-
|
| 382 |
|
| 383 |
async with db_lifespan(mcp) as db_ctx:
|
| 384 |
tables = await get_all_tables(db_ctx.pool, db_ctx.schema)
|
|
@@ -389,7 +389,7 @@ async def get_all_schemas() -> str:
|
|
| 389 |
all_schemas = []
|
| 390 |
for table in tables:
|
| 391 |
table_name = table['table_name']
|
| 392 |
-
columns = await get_table_schema_info(db_ctx.pool,
|
| 393 |
table_schema = format_table_schema(table_name, columns)
|
| 394 |
all_schemas.append(table_schema)
|
| 395 |
all_schemas.append("") # Add empty line between tables
|
|
@@ -447,7 +447,7 @@ async def generate_analytical_query(table_name: str) -> list[PromptMessage]:
|
|
| 447 |
Args:
|
| 448 |
table_name: The name of the table to generate analytical queries for
|
| 449 |
"""
|
| 450 |
-
|
| 451 |
try:
|
| 452 |
async with db_lifespan(mcp) as db_ctx:
|
| 453 |
pool = db_ctx.pool
|
|
@@ -455,7 +455,7 @@ async def generate_analytical_query(table_name: str) -> list[PromptMessage]:
|
|
| 455 |
columns = await conn.fetch(f"""
|
| 456 |
SELECT column_name, data_type
|
| 457 |
FROM information_schema.columns
|
| 458 |
-
WHERE table_schema = {
|
| 459 |
ORDER BY ordinal_position
|
| 460 |
""", db_ctx.schema, table_name)
|
| 461 |
|
|
|
|
| 41 |
async def db_lifespan(server: FastMCP) -> AsyncIterator[DbContext]:
|
| 42 |
"""Manage database connection lifecycle"""
|
| 43 |
dsn = os.environ["DB_URL"]
|
| 44 |
+
db_schema = os.environ["DB_SCHEMA"]
|
| 45 |
|
| 46 |
pool = await asyncpg.create_pool(
|
| 47 |
dsn,
|
|
|
|
| 52 |
command_timeout=300,
|
| 53 |
)
|
| 54 |
try:
|
| 55 |
+
yield DbContext(pool=pool, schema=db_schema)
|
| 56 |
finally:
|
| 57 |
# Clean up
|
| 58 |
await pool.close()
|
|
|
|
| 248 |
|
| 249 |
|
| 250 |
# Database helper functions
|
| 251 |
+
async def get_all_tables(pool, db_schema):
|
| 252 |
"""Get all tables from the database"""
|
| 253 |
+
print(f"schema: {db_schema}")
|
| 254 |
async with pool.acquire() as conn:
|
| 255 |
result = await conn.fetch("""
|
| 256 |
SELECT c.relname AS table_name
|
|
|
|
| 265 |
AND n.nspname = $1
|
| 266 |
AND c.relname NOT LIKE 'pg_%'
|
| 267 |
ORDER BY c.relname;
|
| 268 |
+
""", db_schema)
|
| 269 |
|
| 270 |
return result
|
| 271 |
|
| 272 |
|
| 273 |
+
async def get_table_schema_info(pool, db_schema, table_name):
|
| 274 |
"""Get schema information for a specific table"""
|
| 275 |
async with pool.acquire() as conn:
|
| 276 |
columns = await conn.fetch("""
|
|
|
|
| 284 |
WHERE table_schema = $1
|
| 285 |
AND table_name = $2
|
| 286 |
ORDER BY ordinal_position;
|
| 287 |
+
""", db_schema, table_name)
|
| 288 |
|
| 289 |
return columns
|
| 290 |
|
|
|
|
| 325 |
async def get_table_schema(table_name: str) -> str:
|
| 326 |
"""Get schema information for a specific table"""
|
| 327 |
try:
|
| 328 |
+
db_schema = os.environ["DB_SCHEMA"]
|
| 329 |
|
| 330 |
async with db_lifespan(mcp) as db_ctx:
|
| 331 |
+
columns = await get_table_schema_info(db_ctx.pool, db_schema, table_name)
|
| 332 |
|
| 333 |
if not columns:
|
| 334 |
+
return f"Table '{table_name}' not found in {db_schema} schema."
|
| 335 |
|
| 336 |
return format_table_schema(table_name, columns)
|
| 337 |
except asyncpg.exceptions.PostgresError as e:
|
|
|
|
| 348 |
table_name: The name of the table to get foreign keys from
|
| 349 |
schema: The schema name (defaults to 'public')
|
| 350 |
"""
|
| 351 |
+
db_schema = os.environ["DB_SCHEMA"]
|
| 352 |
|
| 353 |
sql = """
|
| 354 |
SELECT
|
|
|
|
| 366 |
JOIN information_schema.constraint_column_usage ccu
|
| 367 |
ON rc.unique_constraint_name = ccu.constraint_name
|
| 368 |
WHERE tc.constraint_type = 'FOREIGN KEY'
|
| 369 |
+
AND tc.table_schema = {db_schema}
|
| 370 |
AND tc.table_name = {table_name}
|
| 371 |
ORDER BY tc.constraint_name, kcu.ordinal_position
|
| 372 |
"""
|
| 373 |
|
| 374 |
+
return execute_query(sql, (db_schema, table_name))
|
| 375 |
|
| 376 |
|
| 377 |
@mcp.tool(description="Fetches and formats the schema details for all tables in the configured database schema.")
|
| 378 |
async def get_all_schemas() -> str:
|
| 379 |
"""Get schema information for all tables in the database"""
|
| 380 |
try:
|
| 381 |
+
db_schema = os.environ["DB_SCHEMA"]
|
| 382 |
|
| 383 |
async with db_lifespan(mcp) as db_ctx:
|
| 384 |
tables = await get_all_tables(db_ctx.pool, db_ctx.schema)
|
|
|
|
| 389 |
all_schemas = []
|
| 390 |
for table in tables:
|
| 391 |
table_name = table['table_name']
|
| 392 |
+
columns = await get_table_schema_info(db_ctx.pool, db_schema, table_name)
|
| 393 |
table_schema = format_table_schema(table_name, columns)
|
| 394 |
all_schemas.append(table_schema)
|
| 395 |
all_schemas.append("") # Add empty line between tables
|
|
|
|
| 447 |
Args:
|
| 448 |
table_name: The name of the table to generate analytical queries for
|
| 449 |
"""
|
| 450 |
+
db_schema = os.environ["DB_SCHEMA"]
|
| 451 |
try:
|
| 452 |
async with db_lifespan(mcp) as db_ctx:
|
| 453 |
pool = db_ctx.pool
|
|
|
|
| 455 |
columns = await conn.fetch(f"""
|
| 456 |
SELECT column_name, data_type
|
| 457 |
FROM information_schema.columns
|
| 458 |
+
WHERE table_schema = {db_schema} AND table_name = {table_name}
|
| 459 |
ORDER BY ordinal_position
|
| 460 |
""", db_ctx.schema, table_name)
|
| 461 |
|