diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2fd60696c..ad9237753 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -33,3 +33,5 @@ jobs:
run: |
poetry install
poetry run python -m unittest -v tests/*.py
+ - name: Run mypy
+ run: poetry run mypy
diff --git a/diagrams/__init__.py b/diagrams/__init__.py
index 6fe2a8024..0416cd593 100644
--- a/diagrams/__init__.py
+++ b/diagrams/__init__.py
@@ -1,49 +1,44 @@
-import contextvars
import os
import uuid
+from contextvars import ContextVar
from pathlib import Path
-from typing import Dict, List, Optional, Union
+from typing import Any, AnyStr, cast, Dict, List, Mapping, Optional, Tuple, Type, Union
+from types import TracebackType
-from graphviz import Digraph
+from graphviz import Digraph # type: ignore[import]
# Global contexts for a diagrams and a cluster.
#
# These global contexts are for letting the clusters and nodes know
# where context they are belong to. So the all clusters and nodes does
# not need to specify the current diagrams or cluster via parameters.
-__diagram = contextvars.ContextVar("diagrams")
-__cluster = contextvars.ContextVar("cluster")
+__diagram: ContextVar[Optional["Diagram"]] = ContextVar("diagrams")
+__cluster: ContextVar[Optional["Cluster"]] = ContextVar("cluster")
-def getdiagram() -> "Diagram":
- try:
- return __diagram.get()
- except LookupError:
- return None
+def getdiagram() -> Optional["Diagram"]:
+ return __diagram.get(None)
-def setdiagram(diagram: "Diagram"):
+def setdiagram(diagram: Optional["Diagram"]) -> None:
__diagram.set(diagram)
-def getcluster() -> "Cluster":
- try:
- return __cluster.get()
- except LookupError:
- return None
+def getcluster() -> Optional["Cluster"]:
+ return __cluster.get(None)
-def setcluster(cluster: "Cluster"):
+def setcluster(cluster: Optional["Cluster"]) -> None:
__cluster.set(cluster)
class Diagram:
- __directions = ("TB", "BT", "LR", "RL")
- __curvestyles = ("ortho", "curved")
- __outformats = ("png", "jpg", "svg", "pdf", "dot")
+ __directions: Tuple[str, ...] = ("TB", "BT", "LR", "RL")
+ __curvestyles: Tuple[str, ...] = ("ortho", "curved")
+ __outformats: Tuple[str, ...] = ("png", "jpg", "svg", "pdf", "dot")
# fmt: off
- _default_graph_attrs = {
+ _default_graph_attrs: Mapping[str, str] = {
"pad": "2.0",
"splines": "ortho",
"nodesep": "0.60",
@@ -52,7 +47,7 @@ class Diagram:
"fontsize": "15",
"fontcolor": "#2D3436",
}
- _default_node_attrs = {
+ _default_node_attrs: Mapping[str, str] = {
"shape": "box",
"style": "rounded",
"fixedsize": "true",
@@ -68,7 +63,7 @@ class Diagram:
"fontsize": "13",
"fontcolor": "#2D3436",
}
- _default_edge_attrs = {
+ _default_edge_attrs: Mapping[str, str] = {
"color": "#7B8894",
}
@@ -82,13 +77,13 @@ def __init__(
filename: str = "",
direction: str = "LR",
curvestyle: str = "ortho",
- outformat: str = "png",
+ outformat: Union[List[str], str] = "png",
autolabel: bool = False,
show: bool = True,
strict: bool = False,
- graph_attr: Optional[dict] = None,
- node_attr: Optional[dict] = None,
- edge_attr: Optional[dict] = None,
+ graph_attr: Optional[Mapping[str, Any]] = None,
+ node_attr: Optional[Mapping[str, Any]] = None,
+ edge_attr: Optional[Mapping[str, Any]] = None,
):
"""Diagram represents a global diagrams context.
@@ -116,8 +111,8 @@ def __init__(
filename = "diagrams_image"
elif not filename:
filename = "_".join(self.name.split()).lower()
- self.filename = filename
- self.dot = Digraph(self.name, filename=self.filename, strict=strict)
+ self.filename: str = filename
+ self.dot: Digraph = Digraph(self.name, filename=self.filename, strict=strict)
# Set attributes.
for k, v in self._default_graph_attrs.items():
@@ -143,7 +138,7 @@ def __init__(
else:
if not self._validate_outformat(outformat):
raise ValueError(f'"{outformat}" is not a valid output format')
- self.outformat = outformat
+ self.outformat: Union[List[str], str] = outformat
# Merge passed in attributes
self.dot.graph_attr.update(graph_attr)
@@ -156,18 +151,23 @@ def __init__(
def __str__(self) -> str:
return str(self.dot)
- def __enter__(self):
+ def __enter__(self) -> "Diagram":
setdiagram(self)
return self
- def __exit__(self, exc_type, exc_value, traceback):
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_value: Optional[BaseException],
+ traceback: Optional[TracebackType],
+ ) -> None:
self.render()
# Remove the graphviz file leaving only the image.
os.remove(self.filename)
setdiagram(None)
- def _repr_png_(self):
- return self.dot.pipe(format="png")
+ def _repr_png(self) -> AnyStr:
+ return cast(AnyStr, self.dot.pipe(format="png"))
def _validate_direction(self, direction: str) -> bool:
return direction.upper() in self.__directions
@@ -178,7 +178,7 @@ def _validate_curvestyle(self, curvestyle: str) -> bool:
def _validate_outformat(self, outformat: str) -> bool:
return outformat.lower() in self.__outformats
- def node(self, nodeid: str, label: str, **attrs) -> None:
+ def node(self, nodeid: str, label: str, **attrs: Dict[Any, Any]) -> None:
"""Create a new node."""
self.dot.node(nodeid, label=label, **attrs)
@@ -199,11 +199,11 @@ def render(self) -> None:
class Cluster:
- __directions = ("TB", "BT", "LR", "RL")
- __bgcolors = ("#E5F5FD", "#EBF3E7", "#ECE8F6", "#FDF7E3")
+ __directions: Tuple[str, ...] = ("TB", "BT", "LR", "RL")
+ __bgcolors: Tuple[str, ...] = ("#E5F5FD", "#EBF3E7", "#ECE8F6", "#FDF7E3")
# fmt: off
- _default_graph_attrs = {
+ _default_graph_attrs: Mapping[str, str] = {
"shape": "box",
"style": "rounded",
"labeljust": "l",
@@ -221,7 +221,7 @@ def __init__(
self,
label: str = "cluster",
direction: str = "LR",
- graph_attr: Optional[dict] = None,
+ graph_attr: Optional[Mapping[str, Any]] = None,
):
"""Cluster represents a cluster context.
@@ -231,10 +231,10 @@ def __init__(
"""
if graph_attr is None:
graph_attr = {}
- self.label = label
- self.name = "cluster_" + self.label
+ self.label: str = label
+ self.name: str = f"cluster_{self.label}"
- self.dot = Digraph(self.name)
+ self.dot: Digraph = Digraph(self.name)
# Set attributes.
for k, v in self._default_graph_attrs.items():
@@ -246,24 +246,30 @@ def __init__(
self.dot.graph_attr["rankdir"] = direction
# Node must be belong to a diagrams.
- self._diagram = getdiagram()
- if self._diagram is None:
+ diagram = getdiagram()
+ if diagram is None:
raise EnvironmentError("Global diagrams context not set up")
- self._parent = getcluster()
+ self._diagram: Diagram = diagram
+ self._parent: Optional["Cluster"] = getcluster()
# Set cluster depth for distinguishing the background color
- self.depth = self._parent.depth + 1 if self._parent else 0
+ self.depth: int = self._parent.depth + 1 if self._parent else 0
coloridx = self.depth % len(self.__bgcolors)
self.dot.graph_attr["bgcolor"] = self.__bgcolors[coloridx]
# Merge passed in attributes
self.dot.graph_attr.update(graph_attr)
- def __enter__(self):
+ def __enter__(self) -> "Cluster":
setcluster(self)
return self
- def __exit__(self, exc_type, exc_value, traceback):
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_value: Optional[BaseException],
+ traceback: Optional[TracebackType],
+ ) -> None:
if self._parent:
self._parent.subgraph(self.dot)
else:
@@ -273,7 +279,7 @@ def __exit__(self, exc_type, exc_value, traceback):
def _validate_direction(self, direction: str) -> bool:
return direction.upper() in self.__directions
- def node(self, nodeid: str, label: str, **attrs) -> None:
+ def node(self, nodeid: str, label: str, **attrs: Dict[Any, Any]) -> None:
"""Create a new node in the cluster."""
self.dot.node(nodeid, label=label, **attrs)
@@ -284,32 +290,33 @@ def subgraph(self, dot: Digraph) -> None:
class Node:
"""Node represents a node for a specific backend service."""
- _provider = None
- _type = None
+ _provider: Optional[str] = None
+ _type: Optional[str] = None
- _icon_dir = None
- _icon = None
+ _icon_dir: Optional[str] = None
+ _icon: Optional[str] = None
- _height = 1.9
+ _height: float = 1.9
- def __init__(self, label: str = "", *, nodeid: str = None, **attrs: Dict):
+ def __init__(self, label: str = "", *, nodeid: Optional[str] = None, **attrs: Dict[Any, Any]):
"""Node represents a system component.
:param label: Node label.
"""
# Generates an ID for identifying a node, unless specified
- self._id = nodeid or self._rand_id()
- self.label = label
+ self._id: str = nodeid or self._rand_id()
+ self.label: str = label
# Node must be belong to a diagrams.
- self._diagram = getdiagram()
- if self._diagram is None:
+ diagram = getdiagram()
+ if diagram is None:
raise EnvironmentError("Global diagrams context not set up")
+ self._diagram: Diagram = diagram
if self._diagram.autolabel:
prefix = self.__class__.__name__
if self.label:
- self.label = prefix + "\n" + self.label
+ self.label = f"{prefix}\n{self.label}"
else:
self.label = prefix
@@ -318,16 +325,17 @@ def __init__(self, label: str = "", *, nodeid: str = None, **attrs: Dict):
# that label being spanned between icon image and white space.
# Increase the height by the number of new lines included in the label.
padding = 0.4 * (self.label.count('\n'))
- self._attrs = {
+ icon = self._load_icon()
+ self._attrs: Dict[str, Any] = {
"shape": "none",
"height": str(self._height + padding),
- "image": self._load_icon(),
- } if self._icon else {}
+ "image": icon,
+ } if icon is not None else {}
# fmt: on
self._attrs.update(attrs)
- self._cluster = getcluster()
+ self._cluster: Optional[Cluster] = getcluster()
# If a node is in the cluster context, add it to cluster.
if self._cluster:
@@ -335,23 +343,22 @@ def __init__(self, label: str = "", *, nodeid: str = None, **attrs: Dict):
else:
self._diagram.node(self._id, self.label, **self._attrs)
- def __repr__(self):
- _name = self.__class__.__name__
- return f"<{self._provider}.{self._type}.{_name}>"
+ def __repr__(self) -> str:
+ name = self.__class__.__name__
+ return f"<{self._provider}.{self._type}.{name}>"
- def __sub__(self, other: Union["Node", List["Node"], "Edge"]):
+ def __sub__(self, other: Union["Node", List["Node"], "Edge"]) -> Union["Node", List["Node"], "Edge"]:
"""Implement Self - Node, Self - [Nodes] and Self - Edge."""
if isinstance(other, list):
for node in other:
self.connect(node, Edge(self))
return other
- elif isinstance(other, Node):
+ if isinstance(other, Node):
return self.connect(other, Edge(self))
- else:
- other.node = self
- return other
+ other.node = self
+ return other
- def __rsub__(self, other: Union[List["Node"], List["Edge"]]):
+ def __rsub__(self, other: Union[List["Node"], List["Edge"]]) -> "Node":
"""Called for [Nodes] and [Edges] - Self because list don't have __sub__ operators."""
for o in other:
if isinstance(o, Edge):
@@ -360,32 +367,30 @@ def __rsub__(self, other: Union[List["Node"], List["Edge"]]):
o.connect(self, Edge(self))
return self
- def __rshift__(self, other: Union["Node", List["Node"], "Edge"]):
+ def __rshift__(self, other: Union["Node", List["Node"], "Edge"]) -> Union["Node", List["Node"], "Edge"]:
"""Implements Self >> Node, Self >> [Nodes] and Self Edge."""
if isinstance(other, list):
for node in other:
self.connect(node, Edge(self, forward=True))
return other
- elif isinstance(other, Node):
+ if isinstance(other, Node):
return self.connect(other, Edge(self, forward=True))
- else:
- other.forward = True
- other.node = self
- return other
+ other.forward = True
+ other.node = self
+ return other
- def __lshift__(self, other: Union["Node", List["Node"], "Edge"]):
+ def __lshift__(self, other: Union["Node", List["Node"], "Edge"]) -> Union["Node", List["Node"], "Edge"]:
"""Implements Self << Node, Self << [Nodes] and Self << Edge."""
if isinstance(other, list):
for node in other:
self.connect(node, Edge(self, reverse=True))
return other
- elif isinstance(other, Node):
+ if isinstance(other, Node):
return self.connect(other, Edge(self, reverse=True))
- else:
- other.reverse = True
- return other.connect(self)
+ other.reverse = True
+ return other.connect(self)
- def __rrshift__(self, other: Union[List["Node"], List["Edge"]]):
+ def __rrshift__(self, other: Union[List["Node"], List["Edge"]]) -> "Node":
"""Called for [Nodes] and [Edges] >> Self because list don't have __rshift__ operators."""
for o in other:
if isinstance(o, Edge):
@@ -395,7 +400,7 @@ def __rrshift__(self, other: Union[List["Node"], List["Edge"]]):
o.connect(self, Edge(self, forward=True))
return self
- def __rlshift__(self, other: Union[List["Node"], List["Edge"]]):
+ def __rlshift__(self, other: Union[List["Node"], List["Edge"]]) -> "Node":
"""Called for [Nodes] << Self because list of Nodes don't have __lshift__ operators."""
for o in other:
if isinstance(o, Edge):
@@ -406,11 +411,11 @@ def __rlshift__(self, other: Union[List["Node"], List["Edge"]]):
return self
@property
- def nodeid(self):
+ def nodeid(self) -> str:
return self._id
# TODO: option for adding flow description to the connection edge
- def connect(self, node: "Node", edge: "Edge"):
+ def connect(self, node: "Node", edge: "Edge") -> "Node":
"""Connect to other node.
:param node: Other node instance.
@@ -426,18 +431,19 @@ def connect(self, node: "Node", edge: "Edge"):
return node
@staticmethod
- def _rand_id():
+ def _rand_id() -> str:
return uuid.uuid4().hex
- def _load_icon(self):
- basedir = Path(os.path.abspath(os.path.dirname(__file__)))
- return os.path.join(basedir.parent, self._icon_dir, self._icon)
+ def _load_icon(self) -> Optional[str]:
+ if self._icon_dir is None or self._icon is None:
+ return None
+ return str(Path(__file__).parent / self._icon_dir / self._icon)
class Edge:
"""Edge represents an edge between two nodes."""
- _default_edge_attrs = {
+ _default_edge_attrs: Mapping[str, str] = {
"fontcolor": "#2D3436",
"fontname": "Sans-Serif",
"fontsize": "13",
@@ -445,13 +451,13 @@ class Edge:
def __init__(
self,
- node: "Node" = None,
+ node: Optional["Node"] = None,
forward: bool = False,
reverse: bool = False,
label: str = "",
color: str = "",
style: str = "",
- **attrs: Dict,
+ **attrs: Dict[Any, Any],
):
"""Edge represents an edge between two nodes.
@@ -466,11 +472,11 @@ def __init__(
if node is not None:
assert isinstance(node, Node)
- self.node = node
- self.forward = forward
- self.reverse = reverse
+ self.node: Optional[Node] = node
+ self.forward: bool = forward
+ self.reverse: bool = reverse
- self._attrs = {}
+ self._attrs: Dict[str, Any] = {}
# Set attributes.
for k, v in self._default_edge_attrs.items():
@@ -486,7 +492,7 @@ def __init__(
self._attrs["style"] = style
self._attrs.update(attrs)
- def __sub__(self, other: Union["Node", "Edge", List["Node"]]):
+ def __sub__(self, other: Union["Node", "Edge", List["Node"]]) -> Union["Node", "Edge", List["Node"]]:
"""Implement Self - Node or Edge and Self - [Nodes]"""
return self.connect(other)
@@ -494,12 +500,12 @@ def __rsub__(self, other: Union[List["Node"], List["Edge"]]) -> List["Edge"]:
"""Called for [Nodes] or [Edges] - Self because list don't have __sub__ operators."""
return self.append(other)
- def __rshift__(self, other: Union["Node", "Edge", List["Node"]]):
+ def __rshift__(self, other: Union["Node", "Edge", List["Node"]]) -> Union["Node", "Edge", List["Node"]]:
"""Implements Self >> Node or Edge and Self >> [Nodes]."""
self.forward = True
return self.connect(other)
- def __lshift__(self, other: Union["Node", "Edge", List["Node"]]):
+ def __lshift__(self, other: Union["Node", "Edge", List["Node"]]) -> Union["Node", "Edge", List["Node"]]:
"""Implements Self << Node or Edge and Self << [Nodes]."""
self.reverse = True
return self.connect(other)
@@ -512,35 +518,36 @@ def __rlshift__(self, other: Union[List["Node"], List["Edge"]]) -> List["Edge"]:
"""Called for [Nodes] or [Edges] << Self because list of Edges don't have __lshift__ operators."""
return self.append(other, reverse=True)
- def append(self, other: Union[List["Node"], List["Edge"]], forward=None, reverse=None) -> List["Edge"]:
+ def append(
+ self, other: Union[List["Node"], List["Edge"]], forward: Optional[bool] = None, reverse: Optional[bool] = None
+ ) -> List["Edge"]:
result = []
for o in other:
if isinstance(o, Edge):
o.forward = forward if forward else o.forward
- o.reverse = forward if forward else o.reverse
+ o.reverse = reverse if reverse else o.reverse
self._attrs = o.attrs.copy()
result.append(o)
else:
- result.append(Edge(o, forward=forward, reverse=reverse, **self._attrs))
+ result.append(Edge(o, forward=bool(forward), reverse=bool(reverse), **self._attrs))
return result
- def connect(self, other: Union["Node", "Edge", List["Node"]]):
+ def connect(self, other: Union["Node", "Edge", List["Node"]]) -> Union["Node", "Edge", List["Node"]]:
if isinstance(other, list):
- for node in other:
- self.node.connect(node, self)
+ if self.node is not None:
+ for node in other:
+ self.node.connect(node, self)
return other
- elif isinstance(other, Edge):
+ if isinstance(other, Edge):
self._attrs = other._attrs.copy()
return self
- else:
- if self.node is not None:
- return self.node.connect(other, self)
- else:
- self.node = other
- return self
+ if self.node is not None:
+ return self.node.connect(other, self)
+ self.node = other
+ return self
@property
- def attrs(self) -> Dict:
+ def attrs(self) -> Dict[str, Any]:
if self.forward and self.reverse:
direction = "both"
elif self.forward:
@@ -552,4 +559,4 @@ def attrs(self) -> Dict:
return {**self._attrs, "dir": direction}
-Group = Cluster
+Group: Type[Cluster] = Cluster
diff --git a/diagrams/aws/general.py b/diagrams/aws/general.py
index dd6d4bea7..ce0270a0c 100644
--- a/diagrams/aws/general.py
+++ b/diagrams/aws/general.py
@@ -102,8 +102,3 @@ class User(_General):
class Users(_General):
_icon = "users.png"
-
-
-# Aliases
-
-OfficeBuilding = GenericOfficeBuilding
diff --git a/diagrams/c4/__init__.py b/diagrams/c4/__init__.py
index 90ce7a926..b383eb78a 100644
--- a/diagrams/c4/__init__.py
+++ b/diagrams/c4/__init__.py
@@ -3,10 +3,12 @@
"""
import html
import textwrap
+from typing import Any, Dict
+
from diagrams import Cluster, Node, Edge
-def _format_node_label(name, key, description):
+def _format_node_label(name: str, key: str, description: str) -> str:
"""Create a graphviz label string for a C4 node"""
title = f'{html.escape(name)}
'
subtitle = f'[{html.escape(key)}]
' if key else ""
@@ -14,7 +16,7 @@ def _format_node_label(name, key, description):
return f"<{title}{subtitle}{text}>"
-def _format_description(description):
+def _format_description(description: str) -> str:
"""
Formats the description string so it fits into the C4 nodes.
@@ -29,7 +31,7 @@ def _format_description(description):
return "
".join(lines)
-def _format_edge_label(description):
+def _format_edge_label(description: str) -> str:
"""Create a graphviz label string for a C4 edge"""
wrapper = textwrap.TextWrapper(width=24, max_lines=3)
lines = [html.escape(line) for line in wrapper.wrap(description)]
@@ -37,9 +39,9 @@ def _format_edge_label(description):
return f'<{text}>'
-def C4Node(name, technology="", description="", type="Container", **kwargs):
+def C4Node(name: str, technology: str = "", description: str = "", type: str = "Container", **kwargs: Dict[str, Any]) -> Node:
key = f"{type}: {technology}" if technology else type
- node_attributes = {
+ node_attributes: Dict[str, Any] = {
"label": _format_node_label(name, key, description),
"labelloc": "c",
"shape": "rect",
@@ -57,8 +59,8 @@ def C4Node(name, technology="", description="", type="Container", **kwargs):
return Node(**node_attributes)
-def Container(name, technology="", description="", **kwargs):
- container_attributes = {
+def Container(name: str, technology: str = "", description: str = "", **kwargs: Dict[str, Any]) -> Node:
+ container_attributes: Dict[str, Any] = {
"name": name,
"technology": technology,
"description": description,
@@ -68,8 +70,8 @@ def Container(name, technology="", description="", **kwargs):
return C4Node(**container_attributes)
-def Database(name, technology="", description="", **kwargs):
- database_attributes = {
+def Database(name: str, technology: str = "", description: str = "", **kwargs: Dict[str, Any]) -> Node:
+ database_attributes: Dict[str, Any] = {
"name": name,
"technology": technology,
"description": description,
@@ -81,8 +83,8 @@ def Database(name, technology="", description="", **kwargs):
return C4Node(**database_attributes)
-def System(name, description="", external=False, **kwargs):
- system_attributes = {
+def System(name: str, description: str = "", external: bool = False, **kwargs: Dict[str, Any]) -> Node:
+ system_attributes: Dict[str, Any] = {
"name": name,
"description": description,
"type": "External System" if external else "System",
@@ -92,8 +94,8 @@ def System(name, description="", external=False, **kwargs):
return C4Node(**system_attributes)
-def Person(name, description="", external=False, **kwargs):
- person_attributes = {
+def Person(name: str, description: str = "", external: bool = False, **kwargs: Dict[str, Any]) -> Node:
+ person_attributes: Dict[str, Any] = {
"name": name,
"description": description,
"type": "External Person" if external else "Person",
@@ -104,8 +106,8 @@ def Person(name, description="", external=False, **kwargs):
return C4Node(**person_attributes)
-def SystemBoundary(name, **kwargs):
- graph_attributes = {
+def SystemBoundary(name: str, **kwargs: Dict[str, Any]) -> Cluster:
+ graph_attributes: Dict[str, Any] = {
"label": html.escape(name),
"bgcolor": "white",
"margin": "16",
@@ -115,8 +117,8 @@ def SystemBoundary(name, **kwargs):
return Cluster(name, graph_attr=graph_attributes)
-def Relationship(label="", **kwargs):
- edge_attributes = {
+def Relationship(label: str = "", **kwargs: Dict[str, Any]) -> Edge:
+ edge_attributes: Dict[str, Any] = {
"style": "dashed",
"color": "gray60",
"label": _format_edge_label(label) if label else "",
diff --git a/diagrams/custom/__init__.py b/diagrams/custom/__init__.py
index 9845932dd..80488d430 100644
--- a/diagrams/custom/__init__.py
+++ b/diagrams/custom/__init__.py
@@ -2,6 +2,8 @@
Custom provides the possibility of load an image to be presented as a node.
"""
+from typing import Any, Dict, Optional, override
+
from diagrams import Node
@@ -12,9 +14,11 @@ class Custom(Node):
fontcolor = "#ffffff"
- def _load_icon(self):
+ @override
+ def _load_icon(self) -> Optional[str]:
return self._icon
- def __init__(self, label, icon_path, *args, **kwargs):
- self._icon = icon_path
- super().__init__(label, *args, **kwargs)
+ @override
+ def __init__(self, label: str, icon_path: Optional[str], *, nodeid: Optional[str] = None, **attrs: Dict[Any, Any]):
+ self._icon: Optional[str] = icon_path
+ super().__init__(label, nodeid=nodeid, **attrs)
diff --git a/poetry.lock b/poetry.lock
index 3c271de36..b7a684453 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,10 +1,9 @@
-# This file is automatically @generated by Poetry and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "astroid"
version = "2.9.0"
description = "An abstract syntax tree for Python with inference support."
-category = "dev"
optional = false
python-versions = "~=3.6"
files = [
@@ -23,7 +22,6 @@ wrapt = ">=1.11,<1.14"
name = "black"
version = "22.12.0"
description = "The uncompromising code formatter."
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -60,7 +58,6 @@ uvloop = ["uvloop (>=0.15.2)"]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -76,7 +73,6 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
name = "colorama"
version = "0.4.3"
description = "Cross-platform colored terminal text."
-category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
@@ -88,7 +84,6 @@ files = [
name = "exceptiongroup"
version = "1.1.0"
description = "Backport of PEP 654 (exception groups)"
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -103,7 +98,6 @@ test = ["pytest (>=6)"]
name = "graphviz"
version = "0.20.1"
description = "Simple Python interface for Graphviz"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -120,7 +114,6 @@ test = ["coverage", "mock (>=4)", "pytest (>=7)", "pytest-cov", "pytest-mock (>=
name = "importlib-metadata"
version = "1.5.0"
description = "Read metadata from Python packages"
-category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
files = [
@@ -139,7 +132,6 @@ testing = ["importlib-resources", "packaging"]
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -151,7 +143,6 @@ files = [
name = "isort"
version = "4.3.21"
description = "A Python utility / library to sort Python imports."
-category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
@@ -169,7 +160,6 @@ xdg-home = ["appdirs (>=1.4.0)"]
name = "jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -187,7 +177,6 @@ i18n = ["Babel (>=2.7)"]
name = "lazy-object-proxy"
version = "1.4.3"
description = "A fast and thorough lazy object proxy."
-category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
@@ -218,7 +207,6 @@ files = [
name = "markupsafe"
version = "2.0.1"
description = "Safely add untrusted strings to HTML/XML markup."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -297,7 +285,6 @@ files = [
name = "mccabe"
version = "0.6.1"
description = "McCabe checker, plugin for flake8"
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -305,23 +292,68 @@ files = [
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
+[[package]]
+name = "mypy"
+version = "1.4.1"
+description = "Optional static typing for Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"},
+ {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"},
+ {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"},
+ {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"},
+ {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"},
+ {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"},
+ {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"},
+ {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"},
+ {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"},
+ {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"},
+ {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"},
+ {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"},
+ {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"},
+ {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"},
+ {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"},
+ {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"},
+ {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"},
+ {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"},
+ {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"},
+ {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"},
+ {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"},
+ {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"},
+ {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"},
+ {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"},
+ {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"},
+ {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"},
+]
+
+[package.dependencies]
+mypy-extensions = ">=1.0.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
+typing-extensions = ">=4.1.0"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+install-types = ["pip"]
+python2 = ["typed-ast (>=1.4.0,<2)"]
+reports = ["lxml"]
+
[[package]]
name = "mypy-extensions"
-version = "0.4.3"
-description = "Experimental type system extensions for programs checked with the mypy typechecker."
-category = "dev"
+version = "1.0.0"
+description = "Type system extensions for programs checked with the mypy type checker."
optional = false
-python-versions = "*"
+python-versions = ">=3.5"
files = [
- {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
- {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
+ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "packaging"
version = "20.8"
description = "Core utilities for Python packages"
-category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
@@ -336,7 +368,6 @@ pyparsing = ">=2.0.2"
name = "pathspec"
version = "0.10.1"
description = "Utility library for gitignore style pattern matching of file paths."
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -348,7 +379,6 @@ files = [
name = "platformdirs"
version = "2.4.0"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@@ -364,7 +394,6 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock
name = "pluggy"
version = "0.13.1"
description = "plugin and hook calling mechanisms for python"
-category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
@@ -382,7 +411,6 @@ dev = ["pre-commit", "tox"]
name = "pylint"
version = "2.12.0"
description = "python code static checker"
-category = "dev"
optional = false
python-versions = "~=3.6"
files = [
@@ -403,7 +431,6 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""
name = "pyparsing"
version = "2.4.7"
description = "Python parsing module"
-category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
@@ -415,7 +442,6 @@ files = [
name = "pytest"
version = "7.3.0"
description = "pytest: simple powerful testing with Python"
-category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@@ -439,7 +465,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
name = "rope"
version = "0.14.0"
description = "a python refactoring library..."
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -452,7 +477,6 @@ files = [
name = "setuptools"
version = "59.6.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
-category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@@ -468,7 +492,6 @@ testing = ["flake8-2020", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock"
name = "toml"
version = "0.10.0"
description = "Python Library for Tom's Obvious, Minimal Language"
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -480,7 +503,6 @@ files = [
name = "tomli"
version = "1.2.3"
description = "A lil' TOML parser"
-category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@@ -492,7 +514,6 @@ files = [
name = "typed-ast"
version = "1.5.4"
description = "a fork of Python 2 and 3 ast modules with type comment support"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -526,7 +547,6 @@ files = [
name = "typing-extensions"
version = "4.1.1"
description = "Backported and Experimental Type Hints for Python 3.6+"
-category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@@ -538,7 +558,6 @@ files = [
name = "wrapt"
version = "1.11.2"
description = "Module for decorators, wrappers and monkey patching."
-category = "dev"
optional = false
python-versions = "*"
files = [
@@ -549,7 +568,6 @@ files = [
name = "zipp"
version = "3.1.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@@ -564,4 +582,4 @@ testing = ["func-timeout", "jaraco.itertools"]
[metadata]
lock-version = "2.0"
python-versions = "^3.7"
-content-hash = "5c2b82d1c8a6a283f63558fc693271f42fdcaeab73616713eb9b38b0b59787fc"
+content-hash = "03625aefdb3d0562b74d70e00eda5ddb3a477a433adc9aa01b351e3cef897403"
diff --git a/pyproject.toml b/pyproject.toml
index 7b638a8fc..99f8fa7cc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,6 +21,15 @@ pylint = "^2.7"
rope = "^0.14.0"
isort = "^4.3"
black = "^22.12.0"
+mypy = ">=1.4,<1.8"
[tool.black]
line-length = 120
+
+[tool.mypy]
+files = "diagrams, tests"
+mypy_path = "src"
+namespace_packages = true
+explicit_package_bases = true
+show_error_codes = true
+strict = true
\ No newline at end of file
diff --git a/tests/test_c4.py b/tests/test_c4.py
index 3877ec0fb..b76b84034 100644
--- a/tests/test_c4.py
+++ b/tests/test_c4.py
@@ -3,47 +3,46 @@
import string
import unittest
-from diagrams import Diagram
-from diagrams import setcluster, setdiagram
+from diagrams import setcluster, setdiagram, Diagram
from diagrams.c4 import Person, Container, Database, System, SystemBoundary, Relationship
class C4Test(unittest.TestCase):
- def setUp(self):
- self.name = "diagram-" + "".join([random.choice(string.hexdigits) for n in range(7)]).lower()
+ def setUp(self) -> None:
+ self.name: str = "diagram-" + "".join([random.choice(string.hexdigits) for n in range(7)]).lower()
- def tearDown(self):
+ def tearDown(self) -> None:
setdiagram(None)
setcluster(None)
try:
- os.remove(self.name + ".png")
+ os.remove(f"{self.name}.png")
except FileNotFoundError:
pass
- def test_nodes(self):
+ def test_nodes(self) -> None:
with Diagram(name=self.name, show=False):
person = Person("person", "A person.")
container = Container("container", "Java application", "The application.")
database = Database("database", "Oracle database", "Stores information.")
- def test_external_nodes(self):
+ def test_external_nodes(self) -> None:
with Diagram(name=self.name, show=False):
external_person = Person("person", external=True)
external_system = System("external", external=True)
- def test_systems(self):
+ def test_systems(self) -> None:
with Diagram(name=self.name, show=False):
system = System("system", "The internal system.")
system_without_description = System("unknown")
- def test_edges(self):
+ def test_edges(self) -> None:
with Diagram(name=self.name, show=False):
c1 = Container("container1")
c2 = Container("container2")
c1 >> c2
- def test_edges_with_labels(self):
+ def test_edges_with_labels(self) -> None:
with Diagram(name=self.name, show=False):
c1 = Container("container1")
c2 = Container("container2")
@@ -51,14 +50,14 @@ def test_edges_with_labels(self):
c1 >> Relationship("depends on") >> c2
c1 << Relationship("is depended on by") << c2
- def test_edge_without_constraint(self):
+ def test_edge_without_constraint(self) -> None:
with Diagram(name=self.name, show=False):
s1 = System("system 1")
s2 = System("system 2")
- s1 >> Relationship(constraint="False") >> s2
+ s1 >> Relationship(constraint="False") >> s2 # type: ignore[arg-type]
- def test_cluster(self):
+ def test_cluster(self) -> None:
with Diagram(name=self.name, show=False):
with SystemBoundary("System"):
Container("container", "type", "description")
diff --git a/tests/test_diagram.py b/tests/test_diagram.py
index 00bdacc6e..52e78f774 100644
--- a/tests/test_diagram.py
+++ b/tests/test_diagram.py
@@ -3,15 +3,14 @@
import unittest
import pathlib
-from diagrams import Cluster, Diagram, Edge, Node
-from diagrams import getcluster, getdiagram, setcluster, setdiagram
+from diagrams import Cluster, Diagram, Edge, Node, getcluster, getdiagram, setcluster, setdiagram
class DiagramTest(unittest.TestCase):
- def setUp(self):
- self.name = "diagram_test"
+ def setUp(self) -> None:
+ self.name: str = "diagram_test"
- def tearDown(self):
+ def tearDown(self) -> None:
setdiagram(None)
setcluster(None)
# Only some tests generate the image file.
@@ -20,11 +19,11 @@ def tearDown(self):
except OSError:
# Consider it file
try:
- os.remove(self.name + ".png")
+ os.remove(f"{self.name}.png")
except FileNotFoundError:
pass
- def test_validate_direction(self):
+ def test_validate_direction(self) -> None:
# Normal directions.
for dir in ("TB", "BT", "LR", "RL", "tb"):
Diagram(direction=dir)
@@ -34,7 +33,7 @@ def test_validate_direction(self):
with self.assertRaises(ValueError):
Diagram(direction=dir)
- def test_validate_curvestyle(self):
+ def test_validate_curvestyle(self) -> None:
# Normal directions.
for cvs in ("ortho", "curved", "CURVED"):
Diagram(curvestyle=cvs)
@@ -44,7 +43,7 @@ def test_validate_curvestyle(self):
with self.assertRaises(ValueError):
Diagram(curvestyle=cvs)
- def test_validate_outformat(self):
+ def test_validate_outformat(self) -> None:
# Normal output formats.
for fmt in ("png", "jpg", "svg", "pdf", "PNG", "dot"):
Diagram(outformat=fmt)
@@ -54,18 +53,18 @@ def test_validate_outformat(self):
with self.assertRaises(ValueError):
Diagram(outformat=fmt)
- def test_with_global_context(self):
+ def test_with_global_context(self) -> None:
self.assertIsNone(getdiagram())
with Diagram(name=os.path.join(self.name, "with_global_context"), show=False):
self.assertIsNotNone(getdiagram())
self.assertIsNone(getdiagram())
- def test_node_not_in_diagram(self):
+ def test_node_not_in_diagram(self) -> None:
# Node must be belong to a diagrams.
with self.assertRaises(EnvironmentError):
Node("node")
- def test_node_to_node(self):
+ def test_node_to_node(self) -> None:
with Diagram(name=os.path.join(self.name, "node_to_node"), show=False):
node1 = Node("node1")
node2 = Node("node2")
@@ -73,7 +72,7 @@ def test_node_to_node(self):
self.assertEqual(node1 >> node2, node2)
self.assertEqual(node1 << node2, node2)
- def test_node_to_nodes(self):
+ def test_node_to_nodes(self) -> None:
with Diagram(name=os.path.join(self.name, "node_to_nodes"), show=False):
node1 = Node("node1")
nodes = [Node("node2"), Node("node3")]
@@ -81,7 +80,7 @@ def test_node_to_nodes(self):
self.assertEqual(node1 >> nodes, nodes)
self.assertEqual(node1 << nodes, nodes)
- def test_nodes_to_node(self):
+ def test_nodes_to_node(self) -> None:
with Diagram(name=os.path.join(self.name, "nodes_to_node"), show=False):
node1 = Node("node1")
nodes = [Node("node2"), Node("node3")]
@@ -89,32 +88,31 @@ def test_nodes_to_node(self):
self.assertEqual(nodes >> node1, node1)
self.assertEqual(nodes << node1, node1)
- def test_default_filename(self):
+ def test_default_filename(self) -> None:
self.name = "example_1"
with Diagram(name="Example 1", show=False):
Node("node1")
self.assertTrue(os.path.exists(f"{self.name}.png"))
- def test_custom_filename(self):
+ def test_custom_filename(self) -> None:
self.name = "my_custom_name"
with Diagram(name="Example 1", filename=self.name, show=False):
Node("node1")
self.assertTrue(os.path.exists(f"{self.name}.png"))
- def test_empty_name(self):
+ def test_empty_name(self) -> None:
"""Check that providing an empty name don't crash, but save in a diagrams_image.xxx file."""
self.name = 'diagrams_image'
with Diagram(show=False):
Node("node1")
self.assertTrue(os.path.exists(f"{self.name}.png"))
- def test_autolabel(self):
+ def test_autolabel(self) -> None:
with Diagram(name=os.path.join(self.name, "nodes_to_node"), show=False):
node1 = Node("node1")
self.assertTrue(node1.label,"Node\nnode1")
-
- def test_outformat_list(self):
+ def test_outformat_list(self) -> None:
"""Check that outformat render all the files from the list."""
self.name = 'diagrams_image'
with Diagram(show=False, outformat=["dot", "png"]):
@@ -128,10 +126,10 @@ def test_outformat_list(self):
class ClusterTest(unittest.TestCase):
- def setUp(self):
- self.name = "cluster_test"
+ def setUp(self) -> None:
+ self.name: str = "cluster_test"
- def tearDown(self):
+ def tearDown(self) -> None:
setdiagram(None)
setcluster(None)
# Only some tests generate the image file.
@@ -140,7 +138,7 @@ def tearDown(self):
except OSError:
pass
- def test_validate_direction(self):
+ def test_validate_direction(self) -> None:
# Normal directions.
for dir in ("TB", "BT", "LR", "RL"):
with Diagram(name=os.path.join(self.name, "validate_direction"), show=False):
@@ -152,14 +150,14 @@ def test_validate_direction(self):
with Diagram(name=os.path.join(self.name, "validate_direction"), show=False):
Cluster(direction=dir)
- def test_with_global_context(self):
+ def test_with_global_context(self) -> None:
with Diagram(name=os.path.join(self.name, "with_global_context"), show=False):
self.assertIsNone(getcluster())
with Cluster():
self.assertIsNotNone(getcluster())
self.assertIsNone(getcluster())
- def test_with_nested_cluster(self):
+ def test_with_nested_cluster(self) -> None:
with Diagram(name=os.path.join(self.name, "with_nested_cluster"), show=False):
self.assertIsNone(getcluster())
with Cluster() as c1:
@@ -169,12 +167,12 @@ def test_with_nested_cluster(self):
self.assertEqual(c1, getcluster())
self.assertIsNone(getcluster())
- def test_node_not_in_diagram(self):
+ def test_node_not_in_diagram(self) -> None:
# Node must be belong to a diagrams.
with self.assertRaises(EnvironmentError):
Node("node")
- def test_node_to_node(self):
+ def test_node_to_node(self) -> None:
with Diagram(name=os.path.join(self.name, "node_to_node"), show=False):
with Cluster():
node1 = Node("node1")
@@ -183,7 +181,7 @@ def test_node_to_node(self):
self.assertEqual(node1 >> node2, node2)
self.assertEqual(node1 << node2, node2)
- def test_node_to_nodes(self):
+ def test_node_to_nodes(self) -> None:
with Diagram(name=os.path.join(self.name, "node_to_nodes"), show=False):
with Cluster():
node1 = Node("node1")
@@ -192,7 +190,7 @@ def test_node_to_nodes(self):
self.assertEqual(node1 >> nodes, nodes)
self.assertEqual(node1 << nodes, nodes)
- def test_nodes_to_node(self):
+ def test_nodes_to_node(self) -> None:
with Diagram(name=os.path.join(self.name, "nodes_to_node"), show=False):
with Cluster():
node1 = Node("node1")
@@ -203,10 +201,10 @@ def test_nodes_to_node(self):
class EdgeTest(unittest.TestCase):
- def setUp(self):
- self.name = "edge_test"
+ def setUp(self) -> None:
+ self.name: str = "edge_test"
- def tearDown(self):
+ def tearDown(self) -> None:
setdiagram(None)
setcluster(None)
# Only some tests generate the image file.
@@ -215,34 +213,34 @@ def tearDown(self):
except OSError:
pass
- def test_node_to_node(self):
+ def test_node_to_node(self) -> None:
with Diagram(name=os.path.join(self.name, "node_to_node"), show=False):
node1 = Node("node1")
node2 = Node("node2")
self.assertEqual(node1 - Edge(color="red") - node2, node2)
- def test_node_to_nodes(self):
+ def test_node_to_nodes(self) -> None:
with Diagram(name=os.path.join(self.name, "node_to_nodes"), show=False):
with Cluster():
node1 = Node("node1")
nodes = [Node("node2"), Node("node3")]
- self.assertEqual(node1 - Edge(color="red") - nodes, nodes)
+ self.assertEqual(node1 - Edge(color="red") - nodes, nodes) # type: ignore[operator]
- def test_nodes_to_node(self):
+ def test_nodes_to_node(self) -> None:
with Diagram(name=os.path.join(self.name, "nodes_to_node"), show=False):
with Cluster():
node1 = Node("node1")
nodes = [Node("node2"), Node("node3")]
self.assertEqual(nodes - Edge(color="red") - node1, node1)
- def test_nodes_to_node_with_additional_attributes(self):
+ def test_nodes_to_node_with_additional_attributes(self) -> None:
with Diagram(name=os.path.join(self.name, "nodes_to_node_with_additional_attributes"), show=False):
with Cluster():
node1 = Node("node1")
nodes = [Node("node2"), Node("node3")]
self.assertEqual(nodes - Edge(color="red") - Edge(color="green") - node1, node1)
- def test_node_to_node_with_attributes(self):
+ def test_node_to_node_with_attributes(self) -> None:
with Diagram(name=os.path.join(self.name, "node_to_node_with_attributes"), show=False):
with Cluster():
node1 = Node("node1")
@@ -251,7 +249,7 @@ def test_node_to_node_with_attributes(self):
self.assertEqual(node1 >> Edge(color="green", label="1.2") >> node2, node2)
self.assertEqual(node1 << Edge(color="blue", label="1.3") >> node2, node2)
- def test_node_to_node_with_additional_attributes(self):
+ def test_node_to_node_with_additional_attributes(self) -> None:
with Diagram(name=os.path.join(self.name, "node_to_node_with_additional_attributes"), show=False):
with Cluster():
node1 = Node("node1")
@@ -260,7 +258,7 @@ def test_node_to_node_with_additional_attributes(self):
self.assertEqual(node1 >> Edge(color="green", label="2.2") >> Edge(color="red") >> node2, node2)
self.assertEqual(node1 << Edge(color="blue", label="2.3") >> Edge(color="black") >> node2, node2)
- def test_nodes_to_node_with_attributes_loop(self):
+ def test_nodes_to_node_with_attributes_loop(self) -> None:
with Diagram(name=os.path.join(self.name, "nodes_to_node_with_attributes_loop"), show=False):
with Cluster():
node = Node("node")
@@ -269,21 +267,21 @@ def test_nodes_to_node_with_attributes_loop(self):
self.assertEqual(node >> Edge(color="blue", label="3.3") << node, node)
self.assertEqual(node << Edge(color="pink", label="3.4") >> node, node)
- def test_nodes_to_node_with_attributes_bothdirectional(self):
+ def test_nodes_to_node_with_attributes_bothdirectional(self) -> None:
with Diagram(name=os.path.join(self.name, "nodes_to_node_with_attributes_bothdirectional"), show=False):
with Cluster():
node1 = Node("node1")
nodes = [Node("node2"), Node("node3")]
self.assertEqual(nodes << Edge(color="green", label="4") >> node1, node1)
- def test_nodes_to_node_with_attributes_bidirectional(self):
+ def test_nodes_to_node_with_attributes_bidirectional(self) -> None:
with Diagram(name=os.path.join(self.name, "nodes_to_node_with_attributes_bidirectional"), show=False):
with Cluster():
node1 = Node("node1")
nodes = [Node("node2"), Node("node3")]
self.assertEqual(nodes << Edge(color="blue", label="5") >> node1, node1)
- def test_nodes_to_node_with_attributes_onedirectional(self):
+ def test_nodes_to_node_with_attributes_onedirectional(self) -> None:
with Diagram(name=os.path.join(self.name, "nodes_to_node_with_attributes_onedirectional"), show=False):
with Cluster():
node1 = Node("node1")
@@ -291,7 +289,7 @@ def test_nodes_to_node_with_attributes_onedirectional(self):
self.assertEqual(nodes >> Edge(color="red", label="6.1") >> node1, node1)
self.assertEqual(nodes << Edge(color="green", label="6.2") << node1, node1)
- def test_nodes_to_node_with_additional_attributes_directional(self):
+ def test_nodes_to_node_with_additional_attributes_directional(self) -> None:
with Diagram(name=os.path.join(self.name, "nodes_to_node_with_additional_attributes_directional"), show=False):
with Cluster():
node1 = Node("node1")
@@ -305,7 +303,7 @@ def test_nodes_to_node_with_additional_attributes_directional(self):
class ResourcesTest(unittest.TestCase):
- def test_folder_depth(self):
+ def test_folder_depth(self) -> None:
"""
The code currently only handles resource folders up to a dir depth of 2
i.e. resources///, so check that this depth isn't