diff --git a/snakebids/core/input_generation.py b/snakebids/core/input_generation.py index 01d69fee..8ae79287 100644 --- a/snakebids/core/input_generation.py +++ b/snakebids/core/input_generation.py @@ -412,7 +412,7 @@ def _is_network_fs_path(path: Path | str): """ # StackOverflow reference: (by Kukanani) # https://stackoverflow.com/questions/8357098/how-can-i-check-if-a-url-is-absolute-using-python - return bool(urlparse(path).netloc) # netloc="" for non-local paths + return bool(urlparse(str(path)).netloc) # netloc="" for non-local paths def _gen_bids_layout( diff --git a/snakebids/tests/strategies.py b/snakebids/tests/strategies.py index 0c837c18..c4a24fda 100644 --- a/snakebids/tests/strategies.py +++ b/snakebids/tests/strategies.py @@ -37,18 +37,31 @@ valid_entities: tuple[str, ...] = tuple(BidsConfig.load("bids").entities.keys()) path_characters = st.characters(blacklist_characters=["/", "\x00"], codec="UTF-8") +# see StackOverflow +# https://stackoverflow.com/questions/1547899/which-characters-make-a-url-invalid#1547940 +scheme_characters_str = ( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~?#[]@!$&'()*+,;=" +) +scheme_characters = st.sampled_from(scheme_characters_str) def nothing() -> Any: return st.nothing() # type: ignore +def schemes() -> st.SearchStrategy[str | None]: # generate the prefix part of a url + fixed = st.sampled_from(["C://", "c:\\", "gs://", "s3://"]) + random = st.text(scheme_characters, min_size=1, max_size=5) + return st.one_of(st.none(), random, fixed) + + def paths( *, min_segments: int = 0, max_segments: int | None = None, absolute: bool | None = None, resolve: bool = False, + prefix_st: st.SearchStrategy[str | None] = None, # for urls e.g. "gs://..." ) -> st.SearchStrategy[Path]: paths = st.lists( st.text(path_characters, min_size=1), @@ -67,6 +80,11 @@ def paths( else: result = absolute_paths | relative_paths + # append scheme prefix if argument prefix_st is provided + if prefix_st: + prefix_st = prefix_st.map(lambda s: "" if s is None else s) # string prefix + result = st.tuples(prefix_st, result).map(lambda t: t[0] + t[1]) + if resolve: return result.map(lambda p: p.resolve()) diff --git a/snakebids/tests/test_generate_inputs.py b/snakebids/tests/test_generate_inputs.py index 78f86f47..27dae7fe 100644 --- a/snakebids/tests/test_generate_inputs.py +++ b/snakebids/tests/test_generate_inputs.py @@ -1893,15 +1893,26 @@ 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)) + @given( + component=scheme_bids_component_strategy(max_values=1, restrict_patterns=True) + ) def test_splits_wildcards_from_path(self, component: BidsComponent): path = component.expand()[0] entities = [BidsEntity.normalize(e).entity for e in component.zip_lists] tpl_path, matches = _parse_bids_path(path, entities) assert tpl_path.format(**matches) == path - @given(component=sb_st.bids_components(max_values=1, restrict_patterns=True)) + @given( + component=scheme_bids_component_strategy(max_values=1, restrict_patterns=True) + ) def test_one_match_found_for_each_entity(self, component: BidsComponent): path = component.expand()[0] entities = [BidsEntity.normalize(e).entity for e in component.zip_lists] @@ -1911,7 +1922,7 @@ def test_one_match_found_for_each_entity(self, component: BidsComponent): } @given( - component=sb_st.bids_components( + component=scheme_bids_component_strategy( max_values=1, restrict_patterns=True, extra_entities=False ), entity=sb_st.bids_entity(),