Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
5fd292a
break the code to core and api
gkorland Sep 6, 2025
1848e0d
refactor load steps
gkorland Sep 6, 2025
383d303
Bump litellm from 1.76.2 to 1.76.3
dependabot[bot] Sep 8, 2025
b76bca5
Bump pytest from 8.4.1 to 8.4.2
dependabot[bot] Sep 8, 2025
285ace8
Bump pytest-playwright from 0.7.0 to 0.7.1
dependabot[bot] Sep 8, 2025
432afb0
Bump @typescript-eslint/parser from 8.40.0 to 8.42.0 in /app
dependabot[bot] Sep 8, 2025
45a5e13
Bump eslint from 9.34.0 to 9.35.0 in /app
dependabot[bot] Sep 8, 2025
d1cca0e
Merge branch 'staging' into lib
galshubeli Sep 10, 2025
f9aa51b
lint
galshubeli Sep 10, 2025
2e0c5fd
Merge pull request #226 from FalkorDB/lib
gkorland Sep 11, 2025
edf15d1
Merge branch 'staging' into dependabot/npm_and_yarn/app/staging/types…
gkorland Sep 11, 2025
97f9825
Merge pull request #236 from FalkorDB/dependabot/npm_and_yarn/app/sta…
gkorland Sep 11, 2025
ef4939b
Merge branch 'staging' into dependabot/npm_and_yarn/app/staging/eslin…
gkorland Sep 11, 2025
e7d1c96
Merge pull request #237 from FalkorDB/dependabot/npm_and_yarn/app/sta…
gkorland Sep 11, 2025
df57d54
Merge branch 'staging' into dependabot/pip/staging/pytest-playwright-…
gkorland Sep 11, 2025
3bb1279
Merge pull request #234 from FalkorDB/dependabot/pip/staging/pytest-p…
gkorland Sep 11, 2025
85142f5
Merge branch 'staging' into dependabot/pip/staging/pytest-8.4.2
gkorland Sep 11, 2025
960d15d
Merge pull request #233 from FalkorDB/dependabot/pip/staging/pytest-8…
gkorland Sep 11, 2025
0b09b47
Merge branch 'staging' into dependabot/pip/staging/litellm-1.76.3
gkorland Sep 11, 2025
f883112
Merge pull request #232 from FalkorDB/dependabot/pip/staging/litellm-…
gkorland Sep 11, 2025
9dea5e1
Bump @typescript-eslint/eslint-plugin from 8.40.0 to 8.42.0 in /app
dependabot[bot] Sep 11, 2025
fb07508
Bump playwright from 1.54.0 to 1.55.0
dependabot[bot] Sep 11, 2025
d88b4d0
Merge branch 'staging' into dependabot/pip/staging/playwright-1.55.0
gkorland Sep 11, 2025
46faf48
Merge pull request #231 from FalkorDB/dependabot/pip/staging/playwrig…
gkorland Sep 11, 2025
6b0bb3b
Merge branch 'staging' into dependabot/npm_and_yarn/app/staging/types…
gkorland Sep 11, 2025
684d8c2
Merge pull request #235 from FalkorDB/dependabot/npm_and_yarn/app/sta…
gkorland Sep 11, 2025
945f187
add mcp registry
gkorland Sep 11, 2025
9a5bf2f
Merge pull request #247 from FalkorDB/mcp-reg
gkorland Sep 11, 2025
8edc9a6
add url
gkorland Sep 11, 2025
b421f11
Merge branch 'staging' into mcp-reg
gkorland Sep 11, 2025
3cd62e6
Merge pull request #248 from FalkorDB/mcp-reg
gkorland Sep 11, 2025
54d84c1
update server
gkorland Sep 11, 2025
fdaabcb
fix graphs_list
gkorland Sep 11, 2025
18f74ee
Fix: Convert null to boolean in dropdown isDemo parameter
guglielmo Sep 15, 2025
5d9e35e
Bump pytest-asyncio from 1.1.0 to 1.2.0
dependabot[bot] Sep 15, 2025
699d0da
Bump litellm from 1.76.3 to 1.77.1
dependabot[bot] Sep 15, 2025
14d4d99
Merge pull request #251 from FalkorDB/dependabot/pip/staging/litellm-…
gkorland Sep 16, 2025
5e2e7cb
Merge branch 'staging' into dependabot/pip/staging/pytest-asyncio-1.2.0
gkorland Sep 16, 2025
2bdde8f
Merge pull request #250 from FalkorDB/dependabot/pip/staging/pytest-a…
gkorland Sep 16, 2025
7f73509
Merge branch 'staging' into fix/dropdown-isDemo-null-error
gkorland Sep 16, 2025
7c387e4
Merge pull request #249 from guglielmo/fix/dropdown-isDemo-null-error
galshubeli Sep 16, 2025
21e1f75
Bump authlib from 1.6.3 to 1.6.4
dependabot[bot] Sep 22, 2025
7ef3ba6
Add Contributor Covenant Code of Conduct
gkorland Sep 27, 2025
81d52b1
Merge pull request #255 from FalkorDB/dependabot/pip/staging/authlib-…
gkorland Sep 27, 2025
b57fd0c
Update wordlist.txt
gkorland Sep 27, 2025
7841d05
Delete wordlist.dic
gkorland Sep 27, 2025
67a5430
update deps
gkorland Sep 27, 2025
7f4782e
Merge pull request #264 from FalkorDB/update-deps
gkorland Sep 27, 2025
30e16bb
Bump uvicorn from 0.35.0 to 0.36.0
dependabot[bot] Sep 27, 2025
5b95777
fix docker build
gkorland Sep 27, 2025
1d6f768
Merge branch 'staging' into update-deps
gkorland Sep 27, 2025
79135a1
Merge pull request #265 from FalkorDB/update-deps
gkorland Sep 27, 2025
be80120
Merge branch 'staging' into dependabot/pip/staging/uvicorn-0.36.0
gkorland Sep 27, 2025
2a5779b
Merge pull request #261 from FalkorDB/dependabot/pip/staging/uvicorn-…
gkorland Sep 27, 2025
4ae9ff0
Merge branch 'staging' into gkorland-patch-2
gkorland Sep 27, 2025
4b1dc37
Merge pull request #263 from FalkorDB/gkorland-patch-2
gkorland Sep 27, 2025
99b5354
update all files to python12
gkorland Oct 2, 2025
0d2f346
Merge pull request #270 from FalkorDB/python12
gkorland Oct 2, 2025
d8f6011
Bump fastapi from 0.117.1 to 0.118.0
dependabot[bot] Oct 2, 2025
63608c5
Merge pull request #267 from FalkorDB/dependabot/pip/staging/fastapi-…
gkorland Oct 2, 2025
0a2b47e
move to fastmcp
gkorland Oct 8, 2025
caf4ad7
move the route and mcp init to start
gkorland Oct 8, 2025
f7b8cf3
set tags
gkorland Oct 8, 2025
8a47af9
fix lint
gkorland Oct 8, 2025
44c942e
all should be tools
gkorland Oct 8, 2025
db3b9db
Apply suggestion from @Copilot
gkorland Oct 8, 2025
8c6bd17
make sure tags are set
gkorland Oct 8, 2025
4c1d717
fix lint
gkorland Oct 8, 2025
71bfe07
Merge pull request #280 from FalkorDB/fastmcp
gkorland Oct 8, 2025
0b41046
Update api/core/text2sql.py
gkorland Oct 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name = "pypi"
[packages]
fastapi = "~=0.116.1"
uvicorn = "~=0.35.0"
litellm = "~=1.76.0"
litellm = "~=1.76.3"
falkordb = "~=1.2.0"
psycopg2-binary = "~=2.9.9"
pymysql = "~=1.1.0"
Expand All @@ -20,10 +20,10 @@ fastapi-mcp = "~=0.4.0"
graphiti-core = {ref = "staging", git = "git+https://github.com/FalkorDB/graphiti.git"}

[dev-packages]
pytest = "~=8.4.1"
pytest = "~=8.4.2"
pylint = "~=3.3.4"
playwright = "~=1.54.0"
pytest-playwright = "~=0.7.0"
playwright = "~=1.55.0"
pytest-playwright = "~=0.7.1"
pytest-asyncio = "~=1.1.0"

[requires]
Expand Down
50 changes: 25 additions & 25 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions api/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

"""
Core module for QueryWeaver.

This module provides the core functionality for QueryWeaver including
error handling, database schema loading, and text-to-SQL processing.
"""

from .errors import InternalError, GraphNotFoundError, InvalidArgumentError
from .schema_loader import load_database, list_databases
from .text2sql import MESSAGE_DELIMITER

__all__ = [
"InternalError",
"GraphNotFoundError",
"InvalidArgumentError",
"load_database",
"list_databases",
"MESSAGE_DELIMITER",
]
13 changes: 13 additions & 0 deletions api/core/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Custom exceptions for the text2sql API."""

# Interal Error Exception
class InternalError(Exception):
"""Custom exception for internal errors."""

# Graph not found Exception
class GraphNotFoundError(Exception):
"""Custom exception for graph not found errors."""

# Wrong argument Exception
class InvalidArgumentError(Exception):
"""Custom exception for invalid argument errors."""
161 changes: 161 additions & 0 deletions api/core/schema_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""Database connection routes for the text2sql API."""

import logging
import json
import time
from typing import AsyncGenerator

from pydantic import BaseModel

from api.core.errors import InvalidArgumentError
from api.loaders.base_loader import BaseLoader
from api.loaders.postgres_loader import PostgresLoader
from api.loaders.mysql_loader import MySQLLoader

# Use the same delimiter as in the JavaScript frontend for streaming chunks
MESSAGE_DELIMITER = "|||FALKORDB_MESSAGE_BOUNDARY|||"


class DatabaseConnectionRequest(BaseModel):
"""Database connection request model.

Args:
BaseModel (_type_): _description_
"""

url: str

def _step_start(steps_counter: int) -> dict[str, str]:
"""Yield the starting step message."""
return {
"type": "reasoning_step",
"message": f"Step {steps_counter}: Starting database connection",
}

def _step_detect_db_type(steps_counter: int, url: str) -> tuple[type[BaseLoader], dict[str, str]]:
"""Yield the database type detection step message."""
db_type = None
loader: type[BaseLoader] = BaseLoader # type: ignore
if url.startswith("postgres://") or url.startswith("postgresql://"):
db_type = "postgresql"
loader = PostgresLoader
elif url.startswith("mysql://"):
db_type = "mysql"
loader = MySQLLoader
else:
raise InvalidArgumentError("Invalid database URL format")

return loader, {
"type": "reasoning_step",
"message": f"Step {steps_counter}: Detected database type: {db_type}. "
"Attempting to load schema...",
}


async def _step_attempt_load(
steps_counter: int, loader: type[BaseLoader], user_id: str, url: str
) -> AsyncGenerator[dict[str, str | bool], None]:
"""Yield the attempt to load schema step message."""
success, result = [False, ""]
try:
load_start = time.perf_counter()
async for progress in loader.load(user_id, url):
success, result = progress
if success:
steps_counter += 1
yield {
"type": "reasoning_step",
"message": f"Step {steps_counter}: {result}",
}
else:
break

load_elapsed = time.perf_counter() - load_start
logging.info("Database load attempt finished in %.2f seconds", load_elapsed)

if success:
yield {
"type": "final_result",
"success": True,
"message": "Database connected and schema loaded successfully",
}
else:
# Don't stream the full internal result; give higher-level error
logging.error("Database loader failed: %s", str(result)) # nosemgrep
yield {"type": "error", "message": "Failed to load database schema"}
except Exception as e: # pylint: disable=broad-exception-caught
logging.exception("Error while loading database schema: %s", str(e))
yield {"type": "error", "message": "Error connecting to database"}


def _step_result(result) -> str:
"""Yield the final result message."""
return json.dumps(result) + MESSAGE_DELIMITER


async def load_database(url: str, user_id: str):
"""
Accepts a JSON payload with a database URL and attempts to connect.
Supports both PostgreSQL and MySQL databases.
Streams progress steps as a sequence of JSON messages separated by MESSAGE_DELIMITER.
"""

# Validate URL format
if len(url.strip()) == 0:
raise InvalidArgumentError("Invalid URL format")

Comment thread
coderabbitai[bot] marked this conversation as resolved.
async def generate():
overall_start = time.perf_counter()
steps_counter = 0
try:
# Step 1: Start
steps_counter += 1
result = _step_start(steps_counter)
yield _step_result(result)

# Step 2: Determine type
steps_counter += 1
loader, result = _step_detect_db_type(steps_counter, url)
yield _step_result(result)

# Step 3: Attempt to load schema using the loader
async for progress in _step_attempt_load(
steps_counter, loader, user_id, url
):
yield _step_result(progress)

except InvalidArgumentError as ia:
yield _step_result({"type": "error", "message": str(ia)})
except Exception as e: # pylint: disable=broad-exception-caught
logging.exception("Unexpected error in connect_database stream: %s", str(e))
yield _step_result({"type": "error", "message": "Internal server error"})
finally:
overall_elapsed = time.perf_counter() - overall_start
logging.info(
"connect_database processing completed - Total time: %.2f seconds",
overall_elapsed,
)

return generate()


async def list_databases(user_id: str, general_prefix: str, db) -> list[str]:
"""
This route is used to list all the graphs (databases names) that are available in the database.
"""
user_graphs = await db.list_graphs()

# Only include graphs that start with user_id + '_', and strip the prefix
filtered_graphs = [
graph[len(f"{user_id}_") :]
for graph in user_graphs
if graph.startswith(f"{user_id}_")
]

if general_prefix:
demo_graphs = [
graph for graph in user_graphs if graph.startswith(general_prefix)
]
filtered_graphs = filtered_graphs + demo_graphs

return filtered_graphs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Signature/argument-order bug: list_databases call sites pass (user_id, db, GENERAL_PREFIX).

Current signature is (user_id, general_prefix, db) and will misassign args, causing AttributeError at runtime. Also dedupe overlapping prefixes.

Apply:

-async def list_databases(user_id: str, general_prefix: str, db) -> list[str]:
+from typing import Optional
+
+async def list_databases(user_id: str, db, general_prefix: Optional[str] = None) -> list[str]:
@@
-    user_graphs = await db.list_graphs()
+    user_graphs = await db.list_graphs()
@@
-    if general_prefix:
-        demo_graphs = [
-            graph for graph in user_graphs if graph.startswith(general_prefix)
-        ]
-        filtered_graphs = filtered_graphs + demo_graphs
+    if general_prefix:
+        demo_graphs = [g for g in user_graphs if g.startswith(general_prefix)]
+        # Preserve order while removing duplicates
+        filtered_graphs = list(dict.fromkeys(filtered_graphs + demo_graphs))
 
-    return filtered_graphs
+    return filtered_graphs

🏁 Script executed:

#!/bin/bash
# Confirm all call-sites use the (user_id, db, general_prefix) order.
rg -nP '\blist_databases\s*\(' -C2

Length of output: 686


Signature/argument-order bug — change list_databases to (user_id, db, general_prefix) and dedupe overlaps

Call-site api/routes/graphs.py:42 calls list_databases(request.state.user_id, db, GENERAL_PREFIX); current signature (user_id, general_prefix, db) will assign a string to db and raise AttributeError at runtime. Function location: api/core/schema_loader.py (lines ~142–161).

-async def list_databases(user_id: str, general_prefix: str, db) -> list[str]:
+from typing import Optional
+
+async def list_databases(user_id: str, db, general_prefix: Optional[str] = None) -> list[str]:
@@
-    user_graphs = await db.list_graphs()
+    user_graphs = await db.list_graphs()
@@
-    if general_prefix:
-        demo_graphs = [
-            graph for graph in user_graphs if graph.startswith(general_prefix)
-        ]
-        filtered_graphs = filtered_graphs + demo_graphs
+    if general_prefix:
+        demo_graphs = [g for g in user_graphs if g.startswith(general_prefix)]
+        # Preserve order while removing duplicates
+        filtered_graphs = list(dict.fromkeys(filtered_graphs + demo_graphs))
 
-    return filtered_graphs
+    return filtered_graphs
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def list_databases(user_id: str, general_prefix: str, db) -> list[str]:
"""
This route is used to list all the graphs (databases names) that are available in the database.
"""
user_graphs = await db.list_graphs()
# Only include graphs that start with user_id + '_', and strip the prefix
filtered_graphs = [
graph[len(f"{user_id}_") :]
for graph in user_graphs
if graph.startswith(f"{user_id}_")
]
if general_prefix:
demo_graphs = [
graph for graph in user_graphs if graph.startswith(general_prefix)
]
filtered_graphs = filtered_graphs + demo_graphs
return filtered_graphs
from typing import Optional
async def list_databases(user_id: str, db, general_prefix: Optional[str] = None) -> list[str]:
"""
This route is used to list all the graphs (databases names) that are available in the database.
"""
user_graphs = await db.list_graphs()
# Only include graphs that start with user_id + '_', and strip the prefix
filtered_graphs = [
graph[len(f"{user_id}_") :]
for graph in user_graphs
if graph.startswith(f"{user_id}_")
]
if general_prefix:
demo_graphs = [g for g in user_graphs if g.startswith(general_prefix)]
# Preserve order while removing duplicates
filtered_graphs = list(dict.fromkeys(filtered_graphs + demo_graphs))
return filtered_graphs
🤖 Prompt for AI Agents
In api/core/schema_loader.py around lines 142–161, the function signature and
argument order are wrong: change list_databases to accept (user_id: str, db,
general_prefix: str) so the db parameter gets the DB object passed by callers;
update the type hint for db appropriately (e.g., the DB client type or Any).
Inside the function preserve the existing filtering logic but when combining
user-specific graphs and demo_graphs dedupe overlaps (e.g., use an ordered set
or filter demo_graphs to exclude names that already matched the user prefix)
before returning, and ensure the return type annotation remains list[str].

Loading