Plugin oder Treiber wird gesucht für SQLAlchemy

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Hallo,

ich wollte mein Programm per ausführbare Datei mit cxFreeze ausführbar machen.

So weit hat auch alles geklappt. Habe mit "cxfreeze-quickstart" eine setup.py erstellt. Das Programm startet(GUI), doch so bald ich den Button anklicke, der eine Berechnung ausführt, die Daten aus einer Datenbank brauch, bekomme ich diesen Fehler.

Code: Alles auswählen

File "/home/masterblack/.local/lib/python3.12/site-packages/sqlalchemy/util/langhelpers.py", line 375, in load
    raise exc.NoSuchModuleError(
sqlalchemy.exc.NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:sqlite
Ich habe hier den gleichen Fehler gefunden nur mit einem anderen Datenbank Typen.
https://www.geeksforgeeks.org/how-to-fi ... in-python/

Nur ich kann keine vergleichbare Datei zum installieren finden.

Oder muss ich vielleicht ein Package in der setup.py mitgeben?

Hier noch meine setup.py:

Code: Alles auswählen

from cx_Freeze import setup, Executable

# Dependencies are automatically detected, but it might need
# fine tuning.
build_options = {'packages': [], 'excludes': []}

base = 'gui'

executables = [
    Executable('verbrauchsrechner.py', base=base)
]

setup(name='Verbrauchsrechner',
      version = '0.5',
      description = '',
      options = {'build_exe': build_options},
      executables = executables)
Grüße Shredder
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Hat keiner eine Idee? Oder ist dies auch vielleicht die falsche Kategorie?
Benutzeravatar
sparrow
User
Beiträge: 4362
Registriert: Freitag 17. April 2009, 10:28

Soweit ich weiß, bündelt cx-_Freeze ebenso wie pyinstaller deine Applikation + benötigte Module + Interpreter zu einem ausführbaren Archiv.
Und wie dein generierter Code oben schon sagt: "Dependencies are automatically detected, but it might needfine tuning."
Das heißt, es wurde versucht deine Abhängigkeiten - also zusätzlich installierten Requierements, automatisch zu ermitteln. Das scheint nicht funktioniert zu haben. Du musst also herausfinden, wie du cx_Freeze sagst, dass es noch zusätzliche Module einpacken soll. Nämlich die, die dir fehlen.
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Soweit ich weiß, bündelt cx-_Freeze ebenso wie pyinstaller deine Applikation + benötigte Module + Interpreter zu einem ausführbaren Archiv.
Ja genau.
Du musst also herausfinden, wie du cx_Freeze sagst, dass es noch zusätzliche Module einpacken soll. Nämlich die, die dir fehlen.
Ja dies ist mein Problem. Also wenn ich in der setup.py bei packages[] oder includes[] "sqlalchemy.dialects.sqlite" mit gebe

Code: Alles auswählen

from cx_Freeze import setup, Executable

# Dependencies are automatically detected, but it might need
# fine tuning.
build_options = {'packages': [], 'excludes': [],
                 'includes': ["sqlalchemy.dialects.sqlite"]}

base = 'gui'

executables = [
    Executable('verbrauchsrechner.py', base=base)
]

setup(name='Verbrauchsrechner',
      version = '0.5',
      description = '',
      options = {'build_exe': build_options},
      executables = executables)
kommt das raus:

Code: Alles auswählen

File "/home/masterblack/.local/lib/python3.12/site-packages/sqlalchemy/engine/create.py", line 643, in connect
    return dialect.connect(*cargs, **cparams)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/masterblack/.local/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 621, in connect
    return self.loaded_dbapi.connect(*cargs, **cparams)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) unable to open database file
(Background on this error at: https://sqlalche.me/e/20/e3q8)
Link nützt mir aber leider auch nichts.
Benutzeravatar
sparrow
User
Beiträge: 4362
Registriert: Freitag 17. April 2009, 10:28

Das hat aber nichts mit dem ursprünglichen Fehler zu tun.
Alle Module scheinen nun ja gefunden zu werden - aber das öffenen der Datenbankdatei schlägt fehl.
Da müsstest du dann schon zeigen, wie du das versuchst.
Benutzeravatar
__blackjack__
User
Beiträge: 13549
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Master_Shredder: Da können wir jetzt auch nicht mehr sagen als dass die Datei offenbar nicht geöffnet werden kann. Wie versuchst Du das denn? Ist die Datei auch mit in dem Installer? Wohin wird die entpackt? Wie gibst Du das im Programm an?
„Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.“ — Brian W. Kernighan
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

OK.

Also meine Datenbank rufe ich über datenbankverbindung.py auf und die sieht so aus:

Code: Alles auswählen

"""
Verbindung zur verbrauchsrechner Datenbank
"""

from typing import List, Optional

from sqlalchemy import ForeignKey, create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, Relationship, Session, mapped_column


def setup_session():
    engine = create_engine("sqlite:///datenbank/verbrauchsrechner.db")
    return Session(engine)


class Base(DeclarativeBase):
    """
    Klasse Base
    """

    pass


class Vertragsdaten(Base):
    """
    Klasse Vertragsdaten für Tabelle vertragsdaten
    """

    __tablename__ = "vertragsdaten"

    vertragsdaten_id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    versorger: Mapped[Optional[str]]
    vertragsbeginn: Mapped[Optional[str]]
    tarif: Mapped[List["Tarif"]] = Relationship(back_populates="vertragsdaten")


class Tarif(Base):
    """
    Klasse Tarif für Tabelle tarif
    """

    __tablename__ = "tarif"

    tarif_id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    arbeitspreis: Mapped[Optional[float]]
    grundpreis: Mapped[Optional[float]]
    mehrwertsteuer: Mapped[Optional[float]]
    zaehlerstand_letzte_abrechnung: Mapped[Optional[float]]
    monatlicher_abschlag: Mapped[Optional[float]]
    vertragsdaten_id: Mapped[int] = mapped_column(
        ForeignKey("vertragsdaten.vertragsdaten_id")
    )
    vertragsdaten: Mapped["Vertragsdaten"] = Relationship(back_populates="tarif")
Wie versuchst Du das denn? Ist die Datei auch mit in dem Installer? Wohin wird die entpackt? Wie gibst Du das im Programm an?
Also ich nutze rein Linux und installiere auch nichts. Ich will nur mein Programm einfach starten können.
Die Datenbank ist gepackt dort wo sie im Programm ursprünglich auch ist: Verbrauchsrechner/build/exe.linux-x86_64-3.12/lib/datenbank

Die Datenbank existiert, mit Inhalt und der wird abgefragt.
Benutzeravatar
sparrow
User
Beiträge: 4362
Registriert: Freitag 17. April 2009, 10:28

Du versuchst aber die Datenbank "/datenbank/verbrauchsrechner.db" zu öffnen. Also den absoluten Pfad.
Ich nehmen nicht an, dass in deinem System /datenbank existiert.
Du musst in der cx_Freeze Dokumentation nachschauen, wie man Dokumente/Resourcen anspricht, die in dem Projekt selbst enthalten sind.
Benutzeravatar
sparrow
User
Beiträge: 4362
Registriert: Freitag 17. April 2009, 10:28

Noch ergänzend: Das funktioniert nur, wenn du die Datenbank nur liest. Wenn du auch etwas schreiben möchtest, dann kannst du die nicht mitliefern. Mit jedem Entpacken würde sie potentiell überschrieben werden.

In dem Fall muss dein Programm beim Start prüfen ob es eine Datenbankdstei gibt und falls nicht, diese anlegen.
Je nach Art des Programms wäre das home Verzeichnis des Benutzers oder /var ein üblicher Ort um sie abzulegen.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1121
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Ich weiß nicht wie cx_Freeze arbeitet, gehe aber von aus, dass es so ähnlich ist wie bei PyInstaller. Das Programm packt Interpreter und Code in eine ausführbare Datei. Weitere Daten müssen explizit mit angegeben werden, damit diese auch in die EXE gepackt werden. Beim Ausführen des Programms wird ein temporäres Verzeichnis erstellt, in dem die Ressourcen (Code und andere Dateien) entpackt werden. Der Pfad ist rein zufällig gewählt und ändert sich bei jedem Aufruf.

Ich denke mal, dass man am besten mit importlib.ressources.files arbeitet, um den richtigen Pfad zur DB zu erfahren.

Ich habs mal mit PyInstaller getestet. So sieht das Verzeichnis aus.

Code: Alles auswählen

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        09.10.2024     10:50                ressources
-a----        09.10.2024     10:47            199 main.py
-a----        09.10.2024     10:34            722 main.spec
-a----        09.10.2024     10:48             35 make_exe.bat
In ressources ist nur eine __init__.py und database.db wird beim Aufruf der __init__.py angelegt.
Das habe ich getan, bevor ich das Programm mit PyInstaller gepackt habe.

Hier die main.py:

Code: Alles auswählen

from ressources import open_database


with open_database() as db:
    cursor = db.cursor()
    with db:
        for result in cursor.execute("SELECT * FROM foo;"):
            print(result)

und in ressources/__init__.py:

Code: Alles auswählen

import sqlite3

from importlib import resources

__all__ = ("open_database", "database_file")

database_file = resources.files("ressources").joinpath("database.db")

if not database_file.exists():
    import string
    import random

    with sqlite3.connect(database_file) as _db:
        cursor = _db.cursor()
        with _db:
            for _ in range(100):
                cursor.execute("CREATE TABLE IF NOT EXISTS foo (id integer, name string);")
                cursor.execute("INSERT INTO foo VALUES (?, ?)", (random.randint(0, 255), "".join(random.choices(string.ascii_lowercase, k=10))))


def open_database() -> sqlite3.Connection:
    return sqlite3.connect(database_file)
Dann erstelle ich die exe.
Das erste mal mit:

Code: Alles auswählen

python -m PyInstaller -F main.py
Danach habe ich die erstellte Datei main.spec bearbeitet:

Code: Alles auswählen

# -*- mode: python ; coding: utf-8 -*-


a = Analysis(
    ['main.py'],
    pathex=[],
    binaries=[],
    datas=[("ressources", "ressources")],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name='main',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

Das Einzige, was ich geändert habe: datas=[("ressources", "ressources")],
Danach habe ich die exe erneut erstellt, aber diesmal mit der main.spec:

Code: Alles auswählen

python -m PyInstaller main.spec
Getestet habe ich nur unter Windows.

Code: Alles auswählen

py -3.13 -m PyInstaller .\main.spec
In dist wird dann die Datei main.exe erstellt. Unter Linux müsste die eine andere Endung haben.

Wenn du das auf cx_Freeze übertragen willst, musst du in Erfahrung bringen, wie du cx_Freeze dazu bekommst, Ressourcen mit in die ausführbare Datei zu packen.
Die Vorgehensweise, den richtigen Pfad zur Datenbank zu bekommen, bleibt aber gleich.

Wenn du schreiben willst, geht das nicht, da nach dem Beenden des Prozesses alle temporären Dateien gelöscht werden, auch die Datenbank.
Für sowas gibt es bekannte User-Verzeichnisse. Unter Linux ist es z.B. ~/.config, ~/.local
Unter Windows ist es %localAppData% und %AppData%. %AppData% ist im Roaming-Verzeichnis und wird via OneDrive synchronisiert, sofern man das nutzt.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Hallo,

ich bin, glaube ich, auf der richtigen Spur.

Ich habe in die "__init__.py" vom Modul "datenbank" die Überprüfung ob eine Datenbank existiert eingefügt.

Code: Alles auswählen

import sqlite3
from importlib import resources

# __all__ = "database_file"

database_file = resources.files("datenbank").joinpath("verbrauchsrechner.db")

if not database_file.exists():
    with sqlite3.connect(database_file) as _db:
        cursor = _db.cursor()
        with _db:
            cursor.execute(
                "CREATE TABLE IF NOT EXISTS tarif (tarif_id integer, arbeitspreis real, grundpreis real,"
                " mehrwertsteuer real, zaehlerstand_letzte_abrechnung, monatlicher_abschlag real,"
                " vertragsdaten_id);"
            )

            cursor.execute("INSERT INTO tarif VALUES (1, 26.85, 19.0, 144.828, 32380.0, 30.0, 1)")
Funktioniert!

Und dann habe ich meine "setup.py" angepasst mit "install_exe" in Verbindung mit "install_dir" kann ich den in Ort der Installation angeben.
So weit ich dies verstanden habe: https://cx-freeze.readthedocs.io/en/sta ... ml#install

Nur soll ich noch eine Distribution angeben, ich finde aber keinen Befehl/Argument dafür.

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/masterblack/PycharmProjects/Verbrauchsrechner/setup.py", line 20, in <module>
    install_exe(install_dir = '~/.config/Verbrauchsrechner_0.5')
TypeError: Command.__init__() missing 1 required positional argument: 'dist'

Code: Alles auswählen

from cx_Freeze import setup, Executable, install_exe

# Dependencies are automatically detected, but it might need
# fine tuning.
build_options = {'packages': ["sqlalchemy.dialects.sqlite"], 'excludes': [],
                 'includes': []}

base = 'gui'

executables = [
    Executable('verbrauchsrechner.py', base=base)
]

setup(name='Verbrauchsrechner',
      version = '0.5',
      description = '',
      options = {'build_exe': build_options},
      executables = executables)

install_exe(install_dir = '~/.config/Verbrauchsrechner_0.5')
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Es funktioniert :D !
wie du cx_Freeze dazu bekommst, Ressourcen mit in die ausführbare Datei zu packen.
Ich musste nur den datenbank-Ordner/Package mitgeben. Mit "include_files":

Code: Alles auswählen

from cx_Freeze import Executable, setup

# Dependencies are automatically detected, but it might need
# fine tuning.
build_options = {
    "packages": ["sqlalchemy.dialects.sqlite"],
    "excludes": [],
    "includes": [],
    "include_files": ["datenbank"],
}

base = "gui"

executables = [Executable("verbrauchsrechner.py", base=base)]

setup(
    name="Verbrauchsrechner",
    version="0.5",
    description="",
    options={"build_exe": build_options},
    executables=executables,
)
Es installiert sich zwar nicht, aber das brauche ich auch nicht. Ich kann meine Abfragen machen und gut.
Und das reicht mir erst mal aus. Speichern muss ich nichts, also nur ab und an eine Abfrage(Also meinen Stromverbrauch checken) und fertig.
Benutzeravatar
sparrow
User
Beiträge: 4362
Registriert: Freitag 17. April 2009, 10:28

Und wie kommen dann die Daten in die Datenbank?
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Also die Daten habe(manuell) bei der Datenbank Anlegung(auch manuell) eingegeben.
Dies heißt die Datenbank ist wie ich sie in meinem Programm habe verfügbar.
Sie liegt jetzt nur zusätzlich im gleichen Verzeichnis wie meine ausführbare Datei bzw. die Ordnerstruktur also "datenbank/* (verbrauchsrechner.db...)".
Antworten