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}]
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'