-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathweather_api_backend.py
More file actions
363 lines (298 loc) · 14.7 KB
/
weather_api_backend.py
File metadata and controls
363 lines (298 loc) · 14.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
from flask import Flask, request, jsonify
from flask_cors import CORS
import requests
from urllib.parse import urlencode
import time
import logging
app = Flask(__name__)
CORS(app)
# Configure logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[logging.StreamHandler()])
# Create a logger object
logger = logging.getLogger(__name__)
# Base URL for the weather API
BASE_URL_API = 'http://apiadvisor.climatempo.com.br/api/v1/forecast/locale/'
CITY_API_BASE_URL = 'http://apiadvisor.climatempo.com.br/api/v1/locale/city'
# Initialize an empty cache dictionary
weatherInfoCache = {}
cityInfoCache = {}
WEATHER_CACHE_EXPIRATION_TIME = 3600 # Cache expiration time for weather information by city in seconds (1 hour)
@app.route('/weather/getInfo', methods=['POST'])
def get_info():
try:
# Read the form data from the frontend request
data = request.form
headers = request.headers
# Retrieve city ID and forecast days from the form data
city_id = data.get("city_id", "")
forecast_days = data.get("forecast_days", "15")
logger.info("Incoming request for weather report for city ID: " + city_id + " for the next " + forecast_days + " days")
# Retrieve the API token from the request headers
token_api = headers.get('token')
if not city_id:
logger.error('Missing required parameter "city_id"')
return jsonify({'error': 'Missing required parameter "city_id"'}), 400
if not token_api:
logger.error('Missing API Access Token (header "token")')
return jsonify({'error': 'Missing API Access Token (header "token")'}), 400
# Check cache first
cached_data = get_cached_weather_data(city_id, forecast_days)
if cached_data:
logger.info("Finished processing weather report for city ID: " + city_id + " for the next " + forecast_days + " days")
return jsonify(cached_data)
# Construct the URL with query parameters
url = f"{BASE_URL_API}{city_id}/days/{forecast_days}?token={token_api}"
# Make the GET request to the API
response = requests.get(url)
response.raise_for_status() # Raise HTTPError for bad responses
# Log response content
logger.info('API Response Status Code: %d', response.status_code)
logger.info('API Response JSON: %s', response.json())
# Store response data in cache
store_weather_data(city_id, forecast_days, response.json())
logger.info("Finished processing weather report for city ID: " + city_id + " for the next " + forecast_days + " days")
return jsonify(response.json())
except requests.exceptions.RequestException as e:
logger.error('Request error: %s', e)
response_json = response.json()
error_detail = response_json.get("detail", "Request error")
logger.error('API Response JSON: %s', response_json)
return jsonify({'error': error_detail, 'details': str(e)}), response.status_code
except Exception as e:
logger.error('Unexpected error: %s', e)
return jsonify({'error': 'Unexpected error', 'details': str(e)}), 500
@app.route('/weather/v2/getInfo', methods=['POST'])
def get_info_v2():
try:
# Read the form data from the frontend request
data = request.form
headers = request.headers
# Retrieve city ID, city name, state, and forecast days from the form data
city_id = data.get("city_id", "")
city_name = data.get("city_name", "")
state = data.get("state", "")
forecast_days = data.get("forecast_days", "15")
# Determine the city identifier for logging
if city_name:
city_identifier = city_name
elif city_id:
city_identifier = f"ID: {city_id}"
else:
city_identifier = "Not provided"
logger.info("Incoming request for weather report for city %s for the next %s days", city_identifier, forecast_days)
# Retrieve the API token from the request headers
token_api = headers.get('token')
if not token_api:
logger.error('Missing API Access Token (header "token")')
return jsonify({'error': 'Missing API Access Token (header "token")'}), 400
if city_id:
# If city ID is provided, use it directly
city_id = city_id
elif city_name and state:
# If city name and state are provided, fetch city ID from the API
city_id = get_city_id_from_api_internal(city_name, state, token_api)
if not city_id:
logger.error('Could not find city ID for city name: %s and state: %s', city_name, state)
return jsonify({'error': 'City not found'}), 404
else:
logger.error('Missing required parameters "city_id" or both "city_name" and "state"')
return jsonify({'error': 'Missing required parameters'}), 400
# Check cache first
cached_data = get_cached_weather_data(city_id, forecast_days)
if cached_data:
logger.info("Finished processing weather report for city ID: " + str(city_id) + " for the next " + forecast_days + " days")
return jsonify(cached_data)
# Construct the URL with query parameters
url = f"{BASE_URL_API}{city_id}/days/{forecast_days}?token={token_api}"
# Make the GET request to the API
response = requests.get(url)
response.raise_for_status() # Raise HTTPError for bad responses
# Log response content
logger.info('API Response Status Code: %d', response.status_code)
logger.info('API Response JSON: %s', response.json())
# Store response data in cache
store_weather_data(city_id, forecast_days, response.json())
logger.info("Finished processing weather report for city ID: " + str(city_id) + " for the next " + forecast_days + " days")
return jsonify(response.json())
except requests.exceptions.RequestException as e:
logger.error('Request error: %s', e)
response_json = response.json()
error_detail = response_json.get("detail", "Request error")
logger.error('API Response JSON: %s', response_json)
return jsonify({'error': error_detail, 'details': str(e)}), response.status_code
except Exception as e:
logger.error('Unexpected error: %s', e, exc_info=True)
return jsonify({'error': 'Unexpected error', 'details': str(e)}), 500
@app.route('/weather/registerCity', methods=['PUT'])
def register_city():
try:
# Read the form data from the frontend request
data = request.form
headers = request.headers
logger.info("Incoming request for city ID token registration")
# Retrieve the API token from the request headers
token_api = headers.get('token')
if not token_api:
logger.error('Missing API Access Token (header "token")')
return jsonify({'error': 'Missing API Access Token (header "token")'}), 400
# Define the URL for registering cities
BASE_URL_REGISTER_CITY = f'http://apiadvisor.climatempo.com.br/api-manager/user-token/{token_api}/locales'
# Retrieve and format the locale IDs from the form data
locale_ids = data.getlist("city_id") # Use getlist to retrieve a list of values
if not locale_ids:
logger.error('No locale IDs provided for registration in API token')
return jsonify({'error': 'No locale IDs provided for registration in API token'}), 400
# Ensure all are integers
locale_ids = [int(id) for id in locale_ids if id.isdigit()]
if not locale_ids:
logger.error('Invalid locale IDs provided')
return jsonify({'error': 'Invalid locale IDs provided'}), 400
# Prepare the data for the PUT request
encoded_data = urlencode({'localeId[]': locale_ids}, doseq=True)
# Headers
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
# Make the PUT request
response = requests.put(BASE_URL_REGISTER_CITY, headers=headers, data=encoded_data)
response.raise_for_status() # Raise HTTPError for bad responses
logger.info("Finished request for city ID token registration")
return jsonify(response.json())
except requests.exceptions.RequestException as e:
logger.error('Request error: %s', e)
response_json = response.json()
error_detail = response_json.get("detail", "Request error")
logger.error('API Response JSON: %s', response_json)
return jsonify({'error': error_detail, 'details': str(e)}), response.status_code
except Exception as e:
logger.error('Unexpected error: %s', e)
response_json = response.json()
error_detail = response_json.get("detail", "Unknown error")
logger.error('API Response JSON: %s', response_json)
return jsonify({'error': 'Unexpected error', 'details': str(e)}), 500
@app.route('/weather/getCityId', methods=['POST'])
def get_city_id():
try:
# Read the form data from the frontend request
data = request.form
headers = request.headers
# Retrieve city name, state, and API token from the form data and headers
city_name = data.get("city_name", "")
state = data.get("state", "")
token_api = headers.get('token')
logger.info("Incoming search for city ID request for city name: " + city_name + " and state: " + state)
if not city_name:
logger.error('Missing required parameter "city_name"')
return jsonify({'error': 'Missing required parameter "city_name"'}), 400
#if not state:
# logger.error('Missing required parameter "state"')
# return jsonify({'error': 'Missing required parameter "state"'}), 400
if not token_api:
logger.error('Missing API Access Token (header "token")')
return jsonify({'error': 'Missing API Access Token (header "token")'}), 400
# Check cache first
cached_data = get_cached_city_data(city_name, state)
if cached_data:
logger.info("Finished search for city ID for city name: " + city_name + " and state: " + state)
return jsonify(cached_data)
# Construct the URL with query parameters
params = {
'name': city_name,
'state': state,
'token': token_api
}
url = f"{CITY_API_BASE_URL}?{urlencode(params)}"
# Make the GET request to the API
response = requests.get(url)
response.raise_for_status() # Raise HTTPError for bad responses
# Log response content for debugging
logger.info('API Response Status Code: %d', response.status_code)
logger.info('API Response JSON: %s', response.json())
store_city_data(city_name, state, response.json())
logger.info("Finished search for city ID for city name: " + city_name + " and state: " + state)
return jsonify(response.json())
except requests.exceptions.RequestException as e:
logger.error('Request error: %s', e)
response_json = response.json()
error_detail = response_json.get("detail", "Request error")
logger.error('API Response JSON: %s', response_json)
return jsonify({'error': error_detail, 'details': str(e)}), response.status_code
except Exception as e:
logger.error('Unexpected error: %s', e)
response_json = response.json()
error_detail = response_json.get("detail", "Unknown error")
logger.error('API Response JSON: %s', response_json)
return jsonify({'error': 'Unexpected error', 'details': str(e)}), 500
@app.route('/')
def home():
return "Welcome to the Flask App!"
def store_weather_data(city_id, forecast_days, weather_data):
"""Store weather data in the cache."""
if not weather_data:
logger.info('No data to cache for the period %s days for City ID: %s', forecast_days, city_id)
return
cache_key = (city_id, forecast_days)
weatherInfoCache[cache_key] = {
'data': weather_data,
'timestamp': time.time()
}
logger.info('Cached data for the period %s days for City ID: %s', forecast_days, city_id)
def get_cached_weather_data(city_id, forecast_days):
"""Retrieve weather data from the cache if it's still valid."""
cache_key = (city_id, forecast_days)
if cache_key in weatherInfoCache:
cached_data = weatherInfoCache[cache_key]
if time.time() - cached_data['timestamp'] < WEATHER_CACHE_EXPIRATION_TIME:
logger.info('Cache hit for the period %s days for City ID: %s', forecast_days, city_id)
return cached_data['data']
else:
del weatherInfoCache[cache_key] # Remove expired cache
logger.info('Cache miss for the period %s days for City ID: %s', forecast_days, city_id)
return None
def store_city_data(city_name, state, city_data):
"""Store city data in the cache."""
if not city_data:
logger.info('No data to cache for for City name: %s', city_name)
return
cache_key = (city_name, state)
cityInfoCache[cache_key] = {
'data': city_data,
'timestamp': time.time()
}
logger.info('Cached data for the city: %s', city_name)
def get_cached_city_data(city_name, state):
"""Retrieve city data from the cache if it's still valid."""
cache_key = (city_name, state)
if cache_key in cityInfoCache:
cached_data = cityInfoCache[cache_key]
logger.info('Cache hit for City: %s', city_name)
return cached_data['data']
logger.info('Cache miss for City: %s', city_name)
return None
def get_city_id_from_api_internal(city_name, state, token_api):
"""Retrieve the city ID based on the city name and state from the API."""
params = {
'name': city_name,
'state': state,
'token': token_api
}
url = f"{CITY_API_BASE_URL}?{urlencode(params)}"
try:
logger.info('Getting city ID for weather report request: %s', city_name)
response = requests.get(url)
response.raise_for_status()
city_data = response.json()
if city_data:
# Assume the first result is the correct city
return city_data[0].get("id")
else:
return None
except requests.exceptions.RequestException as e:
logger.error('Failed to fetch city ID from API: %s', e)
return None
# Remember to configure the host and port if going to open the API to the internet
#if __name__ == '__main__':
#app.run(port=8000, debug=False)
#app.run(debug=False)