Skip to content

Commit 9e01519

Browse files
committed
[review] implement review comments
Signed-off-by: Meret Behrens <[email protected]>
1 parent 2746d83 commit 9e01519

File tree

7 files changed

+42
-54
lines changed

7 files changed

+42
-54
lines changed

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,16 @@ instead of `bin`.
8080

8181
* For help use `pyspdxtools --help`
8282

83-
3. **VISUALIZATION** (optional feature)
83+
3. **GRAPH GENERATION** (optional feature)
8484

85-
* Make sure you install the optional dependencies `networkx` and `pygraphviz`. To do so run `pip install ".[visualization]"`.
86-
* Use `pyspdxtools -i <input_file> --visualize <output_file>` where `<output_file>` is a valid output for `pygraphviz` (check
85+
* This feature generates a graph representing all elements in the SPDX document and their connections based on the provided
86+
relationships. The graph can be rendered to a picture. Below is an example for the file `tests/data/formats/SPDXJSONExample-v2.3.spdx.json`:
87+
![SPDXJSONExample-v2.2.spdx.png](SPDXJSONExample-v2.2.spdx.png)
88+
* Make sure you install the optional dependencies `networkx` and `pygraphviz`. To do so run `pip install ".[graph_generation]"`.
89+
* Use `pyspdxtools -i <input_file> --graph_generation <output_file>` where `<output_file>` is a valid output for `pygraphviz` (check
8790
the documentation [here](https://pygraphviz.github.io/documentation/stable/reference/agraph.html#pygraphviz.AGraph.draw)).
8891
* If you are using a source distribution, try running
89-
`pyspdxtools -i tests/data/formats/SPDXJSONExample-v2.3.spdx.json --visualize SPDXJSONExample-v2.3.spdx.png` to generate
92+
`pyspdxtools -i tests/data/formats/SPDXJSONExample-v2.3.spdx.json --graph_generation SPDXJSONExample-v2.3.spdx.png` to generate
9093
a png with an overview of the structure of the example file.
9194

9295
## Library usage

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ dynamic = ["version"]
3030
[project.optional-dependencies]
3131
test = ["pytest"]
3232
code_style = ["isort", "black", "flake8"]
33-
visualization = ["pygraphviz", "networkx"]
33+
graph_generation = ["pygraphviz", "networkx"]
3434

3535
[project.scripts]
3636
pyspdxtools = "spdx.clitools.pyspdxtools:main"

src/common/visualization/__init__.py

Whitespace-only changes.

src/spdx/clitools/pyspdxtools.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import click
2020

21-
from common.visualization.visualization import visualize_document
21+
from common.visualization.visualization import export_graph_from_document
2222
from spdx.model.document import Document
2323
from spdx.parser.error import SPDXParsingError
2424
from spdx.parser.parse_anything import parse_file
@@ -43,12 +43,12 @@
4343
)
4444
@click.option("--novalidation", is_flag=True, help="Don't validate the provided document.")
4545
@click.option(
46-
"--visualize",
46+
"--graph_generation",
4747
default="",
4848
help="The file to write the structure of the spdx document as a pygraphviz AGraph to. Note: You need to install"
4949
" the optional dependencies 'networkx' and 'pygraphviz' for this feature.",
5050
)
51-
def main(infile: str, outfile: str, version: str, novalidation: bool, visualize: str):
51+
def main(infile: str, outfile: str, version: str, novalidation: bool, graph_generation: str):
5252
"""
5353
CLI-tool for validating SPDX documents and converting between RDF, TAG-VALUE, JSON, YAML and XML formats.
5454
Formats are determined by the file endings.
@@ -82,13 +82,13 @@ def main(infile: str, outfile: str, version: str, novalidation: bool, visualize:
8282
if outfile and outfile != "-":
8383
write_file(document, outfile, validate=False)
8484

85-
if visualize:
85+
if graph_generation:
8686
try:
87-
visualize_document(document, visualize)
87+
export_graph_from_document(document, graph_generation)
8888
except ImportError:
8989
logging.error(
90-
"To be able to visualize the structure of the parsed document "
91-
"you need to install 'networkx' and 'pygraphviz'. Run 'pip install \".[visualization]\"'."
90+
"To be able to draw a relationship graph of the parsed document "
91+
"you need to install 'networkx' and 'pygraphviz'. Run 'pip install \".[graph_generation]\"'."
9292
)
9393
sys.exit(1)
9494

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,18 @@
1212
from spdx.model.relationship import Relationship
1313

1414

15-
def visualize_document(document: Document, visualize: str) -> None:
16-
try:
17-
from networkx.drawing import nx_agraph
18-
except ImportError:
19-
raise ImportError
15+
def export_graph_from_document(document: Document, file_name: str) -> None:
16+
from networkx.drawing import nx_agraph
2017

21-
graph = generate_graph_from_spdx(document)
18+
graph = generate_relationship_graph_from_spdx(document)
2219
_color_nodes(graph)
23-
attributes_graph = nx_agraph.to_agraph(graph) # convert to a graphviz graph
24-
attributes_graph.draw(visualize, prog="dot")
20+
attributes_graph = nx_agraph.to_agraph(graph) # convert to a pygraphviz graph
21+
attributes_graph.draw(file_name, prog="dot")
2522

2623

27-
def generate_graph_from_spdx(document: Document) -> DiGraph:
28-
try:
29-
from networkx import DiGraph
30-
except ImportError:
31-
raise ImportError
24+
def generate_relationship_graph_from_spdx(document: Document) -> DiGraph:
25+
from networkx import DiGraph
26+
3227
graph = DiGraph()
3328
graph.add_node(document.creation_info.spdx_id, element=document.creation_info)
3429

@@ -38,42 +33,40 @@ def generate_graph_from_spdx(document: Document) -> DiGraph:
3833
]
3934
graph.add_nodes_from(contained_element_nodes)
4035

41-
relationships_by_spdx_id: Dict[str, List[Relationship]] = _get_relationships_by_spdx_id(document.relationships)
36+
relationships_by_spdx_id: Dict[str, List[Relationship]] = dict()
37+
for relationship1 in document.relationships:
38+
relationships_by_spdx_id.setdefault(relationship1.spdx_element_id, []).append(relationship1)
4239

4340
for spdx_id, relationships in relationships_by_spdx_id.items():
4441
if spdx_id not in graph.nodes():
45-
graph.add_node(spdx_id, element=get_element_from_spdx_id(document, spdx_id))
42+
# this will add any external spdx_id to the graph where we have no further information about the element
43+
# to indicate that this node represents an element we add the attribute "element"
44+
graph.add_node(spdx_id, element=None)
4645
for relationship in relationships:
4746
relationship_node_key = relationship.spdx_element_id + "_" + relationship.relationship_type.name
4847
graph.add_node(relationship_node_key, comment=relationship.comment)
4948
graph.add_edge(relationship.spdx_element_id, relationship_node_key)
50-
# if the related spdx element is SpdxNone or SpdxNoAssertion we need a
51-
# type conversion
49+
# if the related spdx element is SpdxNone or SpdxNoAssertion we need a type conversion
5250
related_spdx_element_id = str(relationship.related_spdx_element_id)
5351

5452
if related_spdx_element_id not in graph.nodes():
53+
# this will add any external spdx_id to the graph where we have no further information about the element
54+
# to indicate that this node represents an element we add the attribute "element"
5555
graph.add_node(
5656
related_spdx_element_id,
57-
element=get_element_from_spdx_id(document, spdx_id),
57+
element=None,
5858
)
5959
graph.add_edge(relationship_node_key, related_spdx_element_id)
6060

6161
return graph
6262

6363

64-
def _get_relationships_by_spdx_id(
65-
relationships: List[Relationship],
66-
) -> Dict[str, List[Relationship]]:
67-
relationships_by_spdx_id: Dict[str, List[Relationship]] = dict()
68-
for relationship in relationships:
69-
relationships_by_spdx_id.setdefault(relationship.spdx_element_id, []).append(relationship)
70-
71-
return relationships_by_spdx_id
72-
73-
7464
def _color_nodes(graph: DiGraph) -> None:
7565
for node in graph.nodes():
7666
if "_" in node:
67+
# nodes representing a RelationshipType are concatenated with the spdx_element_id
68+
# to only see the RelationshipType when rendering the graph to a picture we add
69+
# a label to these nodes
7770
graph.add_node(node, color="lightgreen", label=node.split("_", 1)[-1])
7871
elif node == "SPDXRef-DOCUMENT":
7972
graph.add_node(node, color="indianred2")

tests/common/__init__.py

Whitespace-only changes.
Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: 2023 TNG Technology Consulting GmbH <https://www.tngtech.com>
1+
# SPDX-FileCopyrightText: 2023 spdx contributors
22
#
33
# SPDX-License-Identifier: Apache-2.0
44
from pathlib import Path
@@ -7,9 +7,8 @@
77

88
import pytest
99

10-
from common.visualization.visualization import generate_graph_from_spdx
10+
from spdx.graph_generation import generate_relationship_graph_from_spdx
1111
from spdx.model.document import Document
12-
from spdx.model.package import Package
1312
from spdx.model.relationship import Relationship, RelationshipType
1413
from spdx.parser.parse_anything import parse_file
1514
from tests.spdx.fixtures import document_fixture, file_fixture, package_fixture
@@ -63,10 +62,9 @@ def test_generate_graph_from_spdx(
6362
relationship_node_keys: List[str],
6463
) -> None:
6564
document = parse_file(str(Path(__file__).resolve().parent.parent / "spdx" / "data" / "formats" / file_name))
66-
graph = generate_graph_from_spdx(document)
65+
graph = generate_relationship_graph_from_spdx(document)
6766

6867
assert document.creation_info.spdx_id in graph.nodes()
69-
7068
assert graph.number_of_nodes() == nodes_count
7169
assert graph.number_of_edges() == edges_count
7270
assert "SPDXRef-DOCUMENT_DESCRIBES" in graph.nodes()
@@ -77,7 +75,7 @@ def test_generate_graph_from_spdx(
7775
def test_complete_connected_graph() -> None:
7876
document = _create_minimal_document()
7977

80-
graph = generate_graph_from_spdx(document)
78+
graph = generate_relationship_graph_from_spdx(document)
8179

8280
TestCase().assertCountEqual(
8381
graph.nodes(),
@@ -107,15 +105,9 @@ def test_complete_connected_graph() -> None:
107105

108106
def test_complete_unconnected_graph() -> None:
109107
document = _create_minimal_document()
110-
document.packages += [
111-
Package(
112-
spdx_id="SPDXRef-Package-C",
113-
name="Package without connection to document",
114-
download_location="https://download.location.com",
115-
)
116-
]
108+
document.packages += [package_fixture(spdx_id="SPDXRef-Package-C", name="Package without connection to document")]
117109

118-
graph = generate_graph_from_spdx(document)
110+
graph = generate_relationship_graph_from_spdx(document)
119111

120112
TestCase().assertCountEqual(
121113
graph.nodes(),

0 commit comments

Comments
 (0)