Repository navigation
Conversation
…d it vllm-project#217 renamed src/vllm_router/batch/ to src/vllm_router/services/batch_service/ in 95efecc, on 2025-03-04. local_processor.py moved with it and kept importing BatchInfo and BatchStatus from the old path, at module level and again inside retrieve_batch. The module it wants is now its own sibling. app.py calls initialize_batch_processor whenever --enable-batch-api is passed, and that function imports LocalBatchProcessor, so the router aborts at startup with ModuleNotFoundError: No module named 'vllm_router.batch' rather than serving the batch endpoints. vllm-project#784 fixed the same orphaned import one layer out, in services/batch_service/__init__.py, and stopped there. aiosqlite is imported at the top of the same file and is declared in neither pyproject.toml nor src/vllm_router/requirements.txt, so on a stock install it fails one line earlier than the import above. The e2e job installs from pyproject.toml and now collects the new test, so it is added there and in requirements.txt beside the other runtime dependencies. Two regression tests cover the path app.py takes: one constructs the processor through initialize_batch_processor, one creates and retrieves a batch. Both fail with the import change reverted, and correcting only the module-level import leaves the second one failing. Signed-off-by: chrikrah <48338417+chrikrah@users.noreply.github.com>
There was a problem hiding this comment.
Code Review
This pull request adds aiosqlite as a dependency, introduces unit tests for the batch service, and updates imports in the local batch processor. Feedback on the changes suggests using pytest's built-in tmp_path fixture instead of a hardcoded temporary directory to prevent potential test flakiness, and removing a redundant local import of BatchStatus in the local processor.
| TEST_BASE_PATH = "/tmp/test_vllm_batch" | ||
| pytest_plugins = ("pytest_asyncio",) | ||
|
|
||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def cleanup(): | ||
| if os.path.exists(TEST_BASE_PATH): | ||
| shutil.rmtree(TEST_BASE_PATH) | ||
| yield | ||
| if os.path.exists(TEST_BASE_PATH): | ||
| shutil.rmtree(TEST_BASE_PATH) | ||
|
|
||
|
|
||
| def test_initialize_local_batch_processor(): | ||
| """app.py calls this whenever --enable-batch-api is passed, so an import | ||
| error inside local_processor aborts router startup.""" | ||
| storage = FileStorage(os.path.join(TEST_BASE_PATH, "files")) | ||
| processor = initialize_batch_processor( | ||
| "local", os.path.join(TEST_BASE_PATH, "db"), storage | ||
| ) | ||
| assert processor.db_path.endswith("batch_queue.db") | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_create_and_retrieve_batch(): | ||
| storage = FileStorage(os.path.join(TEST_BASE_PATH, "files")) | ||
| processor = initialize_batch_processor( | ||
| "local", os.path.join(TEST_BASE_PATH, "db"), storage | ||
| ) | ||
| await processor.setup_db() | ||
|
|
||
| created = await processor.create_batch( | ||
| input_file_id="file-1", | ||
| endpoint="/v1/chat/completions", | ||
| completion_window="24h", | ||
| ) | ||
| retrieved = await processor.retrieve_batch(created.id) | ||
|
|
||
| assert retrieved.id == created.id | ||
| assert retrieved.status is BatchStatus.PENDING | ||
| assert retrieved.input_file_id == "file-1" |
There was a problem hiding this comment.
Using a hardcoded path like /tmp/test_vllm_batch and manually cleaning it up can lead to flaky tests, permission issues, or conflicts during concurrent test execution (e.g., in CI/CD environments). It is highly recommended to use pytest's built-in tmp_path fixture, which automatically creates a unique temporary directory for each test run and handles cleanup.
pytest_plugins = ("pytest_asyncio",)
def test_initialize_local_batch_processor(tmp_path):
"""app.py calls this whenever --enable-batch-api is passed, so an import
error inside local_processor aborts router startup."""
storage = FileStorage(os.path.join(tmp_path, "files"))
processor = initialize_batch_processor(
"local", os.path.join(tmp_path, "db"), storage
)
assert processor.db_path.endswith("batch_queue.db")
@pytest.mark.asyncio
async def test_create_and_retrieve_batch(tmp_path):
storage = FileStorage(os.path.join(tmp_path, "files"))
processor = initialize_batch_processor(
"local", os.path.join(tmp_path, "db"), storage
)
await processor.setup_db()
created = await processor.create_batch(
input_file_id="file-1",
endpoint="/v1/chat/completions",
completion_window="24h",
)
retrieved = await processor.retrieve_batch(created.id)
assert retrieved.id == created.id
assert retrieved.status is BatchStatus.PENDING
assert retrieved.input_file_id == "file-1"| # Convert status string to BatchStatus enum. | ||
| from vllm_router.batch.batch import BatchStatus | ||
| from vllm_router.services.batch_service.batch import BatchStatus |
There was a problem hiding this comment.
The local import of BatchStatus is redundant because it is already imported at the module level on line 25. Removing it simplifies the code and avoids unnecessary import overhead.
| # Convert status string to BatchStatus enum. | |
| from vllm_router.batch.batch import BatchStatus | |
| from vllm_router.services.batch_service.batch import BatchStatus | |
| # Convert status string to BatchStatus enum. |
Both from gemini-code-assist's review. The tests used a hardcoded /tmp/test_vllm_batch with a manual cleanup fixture, copying the sibling test_file_storage.py. pytest's tmp_path gives each test its own directory and removes the fixture, and it does not collide when CI runs tests concurrently. The local import of BatchStatus inside retrieve_batch was there to work around the broken module-level one. With the module-level import corrected it is dead weight, so it goes. Replacing the whole file with the origin/main copy still gives ModuleNotFoundError on both tests, so the module-level import is what they pin. Signed-off-by: chrikrah <48338417+chrikrah@users.noreply.github.com>
|
Both taken, in
The local Replacing the whole file with the |
|
This one is ready whenever it suits you. Approved on 2026-09-18, Nothing is owed from my side, so this is only a note that it is not waiting on me. The other three from me here, #1094, #1095 and #1097, all sit at |
#217 renamed
src/vllm_router/batch/tosrc/vllm_router/services/batch_service/in95efecc, on 2025-03-04.local_processor.pymoved with it and kept importingBatchInfoandBatchStatusfrom the old path, at line 24 and again insideretrieve_batchat line 127. The module it wants is now its own sibling.app.py:254callsinitialize_batch_processorwhenever a user passes--enable-batch-api. That function importsLocalBatchProcessor, so the router aborts at startup:#784 fixed the same orphaned import one layer out, in
services/batch_service/__init__.py, and stopped there.Line 22 of the same file imports
aiosqlite. Neitherpyproject.tomlnorsrc/vllm_router/requirements.txtdeclares it, so a stock install fails one line earlier.router-e2e-test.yml:47installs frompyproject.tomland line 80 collectssrc/tests/test_*.py, which now includes the new file, so this branch declares it in both places.Two regression tests cover the path
app.pytakes. One builds the processor throughinitialize_batch_processor. The other creates a batch and reads it back. Run against the pinnedaiosqlite==0.21.0:Revert the import change, keep the tests, and both fail:
Correcting only the module-level import leaves the second one failing, which is what the line 127 edit is for.
No open issue covers this, so there is nothing to link. #766 reported the sibling defect and #784 closed it.
-swhen doinggit commit[Bugfix],[Feat], and[CI].One limit: no live vLLM backend served these endpoints, so this covers startup and the SQLite round trip only.
cancel_batchalso writes the literal"cancelled", whichBatchStatushas no member for, soretrieve_batchraisesValueErrorafterwards. That is a separate defect and stays out of this change.