Eine Funktion jeden Tag zu einer bestimmten Uhrzeit ausführen mit dem Python3 sched Modul

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
benpython
User
Beiträge: 8
Registriert: Sonntag 10. Mai 2020, 12:24

Hallo Zusammen,

ich versuche mit dem sched Modul von Python3 eine Funktion jeden Tag zu einer bestimmten Uhrzeit auszuführen.
Ich habe eine Funktion sendEmail() diese soll immer um12 Uhr ausgeführt werden.

Die Dokumentation unter https://docs.python.org/3/library/sched.html habe ich mir durchgelesen aber ich werde nicht schlau daraus.
Folgendes habe ich probiert.

Code: Alles auswählen

import time
import sched


def sendEmail():
    print("sending Email")


t = time.strftime("12:00")  # String in Zeitformat umrechnen?!?!?
scheduler = sched.scheduler(time.time, time.sleep)
scheduler.enterabs(t, 1, sendEmail)
scheduler.run()
Fehlermeldung ist: "TypeError: '>' not supported between instances of 'str' and 'float'"
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@benpython: Du solltest `scheduler()` nichts übergeben. Beim zweiten Argument übergibst Du sowieso den Defaultwert, und beim ersten ersetzt Du `time.monotonic()` durch das schlechtere `time.time()`.

`t` ist eine Zeichenkette. Und zwar "12:00". `time.strftime()` ist also ziemlich sinnlos wenn man damit eine konstante Zeichenkette ”formatiert”. Das ist einfach ``t = "12:00"``. `scheduler` will aber einen Wert haben der zu `time.monotonic()`/`time.time()` passt. Also die Sekunden seit „Epoch“. Da ist auch das Datum enthalten. Wenn das also morgen um 12 Uhr ausgeführt werden soll, dann musst Du da auch morgen um 12 als Sekundenanzahl seit „Epoch“ angeben. Und wenn das jeden Tag passieren soll auch für jeden Tag ein Ereignis eintragen. Wobei man das dann natürlich auch immer dann eintragen könnte wenn ein Ereignis abgearbeitet wurde.

`t` und `scheduler` haben auf Modulebene nichts zu suchen, das sollte in einer Funktion stehen. Üblicherweise heisst die `main()`.

`t` ist kein guter Name weil der nichts aussagt.

Namen schreibt man in Python klein_mit_unterstrichen. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Eventuell könnte auch das externe `apscheduler`-Modul interessant sein, wenn man anfängt sich mit `sched` selbst so etwas zu basteln.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
noisefloor
User
Beiträge: 4194
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

warum lässt du das nicht einfach das Betriebssystem machen, unter Linux z.B. mit einer systemd Timer Unit?

Gruß, noisefloor
benpython
User
Beiträge: 8
Registriert: Sonntag 10. Mai 2020, 12:24

__blackjack__ hat geschrieben: Sonntag 10. Mai 2020, 16:51 Eventuell könnte auch das externe `apscheduler`-Modul interessant sein, wenn man anfängt sich mit `sched` selbst so etwas zu basteln.
Bin umgeschwenkt auf apscheduler. Nachfolgendes Skript soll jeden Tag um 23 Uhr DPDPrint.exe beenden, um 4 Uhr morgens wieder starten, einloggen und den Imprortservice starten.
Leider verpasst apscheduler die Ausführung des Jobs und ich weiss nicht warum das so ist. Finde dazu im Netz nicht viel. Und das was ich gefunden habe begreife ich einfach nicht.

Mein Skript soweit:

Code: Alles auswählen

import os, pyautogui, time
import pygetwindow as gw
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger

PASSWORD = os.environ.get('dpdPrintPassword') # set ur environment Variable with os.environ['dpdPrintPassword'] = 'yoursecretpassword'

def killDPD():
    os.system('"taskkill /f /im DPDPrint.exe"')
    os.system('"taskkill /f /im DPDPrint.exe"')
    os.system('"taskkill /f /im DPDPrint.exe"')
    
def restartDPD():
    windows = gw.getAllTitles()
    for window in windows:
        window.minimize()
    os.system('"C:\Program Files (x86)\DPD\DPDPrint\DPDPrint.exe"')
    time.sleep(60) # Wait till login is loaded
    dpdprint = gw.getWindowsWithTitle('DPD Print')[0]
    dpdprint.activate()
    pyautogui.click(859, 496)
    time.sleep(1)
    pyautogui.write(PASSWORD, interval=0.05) 
    time.sleep(1)
    pyautogui.click(735, 527)
    time.sleep(60)

    # START IMPORTSERVICE
    dpdprint = gw.getWindowsWithTitle('DPD Print')[0]
    dpdprint.activate()
    time.sleep(2)
    if not dpdprint.isMaximized:
        dpdprint.maximize()
    time.sleep(2)
    pyautogui.click(1355, 127)

scheduler = BlockingScheduler(timezone="Europe/Berlin")
scheduler.add_job(killDPD, CronTrigger(hour=23))
scheduler.add_job(restartDPD, CronTrigger(hour=4))
scheduler.print_jobs()
scheduler.start()
Vielleicht kennt sich jemand von euch besser mit apscheduler aus und kann Hilfestellung geben.

Im Voraus vielen Dank!
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

os.system sollte man aus gutem Grund nicht verwenden, und so wie Du es verwendest, sieht es sehr falsch aus.

Woher weißt Du, dass apscheduler die Ausführung des Jobs verpasst?
Wie startest Du und wo läut das ganze?
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@benpython: Noch mal der Hinweis auf die Namenschreibweise: klein_mit_unterstrichen.

Der Code auf Modulebene sollte in einer Hauptfunktion verschwinden.

Den Pfad zur EXE würde ich als Konstante definieren, denn der Pfad beziehungsweise der Dateiname wird ja mehrfach im Code benötigt und sollte nicht mehrfach dort stehen.

`os.system()` sollte man nicht mehr verwenden. Zum starten von externen Prozessen gibt es das `subprocess`-Modul. Damit kann man die Prozesse dann unter anderem auch ohne eine unnötige zusätzliche Shell ausführen.

Funktioniert das beenden des/der Prozesse(s) so überhaupt? Die " sagen der Shell ja, dass das *ein* Element ist, also dass das Programm ``taskkill /f /im DPDPrint.exe`` heisst und keine Argumente bekommt. Das Programm heisst doch aber nur ``taskkill``und der Rest sind die Argumente. Warum wird das dreimal ausgeführt?

Ich würde an der Stelle auch eher das `psutil`-Modul verwenden, statt ein externes Programm zu starten.

Funktioniert das starten grundsätzlich nicht oder nur einmal? Denn der Code funktioniert so wie er da steht ja nur wenn das Starten von ``DPDPrint.exe`` nicht blockiert.

Code: Alles auswählen

#!/usr/bin/env python3
import os
import subprocess
import time
from pathlib import Path

import pyautogui
import pygetwindow
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger

#
# Set your environment variable with
# ``os.environ["dpdPrintPassword"] = "yoursecretpassword"``.
#
PASSWORD = os.environ.get("dpdPrintPassword")
DPD_EXE_PATH = Path(R"C:\Program Files (x86)\DPD\DPDPrint\DPDPrint.exe")
WINDOW_TITLE = "DPD Print"


def kill_dpd():
    #
    # TODO Use `psutil` instead of starting an external program.
    #
    for _ in range(3):
        subprocess.run(
            ["taskkill", "/f", "/im", DPD_EXE_PATH.name], check=True
        )


def activate_first_window(window_title):
    window = pygetwindow.getWindowsWithTitle(window_title)[0]
    window.activate()
    return window


def restart_dpd():
    for window in pygetwindow.getAllTitles():
        window.minimize()
    #
    # FIXME Is this blocking?
    #
    subprocess.run([str(DPD_EXE_PATH)], check=True)

    time.sleep(60)  # Wait till login is loaded.
    activate_first_window(WINDOW_TITLE)
    pyautogui.click(859, 496)  # TODO Document what this position means.
    time.sleep(1)
    pyautogui.write(PASSWORD, interval=0.05)
    time.sleep(1)
    pyautogui.click(735, 527)  # TODO Document what this position means.
    time.sleep(60)
    #
    # Start import service.
    #
    dpd_print_window = activate_first_window(WINDOW_TITLE)
    time.sleep(2)
    if not dpd_print_window.isMaximized:
        dpd_print_window.maximize()
    time.sleep(2)
    pyautogui.click(1355, 127)  # TODO Document what this position means.


def main():
    scheduler = BlockingScheduler(timezone="Europe/Berlin")
    scheduler.add_job(kill_dpd, CronTrigger(hour=23))
    scheduler.add_job(restart_dpd, CronTrigger(hour=4))
    scheduler.print_jobs()
    scheduler.start()


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
benpython
User
Beiträge: 8
Registriert: Sonntag 10. Mai 2020, 12:24

Vielen Dank für eure Hilfe. Ich habe den Code jetzt nochmal umgeschrieben. Vielen Dank blackjack für den verbesserten Code.
Leider funktioniert das Programm so immer noch nicht. Genauer gesagt funktioniert das beenden von DPDPrint.exe nicht.
Das hab ich jetzt auf euer anraten mit psutil gelöst.

Alle Funktionen funktionieren einzeln. Aber wenn das Programm läuft, wird DPDPrint.exe nicht beendet und wenn das Script dann versucht die DPDPrint.exe wieder zu starten läuft ja bereits eine instanz der Anwendung.
Ich führe das Script in Powershell aus.
Bild

Code: Alles auswählen

import os
import subprocess
import time
from pathlib import Path
import psutil

import pyautogui
import pygetwindow as gw
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger

#
# Set your environment variable with
# ``os.environ["dpdPrintPassword"] = "yoursecretpassword"``.
#
PASSWORD = os.environ.get("dpdPrintPassword")
DPD_EXE_PATH = Path(R"C:\Program Files (x86)\DPD\DPDPrint\DPDPrint.exe")
WINDOW_TITLE = "DPD Print"

def minimize_windows():
    windows = gw.getAllTitles()
    realWindows = []
    for window in windows:
        if window != "":
            realWindows.append(window)
    for window in realWindows:
        cWindow = gw.getWindowsWithTitle(window)[0]
        cWindow.minimize()

def kill_dpd():
    PROCNAME = 'DPDPrint.exe'
    for proc in psutil.process_iter():
        try:
            if proc.name() == PROCNAME:
                p = psutil.Process(proc.pid)
                p.kill()
        except Exception as e:
            print(e)
            pass


def activate_first_window(window_title):
    window = gw.getWindowsWithTitle(window_title)[0]
    window.activate()
    return window


def restart_dpd():
    minimize_windows()
    subprocess.run([str(DPD_EXE_PATH)], check=True)

    time.sleep(60)  # Wait till login is loaded.
    activate_first_window(WINDOW_TITLE)
    pyautogui.click(859, 496)  # Click into password field.
    time.sleep(1)
    pyautogui.write(PASSWORD, interval=0.05) # Write password to the field
    time.sleep(1)
    pyautogui.click(735, 527)  # Click on submit button.
    time.sleep(60)
    #
    # Start import service.
    #
    dpd_print_window = activate_first_window(WINDOW_TITLE)
    time.sleep(2)
    if not dpd_print_window.isMaximized:
        dpd_print_window.maximize()
    time.sleep(2)
    pyautogui.click(1355, 127)  # Cick the button in the top right corner to start the DPD Print importservice.


def main():
    scheduler = BlockingScheduler(timezone="Europe/Berlin")
    scheduler.add_job(kill_dpd, CronTrigger(hour=23))
    scheduler.add_job(restart_dpd, CronTrigger(hour=4))
    scheduler.print_jobs()
    scheduler.start()


if __name__ == "__main__":
    main()
    
Meine Fragen:
@__blackjack__ : Was macht die Zeile:

Code: Alles auswählen

 if __name__ == "__main__": main() 
Muss ich die Zeit in den CronTrigger anders eintragen? Oder was ist mein Fehler.
benpython
User
Beiträge: 8
Registriert: Sonntag 10. Mai 2020, 12:24

Jetzt hab ich noch ein Problem festgestellt. Wenn das Programm beendet ist und
subprocess.run([str(DPD_EXE_PATH)], check=True) die exe wieder starten will meldet er jedesmal:
CalledProcessError: Command '['C:\\Program Files (x86)\\DPD\\DPDPrint\\DPDPrint.exe']' returned non-zero exit status 3.
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@benpython: Gibt es denn Dokumentation über die Exit-Codes von DPDPrint.exe was beispielsweise 6 und 3 bedeuten?
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten