adamshafishaik commited on
Commit
3573338
·
1 Parent(s): 461c01d

fixed routing issue of pulse umbrella api endpoints

Browse files
Files changed (2) hide show
  1. app/routes/news.py +25 -4
  2. app/services/upstash_cache.py +16 -3
app/routes/news.py CHANGED
@@ -51,8 +51,16 @@ async def get_umbrella_news(category: str, limit: int = 20):
51
  import asyncio
52
  per_cat_limit = max(5, math.ceil(limit / len(sub_categories)))
53
 
54
- # Fetch all subcategories in parallel using the existing get_news_by_category function
55
- tasks = [get_news_by_category(cat, limit=per_cat_limit, page=1) for cat in sub_categories]
 
 
 
 
 
 
 
 
56
  results = await asyncio.gather(*tasks, return_exceptions=True)
57
 
58
  seen = set()
@@ -73,11 +81,24 @@ async def get_umbrella_news(category: str, limit: int = 20):
73
  # Sort by published_at descending
74
  def get_pub_date(art):
75
  pub = art.get('published_at') or art.get('publishedAt')
76
- return pub or ""
77
 
78
  merged.sort(key=lambda x: get_pub_date(x), reverse=True)
79
  final_articles = merged[:limit]
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  response_data = NewsResponse(
82
  success=True,
83
  category=category,
@@ -90,7 +111,7 @@ async def get_umbrella_news(category: str, limit: int = 20):
90
  if upstash_cache.enabled:
91
  await upstash_cache.set(
92
  cache_key,
93
- {"articles": final_articles},
94
  ttl=300
95
  )
96
 
 
51
  import asyncio
52
  per_cat_limit = max(5, math.ceil(limit / len(sub_categories)))
53
 
54
+ # Semaphore caps simultaneous Appwrite+Upstash connections to 4.
55
+ # Without this, 10 parallel fetches spike Upstash burst limits causing
56
+ # "Upstash request failed" blank errors seen in production logs.
57
+ _sem = asyncio.Semaphore(4)
58
+
59
+ async def _fetch_with_sem(cat: str):
60
+ async with _sem:
61
+ return await get_news_by_category(cat, limit=per_cat_limit, page=1)
62
+
63
+ tasks = [_fetch_with_sem(cat) for cat in sub_categories]
64
  results = await asyncio.gather(*tasks, return_exceptions=True)
65
 
66
  seen = set()
 
81
  # Sort by published_at descending
82
  def get_pub_date(art):
83
  pub = art.get('published_at') or art.get('publishedAt')
84
+ return str(pub) if pub else ""
85
 
86
  merged.sort(key=lambda x: get_pub_date(x), reverse=True)
87
  final_articles = merged[:limit]
88
 
89
+ # Sanitize datetime objects to ISO strings before JSON caching.
90
+ # Appwrite returns Python datetime objects in article dicts which are
91
+ # not JSON serializable. The _DatetimeEncoder in upstash_cache now
92
+ # handles this at the json.dumps level — this is belt-and-suspenders.
93
+ def _sanitize_for_cache(art: dict) -> dict:
94
+ from datetime import datetime as dt
95
+ return {
96
+ k: v.isoformat() if isinstance(v, dt) else v
97
+ for k, v in art.items()
98
+ }
99
+
100
+ cache_safe_articles = [_sanitize_for_cache(a) for a in final_articles]
101
+
102
  response_data = NewsResponse(
103
  success=True,
104
  category=category,
 
111
  if upstash_cache.enabled:
112
  await upstash_cache.set(
113
  cache_key,
114
+ {"articles": cache_safe_articles},
115
  ttl=300
116
  )
117
 
app/services/upstash_cache.py CHANGED
@@ -16,11 +16,23 @@ import httpx
16
  import json
17
  import logging
18
  from typing import Any, Optional
19
- from datetime import datetime
20
 
21
  logger = logging.getLogger(__name__)
22
 
23
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  class UpstashCache:
25
  """
26
  REST-based Redis caching service for Upstash
@@ -181,8 +193,9 @@ class UpstashCache:
181
  return False
182
 
183
  try:
184
- # Serialize to JSON
185
- serialized = json.dumps(value)
 
186
 
187
  # Check size (warn if >1MB)
188
  size_kb = len(serialized) / 1024
 
16
  import json
17
  import logging
18
  from typing import Any, Optional
19
+ from datetime import datetime, date
20
 
21
  logger = logging.getLogger(__name__)
22
 
23
 
24
+ class _DatetimeEncoder(json.JSONEncoder):
25
+ """
26
+ JSON encoder that converts datetime/date objects to ISO-8601 strings.
27
+ Prevents 'Object of type datetime is not JSON serializable' when caching
28
+ article dicts that contain Appwrite-returned datetime fields.
29
+ """
30
+ def default(self, obj):
31
+ if isinstance(obj, (datetime, date)):
32
+ return obj.isoformat()
33
+ return super().default(obj)
34
+
35
+
36
  class UpstashCache:
37
  """
38
  REST-based Redis caching service for Upstash
 
193
  return False
194
 
195
  try:
196
+ # Serialize to JSON — use _DatetimeEncoder to handle datetime objects
197
+ # that Appwrite returns in article dicts (e.g. published_at, created_at)
198
+ serialized = json.dumps(value, cls=_DatetimeEncoder)
199
 
200
  # Check size (warn if >1MB)
201
  size_kb = len(serialized) / 1024