RDF 1.2#

This document contains a short introduction to RDF using rudof.

Preliminaries: Install and configure rudof#

The library is available as pyrudof.

!pip install pyrudof
Requirement already satisfied: pyrudof in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (0.1.135)

The main entry point if a class called Rudof through which most of the functionality is provided.

from pyrudof import Rudof, RudofConfig

In order to initialize that class, it is possible to pass a RudofConfig instance which contains configuration parameters for customization.

rudof = Rudof(RudofConfig())
! pip install ipython # If not already installed
!pip install plantuml
from IPython.display import Image # For displaying images
Requirement already satisfied: ipython in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (9.6.0)
Requirement already satisfied: decorator in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from ipython) (5.2.1)
Requirement already satisfied: ipython-pygments-lexers in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from ipython) (1.1.1)
Requirement already satisfied: jedi>=0.16 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from ipython) (0.19.2)
Requirement already satisfied: matplotlib-inline in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from ipython) (0.2.1)
Requirement already satisfied: pexpect>4.3 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from ipython) (4.9.0)
Requirement already satisfied: prompt_toolkit<3.1.0,>=3.0.41 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from ipython) (3.0.52)
Requirement already satisfied: pygments>=2.4.0 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from ipython) (2.19.2)
Requirement already satisfied: stack_data in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from ipython) (0.6.3)
Requirement already satisfied: traitlets>=5.13.0 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from ipython) (5.14.3)
Requirement already satisfied: typing_extensions>=4.6 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from ipython) (4.15.0)
Requirement already satisfied: wcwidth in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from prompt_toolkit<3.1.0,>=3.0.41->ipython) (0.2.14)
Requirement already satisfied: parso<0.9.0,>=0.8.4 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from jedi>=0.16->ipython) (0.8.5)
Requirement already satisfied: ptyprocess>=0.5 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from pexpect>4.3->ipython) (0.7.0)
Requirement already satisfied: executing>=1.2.0 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from stack_data->ipython) (2.2.1)
Requirement already satisfied: asttokens>=2.1.0 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from stack_data->ipython) (3.0.0)
Requirement already satisfied: pure-eval in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from stack_data->ipython) (0.2.3)
Requirement already satisfied: plantuml in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (0.3.0)
Requirement already satisfied: httplib2 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from plantuml) (0.31.0)
Requirement already satisfied: pyparsing<4,>=3.0.4 in /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages (from httplib2->plantuml) (3.2.5)

Support for RDF 1.2#

Reset previous state of rudof and remnove the temporary file out.puml if it exists.

rudof.reset_all()
!rm -f out.puml out.png

Rudof has added support for RDF 1.2. It is possible, for example, to load some RDF 1.2 files and visualize them.

RDF 1.2 introduces triple terms which denote statements that can be the object of some triples.

For example, we can state that :bob is interested in :MonaLisa since 4th October 1998 using the following code:

rudof.read_data_str("""
 prefix : <http://example.org/>
 prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
 prefix xsd: <http://www.w3.org/2001/XMLSchema#>

 :aliceBelief a :Statement ;
    rdf:reifies <<( :bob :knows :dave  )>> ;
    :since 2025 ;
    :accordingTo :dave  .
 """)
uml = rudof.data2plantuml_file('out.puml')

Convert the puml to an image.

!python -m plantuml out.puml
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 230, in <module>
    main()
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 225, in main
    print(list(map(lambda filename: {'filename': filename,
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 226, in <lambda>
    'gen_success': pl.processes_file(filename, directory=args.out)}, args.files)))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 199, in processes_file
    content = self.processes(data)
              ^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 173, in processes
    raise PlantUMLHTTPError(response, content)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 56, in __init__
    if not self.message:
           ^^^^^^^^^^^^
AttributeError: 'PlantUMLHTTPError' object has no attribute 'message'
Image(f"out.png")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1100, in Image._data_and_metadata(self, always_both)
   1099 try:
-> 1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:

TypeError: a bytes-like object is required, not 'str'

The above exception was the direct cause of the following exception:

FileNotFoundError                         Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/formatters.py:1036, in MimeBundleFormatter.__call__(self, obj, include, exclude)
   1033     method = get_real_method(obj, self.print_method)
   1035     if method is not None:
-> 1036         return method(include=include, exclude=exclude)
   1037     return None
   1038 else:

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1090, in Image._repr_mimebundle_(self, include, exclude)
   1088 if self.embed:
   1089     mimetype = self._mimetype
-> 1090     data, metadata = self._data_and_metadata(always_both=True)
   1091     if metadata:
   1092         metadata = {mimetype: metadata}

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1102, in Image._data_and_metadata(self, always_both)
   1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:
-> 1102     raise FileNotFoundError(
   1103         "No such file or directory: '%s'" % (self.data)) from e
   1104 md = {}
   1105 if self.metadata:

FileNotFoundError: No such file or directory: 'out.png'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1100, in Image._data_and_metadata(self, always_both)
   1099 try:
-> 1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:

TypeError: a bytes-like object is required, not 'str'

The above exception was the direct cause of the following exception:

FileNotFoundError                         Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/formatters.py:406, in BaseFormatter.__call__(self, obj)
    404     method = get_real_method(obj, self.print_method)
    405     if method is not None:
--> 406         return method()
    407     return None
    408 else:

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1122, in Image._repr_png_(self)
   1120 def _repr_png_(self):
   1121     if self.embed and self.format == self._FMT_PNG:
-> 1122         return self._data_and_metadata()

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1102, in Image._data_and_metadata(self, always_both)
   1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:
-> 1102     raise FileNotFoundError(
   1103         "No such file or directory: '%s'" % (self.data)) from e
   1104 md = {}
   1105 if self.metadata:

FileNotFoundError: No such file or directory: 'out.png'
<IPython.core.display.Image object>

In RDF 1.2 turtle syntax, there are several possibilities to define triple terms.

Declaring triple terms directly#

!rm -f out.puml out.pnf
rudof.reset_all()

A triple term can be declared enclosing it between <<( and )>>. For example:

rudof.read_data_str("""
PREFIX :    <http://www.example.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

_:e38  :familyName                     "Smith" .
_:anno rdf:reifies <<( _:e38 :jobTitle "Designer" )>> .
_:anno :accordingTo                     :eric .
 """)
uml = rudof.data2plantuml_file('out.puml')
!python -m plantuml out.puml
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 230, in <module>
    main()
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 225, in main
    print(list(map(lambda filename: {'filename': filename,
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 226, in <lambda>
    'gen_success': pl.processes_file(filename, directory=args.out)}, args.files)))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 199, in processes_file
    content = self.processes(data)
              ^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 173, in processes
    raise PlantUMLHTTPError(response, content)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 56, in __init__
    if not self.message:
           ^^^^^^^^^^^^
AttributeError: 'PlantUMLHTTPError' object has no attribute 'message'
Image(f"out.png")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1100, in Image._data_and_metadata(self, always_both)
   1099 try:
-> 1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:

TypeError: a bytes-like object is required, not 'str'

The above exception was the direct cause of the following exception:

FileNotFoundError                         Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/formatters.py:1036, in MimeBundleFormatter.__call__(self, obj, include, exclude)
   1033     method = get_real_method(obj, self.print_method)
   1035     if method is not None:
-> 1036         return method(include=include, exclude=exclude)
   1037     return None
   1038 else:

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1090, in Image._repr_mimebundle_(self, include, exclude)
   1088 if self.embed:
   1089     mimetype = self._mimetype
-> 1090     data, metadata = self._data_and_metadata(always_both=True)
   1091     if metadata:
   1092         metadata = {mimetype: metadata}

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1102, in Image._data_and_metadata(self, always_both)
   1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:
-> 1102     raise FileNotFoundError(
   1103         "No such file or directory: '%s'" % (self.data)) from e
   1104 md = {}
   1105 if self.metadata:

FileNotFoundError: No such file or directory: 'out.png'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1100, in Image._data_and_metadata(self, always_both)
   1099 try:
-> 1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:

TypeError: a bytes-like object is required, not 'str'

The above exception was the direct cause of the following exception:

FileNotFoundError                         Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/formatters.py:406, in BaseFormatter.__call__(self, obj)
    404     method = get_real_method(obj, self.print_method)
    405     if method is not None:
--> 406         return method()
    407     return None
    408 else:

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1122, in Image._repr_png_(self)
   1120 def _repr_png_(self):
   1121     if self.embed and self.format == self._FMT_PNG:
-> 1122         return self._data_and_metadata()

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1102, in Image._data_and_metadata(self, always_both)
   1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:
-> 1102     raise FileNotFoundError(
   1103         "No such file or directory: '%s'" % (self.data)) from e
   1104 md = {}
   1105 if self.metadata:

FileNotFoundError: No such file or directory: 'out.png'
<IPython.core.display.Image object>

Reifying triples#

!rm -f out.puml out.pnf
rudof.reset_all()

Enclosing a triple between << and >>, is a syntactic sugar that declares that there is a reifier whose object is that triple and that can be used to add more declarations about that reifier.

For example:

rudof.read_data_str("""
 PREFIX :    <http://www.example.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

:employee38 :familyName "Smith" .
<< :employee38 :jobTitle "Assistant Designer" >> :accordingTo :employee22 .
 """)
uml = rudof.data2plantuml_file('out.puml')
!python -m plantuml out.puml
Image(f"out.png")
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 230, in <module>
    main()
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 225, in main
    print(list(map(lambda filename: {'filename': filename,
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 226, in <lambda>
    'gen_success': pl.processes_file(filename, directory=args.out)}, args.files)))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 199, in processes_file
    content = self.processes(data)
              ^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 173, in processes
    raise PlantUMLHTTPError(response, content)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 56, in __init__
    if not self.message:
           ^^^^^^^^^^^^
AttributeError: 'PlantUMLHTTPError' object has no attribute 'message'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1100, in Image._data_and_metadata(self, always_both)
   1099 try:
-> 1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:

TypeError: a bytes-like object is required, not 'str'

The above exception was the direct cause of the following exception:

FileNotFoundError                         Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/formatters.py:1036, in MimeBundleFormatter.__call__(self, obj, include, exclude)
   1033     method = get_real_method(obj, self.print_method)
   1035     if method is not None:
-> 1036         return method(include=include, exclude=exclude)
   1037     return None
   1038 else:

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1090, in Image._repr_mimebundle_(self, include, exclude)
   1088 if self.embed:
   1089     mimetype = self._mimetype
-> 1090     data, metadata = self._data_and_metadata(always_both=True)
   1091     if metadata:
   1092         metadata = {mimetype: metadata}

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1102, in Image._data_and_metadata(self, always_both)
   1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:
-> 1102     raise FileNotFoundError(
   1103         "No such file or directory: '%s'" % (self.data)) from e
   1104 md = {}
   1105 if self.metadata:

FileNotFoundError: No such file or directory: 'out.png'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1100, in Image._data_and_metadata(self, always_both)
   1099 try:
-> 1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:

TypeError: a bytes-like object is required, not 'str'

The above exception was the direct cause of the following exception:

FileNotFoundError                         Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/formatters.py:406, in BaseFormatter.__call__(self, obj)
    404     method = get_real_method(obj, self.print_method)
    405     if method is not None:
--> 406         return method()
    407     return None
    408 else:

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1122, in Image._repr_png_(self)
   1120 def _repr_png_(self):
   1121     if self.embed and self.format == self._FMT_PNG:
-> 1122         return self._data_and_metadata()

File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/IPython/core/display.py:1102, in Image._data_and_metadata(self, always_both)
   1100     b64_data = b2a_base64(self.data, newline=False).decode("ascii")
   1101 except TypeError as e:
-> 1102     raise FileNotFoundError(
   1103         "No such file or directory: '%s'" % (self.data)) from e
   1104 md = {}
   1105 if self.metadata:

FileNotFoundError: No such file or directory: 'out.png'
<IPython.core.display.Image object>

Annotation syntax#

# Clean the workspace
!rm -f out.puml out.pnf
rudof.reset_all()

It is also possible to use the annotation syntax where a statement can be annotated with {| and |} as follows:

rudof.read_data_str("""
 PREFIX : <http://example.com/>
 PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
 :alice :name "Alice" ~ :t {|
   :statedBy :bob ;
   :recorded "2021-07-07"^^xsd:date
 |} .
 """)
uml = rudof.data2plantuml_file('out.puml')
!python -m plantuml out.puml
Image(f"out.png")
[{'filename': 'out.puml', 'gen_success': True}]
_images/0bcf32ae66ac9358eb4e681f8271a1f3f8c7607911d0d4c61c3422bbc545461c.png

Notice that in the visualization, we differentiate between a statement that is part of the graph which is represented by a box from a statement that is not part of the graph, which is represented by a cloud symbol.

Tim Berners-Lee’s example#

# @title
rudof.reset_all()

Example declaring that Tim Berners-Lee worked for the CERN between 1984 and 1994 and in 1980, and that he received the Princess of Asturias Award in 2002 together with Vinton-Cerf.

rudof.read_data_str("""
prefix : <http://example.org/>
prefix sh:     <http://www.w3.org/ns/shacl#>
prefix xsd:    <http://www.w3.org/2001/XMLSchema#>
prefix rdfs:   <http://www.w3.org/2000/01/rdf-schema#>

:timbl rdfs:label "Tim Berners Lee" ;
       :employer :CERN {| :start "1984" ;
                          :end   "1994" |}
                       {| :start "1980" ;
                          :end   "1980" |} ;
       :award :PA {| :time "2002" ;
                     :togetherWith :vint |} .
:vint  rdfs:label "Vinton Cerf" .
""")

Visualization:

uml = rudof.data2plantuml_file('out.puml')
!python -m plantuml out.puml
Image(f"out.png")
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 230, in <module>
    main()
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 225, in main
    print(list(map(lambda filename: {'filename': filename,
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 226, in <lambda>
    'gen_success': pl.processes_file(filename, directory=args.out)}, args.files)))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 199, in processes_file
    content = self.processes(data)
              ^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 173, in processes
    raise PlantUMLHTTPError(response, content)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/plantuml.py", line 56, in __init__
    if not self.message:
           ^^^^^^^^^^^^
AttributeError: 'PlantUMLHTTPError' object has no attribute 'message'
_images/0bcf32ae66ac9358eb4e681f8271a1f3f8c7607911d0d4c61c3422bbc545461c.png