Repository: Qiskit/qiskit

Issue:
dag_drawer should check the existence of filename extension
### Information

- **Qiskit Terra version**: 0.10.0.dev0+831d942
- **Python version**: 3.7
- **Operating system**: Mac

### What is the current behavior?

If a filename without extension is passed to the function `dag_drawer`, this [line](https://github.com/Qiskit/qiskit-terra/blob/d090eca91dc1afdb68f563885c4ccf13b31de20e/qiskit/visualization/dag_visualization.py#L91) reports two errors:
```
nxpd.pydot.InvocationException: Program terminated with status: 1. stderr follows: Format: "XXXXX" not recognized. Use one of: ......
During handling of the above exception, another exception occurred:
qiskit.visualization.exceptions.VisualizationError: 'dag_drawer requires GraphViz installed in the system. Check https://www.graphviz.org/download/ for details on how to install GraphViz in your system.'
```

This is confusing because the second error thrown by Qiskit is not the cause of the problem.

### Steps to reproduce the problem

Try `dag_drawer(dag, filename='abc')`

### What is the expected behavior? 

Make the error catching better.

### Suggested solutions

We could either catch this error by reading and filtering the error message, or we could check the existence of the filename's extension, and provide a default one.



Discussion:
Hi @we-taper , thanks for reporting, and I agree the conflicting error messages are confusing. It looks like the error catching around https://github.com/Qiskit/qiskit-terra/blob/d090eca91dc1afdb68f563885c4ccf13b31de20e/qiskit/visualization/dag_visualization.py#L91 could be refined to either inspect the raised `nxpd.pydot.InvocationException` to try to determine the cause of the error, or at least include the generated `stderr` in the `VisualizationError` that's raised by qiskit.
Hi @kdk, I see you started working on this problem a couple of days ago... I was wondering if you're still on it or if I could try my hand at fixing this bug as well :)
I can have a look!
Newcomer here, recently started learning QisKit. Would not mind taking a crack at this.
go for it @Rajan117! I've assigned to you, let us know if you have any questions
Thanks.
I am using this code to reproduce the error. 
`from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.dagcircuit import DAGCircuit
from qiskit.converters import circuit_to_dag
from qiskit.visualization import dag_drawer

q = QuantumRegister(3, 'q')
c = ClassicalRegister(3, 'c')
circ = QuantumCircuit(q, c)
circ.h(q[0])
circ.cx(q[0], q[1])
circ.measure(q[0], c[0])
circ.rz(0.5, q[1]).c_if(c, 2)

dag = circuit_to_dag(circ)
dag_drawer(dag, filename='abc')`

But I instead get this error.

![image](https://user-images.githubusercontent.com/77441871/125172397-6d0d9600-e1b1-11eb-8982-0e057483d852.png)

I assume I have done something wrong in setting up my test environment but unsure how, followed the official QisKit YouTube guide.
I will be honest, I have had a lot of issues in setting up test environments to attempt fixing issues. Is there an easy way to do it?
> I will be honest, I have had a lot of issues in setting up test environments to attempt fixing issues. Is there an easy way to do it?

It's best to follow the steps outlined [here](https://qiskit.org/documentation/contributing_to_qiskit.html#installing-terra-from-source) for the best way to set up your environment (including `pip install -e .`)
Thanks! Will give this another go over the weekend then :)
unassigning due to lack of activity.
```
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.dagcircuit import DAGCircuit
from qiskit.converters import circuit_to_dag
from qiskit.visualization import dag_drawer

q = QuantumRegister(3, 'q')
c = ClassicalRegister(3, 'c')
circ = QuantumCircuit(q, c)
circ.h(q[0])
circ.cx(q[0], q[1])
circ.measure(q[0], c[0])
circ.rz(0.5, q[1]).c_if(c, 2)

dag = circuit_to_dag(circ)
dag_drawer(dag, filename='abc')
```


```
"dot" with args ['-Tabc', '/tmp/tmp9ngv_7rl'] returned code: 1

stdout, stderr:
 b''
b'Format: "abc" not recognized. Use one of: bmp canon cmap cmapx cmapx_np dot dot_json eps fig gd gd2 gif gv ico imap imap_np ismap jpe jpeg jpg json json0 mp pdf pic plain plain-ext png pov ps ps2 svg svgz tif tiff tk vdx vml vmlz vrml wbmp webp xdot xdot1.2 xdot1.4 xdot_json\n'

Traceback (most recent call last):
  File "/tmp/ipykernel_78/1404195018.py", line 15, in <module>
    dag_drawer(dag, filename='abc')
  File "/opt/conda/lib/python3.8/site-packages/qiskit/visualization/dag_visualization.py", line 170, in dag_drawer
    dot.write(filename, format=extension)
  File "/opt/conda/lib/python3.8/site-packages/pydot.py", line 1828, in write
    s = self.create(prog, format, encoding=encoding)
  File "/opt/conda/lib/python3.8/site-packages/pydot.py", line 1956, in create
    assert process.returncode == 0, (
AssertionError: "dot" with args ['-Tabc', '/tmp/tmp9ngv_7rl'] returned code: 1

Use %tb to get the full traceback.
```

I think the message is pretty clear now.

thanks for checking if this is still valid @iuliazidaru! However I think the error is still not very user friendly and it would be good if we could catch it better in the `dag_drawer`
@javabster Please assign the issue to me.

Resolving patch:
diff --git a/qiskit/exceptions.py b/qiskit/exceptions.py
--- a/qiskit/exceptions.py
+++ b/qiskit/exceptions.py
@@ -73,3 +73,7 @@ def __init__(
     def __str__(self) -> str:
         """Return the message."""
         return repr(self.message)
+
+
+class InvalidFileError(QiskitError):
+    """Raised when the file provided is not valid for the specific task."""
diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py
--- a/qiskit/visualization/dag_visualization.py
+++ b/qiskit/visualization/dag_visualization.py
@@ -21,7 +21,7 @@
 import tempfile
 
 from qiskit.dagcircuit.dagnode import DAGOpNode, DAGInNode, DAGOutNode
-from qiskit.exceptions import MissingOptionalLibraryError
+from qiskit.exceptions import MissingOptionalLibraryError, InvalidFileError
 from .exceptions import VisualizationError
 
 try:
@@ -31,6 +31,64 @@
 except ImportError:
     HAS_PIL = False
 
+FILENAME_EXTENSIONS = {
+    "bmp",
+    "canon",
+    "cgimage",
+    "cmap",
+    "cmapx",
+    "cmapx_np",
+    "dot",
+    "dot_json",
+    "eps",
+    "exr",
+    "fig",
+    "gd",
+    "gd2",
+    "gif",
+    "gv",
+    "icns",
+    "ico",
+    "imap",
+    "imap_np",
+    "ismap",
+    "jp2",
+    "jpe",
+    "jpeg",
+    "jpg",
+    "json",
+    "json0",
+    "mp",
+    "pct",
+    "pdf",
+    "pic",
+    "pict",
+    "plain",
+    "plain-ext",
+    "png",
+    "pov",
+    "ps",
+    "ps2",
+    "psd",
+    "sgi",
+    "svg",
+    "svgz",
+    "tga",
+    "tif",
+    "tiff",
+    "tk",
+    "vdx",
+    "vml",
+    "vmlz",
+    "vrml",
+    "wbmp",
+    "webp",
+    "xdot",
+    "xdot1.2",
+    "xdot1.4",
+    "xdot_json",
+}
+
 
 def dag_drawer(dag, scale=0.7, filename=None, style="color"):
     """Plot the directed acyclic graph (dag) to represent operation dependencies
@@ -59,6 +117,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style="color"):
     Raises:
         VisualizationError: when style is not recognized.
         MissingOptionalLibraryError: when pydot or pillow are not installed.
+        InvalidFileError: when filename provided is not valid
 
     Example:
         .. jupyter-execute::
@@ -166,7 +225,13 @@ def edge_attr_func(edge):
     dot = pydot.graph_from_dot_data(dot_str)[0]
 
     if filename:
+        if "." not in filename:
+            raise InvalidFileError("Parameter 'filename' must be in format 'name.extension'")
         extension = filename.split(".")[-1]
+        if extension not in FILENAME_EXTENSIONS:
+            raise InvalidFileError(
+                "Filename extension must be one of: " + " ".join(FILENAME_EXTENSIONS)
+            )
         dot.write(filename, format=extension)
         return None
     elif ("ipykernel" in sys.modules) and ("spyder" not in sys.modules):


Test patch:
diff --git a/test/python/visualization/test_dag_drawer.py b/test/python/visualization/test_dag_drawer.py
--- a/test/python/visualization/test_dag_drawer.py
+++ b/test/python/visualization/test_dag_drawer.py
@@ -17,6 +17,7 @@
 from qiskit import QuantumRegister, QuantumCircuit
 from qiskit.test import QiskitTestCase
 from qiskit.tools.visualization import dag_drawer
+from qiskit.exceptions import InvalidFileError
 from qiskit.visualization.exceptions import VisualizationError
 from qiskit.converters import circuit_to_dag
 
@@ -36,6 +37,18 @@ def test_dag_drawer_invalid_style(self):
         """Test dag draw with invalid style."""
         self.assertRaises(VisualizationError, dag_drawer, self.dag, style="multicolor")
 
+    def test_dag_drawer_checks_filename_correct_format(self):
+        """filename must contain name and extension"""
+        with self.assertRaisesRegex(
+            InvalidFileError, "Parameter 'filename' must be in format 'name.extension'"
+        ):
+            dag_drawer(self.dag, filename="aaabc")
+
+    def test_dag_drawer_checks_filename_extension(self):
+        """filename must have a valid extension"""
+        with self.assertRaisesRegex(InvalidFileError, "Filename extension must be one of: .*"):
+            dag_drawer(self.dag, filename="aa.abc")
+
 
 if __name__ == "__main__":
     unittest.main(verbosity=2)