diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9ae68a..6df00fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] os: [macos, ubuntu, windows] steps: - uses: actions/checkout@v4 diff --git a/ci/environment.yml b/ci/environment.yml index d4b3e06..0f8a0e5 100644 --- a/ci/environment.yml +++ b/ci/environment.yml @@ -3,7 +3,7 @@ channels: - conda-forge - nodefaults dependencies: - - python>=3.9 + - python>=3.10 - pandas - dask - xarray diff --git a/intake_esgf/base.py b/intake_esgf/base.py index 0900f77..e826d9c 100644 --- a/intake_esgf/base.py +++ b/intake_esgf/base.py @@ -5,7 +5,7 @@ import time from functools import partial from pathlib import Path -from typing import Any, Union +from typing import Any import pandas as pd import requests @@ -59,7 +59,7 @@ def combine_results(dfs: list[pd.DataFrame]) -> pd.DataFrame: return df -def get_file_hash(filepath: Union[str, Path], algorithm: str) -> str: +def get_file_hash(filepath: str | Path, algorithm: str) -> str: """Get the file has using the given algorithm.""" algorithm = algorithm.lower() assert algorithm in hashlib.algorithms_available @@ -75,7 +75,7 @@ def get_file_hash(filepath: Union[str, Path], algorithm: str) -> str: def download_and_verify( url: str, - local_file: Union[str, Path], + local_file: str | Path, hash: str, hash_algorithm: str, content_length: int, @@ -126,7 +126,7 @@ def parallel_download( info: dict[str, Any], local_cache: list[Path], download_db: Path, - esg_dataroot: Union[None, list[Path]] = None, + esg_dataroot: None | list[Path] = None, ): """.""" logger = intake_esgf.conf.get_logger() @@ -168,7 +168,7 @@ def parallel_download( def get_search_criteria( - ds: xr.Dataset, project_id: Union[str, None] = None + ds: xr.Dataset, project_id: str | None = None ) -> dict[str, str]: """Return a dictionary of facet information from the dataset attributes.""" if "project" in ds.attrs: @@ -282,7 +282,7 @@ def add_cell_measures(ds: xr.Dataset, catalog) -> xr.Dataset: return ds -def get_cell_measure(var: str, ds: xr.Dataset) -> Union[xr.DataArray, None]: +def get_cell_measure(var: str, ds: xr.Dataset) -> xr.DataArray | None: """Return the dataarray of the measures required by the given var. This routine will examine the `cell_measures` attribute of the specified `var` as diff --git a/intake_esgf/catalog.py b/intake_esgf/catalog.py index 3310277..e7fd9e3 100644 --- a/intake_esgf/catalog.py +++ b/intake_esgf/catalog.py @@ -3,10 +3,11 @@ import re import time import warnings +from collections.abc import Callable from functools import partial from multiprocessing.pool import ThreadPool from pathlib import Path -from typing import Callable, Literal, Union +from typing import Literal, Union import pandas as pd import requests @@ -202,7 +203,7 @@ def _extract_int_pattern(sample: str) -> str: .iloc[:, 0] ) - def search(self, quiet: bool = False, **search: Union[str, list[str]]): + def search(self, quiet: bool = False, **search: str | list[str]): """Populate the catalog by specifying search facets and values. Parameters @@ -287,9 +288,7 @@ def _search(index): self.last_search = search return self - def from_tracking_ids( - self, tracking_ids: Union[str, list[str]], quiet: bool = False - ): + def from_tracking_ids(self, tracking_ids: str | list[str], quiet: bool = False): """Populate the catalog by speciying tracking ids. While tracking_ids should uniquely define individual files, we observe that some @@ -497,8 +496,8 @@ def _move_data( self, infos, num_threads, - globus_endpoint: Union[str, None] = None, - globus_path: Union[Path, None] = None, + globus_endpoint: str | None = None, + globus_path: Path | None = None, ): """Move data either by https or globus transfers.""" logger = intake_esgf.conf.get_logger() @@ -594,7 +593,7 @@ def _find_local_file(info): def to_dataset_dict( self, minimal_keys: bool = True, - ignore_facets: Union[None, str, list[str]] = None, + ignore_facets: None | str | list[str] = None, separator: str = ".", num_threads: int = 6, quiet: bool = False, diff --git a/intake_esgf/config.py b/intake_esgf/config.py index f4f8be9..d4a862e 100644 --- a/intake_esgf/config.py +++ b/intake_esgf/config.py @@ -4,7 +4,6 @@ import copy import logging from pathlib import Path -from typing import Union import yaml @@ -39,7 +38,7 @@ class Config(dict): """A global configuration object used in the package.""" - def __init__(self, filename: Union[Path, None] = None, **kwargs): + def __init__(self, filename: Path | None = None, **kwargs): self.filename = ( Path(filename) if filename is not None @@ -58,7 +57,7 @@ def reset(self): self.clear() self.update(copy.deepcopy(defaults)) - def save(self, filename: Union[Path, None] = None): + def save(self, filename: Path | None = None): """Save current configuration to file as YAML.""" filename = filename or self.filename filename.parent.mkdir(parents=True, exist_ok=True) @@ -76,9 +75,9 @@ def set( *, indices: dict[str, bool] = {}, all_indices: bool = False, - esg_dataroot: Union[list[str], None] = None, - local_cache: Union[list[str], None] = None, - additional_df_cols: Union[list[str], None] = None + esg_dataroot: list[str] | None = None, + local_cache: list[str] | None = None, + additional_df_cols: list[str] | None = None ): """Change intake-esgf configuration options. @@ -156,7 +155,7 @@ def reload_all(self): self.reset() self.load() - def load(self, filename: Union[Path, None] = None): + def load(self, filename: Path | None = None): """Update global config from YAML file or default file if None.""" filename = filename or self.filename if filename.is_file(): diff --git a/intake_esgf/core/globus.py b/intake_esgf/core/globus.py index b5eb8dd..09fffec 100644 --- a/intake_esgf/core/globus.py +++ b/intake_esgf/core/globus.py @@ -3,7 +3,7 @@ import time from datetime import datetime from pathlib import Path -from typing import Any, Union +from typing import Any import pandas as pd from globus_sdk import ( @@ -39,7 +39,7 @@ def __init__(self, index_id="anl-dev"): def __repr__(self): return self.repr - def search(self, **search: Union[str, list[str]]) -> pd.DataFrame: + def search(self, **search: str | list[str]) -> pd.DataFrame: """Search the index and return as a pandas dataframe. This function uses the Globus `post_search()` function where our query consists diff --git a/intake_esgf/core/solr.py b/intake_esgf/core/solr.py index 68d5d67..479dcb0 100644 --- a/intake_esgf/core/solr.py +++ b/intake_esgf/core/solr.py @@ -1,7 +1,7 @@ """A ESGF1 Solr index class.""" import time -from typing import Any, Union +from typing import Any import pandas as pd import requests @@ -43,7 +43,7 @@ def __init__(self, index_node: str = "esgf-node.llnl.gov", distrib: bool = False def __repr__(self): return self.repr - def search(self, **search: Union[str, list[str]]) -> pd.DataFrame: + def search(self, **search: str | list[str]) -> pd.DataFrame: search["distrib"] = search["distrib"] if "distrib" in search else self.distrib facets = get_project_facets(search) + intake_esgf.conf.get( "additional_df_cols", [] diff --git a/intake_esgf/projects.py b/intake_esgf/projects.py index 4ecd3fa..822eee9 100644 --- a/intake_esgf/projects.py +++ b/intake_esgf/projects.py @@ -1,7 +1,6 @@ """Supported projects and their facet definitions.""" from abc import ABC, abstractmethod -from typing import Union from intake_esgf.exceptions import ProjectNotSupported @@ -254,7 +253,7 @@ def grid_facet(self) -> str: projects = {"cmip6": CMIP6(), "cmip5": CMIP5(), "cmip3": CMIP3()} -def get_project_facets(content: dict[str, Union[str, list[str]]]) -> list[str]: +def get_project_facets(content: dict[str, str | list[str]]) -> list[str]: """ Return the facets for the project found defined in the given content. @@ -280,7 +279,7 @@ def get_project_facets(content: dict[str, Union[str, list[str]]]) -> list[str]: return project.id_facets() -def get_likely_project(facets: Union[list, dict]) -> str: +def get_likely_project(facets: list | dict) -> str: """ Return the project which is likely to correspond to the given facets. diff --git a/pyproject.toml b/pyproject.toml index ae40701..06a9884 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ addopts = "--cov=intake_esgf --cov-report=xml --verbose" omit = ["*/intake_esgf/tests/*"] [tool.ruff] -target-version = "py39" +target-version = "py310" ignore = [ "E501", # line too long - let black worry about that ] diff --git a/setup.cfg b/setup.cfg index 79c126a..b62f59c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,13 +11,12 @@ classifiers = License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python :: 3 - Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Intended Audience :: Science/Research Topic :: Scientific/Engineering -requires-python = ">=3.9" +requires-python = ">=3.10" [options] install_requires =