Search code examples
pythonpostgresqldockersqlalchemyfastapi

Dockerized FastAPI SQLAlchemy PostgreSQL 'NoneType' object has no attribute 'execute'


This is a sudden error, whose root cause I haven't been able to figure out. I successfully built 2 Docker images that are connected over the same network:

  1. FastAPI
  2. PostgreSQL

I am not able to use the SQLAlchemy dependency injection to connect to my DB. However, I was able to seed my DB successfully many times over. Only made one additional field addition to my SQLAlchemy model and then this failure occurred.

backend-web-1  | ERROR:services.utils:Health check failed in 0.00 seconds: 'NoneType' object has no attribute 'execute'
backend-web-1  | ERROR:services.utils:Traceback (most recent call last):
backend-web-1  |   File "/app/main.py", line 27, in read_root
backend-web-1  |     db.execute('SELECT 1')
backend-web-1  |     ^^^^^^^^^^
backend-web-1  | AttributeError: 'NoneType' object has no attribute 'execute'

Code failure above happens here

from fastapi import FastAPI, Request, Depends, HTTPException
from sqlalchemy.orm import Session
import psutil
import threading
import time
import traceback

# Import your application modules
from database.database_config import get_db
from database.models import *
from database.crud import *
from api.interview import complete_interview
from api.chat_handling import *
from api.user_registration import register_user
from api.stripe import *
from services.utils import *
from config.env_var import *

app = FastAPI()

@app.get("/")
def read_root(db: Session = Depends(get_db)):
    
    start_time = time.time()
    try:
        # Check database connectivity
        db.execute('SELECT 1')
        db_status = "Connected"

        # Memory and Thread Count
        memory_usage = psutil.virtual_memory().percent
        thread_count = threading.active_count()

        response_time = time.time() - start_time
        logger.info(f"Health check passed in {response_time:.2f} seconds, Memory Usage: {memory_usage}%, Thread Count: {thread_count}, DB Status: {db_status}")
        return {"message": "Hello, World!"}

    except Exception as e:
        response_time = time.time() - start_time
        logger.error(f"Health check failed in {response_time:.2f} seconds: {e}")
        logger.error(traceback.format_exc())
        raise HTTPException(status_code=500, detail=str(e))

For reference, here's how I define my dependency injection:

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

from services.utils import logger
import traceback
from config.env_var import *

DB_USER = os.getenv('DB_USER')
DB_PASSWORD = os.getenv('DB_PASSWORD')
DB_HOST = os.getenv('DB_HOST')
DB_NAME = os.getenv('DB_NAME')

Base = declarative_base()
db_url = f'postgresql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:5432/{DB_NAME}'

try:
    engine = create_engine(db_url)
except Exception as e:
    logger.info(f"Error creating database engine: {e}")
    logger.info(traceback.format_exc())
    raise

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db
    except Exception as e:
        logger.info(f"Database session error: {e}")
        logger.info(traceback.format_exc())
        raise
    finally:
        db.close()

I am able to successfully connect via my FastAPI Docker instance to my PostgreSQL instance via this script though:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import os

DB_USER = os.getenv('DB_USER')
DB_PASSWORD = os.getenv('DB_PASSWORD')
DB_HOST = os.getenv('DB_HOST')
DB_NAME = os.getenv('DB_NAME')

db_url = f'postgresql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:5432/{DB_NAME}'
engine = create_engine(db_url)
SessionLocal = sessionmaker(bind=engine)

def test_session_local():
    session = SessionLocal()
    print(f"Session: {session}")
    session.close()

def test_db_connection():
    try:
        db = SessionLocal()
        # Perform a simple query
        result = db.execute('SELECT 1')
        for row in result:
            print(row)
        db.close()
        print("Connection successful.")
    except Exception as e:
        print(f"Connection failed: {e}")

if __name__ == "__main__":
    test_session_local()
    test_db_connection()

I don't see where I made a critical failure.


Solution

  • After a suggestion @MatsLindh I realized the error came from another endpoint

    It's definition was:

    import stripe
    from datetime import datetime, timedelta
    from sqlalchemy.orm import Session
    from fastapi import Request, Depends, HTTPException
    from typing import Callable, Dict
    
    from config.env_var import STRIPE_WEBHOOK_SECRET
    from database.crud import get_user_by_email, update_user_info
    from database.enums import onboard_complete, two_chat, ten_chat, twenty_chat, monthly_subscription
    from services.utils import logger
    
    def stripe_api_client() -> stripe:
        return stripe
    
    def get_db() -> Session:
        pass
    
    async def extract_event(request: Request, stripe_api: stripe = Depends(stripe_api_client)):
        payload = await request.body()
        sig_header = request.headers.get('stripe-signature')
        try:
            return stripe_api.Webhook.construct_event(payload, sig_header, STRIPE_WEBHOOK_SECRET)
        except ValueError as e:
            logger.error(f"Invalid payload: {e}")
            raise HTTPException(status_code=400, detail="Invalid payload")
        except stripe.error.SignatureVerificationError as e:
            logger.error(f"Invalid signature: {e}")
            raise HTTPException(status_code=400, detail="Invalid signature")
    

    Issue lies with this section, which passes effectively a None Type object, which overwrote my get_db from my database_config.py:

    def get_db() -> Session:
        pass
    

    So I removed that and imported my get_db and problem resolved!