Skip to content

Commit

Permalink
feat: add Python type hinting and annotations
Browse files Browse the repository at this point in the history
Signed-off-by: Davide Madrisan <[email protected]>
  • Loading branch information
madrisan committed Dec 24, 2024
1 parent e8a6faa commit e154795
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 34 deletions.
10 changes: 7 additions & 3 deletions hadcrut5_bars.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
Display a bar plot of the HadCRUT5 Global temperature dataset.
"""

import argparse

import matplotlib.pyplot as plt
import matplotlib.colors

from hadcrut5lib import argparser, HadCRUT5


def parse_args():
def parse_args() -> argparse.Namespace:
"""This function parses and return arguments passed in"""
descr = "Parse and plot the HadCRUT5 temperature datasets"
examples = [
Expand Down Expand Up @@ -49,7 +51,8 @@ def parse_args():
return parser.parse_args()


def plotbar(period, outfile, verbose):
# mypy: disable-error-code="misc, attr-defined"
def plotbar(period: str, outfile: str, verbose: bool):
"""
Create a bar plot for the specified period and diplay it or save it to file
if outfile is set
Expand Down Expand Up @@ -81,7 +84,8 @@ def major_formatter(x, pos):
plt.style.use("dark_background")
_, ax = plt.subplots()

cmap = plt.cm.jet # or plt.cm.bwr # pylint: disable=no-member
# pylint: disable=no-member
cmap = plt.cm.jet # or plt.cm.bwr
norm = matplotlib.colors.Normalize(vmin=-1, vmax=max(mean))
colors = cmap(norm(mean))

Expand Down
12 changes: 7 additions & 5 deletions hadcrut5_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
Display a plot of the HadCRUT5 temperature dataset.
"""

import argparse
from math import trunc
from typing import List

import matplotlib as mpl
import matplotlib.pyplot as plt
Expand All @@ -15,7 +17,7 @@
from hadcrut5lib import argparser, HadCRUT5


def parse_args():
def parse_args() -> argparse.Namespace:
"""This function parses and return arguments passed in"""
descr = "Parse and plot the HadCRUT5 temperature datasets"
examples = [
Expand Down Expand Up @@ -99,17 +101,17 @@ def parse_args():
return parser.parse_args()


def dataset_current_anomaly(temperatures):
def dataset_current_anomaly(temperatures: List[float]) -> float:
"""Return the current anomaly"""
return temperatures[-1]


def dataset_max_anomaly(temperatures):
def dataset_max_anomaly(temperatures: List[float]) -> float:
"""Return the maximum anomaly with respect to 'temperatures'"""
return np.max(temperatures)


def dataset_smoother(years, temperatures, chunksize):
def dataset_smoother(years: List[int | float], temperatures: List[float], chunksize: int):
"""Make the lines smoother by using {chunksize}-year means"""
data_range = range((len(years) + chunksize - 1) // chunksize)
subset_years = [years[i * chunksize] for i in data_range]
Expand All @@ -120,7 +122,7 @@ def dataset_smoother(years, temperatures, chunksize):
return subset_years, subset_temperatures


def plotline(hc5, chunksize, annotate, outfile):
def plotline(hc5: HadCRUT5, chunksize: int, annotate: int, outfile: str):
"""
Create a plot for the specified period and arguments and diplay it or save
it to file if outfile is set
Expand Down
11 changes: 7 additions & 4 deletions hadcrut5_stripe.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
Display a stripe image of the HadCRUT5 Global temperature dataset.
"""

import argparse
from typing import List

import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.colors import ListedColormap
Expand All @@ -14,7 +17,7 @@
from hadcrut5lib import argparser, HadCRUT5


def parse_args():
def parse_args() -> argparse.Namespace:
"""This function parses and return arguments passed in"""
descr = "Parse and plot a stripe image of the HadCRUT5 temperature datasets"
examples = [
Expand Down Expand Up @@ -59,7 +62,7 @@ def parse_args():
return parser.parse_args()


def plotstripe(region, outfile, labels, verbose):
def plotstripe(region: str, outfile: str, labels: bool, verbose: bool):
"""
Create a stripe plot for the specified period and diplay it or save it to
file if outfile is set
Expand All @@ -74,7 +77,7 @@ def plotstripe(region, outfile, labels, verbose):

years = hc5.dataset_years()
yfirst, ylast = years[0], years[-1]
yrange = ylast - yfirst
yrange = int(ylast - yfirst)

regions_switch = {
"global": hc5.GLOBAL_REGION,
Expand Down Expand Up @@ -134,7 +137,7 @@ def plotstripe(region, outfile, labels, verbose):
)

ticks = [0, 0.2, 0.4, 0.6, 0.8, 1]
xlabels = [round(yfirst + x * yrange) for x in ticks]
xlabels: List[str] = [str(round(yfirst + x * yrange)) for x in ticks]
plt.xticks(ticks, xlabels, fontweight="bold", fontsize=12)
else:
ax.get_xaxis().set_visible(False)
Expand Down
45 changes: 23 additions & 22 deletions hadcrut5lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import json
import logging
import sys
from typing import Dict, List, Mapping, Tuple

import numpy as np
import requests
Expand All @@ -28,15 +29,15 @@
__status__ = "stable"


def copyleft(descr):
def copyleft(descr: str) -> str:
"""Print the Copyright message and License"""
return (
f"{descr} v{__version__} ({__status__})\n"
"{__copyright__} <{__email__}>\nLicense: {__license__}"
)


def argparser(descr, examples):
def argparser(descr: str, examples: List[str]) -> argparse.ArgumentParser:
"""Return a new ArgumentParser object"""
return argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
Expand All @@ -49,16 +50,16 @@ def argparser(descr, examples):
class HadCRUT5:
"""Class for parsing and plotting HadCRUT5 datasets"""

# current dataset version
_DATASET_VERSION = "5.0.2.0"
"""current dataset version"""

# list of all the available data types
_DEFAULT_DATATYPE = "annual"
_VALID_DATATYPES = [_DEFAULT_DATATYPE, "monthly"]
"""list of all the available data types"""

# list of all the valid periods
_DEFAULT_PERIOD = "1961-1990"
_VALID_PERIODS = [_DEFAULT_PERIOD, "1850-1900", "1880-1920"]
"""list of all the valid periods"""

GLOBAL_REGION = "Global"
NORTHERN_REGION = "Northern Hemisphere"
Expand Down Expand Up @@ -114,10 +115,10 @@ def datasets_download(self):
if self._enable_southern:
self._wget_dataset_file(self._southern_hemisphere_filename)

def datasets_load(self):
def datasets_load(self) -> None:
"""Load all the netCDFv4 datasets"""

def dataset_load(dataset_filename):
def dataset_load(dataset_filename: str) -> Dict:
"""Load the data provided by the netCDFv4 file 'dataset_filename'"""
dataset = nc_Dataset(dataset_filename)
return {
Expand All @@ -126,7 +127,7 @@ def dataset_load(dataset_filename):
"variables": dataset.variables,
}

def dataset_metadata_dump(dataset_name, dataset):
def dataset_metadata_dump(dataset_name: str, dataset) -> None:
metadata = dataset["metadata"]
self.logging_debug(
(f'Metadata for "{dataset_name}" dataset:\n{json.dumps(metadata, indent=2)}'),
Expand All @@ -145,14 +146,14 @@ def dataset_metadata_dump(dataset_name, dataset):
self._datasets[region] = dataset_load(self._southern_hemisphere_filename)
dataset_metadata_dump(region, self._datasets[region])

def datasets_normalize(self):
def datasets_normalize(self) -> None:
"""
Normalize the temperature means to the required time period.
Set _datasets_normalized with a tuple containing lower, mean, and upper
temperatures for every enabled region
"""

def normalization_value(temperatures):
def normalization_value(temperatures: List[float]) -> float:
"""
Return the value to be substracted to temperatures in order to
obtain a mean-centered dataset for the required period
Expand All @@ -176,7 +177,7 @@ def normalization_value(temperatures):
raise ValueError(f'Unsupported period "{self._period}"')

self.logging_debug("The mean anomaly in {self._period} is about {norm_temp:.8f}°C")
return norm_temp
return float(norm_temp)

for region, data in self._datasets.items():
mean = data["variables"]["tas_mean"]
Expand All @@ -196,26 +197,26 @@ def normalization_value(temperatures):
f"normalized dataset ({region}): mean \\\n{np.array(mean) - norm_temp}"
)

def datasets_regions(self):
def datasets_regions(self) -> Mapping[str, object]:
"""Return the dataset regions set by the user at command-line"""
return self._datasets.keys()

def logging(self, message):
def logging(self, message: str) -> None:
"""Print a message"""
logging.info(message)

def logging_debug(self, message):
def logging_debug(self, message: str) -> None:
"""Print a message when in verbose mode only"""
if self._verbose:
logging.info(message)

def _hadcrut5_data_url(self, filename):
def _hadcrut5_data_url(self, filename: str) -> str:
site = "https://www.metoffice.gov.uk"
path = f"/hadobs/hadcrut5/data/HadCRUT.{self._DATASET_VERSION}/analysis/diagnostics/"
url = f"{site}{path}{filename}"
return url

def _wget_dataset_file(self, filename):
def _wget_dataset_file(self, filename: str) -> None:
"""Download a netCDFv4 HadCRUT5 file if not already found locally"""
try:
with open(filename, encoding="utf-8"):
Expand All @@ -240,31 +241,31 @@ def _wget_dataset_file(self, filename):
handle.write(block)

@property
def dataset_datatype(self):
def dataset_datatype(self) -> str:
"""Return the datatype string"""
return self._datatype

@property
def dataset_history(self):
def dataset_history(self) -> str:
"""Return the datatype history from metadata"""
# The datasets have all the same length so choose the first one
region = list(self._datasets.keys())[0]
metadata = self._datasets[region]["metadata"]
return metadata.get("history")

def dataset_normalized_data(self, region):
def dataset_normalized_data(self, region) -> Tuple[List[float], List[float], List[float]]:
"""Return the dataset data normalized for the specified region"""
lower = self._datasets_normalized[region]["lower"]
mean = self._datasets_normalized[region]["mean"]
upper = self._datasets_normalized[region]["upper"]
return (lower, mean, upper)

@property
def dataset_period(self):
def dataset_period(self) -> str:
"""Return the dataset period as a string"""
return self._period

def dataset_years(self):
def dataset_years(self) -> List[int|float]:
"""
Return an array of years corresponding of the loaded datasets.
If the original dataset packages monthly data, the returning vector
Expand All @@ -279,7 +280,7 @@ def dataset_years(self):
return years

@property
def is_monthly_dataset(self):
def is_monthly_dataset(self) -> bool:
"""
Return True if the loaded dataset provide monthly data,
False otherwise
Expand Down

0 comments on commit e154795

Please sign in to comment.