fortlit: Zeit und Literatur

Ich bin auf einen Blogartikel zu einer Kindle basierten Uhr gestossen. Darin beschreibt Jaap Meijers wie er zu jeder Uhrzeit ein Zitat aus der Literatur auf dem Kindle darstellt. Er nutzt dazu Daten des Guardians, die Zeiten aus Büchern sammeln ließen. Sein Projekt inspirierte JohannesNE zu einer Webversion. Ich wieder rum wollte dies nutzen um mir bei jedem Shell Aufruf mir das passende Zitat zu der aktuellen Zeit anzeigen zu lassen. Daraus entstand fortlit. Ein kleines Python Script, welches man in die Shell seiner Wahl einbauen kann. Ich habe die Daten ein wenig gesäubert und ein schönes JSON daraus gebaut. Für die Einfachheit gibt es auch ein PEX-File. Viel Spaß!

Der Name ist eine Anlehnung an das kleine Programm Fortune.

If I was punctual in quitting Mlle. Reuter's domicile, I was at least equally punctual in arriving there; I came the next day at five minutes before two, and on reaching the schoolroom door, before I opened it, I heard a rapid, gabbling sound, which warned me that the "priere du midi" was not yet concluded.
- The Professor, Charlotte Brontë

xonsh: pipenv shell hack

Ich benutze nun schon ein paar Monate pipenv. Dies macht das verwalten von Projekt-Virtualenvironments einfach. Das Programm handelt auch die Listen und Abhängigkeiten zu Library's die für das eigene Projekt gebraucht werden. Es erstellt automatisch ein Pipfile. Dies soll irgendwann mal auch für pip implementiert werden um die lästige requirements.txt abzulösen.

So schön so gut. Meine Shells sind mal wieder in heavy rotation und so bin ich von zsh zu fish wieder bei xonsh gelandet. Mir liegt einfach Python. Das Problem mit pipenv ist nun das, dass Kommando pipenv shell nicht mehr funktioniert. Das aktivieren von virtualenvs scheint vor allem für die klassischen Shells konzipiert zu sein. xonsh benutzt seine eigene Verwaltung für die virtuellen Umgebungen: vox. Da alles andere zu funktionieren scheint, habe ich mir einfach einen Alias geschrieben, der auf der offiziellen pipenv-Funktion zum finden von virtuellen Umgebungen beruht.

def _pipenv_shell(args, stdin=None):
    import base64
    import hashlib
    import os
    import re

    if args[0] == 'shell':
        print('WARNING: using own xonsh alias function instead of pipenv shell')
        name = $PWD.split(os.sep)[-1]
        sanitized = re.sub(r'[ $`!*@"\\\r\n\t]', '_', name)[0:42]
        hash = hashlib.sha256(os.path.join($PWD, 'Pipfile').encode()).digest()[:6]
        encoded_hash = base64.urlsafe_b64encode(hash).decode()
        venv_name = sanitized + '-' + encoded_hash
        for venv in $(ls $VIRTUALENV_HOME).splitlines():
            if venv == venv_name:
                vox activate @(venv)
    else:
        ~/.local/bin/pipenv @(args)

Das füge ich dann dem alias-Dictionary hinzu:

aliases['pipenv'] = _pipenv_shell

Damit pipenv und vox das selbe Verzeichnis für die virtualenvs benutzen setze ich

$VIRTUALENV_HOME = os.path.join(os.path.expanduser('~'), '.local/share/virtualenvs')

Ich hoffe das ich diese Krücke irgendwann nicht mehr brauche...

Python Programme ausrollen mit PEX

Python hat mit vielen Vorurteilen zu kämpfen. Es sei langsam, nur komisches Gescripte, entweder zu dynamisch oder nicht dynamisch genug. Ein, zum Teil, verständlicher Vorwurf ist die Schwierigkeit des Ausrollens und Veröffentlichung von Paketen. Dem möchte ich nur zum Teil zustimmen. Als völliger Programmier-Noob tue ich es mir ab und zu wirklich schwer eine gute setup.py zu schreiben. Und muss ich dies tun, suche ich mich durch die verschiedensten Auswüchse meiner liebsten Python-Projekte auf GitHub. Was mir da immer hilft, ist das Beispiel Projekt. Daran kann man sich wunderbar entlang hangeln. Dann steht das nächste Problem an. Wie veröffentliche ich das ganze am besten. Einerseits lädt man es bei PyPI hoch. Auf der Benutzerseite fragt man sich jedesmal wie man das Paket am besten installieren. Man möchte niemals pip install als root ausführen und das Paket einfach blind in den globalen Raum installieren. Es gibt pip install --user foobar. Dies installiert es zwar global aber nur im eigenen Home-Verzeichnis. Auch nicht so toll und es kann natürlich zu Abhängigkeitesproblemen kommen, so sehr diese Raum mit anderen Paketen wächst und immer mehr verwulstet. Zum entwickeln benutzt man immer virtualenvs um sich seine abgeschlossenen Umgebungen zu bauen. Als Anwender ist das auch ziemlich unschön diese zu managen und am Ende verliert man dann doch den Überblich. Es gibt da pipsi. Dieses kleine Tool nimmt das Umgebungs-Management in die Hand. Funktioniert ganz wunderbar. Doch noch gibt es ganz andere Problemherde. Was ist mit Abhängigkeiten die ein Kompilieren nötig haben? Immer mehr Sachen müssen installiert werden und am Ende klappt es zwar irgendwie, schön ist es aber nicht. Gerade wenn das Paket von normalen Endnutzern benutzt werden soll.

Neben Tools wie pyinstaller gibt es, das von Twitter entwickelte, PEX. Dies macht sich zu eigen das Python Module aus Zip-Files importieren kann und Python wohl ziemlich vergibt was die Struktur von Zip-Files anbelangt. pex bastelt ein virtualenv, zippt es und knallt einen Shebang vor das Zip. Nun ist es ausführbar und man benötigt nur noch einen passenden Python Interpreter, alles was zum ausführen gebraucht wird, befindet sich in dem Zip. Dies ist ein anderer Ansatz als Python und seine Abhängigkeiten in ein Gesammtpaket zu schnüren. Python wird weiterhin auf dem System gebraucht. pex unterstützt sogar mehrere Python Versionen und Plattformen in einem PEX-File.

Ich habe das ganze mal für mein kleines Tool DoTheBackup gemacht:

pex -e dothebackup.ui:main --python=python3.6 --python=python3.5 --python=python3.4 --python-shebang=/usr/bin/python3 -o dist/dothebackup-`uname -s`-`uname -m`.pex --no-wheel --disable-cache -v .
  • -e dothebackup.ui:main: Dies ist der Entrypoint. Also die Funktion die ausgeführt wird, wenn das Programm ausgeführt wird. In diesem Fall eine click Funktion.
  • --python=python3.6: Hier beschreibt man die Python Version für die das File gebaut wird. Das schöne: Man kann mehrere angeben.
  • --python-shebang=/usr/bin/python3: Wir wollen es so universell halten wie möglich. Standardmäßig setzt pex hier die volle Version ein: /usr/bin/python3.6. Dies bringt uns aber nichts wenn es auch auf anderen Versionen laufen soll. /usr/bin/python3 should do the trick.
  • -o dist/dothebackup-uname -s-uname -m.pex: Dies beschreibt das Outputfile. In diesem Fall: dothebackup-Linux-x86_64.pex.
  • --no-wheel: Dies habe ich gebraucht wegen irgendeinen Fehlers. Er benutzt zum bauen keine wheels.
  • --disable-cache: pip benutzt keine Pakete aus dem Cache.
  • -v: Verbose.
  • .: Die Location.. also das aktuelle Verzeichnis.

Das bauen des PEX-Files lasse ich von Travis machen. Dies in meinem ultimativen Python Docker Image. Alles dazu findet ihr in dem Repo.

Hier noch ein kleines Video das PEX erklärt:

Audio überall. Mopidy und Snapcast

source:

Musik hören geht alle Zeiten wieder durch mehrere Iterationen. Von wild, ungetaggten, Files die ich bin XMMS abspielte, über die ersten Versuche mit einer Verwaltung unter Amarok und eine sehr düstere Zeit mit, dem vom Herzen tiefst gehassten, iTunes. Das erste Licht am Ende des Tunnels sah ich mit MPD und dem Feature von ncmpcpp Tags und Files umzubenennen und richtig zu taggen. Später dann mit Picard vom MusicBrainz Projekt. Dann kam ich schnell auf ein Kommandozeilen-Tool was dies auch ganz wunderbar kann: beets. Ab da wollte ich mir nicht mehr von anderen Programmen in meine Files schreiben lassen und erst recht nicht sortieren. Nun gibt es Clouds oder ähnliche Marketingkonstrukte und man installiert sich Software auf dem Heimserver wie Plex oder seine Open-Source-Alternative Emby. Mit diesen Diensten kann man seine Mediensammlung theoretisch über das Internet konsumieren. Klappt in den besten Fällen ziemlich gut. Gerade die UI von Emby kommt mir ziemlich behäbig vor (kann natürlich auch an meinem Server liegen). Ich erinnerte mich an meine unbeschwerte Zeit mit MPD. Alles flutschte und funktionierte mit den verschiedensten Clients. Nun gibt es ein kleines Projekt mit dem Namen Mopidy. Ein in Python geschriebener Audio Server der so tut als ob er MPD ist aber noch viel mehr zu bieten hat. Es gibt Plugins um auf die verschiedensten Bibliotheken zuzugreifen. Unter anderem Spotify, Youtube, Google Music, usw. Also habe ich mich mal ran gesetzt und ein Emby Plugin geschrieben. Nun kann ich das langsame UI umgehen und viele weitere Möglichkeiten machen sich auf.

Ich lasse Mopidy und Snapcast in einem Docker Container laufen. Snapcast bietet Multi-Room Streaming. Mehrere Devices die über das Netz ihren Output synchronisieren und das bei mir sogar über ein VPN hinweg. So kann ich Raspberry Pi's über all im Haus verteilen. Diese starten dann den snapclient nach dem booten und die Musik spielt ab.

Mopidy muss dazu den Output in ein FIFO-File schreiben. Dazu muss in der Config folgendes stehen:

[audio]
output = audioresample ! audioconvert ! audio/x-raw,rate=48000,channels=2,format=S16LE ! wavenc ! filesink location=/tmp/snapfifo

Den Server startet man mit den Defaults mit einem einfachen snapserver. Der Client sollte den Server über Avahi finden. Da ich den Port an meinem Container nicht freigegeben habe mache ich das manuell mit snapclient -h 192.168.1.77. Mein Docker Setup findet man hier.

xonsh aus Bash heraus starten

Noch ein kleiner Nachtrag zu dem Artikel von gestern. Heute wollte ich mal wieder in meiner Blogging Vagrant Docker Box alles auf den neusten stand bringen. Ich benutze dazu vagrant provision. Dies startet das Provisioning, also holt meine neuen Config-Files. installiert neue Software usw. Ansible benutzt SSH um auf die Vagrant Box zuzugreifen und dies war auf einmal ein Problem. Ansible lief sofort an die Wand.

/bin/sh: sudo -H -S -n -u root /bin/sh -c 'echo BECOME-SUCCESS-mtobhjchhszgqaaixzbsbsolwbuprmhn; LANG=de_DE.UTF-7 LC_ALL=de_DE.UTF-8 LC_MESSAGES=de_DE.UTF-8 /usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1469778394.25-104576843898387/apk; rm -rf \"/home/vagrant/.ansible/tmp/ansible-tmp-1469778394.25-104576843898387/\" > /dev/null 2>&1' && sleep 0: not found\r\n

Loggte ich mich ein und führe das Ansible Playbook manuell aus, kein Problem. Ich hatte xonsh als Login Shell eingerichtet und da läuft etwas schief. SSH ruft den Befehl per exec auf und da hat xonsh noch ein paar Probleme. Es gibt den Shell Befehl exec und die Python-Funktion exec. Diese sind schwierig zu unterscheiden und schon läuft es schief. Es wird an einem Fix gearbeitet. Der soll auch in den nächsten Tagen released werden. Bis dahin habe ich mich dazu entschieden meine Login-Shell wieder auf Bash umzustellen und daraus dann xonsh zu starten.

Dazu benutze ich das File ~/.profile. Dies ist dafür da Sachen beim Login auszuführen. Aber nur wenn es ~/.bash_profile und ~/bash_login nicht gibt. Ich erweitere also ~/.profile.

[ -f /usr/local/bin/xonsh ] && exec /usr/local/bin/xonsh

Wenn es /usr/local/bin/xonsh gibt dann führe es aus.

Ich nahm an das es so funktionieren würde. Falsch gedacht. Es gab einige Probleme. Das schwerwiegendste war das LightDM mich nicht mehr einloggen wollte. Es liest beim einloggen die ~/.profile und der exec Befehl behindert das ausführen von i3. Das starten von xonsh aus .profile heraus erschien mir als ein guter Weg. Aber sind wir mal ehrlich: Das ich xonsh nicht per chsh setzen kann, endet wohl oder übel in heftigstes, unsauberes gefrickel. Was macht man sonst an einem Samstag Vormittag? Nun kam ich zu folgender Lösung: Ich starte xonsh aus der ~/.bashrc mit:

[ -f /usr/local/bin/xonsh ] && exec /usr/local/bin/xonsh

Überraschung: die wird nicht immer geladen. SSH auf eine Alpine Linux Box warf mich in eine bash Shell. Also sollte man sich eine ~/.bash_profile anlegen mit folgenden Inhalt:

if [ -f ~/.bashrc ]; then
  . ~/.bashrc
fi

Nun geht erstmal wieder alles. Ein Workaround... aber was solls? Ich warte auf das nächste Krachen. Aber am meisten freue ich mich auf den Fix.