fix: wrap AsyncSession in context manager across all API blueprints
Replace all bare `session = AsyncSession(bind=conn)` assignments with `async with AsyncSession(bind=conn) as session:` in downloads.py, credentials.py, settings.py, sources.py, and subscriptions.py to ensure sessions are properly closed on all code paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+104
-109
@@ -81,24 +81,23 @@ async def get_setting_value(session: AsyncSession, key: str, default=None):
|
||||
async def get_all_settings():
|
||||
"""Get all application settings."""
|
||||
async with current_app.db_engine.connect() as conn:
|
||||
session = AsyncSession(bind=conn)
|
||||
async with AsyncSession(bind=conn) as session:
|
||||
result = await session.execute(select(Setting))
|
||||
settings = result.scalars().all()
|
||||
|
||||
result = await session.execute(select(Setting))
|
||||
settings = result.scalars().all()
|
||||
# Build settings dict with defaults
|
||||
settings_dict = dict(DEFAULT_SETTINGS)
|
||||
for s in settings:
|
||||
settings_dict[s.key] = s.value
|
||||
|
||||
# Build settings dict with defaults
|
||||
settings_dict = dict(DEFAULT_SETTINGS)
|
||||
for s in settings:
|
||||
settings_dict[s.key] = s.value
|
||||
# Ensure extension API key exists
|
||||
api_key = await get_or_create_extension_api_key(session)
|
||||
settings_dict[EXTENSION_API_KEY_SETTING] = api_key
|
||||
|
||||
# Ensure extension API key exists
|
||||
api_key = await get_or_create_extension_api_key(session)
|
||||
settings_dict[EXTENSION_API_KEY_SETTING] = api_key
|
||||
# Get cached storage statistics (calculated by background task)
|
||||
storage_stats = await get_cached_storage_stats(session)
|
||||
|
||||
# Get cached storage statistics (calculated by background task)
|
||||
storage_stats = await get_cached_storage_stats(session)
|
||||
|
||||
return jsonify({"settings": settings_dict, "storage_stats": storage_stats})
|
||||
return jsonify({"settings": settings_dict, "storage_stats": storage_stats})
|
||||
|
||||
|
||||
@bp.route("", methods=["PATCH"])
|
||||
@@ -110,68 +109,66 @@ async def update_settings():
|
||||
return jsonify({"error": "No settings provided"}), 400
|
||||
|
||||
async with current_app.db_engine.connect() as conn:
|
||||
session = AsyncSession(bind=conn)
|
||||
async with AsyncSession(bind=conn) as session:
|
||||
for key, value in data.items():
|
||||
# Check if setting exists
|
||||
result = await session.execute(select(Setting).where(Setting.key == key))
|
||||
setting = result.scalar()
|
||||
|
||||
for key, value in data.items():
|
||||
# Check if setting exists
|
||||
result = await session.execute(select(Setting).where(Setting.key == key))
|
||||
setting = result.scalar()
|
||||
if setting:
|
||||
current_app.logger.info(f"Updating setting '{key}': {setting.value} -> {value} (type: {type(value).__name__})")
|
||||
setting.value = value
|
||||
else:
|
||||
current_app.logger.info(f"Creating setting '{key}': {value} (type: {type(value).__name__})")
|
||||
setting = Setting(key=key, value=value)
|
||||
session.add(setting)
|
||||
|
||||
if setting:
|
||||
current_app.logger.info(f"Updating setting '{key}': {setting.value} -> {value} (type: {type(value).__name__})")
|
||||
setting.value = value
|
||||
else:
|
||||
current_app.logger.info(f"Creating setting '{key}': {value} (type: {type(value).__name__})")
|
||||
setting = Setting(key=key, value=value)
|
||||
session.add(setting)
|
||||
await session.commit()
|
||||
|
||||
await session.commit()
|
||||
# Return updated settings
|
||||
result = await session.execute(select(Setting))
|
||||
settings = result.scalars().all()
|
||||
settings_dict = dict(DEFAULT_SETTINGS)
|
||||
for s in settings:
|
||||
settings_dict[s.key] = s.value
|
||||
|
||||
# Return updated settings
|
||||
result = await session.execute(select(Setting))
|
||||
settings = result.scalars().all()
|
||||
settings_dict = dict(DEFAULT_SETTINGS)
|
||||
for s in settings:
|
||||
settings_dict[s.key] = s.value
|
||||
|
||||
current_app.logger.info(f"Updated settings: {list(data.keys())}")
|
||||
return jsonify({"settings": settings_dict})
|
||||
current_app.logger.info(f"Updated settings: {list(data.keys())}")
|
||||
return jsonify({"settings": settings_dict})
|
||||
|
||||
|
||||
@bp.route("/api-key", methods=["GET"])
|
||||
async def get_extension_api_key():
|
||||
"""Get the extension API key."""
|
||||
async with current_app.db_engine.connect() as conn:
|
||||
session = AsyncSession(bind=conn)
|
||||
api_key = await get_or_create_extension_api_key(session)
|
||||
return jsonify({"api_key": api_key})
|
||||
async with AsyncSession(bind=conn) as session:
|
||||
api_key = await get_or_create_extension_api_key(session)
|
||||
return jsonify({"api_key": api_key})
|
||||
|
||||
|
||||
@bp.route("/api-key/regenerate", methods=["POST"])
|
||||
async def regenerate_extension_api_key():
|
||||
"""Regenerate the extension API key."""
|
||||
async with current_app.db_engine.connect() as conn:
|
||||
session = AsyncSession(bind=conn)
|
||||
async with AsyncSession(bind=conn) as session:
|
||||
# Find existing key
|
||||
result = await session.execute(
|
||||
select(Setting).where(Setting.key == EXTENSION_API_KEY_SETTING)
|
||||
)
|
||||
setting = result.scalar()
|
||||
|
||||
# Find existing key
|
||||
result = await session.execute(
|
||||
select(Setting).where(Setting.key == EXTENSION_API_KEY_SETTING)
|
||||
)
|
||||
setting = result.scalar()
|
||||
# Generate new key
|
||||
new_key = generate_api_key()
|
||||
|
||||
# Generate new key
|
||||
new_key = generate_api_key()
|
||||
if setting:
|
||||
setting.value = new_key
|
||||
else:
|
||||
setting = Setting(key=EXTENSION_API_KEY_SETTING, value=new_key)
|
||||
session.add(setting)
|
||||
|
||||
if setting:
|
||||
setting.value = new_key
|
||||
else:
|
||||
setting = Setting(key=EXTENSION_API_KEY_SETTING, value=new_key)
|
||||
session.add(setting)
|
||||
await session.commit()
|
||||
|
||||
await session.commit()
|
||||
|
||||
current_app.logger.info("Extension API key regenerated")
|
||||
return jsonify({"api_key": new_key, "message": "API key regenerated"})
|
||||
current_app.logger.info("Extension API key regenerated")
|
||||
return jsonify({"api_key": new_key, "message": "API key regenerated"})
|
||||
|
||||
|
||||
@bp.route("/gallery-dl", methods=["GET"])
|
||||
@@ -246,28 +243,27 @@ async def get_logs():
|
||||
limit = min(int(request.args.get("limit", 50)), 200)
|
||||
|
||||
async with current_app.db_engine.connect() as conn:
|
||||
session = AsyncSession(bind=conn)
|
||||
async with AsyncSession(bind=conn) as session:
|
||||
# Get recent downloads with output logs
|
||||
query = select(Download).order_by(desc(Download.created_at)).limit(limit)
|
||||
result = await session.execute(query)
|
||||
downloads = result.scalars().all()
|
||||
|
||||
# Get recent downloads with output logs
|
||||
query = select(Download).order_by(desc(Download.created_at)).limit(limit)
|
||||
result = await session.execute(query)
|
||||
downloads = result.scalars().all()
|
||||
logs = []
|
||||
for dl in downloads:
|
||||
metadata = dl.metadata_ or {}
|
||||
if metadata.get("stdout") or metadata.get("stderr") or dl.error_message:
|
||||
logs.append({
|
||||
"id": dl.id,
|
||||
"url": dl.url,
|
||||
"status": dl.status,
|
||||
"created_at": dl.created_at.isoformat() + "Z" if dl.created_at else None,
|
||||
"stdout": metadata.get("stdout", ""),
|
||||
"stderr": metadata.get("stderr", ""),
|
||||
"error_message": dl.error_message,
|
||||
})
|
||||
|
||||
logs = []
|
||||
for dl in downloads:
|
||||
metadata = dl.metadata_ or {}
|
||||
if metadata.get("stdout") or metadata.get("stderr") or dl.error_message:
|
||||
logs.append({
|
||||
"id": dl.id,
|
||||
"url": dl.url,
|
||||
"status": dl.status,
|
||||
"created_at": dl.created_at.isoformat() + "Z" if dl.created_at else None,
|
||||
"stdout": metadata.get("stdout", ""),
|
||||
"stderr": metadata.get("stderr", ""),
|
||||
"error_message": dl.error_message,
|
||||
})
|
||||
|
||||
return jsonify({"logs": logs, "count": len(logs)})
|
||||
return jsonify({"logs": logs, "count": len(logs)})
|
||||
|
||||
|
||||
@bp.route("/system-info", methods=["GET"])
|
||||
@@ -333,39 +329,38 @@ async def debug_schedule_interval():
|
||||
the worker would read.
|
||||
"""
|
||||
async with current_app.db_engine.connect() as conn:
|
||||
session = AsyncSession(bind=conn)
|
||||
async with AsyncSession(bind=conn) as session:
|
||||
# Query the raw setting from database
|
||||
result = await session.execute(
|
||||
select(Setting).where(Setting.key == "download.schedule_interval")
|
||||
)
|
||||
setting = result.scalar()
|
||||
|
||||
# Query the raw setting from database
|
||||
result = await session.execute(
|
||||
select(Setting).where(Setting.key == "download.schedule_interval")
|
||||
)
|
||||
setting = result.scalar()
|
||||
if setting:
|
||||
raw_value = setting.value
|
||||
db_info = {
|
||||
"found_in_database": True,
|
||||
"raw_value": raw_value,
|
||||
"raw_type": type(raw_value).__name__,
|
||||
"as_int": int(raw_value) if raw_value is not None else None,
|
||||
"updated_at": setting.updated_at.isoformat() if setting.updated_at else None,
|
||||
}
|
||||
else:
|
||||
db_info = {
|
||||
"found_in_database": False,
|
||||
"raw_value": None,
|
||||
"raw_type": None,
|
||||
"as_int": None,
|
||||
"updated_at": None,
|
||||
}
|
||||
|
||||
if setting:
|
||||
raw_value = setting.value
|
||||
db_info = {
|
||||
"found_in_database": True,
|
||||
"raw_value": raw_value,
|
||||
"raw_type": type(raw_value).__name__,
|
||||
"as_int": int(raw_value) if raw_value is not None else None,
|
||||
"updated_at": setting.updated_at.isoformat() if setting.updated_at else None,
|
||||
}
|
||||
else:
|
||||
db_info = {
|
||||
"found_in_database": False,
|
||||
"raw_value": None,
|
||||
"raw_type": None,
|
||||
"as_int": None,
|
||||
"updated_at": None,
|
||||
}
|
||||
# What the worker would use
|
||||
fallback_value = DEFAULT_SETTINGS.get("download.schedule_interval", 3600)
|
||||
worker_would_use = db_info["as_int"] if db_info["found_in_database"] else fallback_value
|
||||
|
||||
# What the worker would use
|
||||
fallback_value = DEFAULT_SETTINGS.get("download.schedule_interval", 3600)
|
||||
worker_would_use = db_info["as_int"] if db_info["found_in_database"] else fallback_value
|
||||
|
||||
return jsonify({
|
||||
"database": db_info,
|
||||
"default_settings_value": fallback_value,
|
||||
"worker_would_use": worker_would_use,
|
||||
"worker_would_use_hours": worker_would_use / 3600 if worker_would_use else None,
|
||||
})
|
||||
return jsonify({
|
||||
"database": db_info,
|
||||
"default_settings_value": fallback_value,
|
||||
"worker_would_use": worker_would_use,
|
||||
"worker_would_use_hours": worker_would_use / 3600 if worker_would_use else None,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user