diff --git a/mriqc/interfaces/bids.py b/mriqc/interfaces/bids.py index a2de5227..178ed868 100644 --- a/mriqc/interfaces/bids.py +++ b/mriqc/interfaces/bids.py @@ -23,7 +23,7 @@ import re from pathlib import Path -import simplejson as json +import orjson as json from nipype.interfaces.base import ( BaseInterfaceInputSpec, DynamicTraitedSpec, @@ -187,16 +187,17 @@ def _run_interface(self, runtime): self._out_dict['provenance'] = {} self._out_dict['provenance'].update(prov_dict) - with open(out_file, 'w') as f: - f.write( - json.dumps( - self._out_dict, - sort_keys=True, - indent=2, - ensure_ascii=False, - ) + Path(out_file).write_bytes( + json.dumps( + self._out_dict, + option=( + json.OPT_SORT_KEYS + | json.OPT_INDENT_2 + | json.OPT_APPEND_NEWLINE + | json.OPT_SERIALIZE_NUMPY + ), ) - + ) return runtime diff --git a/mriqc/interfaces/webapi.py b/mriqc/interfaces/webapi.py index 291f085b..1ee0b2cd 100644 --- a/mriqc/interfaces/webapi.py +++ b/mriqc/interfaces/webapi.py @@ -20,8 +20,9 @@ # # https://www.nipreps.org/community/licensing/ # -import json +from pathlib import Path +import orjson from nipype.interfaces.base import ( BaseInterfaceInputSpec, Bunch, @@ -127,6 +128,7 @@ class UploadIQMsInputSpec(BaseInterfaceInputSpec): class UploadIQMsOutputSpec(TraitedSpec): api_id = traits.Either(None, traits.Str, desc='Id for report returned by the web api') + payload_file = File(desc='Submitted payload (only for debugging)') class UploadIQMs(SimpleInterface): @@ -153,6 +155,18 @@ def _run_interface(self, runtime): modality=self.inputs.modality, ) + payload_str = orjson.dumps( + payload, + option=( + orjson.OPT_SORT_KEYS + | orjson.OPT_INDENT_2 + | orjson.OPT_APPEND_NEWLINE + | orjson.OPT_SERIALIZE_NUMPY + ), + ) + Path('payload.json').write_bytes(payload_str) + self._results['payload_file'] = str(Path('payload.json').absolute()) + try: self._results['api_id'] = response.json()['_id'] except (AttributeError, KeyError, ValueError): @@ -161,7 +175,7 @@ def _run_interface(self, runtime): 'QC metrics upload failed to create an ID for the record ' f'uploaded. Response from server follows: {response.text}' '\n\nPayload:\n' - f'{json.dumps(payload, indent=2)}' + f'{payload_str}' ) config.loggers.interface.warning(errmsg) @@ -177,7 +191,7 @@ def _run_interface(self, runtime): '', '', 'Payload:', - json.dumps(payload, indent=2), + payload_str, ] ) config.loggers.interface.warning(errmsg) @@ -209,8 +223,6 @@ def upload_qc_metrics( """ from copy import deepcopy - from json import dumps, loads - from pathlib import Path import requests @@ -219,7 +231,7 @@ def upload_qc_metrics( errmsg = 'Unknown API endpoint' if not endpoint else 'Authentication failed.' return Bunch(status_code=1, text=errmsg) - in_data = loads(Path(in_iqms).read_text()) + in_data = orjson.loads(Path(in_iqms).read_bytes()) # Extract metadata and provenance meta = in_data.pop('bids_meta') @@ -267,20 +279,23 @@ def upload_qc_metrics( start_message = messages.QC_UPLOAD_START.format(url=endpoint) config.loggers.interface.info(start_message) + + errmsg = None try: # if the modality is bold, call "bold" endpoint response = requests.post( f'{endpoint}/{modality}', headers=headers, - data=dumps(data), + data=orjson.dumps(data, option=orjson.OPT_SERIALIZE_NUMPY), timeout=15, ) except requests.ConnectionError as err: - errmsg = f'Error uploading IQMs -- Connection error:\n{err}' - return Bunch(status_code=1, text=errmsg) + errmsg = (f'Error uploading IQMs: Connection error:', f'{err}') except requests.exceptions.ReadTimeout as err: - errmsg = f'Error uploading IQMs -- {endpoint} seems down:\n{err}' - return Bunch(status_code=1, text=errmsg) + errmsg = (f'Error uploading IQMs: Server {endpoint} is down.', f'{err}') + + if errmsg is not None: + response = Bunch(status_code=1, text='\n'.join(errmsg)) return response, data diff --git a/pyproject.toml b/pyproject.toml index 4202717f..1e3bf042 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ "nitransforms ~= 24.0", "niworkflows ~=1.10.1", "numpy ~=1.20", + "orjson", "pandas", "pybids >= 0.15.6", "PyYAML",