Sonntag, 23. März 2025

openpyxl - data_only beware

openpyxl is a popular library to access Excel data via python.

However, there is an unexpected side effect if you have formulas in your Excel workbook.
I'm not the first to find that out the hard way - so here is another warning.

If you have a formula in your xlsx-spreadsheet like "=B1-A1" the spreadsheet app (Excel, LibreOffice etc.) stores the formula as well as the calculated result which you can see in the cell.

If the formula is stored e.g. in "C1", and you open the file in your python script like this:

    import openpyxl

    wb = openpyxl.load_workbook(filename="testwb.xlsx")
    print(sheet["C1"].value)
    # prints the formula

The result will be the formula, not the calculated value.

In order to access the calculated value, you have to open it with data_only set to True.

    import openpyxl

    wb = openpyxl.load_workbook(filename="testwb.xlsx", data_only=True)
    print(sheet["C1"].value)
    # prints the calculated value


The catch is that if you try to save the workbook later with for example

    wb.save("testwb2.xlsx")

all formulas in the entire workbook are gone (if the workbook was loaded with data_only=True).

If you must have access to the results of the formulas that Excel has calculated, the work-around is to open two instances of the workbook: one with and one without data_only.
Make the one using data only also read_only, just in case

    wb = openpyxl.load_workbook(filename="testwb.xlsx", data_only=False)
    wb_dataonly = openpyxl.load_workbook(filename="testwb.xlsx", data_only=True, read_only=True)

This way you have access to the calculated values via wb_dataonly and you can add data and save the result using wb... and yes, you have to keep in mind that the two instances go out of sync as soon as you modify wb.


Donnerstag, 13. März 2025

Bitwarden's ssh-agent

 

The most secure way of accessing external ssh servers is the use of ssh-keys and I'm deploying them regularly. The private key is stored on my hard drive, and I'm protecting it with a passphrase. Remembering the passphrase especially for a site you rarely use has always been a PITA.

Since the beginning of this year (2025) the password manager Bitwarden allows you to manage your ssh-keys as well. So I gave it a try.

Under Ubuntu you have to install the Bitwarden desktop app from the snap repo and connect it to your vault. After setting SSH_AUTH_SOCK in your .bashrc the desktop app acts as your ssh-agent:

export SSH_AUTH_SOCK=/home/your_user_name/snap/bitwarden/current/.bitwarden-ssh-agent.sock

While importing keys I've noticed that Bitwarden only likes the "new ones" (that use Ed25519 elliptic curve crypto).  It was a good opportunity to re-key.

Bitwarden itself always generates Ed25519 keys.  The reason might be that this special kind of key allows you to calculate the public key from the private one.

When importing keys into Bitwarden I had to provide the passphrase to my keys, which made me hesitate - perhaps a Gibson-ian reaction :) - because it meant that the key is stored "naked" in the Bitwarden vault.
I would have liked it more if Bitwarden would have provided the passphrase on request - but this would have made integration as ssh-agent impossible.

How is the risk mitigated?

  • You are prompted each time a ssh-key is requested from the vault, which is an improvement over the regular ssh-agent.
  • There is no indication where a key can be used, if you don't put it into the comment.
  • Up until now Bitwarden has a spotless record of securing your vault.


Integration with .ssh/config

The .ssh/config file allows you to configure additional items like hostname, user name, port, the ssh-key AKA the identity file, and port forwarding rules for a given host.  This way you don't have to specify them every time in a ssh command.  If and only if an identity file is configured for a given host a ssh-agent will be queried.

If you generate the ssh-key within Bitwarden the private key is stored in your vault.  But what do you put into the IdentityFile field to make the system query the Bitwarden app?

As Kiko Piris pointed out here, it needn’t be the private key that is stored on the hard drive.  It might also be the public key.  This will not help you if the Bitwarden app is not running, but at least it will make ssh try to contact the ssh-agent.

You might have noticed during the regular use of the Bitwarden app that the IdentityFile field has to be present in the .ssh/config file but the key file itself is not used.
I still have my passphrase-protected private keys on my hard drive with the IdentityFile field pointing to them.  But when I log in with ssh I’m not queried for the passphrase, instead the Bitwarden Desktop app pops up requesting confirmation to use the key it has stored in its vault.

The Bitwarden app has a button that copies the public key into the clipboard which can be used to create the public key file which then can be specified in the IdentityFile field.

It is - as usual - a compromise between security and convenience.  If it fits your risk profile it's a nice tool.

Montag, 3. März 2025

matplotlib - The secret of the vanishing x-ticks

The versions:
* Ubuntu 24.04
* python 3.12.3
* matplotlib 3.10.0

I've searched for this solution for days. So I describe it here for anyone who might need ist.

The goal is rather simple:

I want to create a figure with three subplots, each with an independent x-axis because I want to display data with different time periods.

I expected to get something like this:

Three separate subplots, each with its own labels on the x-axis showing a grid as well as date and time.

And that is exactly what you get if execute this simple program.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from datetime import datetime

import matplotlib as mp
print(mp.__version__)
# 3.10.0

FORMAT_MAJOR = False
FORMAT_MINOR = False

# Format definitions
# not all are used

years = mdates.YearLocator()            # every year
months = mdates.MonthLocator()          # every month
days = mdates.DayLocator()              # every day
hours = mdates.HourLocator()            # every hour
years_fmt = mdates.DateFormatter('%Y')
month_fmt = mdates.DateFormatter('%m')
day_fmt = mdates.DateFormatter('%d')
hour_fmt = mdates.DateFormatter('%H')

fig, axs = plt.subplots(nrows=3, ncols=1, figsize=(187, 12), sharex="none")

datx0 = [ datetime(2025, 1, 31), datetime(2025, 2, 2), datetime(2025, 2, 3) ]
daty0 = [100, 200, 150]

datx1 = [ datetime(2025, 2, 4), datetime(2025, 2, 5), datetime(2025, 2, 7) ]
daty1 = [150, 100, 150]

datx2 = [ datetime(2025, 2, 1), datetime(2025, 2, 4), datetime(2025, 2, 5) ]
daty2 = [200, 200, 150]

axs[0].plot(datx0, daty0)
axs[1].plot(datx1, daty1)
axs[2].plot(datx2, daty2)

for pos in range(3):  # 0..2
    curraxs = axs[pos]
    curraxs.grid(True)

    if FORMAT_MAJOR:
        curraxs.xaxis.set_major_locator(days)
        curraxs.xaxis.set_major_formatter(day_fmt)
        curraxs.tick_params(axis="x", which="major", rotation=45)

    if FORMAT_MINOR:
        curraxs.xaxis.set_minor_locator(hours)
        curraxs.xaxis.set_minor_formatter(hour_fmt)
        curraxs.tick_params(axis="x", which="minor", rotation=90)

    # only 1% "slack" at each end
    curraxs.set_xmargin(0.01)

print(axs[0].xaxis.get_majorticklabels())
print(axs[1].xaxis.get_majorticklabels())
print(axs[2].xaxis.get_majorticklabels())

plt.show()


As you can see, there are three data series.

  • The first from 2025-1-31 to 2025-2-3.
  • The second from 2025-2-4 to 2025-2-7.
  • The third from 2025-2-1 to 2025-2-5.

The date ranges have been chosen to overlap slightly.  The y-data has no special meaning other than to show different graphs in the subplots.

The vanishing act occurs if you try to format the x-axis labels.

This is usually done with:


import matplotlib.dates as mdates

days = mdates.DayLocator()
day_fmt = mdates.DateFormatter('%d')

axs.xaxis.set_major_locator(days)
axs.xaxis.set_major_formatter(day_fmt)


This works fine for a single axis.  If you have more than one, strange things happen:

That’s the output with the variable FORMAT_MAJOR set to True.

The missing x-ticks become more apparent if you set FORMAT_MINOR to True as well.


  • In the first subplot the ticks for 2025-01-31 are missing.
  • In the second subplot the ticks from 2025-02-05 and above are missing.
  • Only the third subplot has all x-ticks.

The output of the get_majorticklabels() of the three axis at the end of the program...

print(axs[0].xaxis.get_majorticklabels())
print(axs[1].xaxis.get_majorticklabels())
print(axs[2].xaxis.get_majorticklabels())

...gives an indication of what happened:

They are all identical – using the values from the last call 2025-01-01 to 2025-02-05.
Which explains the missing parts at the beginning of the first subplot and the missing days at the end of the second.

So, how to fix this?

It seems that – contrary to what one might believe – the xxxxLocator() calls are not simply generators that produce ticks as requested.  They seem to keep some kind of internal state – in this case of the last subplot – influencing all the other uses.

You have to move them into the for-loop so that for each axis a “new” xxxxLocator() is created.

...

for pos in range(3):  # 0..2
    curraxs = axs[pos]
    curraxs.grid(True)

    days = mdates.DayLocator()
    hours = mdates.HourLocator()


    if FORMAT_MAJOR:
        curraxs.xaxis.set_major_locator(days)
        curraxs.xaxis.set_major_formatter(day_fmt)
        curraxs.tick_params(axis="x", which="major", rotation=45)
        
    ...


This gives the expected result:





Sonntag, 12. Januar 2025

Einfache Monats-e-Rechnung

e-Rechnung

Vor einigen Wochen habe ich über meinen GitHub-Account eine Java-Anwendung zur Erstellung einer einfachen Monats-e-Rechnung verfügbar gemacht.

Ich weise jedoch darauf hin, dass ich weder ein Steuerberater noch ein Anwalt bin, und die von mir getroffenen Entscheidungen – so nachvollziehbar sie auch sein mögen – sich als falsch herausstellen könnten. Für Hinweise bin ich – schon im Eigeninteresse – dankbar.


Was also ist eine e-Rechnung?

Aus Sicht eines Programmierers ist eine e-Rechnung nichts anderes als eine XML-Datei in einem gesetzlich vorgeschriebenen Format, die also solche oder in eine PDF-Datei eingebettet verschickt werden kann, und nach dem Wachstumschancengesetz ab 2025 bzw. spätestens 2028 im B2B-Geschäftsverkehr verwendet werden muss. Kurz gesagt: die Papierrechnung ist tot – so gut wie...

Der Grund ist eigentlich nachvollziehbar.

Stellen Sie sich ein Unternehmen vor, das von seinen Lieferanten Hunderte von Rechnungen im Monat erhält. Dieses Unternehmen hat mit Sicherheit eine Software, die diese Rechnungen verwaltet und rechtzeitig anweist.

Außerdem hat das Unternehmen jemanden, der diese Rechnungen – egal, ob sie auf Papier oder als PDF vorliegen – sichtet, die wichtigen Daten heraussucht und in diese Software eingibt. Einfacher wäre es, wenn die Daten bereits in einer computerlesbaren Form vorliegen würden – und genau das ist eines der Ziele der e-Rechnung.

Bei großen Firmen wird man das mit einem Update in der sowieso vorhandenen Software lösen – bei Einzelkämpfern und kleinen Unternehmen ohne eine solche Software wird es jedoch schwierig, diese gesetzliche Anforderung umzusetzen.

Man könnte auf die Idee kommen, diese XML-Datei (die im Grunde auch nur eine Textdatei ist) von Hand zu erstellen. Das ist jedoch umständlich und fehleranfällig, und irgendwie muss man das XML noch in die PDF-Datei bekommen…

Das heißt, man wird um irgendeine Software nicht herumkommen.

Für die „nackte“ XML-Datei gilt die XRechnung-Spezifikation, für die kombinierte PDF+XML-Variante ist es ZUGFeRD.

Eine ZUGFeRD-Rechnung hat den Vorteil, dass man den PDF-Teil mit einem beliebigen PDF-Viewer anzeigen kann, für die XRechnung ist ein spezieller Viewer notwendig.

Da eine ZUGFeRD-Rechnung aus einem PDF-Teil und einen XML-Teil besteht, existiert natürlich die Gefahr, dass die Daten, die ein Mensch im PDF sehen kann, nicht denen entsprechen, die im XML kodiert sind. Beim Erstellen ist man daher gesetzlich verpflichtet, dass die Informationen in beiden identisch sind. Im Zweifelsfall gilt aber das XML.

Im Netz gibt es einige kostenlose Angebote zum Erzeugen dieser PDF-Rechnungen – auch von namhaften Anbietern – bei denen man die benötigten Daten in eine Maske einträgt und ein konformes PDF erhält… natürlich direkt neben den kostenpflichtigen Angeboten, mit denen das alles viel einfacher geht.

Auch gibt es Open-Source-Fakturierungssoftware, die e-Rechnungen erstellen kann, oder Makros für LibreOffice. Bei den meisten würde ich 90% der Funktionalität nie brauchen und die Funktion, die ich brauche – die Monatsrechnung – fehlt häufig.

Was ist so besonderes an einer Monatsrechnung?

Bei den meisten „einfachen“ Rechnungen bezieht sich die Rechnung nur auf genau EINE Bestellung. Wenn Sie z.B. in einem Webshop einkaufen, bekommen Sie eine Rechnung für genau diese eine Bestellung. Diese 1:1-Beziehung liegt beim überwiegenden Teil aller Rechnungen vor.

Eine Monatsrechnung fasst mehrere Bestellungen aus einem Monat in einer Rechnung zusammen. Damit bezieht sich EINE Rechnung auf MEHRERE Bestellungen.

Auch dieser Fall ist in der ZUGFeRD-Spezifikation vorgesehen, nur leider wird er meist nicht in der Software implementiert.

Was braucht man für eine ZUGFeRD e-Rechnung?

Für den PDF-Teil reicht die übliche Textverarbeitung wie LibreOffice oder Word. Beide haben die Möglichkeit, den Text als PDF/A zu speichern. Dabei stellt die „/A“-Variante („A“ wie Archiv) sicher, dass das PDF auch später noch in der heutigen Form angezeigt werden kann, u.a. dadurch, dass der benutzte Zeichensatz in das PDF mit integriert wird.

Das so erzeugte PDF allein wird aber künftig nicht mehr als Rechnung ausreichen.

Den fehlenden XML-Teil kann man mit dem o.g. Programm erzeugen. Es setzt dabei auf die Mustang-Bibliothek

Deren Vorteile:

  • sie kann das XML erzeugen und in die PDF-Datei integrieren

  • sie kann eine Validierung der so entstandenen PDF-Datei durchführen

Der Nachteil:

  • eine Monatsrechnung kann sie (zurzeit) auch nicht erstellen.

Den letzten Punkt habe ich dadurch gelöst, dass ich die fehlende Funktionalität selbst programmiert und die XML-Datei entsprechend der Spezifikation erweitert habe.

Mustang verbindet dann die zuvor erzeugte PDF-Datei mit der neu erstellten XML-Datei und erzeugt daraus eine ZUGFeRD-Datei. Diese Datei wird dann von einem Validator geprüft.

Der Validator prüft die PDF-Datei, den Aufbau der XML-Datei und die Abhängigkeit der Felder im XML untereinander. Er verwendet dabei Hunderte von Regeln. So prüft er beispielsweise, dass Summen stimmen oder dass ein bestimmtes Feld vorhanden ist, wenn ein anderes Feld einen bestimmten Wert hat usw.

Er verringert so die Gefahr, dass eine Rechnung vom Empfänger zurückgewiesen wird, weil Fehler im XML vorhanden sind.

Eine 100%-ige Garantie ist das aber auch nicht. Es gibt mehrere Validatoren und die sind sich nicht immer ganz einig. Außerdem werden die Validatoren selbst auch weiterentwickelt und deren Ergebnisse können dann in der nächsten Version bei der gleichen PDF-Datei unterschiedlich ausfallen.

Simple Monats e-Rechnung (smer)

Mein Programm steht als sog. „Fat-Jar“ zur Verfügung, das alle Abhängigkeiten bereits mitbringt – vor allem die megabytegroßen Validator-Dateien. Es setzt somit nur ein installiertes Java 17 voraus.

Aufgerufen wird die Version 0.1.2 mit

java -jar smer-0.1.2-all.jar name_der_pdf_datei.pdf [name_des_rechungsdatei.yaml]

Wie in der zugehörigen Dokumentation beschrieben, werden die Daten wie Firmen- und Kundenanschriften, Warenliste und Steuerfälle in YAML-Dateien gespeichert. Diese Daten ändern sich nach dem anfänglichen Erstellen meist nicht mehr.

Der „variable Teil“ sind die Daten der eigentlichen Rechnung. Auch er wird in einer YAML-Datei erwartet, deren Name man optional dem Aufruf mitgeben kann.

YAML ist ein Format, das man mit einem normalen Texteditor bearbeiten kann und dessen Aufbau noch „menschenlesbar“ ist. Die Namen der Felder sind so gewählt, dass sie selbsterklärend sind.

Wie bei ähnlichen Angeboten im Internet werden auch hier die Daten dem Programm auf einem Silbertablett präsentiert. Es macht daraus eine XML-Datei, verbindet diese mit dem vorbereiteten PDF und führt ein Validierung durch.

Wie oben erwähnt müssen Sie sicherstellen, dass die Daten im PDF- und im XML-Teil identisch sind. Das Programm gibt hierzu eine kurze Zusammenfassung der im XML-Teil gespeicherten Zahlen aus.

Das Programm reicht in seiner jetzigen Form aus, um gelegentlich ein PDF zu einer Monatsrechnung „aufzuwerten“.

Spätestens dann, wenn man viele Rechnungen schreibt, wird jedoch der Wunsch nach weiteren Funktionen kommen, wie beispielsweise

  • die Anbindung an eine „richtige“ Datenbank

  • das Erzeugen der PDF-Datei

Solche Erweiterungen sind aber für jeden Betrieb sehr spezifisch und daher hier nicht implementiert. Das Programm ist aber so ausgelegt, dass es sich leicht erweitern lässt.


Samstag, 28. September 2024

Frameo - es ist Zeit

Digitale Bilderrahmen verbreiten sich immer mehr und viele kommen auf die Idee, ihn den Großeltern zu schenken, um Bilder von der eigenen Familie über das Internet auf den Bilderrahmen zu senden, und die älteren Herrschaften so am eigenen Familienleben teilhaben zu lassen.

Das folgende Posting beschreibt ein nicht offensichtliches Problem in Gast-Netzwerken, wie sie z. B. in Altenheimen vorkommen.

Wenn man einen digitalen Bilderrahmen haben möchte, der sich über das Internet befüllen lässt, hört man sehr schnell den Namen „Frameo“. Das System besteht aus einer Mobil-App zum Senden der Bilder (iOS oder Android), der Software auf dem Bilderrahmen (der unter Android läuft) und augenscheinlich einem Frameo-Server in der Cloud, der zumindest die Verbindung zwischen App und Rahmen herstellt.

Das Einsatzgebiet bringt es mit sich, dass der Bilderrahmen nach seiner Einrichtung in den allermeisten Fällen ohne weitere Bedienung laufen muss. Das heißt, der Rahmen muss sich zu den programmierten Uhrzeiten automatisch aus- und wieder einschalten und neue Bilder erscheinen wie von Geisterhand.

Bei Frameo können mehrere „Freunde“ Bilder an den Rahmen senden - löschen kann man Bilder leider nur vor Ort.

Beim Anlegen eines „Freunds“ wird eine 10-stellige PIN generiert, die 12 Stunden lang gültig ist. Während dieser Zeit kann man diese PIN per Telefon, Messenger oder SMS weitergeben. Mit dieser PIN wird in den Mobil-Apps die Verbindung zum Rahmen hergestellt.
Auch die Erzeugung der PINs kann an einen „Freund“ delegiert werden.

So weit, so gut.

Die Probleme begannen hier jedoch mit der Beobachtung, dass der Bilderrahmen beim Booten seine Zeit nicht automatisch einstellte. Bei jedem Neustart oder Ein- und Ausschalten ging die manuell eingestellte Zeit verloren.

Dies war um so erstaunlicher, da der Rahmen mit dem Internet verbunden war und über diesen Weg auch Bilder von den Mobil-Apps übertragen werden konnten.

Der Kundendienst von Frameo hat sich sehr bemüht, konnte das Problem letztlich aber auch nicht finden, gab aber den Anstoß zu dessen Lösung.

Ein Bilderrahmen ist ein klassisches IoT-Gerät und gehört daher aus Sicherheitsgründen in ein Gast-Netzwerk.

Ein Gast-Netzwerk erlaubt den Zugriff auf das Internet, aber nicht auf die Computer des internen Netzwerks.

Auch das WLAN in einem Altenheim sollte als Gast-Netzwerk konfiguriert sein.

Eine wenig bekannte aber standardmäßig aktivierte Sicherheitsfunktion in Fritz-Boxen beschränkt den Internetverkehr auf WWW und E-Mail und ist damit für 99% aller Fälle ausreichend.

Sie blockiert jedoch den Zugriff auf Zeitserver und damit das automatische Stellen von Uhren (z. B. die im Bilderrahmen).

Ohne diese Zeitinformation setzte sich die Uhr im Bilderrahmen immer auf 5:00 Uhr morgens. Die programmierten Ein- und Ausschaltzeiten des Rahmens beziehen sich auf diese Zeit, sodass er sich mitten in der Nacht ein- bzw. ausschaltete.

Die Beschränkung auf WWW und E-Mail in Gast-Netzwerken kann man in der Fritz-Box ausschalten. Seitdem funktioniert auch das automatische Einstellen der Uhrzeit. Es bleibt zu sehen, wie das WLAN im Altenheim konfiguriert ist.

Einen entsprechenden Hinweis habe ich an AVM gegeben, aber ich erwarte keine große Reaktion.

Dienstag, 9. Juli 2024

Postbank behebt HBCI-Fehler

Wie im o.g. Forumthread berichtet, hat die Postbank - in bemerkenswerter Schnelle - den vor einigen Tagen gemeldeten Fehler unterdessen behoben.

Wie ebenfalls im o.g. Forumthread beschrieben, muss man nun jameica dazu veranlassen, die fehlenden Kontobewegungen von der Postbank zu holen:

Zunächst sollte - falls das nicht automatisch geschieht - ein Backup erstellt werden.

Dann: Start > Hibiscus > Konten

Dort das entsprechendes Konto auswählen und mit der rechten Maustaste das Kontext-Menü öffnen.

Nun: Erweitert > Saldo und Datum zurücksetzen

Bei der nächsten Synchronisation holt jameica alle verfügbaren Kontobewegungen vom Server und versucht die so entstehenden Dubletten zu erkennen und zu überspringen.
Das funktioniert, wenn zwischendurch in jameica nichts an den Einträgen verändert wurde. Ansonsten wird die Buchung nicht als Dublette erkannt und neu angelegt. Man sollte deshalb danach Ausschau halten.

Donnerstag, 4. Juli 2024

Fehler in der HBCI-Schnittstelle der Postbank führt zu Problemen bei Finanzsoftware

Eine augenscheinlich Anfang August vorgenommene Änderung der Postbank an den Daten, die sie über ihre HBCI-Schnittstelle ausgibt, führt zu Problemen bei der Finanzsoftware jameica und wahrscheinlich auch bei anderen Programmen.

Einer der Datenpunkte, die pro Transaktion übertragen werden, ist die IBAN des Empfängerkontos. Die IBAN ist in Deutschland 22 Zeichen lang und der Programmierer von jameica hat für diesen Datenpunkt eine Länge von 40 Zeichen vorgesehen - also normalerweise mehr als genug.

Seit Anfang August überträgt die Postbank die IBAN des Empfängerkontos jedoch doppelt, mit einem Schrägstrich dazwischen. Die Zeichenkette ist damit 45 Zeichen lang und passt dann nicht mehr in die eigentlich großzügig bemessenen 40 Zeichen der lokalen Datenbank. Dies führt dazu, dass die Buchung nicht richtig eingelesen werden kann und übersprungen wird.

jameica gibt zwar eine Meldung aus, dass einige Daten nicht eingelesen werden konnten - sogar mit rotem Hintergrund; einen etwas deutlicheren Hinweis hätte ich mir dann doch gewünscht.

Soweit im Augenblick bekannt ist, tritt der Fehler "nur" bei Daueraufträgen und den monatlichen Abbuchungen der Kreditkarte auf.

Der Autor von jameica gibt an, dass die Postbank den Fehler mittlerweile zugibt und einen Fix bis zur KW 29 verspricht... mal sehen

Im Augenblick bleibt nur der Besuch der Bank-Website, wenn man überprüfen will, ob ein Dauerauftrag ausgeführt wurde.

Links

Thread im Forum: https://homebanking-hilfe.de/forum/topic.php?t=26582