Exception on parsing dot file
See original GitHub issueWith structure.dot
as
digraph qnet {
rankdir="TB";
graph [pad="0", ranksep="0.25", nodesep="0.25"];
node [penwidth=0.5, height=0.25, color=black, shape=box, fontsize=10, fontname="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans"];
edge [penwidth=0.5, arrowsize=0.5];
compound=true;
subgraph cluster_qnet {
fontname="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans";
fontsize=12;
label="qnet";
tooltip="qnet";
subgraph cluster_algebra {
toolbox [target="_top"; tooltip="qnet.algebra.toolbox", href="../API/qnet.algebra.toolbox.html"];
library [target="_top"; tooltip="qnet.algebra.library", href="../API/qnet.algebra.library.html"];
core [target="_top"; tooltip="qnet.algebra.core", href="../API/qnet.algebra.core.html"; width=1.3];
pattern_matching [target="_top"; tooltip="qnet.algebra.pattern_matching", href="../API/qnet.algebra.pattern_matching.html"; width=1.3];
toolbox -> library;
toolbox -> core;
toolbox -> pattern_matching;
library -> core;
core -> pattern_matching [weight=0, minlen=0];
library -> pattern_matching;
href = "../API/qnet.algebra.html"; target="_top";
label="algebra";
tooltip="qnet.algebra";
graph[style=filled; fillcolor="#EEEEEE"];
}
convert [target="_top"; tooltip="qnet.convert", href="../API/qnet.convert.html"];
visualization [target="_top"; tooltip="qnet.visualization", href="../API/qnet.visualization.html"];
printing [target="_top"; tooltip="qnet.printing", href="../API/qnet.printing.html"];
utils [target="_top"; tooltip="qnet.utils", href="../API/qnet.utils.html"; width=1];
{ rank=same; convert[width=0.8]; visualization[width=0.8]; printing[width=0.8]; }
convert -> visualization [minlen=3, style=invis];
visualization -> printing [minlen=3];
visualization -> toolbox [minlen=2, lhead=cluster_algebra];
printing -> toolbox [lhead=cluster_algebra];
convert -> toolbox [lhead=cluster_algebra];
core -> utils [ltail=cluster_algebra];
pattern_matching -> utils [ltail=cluster_algebra];
convert -> utils [minlen=6];
printing -> utils [minlen=6];
}
}
I get the following exception:
>>> import pydot
>>> pydot.graph_from_dot_file("structure.dot", encoding='utf-8')
Traceback (most recent call last):
File "<ipython-input-5-d070a81b3bde>", line 1, in <module>
pydot.graph_from_dot_file("structure.dot", encoding='utf-8')
File "/Users/goerz/anaconda3/lib/python3.5/site-packages/pydot.py", line 238, in graph_from_dot_file
graphs = graph_from_dot_data(s)
File "/Users/goerz/anaconda3/lib/python3.5/site-packages/pydot.py", line 221, in graph_from_dot_data
return dot_parser.parse_dot_data(s)
File "/Users/goerz/anaconda3/lib/python3.5/site-packages/dot_parser.py", line 554, in parse_dot_data
err)
TypeError: Can't convert 'ParseException' object to str implicitly
That exception is actually raised during the handling of another Exception:
ParseException: Expected "}" (at char 351), (line:8, col:31)
It doesn’t seem to me that the file is an invalid dot file (and the graphviz command line utilities convert it fine)
Issue Analytics
- State:
- Created 5 years ago
- Comments:20 (11 by maintainers)
Top Results From Across the Web
Exception on parsing dot file · Issue #171 · pydot ... - GitHub
off-by-one errors :-) I'm particularly intrigued by the 2nd class of errors, because it is intimately linked to the architecture of program.
Read more >Stack overflow exception while reading graph .dot file with ...
I want to read graph from dot file with over 10000 nodes, but i get stack overflow exception in the same line. I...
Read more >Solved: Re: Illegal Arg Exception when parsing CSV file t ...
Im getting the file through the GetFile processor and when I view the content from the queue I notice a red dot (see...
Read more >Developers - Exception on parsing dot file - - Bountysource
Exception on parsing dot file. ... With structure.dot as digraph qnet { rankdir="TB"; graph [pad="0", ranksep="0.25", nodesep="0.25"]; node [penwidth=0.5, ...
Read more >SyntaxError: JSON.parse: bad parsing - JavaScript | MDN
The JavaScript exceptions thrown by JSON.parse() occur when string failed to be parsed as JSON.
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@ankostis: I have read up on logging and exception handling a bit and I have seen ‘some’ light. Thank you for your pointers.
I now agree with most of what you said:
However, I am still not sure about what exception to raise exactly. I still want to help that unlucky end-user that gets confronted with it. For example, you wrote:
and
Is there room for an exception to those rules here? Because I do have some idea on what to do with the error and I do think we can actually add some valuable interpretation, like:
I have drafted a basic troubleshooting guide to be included in the pydot documentation that:
I drafted it now as a new paragraph in the
README.md
, but it could also be split off to a newTROUBLESHOOTING.md
of course.The only thing I still need now, is a way to point the user to that documentation.
If we just allow the original exception to bubble up without any additional information, users with little experience reading tracebacks or otherwise unaware of pydot’s role, will not immediately look at our troubleshooting guide. They will just start searching for the term
ParseException
and end up in PyParsing’s documentation and possibly even ask for support there.Some of the ways I considered to point the user to our documentation:
Write the DEBUG log out by default: No. I chased this for a long time, but finally concluded that this approach is all wrong. I kept running into potential logging configuration conflicts with other modules. And printing by default just goes against the whole nature of the DEBUG level. (Note: I will still log a DEBUG line, but I now accept that the user may have to do some work to see it.)
Logging a log line of level ERROR before raising an exception: No. The problem with this is that the exception may get handled at a higher level and then the log line is still there (“You’re giving me two errors.”, Mario Corchero, Pycon 2019, Logging HOWTO: When to use logging).
Add an in-line comment in our code at the end of the line that may indirectly cause the exception, so that the comment will be shown as part of the traceback:
No. Drawbacks: Still not very visible in the middle of a traceback. Plus the comment would probably get deleted by another developer during the next code cleanup.
Drop the original
ParseException
and raise a new exception: No(?) It feels wrong to erase history like that. And copying over all the data is ugly and a lot of work.Try to change the original
ParseException
’s message: No(?) It feels so hacky. Example: https://stackoverflow.com/questions/9157210/how-do-i-raise-the-same-exception-with-a-custom-message-in-python/9157277#9157277Raising a new chained exception, chained to the original
ParseException
: Yes? In your previous comment, under “Are chained-exceptions over-rated?”, you showed that chained exceptions were not always necessary and pointed out that not using them avoided interrupting the stack-trace. I see your point, especially in the clean-up example you gave, but here I also want to weigh it against the benefit of being able to give the user some additional information. Can the pain of experienced users having to read two tracebacks be offset by the gain of inexperienced users saving troubleshooting time?In case we decide to use chained exceptions, the question still remains what kind of exception we should use:
Another one of the same exception (e.g. our
ParseException
chained to the originalParseException
): No. Drawbacks: Confusing to have two of the same exceptions with two different messages. Copying all that data over is ugly, a lot of work and unnecessary duplication. Not copying the data makes the new exception non-conformant with its documented attributes.A built-in exception, such as
ValueError
, with just the message that we want to get out (chained to the original exception):CalledProcessError
): No. Because the original exception does not carry the supplied DOT-string.ParseException
): No(?) I am not sure whether the original exception will always carry the supplied DOT-string. For example, the pyparsing code here suggests that it is possible that the attribute that normally holds the string is left empty.A custom exception (chained to the original): Yes? I hope we are not going to copy over any data from the original exception, now that it is chained to ours. Also, if we use unique names for our custom exceptions, we might not even need to literally point to our documentation, as a web search for that unique name would already point to pydot. An added benefit of using custom exceptions for both PR 218 and PR 219 is that we can offer a unified way of accessing the DOT-string. The question that then naturally comes up is whether we should create:
With those last two options, I run into even bigger questions, like: Why are
pydot.py
(from where Graphviz is called) anddot_parser.py
(from where PyParsing is called) separate? Does sharing an exception or exception hierarchy between the two fit in with that? Should we define the exception hierarchy in a separate file? What to do with the existing exceptions, which have some problems already? Naming issues, etc. etc. My time is limited, so I hope to prevent such scope creep. Maybe we can avoid that discussion for a while now and start with the first option, i.e. create two custom exceptions for PR 218 and PR 219, completely independent from each other. We can use identical names for identical attributes, such as the DOT-string, so that won’t stand in the way of any later integration in an exception hierarchy. We could add a comment near each of the exception class definitions to make future developers aware of this. Further integration can be discussed later then, possibly in a separate PR.To conclude the above list: I am currently thinking of a custom exception class with our own message and carrying only our additional attributes, chained to the original exception which has its own attributes. And for now no integration between the custom exception classes needed for PR 218 and PR 219.
About the contents of our message, I think it can contain a short interpretation from our side (e.g.
Supplied string cannot be parsed as DOT
or, more presumptuous,Invalid DOT-string
orDOT-syntax error
) and a pointer to our documentation (e.g.See pydot documentation for help.
).Example using
ValueError
:Now the error ends in a clear message pointing to the pydot documentation.
Alternatively, using a custom exception with a unique name that should help users find pydot documentation by themselves:
This last one is my current preference. It inherits from
ValueError
, but adds a custom attributedot_string
containing the string our function received and was also sent to pyparsing.I hope you can let me know what you think.
Replying to your other comments:
Ok, I will assume Python 3.5+ in pydot/pydot#218 and pydot/pydot#219 from now on (next force-push). For Python 2, I created a very targeted bug fix for inclusion in a bug fix/point release in pydot/pydot#227.
This is relevant only to PR pydot/pydot#218, I think. I had a quick look and I will look at it in more detail later and see what I can re-use, but I can already tell you that I don’t really feel comfortable monkey-patching the standard library, for the usual risks involved. I would rather use a custom exception, I think. Also, I have come to accept that not all of our additional data needs to be part of the exception string, so maybe there is no need to override
__str__
.Ok, I still need to look at testing. I will come back on this if I have any questions later.
Yes, that should do the trick in many cases. I have put this in the proposed documentation as the first method to try when someone wants to see the DEBUG lines. But there are two possible downsides:
basicConfig
will not have any effect. It will not add a handler, nor will it even change the level of the root logger anymore.level=
argument here sets the level of the root logger toDEBUG
(not its handler, btw, whichbasicConfig
sets up to handle everything anyway). A root logger of levelDEBUG
results in allNOTSET
loggers from all modules to inheritDEBUG
as well. This could make the log quite noisy.Therefore, I plan to document the following alternative as well:
Notes:
logging_tree
package):Finally, two other issues that were brought up earlier in the context of PR 218 and/or PR 219 and on which my thoughts have started shifting:
@ankostis In my PR https://github.com/pydot/pydot/pull/219 I also worked on this part of the code. I will reply to your suggestions here:
First, replying to https://github.com/pydot/pydot/issues/171#issuecomment-612397349:
This is an alternative solution for the
TypeError
error. In my solution, I changederr
tostr(err)
. I tested your solution, both in Python 2.7.16 and 3.7.3, and it also works: No moreTypeError
:I am still missing the newlines though. Aren’t you? Perhaps some Linux/Windows difference? See https://github.com/pydot/pydot/pull/219/files#diff-baef597193866f900a2726a8a4667b12R556 for my solution which also adds newlines.
Second, replying to https://github.com/pydot/pydot/issues/171#issuecomment-612397742:
and to https://github.com/pydot/pydot/issues/171#ref-commit-3efd534 which points to your monkey patch in the pydot-using software https://github.com/pygraphkit/graphtik/commit/3efd534f77c77044d75b59609ff1a7b335200f1c which patches out the pydot
try... except
block altogether:Letting the exception bubble-up sounds reasonable, but perhaps this can also be accomplished by adding a
raise
statement at the end of theexcept
-block, perhaps even doing exception chaining? I guess this means that we cannotreturn None
anymore then. Perhaps some users are currently expecting and handlingNone
, so that may be too big of a change for a point release, maybe even for a minor release.Your patch also shows that you prefer pydot not to print details of the
ParseException
anymore. This touches on the general principle held by many that a library should not print anything at all. I ran into this issue elsewhere: pydot currently also prints a detailed error message when Graphvizdot
returns an error code. I commented on that in https://github.com/pydot/pydot/pull/218#issuecomment-555360553. As I argued there, although I can understand the general notion that a library should not print, the practical problem is that not every user might immediately know what to do with an exception. The printing of error details by pydot allows end users to immediately start troubleshooting their DOT graph syntax rather than first having to learn how to dig into aParseException
or having to ask the maintainer of their pydot-using main software to add such exception-handling code. It may be possible to help the end users with this by other means, for example through good exception chaining with a pydot custom exception type that would lead the end user to some good pydot documentation on how to troubleshoot. But without such an alternative in place, I don’t know if it is a good idea to remove the printing already. Perhaps use a “debug” or “verbose” flag as suggested by @prmtl in https://github.com/pydot/pydot/pull/218#issuecomment-544122160. We could make it a pydot-wide flag, so that it controls both the printing ofParseException
and Graphvizdot
error details, or two separate flags, so that users may implement their own handling step by step, one pydot component at a time. Maybe at first, we can let the flags default to True, so that default behavior remains unchanged (=printing) and it can be included in a minor release already without much risk. Then anyone who wants to handle these exceptions himself already, can start and set them to False manually. Pydot documentation can suggest downstream developers to start looking into this. Then at a later stage, once we get some idea on how well the change has been received and how else we can help end users troubleshoot exceptions, we can announce the default will change to False (=no more printing) in the next major release for example.Hope you can let me know what you think. Thanks.