Fix async generator exception handling and add comprehensive tests
This commit is contained in:
@@ -119,56 +119,86 @@ For each task completed:
|
||||
|
||||
## TODO List:
|
||||
|
||||
✅ **Task Completed Successfully**
|
||||
fix:
|
||||
|
||||
### Issue Fixed: TypeError: 'async for' requires an object with **aiter** method
|
||||
INFO: 127.0.0.1:34916 - "POST /api/anime/search HTTP/1.1" 200
|
||||
INFO: 127.0.0.1:34916 - "POST /api/anime/add HTTP/1.1" 500
|
||||
ERROR: Exception in ASGI application
|
||||
Traceback (most recent call last):
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/anyio/streams/memory.py", line 98, in receive
|
||||
return self.receive_nowait()
|
||||
~~~~~~~~~~~~~~~~~~~^^
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/anyio/streams/memory.py", line 93, in receive_nowait
|
||||
raise WouldBlock
|
||||
anyio.WouldBlock
|
||||
|
||||
**Problem:**
|
||||
The BackgroundLoaderService was crashing when trying to load series data with the error:
|
||||
During handling of the above exception, another exception occurred:
|
||||
|
||||
```
|
||||
TypeError: 'async for' requires an object with __aiter__ method, got _AsyncGeneratorContextManager
|
||||
```
|
||||
Traceback (most recent call last):
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/base.py", line 78, in call_next
|
||||
message = await recv_stream.receive()
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/anyio/streams/memory.py", line 118, in receive
|
||||
raise EndOfStream
|
||||
anyio.EndOfStream
|
||||
|
||||
This error occurred in `_load_series_data` method at line 282 of [background_loader_service.py](src/server/services/background_loader_service.py).
|
||||
During handling of the above exception, another exception occurred:
|
||||
|
||||
**Root Cause:**
|
||||
The code was incorrectly using `async for db in get_db_session():` to get a database session. However, `get_db_session()` is decorated with `@asynccontextmanager`, which returns an async context manager (not an async iterator). Async context managers must be used with `async with`, not `async for`.
|
||||
|
||||
**Solution:**
|
||||
Changed the database session acquisition from:
|
||||
|
||||
```python
|
||||
async for db in get_db_session():
|
||||
# ... code ...
|
||||
break # Exit loop after first iteration
|
||||
```
|
||||
|
||||
To the correct pattern:
|
||||
|
||||
```python
|
||||
async with get_db_session() as db:
|
||||
# ... code ...
|
||||
```
|
||||
|
||||
**Files Modified:**
|
||||
|
||||
1. [src/server/services/background_loader_service.py](src/server/services/background_loader_service.py) - Fixed async context manager usage
|
||||
2. [tests/unit/test_background_loader_session.py](tests/unit/test_background_loader_session.py) - Created comprehensive unit tests
|
||||
|
||||
**Tests:**
|
||||
|
||||
- ✅ 5 new unit tests for background loader database session handling (all passing)
|
||||
- ✅ Tests verify proper async context manager usage
|
||||
- ✅ Tests verify error handling and progress tracking
|
||||
- ✅ Tests verify episode and NFO loading logic
|
||||
- ✅ Includes test demonstrating the difference between `async with` and `async for`
|
||||
|
||||
**Verification:**
|
||||
The fix allows the background loader service to properly load series data including episodes, NFO files, logos, and images without crashing.
|
||||
|
||||
---
|
||||
|
||||
## Next Tasks:
|
||||
|
||||
No pending tasks at this time.
|
||||
Traceback (most recent call last):
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py", line 426, in run_asgi
|
||||
result = await app( # type: ignore[func-returns-value]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
self.scope, self.receive, self.send
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
)
|
||||
^
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in **call**
|
||||
return await self.app(scope, receive, send)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/fastapi/applications.py", line 1106, in **call**
|
||||
await super().**call**(scope, receive, send)
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/applications.py", line 122, in **call**
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/errors.py", line 184, in **call**
|
||||
raise exc
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/errors.py", line 162, in **call**
|
||||
await self.app(scope, receive, \_send)
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/base.py", line 108, in **call**
|
||||
response = await self.dispatch_func(request, call_next)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/lukas/Volume/repo/Aniworld/src/server/middleware/auth.py", line 209, in dispatch
|
||||
return await call_next(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/base.py", line 84, in call_next
|
||||
raise app_exc
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/base.py", line 70, in coro
|
||||
await self.app(scope, receive_or_disconnect, send_no_error)
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/base.py", line 108, in **call**
|
||||
response = await self.dispatch_func(request, call_next)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/lukas/Volume/repo/Aniworld/src/server/middleware/setup_redirect.py", line 120, in dispatch
|
||||
return await call_next(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/base.py", line 84, in call_next
|
||||
raise app_exc
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/base.py", line 70, in coro
|
||||
await self.app(scope, receive_or_disconnect, send_no_error)
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/cors.py", line 91, in **call**
|
||||
await self.simple_response(scope, receive, send, request_headers=headers)
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/cors.py", line 146, in simple_response
|
||||
await self.app(scope, receive, send)
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/exceptions.py", line 79, in **call**
|
||||
raise exc
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/starlette/middleware/exceptions.py", line 68, in **call**
|
||||
await self.app(scope, receive, sender)
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/site-packages/fastapi/middleware/asyncexitstack.py", line 14, in **call**
|
||||
async with AsyncExitStack() as stack:
|
||||
~~~~~~~~~~~~~~^^
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/contextlib.py", line 768, in **aexit**
|
||||
raise exc
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/contextlib.py", line 751, in **aexit**
|
||||
cb_suppress = await cb(\*exc_details)
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/lukas/miniconda3/envs/AniWorld/lib/python3.13/contextlib.py", line 271, in **aexit**
|
||||
raise RuntimeError("generator didn't stop after athrow()")
|
||||
RuntimeError: generator didn't stop after athrow()
|
||||
|
||||
Reference in New Issue
Block a user