Skip to content

Commit ea476e0

Browse files
committed
Sample code for: How to Use GitHub
1 parent 046b5de commit ea476e0

3 files changed

Lines changed: 362 additions & 0 deletions

File tree

how-to-use-github/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# How to Use GitHub
2+
3+
This folder provides the code examples for the Real Python tutorial [How to Use GitHub](https://realpython.com/how-to-use-github/)

how-to-use-github/weather.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import argparse
2+
import json
3+
import sys
4+
from configparser import ConfigParser
5+
from urllib import error, parse, request
6+
7+
# =========================
8+
# Styling / Colors
9+
# =========================
10+
PADDING = 20
11+
RED = "\033[1;31m"
12+
BLUE = "\033[1;34m"
13+
CYAN = "\033[1;36m"
14+
GREEN = "\033[0;32m"
15+
YELLOW = "\033[33m"
16+
WHITE = "\033[37m"
17+
REVERSE = "\033[;7m"
18+
RESET = "\033[0m"
19+
20+
21+
def change_color(color):
22+
print(color, end="")
23+
24+
25+
# =========================
26+
# Weather Config
27+
# =========================
28+
BASE_WEATHER_API_URL = "http://api.openweathermap.org/data/2.5/weather"
29+
# Weather Condition Codes
30+
# https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
31+
THUNDERSTORM = range(200, 300)
32+
DRIZZLE = range(300, 400)
33+
RAIN = range(500, 600)
34+
SNOW = range(600, 700)
35+
ATMOSPHERE = range(700, 800)
36+
CLEAR = range(800, 801)
37+
CLOUDY = range(801, 900)
38+
39+
40+
def read_user_cli_args():
41+
"""Handles the CLI user interactions.
42+
43+
Returns:
44+
argparse.Namespace: Populated namespace object
45+
"""
46+
parser = argparse.ArgumentParser(
47+
description="gets weather and temperature information for a city"
48+
)
49+
parser.add_argument(
50+
"city", nargs="+", type=str, help="enter the city name"
51+
)
52+
parser.add_argument(
53+
"-i",
54+
"--imperial",
55+
action="store_true",
56+
help="display the temperature in imperial units",
57+
)
58+
return parser.parse_args()
59+
60+
61+
def build_weather_query(city_input, imperial=False):
62+
"""Builds the URL for an API request to OpenWeather's Weather API.
63+
64+
Args:
65+
city_input (List[str]): Name of a city as collected by argparse
66+
imperial (bool): Whether or not to use imperial units for temperature
67+
68+
Returns:
69+
str: URL formatted for a call to OpenWeather's city name endpoint
70+
"""
71+
api_key = _get_api_key()
72+
city_name = " ".join(city_input)
73+
url_encoded_city_name = parse.quote_plus(city_name)
74+
units = "imperial" if imperial else "metric"
75+
url = (
76+
f"{BASE_WEATHER_API_URL}?q={url_encoded_city_name}"
77+
f"&units={units}&appid={api_key}"
78+
)
79+
return url
80+
81+
82+
def _get_api_key():
83+
"""Fetch the API key from your configuration file.
84+
85+
Expects a configuration file named "secrets.ini" with structure:
86+
87+
[openweather]
88+
api_key=<YOUR-OPENWEATHER-API-KEY>
89+
"""
90+
config = ConfigParser()
91+
config.read("secrets.ini")
92+
return config["openweather"]["api_key"]
93+
94+
95+
def get_weather_data(query_url):
96+
"""Makes an API request to a URL and returns the data as a Python object.
97+
98+
Args:
99+
query_url (str): URL formatted for OpenWeather's city name endpoint
100+
101+
Returns:
102+
dict: Weather information for a specific city
103+
"""
104+
try:
105+
response = request.urlopen(query_url)
106+
except error.HTTPError as http_error:
107+
if http_error.code == 401: # 401 - Unauthorized
108+
sys.exit("Access denied. Check your API key.")
109+
elif http_error.code == 404: # 404 - Not Found
110+
sys.exit("Can't find weather data for this city.")
111+
else:
112+
sys.exit(f"Something went wrong... ({http_error.code})")
113+
data = response.read()
114+
try:
115+
return json.loads(data)
116+
except json.JSONDecodeError:
117+
sys.exit("Couldn't read the server response.")
118+
119+
120+
def display_weather_info(weather_data, imperial=False):
121+
"""Prints formatted weather information about a city.
122+
123+
Args:
124+
weather_data (dict): API response from OpenWeather by city name
125+
imperial (bool): Whether or not to use imperial units for temperature
126+
127+
More information at https://openweathermap.org/current#name
128+
"""
129+
city = weather_data["name"]
130+
weather_id = weather_data["weather"][0]["id"]
131+
weather_description = weather_data["weather"][0]["description"]
132+
temperature = weather_data["main"]["temp"]
133+
change_color(REVERSE)
134+
print(f"{city:^{PADDING}}", end="")
135+
change_color(RESET)
136+
weather_symbol, color = _select_weather_display_params(weather_id)
137+
change_color(color)
138+
print(f"\t{weather_symbol}", end=" ")
139+
print(
140+
f"\t{weather_description.capitalize():^{PADDING}}",
141+
end=" ",
142+
)
143+
change_color(RESET)
144+
print(f"({temperature}°{'F' if imperial else 'C'})")
145+
146+
147+
def _select_weather_display_params(weather_id):
148+
"""Selects a weather symbol and a display color for a weather state.
149+
150+
Args:
151+
weather_id (int): Weather condition code from the OpenWeather API
152+
153+
Returns:
154+
tuple[str]: Contains a weather symbol and a display color
155+
"""
156+
if weather_id in THUNDERSTORM:
157+
display_params = ("💥", RED)
158+
elif weather_id in DRIZZLE:
159+
display_params = ("💧", CYAN)
160+
elif weather_id in RAIN:
161+
display_params = ("💦", BLUE)
162+
elif weather_id in SNOW:
163+
display_params = ("⛄️", WHITE)
164+
elif weather_id in ATMOSPHERE:
165+
display_params = ("🌀", BLUE)
166+
elif weather_id in CLEAR:
167+
display_params = ("🔆", YELLOW)
168+
elif weather_id in CLOUDY:
169+
display_params = ("💨", WHITE)
170+
else: # In case the API adds new weather codes
171+
display_params = ("🌈", RESET)
172+
return display_params
173+
174+
175+
if __name__ == "__main__":
176+
user_args = read_user_cli_args()
177+
query_url = build_weather_query(user_args.city, user_args.imperial)
178+
weather_data = get_weather_data(query_url)
179+
display_weather_info(weather_data, user_args.imperial)

how-to-use-github/weather_fixed.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import argparse
2+
import json
3+
import sys
4+
from configparser import ConfigParser
5+
from pathlib import Path
6+
from urllib import error, parse, request
7+
8+
# =========================
9+
# Styling / Colors
10+
# =========================
11+
PADDING = 20
12+
RED = "\033[1;31m"
13+
BLUE = "\033[1;34m"
14+
CYAN = "\033[1;36m"
15+
GREEN = "\033[0;32m"
16+
YELLOW = "\033[33m"
17+
WHITE = "\033[37m"
18+
REVERSE = "\033[;7m"
19+
RESET = "\033[0m"
20+
21+
22+
def change_color(color):
23+
print(color, end="")
24+
25+
26+
# =========================
27+
# Weather Config
28+
# =========================
29+
BASE_WEATHER_API_URL = "http://api.openweathermap.org/data/2.5/weather"
30+
# Weather Condition Codes
31+
# https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
32+
THUNDERSTORM = range(200, 300)
33+
DRIZZLE = range(300, 400)
34+
RAIN = range(500, 600)
35+
SNOW = range(600, 700)
36+
ATMOSPHERE = range(700, 800)
37+
CLEAR = range(800, 801)
38+
CLOUDY = range(801, 900)
39+
40+
41+
def read_user_cli_args():
42+
"""Handles the CLI user interactions.
43+
44+
Returns:
45+
argparse.Namespace: Populated namespace object
46+
"""
47+
parser = argparse.ArgumentParser(
48+
description="gets weather and temperature information for a city"
49+
)
50+
parser.add_argument(
51+
"city", nargs="+", type=str, help="enter the city name"
52+
)
53+
parser.add_argument(
54+
"-i",
55+
"--imperial",
56+
action="store_true",
57+
help="display the temperature in imperial units",
58+
)
59+
return parser.parse_args()
60+
61+
62+
def build_weather_query(city_input, imperial=False):
63+
"""Builds the URL for an API request to OpenWeather's Weather API.
64+
65+
Args:
66+
city_input (List[str]): Name of a city as collected by argparse
67+
imperial (bool): Whether or not to use imperial units for temperature
68+
69+
Returns:
70+
str: URL formatted for a call to OpenWeather's city name endpoint
71+
"""
72+
api_key = _get_api_key()
73+
city_name = " ".join(city_input)
74+
url_encoded_city_name = parse.quote_plus(city_name)
75+
units = "imperial" if imperial else "metric"
76+
url = (
77+
f"{BASE_WEATHER_API_URL}?q={url_encoded_city_name}"
78+
f"&units={units}&appid={api_key}"
79+
)
80+
return url
81+
82+
83+
def _get_api_key():
84+
"""Fetch the API key from your configuration file.
85+
86+
Expects a configuration file named "secrets.ini" with structure:
87+
88+
[openweather]
89+
api_key=<YOUR-OPENWEATHER-API-KEY>
90+
"""
91+
config = ConfigParser()
92+
config.read(Path(__file__).parent / "secrets.ini")
93+
return config["openweather"]["api_key"]
94+
95+
96+
def get_weather_data(query_url):
97+
"""Makes an API request to a URL and returns the data as a Python object.
98+
99+
Args:
100+
query_url (str): URL formatted for OpenWeather's city name endpoint
101+
102+
Returns:
103+
dict: Weather information for a specific city
104+
"""
105+
try:
106+
response = request.urlopen(query_url)
107+
except error.HTTPError as http_error:
108+
if http_error.code == 401: # 401 - Unauthorized
109+
sys.exit("Access denied. Check your API key.")
110+
elif http_error.code == 404: # 404 - Not Found
111+
sys.exit("Can't find weather data for this city.")
112+
else:
113+
sys.exit(f"Something went wrong... ({http_error.code})")
114+
data = response.read()
115+
try:
116+
return json.loads(data)
117+
except json.JSONDecodeError:
118+
sys.exit("Couldn't read the server response.")
119+
120+
121+
def display_weather_info(weather_data, imperial=False):
122+
"""Prints formatted weather information about a city.
123+
124+
Args:
125+
weather_data (dict): API response from OpenWeather by city name
126+
imperial (bool): Whether or not to use imperial units for temperature
127+
128+
More information at https://openweathermap.org/current#name
129+
"""
130+
city = weather_data["name"]
131+
weather_id = weather_data["weather"][0]["id"]
132+
weather_description = weather_data["weather"][0]["description"]
133+
temperature = weather_data["main"]["temp"]
134+
change_color(REVERSE)
135+
print(f"{city:^{PADDING}}", end="")
136+
change_color(RESET)
137+
weather_symbol, color = _select_weather_display_params(weather_id)
138+
change_color(color)
139+
print(f"\t{weather_symbol}", end=" ")
140+
print(
141+
f"\t{weather_description.capitalize():^{PADDING}}",
142+
end=" ",
143+
)
144+
change_color(RESET)
145+
print(f"({temperature}°{'F' if imperial else 'C'})")
146+
147+
148+
def _select_weather_display_params(weather_id):
149+
"""Selects a weather symbol and a display color for a weather state.
150+
151+
Args:
152+
weather_id (int): Weather condition code from the OpenWeather API
153+
154+
Returns:
155+
tuple[str]: Contains a weather symbol and a display color
156+
"""
157+
if weather_id in THUNDERSTORM:
158+
display_params = ("💥", RED)
159+
elif weather_id in DRIZZLE:
160+
display_params = ("💧", CYAN)
161+
elif weather_id in RAIN:
162+
display_params = ("💦", BLUE)
163+
elif weather_id in SNOW:
164+
display_params = ("⛄️", WHITE)
165+
elif weather_id in ATMOSPHERE:
166+
display_params = ("🌀", BLUE)
167+
elif weather_id in CLEAR:
168+
display_params = ("🔆", YELLOW)
169+
elif weather_id in CLOUDY:
170+
display_params = ("💨", WHITE)
171+
else: # In case the API adds new weather codes
172+
display_params = ("🌈", RESET)
173+
return display_params
174+
175+
176+
if __name__ == "__main__":
177+
user_args = read_user_cli_args()
178+
query_url = build_weather_query(user_args.city, user_args.imperial)
179+
weather_data = get_weather_data(query_url)
180+
display_weather_info(weather_data, user_args.imperial)

0 commit comments

Comments
 (0)