Seite 1 von 1

Eigenen Code in Anwendung umwandeln

Verfasst: Dienstag 11. Juli 2023, 13:30
von Sero
Hallo zusammen,

ich habe vor kurzem mit Python begonnen und beschäftige mich mit der Automatisierung ständig wiederholender Abläufe meiner Arbeit.
Ich habe da auch schon etwas hinbekommen, und auch in eine .exe umgewandelt, jedoch ist es so nicht möglich, dass meine Kollegen die Anwendung verwenden können.
Meine Kollegen nutzen kein Python und somit auch keine Bibliotheken auf ihren Rechner installiert.

Habt ihr evtl. einen Tipp für mich wie ich an diese Sache herangehen soll?

Ich nutze Pycharm als IDE.

Ich danke schon mal :)

Re: Eigenen Code in Anwendung umwandeln

Verfasst: Dienstag 11. Juli 2023, 14:25
von __deets__
Ich verstehe deine Frage nicht. Du sagst doch, dass du das Tool in eine EXE umgewandelt hast. Dann ist das doch genau ohne installiertes Python nutzbar?

Re: Eigenen Code in Anwendung umwandeln

Verfasst: Dienstag 11. Juli 2023, 20:56
von Patsche
Ich verstehe die Problematik auch nicht.
Wenn du dein Pythonprogramm in eine .exe umgewandelt hast, beispielsweise mit auto-py-to-exe, dann packt das Programm alle benötigten Dateien mit in den erstellten Ordner.
Dieser erstellte Ordner ist dann portabel und kann auf jedem Rechner gestartet werden, unabhängig, ob Python installiert ist, oder nicht.
Das ist doch gerade der Vorteil dieser Umwandlung

Re: Eigenen Code in Anwendung umwandeln

Verfasst: Mittwoch 12. Juli 2023, 08:59
von Sero
Hmmm, dann liegt das Problem wohl woanders. Denn es hat seltsamerweise nur bei mir funktioniert.
Danke trotzdem für die schnelle Antwort. Ich probiere es noch einmal.

Re: Eigenen Code in Anwendung umwandeln

Verfasst: Mittwoch 12. Juli 2023, 09:42
von Dennis89
Hallo,

beschreib doch mal bitte, was du im Detail gemacht hast und welcher Fehler bei den Kollegan auftrat.
Der Code dazu ist auch immer hilfreich. Einfach im vollständigen Editor auf </> drücken und dann an der Courserposition einfach den Code einfügen.

Grüße
Dennis

Re: Eigenen Code in Anwendung umwandeln

Verfasst: Donnerstag 13. Juli 2023, 08:51
von Sero
Also scheinbar war es mal ein Glückstreffer mit der exe. Jetzt klappt es nicht:

Ich habe den Code mit auto-py-to-exe konvertieren lassen (ich habe Verzeichnis gewählt. Bei One File kam aber derselbe Fehler auf).

Ich starte die Anwendung und dann werde ich direkt nach dem Speicherort gefragt. Das ist auch richtig so. Sobald ich aber den Speicherort auswähle, bekomme ich den Fehler

Traceback (most recent call last):
File "1-download_rename_attachment_group.py", line 48, in <module>
File "win32com\client\dynamic.py", line 628, in __getattr__
ModuleNotFoundError: No module named 'win32timezone'


Mein Code:

Code: Alles auswählen

import win32com.client
import os
import datetime
from tkinter import Tk, filedialog, messagebox, ttk
from tqdm import tqdm
import tkinter

# Outlook-Anwendung starten
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")

# Gruppenpostfach "ServiceDesk-DBVertrieb" auswählen
main_folder = outlook.Folders.Item("Hauptordner")

# Ordner "02 - Verfahren" im Gruppenpostfach auswählen
sub_1_folder = service_desk_folder.Folders.Item("Unterordner_1")

# Ordner "QMS/EMS" im "02 - Verfahren"-Ordner auswählen
sub_2_folder = verfahren_folder.Folders.Item("Unterordner_2")

# Heutiges Datum abrufen
heutiges_datum = datetime.date.today()

# Speicherort auswählen
root = Tk()
root.withdraw()
save_path = filedialog.askdirectory(title="Speicherort für die heruntergeladenen Dateien auswählen")

# Flag-Variable, um den Zustand festzuhalten
email_gefunden = False

# Fortschrittsfenster erstellen
progress_window = Tk()
progress_window.title("Emails werden durchsucht")
progress_bar = ttk.Progressbar(progress_window, length=300, mode="determinate", variable=tkinter.DoubleVar())
progress_bar.pack(padx=20, pady=20)
progress_percent = ttk.Label(progress_window, text="")
progress_percent.pack()

# Funktion zum Aktualisieren des Fortschrittsbalkens
def update_progress_bar(value):
    progress_bar["value"] = value
    progress_percent["text"] = f"{int(value)}%"
    progress_window.update()

# Alle E-Mails im "Sub2"-Ordner durchlaufen
email_count = len(sub_2_folder.Items)
for i, item in enumerate(sub_2_folder.Items):
    if item.Class == 43 and item.ReceivedTime.date() == heutiges_datum:  # Nur E-Mails mit Anhängen und heutigem Datum
        for attachment in item.Attachments:
            # Originalnamen und Erweiterung des Anhangs abrufen
            original_name, extension = os.path.splitext(attachment.FileName)

            # Neuen Namen erstellen basierend auf den Bedingungen
            if "prd" in item.Subject:
                new_name = "prd-" + original_name + extension
            elif "abn" in item.Subject:
                new_name = "abn-" + original_name + extension
            else:
                new_name = original_name + extension

            # Anhang herunterladen und im ausgewählten Speicherort speichern
            attachment.SaveAsFile(os.path.join(save_path, new_name))
            email_gefunden = True

    # Fortschritt aktualisieren
    progress_value = (i + 1) / email_count * 100
    update_progress_bar(progress_value)

# Fortschrittsfenster schließen
progress_window.destroy()

# Meldung anzeigen, wenn keine E-Mail gefunden wurde
if not email_gefunden:
    print("Keine Email von heute gefunden")
    messagebox.showinfo("NOT FOUND", "Keine Email von heute gefunden")
else:
    # Erfolgsmeldung anzeigen
    messagebox.showinfo("Erledigt", "Anhänge heruntergeladen")

Re: Eigenen Code in Anwendung umwandeln

Verfasst: Donnerstag 13. Juli 2023, 11:21
von __blackjack__
@Sero: Diese Werkzeuge um aus Python Installer/EXEn zu machen, versuchen zu ermitteln was sie alles mit verpacken müssen. Das klappt bei reinem Python-Code ganz gut, aber bei in C geschriebenen Abhängigkeiten können die ja nicht in das Modul schauen um zu sehen was da noch so mit muss. Das muss man dann manuell angeben.

Anmerkungen zum Quelltext: `tqdm` wird importiert aber nirgends verwendet.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Besonders unübersichtlich wird es wenn man das Hauptprogramm auch noch mit Funktionsdefinitionen vermischt.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Namen sollten nicht weit von ihrem Verwendungsort definiert werden. `email_gefunden` wird ja erst mehrere Zeilen später in einer Schleife benötigt. Wenn man den Code mal umschreibt, beispielsweise weil man Teile in eigene Funktionen/Methoden auslagern möchte, dann sollte zusammengehörender Code nahe beieinander stehen, damit man sich das nicht umständlicher zusammensuchen muss als es notwendig ist.

Der Code berücksichtig nicht wenn der Benutzer gar keinen Speicherpfad auswählt, sondern diesen Auswahldialog einfach abbricht.

Es darf nur *ein* `Tk`-Objekt geben. Das ist *das* Hauptfenster. Zusätzliche Fenster macht man mit `tkinter.Toplevel`. Allerdings gibt es ja ein unbenutzes `Tk`-Objekt. Das kann man ja einfach wieder anzeigen.

`variable` bei `Progressbar` wird nicht verwendet.

`enumerate()` kann man die Startzahl mitgeben, dann braucht man später nicht 1 auf `i` drauf addieren. `i` könnte auch besser heissen. Ebenso `item`, was ja offensichtlich eine `email` ist.

Statt `os.path` & Co verwendet man besser `pathlib`.

Warum wird der Dateiname in Name und Erweiterung aufgeteilt wenn diese beiden Dinge grundsätzlich wieder zusammengefügt werden‽

Die Tk-Hauptschleife wird nirgends aufgerufen, stattdessen wird `update()` verwendet. Das macht man nicht. Da können auch unschöne Sachen passieren, denn für das manuelle Aufrufen von `update()` gelten Regeln für die man sich mit Tk schon ganz gut auskennen muss. Und das skaliert auch überhaupt nicht wenn man anfängt die Hauptschleife selbst zu machen.

Da man hier mit einer Rückruffunktion arbeiten muss, die sich so einiges über die Aufrufe hinweg merken muss, kommt man um eine eigene Klasse nicht herum.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import datetime
from pathlib import Path
from tkinter import Tk, filedialog, messagebox, ttk

import win32com.client


class App(Tk):
    def __init__(self):
        Tk.__init__(self)
        self.title("Emails werden durchsucht")
        progress_bar = ttk.Progressbar(self, length=300, mode="determinate")
        progress_bar.pack(padx=20, pady=20)
        progress_percent = ttk.Label(self)
        progress_percent.pack()

        self.heutiges_datum = None
        self.email_count = None
        self.save_path = None
        self.email_gefunden = None

    def update_progress_bar(self, value):
        self.progress_bar["value"] = value
        self.progress_percent["text"] = f"{int(value)}%"

    def _process_mails(self, emails, email_number=1):
        try:
            email = next(emails)
        except StopIteration:
            if self.email_gefunden:
                messagebox.showinfo("Erledigt", "Anhänge heruntergeladen")
            else:
                messagebox.showinfo(
                    "NOT FOUND", "Keine Email von heute gefunden"
                )
            self.quit()

        else:
            #
            # Nur E-Mails mit Anhängen (`Class`).
            #
            if (
                email.Class == 43
                and email.ReceivedTime.date() == self.heutiges_datum
            ):
                for attachment in email.Attachments:
                    for code in ["prd", "abn"]:
                        if code in email.Subject:
                            prefix = code + "-"
                            break
                    else:
                        prefix = ""
                    #
                    # TODO Schauen ob die API bei `FileName` *garantiert*, dass
                    #   da kein Pfad dabei ist, und diese *Sicherheitslücke*
                    #   entsprechend behandeln.
                    #
                    # TODO Ist da überhaupt ein Dateiname garantiert?  Und was
                    #   ist wenn mehrere Mails dort den gleichen Dateinamen
                    #   haben, sollen dann einfach vorhandene Dateien
                    #   überschrieben werden? Generell vorhandene Dateien
                    #   einfach überschreiben?
                    #
                    attachment.SaveAsFile(
                        str(Path(self.save_path, prefix + attachment.FileName))
                    )
                    self.email_gefunden = True

            self.update_progress_bar(email_number / self.email_count * 100)
            self.after(10, self.process_mails, emails, email_number + 1)

    def process_mails(self, emails, save_path):
        self.save_path = save_path
        self.heutiges_datum = datetime.date.today()
        self.email_count = len(emails)
        self.email_gefunden = False
        self._process_mails(iter(emails))


def main():
    mail_folder = (
        win32com.client.Dispatch("Outlook.Application")
        .GetNamespace("MAPI")
        .Folders.Item("ServiceDesk-DBVertrieb")
        .Folders.Item("02 - Verfahren")
        .Folders.Item("QMS/EMS")
    )
    app = App()
    app.withdraw()

    save_path = filedialog.askdirectory(
        title="Speicherort für die heruntergeladenen Dateien auswählen"
    )
    if save_path:
        app.deiconify()
        app.after_idle(app.process_mails, mail_folder.Items, save_path)
        app.mainloop()


if __name__ == "__main__":
    main()
Bezüglich des `Filename` sind da noch zwei TODOs drin die man (IMHO dringend) angehen sollte.

Re: Eigenen Code in Anwendung umwandeln

Verfasst: Donnerstag 13. Juli 2023, 13:32
von Sero
Wow, erstmal vielen Dank für diese ausführliche Antwort. Ich merke ich bin noch gaaaannzz am Anfang und muss vieles lernen.
Ich muss ehrlicherweise zugeben, dass ich den Code mithilfe von ChatGPT erstellt habe. Ich habe in Python nämlich eher die Auswertung von großen Datenmengen gelernt.

Da ich aber so nicht wirklich viel lernen kann, habe ich mich dazu entschieden in Foren Hilfe zu holen und so zu lernen.

Ich habe nicht alles verstanden, was du mir geschrieben/empfohlen hast, aber ich teste den Code auf jeden Fall und gebe dir eine Rückmeldung.

Hast du evtl. einen Tipp für mich wie ich besser werde in der Python Programmierung. Mir ist klar, dass ich nicht kurzer Zeit ein Pro werde, aber ich investiere gerne die Zeit :)

Danke.

Re: Eigenen Code in Anwendung umwandeln

Verfasst: Donnerstag 13. Juli 2023, 14:50
von __blackjack__
@Sero: Ha, gut das ich ungetestet dran geschrieben habe — ich sehe gerade das der Fortschrittsbalken und das Label für die %-Angabe nicht an das Objekt gebunden werden. 🤔

Programmieren lernt man in dem man es tut. Also Übung.