Python Abhängigkeiten

05.08.2024
Ein kleiner Ausflug in die Abhängigkeiten von Softwareprojekten und wie unterschiedliche Programmiersprachen mit den einhergehenden Problemen umgehen.
Technologien Python Dependencies Tooling Rust

In der modernen Softwareentwicklung ist es üblich, das Rad wenn möglich nicht neu zu erfinden sondern auf bereits vorhandene Funktionalitäten in Bibliotheken zurückzugreifen. Da das eigene Programm von diesen Funktionen abhängt, werden diese auch als Abhängigkeiten bezeichnet. Während dieser Ansatz viele Vorteile bietet kommt er leider nicht ohne Probleme.

Eines der Probleme ist, dass eine solche Abhänhigkeit in der Regel selbst mehrere Abhängigkeiten hat. Diese haben wiederum selbst Abhängigkeiten. Daher ist es keine Seltenheit, dass ein Projekt mit fünf bis zehn direkten Abhängigkeiten schnell bei tausenden sekundären und tertiären Abhängigkeiten ankommt.

Um die Abhängigkeiten zu verwalten werden in der Regel verschiedene Tools eingesetzt. In Python nutzen wir dazu den Paketmanager pip. Für pip können die Abhängigkeiten in einer Datei namens requirements.txt definiert werden. Hier haben wir beispielswesie Weasyprint als Abhängigkeit definiert.

weasyprint==60.1

Weasyprint ist eine Bibliothek zum erstellen von PDFs. Das doppelte Gleichheitzeichen bedeutet, dass exakt die angegebene Version installiert werden soll. Dies geschieht in der Annahme, dass wir als Entwickler dann nicht von einem Update überrascht werden.

Unter der Haube nutzt Weasyprint jedoch pydyf mit folgender Spezifikation pydyf >=0.8.0. Das bedeutet, Weasyprint benutzt 0.8.0 oder neuer.

Unglücklicherweise hat sich die Signatur einer Funktion in pydyf geändert. Das bedeutet, die Anzahl der Argumente der Funktion hat sich geändert. Werden nun für unser Projekt die Abhängigkeiten installiert, würde die aktuellste pydyf Version installiert werden, die in diesem Fall eine geänderte Funktionssignatur hat und daher Laufzeitfehler beim Erstellen von PDF Dokumenten verursacht.

Da unser Projekt nicht die Version von pydyf einschränkt hatten wir folgenden interessanten Fall bei der Entwicklung eines Features, das nichts mit PDF Erzeugung zu tun hat..

  1. Ein Feature wird lokal auf unseren Laptops entwickelt. Alles funktioniert.
  2. Das Feature wird auf ein Testsystem gespielt und abgenommen. Alles funktioniert.
  3. pydyf ändert die Funktionssignatur und springt von 0.10.0 auf 0.11.0
  4. Das Feature wird auf das Livesystem ausgerollt. Die Abhängigkeiten werden installiert und die PDF erzeugung schlägt fehl. Auf dem Testsystem funktioniert es allerdings noch.

Die Lösung liegt auf der Hand: wir pinnen die Version von pydyf ebenfalls explizit auf die letzte funktionierende Verison, oder aktualisieren Weasyprint auf eine neuere Version, die ebenfalls eine Obergrenze für pydyf spezifiziert. Jedoch lässt sich auch damit nicht verhindern, dass ein ähnliches Problem noch einmal auftritt, denn die Abhängigkeiten und ihre Abhängigkeiten und die Versionsrandbedingungen können aus verschiedenen Gründen ab und an fehlerhaft sein.

Besonders bei Python Projekten hilft hier vor allem eines: Tests. Ein Test, ob die PDF Generierung noch funktioniert der in der CI Pipeline ausgeführt wird, hätte uns auf das Problem aufmerksam gemacht und verhindert, dass der entsprechende Code auf das Produktivsystem aufgespielt wird. Aber auch Tests sind lückenhaft. Bei der Komplexität moderner Anwendungen ist es kaum zu schaffen für alle eventualitäten einen Test zu schreiben und selbst dann gibt es keine Garantie, dass nicht doch etwas übersehen wurde. Wir haben jedenfalls einen Test geschrieben, der die PDF Erstellung überprüft und die Pipeline aufhällt, sollte ein Problem vorliegen.

Werfen wir einen Blick über den Tellerrand und schauen uns an, wie andere Programmiersprachen mit diesem Problem umgehen.

Rust

In Rust gibt es mehrere Mechanismen, die verhindern, dass dieses Problem auftreten kann. Zunächst würde der Compiler den Bau eines Programms verweigern, in dem eine Funktionssignatur nicht zum Aufruf passt und sogar eine Lösung vorschlagen. Das kompilieren dieses einfachen Programms:

fn add(a: u64, b: u64) -> u64{
    a+b
}

fn main() {
    let a = 2;
    let b = 3;
    let c = 4;
    let d = add(a, b, c)
}

ergibt den folgenden Fehler inklusive Lösungsvorschlag:

error[E0061]: this function takes 2 arguments but 3 arguments were supplied
  --> src/main.rs:10:13
   |
10 |     let d = add(a, b, c);
   |             ^^^     ---
   |                     | |
   |                     | unexpected argument of type `{integer}`
   |                     help: remove the extra argument
   |
note: function defined here
  --> src/main.rs:1:4
   |
1  | fn add(a: u64, b: u64) -> u64{
   |    ^^^ ------  ------

For more information about this error, try `rustc --explain E0061`.

Allein schon damit kann dieses Problem in Rust Projekten grundsätzlich nicht mehr auftreten.

Der zweite Mechanismus zur Vermeidung dieses Problems ist das Lockfile. Das Rust Build Tool Cargo legt eine Datei an, in der alle notwendigen Abhängigkeiten mit einer exakten Version aufgeführt sind. Wird der Code mit einem Lockfile neu gebaut, werden immer die exakt gleichen Versionen genutzt, wie im Lockfile angegeben, auch wenn es bereits neuere Versionen gibt. Aus diesem Grund ist es auch empfohlen, beim Programmieren einer Anwendung (binary crate) das Lockfile in der Versionsverwaltung zu behalten, um genau dieses Problem zu verhindern, während beim Programmieren einer Bibliothek (library crate) das Lockfile nicht in der Versionsverwaltung registriert werden sollte, denn das würde den Endanwendern Probleme bereiten.

Impressum

Screenion GmbH

Büroanschrift:
Adenauerallee 21, 1. OG
61440 Oberursel

Rechnungsanschrift und Firmensitz:
Oberhöchstadter Straße 70a
61440 Oberursel
Deutschland

Fon: +49 (0)6171 9519800
Fax: 06171-9519808
post@screenion.de
Web: https://www.screenion.de

Geschäftsführer: Reto M. Kiefer
Amtsgericht: Bad Homburg HRB 13769
UmSt-Id gemäß §27a Umsatzsteuergesetz: DE273300425

Datenschutz

Mit Ihrem Zugriff auf unsere Website werden Daten, die eine Identifizierung ermöglichen könnten (z.B. IP-Adresse) und weitere Angaben wie Datum, Uhrzeit und aufgerufene Seite In Log-Files gespeichert.

Eine Auswertung der Daten, außer für statistische Zwecke sowie zur Optimierung unseres Internetangebots in anonymisierter Form, findet nicht statt. Sie können unsere Website grundsätzlich ohne Offenlegung Ihrer Identität nutzen.

Des Weiteren verwenden wir keine Cookies oder ähnliche Technologien. Sicher haben Sie schon den Hinweis vermisst :)


Wir verwenden Fotos von unsplash sowie pixabay und danken: