Skip to content

Commit 9439c78

Browse files
authored
Merge pull request #380 from FalkorDB/copilot/add-support-for-snowflake
Add Snowflake loader support with full-stack integration
2 parents 1f728b4 + 368188f commit 9439c78

18 files changed

Lines changed: 1982 additions & 161 deletions

.github/wordlist.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
QueryWeaver
22
FalkorDB
33
OAuth
4+
DDL
5+
DML
46
AGPL
57
Affero
68
nullability

api/app_factory.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import os
66
import secrets
77

8-
from dotenv import load_dotenv
98
from fastapi import FastAPI, Request, HTTPException
109
from fastapi.responses import RedirectResponse, JSONResponse, FileResponse
1110
from fastapi.staticfiles import StaticFiles
@@ -24,8 +23,6 @@
2423
from api.routes.tokens import tokens_router
2524
from api.routes.settings import settings_router
2625

27-
# Load environment variables from .env file
28-
load_dotenv()
2926
logging.basicConfig(
3027
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
3128
)
@@ -147,7 +144,7 @@ def create_app(): # pylint: disable=too-many-statements
147144
app.include_router(graphs_router, prefix="/graphs")
148145
app.include_router(database_router)
149146
app.include_router(tokens_router, prefix="/tokens")
150-
app.include_router(settings_router, prefix="/api")
147+
app.include_router(settings_router, prefix="/settings")
151148

152149

153150

api/config.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@
77
import logging
88
import dataclasses
99
from typing import Union
10+
11+
from dotenv import load_dotenv
1012
from litellm import embedding
1113

14+
# Ensure .env is loaded before Config reads os.getenv() at class definition time
15+
load_dotenv()
16+
1217
# Configure litellm logging to prevent sensitive data leakage
1318
def configure_litellm_logging():
1419
"""Configure litellm to suppress completion logs."""
@@ -64,6 +69,9 @@ def _with_prefix(model: str, provider: str) -> str:
6469
return prefix + model.removeprefix(prefix)
6570

6671

72+
SUPPORTED_VENDORS = ("openai", "anthropic", "gemini", "azure", "ollama", "cohere")
73+
74+
6775
@dataclasses.dataclass
6876
class Config:
6977
"""
@@ -103,7 +111,7 @@ class Config:
103111
EMBEDDING_MODEL_NAME = "voyage/voyage-3"
104112
else:
105113
raise ValueError(
106-
"Anthropic has no native embeddings. "
114+
"ANTHROPIC_API_KEY is set, but Anthropic has no native embeddings. "
107115
"Set EMBEDDING_MODEL or VOYAGE_API_KEY for embeddings."
108116
)
109117
elif os.getenv("COHERE_API_KEY"):

api/core/schema_loader.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from api.loaders.base_loader import BaseLoader
1414
from api.loaders.postgres_loader import PostgresLoader
1515
from api.loaders.mysql_loader import MySQLLoader
16+
from api.loaders.snowflake_loader import SnowflakeLoader
1617

1718
# Use the same delimiter as in the JavaScript frontend for streaming chunks
1819
MESSAGE_DELIMITER = "|||FALKORDB_MESSAGE_BOUNDARY|||"
@@ -44,6 +45,9 @@ def _step_detect_db_type(steps_counter: int, url: str) -> tuple[type[BaseLoader]
4445
elif url.startswith("mysql://"):
4546
db_type = "mysql"
4647
loader = MySQLLoader
48+
elif url.startswith("snowflake://"):
49+
db_type = "snowflake"
50+
loader = SnowflakeLoader
4751
else:
4852
raise InvalidArgumentError("Invalid database URL format")
4953

api/core/text2sql.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
from api.agents import AnalysisAgent, RelevancyAgent, ResponseFormatterAgent, FollowUpAgent
1616
from api.agents.healer_agent import HealerAgent
1717
from api.config import Config
18+
from api.config import SUPPORTED_VENDORS
1819
from api.extensions import db
1920
from api.graph import find, get_db_description, get_user_rules
2021
from api.loaders.postgres_loader import PostgresLoader
2122
from api.loaders.mysql_loader import MySQLLoader
23+
from api.loaders.snowflake_loader import SnowflakeLoader
2224
from api.memory.graphiti_tool import MemoryTool
2325
from api.sql_utils import SQLIdentifierQuoter, DatabaseSpecificQuoter
2426

@@ -83,6 +85,8 @@ def get_database_type_and_loader(db_url: str):
8385
return 'postgresql', PostgresLoader
8486
if db_url_lower.startswith('mysql://'):
8587
return 'mysql', MySQLLoader
88+
if db_url_lower.startswith('snowflake://'):
89+
return 'snowflake', SnowflakeLoader
8690

8791
# Default to PostgresLoader for backward compatibility
8892
return 'postgresql', PostgresLoader
@@ -257,21 +261,17 @@ async def generate(): # pylint: disable=too-many-locals,too-many-branches,too-m
257261
custom_model = chat_data.custom_model
258262

259263
# Validate custom model format (vendor/model)
260-
supported_vendors = ("openai", "anthropic", "gemini", "azure", "ollama", "cohere")
261264
if custom_model:
262265
parts = custom_model.split("/", 1)
263266
if len(parts) != 2 or not parts[0] or not parts[1]:
264267
raise InvalidArgumentError(
265268
"Invalid model format. Expected 'vendor/model' (e.g. 'openai/gpt-4.1')"
266269
)
267-
if parts[0] not in supported_vendors:
270+
if parts[0] not in SUPPORTED_VENDORS:
268271
raise InvalidArgumentError(
269-
f"Unsupported vendor '{parts[0]}'. Supported: {', '.join(supported_vendors)}"
272+
f"Unsupported vendor '{parts[0]}'. Supported: {', '.join(SUPPORTED_VENDORS)}"
270273
)
271274

272-
if custom_api_key is not None and len(custom_api_key.strip()) < 10:
273-
raise InvalidArgumentError("API key is too short")
274-
275275
agent_rel = RelevancyAgent(queries_history, result_history, custom_api_key, custom_model)
276276
agent_an = AnalysisAgent(queries_history, result_history, custom_api_key, custom_model)
277277
follow_up_agent = FollowUpAgent(queries_history, result_history, custom_api_key, custom_model)

api/index.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
"""Main entry point for the text2sql API."""
22

3-
from api.app_factory import create_app
3+
# Load .env before any app imports that read os.getenv at module level
4+
from dotenv import load_dotenv
5+
load_dotenv()
6+
7+
from api.app_factory import create_app # pylint: disable=wrong-import-position
48

59
app = create_app()
610

0 commit comments

Comments
 (0)