fix: Handle missing Civitai API response fields gracefully

Fix KeyError when 'hashes', 'name', or 'model' fields are missing from
Civitai API responses. Use .get() with defaults instead of direct dict
access in:

- LoraMetadata.from_civitai_info()
- CheckpointMetadata.from_civitai_info()
- EmbeddingMetadata.from_civitai_info()
- RecipeScanner._get_hash_from_civitai()
- DownloadManager._process_download()

Fixes #820
This commit is contained in:
Will Miao
2026-02-18 12:02:48 +08:00
parent 16c52877ad
commit b9516c6b62
3 changed files with 39 additions and 36 deletions

View File

@@ -496,7 +496,9 @@ class DownloadManager:
return {"success": False, "error": "No mirror URL found"}
# 3. Prepare download
file_name = file_info["name"]
file_name = file_info.get("name", "")
if not file_name:
return {"success": False, "error": "No filename found in file info"}
save_path = os.path.join(save_dir, file_name)
# 5. Prepare metadata based on model type

View File

@@ -1351,8 +1351,9 @@ class RecipeScanner:
# Get hash from the first file
for file_info in version_info.get('files', []):
if file_info.get('hashes', {}).get('SHA256'):
return file_info['hashes']['SHA256'], False # Return hash with False for isDeleted flag
sha256_hash = (file_info.get('hashes') or {}).get('SHA256')
if sha256_hash:
return sha256_hash, False # Return hash with False for isDeleted flag
logger.debug(f"No SHA256 hash found in version info for ID: {model_version_id}")
return None, False

View File

@@ -143,27 +143,27 @@ class LoraMetadata(BaseModelMetadata):
@classmethod
def from_civitai_info(cls, version_info: Dict, file_info: Dict, save_path: str) -> 'LoraMetadata':
"""Create LoraMetadata instance from Civitai version info"""
file_name = file_info['name']
file_name = file_info.get('name', '')
base_model = determine_base_model(version_info.get('baseModel', ''))
# Extract tags and description if available
tags = []
description = ""
if 'model' in version_info:
if 'tags' in version_info['model']:
tags = version_info['model']['tags']
if 'description' in version_info['model']:
description = version_info['model']['description']
model_data = version_info.get('model') or {}
if 'tags' in model_data:
tags = model_data['tags']
if 'description' in model_data:
description = model_data['description']
return cls(
file_name=os.path.splitext(file_name)[0],
model_name=version_info.get('model').get('name', os.path.splitext(file_name)[0]),
model_name=model_data.get('name', os.path.splitext(file_name)[0]),
file_path=save_path.replace(os.sep, '/'),
size=file_info.get('sizeKB', 0) * 1024,
modified=datetime.now().timestamp(),
sha256=file_info['hashes'].get('SHA256', '').lower(),
sha256=(file_info.get('hashes') or {}).get('SHA256', '').lower(),
base_model=base_model,
preview_url=None, # Will be updated after preview download
preview_url='', # Will be updated after preview download
preview_nsfw_level=0, # Will be updated after preview download
from_civitai=True,
civitai=version_info,
@@ -179,28 +179,28 @@ class CheckpointMetadata(BaseModelMetadata):
@classmethod
def from_civitai_info(cls, version_info: Dict, file_info: Dict, save_path: str) -> 'CheckpointMetadata':
"""Create CheckpointMetadata instance from Civitai version info"""
file_name = file_info['name']
file_name = file_info.get('name', '')
base_model = determine_base_model(version_info.get('baseModel', ''))
sub_type = version_info.get('type', 'checkpoint')
# Extract tags and description if available
tags = []
description = ""
if 'model' in version_info:
if 'tags' in version_info['model']:
tags = version_info['model']['tags']
if 'description' in version_info['model']:
description = version_info['model']['description']
model_data = version_info.get('model') or {}
if 'tags' in model_data:
tags = model_data['tags']
if 'description' in model_data:
description = model_data['description']
return cls(
file_name=os.path.splitext(file_name)[0],
model_name=version_info.get('model').get('name', os.path.splitext(file_name)[0]),
model_name=model_data.get('name', os.path.splitext(file_name)[0]),
file_path=save_path.replace(os.sep, '/'),
size=file_info.get('sizeKB', 0) * 1024,
modified=datetime.now().timestamp(),
sha256=file_info['hashes'].get('SHA256', '').lower(),
sha256=(file_info.get('hashes') or {}).get('SHA256', '').lower(),
base_model=base_model,
preview_url=None, # Will be updated after preview download
preview_url='', # Will be updated after preview download
preview_nsfw_level=0,
from_civitai=True,
civitai=version_info,
@@ -217,28 +217,28 @@ class EmbeddingMetadata(BaseModelMetadata):
@classmethod
def from_civitai_info(cls, version_info: Dict, file_info: Dict, save_path: str) -> 'EmbeddingMetadata':
"""Create EmbeddingMetadata instance from Civitai version info"""
file_name = file_info['name']
file_name = file_info.get('name', '')
base_model = determine_base_model(version_info.get('baseModel', ''))
sub_type = version_info.get('type', 'embedding')
# Extract tags and description if available
tags = []
description = ""
if 'model' in version_info:
if 'tags' in version_info['model']:
tags = version_info['model']['tags']
if 'description' in version_info['model']:
description = version_info['model']['description']
model_data = version_info.get('model') or {}
if 'tags' in model_data:
tags = model_data['tags']
if 'description' in model_data:
description = model_data['description']
return cls(
file_name=os.path.splitext(file_name)[0],
model_name=version_info.get('model').get('name', os.path.splitext(file_name)[0]),
model_name=model_data.get('name', os.path.splitext(file_name)[0]),
file_path=save_path.replace(os.sep, '/'),
size=file_info.get('sizeKB', 0) * 1024,
modified=datetime.now().timestamp(),
sha256=file_info['hashes'].get('SHA256', '').lower(),
sha256=(file_info.get('hashes') or {}).get('SHA256', '').lower(),
base_model=base_model,
preview_url=None, # Will be updated after preview download
preview_url='', # Will be updated after preview download
preview_nsfw_level=0,
from_civitai=True,
civitai=version_info,