From 0c49d6ca226ba67d39f721b9e13ea2ecdfcda802 Mon Sep 17 00:00:00 2001 From: polochinoc Date: Mon, 4 Dec 2023 22:11:54 +0100 Subject: [PATCH 1/4] 2 new routes to retrieve monthly and seasonal rainfall averages --- app.py | 26 +++++++++++++++++++ src/api/swagger/graph/__init__.py | 0 .../swagger/graph/monthly_averages_specs.py | 22 ++++++++++++++++ .../swagger/graph/seasonal_averages_specs.py | 22 ++++++++++++++++ src/api/swagger/media_types.py | 1 + src/api/swagger/parameters_specs.py | 8 ++++++ src/core/models/all_rainfall.py | 5 ++-- src/core/utils/functions/plotting.py | 14 +++++++--- 8 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 src/api/swagger/graph/__init__.py create mode 100644 src/api/swagger/graph/monthly_averages_specs.py create mode 100644 src/api/swagger/graph/seasonal_averages_specs.py diff --git a/app.py b/app.py index 053227c..1f0665a 100644 --- a/app.py +++ b/app.py @@ -8,6 +8,7 @@ from typing import Union +import matplotlib.pyplot as plt from flasgger import Swagger, swag_from from flask import Flask, jsonify, request, Response, send_file @@ -18,6 +19,7 @@ YearsAboveOrBelowNormalSchema, ) from src.api.swagger.csv import minimal_csv_specs +from src.api.swagger.graph import monthly_averages_specs, seasonal_averages_specs from src.api.swagger.media_types import MediaType from src.api.swagger.rainfall import ( average_specs, @@ -256,5 +258,29 @@ def minimal_csv() -> Response: return send_file(params[-1], mimetype=MediaType.TXT_CSV, as_attachment=True) +@app.route(f"{base_path}/graph/monthly_averages") +@swag_from(monthly_averages_specs.route_specs) +def monthly_averages() -> Response: + params: tuple = parse_args(request.args, param.svg_path) + + all_rainfall.bar_rainfall_averages() + plt.savefig(params[0]) + plt.close() + + return send_file(params[0], mimetype=MediaType.IMG_SVG, as_attachment=True) + + +@app.route(f"{base_path}/graph/seasonal_averages") +@swag_from(seasonal_averages_specs.route_specs) +def seasonal_averages() -> Response: + params: tuple = parse_args(request.args, param.svg_path) + + all_rainfall.bar_rainfall_averages(monthly=False) + plt.savefig(params[0]) + plt.close() + + return send_file(params[0], mimetype=MediaType.IMG_SVG, as_attachment=True) + + if __name__ == "__main__": app.run(debug=True) diff --git a/src/api/swagger/graph/__init__.py b/src/api/swagger/graph/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/swagger/graph/monthly_averages_specs.py b/src/api/swagger/graph/monthly_averages_specs.py new file mode 100644 index 0000000..f5f593b --- /dev/null +++ b/src/api/swagger/graph/monthly_averages_specs.py @@ -0,0 +1,22 @@ +""" +/graph/monthly_averages + +Retrieve monthly averages of data as an SVG. +""" + +import src.api.swagger.parameters_specs as param +from src.api.swagger.media_types import MediaType + +route_specs: dict = { + "operationId": "getMonthlyAverages", + "summary": "Retrieve monthly averages of data as an SVG.", + "tags": ["Graph"], + "responses": { + "200": { + "description": "Monthly averages as an SVG", + "content": MediaType.IMG_SVG, + }, + }, + "parameters": [param.svg_path], + "produces": MediaType.IMG_SVG, +} diff --git a/src/api/swagger/graph/seasonal_averages_specs.py b/src/api/swagger/graph/seasonal_averages_specs.py new file mode 100644 index 0000000..cece892 --- /dev/null +++ b/src/api/swagger/graph/seasonal_averages_specs.py @@ -0,0 +1,22 @@ +""" +/graph/seasonal_averages + +Retrieve seasonal averages of data as an SVG. +""" + +import src.api.swagger.parameters_specs as param +from src.api.swagger.media_types import MediaType + +route_specs: dict = { + "operationId": "getSeasonalAverages", + "summary": "Retrieve seasonal averages of data as an SVG.", + "tags": ["Graph"], + "responses": { + "200": { + "description": "Seasonal averages as an SVG", + "content": MediaType.IMG_SVG, + }, + }, + "parameters": [param.svg_path], + "produces": MediaType.IMG_SVG, +} diff --git a/src/api/swagger/media_types.py b/src/api/swagger/media_types.py index 1eb15cf..839f52b 100644 --- a/src/api/swagger/media_types.py +++ b/src/api/swagger/media_types.py @@ -14,3 +14,4 @@ class MediaType(str, BaseEnum): APP_JSON: str = mimetypes.types_map[".json"] TXT_CSV: str = mimetypes.types_map[".csv"] + IMG_SVG: str = mimetypes.types_map[".svg"] diff --git a/src/api/swagger/parameters_specs.py b/src/api/swagger/parameters_specs.py index ee7dedc..f8777aa 100644 --- a/src/api/swagger/parameters_specs.py +++ b/src/api/swagger/parameters_specs.py @@ -65,3 +65,11 @@ "name": "csv_path", "in": "query", } + +svg_path: dict = { + "default": "graph.svg", + "required": True, + "type": "string", + "name": "svg_path", + "in": "query", +} diff --git a/src/core/models/all_rainfall.py b/src/core/models/all_rainfall.py index 664d0ad..70c557d 100644 --- a/src/core/models/all_rainfall.py +++ b/src/core/models/all_rainfall.py @@ -322,10 +322,11 @@ def bar_rainfall_averages(self, monthly: Optional[bool] = True) -> list: if False, plots seasonal rainfall averages. :return: A list of the Rainfall averages for each month or season. """ + label: str = f"Average rainfall (mm) between {self.starting_year} and {self.get_last_year()}" if monthly: - return plotting.bar_monthly_rainfall_averages(self.monthly_rainfalls) + return plotting.bar_monthly_rainfall_averages(self.monthly_rainfalls, label) - return plotting.bar_seasonal_rainfall_averages(self.seasonal_rainfalls) + return plotting.bar_seasonal_rainfall_averages(self.seasonal_rainfalls, label) def bar_rainfall_linreg_slopes(self, monthly: Optional[bool] = True) -> list: """ diff --git a/src/core/utils/functions/plotting.py b/src/core/utils/functions/plotting.py index 7024559..0193b5d 100644 --- a/src/core/utils/functions/plotting.py +++ b/src/core/utils/functions/plotting.py @@ -86,12 +86,15 @@ def bar_column_according_to_year(yearly_rainfall: pd.DataFrame, label: Label) -> return True -def bar_monthly_rainfall_averages(monthly_rainfalls: dict) -> list: +def bar_monthly_rainfall_averages( + monthly_rainfalls: dict, label: Optional[str] = "Average rainfall (mm)" +) -> list: """ Plots a bar graphic displaying average rainfall for each month passed through the dict. :param monthly_rainfalls: A list of instances of MonthlyRainfall. To be purposeful, all instances should have the same time frame in years. + :param label: A string to use as a label for bar graphic. (optional) :return: A list of the Rainfall averages for each month. """ month_labels, averages = [], [] @@ -99,7 +102,7 @@ def bar_monthly_rainfall_averages(monthly_rainfalls: dict) -> list: month_labels.append(monthly_rainfall.month.name[:3]) averages.append(monthly_rainfall.get_average_yearly_rainfall()) - plt.bar(month_labels, averages, label="Average rainfall (mm)") + plt.bar(month_labels, averages, label=label) plt.legend() return averages @@ -124,12 +127,15 @@ def bar_monthly_rainfall_linreg_slopes(monthly_rainfalls: dict) -> list: return slopes -def bar_seasonal_rainfall_averages(seasonal_rainfalls: dict) -> list: +def bar_seasonal_rainfall_averages( + seasonal_rainfalls: dict, label: Optional[str] = "Average rainfall (mm)" +) -> list: """ Plots a bar graphic displaying average rainfall for each season passed through the dict. :param seasonal_rainfalls: A list of instances of SeasonalRainfall. To be purposeful, all instances should have the same time frame in years. + :param label: A string to use as a label for bar graphic. (optional) :return: A list of the Rainfall averages for each season. """ season_labels, averages = [], [] @@ -137,7 +143,7 @@ def bar_seasonal_rainfall_averages(seasonal_rainfalls: dict) -> list: season_labels.append(seasonal_rainfall.season.name) averages.append(seasonal_rainfall.get_average_yearly_rainfall()) - plt.bar(season_labels, averages, label="Average rainfall (mm)") + plt.bar(season_labels, averages, label=label) plt.legend() return averages From 762a72b01fdfd310cbdfb80fe53c1c09761f7ec2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 4 Dec 2023 21:12:55 +0000 Subject: [PATCH 2/4] Updated coverage.svg --- coverage.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coverage.svg b/coverage.svg index ee07d4c..b6c4e36 100644 --- a/coverage.svg +++ b/coverage.svg @@ -15,7 +15,7 @@ coverage coverage - 96% - 96% + 95% + 95% From 28f88b1969af3e4f60ad408c9f6230c5d1c6123e Mon Sep 17 00:00:00 2001 From: polochinoc Date: Wed, 6 Dec 2023 21:10:38 +0100 Subject: [PATCH 3/4] Changed 'pip3' to 'pip' in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14cd7ff..e8b79a5 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ ```commandline git clone https://github.com/paul-florentin-charles/bcn-rainfall-models.git cd bcn-rainfall-models -pip3 install -r requirements.txt +pip install -r requirements.txt python3 app.py ``` From 67cfd7334395b85088d1abea55c7c23492f08a86 Mon Sep 17 00:00:00 2001 From: polochinoc Date: Wed, 6 Dec 2023 21:33:20 +0100 Subject: [PATCH 4/4] Unified svg_path and csv_path swagger parameters as file_name parameter --- app.py | 6 +++--- src/api/swagger/csv/minimal_csv_specs.py | 2 +- src/api/swagger/graph/monthly_averages_specs.py | 2 +- src/api/swagger/graph/seasonal_averages_specs.py | 2 +- src/api/swagger/parameters_specs.py | 15 ++++----------- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/app.py b/app.py index 1f0665a..f69f1ae 100644 --- a/app.py +++ b/app.py @@ -244,7 +244,7 @@ def years_above_normal() -> Response: @swag_from(minimal_csv_specs.route_specs) def minimal_csv() -> Response: params: tuple = parse_args( - request.args, param.time_mode, param.month, param.season, param.csv_path + request.args, param.time_mode, param.month, param.season, param.file_name ) error: Union[Response, dict] = manage_time_mode_errors( @@ -261,7 +261,7 @@ def minimal_csv() -> Response: @app.route(f"{base_path}/graph/monthly_averages") @swag_from(monthly_averages_specs.route_specs) def monthly_averages() -> Response: - params: tuple = parse_args(request.args, param.svg_path) + params: tuple = parse_args(request.args, param.file_name) all_rainfall.bar_rainfall_averages() plt.savefig(params[0]) @@ -273,7 +273,7 @@ def monthly_averages() -> Response: @app.route(f"{base_path}/graph/seasonal_averages") @swag_from(seasonal_averages_specs.route_specs) def seasonal_averages() -> Response: - params: tuple = parse_args(request.args, param.svg_path) + params: tuple = parse_args(request.args, param.file_name) all_rainfall.bar_rainfall_averages(monthly=False) plt.savefig(params[0]) diff --git a/src/api/swagger/csv/minimal_csv_specs.py b/src/api/swagger/csv/minimal_csv_specs.py index 53f2f7f..61f2707 100644 --- a/src/api/swagger/csv/minimal_csv_specs.py +++ b/src/api/swagger/csv/minimal_csv_specs.py @@ -22,6 +22,6 @@ }, "400": error.bad_request_specs, }, - "parameters": [param.csv_path, *param.time_params], + "parameters": [param.file_name, *param.time_params], "produces": MediaType.TXT_CSV, } diff --git a/src/api/swagger/graph/monthly_averages_specs.py b/src/api/swagger/graph/monthly_averages_specs.py index f5f593b..6deea43 100644 --- a/src/api/swagger/graph/monthly_averages_specs.py +++ b/src/api/swagger/graph/monthly_averages_specs.py @@ -17,6 +17,6 @@ "content": MediaType.IMG_SVG, }, }, - "parameters": [param.svg_path], + "parameters": [param.file_name], "produces": MediaType.IMG_SVG, } diff --git a/src/api/swagger/graph/seasonal_averages_specs.py b/src/api/swagger/graph/seasonal_averages_specs.py index cece892..bf817ec 100644 --- a/src/api/swagger/graph/seasonal_averages_specs.py +++ b/src/api/swagger/graph/seasonal_averages_specs.py @@ -17,6 +17,6 @@ "content": MediaType.IMG_SVG, }, }, - "parameters": [param.svg_path], + "parameters": [param.file_name], "produces": MediaType.IMG_SVG, } diff --git a/src/api/swagger/parameters_specs.py b/src/api/swagger/parameters_specs.py index f8777aa..a9efff5 100644 --- a/src/api/swagger/parameters_specs.py +++ b/src/api/swagger/parameters_specs.py @@ -58,18 +58,11 @@ time_params: tuple = (time_mode, month, season) -csv_path: dict = { - "default": "data.csv", - "required": True, - "type": "string", - "name": "csv_path", - "in": "query", -} - -svg_path: dict = { - "default": "graph.svg", +file_name: dict = { + "default": None, "required": True, "type": "string", - "name": "svg_path", + "name": "file_name", + "description": "Name of the file to be retrieved", "in": "query", }