Skip to content

Commit

Permalink
fsspec and upath for cloud/remote paths
Browse files Browse the repository at this point in the history
 - make use of universal_pathlib to enable cloud/remote paths in pybids
 - remove uri prefix when passing to validator
Co-authored-by: KarlHanEdn <tianming@ualberta.ca>
  • Loading branch information
akhanf committed Jul 23, 2024
1 parent d442c8b commit 022024d
Show file tree
Hide file tree
Showing 12 changed files with 31 additions and 25 deletions.
2 changes: 1 addition & 1 deletion bids/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
''' Utilities for manipulating package-level settings. '''

import json
from pathlib import Path
from upath import UPath as Path
import os
import warnings

Expand Down
2 changes: 1 addition & 1 deletion bids/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"""

import os
from pathlib import Path
from upath import UPath as Path
from unittest.mock import patch

import pytest
Expand Down
2 changes: 1 addition & 1 deletion bids/layout/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Database-related functionality.
"""

from pathlib import Path
from upath import UPath as Path
import re
import sqlite3
from functools import lru_cache
Expand Down
13 changes: 8 additions & 5 deletions bids/layout/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import os
import json
import re
import fsspec
from collections import defaultdict
from pathlib import Path
from upath import UPath as Path
from functools import partial, lru_cache

from bids_validator import BIDSValidator
Expand Down Expand Up @@ -173,7 +174,7 @@ def _validate_file(self, f):

# BIDS validator expects absolute paths, but really these are relative
# to the BIDS project root.
to_check = f.relative_to(self._layout._root)
to_check = Path(f.path).relative_to(Path(self._layout._root.path)) # use .path then Path() to drop the uri prefix
# Pretend the path is an absolute path
to_check = Path('/') / to_check
# bids-validator works with posix paths only
Expand Down Expand Up @@ -202,8 +203,9 @@ def _index_dir(self, path, config, force=None):
for c in config:
config_entities.update(c.entities)


# Get lists of 1st-level subdirectories and files in the path directory
_, dirnames, filenames = next(os.walk(path))
_, dirnames, filenames = next(path.fs.walk(path.path))

# Move any directories suffixed with .zarr to filenames
zarrnames = [entry for entry in dirnames if Path(entry).suffix == '.zarr']
Expand Down Expand Up @@ -303,7 +305,7 @@ def _index_metadata(self):
# if they correspond to data files that are indexed
@lru_cache(maxsize=None)
def load_json(path):
with open(path, 'r', encoding='utf-8') as handle:
with Path(path).fs.open(Path(path).path, 'r', encoding='utf-8') as handle:
try:
return json.load(handle)
except (UnicodeDecodeError, json.JSONDecodeError) as e:
Expand All @@ -324,7 +326,7 @@ def load_json(path):

payload = None
if ext == '.json':
payload = partial(load_json, bf.path)
payload = partial(load_json, bf)
else:
filenames.append(bf)

Expand Down Expand Up @@ -496,3 +498,4 @@ def create_association_pair(src, dst, kind, kind2=None):
self.session.bulk_save_objects(all_objs)
self.session.bulk_insert_mappings(Tag, all_tag_dicts)
self.session.commit()

2 changes: 1 addition & 1 deletion bids/layout/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import copy
import enum
import difflib
from pathlib import Path
from upath import UPath as Path
import warnings
from typing import Hashable

Expand Down
19 changes: 10 additions & 9 deletions bids/layout/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import re
import os
from pathlib import Path
from upath import UPath
import warnings
import json
from copy import deepcopy
Expand Down Expand Up @@ -63,19 +64,19 @@ def _init_on_load(self):
def _sanitize_init_args(self, kwargs):
""" Prepare initialization arguments for serialization """
if 'root' in kwargs:
kwargs['root'] = str(Path(kwargs['root']).absolute())
kwargs['root'] = str(UPath(kwargs['root']).absolute())

if 'config' in kwargs and isinstance(kwargs['config'], list):
kwargs['config'] = [
str(Path(config).absolute())
str(UPath(config).absolute())
if isinstance(config, os.PathLike) else config
for config in kwargs['config']
]

# Get abspaths
if kwargs.get('derivatives') not in (None, True, False):
kwargs['derivatives'] = [
str(Path(der).absolute())
str(UPath(der).absolute())
for der in listify(kwargs['derivatives'])
]

Expand Down Expand Up @@ -157,11 +158,11 @@ def load(self, config, session=None):
A Config instance.
"""

if isinstance(config, (str, Path)):
if isinstance(config, (str, Path, UPath)):
config_paths = get_option('config_paths')
if config in config_paths:
config = config_paths[config]
if not Path(config).exists():
if not UPath(config).exists():
raise ValueError("{} is not a valid path.".format(config))
else:
with open(config, 'r') as f:
Expand Down Expand Up @@ -213,11 +214,11 @@ def __init__(self, filename):

@property
def _path(self):
return Path(self.path)
return UPath(self.path)

@property
def _dirname(self):
return Path(self.dirname)
return UPath(self.dirname)

def __getattr__(self, attr):
# Ensures backwards compatibility with old File_ namedtuple, which is
Expand Down Expand Up @@ -245,7 +246,7 @@ def __fspath__(self):
def relpath(self):
"""Return path relative to layout root"""
root = object_session(self).query(LayoutInfo).first().root
return str(Path(self.path).relative_to(root))
return str(UPath(self.path).relative_to(root))

def get_associations(self, kind=None, include_parents=False):
"""Get associated files, optionally limiting by association kind.
Expand Down Expand Up @@ -370,7 +371,7 @@ def copy(self, path_patterns, symbolic_link=False, root=None,
if self._path.is_absolute() or root is None:
path = self._path
else:
path = Path(root) / self._path
path = UPath(root) / self._path

Check warning on line 374 in bids/layout/models.py

View check run for this annotation

Codecov / codecov/patch

bids/layout/models.py#L374

Added line #L374 was not covered by tests

if not path.exists():
raise ValueError("Target filename to copy/symlink (%s) doesn't "
Expand Down
2 changes: 1 addition & 1 deletion bids/layout/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Miscellaneous layout-related utilities."""
from pathlib import Path
from upath import UPath as Path

from .. import config as cf
from ..utils import make_bidsfile, listify
Expand Down
5 changes: 2 additions & 3 deletions bids/layout/validation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Functionality related to validation of BIDSLayouts and BIDS projects."""

from pathlib import Path
from upath import UPath as Path
import json
import re
import warnings
Expand Down Expand Up @@ -65,7 +65,6 @@ def validate_root(root, validate):
"containing the BIDS dataset.")

root = root.absolute()

if not root.exists():
raise ValueError("BIDS root does not exist: %s" % root)

Expand All @@ -83,7 +82,7 @@ def validate_root(root, validate):
else:
err = None
try:
with open(target, 'r', encoding='utf-8') as desc_fd:
with target.fs.open(target.path, 'r', encoding='utf-8') as desc_fd:
description = json.load(desc_fd)
except (UnicodeDecodeError, json.JSONDecodeError) as e:
description = None
Expand Down
2 changes: 1 addition & 1 deletion bids/layout/writing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from string import Formatter
from itertools import product
from ..utils import listify
from pathlib import Path
from upath import UPath as Path

__all__ = ['build_path', 'write_to_file']

Expand Down
2 changes: 1 addition & 1 deletion bids/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import re
import os
from pathlib import Path
from upath import UPath as Path


def listify(obj):
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ dependencies = [
"bids-validator>=1.11", # Keep up-to-date to ensure support for recent modalities
"num2words >=0.5.5",
"click >=8.0",
"fsspec==2024.3.1",
"universal_pathlib==0.2.2",
"gcsfs==2024.3.1",
]
dynamic = ["version"]

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import versioneer

setup(
version=versioneer.get_version(),
version='0.16.5-dev',
cmdclass=versioneer.get_cmdclass(),
)

0 comments on commit 022024d

Please sign in to comment.