Skip to content

Commit

Permalink
Merge pull request #75 from paul-florentin-charles/dev
Browse files Browse the repository at this point in the history
Some more API routes
  • Loading branch information
paul-florentin-charles authored Sep 21, 2024
2 parents 264c4bd + 8d23635 commit eb8ed94
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 142 deletions.
98 changes: 28 additions & 70 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import matplotlib.pyplot as plt
import uvicorn
from fastapi import FastAPI
from fastapi import FastAPI, HTTPException
from starlette.responses import StreamingResponse

from src.api.media_types import MediaType
Expand Down Expand Up @@ -303,54 +303,29 @@ def get_minimal_csv(


@app.get(
"/graph/rainfall_monthly_averages",
"/graph/rainfall_averages",
response_class=StreamingResponse,
summary="Retrieve rainfall monthly averages of data as a PNG.",
description="If no ending year is precised, "
f"computes the relative distance until most recent year available: {all_rainfall.get_last_year()}.",
summary="Retrieve rainfall monthly or seasonal averages of data as a PNG.",
description=f"Time mode should be either '{TimeMode.MONTHLY.value}' or '{TimeMode.SEASONAL.value}'.\n"
f"If no ending year is precised, most recent year available is taken: {all_rainfall.get_last_year()}.",
tags=["Graph"],
operation_id="getRainfallMonthlyAverages",
operation_id="getRainfallAverages",
)
def get_rainfall_monthly_averages(
begin_year: int,
end_year: int | None = None,
):
end_year = end_year or all_rainfall.get_last_year()

all_rainfall.bar_rainfall_averages(begin_year=begin_year, end_year=end_year)

img_buffer = io.BytesIO()
plt.savefig(img_buffer, format="png")
plt.close()
img_buffer.seek(0)

return StreamingResponse(
img_buffer,
headers={
"Content-Disposition": f'inline; filename="rainfall_monthly_averages_{begin_year}_{end_year}.png"'
},
media_type=MediaType.IMG_PNG.value,
)


@app.get(
"/graph/rainfall_seasonal_averages",
response_class=StreamingResponse,
summary="Retrieve rainfall seasonal averages of data as a PNG.",
description="If no ending year is precised, "
f"computes the relative distance until most recent year available: {all_rainfall.get_last_year()}.",
tags=["Graph"],
operation_id="getRainfallSeasonalAverages",
)
def get_rainfall_seasonal_averages(
def get_rainfall_averages(
time_mode: TimeMode,
begin_year: int,
end_year: int | None = None,
):
end_year = end_year or all_rainfall.get_last_year()

all_rainfall.bar_rainfall_averages(
begin_year=begin_year, end_year=end_year, monthly=False
averages = all_rainfall.bar_rainfall_averages(
time_mode=time_mode.value, begin_year=begin_year, end_year=end_year
)
if averages is None:
raise HTTPException(
status_code=400,
detail=f"time_mode should be either '{TimeMode.MONTHLY.value}' or '{TimeMode.SEASONAL.value}'.",
)

img_buffer = io.BytesIO()
plt.savefig(img_buffer, format="png")
Expand All @@ -360,51 +335,34 @@ def get_rainfall_seasonal_averages(
return StreamingResponse(
img_buffer,
headers={
"Content-Disposition": f'inline; filename="rainfall_seasonal_averages_{begin_year}_{end_year}.png"'
"Content-Disposition": f'inline; filename="rainfall_{time_mode.value}_averages_{begin_year}_{end_year}.png"'
},
media_type=MediaType.IMG_PNG.value,
)


@app.get(
"/graph/rainfall_monthly_linreg_slopes",
"/graph/rainfall_linreg_slopes",
response_class=StreamingResponse,
summary="Retrieve rainfall monthly linear regression slopes of data as a PNG.",
summary="Retrieve rainfall monthly or seasonal linear regression slopes of data as a PNG.",
description=f"Time mode should be either '{TimeMode.MONTHLY.value}' or '{TimeMode.SEASONAL.value}'.",
tags=["Graph"],
operation_id="getRainfallMonthlyLinregSlopes",
operation_id="getRainfallLinregSlopes",
)
def get_rainfall_monthly_linreg_slopes():
all_rainfall.bar_rainfall_linreg_slopes()

img_buffer = io.BytesIO()
plt.savefig(img_buffer, format="png")
plt.close()
img_buffer.seek(0)

filename = f"rainfall_monthly_linreg_slopes_{all_rainfall.starting_year}_{all_rainfall.get_last_year()}.png"

return StreamingResponse(
img_buffer,
headers={"Content-Disposition": f'inline; filename="{filename}"'},
media_type=MediaType.IMG_PNG.value,
)

@app.get(
"/graph/rainfall_seasonal_linreg_slopes",
response_class=StreamingResponse,
summary="Retrieve rainfall seasonal linear regression slopes of data as a PNG.",
tags=["Graph"],
operation_id="getRainfallSeasonalLinregSlopes",
)
def get_rainfall_seasonal_linreg_slopes():
all_rainfall.bar_rainfall_linreg_slopes(monthly=False)
def get_rainfall_linreg_slopes(time_mode: TimeMode):
linreg_slopes = all_rainfall.bar_rainfall_linreg_slopes(time_mode.value)
if linreg_slopes is None:
raise HTTPException(
status_code=400,
detail=f"time_mode should be either '{TimeMode.MONTHLY.value}' or '{TimeMode.SEASONAL.value}'.",
)

img_buffer = io.BytesIO()
plt.savefig(img_buffer, format="png")
plt.close()
img_buffer.seek(0)

filename = f"rainfall_seasonal_linreg_slopes_{all_rainfall.starting_year}_{all_rainfall.get_last_year()}.png"
filename = f"rainfall_{time_mode.value}_linreg_slopes_{all_rainfall.starting_year}_{all_rainfall.get_last_year()}.png"

return StreamingResponse(
img_buffer,
Expand Down
4 changes: 2 additions & 2 deletions coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 4 additions & 6 deletions src/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ def raise_time_mode_error_or_do_nothing(
if time_mode is 'seasonal' and season is None.
:return: None.
"""
if time_mode == TimeMode.MONTHLY:
if month is None:
raise HTTPException(status_code=400, detail="Month cannot be null.")
if time_mode == TimeMode.MONTHLY and month is None:
raise HTTPException(status_code=400, detail="Month cannot be null.")

if time_mode == TimeMode.SEASONAL:
if season is None:
raise HTTPException(status_code=400, detail="Season cannot be null.")
if time_mode == TimeMode.SEASONAL and season is None:
raise HTTPException(status_code=400, detail="Season cannot be null.")
108 changes: 50 additions & 58 deletions src/core/models/all_rainfall.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,10 @@ def export_as_csv(
:return: CSV data as a string if no path is set.
None otherwise.
"""
entity = self.get_entity_for_time_mode(time_mode, month, season)
if entity := self.get_entity_for_time_mode(time_mode, month, season):
return entity.export_as_csv(path)

if entity is None:
return entity

return entity.export_as_csv(path)
return None

def get_average_rainfall(
self,
Expand All @@ -153,12 +151,10 @@ def get_average_rainfall(
Set if time_mode is 'seasonal' (optional).
:return: A float representing the average Rainfall.
"""
entity = self.get_entity_for_time_mode(time_mode, month, season)

if entity is None:
return entity
if entity := self.get_entity_for_time_mode(time_mode, month, season):
return entity.get_average_yearly_rainfall(begin_year, end_year)

return entity.get_average_yearly_rainfall(begin_year, end_year)
return None

def get_normal(
self,
Expand All @@ -181,12 +177,10 @@ def get_normal(
:return: A float representing the Rainfall normal.
"""

entity = self.get_entity_for_time_mode(time_mode, month, season)

if entity is None:
return entity
if entity := self.get_entity_for_time_mode(time_mode, month, season):
return entity.get_normal(begin_year, self.round_precision)

return entity.get_normal(begin_year, self.round_precision)
return None

def get_relative_distance_from_normal(
self,
Expand Down Expand Up @@ -214,14 +208,12 @@ def get_relative_distance_from_normal(
Set if time_mode is 'seasonal' (optional).
:return: A float representing the relative distance to rainfall normal.
"""
entity = self.get_entity_for_time_mode(time_mode, month, season)

if entity is None:
return entity
if entity := self.get_entity_for_time_mode(time_mode, month, season):
return entity.get_relative_distance_from_normal(
normal_year, begin_year, end_year
)

return entity.get_relative_distance_from_normal(
normal_year, begin_year, end_year
)
return None

def get_rainfall_standard_deviation(
self,
Expand Down Expand Up @@ -252,14 +244,12 @@ def get_rainfall_standard_deviation(
:return: The standard deviation as a float.
Nothing if the specified column does not exist.
"""
entity = self.get_entity_for_time_mode(time_mode, month, season)
if entity := self.get_entity_for_time_mode(time_mode, month, season):
return entity.get_standard_deviation(
begin_year, end_year, weigh_by_average=weigh_by_average
)

if entity is None:
return entity

return entity.get_standard_deviation(
begin_year, end_year, weigh_by_average=weigh_by_average
)
return None

def get_years_below_normal(
self,
Expand Down Expand Up @@ -287,12 +277,10 @@ def get_years_below_normal(
Set if time_mode is 'seasonal' (optional).
:return: A float representing the relative distance to rainfall normal.
"""
entity = self.get_entity_for_time_mode(time_mode, month, season)

if entity is None:
return entity
if entity := self.get_entity_for_time_mode(time_mode, month, season):
return entity.get_years_below_normal(normal_year, begin_year, end_year)

return entity.get_years_below_normal(normal_year, begin_year, end_year)
return None

def get_years_above_normal(
self,
Expand Down Expand Up @@ -320,12 +308,10 @@ def get_years_above_normal(
Set if time_mode is 'seasonal' (optional).
:return: A float representing the relative distance to rainfall normal.
"""
entity = self.get_entity_for_time_mode(time_mode, month, season)
if entity := self.get_entity_for_time_mode(time_mode, month, season):
return entity.get_years_above_normal(normal_year, begin_year, end_year)

if entity is None:
return entity

return entity.get_years_above_normal(normal_year, begin_year, end_year)
return None

def get_last_year(self) -> int:
"""
Expand All @@ -339,53 +325,59 @@ def get_last_year(self) -> int:

def bar_rainfall_averages(
self,
time_mode: str,
begin_year: int,
end_year: int | None = None,
monthly=True,
) -> list:
) -> list | None:
"""
Plots a bar graphic displaying average rainfall for each month or each season.
:param time_mode: A string setting the time period ['monthly', 'seasonal'].
:param begin_year: An integer representing the year
to start getting our rainfall values.
:param end_year: An integer representing the year
to end getting our rainfall values (optional).
:param monthly: If True, plots monthly rainfall averages.
If False, plots seasonal rainfall averages. Defaults to True (optional).
:return: A list of the Rainfall averages for each month or season.
None if time_mode is not within {'monthly', 'seasonal'}.
"""
label = f"Average rainfall (mm) between {begin_year or self.starting_year} and {end_year or self.get_last_year()}"
if monthly:
end_year = end_year or self.get_last_year()
label = f"Average rainfall (mm) between {begin_year} and {end_year}"

if time_mode == TimeMode.MONTHLY.value:
return plotting.bar_monthly_rainfall_averages(
list(self.monthly_rainfalls.values()),
begin_year=begin_year,
end_year=end_year,
label=label,
)
elif time_mode == TimeMode.SEASONAL.value:
return plotting.bar_seasonal_rainfall_averages(
list(self.seasonal_rainfalls.values()),
begin_year=begin_year,
end_year=end_year,
label=label,
)

return plotting.bar_seasonal_rainfall_averages(
list(self.seasonal_rainfalls.values()),
begin_year=begin_year,
end_year=end_year,
label=label,
)
return None

def bar_rainfall_linreg_slopes(self, monthly=True) -> list:
def bar_rainfall_linreg_slopes(self, time_mode: str) -> list | None:
"""
Plots a bar graphic displaying linear regression slope for each month or each season.
:param monthly: if True, plots monthly rainfall LinReg slopes.
if False, plots seasonal rainfall LinReg slopes. Defaults to True (optional).
:param time_mode: A string setting the time period ['monthly', 'seasonal'].
:return: A list of the Rainfall LinReg slopes for each month or season.
None if time_mode is not within {'monthly', 'seasonal'}.
"""
if monthly:
if time_mode == TimeMode.MONTHLY.value:
return plotting.bar_monthly_rainfall_linreg_slopes(
list(self.monthly_rainfalls.values())
)
elif time_mode == TimeMode.SEASONAL.value:
return plotting.bar_seasonal_rainfall_linreg_slopes(
list(self.seasonal_rainfalls.values())
)

return plotting.bar_seasonal_rainfall_linreg_slopes(
list(self.seasonal_rainfalls.values())
)
return None

def get_entity_for_time_mode(
self, time_mode: str, month: str | None = None, season: str | None = None
Expand Down
Loading

0 comments on commit eb8ed94

Please sign in to comment.