Fix root cause: filter non-picklable objects from entry before persisting with shelve
Co-authored-by: alexta69 <7450369+alexta69@users.noreply.github.com>
This commit is contained in:
parent
71c1b2f9ce
commit
2941b4dc68
2 changed files with 37 additions and 9 deletions
12
app/main.py
12
app/main.py
|
|
@ -123,14 +123,10 @@ class ObjectSerializer(json.JSONEncoder):
|
||||||
elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes)):
|
elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes)):
|
||||||
try:
|
try:
|
||||||
return list(obj)
|
return list(obj)
|
||||||
except (TypeError, ValueError, RuntimeError):
|
except:
|
||||||
# If conversion to list fails, log a warning and return a placeholder
|
pass
|
||||||
log.warning(f"Failed to convert {type(obj).__name__} to list, using string representation")
|
# Fall back to default behavior
|
||||||
return str(obj)
|
return json.JSONEncoder.default(self, obj)
|
||||||
# For objects that can't be serialized, return their string representation
|
|
||||||
# This prevents HTTP 500 errors while still providing useful information
|
|
||||||
log.warning(f"Object of type {type(obj).__name__} is not directly JSON serializable, using string representation")
|
|
||||||
return str(obj)
|
|
||||||
|
|
||||||
serializer = ObjectSerializer()
|
serializer = ObjectSerializer()
|
||||||
app = web.Application()
|
app = web.Application()
|
||||||
|
|
|
||||||
34
app/ytdl.py
34
app/ytdl.py
|
|
@ -44,9 +44,41 @@ class DownloadInfo:
|
||||||
self.size = None
|
self.size = None
|
||||||
self.timestamp = time.time_ns()
|
self.timestamp = time.time_ns()
|
||||||
self.error = error
|
self.error = error
|
||||||
self.entry = entry
|
# Extract only picklable playlist metadata from entry
|
||||||
|
# This prevents issues when shelve tries to pickle DownloadInfo objects
|
||||||
|
# that contain non-picklable objects like generators
|
||||||
|
self.entry = self._extract_picklable_entry(entry) if entry else None
|
||||||
self.playlist_item_limit = playlist_item_limit
|
self.playlist_item_limit = playlist_item_limit
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_picklable_entry(entry):
|
||||||
|
"""Extract only picklable data from entry dict.
|
||||||
|
|
||||||
|
This is needed because yt-dlp may return entry dicts containing
|
||||||
|
non-picklable objects like generators, which cause errors when
|
||||||
|
shelve tries to persist DownloadInfo objects.
|
||||||
|
"""
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Extract only basic types that are picklable
|
||||||
|
picklable_entry = {}
|
||||||
|
for key, value in entry.items():
|
||||||
|
# Only include playlist-related properties (used for output template)
|
||||||
|
# and other simple string/int/bool values
|
||||||
|
if key.startswith('playlist') or key in ('id', 'title', 'url', 'webpage_url', '_type'):
|
||||||
|
if isinstance(value, (str, int, float, bool, type(None))):
|
||||||
|
picklable_entry[key] = value
|
||||||
|
elif isinstance(value, (list, tuple)):
|
||||||
|
# For lists/tuples, only include if all items are basic types
|
||||||
|
try:
|
||||||
|
if all(isinstance(item, (str, int, float, bool, type(None))) for item in value):
|
||||||
|
picklable_entry[key] = value
|
||||||
|
except (TypeError, AttributeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return picklable_entry if picklable_entry else None
|
||||||
|
|
||||||
class Download:
|
class Download:
|
||||||
manager = None
|
manager = None
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue