Python

Python ist eine interpretierte, dynamisch typisierte Hochsprache und gehört auf RHEL-Systemen zur Basisausstattung - viele Systemwerkzeuge wie dnf, firewalld oder subscription-manager sind in Python geschrieben. Python 2 wurde am 2020-01-01 abgekündigt und ist weder in RHEL 8 noch in neueren Versionen standardmässig enthalten. Dieser Artikel bezieht sich ausschliesslich auf Python 3 und als untere Grenze auf RHEL 8.

Eigene Software der Linuxfabrik (firewallfabrik, linuxfabrik-lib, Monitoring-Plugins) verlangt häufig eine deutlich neuere Python-Version als die RHEL-Standardinstallation mitbringt. Beispiel firewallfabrik: requires-python = ">=3.14". Eine parallele Python-Installation über DNF Module- oder Application-Streams ist der normale Weg (siehe Abschnitt installation).

Begriffe
  • bandit: Security-Scanner für typische Python-Sicherheitsprobleme.

  • PEP 621: definiert die Projekt-Metadaten im [project]-Abschnitt von pyproject.toml.

  • PEP 668: „Externally Managed Environments“ - ab Python 3.11 markieren Distributionen ihr System-Python als geschützt, sodass pip install ohne venv mit einer Fehlermeldung abbricht.

  • PEP 735: definiert [dependency-groups] für Dev-Abhängigkeiten, die nicht im gebauten Wheel landen.

  • pip: Referenz-Paketmanager der Python Packaging Authority (PyPA). Installiert Pakete aus PyPI in die aktive Umgebung.

  • pipx: installiert eigenständige Python-CLI-Tools (z.B. ruff, httpie, ansible) jeweils in eine eigene venv und legt Wrapper nach ~/.local/bin/.

  • PyPI: Python Package Index, gesprochen „Pie Pea Eye“, der zentrale Paket-Hub für Python.

  • pyright: statischer Type-Checker. Bei der Linuxfabrik (z.B. FirewallFabrik) der bevorzugte Type-Checker, nicht mypy.

  • pytest: De-facto-Standard-Test-Framework.

  • ruff: Linter und Formatter in einem, ebenfalls in Rust; ersetzt Kombinationen aus pylint, flake8, isort und black.

  • System-Python: die mit dem Betriebssystem gelieferte Python-Installation. Wird von dnf und anderen RHEL-Tools benutzt. Nicht anfassen, keine Pakete per pip global installieren.

  • TOML / pyproject.toml: Tom’s Obvious Minimal Language ist ein schlankes Konfigurationsformat mit Sections ([section]) und key = value-Einträgen, ähnlich INI, aber mit echten Datentypen (Strings, Zahlen, Listen, verschachtelte Tables). pyproject.toml im Projekt-Root ist seit PEP 518 die zentrale Konfigurationsdatei eines Python-Projekts und löst setup.py, setup.cfg, requirements*.txt sowie verstreute Tool-Configs (.flake8, .pylintrc, pytest.ini, …) ab. Sie enthält Build-System, Projekt-Metadaten (PEP 621) und Tool-Konfigurationen ([tool.ruff], [tool.bandit], usw.). YAML wurde bei der Wahl des Formats bewusst verworfen. Die Gründe, nachzulesen in der PEP-518-Diskussion:

    • YAML-Parser sind gross und komplex. Ein minimaler TOML-Parser lässt sich in wenigen hundert Zeilen bauen und im Bootstrap von pip mitliefern. Seit Python 3.11 gehört tomllib zur Standardbibliothek.

    • YAML ist whitespace-sensitiv. Inkonsistente Einrückung führt still zu anderen Strukturen, was für Konfigurationsdateien unnötig riskant ist.

    • YAML ist kontextabhängig typisiert. Berühmt ist das sogenannte „Norway-Problem“: NO wird als Boolean false geparst statt als String.

    • yaml.load() konnte historisch beliebigen Python-Code ausführen; nur yaml.safe_load() ist sicher. In TOML gibt es diese Fussangel nicht.

  • uv: sehr schneller, in Rust geschriebener Ersatz für pip und venv (Astral). Abwärtskompatibel, primär für CI-Pipelines und Entwicklerworkstations interessant.

  • venv: virtuelle Umgebung, ein isolierter Python-Interpreter samt eigenem site-packages-Verzeichnis. Wird pro Projekt erstellt und ersetzt früher übliche Tools wie virtualenv oder virtualenvwrapper.

  • vulture: findet nicht verwendete Funktionen, Variablen und Imports („toter Code“).

  • Wheel: das Standard-Binärformat für Python-Pakete (.whl), ersetzt das ältere Egg-Format.

Installation

System-Python ist auf allen unterstützten RHEL-Versionen bereits installiert oder wird als Abhängigkeit von Basispaketen mitgezogen. Die folgende Tabelle zeigt die standardmässig verfügbaren Python-Versionen:

Distribution

System-Python

Alternative Versionen via dnf

RHEL 8

3.6

python3.8, python3.9, python3.11, python3.12 (Module Streams)

RHEL 9

3.9

python3.11, python3.12

RHEL 10

3.12

python3.13

Bemerkung

RHEL 8 verwendet das Module-System für parallele Python-Versionen. Ab RHEL 9 werden die neueren Interpreter als reguläre Pakete geliefert und lassen sich parallel installieren.

System-Python explizit installieren (ist aber praktisch immer schon vorhanden):

# RHEL 8+
dnf --assumeyes install python3 python3-pip

Neuere Python-Version parallel installieren. Beispiel Python 3.12 auf RHEL 9:

dnf --assumeyes install python3.12 python3.12-pip

Auf RHEL 8 läuft das über dnf module:

dnf --assumeyes module install python3.12/common

Die Alternative python3.12 ist danach als eigener Interpreter unter /usr/bin/python3.12 verfügbar. python3 bleibt weiterhin auf die System-Version zeigen.

Warnung

Niemals pip install ohne venv als root ausführen. Das würde Dateien in das System-Python-Verzeichnis schreiben, die dnf dann nicht mehr verwaltet - der klassische Weg ins Chaos bei Updates. Ab RHEL 10 bzw. Python 3.11 wird das per PEP 668 sogar aktiv blockiert.

Virtuelle Umgebungen

Jedes Python-Projekt (auch das kleinste Skript mit externer Abhängigkeit) gehört in eine eigene venv. Nur so bleibt das System-Python sauber und Projekte können unterschiedliche Bibliotheks-Versionen nutzen, ohne sich gegenseitig zu beschädigen.

# create the venv (unprivileged user, home directory)
python3 -m venv ~/venvs/myproject

# activate the venv
source ~/venvs/myproject/bin/activate

# upgrade pip, setuptools and wheel
python3 -m pip install --upgrade pip setuptools wheel

# install the required packages
python3 -m pip install requests pyyaml

# leave the venv
deactivate

Nach dem Aktivieren zeigt der Prompt das aktuelle venv an (z.B. (myproject)) und python, python3 und pip verweisen auf den venv-Interpreter. Zum endgültigen Entfernen reicht ein rm --recursive --force ~/venvs/myproject.

Eine spezifische Python-Version für die venv wählen:

python3.12 -m venv ~/venvs/myproject

requirements.txt

Abhängigkeiten eines Projekts werden in einer requirements.txt festgehalten und versionsgenau gepinnt:

requirements.txt
pyyaml==6.0.1
requests==2.31.0

Installation:

python3 -m pip install --requirement requirements.txt

Für reproduzierbare Builds empfiehlt sich pip-tools (pip-compile) oder das wesentlich schnellere uv, das aus einer requirements.in einen vollständig aufgelösten Lockfile mit Hashes erzeugt.

Moderne Projekte verzichten auf requirements.txt komplett und deklarieren alle Abhängigkeiten in pyproject.toml (siehe Packaging und Veröffentlichung auf PyPI).

pipx

Für eigenständige Python-CLI-Tools (ansible, ruff, httpie, twine, …) ist pipx die richtige Wahl. Jedes Tool bekommt eine eigene, isolierte venv, die Executables landen in ~/.local/bin/:

dnf --assumeyes install pipx
pipx ensurepath

pipx install ruff
pipx install --python python3.12 ansible
pipx list
pipx upgrade ruff
pipx uninstall ruff

Code-Qualität

Linuxfabrik-Konvention: erst die deterministischen Static-Analysis-Tools (ruff, pyright, bandit, vulture) sauber laufen lassen, danach erst LLM-Agents. Alle Toolkonfigurationen liegen in pyproject.toml, damit das Projekt ohne Zusatzdateien portabel bleibt. pre-commit run --all-files deckt die komplette Codebasis ab, nicht nur die staged Files.

Syntax-Check

Schneller Offline-Check, ohne Tools zu installieren:

python3 -m py_compile script.py

Lint und Format mit ruff

ruff ist Linter und Formatter in einem. Ein Bruchteil der Laufzeit von pylint + black + isort, vollständig in pyproject.toml konfigurierbar.

pipx install ruff

ruff check .          # lint
ruff format .         # format (black-compatible)
ruff check --fix .    # auto-fix

Linuxfabrik-Standard-Konfiguration (orientiert an firewallfabrik):

pyproject.toml
[tool.ruff]
target-version = "py312"

[tool.ruff.lint]
select = [
    "B",    # flake8-bugbear: potential bugs
    "C4",   # flake8-comprehensions
    "E",    # pycodestyle errors
    "F",    # pyflakes: logic errors
    "I",    # isort: import sorting
    "PTH",  # flake8-use-pathlib: os.path -> pathlib
    "RUF",  # Ruff-specific rules
    "SIM",  # flake8-simplify
    "TID",  # flake8-tidy-imports
    "UP",   # pyupgrade: modernize syntax
    "W",    # pycodestyle warnings
]
ignore = [
    "E501",  # line too long - prose comments and URLs
]

[tool.ruff.format]
quote-style = "single"

Bemerkung

Single Quotes als Standard-Quote-Style. Double Quotes nur innerhalb eines f-String-Ausdrucks oder wenn der String selbst Single Quotes enthält. """ für alle Triple-Quoted-Strings (Docstrings, SQL, DESCRIPTION). Die Regel wird durch ruff format erzwungen.

Type-Checking mit pyright

Bei der Linuxfabrik ist pyright der bevorzugte statische Type-Checker, nicht mypy. Pyright läuft unabhängig von ruff.

pipx install pyright

pyright src/
pyproject.toml
[tool.pyright]
exclude = ["src/mypackage/gui", "venv"]

Security mit bandit

bandit findet typische Sicherheitsprobleme wie hardcodierte Passwörter, unsichere subprocess-Calls oder unsicheres Deserialize.

pipx install bandit

bandit --configfile pyproject.toml --recursive src/
pyproject.toml
[tool.bandit]
# Global skips only with a justification as a comment right above the keys.
# B110 (try/except/pass) and B112 (try/except/continue): intentional patterns
# for graceful degradation in GUI/CLI tools.
# B311 (pseudo-random): not used for cryptographic purposes.
skips = ["B110", "B112", "B311"]

[tool.bandit.assert_used]
# pytest-style asserts are idiomatic in tests/.
skips = ["*/tests/*.py", "tests/*.py"]

Einzelne Befunde lassen sich per # nosec BXXX hinter der betreffenden Codezeile unterdrücken. Die Begründung immer in eine eigene Kommentarzeile direkt darüber, nie in dieselbe Zeile - bandit parst alles nach # nosec als weitere Test-IDs und verschluckt Freitext stillschweigend:

# Short justification on its own line above the call site.
offending_call(...)  # nosec B603

Dead Code mit vulture

vulture findet nicht verwendete Funktionen, Variablen und Imports.

pipx install vulture

vulture src/
pyproject.toml
[tool.vulture]
min_confidence = 80
paths = ["src/mypackage"]
# Framework hooks invoked dynamically, therefore flagged as false-positive.
ignore_names = [
    "dialect",
    "indentless",
]

Tests mit pytest

pytest ist das De-facto-Test-Framework. Testdateien liegen nach Konvention unter tests/ und heissen test_*.py.

pipx install pytest

pytest -v
pytest --cov=src                     # with pytest-cov for coverage
pytest tests/test_foo.py::test_bar   # run a single test
pyproject.toml
[tool.pytest.ini_options]
testpaths = ["tests"]

pylint als Alternative

ruff deckt inzwischen den grössten Teil dessen ab, was pylint historisch geprüft hat, ist aber um Grössenordnungen schneller. Für tiefergehende Struktur-Checks (Zyklen, Klassenhierarchie, Code-Smells) bleibt pylint weiterhin nützlich.

pipx install pylint
pylint src/

pre-commit

Zentrale Konfiguration in .pre-commit-config.yaml im Repo-Root:

pipx install pre-commit
pre-commit install              # register the Git hook
pre-commit run --all-files      # check the whole codebase

Ein Linuxfabrik-typisches Setup mit Ruff, Bandit und Vulture:

.pre-commit-config.yaml
repos:
  - repo: 'https://github.com/astral-sh/ruff-pre-commit'
    rev: 'v0.15.10'
    hooks:
      - id: 'ruff-check'
        args: ['--fix']
      - id: 'ruff-format'

  - repo: 'https://github.com/PyCQA/bandit'
    rev: '1.9.4'
    hooks:
      - id: 'bandit'
        args: ['-c', 'pyproject.toml']
        additional_dependencies: ['bandit[toml]']
        types_or: ['python']

  - repo: 'https://github.com/jendrikseipp/vulture'
    rev: 'v2.16'
    hooks:
      - id: 'vulture'
        args: ['--min-confidence=80']
        types_or: ['python']

Python Cheat Sheet

Typ-Prüfung:

if isinstance(var, dict):
    ...

Pfade zusammensetzen (pathlib statt os.path.join):

from pathlib import Path

config = Path('/etc') / 'app' / 'config.yml'
if config.exists():
    data = config.read_text(encoding='utf-8')

Optionalen Import kapseln:

try:
    import psutil
    HAVE_PSUTIL = True
except ImportError:
    HAVE_PSUTIL = False

Liste als einzelne Argumente übergeben:

fruits = ['lemon', 'pear', 'watermelon']
myfunc(*fruits)

Dictionary als Keyword-Arguments übergeben:

connection = {
    'database': 'mydb',
    'host': 'db.example.com',
    'password': 'linuxfabrik',
    'user': 'admin',
}
cnx = mysql.connector.connect(**connection)

Schleifen über Listen und Dicts:

# list
for value in data:
    ...
for index, value in enumerate(data):
    ...

# dict
for key, value in data.items():
    ...

Exceptions gezielt fangen:

try:
    ...
except (AttributeError, KeyError) as err:
    ...

Ausführungszeit messen:

import time

start = time.time()
# ... do the work ...
print(f'duration: {time.time() - start:.3f} s')

Shell-Pipe cmd1 | cmd2 | cmd3 nachbauen:

import subprocess

p1 = subprocess.Popen(['dmesg'], stdout=subprocess.PIPE)
p2 = subprocess.Popen(['grep', 'error'], stdin=p1.stdout, stdout=subprocess.PIPE)
p1.stdout.close()
output = p2.communicate()[0]

Prozess-Informationen:

import platform

platform.python_version()        # '3.12.1'
platform.python_implementation() # 'CPython'
platform.system()                # 'Linux'
platform.machine()               # 'x86_64'

Schneller HTTP-Server

Zum Ausliefern einer einzelnen Datei oder eines Verzeichnisses per HTTP (z.B. die /root/anaconda-ks.cfg):

# serve the current directory on port 8080
python3 -m http.server --bind 0.0.0.0 8080

# localhost only
python3 -m http.server --bind 127.0.0.1 8080

index.html-Dateien werden automatisch als DirectoryIndex verwendet.

Packaging und Veröffentlichung auf PyPI

Beispiel anhand der Linuxfabrik libs.

Moderne Python-Pakete nutzen:

  • ein src-Layout (src/mypackage/) - schützt davor, versehentlich den Projekt-Root statt die installierte Version zu importieren.

  • pyproject.toml als einzige Konfigurationsdatei (PEP 621, PEP 735), statt setup.py und setup.cfg.

  • ~= als Versions-Operator für Dependencies („compatible release“, erlaubt Patch-Updates, blockt Major-Wechsel) - z.B. jinja2~=3.1 erlaubt 3.1.x, aber nicht 4.x.

Minimalvorlage:

pyproject.toml
[build-system]
build-backend = "setuptools.build_meta"
requires = ["setuptools >= 77.0.3"]

[project]
authors = [
    { name = "Linuxfabrik GmbH, Zurich, Switzerland", email = "info@linuxfabrik.ch" },
]
dependencies = [
    "jinja2~=3.1",
    "pyyaml~=6.0.3",
]
description = "Kurze Beschreibung des Pakets."
dynamic = ["version"]
license-files = ["LICENSE"]
name = "mypackage"
readme = "README.md"
requires-python = ">=3.12"

[project.optional-dependencies]
# install via: pip install mypackage[gui]
gui = ["PySide6~=6.8"]

[dependency-groups]
# PEP 735: dev dependencies, not part of the built wheel.
dev = ["pytest~=9.0.2"]

[project.scripts]
mycli = "mypackage.cli:main"

[project.urls]
Homepage = "https://github.com/linuxfabrik/mypackage/"
Tracker = "https://github.com/linuxfabrik/mypackage/issues/"

[tool.setuptools.dynamic]
version = { attr = "mypackage.__version__" }

[tool.setuptools.packages.find]
where = ["src"]

venv vorbereiten und Build-Werkzeuge installieren:

python3 -m venv ~/venvs/mypackage
source ~/venvs/mypackage/bin/activate

python3 -m pip install --upgrade pip setuptools wheel
python3 -m pip install --upgrade build twine

Package bauen und Distribution-Metadaten prüfen:

cd /path/to/mypackage
python3 -m build

# check that the package description will render correctly on PyPI
twine check dist/*

Twine gegen die PyPI-Test-Instanz konfigurieren (API-Token zuvor unter https://test.pypi.org/manage/account/ erstellen):

$HOME/.pypirc
[testpypi]
password = pypi-204513be14d74574a3bef240699e7117
username = __token__

Hochladen auf die Test-Instanz:

twine upload --repository testpypi dist/*

Installation des hochgeladenen Pakets aus Test-PyPI testen. Die regulären Abhängigkeiten kommen weiterhin aus dem produktiven PyPI:

python3 -m pip install --index-url https://test.pypi.org/simple/ --upgrade mypackage

Wenn alles passt, API-Token unter https://pypi.org/manage/account/ erstellen und für das produktive Repository konfigurieren:

$HOME/.pypirc
[pypi]
password = pypi-<...>
username = __token__

Release hochladen:

twine upload dist/*

Anschliessend unter https://pypi.org/project/<name>/ prüfen.

Executables erzeugen

pyinstaller

pyinstaller packt Python-Skripte samt Interpreter und Abhängigkeiten in eine einzelne ausführbare Datei. Damit die gebauten Executables möglichst überall laufen, sollte der Build auf einer alten, aber noch gepflegten Distribution passieren (Faustregel: die älteste Ziel-Distribution). Bauen auf RHEL 10 und ausliefern auf RHEL 8 scheitert an zu neuen Systembibliotheken (z.B. dlopen: /lib64/libm.so.6: version 'GLIBC_2.29' not found).

dnf --assumeyes install binutils glibc
dnf --assumeyes install python3.12 python3.12-devel

python3.12 -m venv ~/venvs/pyinstaller
source ~/venvs/pyinstaller/bin/activate

python3 -m pip install --upgrade pip
python3 -m pip install pyinstaller

# add project-specific libraries:
python3 -m pip install BeautifulSoup4 lxml psutil PyMySQL smbprotocol

pyinstaller \
    --clean \
    --distpath /tmp/dist \
    --noconfirm \
    --noupx \
    --onedir \
    --specpath /tmp/spec \
    --workpath /tmp/build \
    /path/to/my/script.py

Nuitka

Nuitka kompiliert Python nach C und erzeugt daraus eigenständige Binaries oder importierbare Module. Gegenüber pyinstaller entsteht echter Maschinencode, der Build dauert aber länger.

python3 -m pip install nuitka
python3 -m nuitka --standalone --onefile script.py

Die vollständige Optionsliste zeigt python3 -m nuitka --help.

GUI und TUI

GTK / Gtk
Tk
  • tkinter ist das Standard-GUI-Toolkit der Python-Standardbibliothek. Nur für ganz einfache Dialoge verwenden.

TUI
  • newt / snack ist eine alte, auf RHEL vorhandene Library (python3-newt). Für moderne TUIs eher textual oder rich verwenden.

Qt 6 mit PySide6

Die FirewallFabrik ist eine voll ausgebaute Qt-6-Anwendung in Python. Als Binding kommt PySide6 (offizielles Qt-Binding der Qt Group, LGPL) zum Einsatz, nicht PyQt6 (GPL/ kommerziell). Beide APIs sind weitgehend identisch.

Installation:

# im venv
python3 -m pip install PySide6

Minimales Programm, das die Linuxfabrik-Konventionen abbildet (SPDX-Header, Single Quotes, argparse mit --version, Organisationsname, Fusion-Style, sauberer Ctrl+C-Handler, Wayland-Defaults wie in FirewallFabrik):

hello_qt.py
# Copyright (C) 2026 Linuxfabrik <info@linuxfabrik.ch>
#
# SPDX-License-Identifier: GPL-2.0-or-later

"""Minimal PySide6 example following Linuxfabrik conventions."""

import argparse
import os
import signal
import sys

try:
    from PySide6.QtCore import QTimer
    from PySide6.QtWidgets import (
        QApplication,
        QLabel,
        QMainWindow,
        QStyleFactory,
        QVBoxLayout,
        QWidget,
    )
except ImportError:
    print(
        'Python module "PySide6" is not installed.',
        file=sys.stderr,
    )
    sys.exit(1)

__version__ = '1.0.0'


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Hello Qt')

        central = QWidget()
        layout = QVBoxLayout(central)
        layout.addWidget(QLabel('Hello, world.'))
        self.setCentralWidget(central)


def main():
    parser = argparse.ArgumentParser(
        prog='hello-qt',
        description='Minimal PySide6 example.',
    )
    parser.add_argument(
        '-v',
        '--version',
        action='version',
        version=f'hello-qt {__version__}',
    )
    # parse_known_args lets Qt-specific flags (e.g. -platform) pass through.
    _args, remaining = parser.parse_known_args()

    # prefer Wayland if available and nothing was set explicitly
    if 'QT_QPA_PLATFORM' not in os.environ and os.environ.get('WAYLAND_DISPLAY'):
        os.environ['QT_QPA_PLATFORM'] = 'wayland;wayland-egl'

    # set the desktop file name before QApplication construction
    QApplication.setDesktopFileName('ch.linuxfabrik.hello-qt')

    app = QApplication(remaining)
    app.setStyle(QStyleFactory.create('Fusion'))
    app.setOrganizationName('Linuxfabrik')
    app.setApplicationName('hello-qt')

    # Qt's event loop blocks Python's signal handling. A periodic no-op
    # timer lets Python handle SIGINT between Qt events (Ctrl+C in the terminal).
    signal.signal(signal.SIGINT, lambda *_args: app.quit())
    _sigint_timer = QTimer()
    _sigint_timer.start(200)
    _sigint_timer.timeout.connect(lambda: None)

    window = MainWindow()
    window.show()

    sys.exit(app.exec())


if __name__ == '__main__':
    main()

Start:

python3 hello_qt.py

# with Qt-specific flags
python3 hello_qt.py -platform xcb

Wichtige Punkte, die sich im Projekt FirewallFabrik bewährt haben:

PySide6 statt PyQt6

LGPL-Lizenz, offiziell von der Qt Group, keine Dual-Licensing-Stolpersteine.

Fusion-Style erzwingen

Qt-Default-Styles unterscheiden sich je nach Desktop (Breeze unter KDE, Adwaita unter GNOME) und produzieren inkonsistente Layouts.

Wayland-Präferenz explizit setzen

Sobald WAYLAND_DISPLAY existiert, sonst fällt Qt unter manchen Desktops auf XWayland zurück.

QApplication.setDesktopFileName(...) vor der QApplication-Konstruktion aufrufen

Damit der Wayland-Plugin-Init den Namen sieht und keine Doppelregistrierung versucht.

argparse.parse_known_args() verwenden

Damit Qt-eigene Flags (-platform, -style, …) an Qt durchgereicht werden.

Signal-Handler für SIGINT mit Hilfstimer registrieren

Sonst lässt sich die Anwendung aus dem Terminal nicht sauber per Ctrl+C beenden.

Qt-spezifische Imports kommen in den Namenskonventionen von Qt (CamelCase: QMainWindow, setCentralWidget). Eigener Code folgt PEP 8 (snake_case). Das ist kein Widerspruch - ruff erkennt die Qt-API und beschwert sich nicht.

Troubleshooting

bandit meldet Test in comment: ... is not a test name or id, ignoring

Der Kommentar hinter # nosec enthält Freitext. Bandit parst alles nach # nosec als Test-IDs. Freitext in eine eigene Kommentarzeile direkt oberhalb der Call-Site verschieben, hinter # nosec nur die IDs stehen lassen (# nosec B603 B607).

error: externally-managed-environment

Tritt auf, wenn pip install gegen das System-Python läuft. Korrekt: venv erstellen und darin installieren. Quick-and-dirty und nur für die eigene Workstation geeignet: python3 -m pip install --break-system-packages .... Das Flag umgeht den PEP-668-Schutz und ist auf Servern nicht angebracht.

ModuleNotFoundError nach venv-Wechsel

Die venv wurde mit einer anderen Python-Minor-Version erstellt, als nun aktiv ist. venv neu erstellen: rm --recursive --force /path/to/venv; python3 -m venv /path/to/venv.

pyinstaller-Executable meldet PackageNotFoundError: python-keystoneclient

pyinstaller zusätzlich --copy-metadata python-keystoneclient mitgeben.

pyinstaller-Executable meldet FileNotFoundError: .../os_service_types/data/service-types.json

pyinstaller zusätzlich --collect-data os_service_types mitgeben.

pip-Cache liefert AssertionError: msgpack .dist-info directory not found

Pip-Cache ist korrupt. rm --recursive --force ~/.cache/pip/ und pip install mit --no-cache-dir erneut ausführen.