6

I'm an old guy trying to learn programming. I've tried searching for an answer to this question but most of the replies are way over my head. So here goes, I've written code to get data from the web, convert it to json, and the print the results in the desired format. I'm now using Tkinter to make a display of this data. I've been successful in displaying the data and updating the labels but I am having trouble getting the URL to update (which feeds the input for the labels). So how can I update or run the request.get on a scheduled interval (once every 3 hours) without using a loop that would hold up the rest of the program?

This is what I have done so far (to run this program you will need to input your api from openweather.com)....

import requests
import time
from tkinter import *

# Input Values
api = 'Enter API Key from Openweather.com'
lat = '33.608'
lon = '-111.863'
unit = 'imperial' # unit must be 'imperial' or 'metric'
fcast_time = [0, 4, 8, 12, 16, 20, 24, 28]  # each element is a 3 hour period (i.e. 4 would be 12 hours out), max is 40

main_window = Tk()

url2 = 'http://api.openweathermap.org/data/2.5/forecast?'+'lat='+str(lat)+'&lon='+str(lon)+'&APPID='+str(api)+'&units='+str(unit)

def fconvertTime(tperiod):
    period = fcast_data['list'][fcast_time[tperiod]]['dt']
    ftime = time.strftime("%a %p", time.localtime(period))
    return(ftime)

r_forecast = requests.get(url2)
fcast_data = (r_forecast.json())

def forecast_layout(frame_name, tperiod):
    label_fcast_day = Label(frame_name, text=fconvertTime(tperiod), justify=CENTER, font=("Ariel", 8), bg="black",
                            fg="white", width=13)
    label_test_update = Label(frame_name, text=time.strftime('%H:%M:%S'), justify=CENTER, font=("Ariel", 8), bg="black",
                       fg="white", width=13)
    label_fcast_day.grid(row=0, column=0)
    label_test_update.grid(row=3, column=0)

# Configure Main Window
main_window.title("Weather")
main_window.geometry("705x500")
main_window.resizable(True, True)
main_window.configure(bg="black")

# Define sub-frames
forecast_frame = Frame(main_window, bg="blue")
forecast_frame.grid(row=0, column=0, columnspan=3)


# Build forecast_frame
frame_fcast1 = Frame(forecast_frame)
forecast_layout(frame_fcast1, 0)
frame_fcast1.grid(row=0, column=0, pady=2, padx=2, sticky=W)

frame_fcast2 = Frame(forecast_frame)
forecast_layout(frame_fcast2, 1)
frame_fcast2.grid(row=0, column=1, pady=2, padx=2, sticky=W)


main_window.mainloop()

Thanks in advance for any assistance! This is all very new to me (a few weeks) so a detailed explanation would be appreciated.

  • Read [how-do-you-run-your-own-code-alongside-tkinters-event-loop](https://stackoverflow.com/questions/459083/how-do-you-run-your-own-code-alongside-tkinters-event-loop/459131#459131) – stovfl Nov 06 '18 at 20:39
  • Please in the future simplify your question. There is way too much code here. Please provide [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve). – Mike - SMT Nov 06 '18 at 21:08
  • Thank you for the reply and I have done this to update the labels. However, I can't seem to get this to work for calling a URL. The label will update but I need to provide the label with up-to-date data supplied by the URL. – Cooperstown Nov 06 '18 at 21:14

2 Answers2

4

You want to use the after function here, which will run code alongside TKinter's main loop. You've touched on something important here; the process can only do one thing at a time, so you need the mainloop in tk to keep track of when it's time to run your code, so it can do the rest of its work in the interim.

def task():
    print("hello")
    # do your extractions
    main_window.after(60000, task)  # reschedule event in 60 seconds

main_window.after(60000, task)
main_window.mainloop()

This will, before the main loop starts, schedule a task that will run in 60000 milliseconds (or 60 seconds). task can be any function that you define.

When task runs, it too schedules another execution of itself when it finishes. You can then do your work in the task function.

Athena
  • 3,200
  • 3
  • 27
  • 35
  • Thank you for the reply. I inserted: r_forecast = requests.get(url2) and fcast_data = (r_forecast.json()) and main_window.after(10800, task) in the task function and put the function within the Tkinter loop. Also added the main.window.after line at the bottom of the Tkinter loop. It takes awhile before anything updates so I won't know if this works for another few hours. I did get a highlighted note in PyCharm that said "Shadows name 'fcast_data' from outer scope" and "Local variable 'fcast_data' value is not used". Not sure how this will impact the results. – Cooperstown Nov 06 '18 at 21:06
  • `after` doesn't run "alongside" mainloop. `mainloop` simply watches a queue for events and processes the callbacks. `after` merely adds something to the queue. You also mention threads, but `after` and threads are completely unrelated. – Bryan Oakley Nov 06 '18 at 22:38
  • @Cooperstown I'd recommend setting a shorter window to see if the updates work, then increase it once you know it works – Athena Nov 06 '18 at 22:45
  • @Ares I did set the time to a shorter period and the time component updates just fine. Unfortunately, I won't know if it successfully updated from the URL link until something changes on that side which in this case will be once the clock ticks over to tomorrow. Thanks again and hope this works! – Cooperstown Nov 06 '18 at 23:08
  • @Ares Unfortunately no luck. Even tried removing all of the functions except for the update functions but the URL is just not updating. I inserted a print command in the Task function just to make sure it was working and I do see it show up in the terminal but the URL is not getting the new info. Really want to thank you for your help. I'll just have to do more research in other places. I get the sense this forum is for professional programmers and not for hobbyists looking for some guidance. Again, many thanks for taking the time to reply! – Cooperstown Nov 07 '18 at 09:39
0

I was able to find a solution to this issue.

update_frequency = 10800000  #value is in millisecends

def url2_renew():
        global r_forecast
        global fcast_data
        r_forecast = requests.get(url2)
        fcast_data = (r_forecast.json())
        main_window.after(update_frequency, url2_renew)

Because the URL had already been called I had to add 'global' in the function so it would know to use the global variable and not local to the function.