diff --git a/snakebids/core/input_generation.py b/snakebids/core/input_generation.py index 8ae79287..4b917374 100644 --- a/snakebids/core/input_generation.py +++ b/snakebids/core/input_generation.py @@ -461,7 +461,7 @@ def _gen_bids_layout( if not pybidsdb_dir: pybidsdb_dir = None # Otherwise check for relative path and update - elif not _is_network_fs_path(pybidsdb_dir) and not Path(pybidsdb_dir).is_absolute(): + elif not Path(pybidsdb_dir).is_absolute(): pybidsdb_dir = None _logger.warning("Absolute path must be provided, database will not be used") diff --git a/snakebids/tests/strategies.py b/snakebids/tests/strategies.py index db6b0c62..000b92e0 100644 --- a/snakebids/tests/strategies.py +++ b/snakebids/tests/strategies.py @@ -43,7 +43,7 @@ def nothing() -> Any: return st.nothing() # type: ignore -def schemes() -> st.SearchStrategy[str | None]: +def schemes() -> st.SearchStrategy[str]: # Generate the prefix part of a url. # # we only sample from a fixed set of schemes. If schemes are randomly generated @@ -53,8 +53,7 @@ def schemes() -> st.SearchStrategy[str | None]: # Note for its current status we don't guarantee the following schemes to work # but only requiring them to be retained correctly i.e. not from 'gs://' to # 'gs:/' when fed into generate_inputs() and path expansion - fixed = st.sampled_from(["C://", "c:\\", "gs://", "s3://"]) - return st.one_of(st.none(), fixed) + return st.sampled_from(["C://", "c:\\", "gs://", "s3://"]) def paths( @@ -63,7 +62,7 @@ def paths( max_segments: int | None = None, absolute: bool | None = None, resolve: bool = False, - prefix_st: st.SearchStrategy[str | None] = None, # for urls e.g. "gs://..." + scheme: bool | None = None, # for prefixing urls with e.g. "gs://..." ) -> st.SearchStrategy[Path]: paths = st.lists( st.text(path_characters, min_size=1), @@ -75,13 +74,24 @@ def paths( absolute_paths = paths.map(lambda p: Path("/", p)) - # append scheme prefix if argument prefix_st is provided - if prefix_st: - assert ( - absolute_paths is None or absolute_paths is False - ), "scheme prepends relative path, set absolute=False if prefix_st not None!" - prefix_st = prefix_st.map(lambda s: "" if s is None else s) # string prefix - relative_paths = st.tuples(prefix_st, relative_paths).map(lambda t: t[0] + t[1]) + assert not ( + scheme and absolute + ), "Path schemes prepends relative path, so don't set absolute_paths=True!" + + # absolute path and path schemes are not compatible + # implicitly set the other if None is provided + if absolute and scheme is None: + scheme = False + elif scheme and absolute is None: + absolute = False + + if scheme is not False: + scheme_st = schemes() + if scheme is None: + # probabilistically return a schemed path + scheme_st = st.one_of(st.none(), scheme_st) + scheme_st = scheme_st.map(lambda s: "" if s is None else s) + relative_paths = st.tuples(scheme_st, relative_paths).map(lambda t: t[0] + t[1]) if absolute: result = absolute_paths diff --git a/snakebids/tests/test_generate_inputs.py b/snakebids/tests/test_generate_inputs.py index 227a01a4..e516b68f 100644 --- a/snakebids/tests/test_generate_inputs.py +++ b/snakebids/tests/test_generate_inputs.py @@ -1603,8 +1603,8 @@ def test_all_custom_paths(count: int): assert not _all_custom_paths(config) -def test_is_network_fs_path(): - test_data = ( +class TestRecogPathSchemes: + PATH_AND_TYPES = ( ("file", "RELATIVE"), ("hello", "RELATIVE"), ("gs", "RELATIVE"), @@ -1618,8 +1618,24 @@ def test_is_network_fs_path(): ("s3://some/s3/bucket", "NETWORK"), ) - if sys.version_info[:2] >= (3, 12): - # Due to required feature this only runs inPython version >= 3.12. + @pytest.mark.parametrize(("path", "path_type"), PATH_AND_TYPES) + def test_is_network_fs_path(self, path, path_type): + isnet = path_type == "NETWORK" + + # test the path itself, and the corresponding Path(path) + assert ( + _is_network_fs_path(path) == isnet + ), f"Path {path} should have isnet={isnet} but got {_is_network_fs_path(path)}" + if not isnet: + assert not _is_network_fs_path(Path(path)) + + @pytest.mark.skipif( + sys.version_info < (3, 12), reason="Path class has no with_segments()" + ) + @pytest.mark.parametrize( + ("path", "path_type"), [tup for tup in PATH_AND_TYPES if tup[1] == "RELATIVE"] + ) + def test_path_subclassing(self, path, path_type): # Google cloud is not posix, for mocking purpose however we just # need a class that is a subclass of Path class MockGCSPath(PosixPath): @@ -1629,18 +1645,7 @@ def __init__(self, *pathsegments): def __str__(self): # __fspath__ calls __str__ by default return f"gs://{super().__str__()}" - for path, path_type in test_data: - # https://stackoverflow.com/questions/8357098/how-can-i-check-if-a-url-is-absolute-using-python - isnet = path_type == "NETWORK" - - # test the path itself, and the corresponding Path(path) - assert ( - _is_network_fs_path(path) == isnet - ), f"Path {path} should have isnet={isnet} but got {_is_network_fs_path(path)}" - if not isnet: - assert not _is_network_fs_path(Path(path)) - if sys.version_info[:2] >= (3, 12) and path_type == "RELATIVE": - assert _is_network_fs_path(MockGCSPath(path)) + assert _is_network_fs_path(MockGCSPath(path)) @example_if( @@ -1906,13 +1911,6 @@ def test_when_all_custom_paths_no_layout_indexed( spy.assert_not_called() -@st.composite -def scheme_bids_component_strategy(draw, **kwargs): - root = draw(sb_st.schemes()) - bids_path_strategy = sb_st.bids_components(root=root, **kwargs) - return draw(bids_path_strategy) - - class TestParseBidsPath: @given( component=sb_st.bids_components(max_values=1, restrict_patterns=True),