Repository: pandas-dev/pandas

Issue:
read_sas catches own error
#### Code Sample, a copy-pastable example if possible

```python
import pandas as pd
pd.read_sas('/tmp/foo')
```

Output (`/tmp/foo` does not have to exist):

```python
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-10-de3f5c15fb71> in <module>
      1 import pandas as pd
----> 2 pd.read_sas('/tmp/foo')

~/.virtualenvs/pandas/lib/python3.7/site-packages/pandas/io/sas/sasreader.py in read_sas(filepath_or_buffer, format, index, encoding, chunksize, iterator)
     50             pass
     51 
---> 52     if format.lower() == 'xport':
     53         from pandas.io.sas.sas_xport import XportReader
     54         reader = XportReader(filepath_or_buffer, index=index,

AttributeError: 'NoneType' object has no attribute 'lower'
```

#### Problem description

When `format=None` the function attempts to infer the file's format by naively looking at the extension. If it is neither `'.xpt'` nor `'.sas7bdat'` then an error is raised. However, this logic is wrapped in a try/except block that catches the raised error and silently discards it:

https://github.com/pandas-dev/pandas/blob/179822a0a5b3e84c841bcb76e72a269d13cd3aad/pandas/io/sas/sasreader.py#L35-L53

The function then attempts to call `format.lower()` but `format` is still `None` so an `AttributeError` is raised.

#### Expected Output

Expected behaviour: error raised when unable to infer format. Suggest documenting that inference is done by checking the file's extension.

```python
ValueError: unable to infer format of SAS file
```

#### Output of ``pd.show_versions()``

<details>

[paste the output of ``pd.show_versions()`` here below this line]
INSTALLED VERSIONS
------------------
commit: None
python: 3.7.1.final.0
python-bits: 64
OS: Linux
OS-release: 4.19.12-arch1-1-ARCH
machine: x86_64
processor: 
byteorder: little
LC_ALL: None
LANG: en_GB.UTF-8
LOCALE: en_GB.UTF-8

pandas: 0.23.4
pytest: None
pip: 18.1
setuptools: 40.6.3
Cython: None
numpy: 1.15.4
scipy: None
pyarrow: None
xarray: None
IPython: 7.2.0
sphinx: None
patsy: None
dateutil: 2.7.5
pytz: 2018.7
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: None
matplotlib: None
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: None
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
</details>



Discussion:

Resolving patch:
diff --git a/pandas/io/sas/sasreader.py b/pandas/io/sas/sasreader.py
--- a/pandas/io/sas/sasreader.py
+++ b/pandas/io/sas/sasreader.py
@@ -16,8 +16,8 @@ def read_sas(filepath_or_buffer, format=None, index=None, encoding=None,
     filepath_or_buffer : string or file-like object
         Path to the SAS file.
     format : string {'xport', 'sas7bdat'} or None
-        If None, file format is inferred.  If 'xport' or 'sas7bdat',
-        uses the corresponding format.
+        If None, file format is inferred from file extension. If 'xport' or
+        'sas7bdat', uses the corresponding format.
     index : identifier of index column, defaults to None
         Identifier of column that should be used as index of the DataFrame.
     encoding : string, default is None
@@ -39,16 +39,13 @@ def read_sas(filepath_or_buffer, format=None, index=None, encoding=None,
         filepath_or_buffer = _stringify_path(filepath_or_buffer)
         if not isinstance(filepath_or_buffer, compat.string_types):
             raise ValueError(buffer_error_msg)
-        try:
-            fname = filepath_or_buffer.lower()
-            if fname.endswith(".xpt"):
-                format = "xport"
-            elif fname.endswith(".sas7bdat"):
-                format = "sas7bdat"
-            else:
-                raise ValueError("unable to infer format of SAS file")
-        except ValueError:
-            pass
+        fname = filepath_or_buffer.lower()
+        if fname.endswith(".xpt"):
+            format = "xport"
+        elif fname.endswith(".sas7bdat"):
+            format = "sas7bdat"
+        else:
+            raise ValueError("unable to infer format of SAS file")
 
     if format.lower() == 'xport':
         from pandas.io.sas.sas_xport import XportReader


Test patch:
diff --git a/pandas/tests/io/sas/test_sas.py b/pandas/tests/io/sas/test_sas.py
--- a/pandas/tests/io/sas/test_sas.py
+++ b/pandas/tests/io/sas/test_sas.py
@@ -3,6 +3,7 @@
 from pandas.compat import StringIO
 
 from pandas import read_sas
+import pandas.util.testing as tm
 
 
 class TestSas(object):
@@ -15,3 +16,10 @@ def test_sas_buffer_format(self):
                "name, you must specify a format string")
         with pytest.raises(ValueError, match=msg):
             read_sas(b)
+
+    def test_sas_read_no_format_or_extension(self):
+        # see gh-24548
+        msg = ("unable to infer format of SAS file")
+        with tm.ensure_clean('test_file_no_extension') as path:
+            with pytest.raises(ValueError, match=msg):
+                read_sas(path)