Python-Skripte in Alfred-Workflows mit virtuellen Umgebungen nutzen

Schmuckbild lineart: Eine Person wird dargestellt, wie sie Code auf einem großen Computerbildschirm debuggt, dabei intensiv auf die vorliegende Aufgabe konzentriert. Die Umgebung entspricht der eines typischen Arbeitsplatzes.

In mei­nem Artikel „Obsidian Bookmark Seite, aber schö­ner” nut­ze ich für mei­nen Alfred-Workflow ein Python-Skript und die Python-Bibliotheken lxml und linkpreview.

Wie im Original-Skript am Ende des Artikels im Nachtrag zu sehen, habe ich Python mit Homebrew auf mei­nem Rechner instal­liert und folg­lich in der „Shebang” den Pfad #!/opt/homebrew/bin/python3 ange­ge­ben. Die benö­tig­ten Bibliotheken instal­lier­te ich glo­bal, was damals pro­blem­los funk­tio­nier­te.

Kürzlich stell­te ich jedoch fest, dass der Alfred-Workflow kei­nen Bookmark mehr erstell­te, da das Python-Skript die Bibliotheken nicht mehr fand. Warum? Das weiss ich nicht. Vielleicht war ein Upgrade von Homebrew Schuld, das eine neue Python-Version instal­lier­te, wel­che nun bei dem Versuch die Libraries im Terminal neu zu instal­lie­ren, den Fehler error: externally-managed-environment feu­er­te.

Obwohl man die­sen Fehler umge­hen kann, indem man pip3 mit dem Parameter: --break-system-packages auf­ruft, wäre der kor­rek­te Ansatz, für das Python-Skript eine eige­ne vir­tu­el­le Umgebung zu erstel­len. In die­ser kön­nen alle Abhängigkeiten des Python-Skripts iso­liert vom Rest des Systems instal­liert und ver­wal­tet wer­den. Und wenn ich es rich­tig ver­ste­he, wäre die­ses Umgebung dann auch etwas bes­ser gegen Änderungen bei dem nächs­ten Upgrade von Python geschützt.

Trotz inten­si­ver Suche fand ich aber kei­nen Hinweis dar­auf, wie man eine vir­tu­el­le Umgebung für ein Python-Skript in einem Alfred-Workflow umset­zen kann. Letztendlich habe ich mir jedoch eine Methode aus­ge­dacht, um eine sol­che Umgebung auch in einem Alfred-Workflow zu nut­zen. Ob dies die „ulti­ma­ti­ve” Lösung ist, ob ich etwas über­se­hen habe oder ob es bes­se­re Lösungen für das Problem gibt, kann ich nicht sagen. Diese Lösung funk­tio­niert aber, zumin­dest im Moment.

Lösungsweg

Meine Idee besteht dar­in, eine ent­spre­chen­de vir­tu­el­le Umgebung im Alfred-Workflow-Ordner im Terminal auf der Kommandozeile zu erstel­len und spä­ter beim Aufruf des Python-Skripts in dem Alfred-Workflow zu nut­zen. AUf der Kommandozeile kön­nen dann auch, wäh­rend die vir­tu­el­le Umgebung aktiv ist, die benö­tig­ten Libraries instal­liert wer­den.

Im dem neu­en Ansatz wird das Python-Skript dann nicht mehr direkt im Alfred-Workflow auf­ge­ru­fen, son­dern über ein zsh-Skript. in die­sem Skript wird zunächst die vir­tu­el­le Umgebung akti­viert. Erst dann wird das Python-Skript auf­ge­ru­fen, wobei das erstell­te Markdown zunächst in eine Variable geschrie­ben wird. Dies ermög­licht die Deaktivierung der vir­tu­el­len Umgebung nach der Ausführung des Python-Skripts und die anschlies­sen­den Übergabe des Inhalt der Variable via des echo-Befehls an die nächs­te Aktion, das bis­he­ri­ge zsh-Skript, das den Preview in die Bookmark-Datei für Obsidian schreibt.

Schritt 1: Ermittlung des Pfads des Workflow-Ordners

Alle rele­van­ten Daten eines Alfred-Workflows wer­den in einem Ordner gespei­chert, z.B. ~/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.6510DB4E-C3AC-4B75-90D8-3CE9B0CE91B2. Diesen Ordner fin­det man leicht, indem man auf das Ordnersymbol in einer Skript-Aktion unten rechts klickt. Dadurch der Workflow-Ordner im Finder geöff­net wird.

Schritt 2: Erstellen der virtuellen Umgebung

In die­sem Ordner muss die vir­tu­el­le Umgebung erstellt wer­den. Dazu muss man im Terminal das „Arbeitsverzeichnis” auf die­sen Ordner set­zen. Dazu gibt man im einem Terminal Fenster zunächst ein cd gefolgt von einem Leerzeichen ein, klickt danach den Titel im Finder-Fenster des Alfred-Workflow-Ordners und zieht das erschei­nen­de Ordnersymbol ins Terminal, wodurch der Pfad als Text erscheint. Mit dem Drücken der Eingabetaste wech­selt man dann in den ent­spre­chen­den Ordner. Mit dem Befehl pwd kann man über­prü­fen, ob man alles geklappt hat un man sich auf der Kommandozeile im rich­ti­gen Ordner befin­det.

Nun müs­sen die fol­gen­den Befehlen ein­ge­ge­ben wer­den:

# Erstellung der virtuellen Umgebung
>$ python3 -m venv env 
# Aktivierung der Umgebung
>$ source env/bin/activate
# Installation der linkpreview Library innerhalb der
(env) >$ pip install linkpreview
# Installation der lxml Library
(env) >$ pip install lxml
# Eventueller Test des Python-Skripts
# innerhalb der virtuellen Umgebung
(env) >$ python script.py https://ileif.de
# Deaktivierung der virtuellen Umgebung
(env) >$ deactivate

Innerhalb der vir­tu­el­len Umgebung kann auf die 3 beim Aufruf von python und pip ver­zich­tet wer­den, da in der Umgebung die Python Version läut, mit der man sie erstellt hat.

Schritt 3: Anpassungen des Workflows

Um den Workflow anzu­pas­sen, sind drei Änderungen erfor­der­lich. Zuerst muss die „Shebang”, also die ers­te Zeile des Skripts, ent­fernt wer­den, da das Python-Skript nun mit dem „vir­tu­el­len” Python der Umgebung aus­ge­führt wird. Das Python-Skript soll­te dann wie folgt aus­se­hen und in dem Workflow-Ordner als script.py gespei­chert sein.

import sys
from linkpreview import link_preview
import requests
from bs4 import BeautifulSoup
def get_first_paragraph(soup):
    paragraph = soup.find('p')
    if paragraph:
        return paragraph.get_text().strip()
    return "No excerpt available"
def generate_markdown_tile(url):
    try:
        # Versuch, den Inhalt der URL zu erhalten
        response = requests.get(url)
        response.raise_for_status()  # Löst eine Ausnahme aus, wenn der HTTP-Statuscode 4xx oder 5xx ist
    except requests.exceptions.HTTPError as e:
        # Gibt eine benutzerdefinierte Fehlermeldung zurück, wenn ein HTTPError auftritt
        return f"Error fetching URL: {e}"
    # Falls kein Fehler auftritt, fahre fort mit der normalen Verarbeitung
    soup = BeautifulSoup(response.content, 'html.parser')
    preview = link_preview(url, parser="lxml")
    if preview.image is None:
        # Falls kein Bild vorhanden ist, kann ggf. eins ergänzt werden
        # preview.image = "https://example.com/noimage.png"
    if not preview.description:
        description = get_first_paragraph(soup)
    else:
        description = preview.description
    markdown_tile = f"""
 <table padding=50px>
        <tr>
            <td> 
                <img src="{preview.image}" alt="{preview.title}" width="200" style="padding: 10px;"> 
            </td> 
            <td> 
                <a href="{url}" style="font-size: 16px;">{preview.title}</a>
                <br>
                <p>{description}</p>
            </td>
        </tr> 
    </table>
"""
    return markdown_tile
if __name__ == "__main__":
    import sys
    if len(sys.argv) != 2:
        print("Usage: python script_name.py <url>")
        sys.exit(1)
    url = sys.argv[1]
    markdown_tile = generate_markdown_tile(url)
    print(markdown_tile)

Die Python-Skript Aktion in der alten Alfred-Workflow-Lösung wird dann durch das fol­gen­de zsh-Skript ersetzt:

# Übergabe der URL
url={query}
# Aktivierung der virtuellen Umgebung
source env/bin/activate
# Ausführung des Python-Skripts mit dem übergebenen Parameter
markdown=$(python3 script.py "$url")
# Deaktivierung der virtuellen Umgebung
deactivate
# Ausgabe des Ergebnisses
echo "$markdown"

Der Workflow soll­te dann so aus­se­hen:

Mit die­sen Anpassungen soll­te der Workflow nun wie­der wie vor­ge­se­hen lau­fen. Dieses Verfahren erscheint mir auch als eine prak­ti­ka­ble Lösung für ande­re Python-basier­te Lösungen in Alfred-Workflows, die zusätz­li­che Libraries benö­ti­gen. Sollte es jedoch bes­se­re Ansätze geben, bin ich für Hinweise in den Kommentaren dank­bar.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert