Merge branch 'mingrammer:master' into master

pull/1146/head
Rajesh MSFT 4 months ago committed by GitHub
commit b0f6a0a1e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -236,6 +236,10 @@ class TrustedAdvisor(_Management):
_icon = "trusted-advisor.png"
class UserNotifications(_Management):
_icon = "user-notifications.png"
class WellArchitectedTool(_Management):
_icon = "well-architected-tool.png"

@ -12,8 +12,8 @@ class AzureOpenAI(_Ml):
_icon = "azure-open-ai.png"
class AzureSpeedToText(_Ml):
_icon = "azure-speed-to-text.png"
class AzureSpeechService(_Ml):
_icon = "azure-speech-service.png"
class BatchAI(_Ml):

@ -0,0 +1,38 @@
import argparse
import sys
def run() -> int:
"""
Run diagrams code files in a diagrams environment.
Args:
paths: A list of paths to Python files containing diagrams code.
Returns:
The exit code.
"""
parser = argparse.ArgumentParser(
description="Run diagrams code files in a diagrams environment.",
)
parser.add_argument(
"paths",
metavar="path",
type=str,
nargs="+",
help="a Python file containing diagrams code",
)
args = parser.parse_args()
for path in args.paths:
with open(path, encoding='utf-8') as f:
exec(f.read())
return 0
def main():
sys.exit(run())
if __name__ == "__main__":
main()

@ -24,4 +24,8 @@ class Openstreetmap(_Data):
_icon = "openstreetmap.png"
class Overturemaps(_Data):
_icon = "overturemaps.png"
# Aliases

@ -1,5 +1,5 @@
# use latest python alpine image.
FROM python:3.13.2-alpine3.20
FROM python:3.13.3-alpine3.20
# install system dependencies.
RUN apk update && apk add --no-cache \

@ -7,7 +7,7 @@ title: Installation
**diagrams** uses [Graphviz](https://www.graphviz.org/) to render the diagram, so you need to [install Graphviz](https://graphviz.gitlab.io/download/) to use it.
> macOS users using [Homebrew](https://brew.sh) can install Graphviz via `brew install graphviz` . Similarly, Windows users with [Chocolatey](https://chocolatey.org) installed can run `choco install graphviz`.
> macOS users using [Homebrew](https://brew.sh) can install Graphviz via `brew install graphviz` . Similarly, Windows users with [Chocolatey](https://chocolatey.org) installed can run `choco install graphviz` or use [Winget](https://learn.microsoft.com/windows/package-manager/) via `winget install Graphviz.Graphviz -i`.
After installing Graphviz (or if you already have it), install **diagrams**:
@ -20,6 +20,9 @@ $ pipenv install diagrams
# using poetry
$ poetry add diagrams
# using uv
$ uv tool install diagrams
```
## Quick Start
@ -47,6 +50,14 @@ This generates the diagram below:
It will be saved as `web_service.png` in your working directory.
### CLI
With the `diagrams` CLI you can process one or more diagram files at once.
```shell
$ diagrams diagram1.py diagram2.py
```
## Next
See more [Examples](/docs/getting-started/examples) or see the [Guides](/docs/guides/diagram) page for more details.

@ -65,5 +65,131 @@ with Diagram(name="Advanced Web Service with On-Premises (colored)", show=False)
>> Edge(color="darkorange") \
>> aggregator
```
![advanced web service with on-premise diagram colored](/img/advanced_web_service_with_on-premise_colored.png)
![advanced web service with on-premises diagram colored](/img/advanced_web_service_with_on-premises_colored.png)
## Less Edges
As you can see on the previous graph the edges can quickly become noisy. Below are two examples to solve this problem.
One approach is to get creative with the Node class to create blank placeholders, together with named nodes within Clusters, and then only pointing to single named elements within those Clusters.
Compare the output below to the example output above .
```python
from diagrams import Cluster, Diagram, Node
from diagrams.onprem.analytics import Spark
from diagrams.onprem.compute import Server
from diagrams.onprem.database import PostgreSQL
from diagrams.onprem.inmemory import Redis
from diagrams.onprem.aggregator import Fluentd
from diagrams.onprem.monitoring import Grafana, Prometheus
from diagrams.onprem.network import Nginx
from diagrams.onprem.queue import Kafka
with Diagram("\nAdvanced Web Service with On-Premise Less edges", show=False) as diag:
ingress = Nginx("ingress")
with Cluster("Service Cluster"):
serv1 = Server("grpc1")
serv2 = Server("grpc2")
serv3 = Server("grpc3")
with Cluster(""):
blankHA = Node("", shape="plaintext", width="0", height="0")
metrics = Prometheus("metric")
metrics << Grafana("monitoring")
aggregator = Fluentd("logging")
blankHA >> aggregator >> Kafka("stream") >> Spark("analytics")
with Cluster("Database HA"):
db = PostgreSQL("users")
db - PostgreSQL("replica") << metrics
blankHA >> db
with Cluster("Sessions HA"):
sess = Redis("session")
sess - Redis("replica") << metrics
blankHA >> sess
ingress >> serv2 >> blankHA
diag
```
![advanced web service with on-premise less edges](/img/advanced_web_service_with_on-premise_less_edges.png)
## Merged Edges
Yet another option is to set the graph_attr dictionary key "concentrate" to "true".
Note the following restrictions:
1. the Edge must end at the same headport
2. This only works when the "splines" graph_attr key is set to the value "spline". It has no effect when the value was set to "ortho", which is the default for the diagrams library.
3. this will only work with the "dot" layout engine, which is the default for the diagrams library.
For more information see:
https://graphviz.gitlab.io/doc/info/attrs.html#d:concentrate
https://www.graphviz.org/pdf/dotguide.pdf Section 3.3 Concentrators
```python
from diagrams import Cluster, Diagram, Edge, Node
from diagrams.onprem.analytics import Spark
from diagrams.onprem.compute import Server
from diagrams.onprem.database import PostgreSQL
from diagrams.onprem.inmemory import Redis
from diagrams.onprem.aggregator import Fluentd
from diagrams.onprem.monitoring import Grafana, Prometheus
from diagrams.onprem.network import Nginx
from diagrams.onprem.queue import Kafka
graph_attr = {
"concentrate": "true",
"splines": "spline",
}
edge_attr = {
"minlen":"3",
}
with Diagram("\n\nAdvanced Web Service with On-Premise Merged edges", show=False,
graph_attr=graph_attr,
edge_attr=edge_attr) as diag:
ingress = Nginx("ingress")
metrics = Prometheus("metric")
metrics << Edge(minlen="0") << Grafana("monitoring")
with Cluster("Service Cluster"):
grpsrv = [
Server("grpc1"),
Server("grpc2"),
Server("grpc3")]
blank = Node("", shape="plaintext", height="0.0", width="0.0")
with Cluster("Sessions HA"):
sess = Redis("session")
sess - Redis("replica") << metrics
with Cluster("Database HA"):
db = PostgreSQL("users")
db - PostgreSQL("replica") << metrics
aggregator = Fluentd("logging")
aggregator >> Kafka("stream") >> Spark("analytics")
ingress >> [grpsrv[0], grpsrv[1], grpsrv[2],]
[grpsrv[0], grpsrv[1], grpsrv[2],] - Edge(headport="w", minlen="1") - blank
blank >> Edge(headport="w", minlen="2") >> [sess, db, aggregator]
diag
```
![advanced web service with on-premise merged edges](/img/advanced_web_service_with_on-premise_merged_edges.png)

@ -1001,6 +1001,9 @@ Node classes list of the aws provider.
<img width="30" src="/img/resources/aws/management/trusted-advisor.png" alt="TrustedAdvisor" style="float: left; padding-right: 5px;" >
**diagrams.aws.management.TrustedAdvisor**
<img width="30" src="/img/resources/aws/management/user-notifications.png" alt="UserNotifications" style="float: left; padding-right: 5px;" >
**diagrams.aws.management.UserNotifications**
<img width="30" src="/img/resources/aws/management/well-architected-tool.png" alt="WellArchitectedTool" style="float: left; padding-right: 5px;" >
**diagrams.aws.management.WellArchitectedTool**

@ -491,8 +491,8 @@ Node classes list of the azure provider.
<img width="30" src="/img/resources/azure/ml/azure-open-ai.png" alt="AzureOpenAI" style="float: left; padding-right: 5px;" >
**diagrams.azure.ml.AzureOpenAI**
<img width="30" src="/img/resources/azure/ml/azure-speed-to-text.png" alt="AzureSpeedToText" style="float: left; padding-right: 5px;" >
**diagrams.azure.ml.AzureSpeedToText**
<img width="30" src="/img/resources/azure/ml/azure-speech-service.png" alt="AzureSpeechService" style="float: left; padding-right: 5px;" >
**diagrams.azure.ml.AzureSpeechService**
<img width="30" src="/img/resources/azure/ml/batch-ai.png" alt="BatchAI" style="float: left; padding-right: 5px;" >
**diagrams.azure.ml.BatchAI**

@ -41,6 +41,9 @@ Node classes list of the gis provider.
<img width="30" src="/img/resources/gis/data/openstreetmap.png" alt="Openstreetmap" style="float: left; padding-right: 5px;" >
**diagrams.gis.data.Openstreetmap**
<img width="30" src="/img/resources/gis/data/overturemaps.png" alt="Overturemaps" style="float: left; padding-right: 5px;" >
**diagrams.gis.data.Overturemaps**
## gis.database

@ -9,6 +9,9 @@ homepage = "https://diagrams.mingrammer.com"
repository = "https://github.com/mingrammer/diagrams"
include = ["resources/**/*"]
[tool.poetry.scripts]
diagrams="diagrams.cli:main"
[tool.poetry.dependencies]
python = "^3.9"
graphviz = ">=0.13.2,<0.21.0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@ -0,0 +1,95 @@
import os
import unittest
from io import StringIO
from unittest.mock import mock_open, patch
from diagrams.cli import run
class CliTest(unittest.TestCase):
def setUp(self):
self.test_file = "test_diagram.py"
# dummy content for the test file
self.test_content_1 = """
from diagrams import Diagram
with Diagram(name="Test", show=False):
pass
"""
# content from getting started examples with utf-8
# only support the installed fonts defined in Dockerfile
self.test_content_2 = """
from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB
with Diagram("test_2", show=False, direction="TB"):
ELB("lb") >> [EC2("ワーカー1"),
EC2("작업자 2를"),
EC2("робітник 3"),
EC2("worker4"),
EC2("työntekijä 4")] >> RDS("events")
"""
def tearDown(self):
try:
os.remove("test.png")
except FileNotFoundError:
pass
def test_run_with_valid_file(self):
# write the test file
with open(self.test_file, "w") as f:
f.write(self.test_content_1)
with patch("sys.argv", ["diagrams", self.test_file]):
exit_code = run()
self.assertEqual(exit_code, 0)
try:
os.remove(self.test_file)
except FileNotFoundError:
pass
def test_run_with_multiple_files(self):
multiple_files = ["file1.py", "file2.py"]
# write the code files
with open("file1.py", "w") as f:
f.write(self.test_content_1)
with open("file2.py", "w") as f:
f.write(self.test_content_2)
with patch("sys.argv", ["diagrams"] + multiple_files):
exit_code = run()
self.assertEqual(exit_code, 0)
# cleanup code file
for one_file in multiple_files:
try:
os.remove(one_file)
except FileNotFoundError:
pass
# cleanup generated image
try:
os.remove("test_2.png")
except FileNotFoundError:
pass
def test_run_with_no_arguments(self):
with patch("sys.argv", ["diagrams"]):
with patch("sys.stderr", new=StringIO()) as fake_stderr:
with self.assertRaises(SystemExit):
run()
self.assertIn("the following arguments are required: path", fake_stderr.getvalue())
def test_run_with_nonexistent_file(self):
with patch("sys.argv", ["diagrams", "nonexistent.py"]):
with self.assertRaises(FileNotFoundError):
run()
def test_run_with_invalid_python_code(self):
invalid_content = "this is not valid python code"
with patch("builtins.open", mock_open(read_data=invalid_content)):
with patch("sys.argv", ["diagrams", self.test_file]):
with self.assertRaises(SyntaxError):
run()

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Loading…
Cancel
Save