aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Chudnick <sam@chudnick.com>2023-06-04 17:47:00 -0400
committerSam Chudnick <sam@chudnick.com>2023-06-04 17:47:00 -0400
commit52cd654f581e7986de524a97e9ae61bf38324228 (patch)
tree7c7d589d39a3ca010278b9a0aaefa2cd5dcf3797
parentc023b5704d49679989ddae60851b82f051f4ac2f (diff)
Started turning into flask app
-rw-r--r--app.py45
-rw-r--r--forms.py8
-rw-r--r--library.py68
-rw-r--r--main.py53
-rw-r--r--static/style.css102
-rw-r--r--templates/base.html21
-rw-r--r--templates/daily.html13
-rw-r--r--templates/hourly.html18
-rw-r--r--templates/index.html26
9 files changed, 291 insertions, 63 deletions
diff --git a/app.py b/app.py
new file mode 100644
index 0000000..e929032
--- /dev/null
+++ b/app.py
@@ -0,0 +1,45 @@
1#!/usr/bin/env python3
2import json, requests, datetime, argparse, pytz, flask
3import library, forms
4
5app = flask.Flask(__name__)
6app.config['SECRET_KEY'] = "hunter2"
7
8@app.route('/', methods=('GET','POST'))
9def index():
10 form = forms.WeatherForm()
11 if form.validate_on_submit():
12 location = form.location.data
13 days = form.days.data
14 forecast_type = form.forecast_type.data
15 print(location)
16 print(days)
17 print(forecast_type)
18 if forecast_type == 'hourly':
19 return flask.redirect(flask.url_for('hourly', location=location, days=days))
20 elif forecast_type == 'daily':
21 return flask.redirect(flask.url_for('daily', location=location, days=days))
22
23 return flask.render_template("index.html", form=form)
24
25@app.route('/hourly')
26def hourly():
27 location = flask.request.args.get('location', type=str)
28 days = flask.request.args.get('days', type=int)
29 latitude, longitude = library.get_lat_long(location)
30 grid_data = library.get_grid_data(latitude, longitude)
31 raw_data = library.get_raw_data(grid_data["grid_id"], grid_data["grid_x"], grid_data["grid_y"])
32 data = library.hourly_forecast(raw_data, days)
33 return flask.render_template("hourly.html", data=data)
34
35
36@app.route('/daily')
37def daily():
38 location = flask.request.args.get('location', type=str)
39 days = flask.request.args.get('days', type=int)
40 latitude, longitude = library.get_lat_long(location)
41 grid_data = library.get_grid_data(latitude, longitude)
42 raw_data = library.get_raw_data(grid_data["grid_id"], grid_data["grid_x"], grid_data["grid_y"])
43 raw_forecast = library.get_raw_forecast(grid_data["grid_id"], grid_data["grid_x"], grid_data["grid_y"])
44 data = library.daily_forecast(raw_data, raw_forecast, days)
45 return flask.render_template("daily.html", data=data)
diff --git a/forms.py b/forms.py
new file mode 100644
index 0000000..804a0f2
--- /dev/null
+++ b/forms.py
@@ -0,0 +1,8 @@
1import flask_wtf, wtforms
2
3class WeatherForm(flask_wtf.FlaskForm):
4 location = wtforms.StringField("Location", validators=[wtforms.validators.DataRequired()])
5 days = wtforms.SelectField("Days", choices=[('1','1'),('2','2'),('3','3'),('4','4'),('5','5'),('6','6'),('7','7')])
6 forecast_type = wtforms.RadioField("Type", choices=[('hourly', 'Hourly Forecast'),('daily','Daily Forecast')], default="hourly")
7 submit = wtforms.SubmitField("Submit")
8
diff --git a/library.py b/library.py
index dcb4630..0d490a1 100644
--- a/library.py
+++ b/library.py
@@ -23,12 +23,14 @@ def get_grid_data(latitude, longitude):
23 23
24 24
25def get_raw_data(grid_id, grid_x, grid_y): 25def get_raw_data(grid_id, grid_x, grid_y):
26 raw_data = json.loads(requests.get(f"https://api.weather.gov/gridpoints/{grid_id}/{grid_x},{grid_y}").text) 26 headers = {"User-Agent": "pywttr 0.1"}
27 raw_data = json.loads(requests.get(f"https://api.weather.gov/gridpoints/{grid_id}/{grid_x},{grid_y}", headers=headers).text)
27 return raw_data 28 return raw_data
28 29
29 30
30def get_raw_forecast(grid_id, grid_x, grid_y): 31def get_raw_forecast(grid_id, grid_x, grid_y):
31 raw_data = json.loads(requests.get(f"https://api.weather.gov/gridpoints/{grid_id}/{grid_x},{grid_y}/forecast", user_agent="my-test-app").text) 32 headers = {"User-Agent": "pywttr 0.1"}
33 raw_data = json.loads(requests.get(f"https://api.weather.gov/gridpoints/{grid_id}/{grid_x},{grid_y}/forecast", headers=headers).text)
32 return raw_data 34 return raw_data
33 35
34 36
@@ -136,11 +138,22 @@ def get_daily_lows(raw_data):
136 time_str, duration_str = low["validTime"].split('/') 138 time_str, duration_str = low["validTime"].split('/')
137 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z") 139 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
138 duration = parse_duration(duration_str) 140 duration = parse_duration(duration_str)
139 daily_lows.append({"time":time,"duration":duration,"low_celc":low_celc,"low_fahr":low_far}) 141 daily_lows.append({"time":time,"duration":duration,"low_celc":low_celc,"low_fahr":low_fahr})
140 142
141 return daily_lows 143 return daily_lows
142 144
143 145
146def get_daily_forecast(raw_data):
147 daily_forecast_raw = raw_data["properties"]["periods"]
148
149 daily_forecast = []
150 for point in daily_forecast_raw:
151 time_str = point["startTime"]
152 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
153 daily_forecast.append({"time":time, "short_forecast":point["shortForecast"], "detailed_forecast":point["detailedForecast"]})
154 return make_current(set_timezone(daily_forecast))
155
156
144def get_temperature(raw_data): 157def get_temperature(raw_data):
145 raw_values = raw_data["properties"]["temperature"]["values"] 158 raw_values = raw_data["properties"]["temperature"]["values"]
146 ret = [] 159 ret = []
@@ -334,11 +347,9 @@ def get_hourly_data(raw_data, end_time):
334 precip_amount = get_precip_amount(raw_data) 347 precip_amount = get_precip_amount(raw_data)
335 wind_speed = get_wind_speed(raw_data) 348 wind_speed = get_wind_speed(raw_data)
336 wind_direction = get_wind_direction(raw_data) 349 wind_direction = get_wind_direction(raw_data)
337 350 ret_list = []
338 ret = []
339 i = 0 351 i = 0
340 while i < len(temps) and temps[i]["time"] < end_time: 352 while i < len(temps) and temps[i]["time"] < end_time:
341
342 if i >= len(temps): 353 if i >= len(temps):
343 temps.append({"value":"N/A"}) 354 temps.append({"value":"N/A"})
344 if i >= len(humidity): 355 if i >= len(humidity):
@@ -352,8 +363,6 @@ def get_hourly_data(raw_data, end_time):
352 if i >= len(wind_direction): 363 if i >= len(wind_direction):
353 wind_direction.append({"value":"N/A"}) 364 wind_direction.append({"value":"N/A"})
354 365
355
356
357 val_dict = { "time": temps[i]["time"], 366 val_dict = { "time": temps[i]["time"],
358 "temp": temps[i]["value_fahr"], 367 "temp": temps[i]["value_fahr"],
359 "humidity": humidity[i]["value"], 368 "humidity": humidity[i]["value"],
@@ -361,7 +370,46 @@ def get_hourly_data(raw_data, end_time):
361 "precip_amount": precip_amount[i]["value"], 370 "precip_amount": precip_amount[i]["value"],
362 "wind_speed": wind_speed[i]["value"], 371 "wind_speed": wind_speed[i]["value"],
363 "wind_direction": wind_direction[i]["value"] } 372 "wind_direction": wind_direction[i]["value"] }
364 ret.append(val_dict) 373 ret_list.append(val_dict)
365 i+=1 374 i+=1
366 375
367 return ret 376 return ret_list
377
378
379def get_daily_data(raw_data, raw_forecast, end_time):
380 daily_highs = get_daily_highs(raw_data)
381 daily_lows = get_daily_lows(raw_data)
382 daily_forecasts = get_daily_forecast(raw_forecast)
383 ret_list = []
384 i = 0
385 while i < len(daily_highs) and daily_highs[i]["time"] < end_time:
386 val_dict = { "time": daily_highs[i]["time"],
387 "high": daily_highs[i]["high_fahr"],
388 "low": daily_lows[i]["low_fahr"],
389 "short_forecast_am": daily_forecasts[i]["short_forecast"],
390 "detailed_forecast_am": daily_forecasts[i]["detailed_forecast"],
391 "short_forecast_pm": daily_forecasts[i+1]["short_forecast"],
392 "detailed_forecast_pm": daily_forecasts[i+1]["detailed_forecast"] }
393 ret_list.append(val_dict)
394 i+=1
395 return ret_list
396
397
398def hourly_forecast(raw_data, days):
399 init_time = get_current_rounded_time()
400 if days > 0:
401 delta = datetime.timedelta(days=days)
402 else:
403 delta = datetime.timedelta(hours=(24-init_time.hour))
404 end_time = init_time + delta
405 return get_hourly_data(raw_data, end_time)
406
407
408def daily_forecast(raw_data, raw_forecast, days):
409 init_time = get_current_rounded_time()
410 if days > 0:
411 delta = datetime.timedelta(days=days)
412 else:
413 delta = datetime.timedelta(days=5)
414 end_time = init_time + delta
415 return get_daily_data(raw_data, raw_forecast, end_time)
diff --git a/main.py b/main.py
deleted file mode 100644
index 3fbf3e7..0000000
--- a/main.py
+++ /dev/null
@@ -1,53 +0,0 @@
1#!/usr/bin/env python3
2import json, requests, datetime, argparse, pytz
3
4import library
5
6def parse_args():
7 parser = argparse.ArgumentParser()
8 parser.add_argument('-d', '--days', type=int)
9 parser.add_argument('-H', '--hours', type=int)
10 parser.add_argument('-l', '--location', type=str, required=True)
11 return parser.parse_args()
12
13
14def hourly_forecast(args, raw_data):
15
16 init_time = library.get_current_rounded_time()
17 if args.hours is not None:
18 delta = datetime.timedelta(hours=args.hours)
19 elif args.days is not None:
20 delta = datetime.timedelta(days=args.days)
21 else:
22 delta = datetime.timedelta(hours=(24-init_time.hour))
23 end_time = init_time + delta
24
25 hourly_data = library.get_hourly_data(raw_data, end_time)
26
27 for point in hourly_data:
28 print(point["time"].strftime("%a %x %I:%M %p"))
29 print(f'\t Temperature - { point["temp"] }°F')
30 print(f'\t Humidity - { point["humidity"] }%')
31 print(f'\t Chance of Precipitation - { point["precip_chance"] }%')
32 print(f'\t Precipitation Amount - { point["precip_amount"] } in')
33 print(f'\t Wind Speed - { point["wind_speed"] } MPH')
34 print(f'\t Wind Direction - { point["wind_direction"] }')
35 print('\n')
36
37
38
39def daily_forecast(raw_data):
40 pass
41
42
43def main():
44 args = parse_args()
45 latitude, longitude = library.get_lat_long(args.location)
46 grid_data = library.get_grid_data(latitude, longitude)
47 raw_data = library.get_raw_data(grid_data["grid_id"], grid_data["grid_x"], grid_data["grid_y"])
48 hourly_forecast(args, raw_data)
49
50
51
52if __name__ == '__main__':
53 main()
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..693eee9
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,102 @@
1@charset "UTF-8";
2
3:root {
4 /* Set sans-serif & mono fonts */
5 --sans-font: Inter, Lato,Helvetica,"IBM Plex Sans","Roboto","Nimbus Sans L","Noto Sans", "Segoe UI",Arial,Helvetica,"Helvetica Neue",sans-serif;
6 --mono-font: "mononoki Nerd Font","IBM Plex Mono","Roboto Mono","Ubuntu Mono","Fira Code","Overpass Mono", Monaco,"Droid Sans Mono",monospace;
7 --bg: #242933;
8 --accent-bg: rgb(46, 52, 64);
9 --text: #eceff4;
10 --text-light: #d8dee9;
11 --border: #88c0d0;
12 --accent: #81a1c1;
13 --accent-light: #bf616a;
14 --code: #ebcb8b;
15 --alert: #a3be8c;
16 --alert-bg: #8fbcbb;
17 --code-bg: #2e3440;
18}
19
20
21html, body, footer {
22 background: var(--bg);
23 color: var(--text);
24 font-family: var(--sans-font);
25 justify-content: center;
26 align-items: center;
27 display: flex;
28}
29
30div.weather-box {
31 height: 100%;
32 align-items: center;
33 display: inline-flex;
34}
35
36div.daily-box {
37 text-align: center;
38 padding: 1em;
39 border: 0.5em solid var(--acent-bg);
40 border-radius: 5%;
41 box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12), 0 1px 4px rgba(0, 0, 0, 0.24);
42 box-sizing: border-box;
43 width: 20em;
44 height: 10em;
45 overflow-wrap: normal;
46 float: left;
47}
48
49div.hourly-box {
50 text-align: center;
51 padding: 1em;
52 border: 0.5em solid var(--acent-bg);
53 border-radius: 5%;
54 box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12), 0 1px 4px rgba(0, 0, 0, 0.24);
55 box-sizing: border-box;
56 width: 20em;
57 height: 10em;
58 overflow-wrap: normal;
59}
60
61
62/*=== Links */
63a {
64 color: var(--accent);
65}
66
67a:hover {
68 color: var(--accent);
69}
70
71input, select, textarea {
72 margin: 5px;
73 padding: 5px;
74 color: var(--text);
75 border: 1px solid var(--border);
76 border-radius: 6px;
77 border-color: var(--border);
78 background-color: var(--bg);
79 min-height: 25px;
80 line-height: 25px;
81 vertical-align: middle;
82}
83
84input:disabled, select:disabled {
85 color: #aaa;
86 border-color: var(--border);
87}
88
89button {
90 font-family: var(--sans-font);
91}
92
93button.as-link,
94button.as-link:hover,
95button.as-link:active {
96 background: transparent;
97 /* background-color: var(--bg);A*/
98}
99
100button.as-link[disabled] {
101 color: #ddd !important;
102}
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 0000000..d464a5a
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,21 @@
1<!DOCTYPE html>
2<html>
3<head>
4 <meta charset="utf-8">
5 <title>{% block title %} {% endblock %}</title>
6 <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
7 <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css' ) }}">
8</head>
9<body>
10
11 <div class="container">
12 {% block content %} {% endblock %}
13 </div>
14
15<footer>
16 {% block footer %}
17 {% endblock %}
18</footer>
19{% block scripts %}{% endblock %}
20</body>
21</html>
diff --git a/templates/daily.html b/templates/daily.html
new file mode 100644
index 0000000..5eb765e
--- /dev/null
+++ b/templates/daily.html
@@ -0,0 +1,13 @@
1{% extends 'base.html' %}
2{% block content %}
3<div class=weather-box>
4{% for item in data %}
5 <div class=daily-box>
6 {{ item.time.strftime("%a %x") }} <br>
7 {{ item.high }}°F / {{ item.low }}°F <br>
8 {{ item.short_forecast_am }} <br><br>
9 </div>
10{% endfor %}
11</div>
12{% endblock %}
13
diff --git a/templates/hourly.html b/templates/hourly.html
new file mode 100644
index 0000000..56f9d9b
--- /dev/null
+++ b/templates/hourly.html
@@ -0,0 +1,18 @@
1{% extends 'base.html' %}
2
3{% block content %}
4<div class=weather-box>
5{% for item in data %}
6 <div class=daily-box>
7 {{ item.time.strftime("%a %x %I:%M %p") }} <br>
8 {{ item.temp }}°F <br>
9 {{ item.humidity }}%<br>
10 {{ item.precip_chance }}%<br>
11 {{ item.precip_amount }}in<br>
12 {{ item.wind_speed }}MPH<br>
13 {{ item.wind_direction }}<br><br>
14 </div>
15{% endfor %}
16</div>
17{% endblock %}
18
diff --git a/templates/index.html b/templates/index.html
new file mode 100644
index 0000000..2d97379
--- /dev/null
+++ b/templates/index.html
@@ -0,0 +1,26 @@
1{% extends 'base.html' %}
2
3{% block content %}
4<h1>{% block title %} Enter a Location {% endblock %}</h1>
5<form method="post">
6 {{ form.csrf_token }}
7 <p>
8 {{ form.location.label }} <br>
9 {{ form.location }}
10 </p>
11 <p>
12 {{ form.days.label }} <br>
13 {{ form.days }}
14 </p>
15 <p>
16 {{ form.forecast_type.label }} <br>
17 {{ form.forecast_type }}
18 </p>
19 <p>{{ form.submit() }}</p>
20</form>
21{% endblock %}
22
23<form method="POST" action="/">
24 {{ form.name.label }} {{ form.name(size=20) }}
25 <input type="submit" value="Go">
26</form>