mingrammer / diagrams

:art: Diagram as Code for prototyping cloud system architectures
https://diagrams.mingrammer.com
MIT License
36.88k stars 2.39k forks source link

Is it possible to save the resulting diagram in DOT format? #441

Open cldeluna opened 3 years ago

cldeluna commented 3 years ago

is it possible to save the resulting diagram in dot notation?

Basically the pyGraphviz equivalent of this: G.write("file.dot")

It would be nice to be able to keep the dot files under revision control and even "snapshot" drawings in text.

gabriel-tessier commented 3 years ago

@cldeluna

Thank you for the issue.

@mingrammer

What about using outformat, something like: with Diagram("Simple Diagram", outformat="dot"):

According to the documentation outformat support this values with png as default, we can add dot.

(png, jpg, svg, and pdf) are allowed.

according to Go-Diagrams readme it generate both.

Go-Diagrams will create a folder in the current working directory with the graphviz DOT file and any image assets.

Which one is better, generate a folder with dot file and the image(s) or only one file depend on the outformat option?

Depends on the choice and If it's ok I can work on it.

I prefer the outformat option because it will remain consistent with the current behavior.

cldeluna commented 3 years ago

Just to add another data point, I do also prefer the outformat option. But a question, would it be possible to have multiple outformats? Otherwise, Id have to do a run to generate the image (JPG, PNG,etc.) and then another one to generate the dot file...that may be why the Go-Diagrams approach does both. Alternatively I would execute and generate the dot file but then I'd have to be able to read in the dot file with diagrams and render it. Is that possible?

clayms commented 3 years ago

A Graphviz dot file is created each time you generate an output. This library then deletes the dot file leaving only the image.

I have tried to change the code that removes the dot file, but could not find the dot file after I ran my script.

See https://github.com/mingrammer/diagrams/issues/436#issuecomment-758717910

gabriel-tessier commented 3 years ago

@cldeluna I pushed a change to allow to generate .dot file and be able to output several format in one call. I hope my change cover your needs.

using grouped workers example from the website with outformat=["png", "dot"]

from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB

with Diagram("Grouped Workers", show=False, direction="TB", outformat=["png", "dot"]):
    ELB("lb") >> [EC2("worker1"),
                  EC2("worker2"),
                  EC2("worker3"),
                  EC2("worker4"),
                  EC2("worker5")] >> RDS("events")

Generating this png file:

grouped_workers

and a dot file with this content:

digraph "Grouped Workers" {
    graph [bb="0,0,677,544",
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=15,
        label="Grouped Workers",
        lheight=0.24,
        lp="338.5,12.5",
        lwidth=1.72,
        nodesep=0.60,
        pad=2.0,
        rankdir=TB,
        ranksep=0.75,
        splines=ortho
    ];
    node [fixedsize=true,
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=13,
        height=1.4,
        imagescale=true,
        label="\N",
        labelloc=b,
        shape=box,
        style=rounded,
        width=1.4
    ];
    edge [color="#7B8894"];
    "2d62583dfd8245d2accd11bc42aa604d"  [height=1.9028,
        image="/usr/src/diagrams/resources/aws/network/elastic-load-balancing.png",
        label=lb,
        pos="338.5,475.5",
        shape=none,
        width=1.4028];
    b51be24db13042aab70e2e0e777e6545    [height=1.9028,
        image="/usr/src/diagrams/resources/aws/compute/ec2.png",
        label=worker1,
        pos="50.5,284.5",
        shape=none,
        width=1.4028];
    "2d62583dfd8245d2accd11bc42aa604d" -> b51be24db13042aab70e2e0e777e6545  [dir=forward,
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=13,
        pos="e,50.5,353.23 287.67,498 205,498 50.5,498 50.5,498 50.5,498 50.5,363.23 50.5,363.23"];
    aabdd269c3f64c918195432aa9aedb51    [height=1.9028,
        image="/usr/src/diagrams/resources/aws/compute/ec2.png",
        label=worker2,
        pos="194.5,284.5",
        shape=none,
        width=1.4028];
    "2d62583dfd8245d2accd11bc42aa604d" -> aabdd269c3f64c918195432aa9aedb51  [dir=forward,
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=13,
        pos="e,194.5,353.11 287.73,452 246.27,452 194.5,452 194.5,452 194.5,452 194.5,363.11 194.5,363.11"];
    "21f9de773bdd4e4f98ce4e5a3df4a631"  [height=1.9028,
        image="/usr/src/diagrams/resources/aws/compute/ec2.png",
        label=worker3,
        pos="338.5,284.5",
        shape=none,
        width=1.4028];
    "2d62583dfd8245d2accd11bc42aa604d" -> "21f9de773bdd4e4f98ce4e5a3df4a631"    [dir=forward,
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=13,
        pos="e,338.5,353.22 338.5,406.81 338.5,406.81 338.5,363.22 338.5,363.22"];
    a8c9872205ed421083510d5d74996188    [height=1.9028,
        image="/usr/src/diagrams/resources/aws/compute/ec2.png",
        label=worker4,
        pos="482.5,284.5",
        shape=none,
        width=1.4028];
    "2d62583dfd8245d2accd11bc42aa604d" -> a8c9872205ed421083510d5d74996188  [dir=forward,
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=13,
        pos="e,482.5,353.11 389.27,452 430.73,452 482.5,452 482.5,452 482.5,452 482.5,363.11 482.5,363.11"];
    "4286f7e4faa0404e887f86aef69cd96e"  [height=1.9028,
        image="/usr/src/diagrams/resources/aws/compute/ec2.png",
        label=worker5,
        pos="626.5,284.5",
        shape=none,
        width=1.4028];
    "2d62583dfd8245d2accd11bc42aa604d" -> "4286f7e4faa0404e887f86aef69cd96e"    [dir=forward,
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=13,
        pos="e,626.5,353.23 389.33,498 472,498 626.5,498 626.5,498 626.5,498 626.5,363.23 626.5,363.23"];
    "1d6cb893592146d59ca99c297a816619"  [height=1.9028,
        image="/usr/src/diagrams/resources/aws/database/rds.png",
        label=events,
        pos="338.5,93.5",
        shape=none,
        width=1.4028];
    b51be24db13042aab70e2e0e777e6545 -> "1d6cb893592146d59ca99c297a816619"  [dir=forward,
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=13,
        pos="e,287.67,70 50.5,215.75 50.5,153.01 50.5,70 50.5,70 50.5,70 277.67,70 277.67,70"];
    aabdd269c3f64c918195432aa9aedb51 -> "1d6cb893592146d59ca99c297a816619"  [dir=forward,
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=13,
        pos="e,287.73,116 194.5,215.96 194.5,169.31 194.5,116 194.5,116 194.5,116 277.73,116 277.73,116"];
    "21f9de773bdd4e4f98ce4e5a3df4a631" -> "1d6cb893592146d59ca99c297a816619"    [dir=forward,
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=13,
        pos="e,338.5,162.22 338.5,215.81 338.5,215.81 338.5,172.22 338.5,172.22"];
    a8c9872205ed421083510d5d74996188 -> "1d6cb893592146d59ca99c297a816619"  [dir=forward,
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=13,
        pos="e,389.27,116 482.5,215.96 482.5,169.31 482.5,116 482.5,116 482.5,116 399.27,116 399.27,116"];
    "4286f7e4faa0404e887f86aef69cd96e" -> "1d6cb893592146d59ca99c297a816619"    [dir=forward,
        fontcolor="#2D3436",
        fontname="Sans-Serif",
        fontsize=13,
        pos="e,389.33,70 626.5,215.75 626.5,153.01 626.5,70 626.5,70 626.5,70 399.33,70 399.33,70"];
}
clayms commented 3 years ago

A Graphviz dot file is created each time you generate an output. This library then deletes the dot file leaving only the image.

I have tried to change the code that removes the dot file, but could not find the dot file after I ran my script.

See #436 (comment)

Actually, you don't need to change any of the code at all, just call the .dot method on the diagram object, and then the .save method on that.

Using @gabriel-tessier 's example:

from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB

with Diagram("Grouped Workers", show=False, direction="TB", ) as diag:
    ELB("lb") >> [EC2("worker1"),
                  EC2("worker2"),
                  EC2("worker3"),
                  EC2("worker4"),
                  EC2("worker5")] >> RDS("events")

diag.dot.save(filename="aws.gv")

Contents of "aws.gv"

digraph "Grouped Workers" {
    graph [fontcolor="#2D3436" fontname="Sans-Serif" fontsize=15 label="Grouped Workers" nodesep=0.60 pad=2.0 rankdir=TB ranksep=0.75 splines=ortho]
    node [fixedsize=true fontcolor="#2D3436" fontname="Sans-Serif" fontsize=13 height=1.4 imagescale=true labelloc=b shape=box style=rounded width=1.4]
    edge [color="#7B8894"]
    "9f3f6dfd35bd41779b74a6245dc0eac7" [label=lb height=1.9 image="/home/user/miniconda/envs/diagrams/lib/python3.6/site-packages/resources/aws/network/elastic-load-balancing.png" shape=none]
    "916e9529f6304413b778746af465701f" [label=worker1 height=1.9 image="/home/user/miniconda/envs/diagrams/lib/python3.6/site-packages/resources/aws/compute/ec2.png" shape=none]
    "2cdf874d396d424faa23c1bcd93174a2" [label=worker2 height=1.9 image="/home/user/miniconda/envs/diagrams/lib/python3.6/site-packages/resources/aws/compute/ec2.png" shape=none]
    "092e81b626c14826b9c9328128f096bf" [label=worker3 height=1.9 image="/home/user/miniconda/envs/diagrams/lib/python3.6/site-packages/resources/aws/compute/ec2.png" shape=none]
    a0dc07c8105b4410a5ef7cd522025fe2 [label=worker4 height=1.9 image="/home/user/miniconda/envs/diagrams/lib/python3.6/site-packages/resources/aws/compute/ec2.png" shape=none]
    e23a149f10384035914e6f7cd6bb843d [label=worker5 height=1.9 image="/home/user/miniconda/envs/diagrams/lib/python3.6/site-packages/resources/aws/compute/ec2.png" shape=none]
    "9f3f6dfd35bd41779b74a6245dc0eac7" -> "916e9529f6304413b778746af465701f" [dir=forward fontcolor="#2D3436" fontname="Sans-Serif" fontsize=13]
    "9f3f6dfd35bd41779b74a6245dc0eac7" -> "2cdf874d396d424faa23c1bcd93174a2" [dir=forward fontcolor="#2D3436" fontname="Sans-Serif" fontsize=13]
    "9f3f6dfd35bd41779b74a6245dc0eac7" -> "092e81b626c14826b9c9328128f096bf" [dir=forward fontcolor="#2D3436" fontname="Sans-Serif" fontsize=13]
    "9f3f6dfd35bd41779b74a6245dc0eac7" -> a0dc07c8105b4410a5ef7cd522025fe2 [dir=forward fontcolor="#2D3436" fontname="Sans-Serif" fontsize=13]
    "9f3f6dfd35bd41779b74a6245dc0eac7" -> e23a149f10384035914e6f7cd6bb843d [dir=forward fontcolor="#2D3436" fontname="Sans-Serif" fontsize=13]
    "74a2b8bde2c84310b0d551f214b45d26" [label=events height=1.9 image="/home/user/miniconda/envs/diagrams/lib/python3.6/site-packages/resources/aws/database/rds.png" shape=none]
    "916e9529f6304413b778746af465701f" -> "74a2b8bde2c84310b0d551f214b45d26" [dir=forward fontcolor="#2D3436" fontname="Sans-Serif" fontsize=13]
    "2cdf874d396d424faa23c1bcd93174a2" -> "74a2b8bde2c84310b0d551f214b45d26" [dir=forward fontcolor="#2D3436" fontname="Sans-Serif" fontsize=13]
    "092e81b626c14826b9c9328128f096bf" -> "74a2b8bde2c84310b0d551f214b45d26" [dir=forward fontcolor="#2D3436" fontname="Sans-Serif" fontsize=13]
    a0dc07c8105b4410a5ef7cd522025fe2 -> "74a2b8bde2c84310b0d551f214b45d26" [dir=forward fontcolor="#2D3436" fontname="Sans-Serif" fontsize=13]
    e23a149f10384035914e6f7cd6bb843d -> "74a2b8bde2c84310b0d551f214b45d26" [dir=forward fontcolor="#2D3436" fontname="Sans-Serif" fontsize=13]
}
cldeluna commented 3 years ago

@gabriel-tessier - thank you for the update to allow saving the dot file. when I change my code to use that new feature with Diagram( f"\n{site_name}\nCurrent Topology", filename=drawing_fp, outformat=["jpg", "dot"], show=False, direction=direction, graph_attr=graph_attr, ):

it looks like an additional update is needed to deal with the list:

Traceback (most recent call last): File "root_diagram.py", line 1671, in main() File "root_diagram.py", line 1613, in main main_processing(vars(arguments)) File "root_diagram.py", line 1223, in main_processing with Diagram( File "/Users/claudia/vEnvs/net_refresh/lib/python3.8/site-packages/diagrams/init.py", line 130, in init if not self._validate_outformat(outformat): File "/Users/claudia/vEnvs/net_refresh/lib/python3.8/site-packages/diagrams/init.py", line 172, in _validate_outformat outformat = outformat.lower() AttributeError: 'list' object has no attribute 'lower' (net_refresh) claudia@Claudias-iMac network_refresh %

I then tried @clayms suggestion which actually does generate the dot file but does error out: In this case the outformat goes back to a single string and I add the "as diag": with Diagram( f"\n{site_name}\nCurrent Topology", filename=drawing_fp, outformat="jpg", show=False, direction=direction, graph_attr=graph_attr, ) as diag:

I then add:
`        diag.dot.save(filename="test.dot")`

Error:

Traceback (most recent call last): File "root_diagram.py", line 1671, in main() File "root_diagram.py", line 1613, in main main_processing(vars(arguments)) File "root_diagram.py", line 1567, in main_processing diag.dot.save(filename="test.dot") File "/Users/claudia/vEnvs/net_refresh/lib/python3.8/site-packages/diagrams/init.py", line 151, in exit os.remove(self.filename) FileNotFoundError: [Errno 2] No such file or directory: '/some_dir/Current_Topology'

The dot file is generated but in trying to remove the file path I set in drawing_fp it errors out as there is no such file.

Before the updates, where drawing_fp = 'Current_Topology" and where outformat="jpg", i get a file "Current_Topology.jpg" in the desired directory. It looks like the code is trying to remove the file "Current_Topology.jpg" (which I need to keep) but since my variable drawing_fp == 'Current_Topology' it is not found.

A bit more testing.

if I pass diag.dot.save(filename=drawing_fp) ...essentially the filepath attribute from the Drawing class, it does not error out but I don't get the .dot file either.

with this I get two files diag.dot.save(filename=f"{drawing_fp}.dot")

I get a Current_Topology.dot.jpg drawing and a Current_Topology.dot file which looks to be a valid dot file but it errors out on the os.remove(self.filename)

Let me know what other information I can provide!

gabriel-tessier commented 3 years ago

@cldeluna if you use the latest version the error is "correct" as the PR is not yet merged. You can follow the last changes here: https://github.com/mingrammer/diagrams/pull/446

cldeluna commented 3 years ago

Thank you @gabriel-tessier, I had not realized that. FWIW I like the option of passing outformat a list. Apart from saving the dot file it could have broader uses...if I wanted to generate a PNG and JPG for the same diagram or JPG and SVG. It all works the same and the specifics of generating the requested format are done "behind the scenes". I'll keep an eye out on #446 and use @clayms workaround in the meantime.