Skip to content

Commit a91b5c6

Browse files
committed
move out render/viewer execution to own backend module
1 parent c6c5290 commit a91b5c6

File tree

3 files changed

+165
-116
lines changed

3 files changed

+165
-116
lines changed

README.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ it with the Graphviz installation of your system.
1212

1313
Use the ``view`` option/method to directly inspect the resulting (PDF, PNG,
1414
SVG, etc.) file with its default application. Graphs can also be rendered
15-
and displayed within `Jupyter notebooks`_ (a.k.a `IPython notebooks`_,
15+
and displayed within `Jupyter notebooks`_ (a.k.a. `IPython notebooks`_,
1616
example_).
1717

1818

graphviz/backend.py

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# backend.py - execute rendering, open files in viewer
2+
3+
import os
4+
import errno
5+
import platform
6+
import subprocess
7+
8+
__all__ = ['render', 'pipe', 'view_linux', 'view_windows', 'view_darwin']
9+
10+
ENGINES = set([ # http://www.graphviz.org/cgi-bin/man?dot
11+
'dot', 'neato', 'twopi', 'circo', 'fdp', 'sfdp', 'patchwork', 'osage',
12+
])
13+
14+
FORMATS = set([ # http://www.graphviz.org/doc/info/output.html
15+
'bmp',
16+
'canon', 'dot', 'gv', 'xdot', 'xdot1.2', 'xdot1.4',
17+
'cgimage',
18+
'cmap',
19+
'eps',
20+
'exr',
21+
'fig',
22+
'gd', 'gd2',
23+
'gif',
24+
'gtk',
25+
'ico',
26+
'imap', 'cmapx',
27+
'imap_np', 'cmapx_np',
28+
'ismap',
29+
'jp2',
30+
'jpg', 'jpeg', 'jpe',
31+
'pct', 'pict',
32+
'pdf',
33+
'pic',
34+
'plain', 'plain-ext',
35+
'png',
36+
'pov',
37+
'ps',
38+
'ps2',
39+
'psd',
40+
'sgi',
41+
'svg', 'svgz',
42+
'tga',
43+
'tif', 'tiff',
44+
'tk',
45+
'vml', 'vmlz',
46+
'vrml',
47+
'wbmp',
48+
'webp',
49+
'xlib',
50+
'x11',
51+
])
52+
53+
PLATFORM = platform.system().lower()
54+
55+
STARTUPINFO = None
56+
57+
if PLATFORM == 'windows': # pragma: no cover
58+
STARTUPINFO = subprocess.STARTUPINFO()
59+
STARTUPINFO.dwFlags |= subprocess.STARTF_USESHOWWINDOW
60+
STARTUPINFO.wShowWindow = subprocess.SW_HIDE
61+
62+
63+
def command(engine, format, filepath=None):
64+
"""Return args list for rendering using subprocess.Popen."""
65+
if engine not in ENGINES:
66+
raise ValueError('unknown engine: %r' % engine)
67+
if format not in FORMATS:
68+
raise ValueError('unknown format: %r' % format)
69+
70+
result = [engine, '-T%s' % format]
71+
if filepath is not None:
72+
result.extend(['-O', filepath])
73+
74+
return result
75+
76+
77+
def render(engine, format, filepath):
78+
"""Render file with Graphviz engine into format, return result filename.
79+
80+
Args:
81+
engine: The layout commmand used for rendering ('dot', 'neato', ...).
82+
format: The output format used for rendering ('pdf', 'png', ...).
83+
filepath: Path to the DOT source file to render.
84+
Returns:
85+
The (possibly relative) path of the rendered file.
86+
Raises:
87+
RuntimeError: If the Graphviz executable is not found.
88+
"""
89+
args = command(engine, format, filepath)
90+
rendered = '%s.%s' % (filepath, format)
91+
92+
try:
93+
proc = subprocess.Popen(args, startupinfo=STARTUPINFO)
94+
except OSError as e:
95+
if e.errno == errno.ENOENT:
96+
raise RuntimeError('failed to execute %r, '
97+
'make sure the Graphviz executables '
98+
'are on your systems\' path' % args)
99+
else: # pragma: no cover
100+
raise
101+
102+
returncode = proc.wait()
103+
104+
return rendered
105+
106+
107+
def pipe(engine, format, data):
108+
"""Return data piped through Graphviz engine into format.
109+
110+
Args:
111+
engine: The layout commmand used for rendering ('dot', 'neato', ...)
112+
format: The output format used for rendering ('pdf', 'png', ...)
113+
data: The binary (encoded) DOT source string to render.
114+
Returns:
115+
Binary (encoded) stdout of the layout command.
116+
Raises:
117+
RuntimeError: If the Graphviz executable is not found.
118+
"""
119+
args = command(engine, format)
120+
121+
try:
122+
proc = subprocess.Popen(args, stdin=subprocess.PIPE,
123+
stdout=subprocess.PIPE, startupinfo=STARTUPINFO)
124+
except OSError as e:
125+
if e.errno == errno.ENOENT:
126+
raise RuntimeError('failed to execute %r, '
127+
'make sure the Graphviz executables '
128+
'are on your systems\' path' % args)
129+
else: # pragma: no cover
130+
raise
131+
132+
outs, errs = proc.communicate(data)
133+
134+
return outs
135+
136+
137+
def view_linux(filepath):
138+
"""Open filepath in the user's preferred application (linux)."""
139+
subprocess.Popen(['xdg-open', filepath])
140+
141+
142+
def view_windows(filepath):
143+
"""Start filepath with its associated application (windows)."""
144+
os.startfile(os.path.normpath(filepath))
145+
146+
147+
def view_darwin(filepath):
148+
"""Open filepath with its default application (mac)."""
149+
subprocess.Popen(['open', filepath])

graphviz/files.py

+15-115
Original file line numberDiff line numberDiff line change
@@ -4,69 +4,14 @@
44

55
import os
66
import io
7-
import errno
87
import codecs
9-
import platform
10-
import subprocess
118

129
from ._compat import text_type
1310

14-
from . import tools
11+
from . import backend, tools
1512

1613
__all__ = ['File', 'Source']
1714

18-
FORMATS = set([ # http://www.graphviz.org/doc/info/output.html
19-
'bmp',
20-
'canon', 'dot', 'gv', 'xdot', 'xdot1.2', 'xdot1.4',
21-
'cgimage',
22-
'cmap',
23-
'eps',
24-
'exr',
25-
'fig',
26-
'gd', 'gd2',
27-
'gif',
28-
'gtk',
29-
'ico',
30-
'imap', 'cmapx',
31-
'imap_np', 'cmapx_np',
32-
'ismap',
33-
'jp2',
34-
'jpg', 'jpeg', 'jpe',
35-
'pct', 'pict',
36-
'pdf',
37-
'pic',
38-
'plain', 'plain-ext',
39-
'png',
40-
'pov',
41-
'ps',
42-
'ps2',
43-
'psd',
44-
'sgi',
45-
'svg', 'svgz',
46-
'tga',
47-
'tif', 'tiff',
48-
'tk',
49-
'vml', 'vmlz',
50-
'vrml',
51-
'wbmp',
52-
'webp',
53-
'xlib',
54-
'x11',
55-
])
56-
57-
ENGINES = set([ # http://www.graphviz.org/cgi-bin/man?dot
58-
'dot', 'neato', 'twopi', 'circo', 'fdp', 'sfdp', 'patchwork', 'osage',
59-
])
60-
61-
PLATFORM = platform.system().lower()
62-
63-
STARTUPINFO = None
64-
65-
if PLATFORM == 'windows': # pragma: no cover
66-
STARTUPINFO = subprocess.STARTUPINFO()
67-
STARTUPINFO.dwFlags |= subprocess.STARTF_USESHOWWINDOW
68-
STARTUPINFO.wShowWindow = subprocess.SW_HIDE
69-
7015

7116
class Base(object):
7217

@@ -76,25 +21,25 @@ class Base(object):
7621

7722
@property
7823
def format(self):
79-
"""The output format used for rendering ('pdf', 'png', etc.)."""
24+
"""The output format used for rendering ('pdf', 'png', ...)."""
8025
return self._format
8126

8227
@format.setter
8328
def format(self, format):
8429
format = format.lower()
85-
if format not in FORMATS:
30+
if format not in backend.FORMATS:
8631
raise ValueError('unknown format: %r' % format)
8732
self._format = format
8833

8934
@property
9035
def engine(self):
91-
"""The layout commmand used for rendering ('dot', 'neato', ...)"""
36+
"""The layout commmand used for rendering ('dot', 'neato', ...)."""
9237
return self._engine
9338

9439
@engine.setter
9540
def engine(self, engine):
9641
engine = engine.lower()
97-
if engine not in ENGINES:
42+
if engine not in backend.ENGINES:
9843
raise ValueError('unknown engine: %r' % engine)
9944
self._engine = engine
10045

@@ -116,13 +61,6 @@ class File(Base):
11661

11762
_default_extension = 'gv'
11863

119-
@staticmethod
120-
def _cmd(engine, format, filepath=None):
121-
result = [engine, '-T%s' % format]
122-
if filepath is not None:
123-
result.extend(['-O', filepath])
124-
return result
125-
12664
def __init__(self, filename=None, directory=None, format=None, engine=None, encoding=None):
12765
if filename is None:
12866
name = getattr(self, 'name', None) or self.__class__.__name__
@@ -150,27 +88,14 @@ def pipe(self, format=None):
15088
Args:
15189
format: The output format used for rendering ('pdf', 'png', etc.).
15290
Returns:
153-
Stdout of the layout command.
91+
Binary (encoded) stdout of the layout command.
15492
"""
15593
if format is None:
15694
format = self._format
15795

158-
cmd = self._cmd(self._engine, format)
159-
16096
data = text_type(self.source).encode(self._encoding)
16197

162-
try:
163-
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE,
164-
stdout=subprocess.PIPE, startupinfo=STARTUPINFO)
165-
except OSError as e:
166-
if e.errno == errno.ENOENT:
167-
raise RuntimeError('failed to execute %r, '
168-
'make sure the Graphviz executables '
169-
'are on your systems\' path' % cmd)
170-
else: # pragma: no cover
171-
raise
172-
173-
outs, errs = proc.communicate(data)
98+
outs = backend.pipe(self._engine, format, data)
17499

175100
return outs
176101

@@ -215,25 +140,11 @@ def render(self, filename=None, directory=None, view=False, cleanup=False):
215140
"""
216141
filepath = self.save(filename, directory)
217142

218-
cmd = self._cmd(self._engine, self._format, filepath)
219-
220-
try:
221-
proc = subprocess.Popen(cmd, startupinfo=STARTUPINFO)
222-
except OSError as e:
223-
if e.errno == errno.ENOENT:
224-
raise RuntimeError('failed to execute %r, '
225-
'make sure the Graphviz executables '
226-
'are on your systems\' path' % cmd)
227-
else: # pragma: no cover
228-
raise
229-
230-
returncode = proc.wait()
143+
rendered = backend.render(self._engine, self._format, filepath)
231144

232145
if cleanup:
233146
os.remove(filepath)
234147

235-
rendered = '%s.%s' % (filepath, self._format)
236-
237148
if view:
238149
self._view(rendered, self._format)
239150

@@ -252,8 +163,8 @@ def view(self):
252163
def _view(self, filepath, format):
253164
"""Start the right viewer based on file format and platform."""
254165
methods = [
255-
'_view_%s_%s' % (format, PLATFORM),
256-
'_view_%s' % PLATFORM,
166+
'_view_%s_%s' % (format, backend.PLATFORM),
167+
'_view_%s' % backend.PLATFORM,
257168
]
258169
for name in methods:
259170
method = getattr(self, name, None)
@@ -262,22 +173,11 @@ def _view(self, filepath, format):
262173
break
263174
else:
264175
raise RuntimeError('%r has no built-in viewer support for %r '
265-
'on %r platform' % (self.__class__, format, PLATFORM))
266-
267-
@staticmethod
268-
def _view_linux(filepath):
269-
"""Open filepath in the user's preferred application (linux)."""
270-
subprocess.Popen(['xdg-open', filepath])
271-
272-
@staticmethod
273-
def _view_windows(filepath):
274-
"""Start filepath with its associated application (windows)."""
275-
os.startfile(os.path.normpath(filepath))
276-
277-
@staticmethod
278-
def _view_darwin(filepath):
279-
"""Open filepath with its default application (mac)."""
280-
subprocess.Popen(['open', filepath])
176+
'on %r platform' % (self.__class__, format, backend.PLATFORM))
177+
178+
_view_linux = staticmethod(backend.view_linux)
179+
_view_windows = staticmethod(backend.view_windows)
180+
_view_darwin = staticmethod(backend.view_darwin)
281181

282182

283183
class Source(File):

0 commit comments

Comments
 (0)