Polyglot Notebooks: Eine praktische Einführung

Nach Jupyter-Vorbild kombinieren die .NET-zentrierten Polyglot Notebooks Markdown und Code. Sie erlauben mehrere Sprachen im gleichen Notebook.

In Pocket speichern vorlesen Druckansicht 12 Kommentare lesen

(Bild: jakkaje879/Shutterstock.com)

Lesezeit: 14 Min.
Von
  • Dr. Eike M. Hirdes
  • Arthur Grot
Inhaltsverzeichnis

Microsofts Polyglot Notebooks nehmen sich die interaktiven Jupyter Notebooks zum Vorbild, sind jedoch .NET-zentriert. Dieser Artikel bietet eine praktische Einführung in die Polyglot Notebooks und beschreibt anhand eines Beispiels ihre grundlegenden Konzepte, wie zum Beispiel Einrichtung, Nutzung verschiedener Programmiersprachen, Teilen von Variablen und Darstellen von Ergebnissen. Einschränkungen in der Nutzung und weiterführende Themen kommen ebenfalls zur Sprache.

Polyglot Notebooks erlauben es Nutzerinnen und Nutzern, Code mit Dokumentation (im Markdown-Format) anzureichern, die Ergebnisse auszugeben und zu visualisieren. Die Code-Blöcke lassen sich einzeln direkt aus dem Notebook ausführen und sogar miteinander kombinieren. Im Unterschied zu den häufig für Python eingesetzten Jupyter Notebooks sind sie darauf ausgelegt, mehrere Sprachen innerhalb eines Notebooks auszuführen.

Dadurch bieten Polyglot Notebooks eine Vielzahl an Anwendungsmöglichkeiten in der Softwareentwicklung:

  • Sie können interaktive Berichte erzeugen, um schnell Daten auszugeben, zu analysieren und sogar zu visualisieren.
  • Die leichtgewichtige Kombination verschiedener Programmiersprachen erlaubt die schnelle Umsetzung von Prototypen und Proof-of-Concepts.
  • Häufig benötigte Skripte lassen sich inklusive der Dokumentation ablegen, etwa für das Onboarding neuer Mitarbeiter.
Arthur Grot

Arthur Grot ist Softwareentwickler bei AIT GmbH & Co. KG. Er hilft dabei die Visionen seiner Kunden mit .NET- und Azure-Technologien in innovative Cloud- und IoT-Softwarelösungen umzusetzen.

Dr. Eike M. Hirdes

Dr. Eike M. Hirdes ist Azure DevOps Enabler bei der AIT GmbH & Co. KG. Er berät Unternehmen in den Bereichen agile Prozesse, Automatisierung und Administration, hauptsächlich im Bereich Azure DevOps. Als Autor der Blogserien AIT Tech Talk und Azure DevOps Nugget veröffentlicht er regelmäßig Artikel auf dem AIT Blog.

Um Polyglot Notebooks zu verwenden, ist zunächst die Erweiterung im Sourcecode-Editor Visual Studio Code (VS Code) zu installieren. Das erfordert mindestens das .NET SDK 7.0 sowie die Polyglot Notebooks Extension aus dem Visual Studio Marketplace. Die Extension installiert automatisch ebenfalls benötigte Jupyter Extensions.

Nach der Installation der benötigten Abhängigkeiten lässt sich mit der Befehlspalette in VS Code (Ctrl+Shift+P) und dem Befehl "Polyglot Notebook: Create default notebook" ein Notebook erstellen. In diesem Beispiel wird zunächst das .dib-Format ausgewählt. Eine Erläuterung der unterschiedlichen Dateiformate mit ihren Vor- und Nachteilen folgt später. Nach der Wahl des Formats öffnet sich ein Notebook mit ".NET Interactive" als ausgewähltem Kernel (Abbildung 1). Der Kernel ist ein im Hintergrund laufender Service, der den Code aus dem Notebook ausführt und das Ergebnis als Output zurückgibt. Jede Sprache hat einen eigenen Subkernel, der für die Ausführung der jeweiligen Sprache zuständig ist.

Neu erstelltes Polyglot Notebook in VS Code (Abb. 1)

Die Kopfleiste zeigt nicht nur den Kernel an, sondern ermöglicht es auch, zusätzlich Code- und Markdown-Zellen anzulegen. Eine Zelle ist eine strukturelle Einheit im Notebook, die entweder Code einer bestimmten Sprache oder Markdown enthält. Damit ist es möglich, Code und Markdown-Dokumentation im Wechsel zu schreiben (Abbildung 2).

Polyglot-Notebook-Übersicht (Abb. 2)

Der Befehl "Run All" führt alle Zellen in dem Notebook nacheinander aus und zeigt den Output an. Sollten beim Ausführen Probleme auftreten und der Kernel durch einen Fehler in einen falschen Zustand geraten, lässt er sich neustarten. Im "Variables"-Menü ist die Ansicht aktivierbar, mit der alle Variablen im Kernel einsehbar sind. Jede Code-Zelle lässt sich auch einzeln über das "Play"-Symbol (▷) ausführen.

Polyglots Multi-Language-Support umfasst derzeit folgende Programmier- und Auszeichnungssprachen: C#, F#, PowerShell, JavaScript, SQL, KQL, HTML, Mermaid, Python und R. VS Code bietet für die Sprachen Syntax-Highlighting, Code-Autovervollständigung und weitere Features an. Seit Dezember 2023 wird auch das HTTP-Protokoll von Polylgot unterstützt, das heißt, es können direkt von Notebooks aus HTTP-Anfragen gesendet und die Antworten angezeigt werden.

Zur Demonstration der Features von Polyglot Notebooks dient ein Beispiel, das von Grund auf neu erstellt wird. Die Ausgangssituation besteht darin, für eine Rückrufaktion schnell herauszufinden, welche Kunden ein bestimmtes Produkt gekauft haben. Diese Daten liegen in einer SQL-Datenbank. Der hierfür benötigte SQL-Connection-String soll im ersten Schritt aufgebaut und dafür die Zugangsdaten abgefragt werden. Im nächsten Schritt wird eine Verbindung mit der Datenbank hergestellt und anschließend werden die benötigten Daten abgerufen. Die Daten werden im letzten Schritt als Diagramm dargestellt, um schnell zu erkennen, welcher Kunde wie stark betroffen ist.

Das Beispiel zeigt, wie sich verschiedene Sprachen und Technologien (Polyglot Magic Commands, C#, SQL und Mermaid, das aus Markdown Grafiken gestaltet) in einem Notebook nutzen lassen und wie diese miteinander interagieren. Magic Commands sind spezielle Befehle für Polyglot Notebooks, die es ermöglichen, Code in anderen Sprachen innerhalb einer Code-Zelle auszuführen oder die Notebook-Umgebung zu steuern, um beispielsweise Variablen zu setzen.

Nachdem Polyglot eingerichtet ist, erfolgt das Erstellen des Connection-String für die SQL-Datenbank. Das erfordert vier Parameter: die URL des SQL-Servers, den Namen der Datenbank, einen Benutzernamen und das Passwort. Die ersten beiden Parameter sind fest definierbar, die letzten beiden sollen Nutzerinnen und Nutzer eingeben.

Mittels Markdown lassen sich sowohl das ganze Notebook als auch einzelne Code-Zellen erklären. Für den vorliegenden Connection-String sollen einerseits Variablen fest definiert, aber auch Nutzende nach Zugangsdaten gefragt werden. Die folgende Zelle im Markdown-Format dient der Dokumentation und Anleitung:

## Create SQL Connection String
Run the following cell to create a sql connection string.
You will be prompted to input username and password.

Als Nächstes erstellt man eine C#-Zelle, um die URL des SQL-Servers (serverUrl) und den Namen der Datenbank (databaseName) als Variablen fest zu definieren. Der Benutzername (username) und das Passwort (password) sollen abgefragt werden. Die Umsetzung für die Abfrage mit Polyglot Magic Commands findet sich in Listing 1. Nach dem Aufruf der Magic Commands in der C#-Zelle sind die erstellten Variablen im C#-Kernel nutzbar.

// Fetch user input with magic commands
var serverUrl = "polyglot.database.windows.net";
var databaseName = " polyglot";

#!set --value @input:"Please provide a username" --name username
#!set --value @password:"Please provide a password" --name password

Listing 1: Abfrage von Benutzerdaten mit Magic Commands in einer C#-Zelle

Wer das lieber komplett in C# umsetzen möchte, muss erst die Bibliothek Microsoft.DotNet.Interactive, hier als NuGet-Package, einbinden. Die C#-Alternative ist in Listing 2 zu sehen.

// Fetch user input with C#
using Microsoft.DotNet.Interactive;

var serverUrl = "polyglot.database.windows.net";
var databaseName = "polyglot";

var username = await Kernel.GetInputAsync("Please provide a username");
var password = await Kernel.GetPasswordAsync("Please provide a password");

LIsting 2: Abfrage von Benutzerdaten in einer C#-Zelle

Beide Arten der Eingabe unterstützen "Input" und "Password". Bei "Input" wird die Eingabe im Klartext angezeigt, bei "Password" hingegen maskiert. Achtung: Nur die Passworteingabe ist maskiert, der Inhalt ist in der Variablenansicht von Polyglot sichtbar (Abbildung 3).

Achtung! Abgefragte Passwörter sind in der Variablenansicht sichtbar (Abb. 3).

Der SQL-Connection-String wird aus den zuvor definierten Variablen zusammengesetzt:

string connectionString = $"Server={serverUrl};Database={databaseName}; User
Id='{username}';Password='{password}';Persist Security Info=true;Integrated Security=false;";

Die Beispieldaten in SQL liefert hier die AdventureWorks-Trainingsdatenbank von Microsoft, die sowohl auf dem eigenen SQL-Server als auch direkt als Azure SQL Database bereitgestellt werden kann. Eine Anleitung stellt Microsoft zur Verfügung.

Dabei ist zu beachten, dass im oben verwendeten Connection String Integrated Security=false gesetzt wurde, da das Beispiel die SQL Server Authentication nutzt. Sollte man Windows Authentication verwenden, ist das anzupassen.

Für die Nutzung von SQL ist als Erstes noch die Bibliothek Microsoft.DotNet.Interactive.SqlServer zu installieren. Da sie ein externes Paket ist, ist sie über die #r-Syntax zu referenzieren (siehe auch Referencing external assemblies):

// magic commands for SQL Server connection
#r "nuget: Microsoft.DotNet.Interactive.SqlServer, *-*"

Erst danach ist der SQL-Connection-String verwendbar, um einen Polyglot-SQL-Subkernel zu erstellen. Das geschieht mit dem Magic Command #!connect. Um die C#-Variable connectionString verwenden zu können, ist der Name des Subkernels anzugeben (hier @csharp, siehe nächstes Listing). Das Thema Variable Sharing folgt in den nächsten Abschnitten tiefergehender.

Im nächsten Schritt wird ein SQL-Subkernel mit dem SQL Connection String in einer C#-Zelle erstellt:

#!connect mssql --kernel-name DemoKernel @csharp:connectionString

Nutzende des Notebooks können nun entweder die Standard-productId verwenden oder sie abfragen lassen. So erfolgt das Festlegen der Standard-productId in einer SQL-Zelle:

-- use default productId
#!set --name productId --value "988"
Sie lässt sich wie folgt abfragen:
-- user defined productId
#!set --name productId --value @input:productId

Als nächstes muss die SQL-Query erstellt werden. Sie verbindet die beiden Tabellen SalesOrderHeader und SalesOrderDetail, um die Kundennummer (CustomderId) und Anzahl der gekauften Produkte pro Bestellung (OrderQty) zu erhalten. Durch das Speichern des Ergebnisses in einer Variable lässt es sich außerhalb der Query weiterverwenden. In diesem Beispiel (Listing 3) ist es die Variable QueryResult.

#!sql-DemoKernel --name QueryResult
SELECT CustomerID, SalesOrderHeader.SalesOrderID, ProductID, OrderQty
  FROM SalesLT.SalesOrderHeader JOIN SalesLT.SalesOrderDetail
          ON SalesLT.SalesOrderDetail.SalesOrderID = SalesLT.SalesOrderHeader.SalesOrderID
WHERE ProductID = @productId
ORDER BY OrderQty desc

Listing 3: Aufruf der SQL-Query aus Polyglot in einer SQL-Zelle

Direkt nach dem Ausführen der SQL-Zelle erscheint die Antwort im Notebook (siehe Abbildung 4).

Ausgabe des SQL-Befehls in Listing 3 (Abb. 4)

Nach dem Ausführen der SQL-Query soll die Ausgabe dazu dienen, eine Grafik zu generieren. Zunächst muss aber die Variable aus dem SQL-Subkernel in den C#-Subkernel geteilt werden. Hierfür verwendet Polyglot Notebooks das Konzept des Variable Sharing. Dadurch lassen sich Variablen zwischen fast allen unterstützten Sprachen und Technologien austauschen, außer Mermaid und HTML (siehe Tabelle 1).


Language

Variable sharing
C#
F#
PowerShell
JavaScript
SQL
KQL (Kusto Query Language)
Python
R
HTML
HTTP
Mermaid

Tabelle 1: Aktuell unterstützte Sprachen mit Vermerk, ob Variablenaustausch möglich ist

(Quelle: Microsoft)

In diesem Beispiel war es nötig, die Ausgabe der SQL-Query in einer Variablen QueryResult zu speichern (siehe erste Zeile in Listing 3). Andere Sprachen arbeiten nativ mit Variablen. Diese liegen in der Regel bereits im Arbeitsspeicher und lassen sich ohne zusätzlichen Befehl teilen. Welche Variablen in welchem Subkernel zu finden sind, lässt sich in der zuvor erwähnten Variablenansicht nachschlagen (siehe Abbildung 2).

In beiden Fällen lässt sich als Nächstes eine neue Zelle in einer anderen Sprache erstellen. Die Zelle beginnt mit dem Magic Command #!share, der als --from-Parameter die Information erhält, aus welchem Subkernel welche Variable in den aktuellen Subkernel geteilt werden soll. Das vorliegende Beispiel soll sowohl die Ausgabe der Query (QueryResult) als auch die Produkt-ID (productId) im C#-Subkernel nutzen. Nach Ausführung der folgenden Zelle stehen beide Werte als C#-Variablen zur Verfügung. Als Parameter werden der Quell-Subkernel und der Variablenname übergeben:

#!share --from sql-DemoKernel QueryResult
#!share --from sql-DemoKernel productId

Durch das JavaScript-basierte Tool Mermaid lassen sich in Polyglot Notebooks Abbildungen generieren. Allerdings unterstützt Mermaid keine Variablen. Daher wird an dieser Stelle der Mermaid-Code mithilfe von C# dynamisch erzeugt, basierend auf der Ausgabe der SQL-Query. Der erzeugte Mermaid-Code kann einen Befehl im Mermaid-Subkernel aufrufen, der dann die Grafik generiert.

Die benötigten Variablen wurden zu diesem Zeitpunkt bereits aus dem SQL-Subkernel geteilt. Mit der Produkt-ID kann man in einem String die Basis für das Tortendiagramm mitsamt Überschrift erzeugen (siehe Listing 4).

An diese Basis werden nun in einer Schleife nacheinander alle Einträge aus dem Ergebnis der Query angehängt, um im Anschluss mit dem Kernel.Root.SendAsync()-Befehl asynchron Code auf dem Mermaid-Subkernel auszuführen.

#!share --from sql-DemoKernel QueryResult
#!share --from sql-DemoKernel productId

var mermaidCodeStringBuilder = new StringBuilder();
mermaidCodeStringBuilder.AppendLine($"""
pie showData
    title Customers who bought product '{productId}'

"""
);

foreach (var element in QueryResult[0].Data){
    var customerId = element.FirstOrDefault(x => x.Key == "CustomerID").Value ?? "Unknown";
    var orderQty = element.FirstOrDefault(x => x.Key == "OrderQty").Value ?? "0";
    mermaidCodeStringBuilder.AppendLine($"\"{customerId}\": {orderQty}");
}

await Kernel.Root.SendAsync(new SubmitCode(mermaidCodeStringBuilder.ToString(), "mermaid"));

Listing 4: Generierung von Mermaid-Code und Aufrufen des Mermaid-Subkernels in einer C#-Zelle

Das durch Mermaid generierte Tortendiagramm in Abbildung 5 zeigt, welcher Kunde wie oft das Produkt 988 gekauft hat und somit von der Rückrufaktion betroffen ist.

Generierte Abbildung mit Mermaid. Darstellung in einem Tortendiagramm, welche Kunden-IDs wie oft das Produkt 988 gekauft haben (Abb. 5).

Für Polyglot Notebooks stehen die zwei Dateiformate .ipynb und .dib zur Verfügung. Bei .ipynb handelt es sich um das klassische Jupyter-Notebook-Format, das den Output des Codes in der Datei mit abspeichert. Output bedeutet hierbei die Ausgabe von ausgeführtem Code, der unter Umständen sensible Daten, wie Secrets oder personen- oder firmenbezogene Daten, enthalten kann und das Einchecken von Notebooks in Quellcodeverwaltungssystemen erschwert. Es ist daher sicherzustellen, dass der Output gelöscht wird. Das Format gewährleistet Kompatibilität zum Jupyter-Format und ermöglicht es, Notebooks auch in anderen Anwendungen zu bearbeiten, wie zum Beispiel JupyterLab.

Das .dib-Format hingegen speichert den Code in einer eigenen Struktur, die nicht mit Jupyter-Produkten kompatibel ist. Der Vorteil dieses Formats ist jedoch, dass das Notebook die Ausgabe des ausgeführten Codes nicht enthält, was die Arbeit mit sensiblen Daten wie Secrets und einem Codeverwaltungssystem wie Git erleichtert. Zusätzlich ist die Struktur flacher als die JSON-Struktur von .ipynb-Dateien, wodurch Codeänderungen bei der Code-Review leichter nachvollziehbar sind.

Sofern die Kompatibilität mit dem Jupyter-Format nicht erforderlich ist, sollte aufgrund der beschriebenen Vorteile mit dem .dib-Format gearbeitet werden.

Leider gibt es ein paar Stolpersteine und Einschränkungen, die beim Erstellen des hier verwendeten Beispiels aufgefallen sind. Zum einen besteht das genannte Problem mit dem .ipynb-Dateiformat, das aber nicht Polyglot-Notebooks-spezifisch ist. Das alternative Format von Poylglot Notebooks (.dib) ist dabei eine geeignete Lösung. Zudem ist die offizielle Dokumentation an diversen Stellen verbesserungswürdig, weist Lücken auf und verzögert das eigentliche Ziel der Umsetzung. Da Mermaid und HTML keine Variablen unterstützen, ist die dynamische Generierung von Diagrammen oder HTML umständlich.

Ein größeres Problem liegt allerdings darin, dass der Polyglot-Notebook-Kernel unter unbekannten Umständen in einen fehlerhaften Zustand geraten und einen Kernel-Neustart erfordern kann. Funktionieren IDE-Features wie Code-Vervollständigung oder Syntax- und Error-Highlighting nicht mehr, lässt sich das gegebenenfalls durch zwischenzeitliches Selektieren einer anderen Code-Zelle mit anschließendem Zurückwechseln beheben. Notfalls ist ein kompletter Neustart von VS Code erforderlich.

Trotz der Stolpersteine sind die Polyglot Notebooks ein sehr nützliches Tool. Sie bieten die Möglichkeit, verschiedene Sprachen schnell miteinander zu kombinieren und die Ergebnisse der Ausführung auszugeben sowie in Tabellen oder Mermaid-Diagrammen darzustellen. Somit stellen sie eine Alternative zu LINQPad und RoslynPad dar, um schnell Prototypen und Proof-of-Concepts zu erstellen. Im Markdown-Format lassen sich die Lösungen direkt für Nicht-Entwicklerinnen und -Entwickler dokumentieren und interaktive Berichte generieren.

Über den Umfang dieses Artikels hinaus gibt es noch viele weitere spannende Themen, die es sich lohnt anzuschauen. Unter anderem lassen sich über Entity Framework C#-Modelle von Datenbanken erstellen, auf die man dann aus Polyglot Notebooks zugreifen kann. Weiterhin kann man direkt aus einem Polyglot Notebook heraus auf andere Notebooks verweisen und Programme daraus starten, sodass es nicht nötig ist, alles in einem einzelnen Notebook zu beschreiben. Durch die integrierten Mermaid-Diagramme eignen sich die Notebooks auch für technische Dokumentation, etwa über Klassen- und Sequenz-Diagramme. Weitere Anregungen finden sich in den Beispielen des GitHub-Repositorys von Polyglot Notebooks.

(mai)