From 99b015c0daaa44f8e5d49c894b75186dab129217 Mon Sep 17 00:00:00 2001 From: polochinoc Date: Wed, 8 Nov 2023 17:48:33 +0100 Subject: [PATCH] Implemented Marshmallow Schemas amidts other small fixes --- src/api/schemas.py | 74 +++++++++++++++ src/api/swagger/rainfall/average_specs.py | 9 +- src/api/swagger/rainfall/normal_specs.py | 9 +- .../relative_distance_to_normal_specs.py | 9 +- .../rainfall/standard_deviation_specs.py | 9 +- src/api/swagger/year/above_normal_specs.py | 7 +- src/api/swagger/year/below_normal_specs.py | 9 +- src/app.py | 95 ++++++++++++++----- src/core/utils/enums/seasons.py | 1 + src/core/utils/enums/time_modes.py | 14 +++ src/core/utils/functions/metrics.py | 2 +- 11 files changed, 176 insertions(+), 62 deletions(-) create mode 100644 src/api/schemas.py create mode 100644 src/core/utils/enums/time_modes.py diff --git a/src/api/schemas.py b/src/api/schemas.py new file mode 100644 index 0000000..b8b072b --- /dev/null +++ b/src/api/schemas.py @@ -0,0 +1,74 @@ +""" +Provides a bunch of Marshmallow Schemas to validate rainfall data processed through the API. +""" + +from typing import Optional, Union + +from flasgger import Schema, fields + +from src.core.utils.enums.time_modes import TimeMode + + +class BaseRainfall(Schema): + """ + Base schema for depicting a value linked to rainfall data. + Should be used as a parent class. + """ + value: Union[float, int] = fields.Number() + begin_year: int = fields.Int() + end_year: Optional[int] = fields.Int() + time_mode: str = fields.Str() + + +class AverageYearlyRainfall(BaseRainfall): + """ + Schema for depicting an average rainfall for a yearly time mode. + """ + name: str = fields.Str(load_default='average rainfall') + value: float = fields.Float() + time_mode: str = fields.Str(load_default=TimeMode.YEARLY.value) + + +class NormalYearlyRainfall(BaseRainfall): + """ + Schema for depicting a rainfall normal for a yearly time mode. + """ + name: str = fields.Str(load_default='rainfall normal') + value: float = fields.Float() + time_mode: str = fields.Str(load_default=TimeMode.YEARLY.value) + + +class RelativeDistanceToNormalYearlyRainfall(BaseRainfall): + """ + Schema for depicting a relative distance to rainfall normal for a yearly time mode. + """ + name: str = fields.Str(load_default='rainfall relative distance to normal') + value: float = fields.Float() + time_mode: str = fields.Str(load_default=TimeMode.YEARLY.value) + + +class StandardDeviationYearlyRainfall(BaseRainfall): + """ + Schema for depicting a rainfall standard deviation for a yearly time mode. + """ + name: str = fields.Str(load_default='rainfall standard deviation') + value: float = fields.Float() + time_mode: str = fields.Str(load_default=TimeMode.YEARLY.value) + + +class YearsAboveNormalSchema(BaseRainfall): + """ + Schema for giving the number of years above rainfall normal for a yearly time mode. + """ + name: str = fields.Str(load_default='years above rainfall normal') + value: float = fields.Int() + time_mode: str = fields.Str(load_default=TimeMode.YEARLY.value) + + +class YearsBelowNormalSchema(BaseRainfall): + """ + Schema for giving the number of years below rainfall normal for a yearly time mode. + """ + name: str = fields.Str(load_default='years below rainfall normal') + value: float = fields.Int() + time_mode: str = fields.Str(load_default=TimeMode.YEARLY.value) diff --git a/src/api/swagger/rainfall/average_specs.py b/src/api/swagger/rainfall/average_specs.py index 0581841..b633bb8 100644 --- a/src/api/swagger/rainfall/average_specs.py +++ b/src/api/swagger/rainfall/average_specs.py @@ -9,7 +9,7 @@ """ import src.api.swagger.parameters_specs as param - +from src.api.schemas import AverageYearlyRainfall route_specs: dict = { "operationId": "getRainfallAverage", @@ -22,11 +22,8 @@ ], "responses": { "200": { - "description": "the average rainfall (in mm)", - "schema": { - "type": "number", - "example": 623.75 - } + "description": "The average rainfall (in mm)", + "schema": AverageYearlyRainfall } }, "parameters": [ diff --git a/src/api/swagger/rainfall/normal_specs.py b/src/api/swagger/rainfall/normal_specs.py index bbf3887..e8f12a8 100644 --- a/src/api/swagger/rainfall/normal_specs.py +++ b/src/api/swagger/rainfall/normal_specs.py @@ -8,7 +8,7 @@ """ import src.api.swagger.parameters_specs as param - +from src.api.schemas import NormalYearlyRainfall route_specs: dict = { "operationId": "getRainfallNormal", @@ -19,11 +19,8 @@ ], "responses": { "200": { - "description": "the 30 years normal (in mm)", - "schema": { - "type": "number", - "example": 607.28 - } + "description": "The 30 years normal (in mm)", + "schema": NormalYearlyRainfall } }, "parameters": [ diff --git a/src/api/swagger/rainfall/relative_distance_to_normal_specs.py b/src/api/swagger/rainfall/relative_distance_to_normal_specs.py index 4f4f5c0..57e5511 100644 --- a/src/api/swagger/rainfall/relative_distance_to_normal_specs.py +++ b/src/api/swagger/rainfall/relative_distance_to_normal_specs.py @@ -13,7 +13,7 @@ """ import src.api.swagger.parameters_specs as param - +from src.api.schemas import RelativeDistanceToNormalYearlyRainfall route_specs: dict = { "operationId": "getRainfallRelativeDistanceToNormal", @@ -30,11 +30,8 @@ ], "responses": { "200": { - "description": "the relative distance to normal as a percentage.", - "schema": { - "type": "number", - "example": -25.67 - } + "description": "The relative distance to normal as a percentage.", + "schema": RelativeDistanceToNormalYearlyRainfall } }, "parameters": [ diff --git a/src/api/swagger/rainfall/standard_deviation_specs.py b/src/api/swagger/rainfall/standard_deviation_specs.py index 919980a..9bdee5a 100644 --- a/src/api/swagger/rainfall/standard_deviation_specs.py +++ b/src/api/swagger/rainfall/standard_deviation_specs.py @@ -9,7 +9,7 @@ """ import src.api.swagger.parameters_specs as param - +from src.api.schemas import StandardDeviationYearlyRainfall route_specs: dict = { "operationId": "getRainfallStandardDeviation", @@ -22,11 +22,8 @@ ], "responses": { "200": { - "description": "the rainfall standard deviation (in mm)", - "schema": { - "type": "number", - "example": 172.43 - } + "description": "The rainfall standard deviation (in mm)", + "schema": StandardDeviationYearlyRainfall } }, "parameters": [ diff --git a/src/api/swagger/year/above_normal_specs.py b/src/api/swagger/year/above_normal_specs.py index 43ccd40..1aa5e71 100644 --- a/src/api/swagger/year/above_normal_specs.py +++ b/src/api/swagger/year/above_normal_specs.py @@ -8,7 +8,7 @@ """ import src.api.swagger.parameters_specs as param - +from src.api.schemas import YearsAboveNormalSchema route_specs: dict = { "operationId": "getYearsAboveNormal", @@ -21,10 +21,7 @@ "responses": { "200": { "description": "the number of years above normal", - "schema": { - "type": "integer", - "example": 23 - } + "schema": YearsAboveNormalSchema } }, "parameters": [ diff --git a/src/api/swagger/year/below_normal_specs.py b/src/api/swagger/year/below_normal_specs.py index bff5956..5a5f94a 100644 --- a/src/api/swagger/year/below_normal_specs.py +++ b/src/api/swagger/year/below_normal_specs.py @@ -8,7 +8,7 @@ """ import src.api.swagger.parameters_specs as param - +from src.api.schemas import YearsBelowNormalSchema route_specs: dict = { "operationId": "getYearsBelowNormal", @@ -20,11 +20,8 @@ ], "responses": { "200": { - "description": "the number of years below normal", - "schema": { - "type": "integer", - "example": 27 - } + "description": "The number of years below normal", + "schema": YearsBelowNormalSchema } }, "parameters": [ diff --git a/src/app.py b/src/app.py index a263571..d387650 100644 --- a/src/app.py +++ b/src/app.py @@ -11,6 +11,7 @@ from flask import Flask, jsonify, request, Response from api.parameters import Parameter +import src.api.schemas as model from src.api.swagger.rainfall import (average_specs, normal_specs, relative_distance_to_normal_specs, standard_deviation_specs) @@ -30,57 +31,99 @@ @app.route(f"{swagger.template['basePath']}/rainfall/average") @swag_from(average_specs.route_specs) def average_rainfall() -> Response: - return jsonify(all_rainfall.yearly_rainfall.get_average_yearly_rainfall( - begin_year=request.args.get(*Parameter.BEGIN_YEAR.value), - end_year=request.args.get(*Parameter.END_YEAR.value) - )) + begin_year: int = request.args.get(*Parameter.BEGIN_YEAR.value) + end_year: int = request.args.get(*Parameter.END_YEAR.value) + value: float = all_rainfall.yearly_rainfall.get_average_yearly_rainfall(begin_year, end_year) + + return jsonify(model.AverageYearlyRainfall().load({ + "value": value, + "begin_year": begin_year, + "end_year": end_year, + })) @app.route(f"{swagger.template['basePath']}/rainfall/normal") @swag_from(normal_specs.route_specs) def normal_rainfall() -> Response: - return jsonify(all_rainfall.yearly_rainfall.get_normal( - begin_year=request.args.get(*Parameter.BEGIN_YEAR.value)) - ) + begin_year: int = request.args.get(*Parameter.BEGIN_YEAR.value) + value: float = all_rainfall.yearly_rainfall.get_normal(begin_year) + + return jsonify(model.NormalYearlyRainfall().load({ + "value": value, + "begin_year": begin_year, + "end_year": begin_year + 29 + })) @app.route(f"{swagger.template['basePath']}/rainfall/relative_distance_to_normal") @swag_from(relative_distance_to_normal_specs.route_specs) def rainfall_relative_distance_to_normal() -> Response: - return jsonify(all_rainfall.yearly_rainfall.get_relative_distance_from_normal( - normal_year=request.args.get(*Parameter.NORMAL_YEAR.value), - begin_year=request.args.get(*Parameter.BEGIN_YEAR.value), - end_year=request.args.get(*Parameter.END_YEAR.value) - )) + normal_year: int = request.args.get(*Parameter.NORMAL_YEAR.value) + begin_year: int = request.args.get(*Parameter.BEGIN_YEAR.value) + end_year: int = request.args.get(*Parameter.END_YEAR.value) + value: float = all_rainfall.yearly_rainfall.get_relative_distance_from_normal( + normal_year, + begin_year, + end_year + ) + + return jsonify(model.RelativeDistanceToNormalYearlyRainfall().load({ + "value": value, + "begin_year": begin_year, + "end_year": end_year, + })) @app.route(f"{swagger.template['basePath']}/rainfall/standard_deviation") @swag_from(standard_deviation_specs.route_specs) def standard_deviation() -> Response: - return jsonify(all_rainfall.yearly_rainfall.get_standard_deviation( - begin_year=request.args.get(*Parameter.BEGIN_YEAR.value), - end_year=request.args.get(*Parameter.END_YEAR.value) - )) + begin_year: int = request.args.get(*Parameter.BEGIN_YEAR.value) + end_year: int = request.args.get(*Parameter.END_YEAR.value) + value: float = all_rainfall.yearly_rainfall.get_standard_deviation(begin_year, end_year) + + return jsonify(model.StandardDeviationYearlyRainfall().load({ + "value": value, + "begin_year": begin_year, + "end_year": end_year, + })) @app.route(f"{swagger.template['basePath']}/year/below_normal") @swag_from(below_normal_specs.route_specs) def years_below_normal() -> Response: - return jsonify(all_rainfall.yearly_rainfall.get_years_below_normal( - normal_year=request.args.get(*Parameter.NORMAL_YEAR.value), - begin_year=request.args.get(*Parameter.BEGIN_YEAR.value), - end_year=request.args.get(*Parameter.END_YEAR.value) - )) + normal_year: int = request.args.get(*Parameter.NORMAL_YEAR.value) + begin_year: int = request.args.get(*Parameter.BEGIN_YEAR.value) + end_year: int = request.args.get(*Parameter.END_YEAR.value) + value: float = all_rainfall.yearly_rainfall.get_years_below_normal( + normal_year, + begin_year, + end_year + ) + + return jsonify(model.YearsBelowNormalSchema().load({ + "value": value, + "begin_year": begin_year, + "end_year": end_year, + })) @app.route(f"{swagger.template['basePath']}/year/above_normal") @swag_from(above_normal_specs.route_specs) def years_above_normal() -> Response: - return jsonify(all_rainfall.yearly_rainfall.get_years_above_normal( - normal_year=request.args.get(*Parameter.NORMAL_YEAR.value), - begin_year=request.args.get(*Parameter.BEGIN_YEAR.value), - end_year=request.args.get(*Parameter.END_YEAR.value) - )) + normal_year: int = request.args.get(*Parameter.NORMAL_YEAR.value) + begin_year: int = request.args.get(*Parameter.BEGIN_YEAR.value) + end_year: int = request.args.get(*Parameter.END_YEAR.value) + value: float = all_rainfall.yearly_rainfall.get_years_above_normal( + normal_year, + begin_year, + end_year + ) + + return jsonify(model.YearsAboveNormalSchema().load({ + "value": value, + "begin_year": begin_year, + "end_year": end_year, + })) if __name__ == '__main__': diff --git a/src/core/utils/enums/seasons.py b/src/core/utils/enums/seasons.py index f40d5b7..3b05ab6 100644 --- a/src/core/utils/enums/seasons.py +++ b/src/core/utils/enums/seasons.py @@ -1,6 +1,7 @@ """ Provides list of Month (Enum) equivalents for all four seasons of the year. """ + from enum import Enum from src.core.utils.enums.months import Month diff --git a/src/core/utils/enums/time_modes.py b/src/core/utils/enums/time_modes.py new file mode 100644 index 0000000..6be5608 --- /dev/null +++ b/src/core/utils/enums/time_modes.py @@ -0,0 +1,14 @@ +""" +Provides list of Time modes (Enum) to inform on what timeframe is used on current rainfall data. +""" + +from enum import Enum + + +class TimeMode(str, Enum): + """ + An enum listing time modes (yearly, monthly and seasonal) represented by strings. + """ + YEARLY: str = 'yearly' + SEASONAL: str = 'seasonal' + MONTHLY: str = 'monthly' diff --git a/src/core/utils/functions/metrics.py b/src/core/utils/functions/metrics.py index 8b2e458..0c8622c 100644 --- a/src/core/utils/functions/metrics.py +++ b/src/core/utils/functions/metrics.py @@ -72,5 +72,5 @@ def get_normal(yearly_rainfall: pd.DataFrame, begin_year) -> float: return get_average_rainfall( df_opr.get_rainfall_within_year_interval(yearly_rainfall, begin_year, - begin_year + 30) + begin_year + 29) )