Add python weather api content (#3)
This commit is contained in:
parent
0a3248c22f
commit
86884c1600
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
|||
.vscode
|
||||
node_modules
|
||||
node_modules
|
||||
__pycache__
|
||||
*.db
|
6
makefile
Normal file
6
makefile
Normal file
|
@ -0,0 +1,6 @@
|
|||
.PHONY: clean
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
find . -type d -name "__pycache__" -exec rm -rf {} +
|
||||
@echo "Cleanup complete."
|
16
python-weather-api/README.md
Normal file
16
python-weather-api/README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Behavior Testing With Fake Flask Weather API
|
||||
|
||||
This sample code is relate to the following blog post:
|
||||
|
||||
- https://thecodebranch.com/posts/python/testing/behavioral-test
|
||||
|
||||
In this code sample, we look at a simple weather API programmed in Python, using Flask. We provided
|
||||
the code to generate the fake data and scripts to load a sqlite database.
|
||||
|
||||
The demo is used to show some strategies to assert the behavior of the API aserting the expected
|
||||
response codes that are expected to occur.
|
||||
|
||||
|
||||
## Screenshot
|
||||
|
||||

|
5
python-weather-api/api/error.py
Normal file
5
python-weather-api/api/error.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
class Error(Exception):
|
||||
def __init__(self, message, status_code=500):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.status_code = status_code
|
48
python-weather-api/api/services.py
Normal file
48
python-weather-api/api/services.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from datetime import datetime
|
||||
from api.error import Error
|
||||
from model.historic import HistoricWeather
|
||||
from model.current import CurrentWeather
|
||||
from model.forecast import ForecastWeather
|
||||
|
||||
|
||||
class BaseService:
|
||||
def __init__(self, database=None):
|
||||
self.database = database
|
||||
|
||||
|
||||
class HistoricWeatherService(BaseService):
|
||||
DATE_FORMAT = "%Y-%m-%d"
|
||||
|
||||
|
||||
def get_historic_weather(self, location, date=None):
|
||||
query = self.database.session.query(HistoricWeather).filter_by(city=location)
|
||||
|
||||
if date:
|
||||
try:
|
||||
parsed_date = datetime.strptime(date, self.DATE_FORMAT)
|
||||
query = query.filter_by(date=parsed_date)
|
||||
except ValueError:
|
||||
raise Error("Date must be YYYY-MM-DD format", 400)
|
||||
|
||||
results = query.all()
|
||||
if not results:
|
||||
raise Error("Resource Not Found", 404)
|
||||
return [result.to_dict() for result in results]
|
||||
|
||||
|
||||
class CurrentWeatherService(BaseService):
|
||||
def get_current_weather(self, location):
|
||||
query = self.database.session.query(CurrentWeather).filter_by(city=location)
|
||||
results = query.all()
|
||||
if not results:
|
||||
raise Error("Resource Not Found", 404)
|
||||
return [result.to_dict() for result in results]
|
||||
|
||||
|
||||
class ForecastWeatherService(BaseService):
|
||||
def get_forecast_weather(self, location):
|
||||
query = self.database.session.query(ForecastWeather).filter_by(city=location)
|
||||
results = query.all()
|
||||
if not results:
|
||||
raise Error("Resource Not Found", 404)
|
||||
return [result.to_dict() for result in results]
|
53
python-weather-api/api/views.py
Normal file
53
python-weather-api/api/views.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from flask import jsonify
|
||||
from flask import request
|
||||
from flask.views import MethodView
|
||||
from functools import wraps
|
||||
from api.error import Error
|
||||
|
||||
|
||||
ALLOWED_API_KEY = "TEST_KEY"
|
||||
|
||||
|
||||
def check_api_key(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
api_key = request.args.get('api_key')
|
||||
if api_key != ALLOWED_API_KEY:
|
||||
return jsonify({"error": "Unauthorized Request"}), 401
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
|
||||
class BaseView(MethodView):
|
||||
def __init__(self, service=None):
|
||||
self.service=service
|
||||
|
||||
|
||||
class HistoricWeatherView(BaseView):
|
||||
@check_api_key
|
||||
def get(self, location, date=None):
|
||||
try:
|
||||
weather_data = self.service.get_historic_weather(location, date)
|
||||
return jsonify({"data": weather_data}), 200
|
||||
except Error as e:
|
||||
return jsonify({"error": e.message}), e.status_code
|
||||
|
||||
|
||||
class CurrentWeatherView(BaseView):
|
||||
@check_api_key
|
||||
def get(self, location):
|
||||
try:
|
||||
current_data = self.service.get_current_weather(location)
|
||||
return jsonify({"data": current_data}), 200
|
||||
except Error as e:
|
||||
return jsonify({"error": e.message}), e.status_code
|
||||
|
||||
|
||||
class ForecastWeatherView(BaseView):
|
||||
@check_api_key
|
||||
def get(self, location):
|
||||
try:
|
||||
forecast_data = self.service.get_forecast_weather(location)
|
||||
return jsonify({"data": forecast_data}), 200
|
||||
except Error as e:
|
||||
return jsonify({"error": e.message}), e.status_code
|
53
python-weather-api/app.py
Normal file
53
python-weather-api/app.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from flask import Flask
|
||||
from database import Database
|
||||
|
||||
|
||||
DATABASE_URL = "sqlite:///weather.db"
|
||||
|
||||
|
||||
def create_app(config_object={}):
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(config_object)
|
||||
|
||||
app.json.sort_keys = False
|
||||
database = Database(DATABASE_URL)
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
database.session.close()
|
||||
return response
|
||||
|
||||
configure_blueprints(app, database)
|
||||
return app
|
||||
|
||||
|
||||
def configure_blueprints(app, database):
|
||||
from api.views import HistoricWeatherView
|
||||
from api.views import CurrentWeatherView
|
||||
from api.views import ForecastWeatherView
|
||||
from api.services import HistoricWeatherService
|
||||
from api.services import CurrentWeatherService
|
||||
from api.services import ForecastWeatherService
|
||||
|
||||
|
||||
historic_service = HistoricWeatherService(database=database)
|
||||
current_service = CurrentWeatherService(database=database)
|
||||
forecast_service = ForecastWeatherService(database=database)
|
||||
|
||||
|
||||
historic_view = HistoricWeatherView.as_view("historic", service=historic_service)
|
||||
app.add_url_rule('/api/historical/<string:location>', view_func=historic_view, methods=["GET"])
|
||||
app.add_url_rule('/api/historical/<string:location>/<string:date>', view_func=historic_view, methods=["GET"])
|
||||
|
||||
|
||||
current_view = CurrentWeatherView.as_view("current", service=current_service)
|
||||
app.add_url_rule('/api/current/<string:location>', view_func=current_view, methods=["GET"])
|
||||
|
||||
|
||||
forecast_view = ForecastWeatherView.as_view("forecast", service=forecast_service)
|
||||
app.add_url_rule('/api/forecast/<string:location>', view_func=forecast_view, methods=["GET"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = create_app({})
|
||||
app.run(host="127.0.0.1", port=5000, debug=True)
|
16
python-weather-api/data/current_data.csv
Normal file
16
python-weather-api/data/current_data.csv
Normal file
|
@ -0,0 +1,16 @@
|
|||
city,condition,temperature,humidity,wind_speed
|
||||
New York,sunny,30,31,13
|
||||
Los Angeles,partly cloudy,17,63,0
|
||||
Chicago,sunny,16,15,9
|
||||
Toronto,cloudy,16,90,28
|
||||
Mexico City,sunny,20,32,13
|
||||
Vancouver,fog,5,96,2
|
||||
Miami,sunny,35,32,5
|
||||
Montreal,cloudy,12,61,17
|
||||
San Francisco,sunny,21,45,8
|
||||
Las Vegas,partly cloudy,22,67,10
|
||||
Houston,partly cloudy,12,42,13
|
||||
Atlanta,thunder showers,18,81,24
|
||||
Seattle,fog,4,100,1
|
||||
Boston,snow,-19,89,5
|
||||
Calgary,partly cloudy,25,64,8
|
|
211
python-weather-api/data/forecast_data.csv
Normal file
211
python-weather-api/data/forecast_data.csv
Normal file
|
@ -0,0 +1,211 @@
|
|||
city,date,day,condition,temperature,humidity
|
||||
New York,2024-06-26,Wednesday,partly cloudy,17,63
|
||||
New York,2024-06-27,Thursday,rain,16,100
|
||||
New York,2024-06-28,Friday,partly cloudy,19,74
|
||||
New York,2024-06-29,Saturday,thunder showers,23,87
|
||||
New York,2024-06-30,Sunday,cloudy,15,81
|
||||
New York,2024-07-01,Monday,partly cloudy,23,71
|
||||
New York,2024-07-02,Tuesday,cloudy,19,50
|
||||
New York,2024-07-03,Wednesday,rain,8,100
|
||||
New York,2024-07-04,Thursday,partly cloudy,20,68
|
||||
New York,2024-07-05,Friday,thunder showers,23,87
|
||||
New York,2024-07-06,Saturday,snow,-20,80
|
||||
New York,2024-07-07,Sunday,partly cloudy,12,67
|
||||
New York,2024-07-08,Monday,cloudy,19,62
|
||||
New York,2024-07-09,Tuesday,partly cloudy,18,56
|
||||
Los Angeles,2024-06-26,Wednesday,fog,9,81
|
||||
Los Angeles,2024-06-27,Thursday,sunny,30,20
|
||||
Los Angeles,2024-06-28,Friday,fog,15,97
|
||||
Los Angeles,2024-06-29,Saturday,sunny,22,39
|
||||
Los Angeles,2024-06-30,Sunday,partly cloudy,12,67
|
||||
Los Angeles,2024-07-01,Monday,sunny,30,21
|
||||
Los Angeles,2024-07-02,Tuesday,sunny,17,46
|
||||
Los Angeles,2024-07-03,Wednesday,partly cloudy,28,72
|
||||
Los Angeles,2024-07-04,Thursday,fog,2,81
|
||||
Los Angeles,2024-07-05,Friday,sunny,17,41
|
||||
Los Angeles,2024-07-06,Saturday,fog,15,99
|
||||
Los Angeles,2024-07-07,Sunday,sunny,21,45
|
||||
Los Angeles,2024-07-08,Monday,partly cloudy,30,68
|
||||
Los Angeles,2024-07-09,Tuesday,partly cloudy,18,50
|
||||
Chicago,2024-06-26,Wednesday,rain,10,78
|
||||
Chicago,2024-06-27,Thursday,partly cloudy,15,43
|
||||
Chicago,2024-06-28,Friday,sunny,20,15
|
||||
Chicago,2024-06-29,Saturday,partly cloudy,27,44
|
||||
Chicago,2024-06-30,Sunday,thunder showers,28,85
|
||||
Chicago,2024-07-01,Monday,thunder showers,26,89
|
||||
Chicago,2024-07-02,Tuesday,sunny,17,10
|
||||
Chicago,2024-07-03,Wednesday,partly cloudy,19,71
|
||||
Chicago,2024-07-04,Thursday,snow,-17,90
|
||||
Chicago,2024-07-05,Friday,thunder showers,21,97
|
||||
Chicago,2024-07-06,Saturday,rain,7,99
|
||||
Chicago,2024-07-07,Sunday,sunny,17,36
|
||||
Chicago,2024-07-08,Monday,sunny,34,23
|
||||
Chicago,2024-07-09,Tuesday,cloudy,15,88
|
||||
Toronto,2024-06-26,Wednesday,sunny,28,16
|
||||
Toronto,2024-06-27,Thursday,thunder showers,18,77
|
||||
Toronto,2024-06-28,Friday,partly cloudy,28,54
|
||||
Toronto,2024-06-29,Saturday,sunny,40,48
|
||||
Toronto,2024-06-30,Sunday,cloudy,14,59
|
||||
Toronto,2024-07-01,Monday,freezing rain,2,95
|
||||
Toronto,2024-07-02,Tuesday,freezing rain,3,93
|
||||
Toronto,2024-07-03,Wednesday,snow,-13,85
|
||||
Toronto,2024-07-04,Thursday,freezing rain,-2,88
|
||||
Toronto,2024-07-05,Friday,thunder showers,19,87
|
||||
Toronto,2024-07-06,Saturday,partly cloudy,18,72
|
||||
Toronto,2024-07-07,Sunday,thunder showers,19,88
|
||||
Toronto,2024-07-08,Monday,rain,5,83
|
||||
Toronto,2024-07-09,Tuesday,partly cloudy,21,45
|
||||
Mexico City,2024-06-26,Wednesday,rain,9,94
|
||||
Mexico City,2024-06-27,Thursday,thunder showers,25,81
|
||||
Mexico City,2024-06-28,Friday,partly cloudy,15,44
|
||||
Mexico City,2024-06-29,Saturday,cloudy,12,63
|
||||
Mexico City,2024-06-30,Sunday,cloudy,23,83
|
||||
Mexico City,2024-07-01,Monday,sunny,39,13
|
||||
Mexico City,2024-07-02,Tuesday,thunder showers,28,93
|
||||
Mexico City,2024-07-03,Wednesday,cloudy,20,77
|
||||
Mexico City,2024-07-04,Thursday,sunny,19,42
|
||||
Mexico City,2024-07-05,Friday,rain,5,80
|
||||
Mexico City,2024-07-06,Saturday,thunder showers,20,93
|
||||
Mexico City,2024-07-07,Sunday,rain,15,80
|
||||
Mexico City,2024-07-08,Monday,rain,19,91
|
||||
Mexico City,2024-07-09,Tuesday,rain,9,78
|
||||
Vancouver,2024-06-26,Wednesday,partly cloudy,22,68
|
||||
Vancouver,2024-06-27,Thursday,fog,9,82
|
||||
Vancouver,2024-06-28,Friday,rain,17,77
|
||||
Vancouver,2024-06-29,Saturday,partly cloudy,14,49
|
||||
Vancouver,2024-06-30,Sunday,partly cloudy,23,65
|
||||
Vancouver,2024-07-01,Monday,cloudy,16,83
|
||||
Vancouver,2024-07-02,Tuesday,fog,14,94
|
||||
Vancouver,2024-07-03,Wednesday,rain,17,76
|
||||
Vancouver,2024-07-04,Thursday,fog,4,84
|
||||
Vancouver,2024-07-05,Friday,fog,9,88
|
||||
Vancouver,2024-07-06,Saturday,fog,10,82
|
||||
Vancouver,2024-07-07,Sunday,partly cloudy,18,44
|
||||
Vancouver,2024-07-08,Monday,partly cloudy,21,74
|
||||
Vancouver,2024-07-09,Tuesday,rain,16,93
|
||||
Miami,2024-06-26,Wednesday,rain,19,92
|
||||
Miami,2024-06-27,Thursday,sunny,22,50
|
||||
Miami,2024-06-28,Friday,thunder showers,21,96
|
||||
Miami,2024-06-29,Saturday,rain,17,80
|
||||
Miami,2024-06-30,Sunday,rain,14,71
|
||||
Miami,2024-07-01,Monday,thunder showers,25,75
|
||||
Miami,2024-07-02,Tuesday,sunny,37,27
|
||||
Miami,2024-07-03,Wednesday,partly cloudy,26,57
|
||||
Miami,2024-07-04,Thursday,rain,5,93
|
||||
Miami,2024-07-05,Friday,thunder showers,19,99
|
||||
Miami,2024-07-06,Saturday,sunny,22,34
|
||||
Miami,2024-07-07,Sunday,thunder showers,22,100
|
||||
Miami,2024-07-08,Monday,rain,18,75
|
||||
Miami,2024-07-09,Tuesday,rain,9,70
|
||||
Montreal,2024-06-26,Wednesday,rain,12,70
|
||||
Montreal,2024-06-27,Thursday,partly cloudy,16,68
|
||||
Montreal,2024-06-28,Friday,thunder showers,26,80
|
||||
Montreal,2024-06-29,Saturday,sunny,20,28
|
||||
Montreal,2024-06-30,Sunday,freezing rain,1,83
|
||||
Montreal,2024-07-01,Monday,thunder showers,20,96
|
||||
Montreal,2024-07-02,Tuesday,sunny,30,44
|
||||
Montreal,2024-07-03,Wednesday,freezing rain,-5,86
|
||||
Montreal,2024-07-04,Thursday,snow,-11,97
|
||||
Montreal,2024-07-05,Friday,rain,7,99
|
||||
Montreal,2024-07-06,Saturday,partly cloudy,25,52
|
||||
Montreal,2024-07-07,Sunday,partly cloudy,23,66
|
||||
Montreal,2024-07-08,Monday,rain,8,83
|
||||
Montreal,2024-07-09,Tuesday,rain,9,88
|
||||
San Francisco,2024-06-26,Wednesday,fog,15,81
|
||||
San Francisco,2024-06-27,Thursday,cloudy,19,51
|
||||
San Francisco,2024-06-28,Friday,sunny,21,31
|
||||
San Francisco,2024-06-29,Saturday,sunny,17,14
|
||||
San Francisco,2024-06-30,Sunday,sunny,21,34
|
||||
San Francisco,2024-07-01,Monday,partly cloudy,22,49
|
||||
San Francisco,2024-07-02,Tuesday,partly cloudy,20,40
|
||||
San Francisco,2024-07-03,Wednesday,sunny,38,38
|
||||
San Francisco,2024-07-04,Thursday,partly cloudy,19,71
|
||||
San Francisco,2024-07-05,Friday,sunny,23,33
|
||||
San Francisco,2024-07-06,Saturday,fog,8,81
|
||||
San Francisco,2024-07-07,Sunday,partly cloudy,24,64
|
||||
San Francisco,2024-07-08,Monday,sunny,30,28
|
||||
San Francisco,2024-07-09,Tuesday,cloudy,21,86
|
||||
Las Vegas,2024-06-26,Wednesday,partly cloudy,30,74
|
||||
Las Vegas,2024-06-27,Thursday,sunny,17,18
|
||||
Las Vegas,2024-06-28,Friday,sunny,30,33
|
||||
Las Vegas,2024-06-29,Saturday,thunder showers,25,93
|
||||
Las Vegas,2024-06-30,Sunday,partly cloudy,16,55
|
||||
Las Vegas,2024-07-01,Monday,sunny,19,36
|
||||
Las Vegas,2024-07-02,Tuesday,thunder showers,19,87
|
||||
Las Vegas,2024-07-03,Wednesday,thunder showers,18,93
|
||||
Las Vegas,2024-07-04,Thursday,partly cloudy,11,46
|
||||
Las Vegas,2024-07-05,Friday,sunny,28,31
|
||||
Las Vegas,2024-07-06,Saturday,partly cloudy,23,55
|
||||
Las Vegas,2024-07-07,Sunday,sunny,22,25
|
||||
Las Vegas,2024-07-08,Monday,thunder showers,24,81
|
||||
Las Vegas,2024-07-09,Tuesday,sunny,39,45
|
||||
Houston,2024-06-26,Wednesday,sunny,21,21
|
||||
Houston,2024-06-27,Thursday,sunny,36,13
|
||||
Houston,2024-06-28,Friday,thunder showers,26,80
|
||||
Houston,2024-06-29,Saturday,sunny,30,11
|
||||
Houston,2024-06-30,Sunday,thunder showers,18,88
|
||||
Houston,2024-07-01,Monday,sunny,19,10
|
||||
Houston,2024-07-02,Tuesday,partly cloudy,28,40
|
||||
Houston,2024-07-03,Wednesday,sunny,23,45
|
||||
Houston,2024-07-04,Thursday,partly cloudy,12,50
|
||||
Houston,2024-07-05,Friday,partly cloudy,10,66
|
||||
Houston,2024-07-06,Saturday,rain,15,77
|
||||
Houston,2024-07-07,Sunday,sunny,21,36
|
||||
Houston,2024-07-08,Monday,partly cloudy,22,73
|
||||
Houston,2024-07-09,Tuesday,sunny,23,43
|
||||
Atlanta,2024-06-26,Wednesday,cloudy,17,56
|
||||
Atlanta,2024-06-27,Thursday,partly cloudy,25,52
|
||||
Atlanta,2024-06-28,Friday,partly cloudy,12,70
|
||||
Atlanta,2024-06-29,Saturday,rain,14,75
|
||||
Atlanta,2024-06-30,Sunday,cloudy,20,52
|
||||
Atlanta,2024-07-01,Monday,thunder showers,25,93
|
||||
Atlanta,2024-07-02,Tuesday,partly cloudy,24,46
|
||||
Atlanta,2024-07-03,Wednesday,thunder showers,24,81
|
||||
Atlanta,2024-07-04,Thursday,partly cloudy,10,74
|
||||
Atlanta,2024-07-05,Friday,rain,18,82
|
||||
Atlanta,2024-07-06,Saturday,rain,14,72
|
||||
Atlanta,2024-07-07,Sunday,rain,15,75
|
||||
Atlanta,2024-07-08,Monday,cloudy,19,72
|
||||
Atlanta,2024-07-09,Tuesday,rain,16,73
|
||||
Seattle,2024-06-26,Wednesday,fog,11,83
|
||||
Seattle,2024-06-27,Thursday,fog,12,94
|
||||
Seattle,2024-06-28,Friday,rain,13,84
|
||||
Seattle,2024-06-29,Saturday,cloudy,18,66
|
||||
Seattle,2024-06-30,Sunday,rain,10,89
|
||||
Seattle,2024-07-01,Monday,fog,11,85
|
||||
Seattle,2024-07-02,Tuesday,cloudy,15,57
|
||||
Seattle,2024-07-03,Wednesday,rain,13,76
|
||||
Seattle,2024-07-04,Thursday,fog,14,95
|
||||
Seattle,2024-07-05,Friday,partly cloudy,19,40
|
||||
Seattle,2024-07-06,Saturday,partly cloudy,15,53
|
||||
Seattle,2024-07-07,Sunday,rain,13,72
|
||||
Seattle,2024-07-08,Monday,partly cloudy,28,50
|
||||
Seattle,2024-07-09,Tuesday,partly cloudy,29,67
|
||||
Boston,2024-06-26,Wednesday,cloudy,22,84
|
||||
Boston,2024-06-27,Thursday,thunder showers,25,94
|
||||
Boston,2024-06-28,Friday,snow,-8,91
|
||||
Boston,2024-06-29,Saturday,sunny,24,47
|
||||
Boston,2024-06-30,Sunday,thunder showers,18,80
|
||||
Boston,2024-07-01,Monday,sunny,18,41
|
||||
Boston,2024-07-02,Tuesday,snow,-4,83
|
||||
Boston,2024-07-03,Wednesday,thunder showers,18,78
|
||||
Boston,2024-07-04,Thursday,sunny,18,32
|
||||
Boston,2024-07-05,Friday,thunder showers,26,85
|
||||
Boston,2024-07-06,Saturday,sunny,26,11
|
||||
Boston,2024-07-07,Sunday,partly cloudy,12,70
|
||||
Boston,2024-07-08,Monday,thunder showers,24,99
|
||||
Boston,2024-07-09,Tuesday,rain,9,97
|
||||
Calgary,2024-06-26,Wednesday,partly cloudy,25,54
|
||||
Calgary,2024-06-27,Thursday,thunder showers,24,76
|
||||
Calgary,2024-06-28,Friday,snow,-27,97
|
||||
Calgary,2024-06-29,Saturday,snow,-30,83
|
||||
Calgary,2024-06-30,Sunday,cloudy,17,78
|
||||
Calgary,2024-07-01,Monday,freezing rain,-1,86
|
||||
Calgary,2024-07-02,Tuesday,thunder showers,23,93
|
||||
Calgary,2024-07-03,Wednesday,sunny,33,14
|
||||
Calgary,2024-07-04,Thursday,sunny,17,28
|
||||
Calgary,2024-07-05,Friday,thunder showers,20,100
|
||||
Calgary,2024-07-06,Saturday,thunder showers,19,90
|
||||
Calgary,2024-07-07,Sunday,thunder showers,24,91
|
||||
Calgary,2024-07-08,Monday,cloudy,24,78
|
||||
Calgary,2024-07-09,Tuesday,freezing rain,-5,84
|
|
30001
python-weather-api/data/historic_data.csv
Normal file
30001
python-weather-api/data/historic_data.csv
Normal file
File diff suppressed because it is too large
Load Diff
17
python-weather-api/database/__init__.py
Normal file
17
python-weather-api/database/__init__.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self, db_url):
|
||||
self.engine = create_engine(db_url)
|
||||
self.Session = sessionmaker(bind=self.engine)
|
||||
self.session = self.Session()
|
||||
|
||||
|
||||
def create_all(self, base_model):
|
||||
base_model.metadata.create_all(self.engine)
|
||||
|
||||
|
||||
def drop_all(self, base_model):
|
||||
base_model.metadata.drop_all(self.engine)
|
BIN
python-weather-api/images/python-weather-api.png
Normal file
BIN
python-weather-api/images/python-weather-api.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
59
python-weather-api/load_database.py
Normal file
59
python-weather-api/load_database.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
import csv
|
||||
from datetime import datetime
|
||||
from database import Database
|
||||
from model.base import Base
|
||||
from model.current import CurrentWeather
|
||||
from model.forecast import ForecastWeather
|
||||
from model.historic import HistoricWeather
|
||||
|
||||
|
||||
DATA_PATH = "../data"
|
||||
DATABASE_URL = "sqlite:///weather.db"
|
||||
database = Database(DATABASE_URL)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
database.drop_all(Base)
|
||||
database.create_all(Base)
|
||||
|
||||
with open("./data/historic_data.csv", "r") as file:
|
||||
reader = csv.DictReader(file)
|
||||
for row in reader:
|
||||
entry = HistoricWeather(
|
||||
city=row["city"].lower(),
|
||||
date=datetime.strptime(row["date"], "%Y-%m-%d"),
|
||||
condition=row["condition"].lower(),
|
||||
humidity=float(row["humidity"]),
|
||||
temperature=float(row["temperature"])
|
||||
)
|
||||
database.session.add(entry)
|
||||
database.session.commit()
|
||||
|
||||
|
||||
with open("./data/current_data.csv", "r") as file:
|
||||
reader = csv.DictReader(file)
|
||||
for row in reader:
|
||||
entry = CurrentWeather(
|
||||
city=row["city"].lower(),
|
||||
condition=row["condition"].lower(),
|
||||
humidity=float(row["humidity"]),
|
||||
temperature=float(row["temperature"]),
|
||||
wind_speed=float(row["wind_speed"])
|
||||
)
|
||||
database.session.add(entry)
|
||||
database.session.commit()
|
||||
|
||||
|
||||
with open("./data/forecast_data.csv", "r") as file:
|
||||
reader = csv.DictReader(file)
|
||||
for row in reader:
|
||||
entry = ForecastWeather(
|
||||
city=row["city"].lower(),
|
||||
date=datetime.strptime(row["date"], "%Y-%m-%d"),
|
||||
day=row["day"].lower(),
|
||||
condition=row["condition"].lower(),
|
||||
humidity=float(row["humidity"]),
|
||||
temperature=float(row["temperature"]),
|
||||
)
|
||||
database.session.add(entry)
|
||||
database.session.commit()
|
6
python-weather-api/makefile
Normal file
6
python-weather-api/makefile
Normal file
|
@ -0,0 +1,6 @@
|
|||
.PHONY: clean
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
find . -type d -name "__pycache__" -exec rm -rf {} +
|
||||
@echo "Cleanup complete."
|
3
python-weather-api/model/base.py
Normal file
3
python-weather-api/model/base.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
Base = declarative_base()
|
27
python-weather-api/model/current.py
Normal file
27
python-weather-api/model/current.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from sqlalchemy import Column
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import String
|
||||
from .base import Base
|
||||
|
||||
|
||||
class CurrentWeather(Base):
|
||||
__tablename__ = 'current_weather'
|
||||
DATE_FORMAT = "%Y-%m-%d"
|
||||
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
city = Column(String(100))
|
||||
temperature = Column(Integer)
|
||||
condition = Column(String(25))
|
||||
humidity = Column(Integer)
|
||||
wind_speed = (Column(Integer))
|
||||
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"city": self.city,
|
||||
"temperature": self.temperature,
|
||||
"condition": self.condition,
|
||||
"humidity": self.humidity,
|
||||
"wind_speed": self.wind_speed,
|
||||
}
|
32
python-weather-api/model/forecast.py
Normal file
32
python-weather-api/model/forecast.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from datetime import datetime
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import DateTime
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import String
|
||||
from .base import Base
|
||||
|
||||
|
||||
class ForecastWeather(Base):
|
||||
__tablename__ = 'forecast_weather'
|
||||
DATE_FORMAT = "%Y-%m-%d"
|
||||
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
city = Column(String(100))
|
||||
temperature = Column(Integer)
|
||||
condition = Column(String(25))
|
||||
humidity = Column(Integer)
|
||||
date = Column(DateTime)
|
||||
day = Column(String(10))
|
||||
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"city": self.city,
|
||||
"temperature": self.temperature,
|
||||
"condition": self.condition,
|
||||
"humidity": self.humidity,
|
||||
"date": datetime.strftime(self.date, self.DATE_FORMAT),
|
||||
"day": self.day
|
||||
}
|
||||
|
29
python-weather-api/model/historic.py
Normal file
29
python-weather-api/model/historic.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from datetime import datetime
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import DateTime
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import String
|
||||
from .base import Base
|
||||
|
||||
|
||||
class HistoricWeather(Base):
|
||||
__tablename__ = 'historic_weather'
|
||||
DATE_FORMAT = "%Y-%m-%d"
|
||||
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
city = Column(String(100))
|
||||
temperature = Column(Integer)
|
||||
condition = Column(String(25))
|
||||
humidity = Column(Integer)
|
||||
date = Column(DateTime)
|
||||
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"city": self.city,
|
||||
"temperature": self.temperature,
|
||||
"condition": self.condition,
|
||||
"humidity": self.humidity,
|
||||
"date": datetime.strftime(self.date, self.DATE_FORMAT)
|
||||
}
|
175
python-weather-api/script/generate_data.py
Normal file
175
python-weather-api/script/generate_data.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
import csv
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
CITIES = [
|
||||
"New York",
|
||||
"Los Angeles",
|
||||
"Chicago",
|
||||
"Toronto",
|
||||
"Mexico City",
|
||||
"Vancouver",
|
||||
"Miami",
|
||||
"Montreal",
|
||||
"San Francisco",
|
||||
"Las Vegas",
|
||||
"Houston",
|
||||
"Atlanta",
|
||||
"Seattle",
|
||||
"Boston",
|
||||
"Calgary"
|
||||
]
|
||||
|
||||
|
||||
CITY_WEATHER_CONDITIONS = {
|
||||
"New York": ["sunny", "rain", "snow", "partly cloudy", "cloudy", "thunder showers"],
|
||||
"Los Angeles": ["sunny", "partly cloudy", "fog"],
|
||||
"Chicago": ["sunny", "rain", "snow", "cloudy", "partly cloudy", "thunder showers", "freezing rain"],
|
||||
"Toronto": ["sunny", "rain", "snow", "cloudy", "partly cloudy", "thunder showers", "freezing rain"],
|
||||
"Mexico City": ["sunny", "rain", "partly cloudy", "cloudy", "thunder showers"],
|
||||
"Vancouver": ["rain", "cloudy", "fog", "partly cloudy"],
|
||||
"Miami": ["sunny", "rain", "thunder showers", "partly cloudy"],
|
||||
"Montreal": ["sunny", "rain", "snow", "cloudy", "partly cloudy", "thunder showers", "freezing rain"],
|
||||
"San Francisco": ["sunny", "fog", "partly cloudy", "cloudy"],
|
||||
"Las Vegas": ["sunny", "partly cloudy", "thunder showers"],
|
||||
"Houston": ["sunny", "rain", "partly cloudy", "thunder showers"],
|
||||
"Atlanta": ["sunny", "rain", "partly cloudy", "cloudy", "thunder showers"],
|
||||
"Seattle": ["rain", "cloudy", "fog", "partly cloudy"],
|
||||
"Boston": ["sunny", "rain", "snow", "cloudy", "partly cloudy", "thunder showers"],
|
||||
"Calgary": ["sunny", "snow", "partly cloudy", "cloudy", "thunder showers", "freezing rain"]
|
||||
}
|
||||
|
||||
|
||||
WEATHER_CONDITIONS = {
|
||||
"sunny": {"temp_range": (15, 40), "humidity_range": (10, 50), "wind_speed_range": (0, 20)},
|
||||
"cloudy": {"temp_range": (10, 25), "humidity_range": (50, 90), "wind_speed_range": (10, 30)},
|
||||
"rain": {"temp_range": (5, 20), "humidity_range": (70, 100), "wind_speed_range": (10, 25)},
|
||||
"fog": {"temp_range": (0, 15), "humidity_range": (80, 100), "wind_speed_range": (0, 10)},
|
||||
"partly cloudy": {"temp_range": (10, 30), "humidity_range": (40, 75), "wind_speed_range": (0, 20)},
|
||||
"thunder showers": {"temp_range": (18, 28), "humidity_range": (75, 100), "wind_speed_range": (15, 30)},
|
||||
"snow": {"temp_range": (-30, 0), "humidity_range": (80, 100), "wind_speed_range": (5, 15)},
|
||||
"freezing rain": {"temp_range": (-5, 3), "humidity_range": (80, 100), "wind_speed_range": (0, 10)}
|
||||
}
|
||||
|
||||
|
||||
def generate_historic_data(start_date="2018-01-01", num_days=2000):
|
||||
weather_data = {}
|
||||
current_date = datetime.strptime(start_date, "%Y-%m-%d")
|
||||
|
||||
for city in CITIES:
|
||||
city_data = {}
|
||||
for _ in range(num_days):
|
||||
date_str = current_date.strftime("%Y-%m-%d")
|
||||
|
||||
condition = random.choice(CITY_WEATHER_CONDITIONS[city])
|
||||
temperature = random.randint(*WEATHER_CONDITIONS[condition]["temp_range"])
|
||||
humidity = random.randint(*WEATHER_CONDITIONS[condition]["humidity_range"])
|
||||
|
||||
city_data[date_str] = {
|
||||
"temperature": temperature,
|
||||
"condition": condition,
|
||||
"humidity": humidity
|
||||
}
|
||||
current_date += timedelta(days=1)
|
||||
weather_data[city] = city_data
|
||||
current_date = datetime.strptime(start_date, "%Y-%m-%d")
|
||||
|
||||
return weather_data
|
||||
|
||||
|
||||
def generate_forecast(days=14):
|
||||
forecasts = {}
|
||||
|
||||
for city in CITIES:
|
||||
forecast = {}
|
||||
for day in range(days):
|
||||
date = datetime.now() + timedelta(days=day)
|
||||
condition = random.choice(CITY_WEATHER_CONDITIONS[city])
|
||||
temperature = random.randint(*WEATHER_CONDITIONS[condition]["temp_range"])
|
||||
humidity = random.randint(*WEATHER_CONDITIONS[condition]["humidity_range"])
|
||||
|
||||
daily_weather = {
|
||||
"day": date.strftime("%A"),
|
||||
"temperature": temperature,
|
||||
"humidity": humidity,
|
||||
"condition": condition,
|
||||
}
|
||||
forecast[date.strftime("%Y-%m-%d")] = daily_weather
|
||||
forecasts[city] = forecast
|
||||
|
||||
return forecasts
|
||||
|
||||
|
||||
def generate_current_weather():
|
||||
weather = {}
|
||||
for city in CITIES:
|
||||
condition = random.choice(CITY_WEATHER_CONDITIONS[city])
|
||||
temperature = random.randint(*WEATHER_CONDITIONS[condition]["temp_range"])
|
||||
humidity = random.randint(*WEATHER_CONDITIONS[condition]["humidity_range"])
|
||||
wind_speed = random.randint(*WEATHER_CONDITIONS[condition]["wind_speed_range"])
|
||||
|
||||
data = {
|
||||
"temperature": temperature,
|
||||
"humidity": humidity,
|
||||
"condition": condition,
|
||||
"wind_speed": wind_speed,
|
||||
}
|
||||
|
||||
weather[city] = data
|
||||
return weather
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
historic = generate_historic_data()
|
||||
forecast = generate_forecast()
|
||||
current = generate_current_weather()
|
||||
|
||||
|
||||
with open("../data/historic_data.csv", mode="w", newline="") as file:
|
||||
writer = csv.writer(file)
|
||||
|
||||
writer.writerow(["city", "date", "condition", "humidity", "temperature"])
|
||||
|
||||
for city, data in historic.items():
|
||||
for date, weather in data.items():
|
||||
row = [
|
||||
city,
|
||||
date,
|
||||
weather.get("condition"),
|
||||
weather.get("humidity"),
|
||||
weather.get("temperature"),
|
||||
]
|
||||
writer.writerow(row)
|
||||
|
||||
with open("../data/forecast_data.csv", mode="w", newline="") as file:
|
||||
writer = csv.writer(file)
|
||||
|
||||
# Write the header
|
||||
writer.writerow(["city", "date", "day", "condition", "temperature", "humidity"])
|
||||
|
||||
for city, data in forecast.items():
|
||||
for date, weather in data.items():
|
||||
row = [
|
||||
city,
|
||||
date,
|
||||
weather.get("day"),
|
||||
weather.get("condition"),
|
||||
weather.get("temperature"),
|
||||
weather.get("humidity"),
|
||||
]
|
||||
writer.writerow(row)
|
||||
|
||||
with open("../data/current_data.csv", mode="w", newline="") as file:
|
||||
writer = csv.writer(file)
|
||||
writer.writerow(["city", "condition", "temperature", "humidity", "wind_speed"])
|
||||
|
||||
for city, data in current.items():
|
||||
row = [
|
||||
city,
|
||||
data.get("condition"),
|
||||
data.get("temperature"),
|
||||
data.get("humidity"),
|
||||
data.get("wind_speed"),
|
||||
]
|
||||
writer.writerow(row)
|
0
python-weather-api/test/__init__.py
Normal file
0
python-weather-api/test/__init__.py
Normal file
8
python-weather-api/test/base.py
Normal file
8
python-weather-api/test/base.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
import unittest
|
||||
from app import create_app
|
||||
|
||||
|
||||
class BaseTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.app = create_app({})
|
||||
self.client = self.app.test_client()
|
78
python-weather-api/test/test_api.py
Normal file
78
python-weather-api/test/test_api.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
from test.base import BaseTestCase
|
||||
|
||||
|
||||
class TestAPI(BaseTestCase):
|
||||
def test_unauthorized_error(self):
|
||||
url = "/api/current/boston"
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
|
||||
def test_authorized_request(self):
|
||||
url = "/api/current/boston?api_key=TEST_KEY"
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
def test_current_weather_success(self):
|
||||
url = "/api/current/boston?api_key=TEST_KEY"
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = response.get_json().get("data")
|
||||
|
||||
entry = data[0]
|
||||
self.assertEqual(entry.get("city"), "boston")
|
||||
|
||||
|
||||
def test_current_weather_not_found(self):
|
||||
url = "/api/current/doesntexistcity?api_key=TEST_KEY"
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
|
||||
def test_forecast_weather_success(self):
|
||||
url = "/api/forecast/boston?api_key=TEST_KEY"
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = response.get_json().get("data")
|
||||
|
||||
entry = data[0]
|
||||
self.assertEqual(entry.get("city"), "boston")
|
||||
|
||||
|
||||
def test_forecast_weather_not_found(self):
|
||||
url = "/api/forecast/doesntexistcity?api_key=TEST_KEY"
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
|
||||
def test_historical_weather_success(self):
|
||||
url = "/api/historical/boston?api_key=TEST_KEY"
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
url = "/api/historical/boston/2023-01-01?api_key=TEST_KEY"
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
def test_historical_weather_not_found(self):
|
||||
url = "/api/historical/doesntexistcity?api_key=TEST_KEY"
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
|
||||
def test_historical_weather_bad_date_format(self):
|
||||
url = "/api/historical/boston/01-01-2023?api_key=TEST_KEY"
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 400)
|
Loading…
Reference in New Issue
Block a user