Repository: Lightning-Universe/lightning-flash

Issue:
`flash.DataModule` can't load files with a pair of square brackets in the filename
## 🐛 Bug

`flash.DataModule` can't load files with a pair of square brackets in the filename, like `sa_Yuzu Pop A [M] Extra Light_dakufalse.jpeg`. Attempting to do so fails with an error:
```
Traceback (most recent call last):
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/call.py", line 38, in _call_and_handle_interrupt
    return trainer_fn(*args, **kwargs)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/trainer.py", line 645, in _fit_impl
    self._run(model, ckpt_path=self.ckpt_path)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/trainer.py", line 1098, in _run
    results = self._run_stage()
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/trainer.py", line 1177, in _run_stage
    self._run_train()
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/trainer.py", line 1200, in _run_train
    self.fit_loop.run()
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/loops/loop.py", line 199, in run
    self.advance(*args, **kwargs)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/loops/fit_loop.py", line 267, in advance
    self._outputs = self.epoch_loop.run(self._data_fetcher)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/loops/loop.py", line 199, in run
    self.advance(*args, **kwargs)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/loops/epoch/training_epoch_loop.py", line 188, in advance
    batch = next(data_fetcher)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/utilities/fetching.py", line 184, in __next__
    return self.fetching_function()
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/utilities/fetching.py", line 265, in fetching_function
    self._fetch_next_batch(self.dataloader_iter)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/utilities/fetching.py", line 280, in _fetch_next_batch
    batch = next(iterator)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/supporters.py", line 568, in __next__
    return self.request_next_batch(self.loader_iters)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/supporters.py", line 580, in request_next_batch
    return apply_to_collection(loader_iters, Iterator, next)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/lightning_utilities/core/apply_func.py", line 47, in apply_to_collection
    return function(data, *args, **kwargs)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/torch/utils/data/dataloader.py", line 628, in __next__
    data = self._next_data()
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/torch/utils/data/dataloader.py", line 671, in _next_data
    data = self._dataset_fetcher.fetch(index)  # may raise StopIteration
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/torch/utils/data/_utils/fetch.py", line 58, in fetch
    data = [self.dataset[idx] for idx in possibly_batched_index]
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/torch/utils/data/_utils/fetch.py", line 58, in <listcomp>
    data = [self.dataset[idx] for idx in possibly_batched_index]
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/core/data/io/input.py", line 294, in __getitem__
    return self._call_load_sample(self.data[index])
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/core/data/io/input.py", line 180, in _call_load_sample
    sample_output = getattr(self, f"{_STAGES_PREFIX[self.running_stage]}_load_sample")(_deepcopy_dict(sample))
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/core/data/io/input.py", line 257, in train_load_sample
    return self.load_sample(sample)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/image/classification/input.py", line 59, in load_sample
    sample = super().load_sample(sample)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/image/data.py", line 76, in load_sample
    sample[DataKeys.INPUT] = load_image(filepath)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/core/data/utilities/loading.py", line 154, in load_image
    return load(file_path, _image_loaders)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/core/data/utilities/loading.py", line 144, in load
    with fsspec.open(file_path) as file:
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/fsspec/core.py", line 441, in open
    return open_files(
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/fsspec/core.py", line 195, in __getitem__
    out = super().__getitem__(item)
IndexError: list index out of range
```

cc @OstaptsovDanil

### To Reproduce

1. Copy the quickstart example from here: https://lightning-flash.readthedocs.io/en/stable/reference/image_classification.html
2. Rename any image in the dataset to contain ` [M] `
3. Run the example
4. See it fail with the error described above

### Expected behavior

No error.

### Environment

 - OS (e.g., Linux): macOS
 - Python version: 3.8.15
 - PyTorch/Lightning/Flash Version (e.g., 1.10/1.5/0.7): torch==1.13.1, flash==0.8.1
 - GPU models and configuration: none
 - Any other relevant information: n/a
`flash.DataModule` can't load files with a pair of square brackets in the filename
## 🐛 Bug

`flash.DataModule` can't load files with a pair of square brackets in the filename, like `sa_Yuzu Pop A [M] Extra Light_dakufalse.jpeg`. Attempting to do so fails with an error:
```
Traceback (most recent call last):
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/call.py", line 38, in _call_and_handle_interrupt
    return trainer_fn(*args, **kwargs)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/trainer.py", line 645, in _fit_impl
    self._run(model, ckpt_path=self.ckpt_path)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/trainer.py", line 1098, in _run
    results = self._run_stage()
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/trainer.py", line 1177, in _run_stage
    self._run_train()
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/trainer.py", line 1200, in _run_train
    self.fit_loop.run()
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/loops/loop.py", line 199, in run
    self.advance(*args, **kwargs)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/loops/fit_loop.py", line 267, in advance
    self._outputs = self.epoch_loop.run(self._data_fetcher)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/loops/loop.py", line 199, in run
    self.advance(*args, **kwargs)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/loops/epoch/training_epoch_loop.py", line 188, in advance
    batch = next(data_fetcher)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/utilities/fetching.py", line 184, in __next__
    return self.fetching_function()
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/utilities/fetching.py", line 265, in fetching_function
    self._fetch_next_batch(self.dataloader_iter)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/utilities/fetching.py", line 280, in _fetch_next_batch
    batch = next(iterator)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/supporters.py", line 568, in __next__
    return self.request_next_batch(self.loader_iters)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/pytorch_lightning/trainer/supporters.py", line 580, in request_next_batch
    return apply_to_collection(loader_iters, Iterator, next)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/lightning_utilities/core/apply_func.py", line 47, in apply_to_collection
    return function(data, *args, **kwargs)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/torch/utils/data/dataloader.py", line 628, in __next__
    data = self._next_data()
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/torch/utils/data/dataloader.py", line 671, in _next_data
    data = self._dataset_fetcher.fetch(index)  # may raise StopIteration
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/torch/utils/data/_utils/fetch.py", line 58, in fetch
    data = [self.dataset[idx] for idx in possibly_batched_index]
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/torch/utils/data/_utils/fetch.py", line 58, in <listcomp>
    data = [self.dataset[idx] for idx in possibly_batched_index]
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/core/data/io/input.py", line 294, in __getitem__
    return self._call_load_sample(self.data[index])
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/core/data/io/input.py", line 180, in _call_load_sample
    sample_output = getattr(self, f"{_STAGES_PREFIX[self.running_stage]}_load_sample")(_deepcopy_dict(sample))
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/core/data/io/input.py", line 257, in train_load_sample
    return self.load_sample(sample)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/image/classification/input.py", line 59, in load_sample
    sample = super().load_sample(sample)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/image/data.py", line 76, in load_sample
    sample[DataKeys.INPUT] = load_image(filepath)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/core/data/utilities/loading.py", line 154, in load_image
    return load(file_path, _image_loaders)
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/flash/core/data/utilities/loading.py", line 144, in load
    with fsspec.open(file_path) as file:
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/fsspec/core.py", line 441, in open
    return open_files(
  File "/Users/yurij/miniforge3/envs/japanese-reader/lib/python3.8/site-packages/fsspec/core.py", line 195, in __getitem__
    out = super().__getitem__(item)
IndexError: list index out of range
```

cc @OstaptsovDanil

### To Reproduce

1. Copy the quickstart example from here: https://lightning-flash.readthedocs.io/en/stable/reference/image_classification.html
2. Rename any image in the dataset to contain ` [M] `
3. Run the example
4. See it fail with the error described above

### Expected behavior

No error.

### Environment

 - OS (e.g., Linux): macOS
 - Python version: 3.8.15
 - PyTorch/Lightning/Flash Version (e.g., 1.10/1.5/0.7): torch==1.13.1, flash==0.8.1
 - GPU models and configuration: none
 - Any other relevant information: n/a


Discussion:

Resolving patch:
diff --git a/flash/core/data/utilities/loading.py b/flash/core/data/utilities/loading.py
--- a/flash/core/data/utilities/loading.py
+++ b/flash/core/data/utilities/loading.py
@@ -12,7 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 import copy
+import glob
+import re
 from functools import partial
+from os import PathLike
+from typing import Union
+from urllib.parse import parse_qs, quote, urlencode, urlparse
 
 import fsspec
 import numpy as np
@@ -139,9 +144,30 @@ def _get_loader(file_path: str, loaders):
     )
 
 
+WINDOWS_FILE_PATH_RE = re.compile("^[a-zA-Z]:(\\\\[^\\\\]|/[^/]).*")
+
+
+def is_local_path(file_path: str) -> bool:
+    if WINDOWS_FILE_PATH_RE.fullmatch(file_path):
+        return True
+    return urlparse(file_path).scheme in ["", "file"]
+
+
+def escape_url(url: str) -> str:
+    parsed = urlparse(url)
+    return f"{parsed.scheme}://{parsed.netloc}{quote(parsed.path)}?{urlencode(parse_qs(parsed.query), doseq=True)}"
+
+
+def escape_file_path(file_path: Union[str, PathLike]) -> str:
+    file_path_str = str(file_path)
+    return glob.escape(file_path_str) if is_local_path(file_path_str) else escape_url(file_path_str)
+
+
 def load(file_path: str, loaders):
     loader = _get_loader(file_path, loaders)
-    with fsspec.open(file_path) as file:
+    # escaping file_path to avoid fsspec treating the path as a glob pattern
+    # fsspec ignores `expand=False` in read mode
+    with fsspec.open(escape_file_path(file_path)) as file:
         return loader(file)
 
 


Test patch:
diff --git a/tests/core/data/utilities/test_loading.py b/tests/core/data/utilities/test_loading.py
--- a/tests/core/data/utilities/test_loading.py
+++ b/tests/core/data/utilities/test_loading.py
@@ -85,7 +85,9 @@ def write_tsv(file_path):
 @pytest.mark.parametrize(
     "extension,write",
     [(extension, write_image) for extension in IMG_EXTENSIONS]
-    + [(extension, write_numpy) for extension in NP_EXTENSIONS],
+    + [(extension, write_numpy) for extension in NP_EXTENSIONS]
+    # it shouldn't try to expand glob patterns in filenames
+    + [(filename, write_image) for filename in ("image [test].jpeg",)],
 )
 def test_load_image(tmpdir, extension, write):
     file_path = os.path.join(tmpdir, f"test{extension}")
@@ -149,6 +151,13 @@ def test_load_data_frame(tmpdir, extension, write):
             Image.Image,
             marks=pytest.mark.skipif(not _IMAGE_TESTING, reason="image libraries aren't installed."),
         ),
+        # it shouldn't try to expand glob patterns in URLs
+        pytest.param(
+            "https://pl-flash-data.s3.amazonaws.com/images/ant_1 [test].jpg",
+            load_image,
+            Image.Image,
+            marks=pytest.mark.skipif(not _IMAGE_TESTING, reason="image libraries aren't installed."),
+        ),
         pytest.param(
             "https://pl-flash-data.s3.amazonaws.com/images/ant_1.jpg",
             load_spectrogram,