Spaces:
Running
Running
Commit ·
dbfb079
1
Parent(s): a918a86
fix: article validation failing for Pydantic models
Browse files- Update is_valid_article to check both camelCase (API) and snake_case (Model) fields
- Fix normalize_article_date to support published_at
- Ensure image_url is preserved during sanitization
- app/utils/data_validation.py +33 -12
- app/utils/date_parser.py +12 -6
app/utils/data_validation.py
CHANGED
|
@@ -65,15 +65,18 @@ def is_valid_article(article: Union[Dict, 'Article']) -> bool:
|
|
| 65 |
return False
|
| 66 |
|
| 67 |
# Required: Published date
|
| 68 |
-
if not article_dict.get('publishedAt'):
|
| 69 |
return False
|
| 70 |
|
| 71 |
# Optional but validate if present: Image URL
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
return True
|
| 79 |
|
|
@@ -112,10 +115,12 @@ def sanitize_article(article: Union[Dict, 'Article']) -> Dict:
|
|
| 112 |
description = re.sub(r'\s+', ' ', description)
|
| 113 |
description = description[:2000]
|
| 114 |
|
| 115 |
-
# Clean image URL
|
| 116 |
-
|
|
|
|
|
|
|
| 117 |
if image_url:
|
| 118 |
-
image_url = image_url[:
|
| 119 |
if not image_url.startswith(('http://', 'https://')):
|
| 120 |
image_url = None
|
| 121 |
|
|
@@ -130,16 +135,32 @@ def sanitize_article(article: Union[Dict, 'Article']) -> Dict:
|
|
| 130 |
quality_score = calculate_quality_score(article_dict)
|
| 131 |
|
| 132 |
# Handle publishedAt (convert datetime to ISO string if needed)
|
| 133 |
-
|
|
|
|
|
|
|
| 134 |
if isinstance(published_at, datetime):
|
| 135 |
published_at = published_at.isoformat()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
return {
|
| 138 |
'title': title,
|
| 139 |
'url': url,
|
| 140 |
'description': description or '',
|
| 141 |
-
'image': image_url,
|
| 142 |
-
'
|
|
|
|
|
|
|
| 143 |
'source': source,
|
| 144 |
'category': article_dict.get('category', '').strip()[:100],
|
| 145 |
'slug': slug,
|
|
|
|
| 65 |
return False
|
| 66 |
|
| 67 |
# Required: Published date
|
| 68 |
+
if not (article_dict.get('publishedAt') or article_dict.get('published_at')):
|
| 69 |
return False
|
| 70 |
|
| 71 |
# Optional but validate if present: Image URL
|
| 72 |
+
# Handle both 'image' (raw API) and 'image_url' (Pydantic/DB)
|
| 73 |
+
image_url = article_dict.get('image') or article_dict.get('image_url')
|
| 74 |
+
if image_url:
|
| 75 |
+
image_url = str(image_url).strip()
|
| 76 |
+
if not image_url.startswith(('http://', 'https://')):
|
| 77 |
+
# Invalid image URL - remove both keys to be safe
|
| 78 |
+
if 'image' in article_dict: article_dict['image'] = None
|
| 79 |
+
if 'image_url' in article_dict: article_dict['image_url'] = None
|
| 80 |
|
| 81 |
return True
|
| 82 |
|
|
|
|
| 115 |
description = re.sub(r'\s+', ' ', description)
|
| 116 |
description = description[:2000]
|
| 117 |
|
| 118 |
+
# Clean image URL - Support both keys
|
| 119 |
+
raw_image = article_dict.get('image') or article_dict.get('image_url')
|
| 120 |
+
image_url = str(raw_image).strip() if raw_image else None
|
| 121 |
+
|
| 122 |
if image_url:
|
| 123 |
+
image_url = image_url[:2048] # Increased to match DB schema (was 1000)
|
| 124 |
if not image_url.startswith(('http://', 'https://')):
|
| 125 |
image_url = None
|
| 126 |
|
|
|
|
| 135 |
quality_score = calculate_quality_score(article_dict)
|
| 136 |
|
| 137 |
# Handle publishedAt (convert datetime to ISO string if needed)
|
| 138 |
+
# Check both keys
|
| 139 |
+
published_at = article_dict.get('publishedAt') or article_dict.get('published_at')
|
| 140 |
+
|
| 141 |
if isinstance(published_at, datetime):
|
| 142 |
published_at = published_at.isoformat()
|
| 143 |
+
elif not published_at:
|
| 144 |
+
# Fallback to current time if missing
|
| 145 |
+
published_at = datetime.now().isoformat()
|
| 146 |
+
|
| 147 |
+
# Return standardized dict (using camelCase for legacy compatibility or standardized snake_case?)
|
| 148 |
+
# The AppwriteDatabase understands both, checking 'published_at' OR 'publishedAt'.
|
| 149 |
+
# But usually it's best to standardize on what the DB considers 'canonical'.
|
| 150 |
+
# However, this function `sanitize_article` returns a dict that replaces the original object.
|
| 151 |
+
# We should probably return both or standardize on snake_case?
|
| 152 |
+
# Existing code returned 'publishedAt', 'image'.
|
| 153 |
+
# Let's keep returning 'publishedAt' for backward compat with whatever else uses this,
|
| 154 |
+
# BUT explicitly set the values we found.
|
| 155 |
|
| 156 |
return {
|
| 157 |
'title': title,
|
| 158 |
'url': url,
|
| 159 |
'description': description or '',
|
| 160 |
+
'image': image_url, # Legacy key
|
| 161 |
+
'image_url': image_url, # Modern key
|
| 162 |
+
'publishedAt': published_at, # Legacy key
|
| 163 |
+
'published_at': published_at, # Modern key
|
| 164 |
'source': source,
|
| 165 |
'category': article_dict.get('category', '').strip()[:100],
|
| 166 |
'slug': slug,
|
app/utils/date_parser.py
CHANGED
|
@@ -74,19 +74,25 @@ def normalize_article_date(article):
|
|
| 74 |
article_dict = dict(article)
|
| 75 |
|
| 76 |
# Normalize the date
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
| 79 |
# Handle datetime objects
|
| 80 |
if isinstance(published_at, datetime):
|
| 81 |
-
|
| 82 |
elif isinstance(published_at, str):
|
| 83 |
-
|
| 84 |
else:
|
| 85 |
# Unknown type, use current time
|
| 86 |
-
|
| 87 |
else:
|
| 88 |
# If missing, use current time
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
return article_dict
|
| 92 |
|
|
|
|
| 74 |
article_dict = dict(article)
|
| 75 |
|
| 76 |
# Normalize the date
|
| 77 |
+
# Check both keys
|
| 78 |
+
published_at = article_dict.get('publishedAt') or article_dict.get('published_at')
|
| 79 |
+
|
| 80 |
+
if published_at:
|
| 81 |
# Handle datetime objects
|
| 82 |
if isinstance(published_at, datetime):
|
| 83 |
+
iso_date = published_at.astimezone(timezone.utc).isoformat().replace('+00:00', 'Z')
|
| 84 |
elif isinstance(published_at, str):
|
| 85 |
+
iso_date = parse_date_to_iso(published_at)
|
| 86 |
else:
|
| 87 |
# Unknown type, use current time
|
| 88 |
+
iso_date = datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
|
| 89 |
else:
|
| 90 |
# If missing, use current time
|
| 91 |
+
iso_date = datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
|
| 92 |
+
|
| 93 |
+
# Set both keys to ensure compatibility
|
| 94 |
+
article_dict['publishedAt'] = iso_date
|
| 95 |
+
article_dict['published_at'] = iso_date
|
| 96 |
|
| 97 |
return article_dict
|
| 98 |
|