-
Notifications
You must be signed in to change notification settings - Fork 126
Fix: Implement robust database connection with Supabase fallback #227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Vishal-RAJ-DEV
wants to merge
1
commit into
AOSSIE-Org:main
Choose a base branch
from
Vishal-RAJ-DEV:fix/database-connection-and-error-handling
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Empty file.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,40 +1,116 @@ | ||
| from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession | ||
| from sqlalchemy.orm import sessionmaker, declarative_base | ||
| from sqlalchemy.exc import SQLAlchemyError | ||
| from sqlalchemy.pool import NullPool | ||
| from sqlalchemy import text | ||
| import asyncio | ||
| import os | ||
| from dotenv import load_dotenv | ||
|
|
||
| # Load environment variables from .env | ||
| load_dotenv() | ||
|
|
||
| # Fetch database credentials | ||
| USER = os.getenv("user") | ||
| PASSWORD = os.getenv("password") | ||
| HOST = os.getenv("host") | ||
| PORT = os.getenv("port") | ||
| DBNAME = os.getenv("dbname") | ||
| # Fetch database credentials and strip whitespace | ||
| USER = os.getenv("user", "").strip() | ||
| PASSWORD = os.getenv("password", "").strip() | ||
| HOST = os.getenv("host", "").strip() | ||
| PORT = os.getenv("port", "").strip() | ||
| DBNAME = os.getenv("dbname", "").strip() | ||
|
|
||
| # Corrected async SQLAlchemy connection string (removed `sslmode=require`) | ||
| # Construct async SQLAlchemy connection string with connection parameters | ||
| DATABASE_URL = f"postgresql+asyncpg://{USER}:{PASSWORD}@{HOST}:{PORT}/{DBNAME}" | ||
|
|
||
| # Initialize async SQLAlchemy components | ||
| try: | ||
| engine = create_async_engine( | ||
| DATABASE_URL, echo=True, connect_args={"ssl": "require"} | ||
| DATABASE_URL, | ||
| echo=False, # Reduce noise in logs | ||
| poolclass=NullPool, | ||
| future=True, | ||
| connect_args={ | ||
| "server_settings": { | ||
| "application_name": "InPact_Backend", | ||
| }, | ||
| "command_timeout": 30, | ||
| # Add SSL configuration for Supabase | ||
| "ssl": "require", | ||
| } | ||
| ) | ||
|
|
||
| AsyncSessionLocal = sessionmaker( | ||
| bind=engine, class_=AsyncSession, expire_on_commit=False | ||
| bind=engine, | ||
| class_=AsyncSession, | ||
| expire_on_commit=False | ||
| ) | ||
|
|
||
| Base = declarative_base() | ||
| print("β Database connected successfully!") | ||
| print("β Database engine created successfully!") | ||
| except SQLAlchemyError as e: | ||
| print(f"β Error connecting to the database: {e}") | ||
| print(f"β Error creating database engine: {e}") | ||
| engine = None | ||
| AsyncSessionLocal = None | ||
| Base = None | ||
|
|
||
|
|
||
| async def test_connection(): | ||
| """Test database connection with detailed diagnostics""" | ||
| try: | ||
| if engine is None: | ||
| return False, "Engine not initialized" | ||
|
|
||
| # Test DNS resolution first | ||
| import socket | ||
| try: | ||
| socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM) | ||
| except socket.gaierror as dns_error: | ||
| if "11001" in str(dns_error): | ||
| return False, f"IPv6 connectivity issue detected for {HOST}. Your network may not support IPv6, but Supabase REST API is available as fallback." | ||
| else: | ||
| return False, f"DNS resolution failed: {dns_error}" | ||
|
|
||
| # Try database connection with timeout | ||
| try: | ||
| async with asyncio.wait_for(engine.begin(), timeout=10) as conn: | ||
| result = await conn.execute(text("SELECT 1")) | ||
| await result.fetchone() | ||
| return True, "PostgreSQL connection successful" | ||
| except asyncio.TimeoutError: | ||
| return False, "Connection timeout - database may be unreachable" | ||
|
|
||
| except Exception as e: | ||
| error_msg = str(e) | ||
| if "11001" in error_msg: | ||
| return False, f"IPv6 connectivity issue: Your system cannot resolve the IPv6-only Supabase database host. Using REST API fallback." | ||
| return False, f"Connection failed: {e}" | ||
|
|
||
|
|
||
| async def test_connection_with_retry(max_retries: int = 2): | ||
| """Test connection with retry mechanism""" | ||
| for attempt in range(max_retries): | ||
| is_connected, message = await test_connection() | ||
| if is_connected: | ||
| return True, message | ||
|
|
||
| if attempt < max_retries - 1: | ||
| print(f"β οΈ Connection attempt {attempt + 1} failed: {message}") | ||
| print(f"π Retrying in 2 seconds...") | ||
| await asyncio.sleep(2) | ||
| else: | ||
| return False, message | ||
|
|
||
| return False, "All connection attempts failed" | ||
|
|
||
|
|
||
| async def get_db(): | ||
| if AsyncSessionLocal is None: | ||
| raise Exception("Database not configured properly") | ||
|
|
||
| async with AsyncSessionLocal() as session: | ||
| yield session | ||
| try: | ||
| yield session | ||
| await session.commit() | ||
| except Exception: | ||
| await session.rollback() | ||
| raise | ||
| finally: | ||
| await session.close() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,54 +4,58 @@ | |
|
|
||
|
|
||
| async def seed_db(): | ||
| users = [ | ||
| { | ||
| "id": "aabb1fd8-ba93-4e8c-976e-35e5c40b809c", | ||
| "username": "creator1", | ||
| "email": "[email protected]", | ||
| "password": "password123", | ||
| "role": "creator", | ||
| "bio": "Lifestyle and travel content creator", | ||
| "profile_image": None, | ||
| "created_at": datetime.utcnow() | ||
| }, | ||
| { | ||
| "id": "6dbfcdd5-795f-49c1-8f7a-a5538b8c6f6f", | ||
| "username": "brand1", | ||
| "email": "[email protected]", | ||
| "password": "password123", | ||
| "role": "brand", | ||
| "bio": "Sustainable fashion brand looking for influencers", | ||
| "profile_image": None, | ||
| "created_at": datetime.utcnow() | ||
| }, | ||
| ] | ||
| try: | ||
| users = [ | ||
| { | ||
| "id": "aabb1fd8-ba93-4e8c-976e-35e5c40b809c", | ||
| "username": "creator1", | ||
| "email": "[email protected]", | ||
| "password": "password123", | ||
| "role": "creator", | ||
| "bio": "Lifestyle and travel content creator", | ||
| "profile_image": None, | ||
| "created_at": datetime.utcnow() | ||
| }, | ||
| { | ||
| "id": "6dbfcdd5-795f-49c1-8f7a-a5538b8c6f6f", | ||
| "username": "brand1", | ||
| "email": "[email protected]", | ||
| "password": "password123", | ||
| "role": "brand", | ||
| "bio": "Sustainable fashion brand looking for influencers", | ||
| "profile_image": None, | ||
| "created_at": datetime.utcnow() | ||
| }, | ||
| ] | ||
|
|
||
| # Insert or update the users | ||
| async with AsyncSessionLocal() as session: | ||
| for user_data in users: | ||
| # Check if user exists | ||
| existing_user = await session.execute( | ||
| User.__table__.select().where(User.email == user_data["email"]) | ||
| ) | ||
| existing_user = existing_user.scalar_one_or_none() | ||
|
|
||
| if existing_user: | ||
| continue | ||
| else: | ||
| # Create new user | ||
| user = User( | ||
| id=user_data["id"], | ||
| username=user_data["username"], | ||
| email=user_data["email"], | ||
| role=user_data["role"], | ||
| profile_image=user_data["profile_image"], | ||
| bio=user_data["bio"], | ||
| created_at=user_data["created_at"] | ||
| # Insert or update the users | ||
| async with AsyncSessionLocal() as session: | ||
| for user_data in users: | ||
| # Check if user exists | ||
| existing_user = await session.execute( | ||
| User.__table__.select().where(User.email == user_data["email"]) | ||
| ) | ||
| session.add(user) | ||
| print(f"Created user: {user_data['email']}") | ||
| existing_user = existing_user.scalar_one_or_none() | ||
|
|
||
| if existing_user: | ||
| continue | ||
| else: | ||
| # Create new user | ||
| user = User( | ||
| id=user_data["id"], | ||
| username=user_data["username"], | ||
| email=user_data["email"], | ||
| role=user_data["role"], | ||
| profile_image=user_data["profile_image"], | ||
| bio=user_data["bio"], | ||
| created_at=user_data["created_at"] | ||
| ) | ||
| session.add(user) | ||
| print(f"Created user: {user_data['email']}") | ||
|
|
||
| # Commit the session | ||
| await session.commit() | ||
| print("β Users seeded successfully.") | ||
| # Commit the session | ||
| await session.commit() | ||
| print("β Users seeded successfully.") | ||
| except Exception as e: | ||
| print(f"β οΈ Database seeding failed: {e}") | ||
| print("Server will continue without seeded data.") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle potential errors from Supabase fallback operations.
If
supabase_service.create_tables()orseed_data()fail, the exceptions are not caught, potentially crashing the startup. Consider wrapping these in a try/except for consistency with the PostgreSQL path.supabase_connected = await supabase_service.connect() if supabase_connected: print("β Using Supabase REST API for database operations") - await supabase_service.create_tables() - await supabase_service.seed_data() + try: + await supabase_service.create_tables() + await supabase_service.seed_data() + except Exception as e: + print(f"β οΈ Supabase initialization error: {e}") else: print("π Server starting in limited mode without database...")π Committable suggestion
π€ Prompt for AI Agents