4. Using capellambse with Jinja2¶
Welcome to the py-capellambse jinja2 templating showcase. When using capella for systems engineering you might want to generate documentation for your model you can use M2DOC, one of capella’s addons which we found too challenging or you can use Jinja2 a richful templating language with high degree of freedom.
This notebook will introduce you into writing jinja2 templates where you’ll plant model information and diagrams. Additonally we’ll give a side-note on how to handle unique identifiers professionally with PVMT and some hints onto how to use jinja in a professional manner which could give you the option onto developping an automated document generation system.
With Jinja2 you are able to generate any text-based format(HTML, XML, CSV, LaTex,…) but during this tutorial we will only generate .html files. The jinja2 syntax is inspired by python. Check out their docs!
Below code loads the needed libraries and instantiates a test model:
[1]:
import jinja2
from IPython.core.display import HTML
import capellambse
path_to_model = "../../../tests/data/melodymodel/5_0/Melody Model Test.aird"
model = capellambse.MelodyModel(path_to_model)
env = jinja2.Environment()
In the following we want to make a template to document all modelled actors from the logical layer. Therefore we define a template string where we iterate over all actors and plant the name, uuid and description into it.
Hint: Make sure that you know of capella’smetamodelas we are implementing the capellambse.layers as close as possible to it while being as efficient and pythonic we can be currently. This knowledge can shorten used statements in the template immensely!
[2]:
templ = """
<h1>Actor definitions</h1>
{% for actor in model.la.all_components.by_is_actor(True) %}
<h2>{{ actor.name }}</h2>
<h3>Actor definition</h3>
<p>UUID: {{ actor.uuid }}</p>
<p>{{ actor.description }}</p>
{% endfor %}
"""
HTML(env.from_string(templ).render(model=model))
[2]:
Actor definitions
Prof. A. P. W. B. Dumbledore
Actor definition
UUID: 08e02248-504d-4ed8-a295-c7682a614f66
Principal of Hogwarts, wearer of the elder wand and greatest mage of all time.
Prof. S. Snape
Actor definition
UUID: 6f463eed-c77b-4568-8078-beec0536f243
Good guy and teacher of brewing arts.
Harry J. Potter
Actor definition
UUID: a8c46457-a702-41c4-a971-c815c4c5a674
R. Weasley
Actor definition
UUID: ff7b8672-84db-4b93-9fea-22a410907fb1
Voldemort
Actor definition
UUID: 3e0ee19f-0e3f-49d4-ae99-29bd4a3260c5
Multiport
Actor definition
UUID: b3888dad-a870-4b8b-97d4-0ddb83ef9251
As an extra: We don’t use the UUID from capella in our documents. If you still need an identifier, in the following it’s explained how we are doing it:
With the capella PVMT, one of the various capella addons, you can make property value groups and then set arbitrary values with these. Capellambse is able to recognize the pvmt extension and gives read and write access. In our workflows we are maintaining an ID database for all model elements. If that is done you can access pvmt attributes like:
{{ ... }}
<p>ID: {{ actor.pvmt["Group.Identification.MY MODEL ID"] }}</p>
{{ ... }}
For a PVMT showcase look into [TODO: pvmt-showcase notebook].
4.1. Filters and object manipulation¶
Now to step up our templating-game, we’ll bring in more complexity. We want a template that documents functional context of all actors. For that iff the actor has a non-empty functions attribute we make a table with columns for function’s uid, name, description and FunctionalExchange
s.
We can define variables in the template via the set statement. Furthermore the builtin jinja filters already give much power for object manipulation during rendering. Here we use map(.) to create FunctionalExchange
iterators and sum these up into one large list that stores all FunctionalExchange
s that have an an actor as either source or target. In the table for-loop we then filter on this lookup container and
set outgoing and incoming FunctionalExchange
s that we need for the last column.
The jupyter environment is great for writing templates b/c you can investigate possible attributes of objects right away in another cell.
Hint: You can define custom filter-functions and add them to the Environment.filters.
[3]:
templ1 = """
<h1>Actor definitions</h1>
{% set fexs = model.la.actor_exchanges.map("func_exchanges") %}
{% for actor in model.la.all_actors %}
<h2>{{ actor.name }}</h2>
<h3>Actor definition</h3>
<p>{{ actor.description }}</p>
<h3>Actor functions</h3>
{% for fnc in actor.allocated_functions %}
{% if loop.first %}
<p>The table below identifies functions of {{ actor.name }}.</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>Function</th>
<th>Description</th>
<th>Involved Subsystems</th>
</tr>
</thead>
<tbody>
{% endif %}
{% set outs = fexs.by_source.owner(fnc) %}
{% set ins = fexs.by_target.owner(fnc) %}
{% set subs = (ins + outs) | map(attribute="owner.name") | unique | sort %}
<tr>
<td>{{ fnc.uuid }}</td>
<td>{{ fnc.name }}</td>
<td>{{ fnc.description }}</td>
<td>{{ subs | join(', ') }}</td>
</tr>
{% if loop.last %}
</tbody>
</table>
<p style="margin-left: 1cm; font-weight: bold; font-style: italic;">Functions of {{ actor.name }}</p>
{% endif %}
{% else %}
<p style="text-align: left;">No actor functions were identified.</p>
{% endfor %}
{% endfor %}
"""
HTML(env.from_string(templ1).render(model=model))
[3]:
Actor definitions
Prof. A. P. W. B. Dumbledore
Actor definition
Principal of Hogwarts, wearer of the elder wand and greatest mage of all time.
Actor functions
The table below identifies functions of Prof. A. P. W. B. Dumbledore.
ID | Function | Description | Involved Subsystems |
---|---|---|---|
f708bc29-d69f-42a0-90cc-11fc01054cd0 | manage the school | ||
beaf5ba4-8fa9-4342-911f-0266bb29be45 | advise Harry |
Functions of Prof. A. P. W. B. Dumbledore
Prof. S. Snape
Actor definition
Good guy and teacher of brewing arts.
Actor functions
The table below identifies functions of Prof. S. Snape.
ID | Function | Description | Involved Subsystems |
---|---|---|---|
a7acb298-d14b-4707-a419-fea272434541 | Teaching | ||
4a2a7f3c-d223-4d44-94a7-50dd2906a70c | maintain a layer of defense for the Sorcerer's Stone |
Functions of Prof. S. Snape
Harry J. Potter
Actor definition
Actor functions
The table below identifies functions of Harry J. Potter.
ID | Function | Description | Involved Subsystems |
---|---|---|---|
aa9931e3-116c-461e-8215-6b9fdbdd4a1b | kill He Who Must Not Be Named |
Functions of Harry J. Potter
R. Weasley
Actor definition
Actor functions
The table below identifies functions of R. Weasley.
ID | Function | Description | Involved Subsystems |
---|---|---|---|
c1a42acc-1f53-42bb-8404-77a5c08c414b | assist Harry | ||
edbd1ad4-31c0-4d53-b856-3ffa60e0e99b | break school rules |
Functions of R. Weasley
Voldemort
Actor definition
Actor functions
No actor functions were identified.
Multiport
Actor definition
Actor functions
The table below identifies functions of Multiport.
ID | Function | Description | Involved Subsystems |
---|---|---|---|
9c1885f5-fac7-48fd-9d54-a11092508867 | LAF 1 |
Functions of Multiport
4.2. Beautiful SVG diagrams¶
Finally we will render a template that displays a diagram. There are many ways to do this and with jinja2 you have full control. We like our figures inside tables such that a caption can be displayed
[4]:
templ = """\
<h2>{{ component.name }}</h2>
{{ component.description }}
<table>
<caption>Figure {{ fig_id }}: {{ fig_caption | e }}</caption>
<tr><td>{{ figure.as_svg | safe }}</td></tr>
</table>
"""
diagram = model.diagrams.by_name("[LAB] Wizzard Education")
rendered = env.from_string(templ).render(
component=model.search("LogicalComponent").by_name("Hogwarts"),
fig_id=1,
fig_caption=diagram.name,
figure=diagram,
)
HTML(rendered)
[4]:
Hogwarts
This instance is the mighty Hogwarts. Do you really need a description? Then maybe read the books or watch atleast the epic movies.
Hint: Take notice of thejinja.Environment. Instead of handing the figure_table-markup over in the rendering call you could also set an insert_figure_as_table function, which you ideally defined before, in the environment globals or you can definemacrosright in the template. These tools can automate repetitive content placement.
4.3. Template inheritance¶
The last cell will present a routine where a full .html document is generated. Earlier rendered content were HTML-fragments to be precise. On top you can see a showcase on jinja’s template-inheritance functionality. The special DictLoader gives support for finding the base template called “template.html”. This was just needed because we are dealing with content in memory and didn’t create template.html in our FileSystem before. Per default jinja is using the FileSystemLoader when creating an Environment. It’s not a bad idea to check the Loaders they have, if you want to understand how template loading is working and/or plan on developing a pipeline system for document distribution.
[5]:
fig_templ = "".join(
(
'{% extends "template.html" %}',
"{% block content %}",
templ,
"{% endblock %}",
)
)
final_templ = """
<!DOCTYPE html>
<html>
<head>
<style>
caption {
caption-side: bottom;
border-top: 1px solid silver;
}
</style>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
"""
env = jinja2.Environment(
loader=jinja2.DictLoader({"template.html": final_templ})
)
rendered = env.from_string(fig_templ).render(
component=model.search("LogicalComponent").by_name("Hogwarts"),
fig_id=1,
fig_caption=diagram.name,
figure=diagram,
)
HTML(rendered)
[5]:
Hogwarts
This instance is the mighty Hogwarts. Do you really need a description? Then maybe read the books or watch atleast the epic movies.