Source code for graphviz.sources

"""Save DOT code objects, render with Graphviz dot, and open in viewer."""

import locale
import logging
import os
import typing

from .encoding import DEFAULT_ENCODING
from . import _tools
from . import saving
from . import jupyter_integration
from . import piping
from . import rendering
from . import unflattening

__all__ = ['Source']


log = logging.getLogger(__name__)


[docs]class Source(rendering.Render, saving.Save, jupyter_integration.JupyterIntegration, piping.Pipe, unflattening.Unflatten): """Verbatim DOT source code string to be rendered by Graphviz. Args: source: The verbatim DOT source code string. filename: Filename for saving the source (defaults to ``'Source.gv'``). directory: (Sub)directory for source saving and rendering. format: Rendering output format (``'pdf'``, ``'png'``, ...). engine: Layout engine used (``'dot'``, ``'neato'``, ...). encoding: Encoding for saving the source. Note: All parameters except ``source`` are optional. All of them can be changed under their corresponding attribute name after instance creation. """
[docs] @classmethod @_tools.deprecate_positional_args(supported_number=2) def from_file(cls, filename: typing.Union[os.PathLike, str], directory: typing.Union[os.PathLike, str, None] = None, format: typing.Optional[str] = None, engine: typing.Optional[str] = None, encoding: typing.Optional[str] = DEFAULT_ENCODING, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None) -> 'Source': """Return an instance with the source string read from the given file. Args: filename: Filename for loading/saving the source. directory: (Sub)directory for source loading/saving and rendering. format: Rendering output format (``'pdf'``, ``'png'``, ...). engine: Layout command used (``'dot'``, ``'neato'``, ...). encoding: Encoding for loading/saving the source. """ directory = _tools.promote_pathlike_directory(directory) filepath = (os.path.join(directory, filename) if directory.parts else os.fspath(filename)) if encoding is None: encoding = locale.getpreferredencoding() log.debug('read %r with encoding %r', filepath, encoding) with open(filepath, encoding=encoding) as fd: source = fd.read() return cls(source, filename=filename, directory=directory, format=format, engine=engine, encoding=encoding, renderer=renderer, formatter=formatter, loaded_from_path=filepath)
@_tools.deprecate_positional_args(supported_number=2) def __init__(self, source: str, filename: typing.Union[os.PathLike, str, None] = None, directory: typing.Union[os.PathLike, str, None] = None, format: typing.Optional[str] = None, engine: typing.Optional[str] = None, encoding: typing.Optional[str] = DEFAULT_ENCODING, *, renderer: typing.Optional[str] = None, formatter: typing.Optional[str] = None, loaded_from_path: typing.Optional[os.PathLike] = None) -> None: super().__init__(filename=filename, directory=directory, format=format, engine=engine, renderer=renderer, formatter=formatter, encoding=encoding) self._loaded_from_path = loaded_from_path self._source = source # work around pytype false alarm _source: str _loaded_from_path: typing.Optional[os.PathLike] def _copy_kwargs(self, **kwargs): """Return the kwargs to create a copy of the instance.""" return super()._copy_kwargs(source=self._source, loaded_from_path=self._loaded_from_path, **kwargs)
[docs] def __iter__(self) -> typing.Iterator[str]: r"""Yield the DOT source code read from file line by line. Yields: Line ending with a newline (``'\n'``). """ lines = self._source.splitlines(keepends=True) yield from lines[:-1] for line in lines[-1:]: suffix = '\n' if not line.endswith('\n') else '' yield line + suffix
@property def source(self) -> str: """The DOT source code as string. Normalizes so that the string always ends in a final newline. """ source = self._source if not source.endswith('\n'): source += '\n' return source
[docs] @_tools.deprecate_positional_args(supported_number=2) def save(self, filename: typing.Union[os.PathLike, str, None] = None, directory: typing.Union[os.PathLike, str, None] = None, *, skip_existing: typing.Optional[bool] = None) -> str: """Save the DOT source to file. Ensure the file ends with a newline. Args: filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``) directory: (Sub)directory for source saving and rendering. skip_existing: Skip write if file exists (default: ``None``). By default skips if instance was loaded from the target path: ``.from_file(self.filepath)``. Returns: The (possibly relative) path of the saved source file. """ skip = (skip_existing is None and self._loaded_from_path and os.path.samefile(self._loaded_from_path, self.filepath)) if skip: log.debug('.save(skip_existing=None) skip writing Source.from_file(%r)', self.filepath) return super().save(filename=filename, directory=directory, skip_existing=skip)