Python Forum

Full Version: Combine console script + GUI (tkinter)
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hello,
I have two python scripts. First is quickstart.py ans second is dater.py. My goal is to run first script and then inside call second script. I tried with sub-process but without success. The main problem is that window from tkinter runs twice. What does it mean? I am setting Start date and End date in calendar, then clicking Apply and at the end clicking X for closing windows. Right after close I am getting another same window which I need to close to go further with script. How to handle that?

import os import base64 import subprocess from functions import batch_vat from dater import * from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError # If modifying these scopes, delete the file token.json. SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"] def main(): """Shows basic usage of the Gmail API. Lists the user's Gmail labels. """ creds = None # The file token.json stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. if os.path.exists("token.json"): creds = Credentials.from_authorized_user_file("token.json", SCOPES) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( "credentials.json", SCOPES ) creds = flow.run_local_server(port=0) # Save the credentials for the next run with open("token.json", "w") as token: token.write(creds.to_json()) subprocess.run(["python", "dater.py"]) start_date = start end_date = end #start_date = input("Start date[year/month/day]: ") #end_date = input("End date[year/month/day]: ") transformed_start_date = start_date.replace("/", "_") transformed_end_date = end_date.replace("/", "_") # Directory directory = f"{transformed_start_date}-{transformed_end_date}" # Parent Directory path parent_dir = "C:/Users/Dawid/Desktop/orlen" # Path path = os.path.join(parent_dir, directory) #print(path) os.mkdir(path) print("## INFO ## Created directory", directory) #source = os. getcwd() #shutil.copy(f"{source}/read.py", f"{path}/read.py") try: # Call the Gmail API service = build("gmail", "v1", credentials=creds) results = service.users().messages().list(userId="me", labelIds="Label_1748439073231171664", q=f"after:{start_date} before:{end_date}").execute() messages = results.get("messages", []) #print(messages) #print(json.dumps(messages, indent = 1)) licznik = 1 for message in messages: message_id = message["id"] #print("Message ID = ", message_id) msg = service.users().messages().get(userId='me', id=message_id).execute() attachment_id = msg["payload"]["parts"][0]["body"]["attachmentId"] #print("Attachment ID: ", attachment_id) att = service.users().messages().attachments().get(userId='me', messageId=message_id, id=attachment_id).execute() attachment_data = att["data"] #print(json.dumps(attachment_data, indent = 1)) file_data = base64.urlsafe_b64decode(attachment_data.encode('UTF-8')) filename = f"paliwo{licznik}.pdf" #print(filename) #path = os.path.join(store_dir + '\\' 'Downloaded files' + '\\' + filename) with open(f"{path}/{filename}", 'wb') as f: f.write(file_data) f.close() licznik = licznik + 1 current_dir = os. getcwd() path_for_batch_vat=f"{current_dir}\\{directory}" print("## INFO ## Changing context to", path_for_batch_vat) os.chdir(path_for_batch_vat) print("## INFO ## Running VAT calculations") batch_vat(path_for_batch_vat) except HttpError as error: # TODO(developer) - Handle errors from gmail API. print(f"An error occurred: {error}") if __name__ == "__main__": main()
from tkinter import * import ttkbootstrap as tb def get_dates(): global start global end start = start_date.entry.get() end = end_date.entry.get() print("## INFO ## Start date: ", start) print("## INFO ## End date: ", end) return start, end root = tb.Window(themename="solar") root.title("Date picker!") root.geometry("300x300") start_date = tb.DateEntry(root, dateformat="%Y/%m/%d", firstweekday=0) start_date_label = tb.Label(root, text="Start date:") start_date.grid(column=1, row=0) start_date_label.grid(column=0, row=0, pady=50, padx=20) end_date = tb.DateEntry(root, dateformat="%Y/%m/%d", firstweekday=0) end_date_label = tb.Label(root, text="End date:") end_date.grid(column=1, row=1) end_date_label.grid(column=0, row=1, padx=20) my_button = tb.Button(root, text="Apply", command=get_dates, width=20) my_button.place(x=80, y=200) root.mainloop()
dater.py creates a window when it is imported in your script. A second dater.py is created when you run(["python", "dater.py"]).

I would import dater.py and create an instance from the script instead of running it as a subprocess. Creating the window from your script will make it easier to get the selected dates than trying to get the subprocess to return information.

Either way you need to modify dater to behave like a dialog. Currently dater is a window. There is no way for your dater window to return date information to your script. When the dater window is visible, the window blocks your script from running. That's fine, because you want your script to wait until it has the date information. But when the window is closed, all the date information gets deleted along with the window. By the time your script knows the window is closed, it is too late to ask for the dates.

Writing dialogs is more difficult than a window. You might want to consider folding your script into your window code instead of having a script that calls a dialog.
This is a low rent way of doing what you want with the dialog.
import ttkbootstrap as ttk def get_dates(start=None, end=None, format="%Y/%m/%d"): """Draw dialog for selecting start and end dates.""" root = ttk.Window() root.title("Enter Dates") # If window is destroyed we cannot get the date values. # Override what the close button does. root.wm_protocol("WM_DELETE_WINDOW", root.quit) frame = ttk.Frame(root) frame.pack(padx=10, pady=10) ttk.Label(frame, text="Start Date").grid(row=0, column=0, pady=(0, 10)) start = ttk.DateEntry(frame, start_date=start, dateformat=format) start.grid(row=0, column=1, pady=(0, 10)) ttk.Label(frame, text="End Date").grid(row=1, column=0, pady=(0, 10)) end = ttk.DateEntry(frame, start_date=end, dateformat=format) end.grid(row=1, column=1, pady=(0, 10)) done = ttk.Button(frame, text="Done", command=root.quit) done.grid(row=2, column=0, columnspan=2, sticky="news") # Wait until the done button or close decoration are pressed. root.mainloop() # Get dates before destroying the window. start = start.entry.get() end = end.entry.get() root.destroy() return start, end print(get_dates())
The script that used the dialog is just a print() statement, but I think you can see how the dialog would get used in a longer script.