From b7ae7fba251914d7b44a508352827cb018850252 Mon Sep 17 00:00:00 2001 From: Hussain Jafari Date: Tue, 12 Nov 2024 09:26:36 -0800 Subject: [PATCH] drop python 3.9 support (#91) Category: CI JIRA issue: https://jira.ihme.washington.edu/browse/MIC-5524 Changes and notes Remove python 3.9 from build matrices. Modernize type hinting. Testing Built repo using cookiecutter. --- {{cookiecutter.package_name}}/README.rst | 2 +- .../python_versions.json | 2 +- .../data/builder.py | 3 +-- .../data/loader.py | 22 +++++++++---------- .../tools/cli.py | 6 ++--- .../tools/make_artifacts.py | 15 ++++++------- .../utilities.py | 13 +++++------ 7 files changed, 28 insertions(+), 35 deletions(-) diff --git a/{{cookiecutter.package_name}}/README.rst b/{{cookiecutter.package_name}}/README.rst index b4303fa..515c263 100644 --- a/{{cookiecutter.package_name}}/README.rst +++ b/{{cookiecutter.package_name}}/README.rst @@ -27,7 +27,7 @@ all necessary requirements as follows:: ({{ cookiecutter.package_name }}) :~$ pip install -e . ...pip will install vivarium and other requirements... -Supported Python versions: 3.9, 3.10, 3.11 +Supported Python versions: 3.10, 3.11 Note the ``-e`` flag that follows pip install. This will install the python package in-place, which is important for making the model specifications later. diff --git a/{{cookiecutter.package_name}}/python_versions.json b/{{cookiecutter.package_name}}/python_versions.json index a32f85f..8b914b0 100644 --- a/{{cookiecutter.package_name}}/python_versions.json +++ b/{{cookiecutter.package_name}}/python_versions.json @@ -1 +1 @@ -["3.9", "3.10", "3.11"] +["3.10", "3.11"] diff --git a/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/data/builder.py b/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/data/builder.py index 53b7c4e..50421cf 100644 --- a/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/data/builder.py +++ b/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/data/builder.py @@ -10,7 +10,6 @@ """ from pathlib import Path -from typing import Optional import pandas as pd from loguru import logger @@ -50,7 +49,7 @@ def open_artifact(output_path: Path, location: str) -> Artifact: def load_and_write_data( - artifact: Artifact, key: str, location: str, years: Optional[str], replace: bool + artifact: Artifact, key: str, location: str, years: str | None, replace: bool ): """Loads data and writes it to the artifact if not already present. diff --git a/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/data/loader.py b/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/data/loader.py index 845f2a9..f109da5 100644 --- a/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/data/loader.py +++ b/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/data/loader.py @@ -12,8 +12,6 @@ No logging is done here. Logging is done in vivarium inputs itself and forwarded. """ -from typing import List, Optional, Union - import numpy as np import pandas as pd from gbd_mapping import causes, covariates, risk_factors @@ -29,7 +27,7 @@ def get_data( - lookup_key: str, location: str, years: Optional[Union[int, str, List[int]]] = None + lookup_key: str, location: str, years: int | str | list[int] | None = None ) -> pd.DataFrame: """Retrieves data from an appropriate source. @@ -66,7 +64,7 @@ def get_data( def load_population_location( - key: str, location: str, years: Optional[Union[int, str, List[int]]] = None + key: str, location: str, years: int | str | list[int] | None = None ) -> str: if key != data_keys.POPULATION.LOCATION: raise ValueError(f"Unrecognized key {key}") @@ -75,38 +73,38 @@ def load_population_location( def load_population_structure( - key: str, location: str, years: Optional[Union[int, str, List[int]]] = None + key: str, location: str, years: int | str | list[int] | None = None ) -> pd.DataFrame: return interface.get_population_structure(location, years) def load_age_bins( - key: str, location: str, years: Optional[Union[int, str, List[int]]] = None + key: str, location: str, years: int | str | list[int] | None = None ) -> pd.DataFrame: return interface.get_age_bins() def load_demographic_dimensions( - key: str, location: str, years: Optional[Union[int, str, List[int]]] = None + key: str, location: str, years: int | str | list[int] | None = None ) -> pd.DataFrame: return interface.get_demographic_dimensions(location, years) def load_theoretical_minimum_risk_life_expectancy( - key: str, location: str, years: Optional[Union[int, str, List[int]]] = None + key: str, location: str, years: int | str | list[int] | None = None ) -> pd.DataFrame: return interface.get_theoretical_minimum_risk_life_expectancy() def load_standard_data( - key: str, location: str, years: Optional[Union[int, str, List[int]]] = None + key: str, location: str, years: int | str | list[int] | None = None ) -> pd.DataFrame: key = EntityKey(key) entity = get_entity(key) return interface.get_measure(entity, key.measure, location, years).droplevel("location") -def load_metadata(key: str, location: str, years: Optional[Union[int, str, List[int]]] = None): +def load_metadata(key: str, location: str, years: int | str | list[int] | None = None): key = EntityKey(key) entity = get_entity(key) entity_metadata = entity[key.measure] @@ -115,7 +113,7 @@ def load_metadata(key: str, location: str, years: Optional[Union[int, str, List[ return entity_metadata -def load_categorical_paf(key: str, location: str, years: Optional[Union[int, str, List[int]]] = None) -> pd.DataFrame: +def load_categorical_paf(key: str, location: str, years: int | str | list[int] | None = None) -> pd.DataFrame: try: risk = { # todo add keys as needed @@ -163,7 +161,7 @@ def _load_em_from_meid(location, meid, measure): # TODO - add project-specific data functions here -def get_entity(key: Union[str, EntityKey]): +def get_entity(key: str | EntityKey): # Map of entity types to their gbd mappings. type_map = { "cause": causes, diff --git a/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/tools/cli.py b/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/tools/cli.py index ffd2b7e..88ada8b 100644 --- a/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/tools/cli.py +++ b/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/tools/cli.py @@ -1,5 +1,3 @@ -from typing import Optional, Tuple - import click from loguru import logger from vivarium.framework.utilities import handle_exceptions @@ -52,10 +50,10 @@ ) def make_artifacts( location: str, - years: Optional[str], + years: str | None, output_dir: str, append: bool, - replace_keys: Tuple[str, ...], + replace_keys: tuple[str, ...], verbose: int, with_debugger: bool, ) -> None: diff --git a/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/tools/make_artifacts.py b/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/tools/make_artifacts.py index 3aa35af..eaa5dfe 100644 --- a/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/tools/make_artifacts.py +++ b/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/tools/make_artifacts.py @@ -10,7 +10,6 @@ import sys import time from pathlib import Path -from typing import Optional, Tuple, Union import click from loguru import logger @@ -30,7 +29,7 @@ def running_from_cluster() -> bool: def check_for_existing( - output_dir: Path, location: str, append: bool, replace_keys: Tuple + output_dir: Path, location: str, append: bool, replace_keys: tuple ) -> None: existing_artifacts = set( [ @@ -66,7 +65,7 @@ def check_for_existing( def build_single( - location: str, years: Optional[str], output_dir: str, replace_keys: Tuple + location: str, years: str | None, output_dir: str, replace_keys: tuple ) -> None: path = Path(output_dir) / f"{sanitize_location(location)}.hdf" build_single_location_artifact(path, location, years, replace_keys) @@ -74,10 +73,10 @@ def build_single( def build_artifacts( location: str, - years: Optional[str], + years: str | None, output_dir: str, append: bool, - replace_keys: Tuple, + replace_keys: tuple, verbose: int, ) -> None: """Main application function for building artifacts. @@ -196,10 +195,10 @@ def build_all_artifacts(output_dir: Path, verbose: int) -> None: def build_single_location_artifact( - path: Union[str, Path], + path: str | Path, location: str, - years: Optional[str], - replace_keys: Tuple = (), + years: str | None, + replace_keys: tuple = (), log_to_file: bool = False, ) -> None: """Builds an artifact for a single location. diff --git a/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/utilities.py b/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/utilities.py index 6413baa..092c04e 100644 --- a/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/utilities.py +++ b/{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/utilities.py @@ -1,5 +1,4 @@ from pathlib import Path -from typing import List, Tuple, Union import click import numpy as np @@ -11,7 +10,7 @@ from {{cookiecutter.package_name}}.constants import metadata -SeededDistribution = Tuple[str, stats.rv_continuous] +SeededDistribution = tuple[str, stats.rv_continuous] def len_longest_location() -> int: @@ -42,7 +41,7 @@ def sanitize_location(location: str): return location.replace(" ", "_").replace("'", "_").lower() -def delete_if_exists(*paths: Union[Path, List[Path]], confirm=False): +def delete_if_exists(*paths: Path | list[Path], confirm=False): paths = paths[0] if isinstance(paths[0], list) else paths existing_paths = [p for p in paths if p.exists()] if existing_paths: @@ -86,7 +85,7 @@ def read_data_by_draw(artifact_path: str, key : str, draw: int) -> pd.DataFrame: def get_norm( mean: float, sd: float = None, - ninety_five_pct_confidence_interval: Tuple[float, float] = None + ninety_five_pct_confidence_interval: tuple[float, float] = None ) -> stats.norm: sd = _get_standard_deviation(mean, sd, ninety_five_pct_confidence_interval) return stats.norm(loc=mean, scale=sd) @@ -95,7 +94,7 @@ def get_norm( def get_truncnorm( mean: float, sd: float = None, - ninety_five_pct_confidence_interval: Tuple[float, float] = None, + ninety_five_pct_confidence_interval: tuple[float, float] = None, lower_clip: float = 0.0, upper_clip: float = 1.0 ) -> stats.norm: @@ -106,7 +105,7 @@ def get_truncnorm( def _get_standard_deviation( - mean: float, sd: float, ninety_five_pct_confidence_interval: Tuple[float, float] + mean: float, sd: float, ninety_five_pct_confidence_interval: tuple[float, float] ) -> float: if sd is None and ninety_five_pct_confidence_interval is None: raise ValueError("Must provide either a standard deviation or a 95% confidence interval.") @@ -127,7 +126,7 @@ def _get_standard_deviation( def get_lognorm_from_quantiles(median: float, lower: float, upper: float, - quantiles: Tuple[float, float] = (0.025, 0.975)) -> stats.lognorm: + quantiles: tuple[float, float] = (0.025, 0.975)) -> stats.lognorm: """Returns a frozen lognormal distribution with the specified median, such that (lower, upper) are approximately equal to the quantiles with ranks (quantile_ranks[0], quantile_ranks[1]).