Skip to content

Commit

Permalink
Ditch ruamel-yaml (keeps messing up due to the comment anchors) in fa…
Browse files Browse the repository at this point in the history
…vor of pyyaml
  • Loading branch information
marcelzwiers committed Jan 14, 2025
1 parent 1f1ef5f commit 390eb24
Show file tree
Hide file tree
Showing 7 changed files with 27 additions and 72 deletions.
62 changes: 8 additions & 54 deletions bidscoin/bcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,13 @@
from importlib.util import spec_from_file_location, module_from_spec
from pathlib import Path
from typing import Union
from ruamel.yaml import YAML
from tqdm import tqdm
from tqdm.contrib.logging import logging_redirect_tqdm
from importlib.util import find_spec
if find_spec('bidscoin') is None:
sys.path.append(str(Path(__file__).parents[1]))
from bidscoin import templatefolder, pluginfolder, bidsmap_template, tutorialurl, trackusage, tracking, configdir, configfile, config, DEBUG

yaml = YAML()
yaml.representer.ignore_aliases = lambda *data: True # Expand aliases (https://stackoverflow.com/questions/58091449/disabling-alias-for-yaml-file-in-python)

LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -266,7 +262,7 @@ def list_plugins(show: bool=False) -> tuple[list[Path], list[Path]]:

def install_plugins(filenames: list[str]=()) -> None:
"""
Installs template bidsmaps and plugins and adds the plugin Options and data format section to the default template bidsmap
Installs template bidsmaps and plugins
:param filenames: Fullpath filenames of the and template bidsmaps plugins that need to be installed
:return: Nothing
Expand All @@ -276,13 +272,15 @@ def install_plugins(filenames: list[str]=()) -> None:

files = [Path(file) for file in filenames if file.endswith('.yaml') or file.endswith('.py')]

# Load the default template bidsmap
with open(bidsmap_template, 'r') as stream:
template = yaml.load(stream)

# Install the template bidsmaps and plugins in their targetfolder
for file in files:

# Check if we can import the plugin
module = import_plugin(file.resolve())
if not module:
LOGGER.error(f"Plugin failure, please re-install a valid version of '{file.name}'")
continue

# Copy the file to their target folder
targetfolder = templatefolder if file.suffix == '.yaml' else pluginfolder
LOGGER.info(f"Installing: '{file}'")
Expand All @@ -295,30 +293,12 @@ def install_plugins(filenames: list[str]=()) -> None:
LOGGER.success(f"The '{file.name}' template bidsmap was successfully installed")
continue

# Check if we can import the plugin
module = import_plugin(file)
if not module:
LOGGER.error(f"Plugin failure, please re-install a valid version of '{file.name}'")
continue

# Add the Options and data format section of the plugin to the default template bidsmap
if hasattr(module, 'OPTIONS') or hasattr(module, 'BIDSMAP'):
if hasattr(module, 'OPTIONS'):
LOGGER.info(f"Adding default {file.name} bidsmap options to the {bidsmap_template.stem} template")
template['Options']['plugins'][file.stem] = module.OPTIONS
if hasattr(module, 'BIDSMAP'):
for key, value in module.BIDSMAP.items():
LOGGER.info(f"Adding default {key} bidsmappings to the {bidsmap_template.stem} template")
template[key] = value
with open(bidsmap_template, 'w') as stream:
yaml.dump(template, stream)

LOGGER.success(f"The '{file.name}' plugin was successfully installed")


def uninstall_plugins(filenames: list[str]=(), wipe: bool=False) -> None:
"""
Uninstalls template bidsmaps and plugins and removes the plugin Options and data format section from the default template bidsmap
Uninstalls template bidsmaps and plugins
:param filenames: Fullpath filenames of the and template bidsmaps plugins that need to be uninstalled
:param wipe: Removes the plugin bidsmapping section if True
Expand All @@ -329,19 +309,9 @@ def uninstall_plugins(filenames: list[str]=(), wipe: bool=False) -> None:

files = [Path(file) for file in filenames if file.endswith('.yaml') or file.endswith('.py')]

# Load the default template bidsmap
with open(bidsmap_template, 'r') as stream:
template = yaml.load(stream)

# Uninstall the plugins
for file in files:

# First check if we can import the plugin
if file.suffix == '.py':
module = import_plugin(pluginfolder/file.name)
else:
module = None

# Remove the file from the target folder
LOGGER.info(f"Uninstalling: '{file}'")
sourcefolder = templatefolder if file.suffix == '.yaml' else pluginfolder
Expand All @@ -354,22 +324,6 @@ def uninstall_plugins(filenames: list[str]=(), wipe: bool=False) -> None:
LOGGER.success(f"The '{file.name}' template bidsmap was successfully uninstalled")
continue

# Remove the Options and data format section from the default template bidsmap
if not module:
LOGGER.warning(f"Cannot remove any {file.stem} bidsmap options from the {bidsmap_template.stem} template")
continue
if removed := hasattr(module, 'OPTIONS'):
LOGGER.info(f"Removing default {file.stem} bidsmap options from the {bidsmap_template.stem} template")
template['Options']['plugins'].pop(file.stem, None)
if wipe and hasattr(module, 'BIDSMAP'):
removed = True
for key, value in module.BIDSMAP.items():
LOGGER.info(f"Removing default {key} bidsmappings from the {bidsmap_template.stem} template")
template.pop(key, None)
if removed:
with open(bidsmap_template, 'w') as stream:
yaml.dump(template, stream)

LOGGER.success(f"The '{file.stem}' plugin was successfully uninstalled")


Expand Down
13 changes: 8 additions & 5 deletions bidscoin/bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import pandas as pd
import ast
import datetime
import yaml
import jsonschema
import bidsschematools.schema as bst
import dateutil.parser
Expand All @@ -25,9 +26,6 @@
sys.path.append(str(Path(__file__).parents[1]))
from bidscoin import bcoin, schemafolder, templatefolder, is_hidden, __version__
from bidscoin.plugins import EventsParser
from ruamel.yaml import YAML
yaml = YAML()
yaml.representer.ignore_aliases = lambda *data: True # Expand aliases (https://stackoverflow.com/questions/58091449/disabling-alias-for-yaml-file-in-python)

# Define custom data types (replace with proper classes or TypeAlias of Python >= 3.10)
Plugin = NewType('Plugin', dict[str, Any])
Expand All @@ -53,6 +51,11 @@
"""The possible extensions of BIDS data files"""


class NoAliasDumper(yaml.SafeDumper):
def ignore_aliases(self, data):
return True


class DataSource:
"""Reads properties, attributes and BIDS-related features to sourcefiles of a supported dataformat (e.g. DICOM or PAR)"""

Expand Down Expand Up @@ -920,7 +923,7 @@ def __init__(self, yamlfile: Path, folder: Path=templatefolder, plugins: Iterabl
if any(checks):
LOGGER.info(f"Reading: {yamlfile}")
with yamlfile.open('r') as stream:
bidsmap_data = yaml.load(stream)
bidsmap_data = yaml.safe_load(stream)
self._data = bidsmap_data
"""The raw YAML data"""

Expand Down Expand Up @@ -1082,7 +1085,7 @@ def save(self, filename: Path=None):
filename.parent.mkdir(parents=True, exist_ok=True)
LOGGER.info(f"Saving bidsmap in: {filename}")
with filename.open('w') as stream:
yaml.dump(self._data, stream)
yaml.dump(self._data, stream, NoAliasDumper, sort_keys=False)

def validate(self, level: int=1) -> bool:
"""
Expand Down
6 changes: 3 additions & 3 deletions bidscoin/bidseditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -933,11 +933,11 @@ def save_options(self):
if yamlfile:
LOGGER.info(f"Saving bidsmap options in: {yamlfile}")
with open(yamlfile, 'r') as stream:
bidsmap = bids.yaml.load(stream)
bidsmap = bids.yaml.safe_load(stream)
bidsmap.options = self.output_bidsmap.options
bidsmap.plugins = self.output_bidsmap.plugins
with open(yamlfile, 'w') as stream:
bids.yaml.dump(bidsmap, stream)
bids.yaml.safe_dump(bidsmap, stream, sort_keys=False)

def sample_doubleclicked(self, item):
"""When source file is double-clicked in the samples_table, show the inspect- or edit-window"""
Expand Down Expand Up @@ -1346,7 +1346,7 @@ def import_menu(self, pos):
if Path(metafile).suffix == '.json':
metadata = json.load(meta_fid)
elif Path(metafile).suffix in ('.yaml', '.yml'):
metadata = bids.yaml.load(meta_fid)
metadata = bids.yaml.safe_load(meta_fid)
else:
dialect = csv.Sniffer().sniff(meta_fid.read())
meta_fid.seek(0)
Expand Down
3 changes: 2 additions & 1 deletion docs/_static/dictionary-custom.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ RDP
README
Radboud
Refactored
RunItem
SBREF
SDAT
SPM
Expand Down Expand Up @@ -280,9 +281,9 @@ rawmapper
rebranded
refactoring
regex
ruamel
runindex
runitem
RunItem
runtime
sbatch
screenshot
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ dependencies = ['pandas',
'numpy',
'pydicom >= 2',
'PyQt6',
'ruamel-yaml > 0.17.2',
'pyyaml',
'tomli >= 1.1.0 ; python_version < "3.11"',
'coloredlogs',
'tqdm >= 4.60.0',
Expand Down
7 changes: 3 additions & 4 deletions tests/test_bidsmapper.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import pytest
import re
import ruamel.yaml
from bidscoin import bcoin, bidsmapper, bidsmap_template

bcoin.setup_logging()


@pytest.mark.parametrize('subprefix', ['Doe'])
@pytest.mark.parametrize('sesprefix', ['0'])
@pytest.mark.parametrize('subprefix', ['Doe', 'Doe^', '*'])
@pytest.mark.parametrize('sesprefix', ['0', '*'])
@pytest.mark.parametrize('store', [False, True])
def test_bidsmapper(raw_dicomdir, bids_dicomdir, bidsmap_dicomdir, subprefix, sesprefix, store):
resubprefix = '' if subprefix=='*' else re.escape(subprefix).replace(r'\-','-')
resesprefix = '' if sesprefix=='*' else re.escape(sesprefix).replace(r'\-','-')
bidsmap = bidsmapper.bidsmapper(raw_dicomdir, bids_dicomdir, bidsmap_dicomdir, bidsmap_template, [], subprefix, sesprefix, unzip='', store=store, automated=True, force=True)
assert isinstance(bidsmap.dataformats[0]._data, ruamel.yaml.CommentedMap)
assert isinstance(bidsmap.dataformats[0]._data, dict)
assert bidsmap.options['subprefix'] == subprefix
assert bidsmap.options['sesprefix'] == sesprefix
assert bidsmap.dataformat('DICOM').subject == f"<<filepath:/{raw_dicomdir.name}/{resubprefix}(.*?)/>>"
Expand Down
6 changes: 2 additions & 4 deletions tests/test_heuristics.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import jsonschema
import json
import yaml
from bidscoin import bcoin, bidscoinroot
from ruamel.yaml import YAML
yaml = YAML()
yaml.representer.ignore_aliases = lambda *data: True # Expand aliases (https://stackoverflow.com/questions/58091449/disabling-alias-for-yaml-file-in-python)

bcoin.setup_logging()

Expand All @@ -15,5 +13,5 @@ def test_jsonschema_validate_bidsmaps():
schema = json.load(stream)
for template in (bidscoinroot/'heuristics').glob('*.yaml'):
with template.open('r') as stream:
bidsmap = yaml.load(stream)
bidsmap = yaml.safe_load(stream)
jsonschema.validate(bidsmap, schema)

0 comments on commit 390eb24

Please sign in to comment.