Skip to content
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

Schema inference enhancements #1037

Merged
merged 11 commits into from
Aug 29, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Features

### Bug fixes
* Fixed the JSON schema inference warning on excluded fields; also improved error message reporting of which method
triggered the error. [PR #1037](https://github.com/catalystneuro/neuroconv/pull/1037)

### Improvements

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,6 @@ def add_to_nwbfile(

metadata['Ecephys']['ElectricalSeries'] = dict(name=my_name, description=my_description)

The default is False (append mode).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CodyCBakerPhD it seems that it came from here. Let me test, anyway, this was an error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would be correct!

https://github.com/catalystneuro/neuroconv/blob/main/src/neuroconv/datainterfaces/ecephys/baserecordingextractorinterface.py#L318 would be interpreted as an argument not the description of the argument it is attached to

starting_time : float, optional
Sets the starting time of the ElectricalSeries to a manually set value.
stub_test : bool, default: False
Expand Down
6 changes: 3 additions & 3 deletions src/neuroconv/nwbconverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ def get_conversion_options_schema(self) -> dict:
version="0.1.0",
)
for interface_name, data_interface in self.data_interface_objects.items():
conversion_options_schema["properties"].update(
{interface_name: unroot_schema(data_interface.get_conversion_options_schema())}
)

schema = data_interface.get_conversion_options_schema()
conversion_options_schema["properties"].update({interface_name: unroot_schema(schema)})
return conversion_options_schema
10 changes: 8 additions & 2 deletions src/neuroconv/utils/json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ def get_json_schema_from_method_signature(method: Callable, exclude: Optional[li
exclude = exclude or []
exclude += ["self", "cls"]

split_qualname = method.__qualname__.split(".")[1:]
method_display = ".".join(split_qualname[1:]) if "<" not in split_qualname[-1] else split_qualname[0]

signature = inspect.signature(obj=method)
parameters = signature.parameters
additional_properties = False
Expand Down Expand Up @@ -140,10 +143,13 @@ def get_json_schema_from_method_signature(method: Callable, exclude: Optional[li
# Attempt to find descriptions within the docstring of the method
parsed_docstring = docstring_parser.parse(method.__doc__)
for parameter_in_docstring in parsed_docstring.params:
if parameter_in_docstring.arg_name in exclude:
continue

if parameter_in_docstring.arg_name not in json_schema["properties"]:
message = (
f"The argument_name '{parameter_in_docstring.arg_name}' from the docstring not occur in the "
"method signature, possibly due to a typo."
f"The argument_name '{parameter_in_docstring.arg_name}' from the docstring of method "
f"'{method_display}' does not occur in the signature, possibly due to a typo."
)
warnings.warn(message=message, stacklevel=2)
continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import pytest
from jsonschema import validate
from pydantic import DirectoryPath, FilePath
from pynwb import NWBFile

from neuroconv.datainterfaces import AlphaOmegaRecordingInterface
from neuroconv.utils import ArrayType, get_json_schema_from_method_signature
from neuroconv.utils import ArrayType, DeepDict, get_json_schema_from_method_signature


def test_get_json_schema_from_method_signature_basic():
Expand Down Expand Up @@ -299,8 +300,119 @@ def method_with_typo_in_docstring(integer: int):
with pytest.warns(expected_warning=UserWarning) as warning_info:
test_json_schema = get_json_schema_from_method_signature(method=method_with_typo_in_docstring)

assert len(warning_info) == 1

expected_warning_message = (
"The argument_name 'integ' from the docstring of method 'method_with_typo_in_docstring' does not occur in "
"the signature, possibly due to a typo."
)
assert warning_info[0].message.args[0] == expected_warning_message

expected_json_schema = {
"properties": {"integer": {"type": "integer"}},
"required": ["integer"],
"type": "object",
"additionalProperties": False,
}

assert test_json_schema == expected_json_schema


def test_get_json_schema_from_method_signature_docstring_warning_with_exclusions():
def method_with_typo_in_docstring_and_exclusions(integer: int, nwbfile: NWBFile, metadata: DeepDict):
"""
This is a docstring with a typo in the argument name.

Parameters
----------
integ : int
This is an integer.
nwbfile : pynwb.NWBFile
An in-memory NWBFile object.
metadata : neuroconv.utils.DeepDict
A dictionary-like object that allows for deep access and modification.
"""
pass

with pytest.warns(expected_warning=UserWarning) as warning_info:
test_json_schema = get_json_schema_from_method_signature(
method=method_with_typo_in_docstring_and_exclusions, exclude=["nwbfile", "metadata"]
)

assert len(warning_info) == 1

expected_warning_message = (
"The argument_name 'integ' from the docstring of method 'method_with_typo_in_docstring_and_exclusions' "
"does not occur in the signature, possibly due to a typo."
)
assert warning_info[0].message.args[0] == expected_warning_message

expected_json_schema = {
"properties": {"integer": {"type": "integer"}},
"required": ["integer"],
"type": "object",
"additionalProperties": False,
}

assert test_json_schema == expected_json_schema


def test_get_json_schema_from_method_signature_docstring_warning_from_bound_method():
class TestClass:
def test_bound_method(self, integer: int):
"""
This is a docstring with a typo in the argument name.

Parameters
----------
integ : int
This is an integer.
"""
pass

with pytest.warns(expected_warning=UserWarning) as warning_info:
test_json_schema = get_json_schema_from_method_signature(method=TestClass.test_bound_method)

assert len(warning_info) == 1

expected_warning_message = (
"The argument_name 'integ' from the docstring of method 'TestClass.test_bound_method' does not occur in the "
"signature, possibly due to a typo."
)
assert warning_info[0].message.args[0] == expected_warning_message

expected_json_schema = {
"properties": {"integer": {"type": "integer"}},
"required": ["integer"],
"type": "object",
"additionalProperties": False,
}

assert test_json_schema == expected_json_schema


def test_get_json_schema_from_method_signature_docstring_warning_from_class_method():
class TestClass:
@classmethod
def test_class_method(self, integer: int):
"""
This is a docstring with a typo in the argument name.

Parameters
----------
integ : int
This is an integer.
"""
pass

with pytest.warns(expected_warning=UserWarning) as warning_info:
test_json_schema = get_json_schema_from_method_signature(method=TestClass.test_class_method)

assert len(warning_info) == 1

expected_warning_message = (
"The argument_name 'integ' from the docstring not occur in the method signature, possibly due to a typo."
"The argument_name 'integ' from the docstring of method 'TestClass.test_class_method' does not occur in the "
"signature, possibly due to a typo."
)
assert warning_info[0].message.args[0] == expected_warning_message

Expand Down
Loading