Repository: sunpy/sunpy

Issue:
File not found error should include the file path when trying to load Map
Currently trying to load a non-existent file using `Map` raises a very generic error message. It would be good if the file that is trying to be loaded could be listed in the error message.

Current message:

```python
Traceback (most recent call last):
  File "main.py", line 84, in <module>
    aia_map = aia_helpers.create_synoptic_map(datetime.now())
  File "/Users/dstansby/github/psp-connection/aia_helpers.py", line 99, in create_synoptic_map
    aia_synop_map = synop_reproject(aia_map, shape)
  File "/Users/dstansby/github/psp-connection/aia_helpers.py", line 82, in synop_reproject
    new_map = Map(synop_map_path)
  File "/Users/dstansby/miniconda3/lib/python3.7/site-packages/sunpy/map/map_factory.py", line 276, in __call__
    data_header_pairs, already_maps = self._parse_args(*args, **kwargs)
  File "/Users/dstansby/miniconda3/lib/python3.7/site-packages/sunpy/map/map_factory.py", line 238, in _parse_args
    raise ValueError("File not found or invalid input")
ValueError: File not found or invalid input
```


Discussion:

Resolving patch:
diff --git a/sunpy/map/map_factory.py b/sunpy/map/map_factory.py
--- a/sunpy/map/map_factory.py
+++ b/sunpy/map/map_factory.py
@@ -203,22 +203,9 @@ def _parse_args(self, *args, **kwargs):
                     data_header_pairs.append(pair)
                     i += 1    # an extra increment to account for the data-header pairing
 
-            # File system path (file or directory)
-            elif _is_path(arg):
-                path = pathlib.Path(arg).expanduser()
-                if path.is_file():
-                    pairs = self._read_file(path, **kwargs)
-                    data_header_pairs += pairs
-                elif path.is_dir():
-                    for afile in sorted(path.glob('*')):
-                        data_header_pairs += self._read_file(afile, **kwargs)
-                else:
-                    raise ValueError(f'{path} is neither a file nor a directory')
-
-            # Glob
-            elif isinstance(arg, str) and glob.glob(os.path.expanduser(arg)):
-                for afile in sorted(glob.glob(os.path.expanduser(arg))):
-                    data_header_pairs += self._read_file(afile, **kwargs)
+            # A database Entry
+            elif isinstance(arg, DatabaseEntryType):
+                data_header_pairs += self._read_file(arg.path, **kwargs)
 
             # Already a Map
             elif isinstance(arg, GenericMap):
@@ -231,12 +218,24 @@ def _parse_args(self, *args, **kwargs):
                 pairs = self._read_file(path, **kwargs)
                 data_header_pairs += pairs
 
-            # A database Entry
-            elif isinstance(arg, DatabaseEntryType):
-                data_header_pairs += self._read_file(arg.path, **kwargs)
+            # File system path (file or directory or glob)
+            elif _possibly_a_path(arg):
+                path = pathlib.Path(arg).expanduser()
+                if _is_file(path):
+                    pairs = self._read_file(path, **kwargs)
+                    data_header_pairs += pairs
+                elif _is_dir(path):
+                    for afile in sorted(path.glob('*')):
+                        data_header_pairs += self._read_file(afile, **kwargs)
+                elif glob.glob(os.path.expanduser(arg)):
+                    for afile in sorted(glob.glob(os.path.expanduser(arg))):
+                        data_header_pairs += self._read_file(afile, **kwargs)
+
+                else:
+                    raise ValueError(f'Did not find any files at {arg}')
 
             else:
-                raise ValueError("File not found or invalid input")
+                raise ValueError(f"Invalid input: {arg}")
 
             i += 1
 
@@ -349,13 +348,34 @@ def _is_url(arg):
     return True
 
 
-def _is_path(arg):
+def _possibly_a_path(arg):
+    """
+    Check if arg can be coerced into a Path object.
+    Does *not* check if the path exists.
+    """
+    try:
+        is_path = pathlib.Path(arg)
+        return True
+    except Exception:
+        return False
+
+
+# In python<3.8 paths with un-representable chars (ie. '*' on windows)
+# raise an error, so make our own version that returns False instead of
+# erroring. These can be removed when we support python >= 3.8
+# https://docs.python.org/3/library/pathlib.html#methods
+def _is_file(path):
+    try:
+        return path.is_file()
+    except Exception:
+        return False
+
+
+def _is_dir(path):
     try:
-        is_path = pathlib.Path(arg).expanduser().exists()
+        return path.is_dir()
     except Exception:
         return False
-    else:
-        return is_path
 
 
 class InvalidMapInput(ValueError):


Test patch:
diff --git a/sunpy/map/tests/test_map_factory.py b/sunpy/map/tests/test_map_factory.py
--- a/sunpy/map/tests/test_map_factory.py
+++ b/sunpy/map/tests/test_map_factory.py
@@ -141,6 +141,17 @@ def test_patterns(self):
         pair_map = sunpy.map.Map(data, header)
         assert isinstance(pair_map, sunpy.map.GenericMap)
 
+    def test_errors(self):
+        # If directory doesn't exist, make sure it's listed in the error msg
+        nonexist_dir = 'nonexist'
+        directory = pathlib.Path(filepath, nonexist_dir)
+        with pytest.raises(ValueError, match=nonexist_dir):
+            maps = sunpy.map.Map(os.fspath(directory))
+
+        with pytest.raises(ValueError, match='Invalid input: 78'):
+            # Check a random unsupported type (int) fails
+            sunpy.map.Map(78)
+
     # requires dask array to run properly
     def test_dask_array(self):
         dask_array = pytest.importorskip('dask.array')