-
Notifications
You must be signed in to change notification settings - Fork 0
added endpoint for testing contributed nwb files #402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two comments:
- I'm not familiar with this await calling another async function. Perhaps @GianlucaFicarelli could comment on whether this part looks ok?
- If I understand correctly, the validation is passed if the file can be opened? _process_nwb and _validate_and_read_nwb_file look like they're doing quite similar things in this regard. Could you clarrify the difference? And do they need to be in two separate async functions.
Thanks!
|
@james-isbister _process_nwb and _validate_and_read_nwb_file have now been consolidated. |
app/endpoints/declared_endpoints.py
Outdated
|
|
||
| test_all_nwb_readers(temp_file_path_local) | ||
|
|
||
| return temp_file_path_local # Return the path for cleanup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the temp file isn't automatically deleted in the context manager, instead of returning the path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file path (temp_file_path_local) is returned from blocking_file_operations and stored in the main function's temp_file_path variable. It is then explicitly and reliably deleted in the finally block of validate_nwb_file via the helper function _cleanup_file(temp_file_path).This manual cleanup is essential because the file needs to persist on the disk while the test_all_nwb_readers function runs on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's enough to move validate_all_nwb_readers inside the context manager to avoid all this complexity, since the file is needed only by that function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, it has now been moved to the context manager to avoid having to explicitly delete temp files.
app/endpoints/declared_endpoints.py
Outdated
| summary="Validate new format.", | ||
| description="Tests a new file (.nwb) with basic validation.", | ||
| ) | ||
| async def validate_nwb_file( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defining this endpoint as async doesn't provide any advantage because it still needs to use threads, and it makes the logic more complicated.
It should be defined as a regular sync endpoint, similarly to the other endpoints in obi-one handled by fastapi.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I have made it sync again.
app/endpoints/declared_endpoints.py
Outdated
| # 2. Run the synchronous validation function | ||
| try: | ||
| # This function now runs synchronously and cleanup is handled | ||
| # automatically when exiting the 'with' block. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and other comments that seem to have been added automatically should be removed because useless. I didn't mark all of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They have been removed.
app/endpoints/declared_endpoints.py
Outdated
|
|
||
| NWB_READERS = [BBPNWBReader, ScalaNWBReader, AIBSNWBReader, TRTNWBReader] # , VUNWBReader] | ||
|
|
||
| test_protocols = ["APThreshold", "SAPThres1", "SAPThres2", "SAPThres3", "SAPTres1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This variable is a constants and should be upper case.
Note also that this file doesn't look to be formatted with make format and make lint. After formatting, it would be even clearer to move this big list into a separate file (constants.py or something similar, without any code).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to upper case and reformatted.
app/endpoints/declared_endpoints.py
Outdated
|
|
||
| # List of all available NWB Reader classes to iterate over | ||
|
|
||
| NWB_READERS = [BBPNWBReader, ScalaNWBReader, AIBSNWBReader, TRTNWBReader] # , VUNWBReader] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VUNWBReader is commented out, but it should be uncommented or removed, or explained.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has been removed.
| def _handle_empty_file(file: UploadFile) -> None: | ||
| """Handle empty file upload by raising an appropriate HTTPException.""" | ||
| L.error(f"Empty file uploaded: {file.filename}") | ||
| raise HTTPException( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency with most of the other code, it should raise ApiError instead of HTTPException.
Moreover, ApiErrorCode.BAD_REQUEST doesn't exist, but a more specific error code can be defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ApiError is now used.
app/endpoints/declared_endpoints.py
Outdated
| ) | ||
| def validate_nwb_file( | ||
| file: Annotated[UploadFile, File(description="Nwb file to upload (.swc, .h5, or .asc)")], | ||
| ) -> dict: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of a generic dict, the endpoint should return a pydantic schema. This has also the advantage to appear in the openapi docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A pydantic schema is now returned.
app/endpoints/declared_endpoints.py
Outdated
| return { | ||
| "status": "success", | ||
| "message": "NWB file validation successful.", | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to the code above, but there should be unit tests for testing the new endpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unit tests added.
| # File extension is needed for the NWB readers to recognize the file type | ||
| file_extension = Path(file.filename).suffix.lower() if file.filename else "" | ||
| if file_extension != ".nwb": | ||
| raise ApiError( | ||
| message="Invalid file extension. Must be .nwb", | ||
| error_code=ApiErrorCode.INVALID_REQUEST, | ||
| http_status_code=HTTPStatus.BAD_REQUEST, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check can be moved at the beginning, so the file isn't even read if the extension is incorrect.
| L.error(f"Error reading uploaded file content: {e!s}") | ||
| raise ApiError( | ||
| message=f"Error reading uploaded file content: {e!s}", | ||
| error_code=ApiErrorCode.INTERNAL_SERVER_ERROR, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ApiErrorCode.INTERNAL_SERVER_ERROR doesn't exist, it can be GENERIC_ERROR
| L.error(f"Error writing file to disk: {e!s}") | ||
| raise ApiError( | ||
| message=f"Error writing file to disk: {e!s}", | ||
| error_code=ApiErrorCode.INTERNAL_SERVER_ERROR, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ApiErrorCode.INTERNAL_SERVER_ERROR doesn't exist, it can be GENERIC_ERROR
| files created during processing are automatically cleaned up. | ||
| """ | ||
| try: | ||
| content = file.file.read() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the logic below, we don't need to load the content in memory here, since the content is just saved to a temporary file below.
It should be possible to unify the two blocks of code reading and writing, so that the temp file is written directly from the stream.
| """ | ||
| for readerclass in NWB_READERS: | ||
| try: | ||
| reader = readerclass(nwb_file_path, TEST_PROTOCOLS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the classes defined in NWB_READERS, the __init__ wants a hdf5 object (h5py.File) and not a path.
I didn't try the code, does it work for you?
The function validate_all_nwb_readers isn't tested in test_validate_nwb.py because the function is patched, so it would be safer to add an explicit test with a small nwb file.
| http_status_code=HTTPStatus.BAD_REQUEST, | ||
| ) | ||
|
|
||
| with tempfile.TemporaryDirectory() as temp_dir: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the maximum expected file size for nwb files?
We should ensure that there is enough space, and it's also better to put a limit on the maximum file size.
| response = client.post( | ||
| ROUTE, | ||
| # Note: Filenames are case-sensitive on the extension check unless lowercase() is used. | ||
| # Assuming the check is case-insensitive if .NWB is tested. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment seems wrong, since the check for the extension is case insensitive, but .NWB is expected to fail in this test.
| @@ -0,0 +1,137 @@ | |||
| from http import HTTPStatus | |||
| from unittest.mock import MagicMock, patch | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests are not run in the CI of this PR. Can you try to rebase or merge from main, to see if it's fixed?
This endpoint runs pynwb validation on nwb files and returns an error if they do not pass.