Python mit Pyinstall - Ordner und externe Dateien auslassen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Juliusvanvern
User
Beiträge: 15
Registriert: Mittwoch 22. Januar 2020, 20:53

Hallo zusammen,

ich hoffe das passt hier rein :)

Vielleicht kann mir jemand einen Tipp mit dem pyinstaller geben.

Generell funktioniert das pyinstaller für mich ganz gut. Wenn ich jedoch, mein Python Skript so schreibe, dass es in dem Verzeichnis, wo es ausgeführt wird, noch zb. zwei Unterordner habe die Einmal heißen Einstellungen und Signaturen, dann packt er alles beim erstellen der Exe in die EXE zusammen. So dass es funktioniert, jedoch aber keine Einstellungen im Nachgang gemacht werden können. In den Dateien können einige Variablen eingestellt werden, die das Programm dann bei der Ausführung nutzt. Das Skript einfach so ausführen, auf Rechnern wo Python isnstalliert ist, funktioniert ohne Probleme.

Nur komme ich nicht klar, wie ich der Exe sagen kann, dass es auf die externen Ordner und Dateien zugreifen soll und diese nicht mit kompiliert. In dem HowTo zu pyinstaller habe ich das --exclude gefunden und damit probiert, das scheint aber nicht der Punkt zu sein.

Hat jemand eine Idee, welche Parameter ich dem pyinstaller mitgeben muss? Vielleicht ist mein Ansatz auch völlig falsch. :D

pyinstaller --onefile --noconsole --exclude=Einstellungen --exclude=Signaturen orga_senden.py

Aus dem Beispiel meines Programmes, wie ich die "Absende E-Mails hole"

Code: Alles auswählen

def get_sender(selected_user):
    absender_file_path = os.path.join(script_dir, "Einstellungen", "absender.txt")
    with open(absender_file_path, "r") as file:
        for line in file:
            name, email = line.strip().split(": ")
            if name.lower() == selected_user.lower():
                return email
    return ""  # Rückgabe eines leeren Strings, falls der Absender nicht gefunden wurde
Vorher hole ich mir den Pfad, wo das Skript aktuell liegt.

Code: Alles auswählen

script_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
Vielen Dank ihr lieben :)
Benutzeravatar
__blackjack__
User
Beiträge: 13123
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Juliusvanvern: `inspect.currentframe()` hat in einem Programm nichts zu suchen. Das ist auch viel zu magisch, weil es davon abhängt aus welchem Modul das aufgerufen wird, beziehungsweise wo überhaupt Aufrufrahmen anfallen und wo nicht. In bestimmten Fällen könnte man die den beispielsweise wegoptimieren, und in C geschriebene Funktionen müssen keinen haben. Normalerweise geht man hier über `__file__` das in jedem Modul definiert ist, beziehungsweise den Weg den Pyinstaller nimmt. Das ist da irgendwo dokumentiert wie man an das Verzeichnis kommt wo die EXE liegt.

`os.path` würde ich in neuen Programmen nicht mehr verwenden, dafür gibt es das `pathlib`-Modul.

Bei Textdateien sollte man immer explizit die Kodierung angeben und nicht hoffen das richtig ”geraten” wird. Bei Namen können ja beispielsweise durchaus Zeichen ausserhalb von ASCII vorkommen. Und selbst wenn es nur ASCII-Zeichen sind, gibt das Probleme wenn die Datei in UTF-16 ist und was anderes geraten wird, oder umgekehrt.

Ich würde kein eigenes Dateiformat erfinden, sondern etwas standardisiertes verwenden. Für CSV oder JSON gibt es Module in der Standardbibliothek, und beide Formate haben nicht das Problem, dass man verhindern muss, dass Trennzeichen *in* den Daten vorkommen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Juliusvanvern
User
Beiträge: 15
Registriert: Mittwoch 22. Januar 2020, 20:53

Supi, vielen dank für die hilfreichen hinweise. Das fileformat anzugeben, daran habe ich gar nicht gedacht. Entsprechend deinen Vorschlägen habe ich das in dem Programm mal umgebatu und in den entsprechenden funktionen neu definiert.

Im Beispiel der ersten funktion für das abholen der Absender.

Code: Alles auswählen

def get_sender(selected_user):
    absender_file_path = script_dir / "Einstellungen" / "absender.txt"
    with open(absender_file_path, "r", encoding="utf-8") as file:
        for line in file:
            name, email = line.strip().split(": ")
            if name.lower() == selected_user.lower():
                return email
    return ""  
Prüfen wo das Skript liegt, habe ich so geändert.

Code: Alles auswählen

script_dir = Path(__file__).resolve().parent
Die Pyinstaller Doku habe ich mal nach __File__ durchsucht. Ich denke da bin ich auch auf das richtige gestoßen, nur steige ich noch nicht so recht dahinter:
https://www.pyinstaller.org/en/stable/r ... using-file

So habe ich es versucht zu erstellen, geht zwar, aber dennoch packt er mir die Infos mit in die Exe.
pyinstaller --add-data "U:\Julius\Python\Organigramm_senden\Organigramm_senden\Einstellungen;Einstellungen" --add-data "U:\Julius\Python\Organigramm_senden\Organigramm_senden\Signaturen;Signaturen" --add-data "mypackage/file.dat:mypackage" --onefile --noconsole orga_senden.py
Benutzeravatar
DeaD_EyE
User
Beiträge: 1022
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Ich unterscheide immer zwischen Konfigurationsdateien und Ressourcen.

Konfigurationsdateien gehören ins Home-Verzeichnis und nicht ins Programmverzeichnis und auch nicht in ein temporäres Verzeichnis. Wenn der Nutzer etwas an der Datei verändern soll/kann, handelt es sich um eine Konfiguration.

Ressourcen gehören ins Programmverzeichnis. Das können z.B. Bilder und Schriftarten sein.

Pfad bei Windows: C:\Users\USERNAME\AppData\Local\PROGRAMM\config
Pfad bei Linux: /home/USERNAME/.local\PROGRAMM/config

Beispiel mit Konfigurationsdateien:

Code: Alles auswählen

import json
import sys
import platform
import logging
from pathlib import Path


NAME = "MeinProgramm"
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(NAME)


def get_config_path():
    match platform.system():
        case "Windows":
            place = "AppData/Local"
        case "Linux":
            place = ".local"
            
    return Path.home().joinpath(place, NAME, "config.json")


def get_config():
    default = {"address": "", "password": ""}
    config_path = get_config_path()
    
    log.info(f"Lade Konfiguration von {config_path}")
    try:
        return json.loads(config_path.read_text())
    except (ValueError, FileNotFoundError):
        log.info("Konfiguration nicht gefunden, erstelle Standardkonfiguration")
        config_path.parent.mkdir(parents=True, exist_ok=True)
        with config_path.open("w") as fd:
            json.dump(default ,fd)
    else:
        log.info("Konfiguration geladen")

    return default


def change_config(**kwargs):
    log.info("Verändere Konfiguration")
    get_config_path().write_text(json.dumps(get_config() | kwargs))
    return get_config()


if __name__ == "__main__":
    print(get_config())
    print(change_config(address="google.de", data=123))

Um sich die Pfade nicht selber zusammenbauen zu müssen, kann man auch eine Bibliothek nutzen: https://pypi.org/project/platformdirs/

Dort ist noch der Name des Autors enthalten. Also meins weicht vom Standard ab.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Antworten