diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cdb31c2b4..a9a00ea1b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,10 +14,13 @@ Bug fixes ^^^^^^^^^ * Fixed pickling issue with ``xclim.sdba.Grouper`` and other classes for usage with `dask>=2024.11`. (:issue:`1992`, :pull:`1993`). * Fixed an issue with ``nimbus`` that was causing URL path components to be improperly joined. (:pull:`1997`). +* `base_kws_vars` in `MBCn` is now copied inside the `adjust` function so that in-place changes do not change the dict globally. (:pull:`1999`). +* Fixed a bug in the logic of ``xclim.testing.utils.load_registry`` that impacted the ability to load a `registry.txt` from a non-default repository. (:pull:`2001`). Internal changes ^^^^^^^^^^^^^^^^ -* Changed French translations with word "pluvieux" to "avec précipitations". (:issue:`1960`, :pull:`1994`). +* Changed french translations with word "pluvieux" to "avec précipitations". (:issue:`1960`, :pull:`1994`). +* In order to address 403 (forbidden) request errors when retrieving data from GitHub via ReadTheDocs, the ``nimbus`` class has been modified to use an overloaded `fetch` method that appends a User-Agent header to the request. (:pull:`2001`). * Addressed a very rare race condition that can happen if `pytest` is tearing down the test environment when running across multiple workers. (:pull:`1863`). CI changes diff --git a/CI/requirements_ci.in b/CI/requirements_ci.in index b95fdf60e..d4c6ec4ab 100644 --- a/CI/requirements_ci.in +++ b/CI/requirements_ci.in @@ -1,5 +1,5 @@ bump-my-version==0.28.1 -deptry==0.21.0 +deptry==0.21.1 flit==3.10.1 pip==24.3.1 pylint==3.3.1 diff --git a/pyproject.toml b/pyproject.toml index f4029a8db..b34efcc0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ dev = [ "codespell ==2.3.0", "coverage[toml] >=7.5.0", "coveralls >=4.0.1", # coveralls is not yet compatible with Python 3.13 - "deptry ==0.21.0", + "deptry ==0.21.1", "flake8 >=7.1.1", "flake8-rst-docstrings >=0.3.0", "h5netcdf>=1.3.0", @@ -134,7 +134,7 @@ target-version = [ ] [tool.bumpversion] -current_version = "0.53.3-dev.2" +current_version = "0.53.3-dev.4" commit = true commit_args = "--no-verify" tag = false diff --git a/xclim/__init__.py b/xclim/__init__.py index 9010e312d..c4a9153d9 100644 --- a/xclim/__init__.py +++ b/xclim/__init__.py @@ -13,7 +13,7 @@ __author__ = """Travis Logan""" __email__ = "logan.travis@ouranos.ca" -__version__ = "0.53.3-dev.2" +__version__ = "0.53.3-dev.4" with _resources.as_file(_resources.files("xclim.data")) as _module_data: diff --git a/xclim/sdba/adjustment.py b/xclim/sdba/adjustment.py index 5c8506071..da5ab54fe 100644 --- a/xclim/sdba/adjustment.py +++ b/xclim/sdba/adjustment.py @@ -5,6 +5,7 @@ """ from __future__ import annotations +from copy import deepcopy from importlib.util import find_spec from inspect import signature from typing import Any @@ -1818,7 +1819,7 @@ def _adjust( period_dim=None, ): # set default values for non-specified parameters - base_kws_vars = base_kws_vars or {} + base_kws_vars = {} if base_kws_vars is None else deepcopy(base_kws_vars) pts_dim = self.pts_dims[0] for v in sim[pts_dim].values: base_kws_vars.setdefault(v, {}) diff --git a/xclim/testing/utils.py b/xclim/testing/utils.py index 25a626bd7..1969b6519 100644 --- a/xclim/testing/utils.py +++ b/xclim/testing/utils.py @@ -13,13 +13,14 @@ import sys import time import warnings -from collections.abc import Sequence +from collections.abc import Callable, Sequence from datetime import datetime as dt +from functools import wraps from importlib import import_module from io import StringIO from pathlib import Path from shutil import copytree -from typing import TextIO +from typing import IO, TextIO from urllib.error import HTTPError, URLError from urllib.parse import urljoin, urlparse from urllib.request import urlretrieve @@ -435,6 +436,8 @@ def load_registry( dict Dictionary of filenames and hashes. """ + if not repo.endswith("/"): + repo = f"{repo}/" remote_registry = audit_url( urljoin( urljoin(repo, branch if branch.endswith("/") else f"{branch}/"), @@ -442,7 +445,19 @@ def load_registry( ) ) - if branch != default_testdata_version: + if repo != default_testdata_repo_url: + external_repo_name = urlparse(repo).path.split("/")[-2] + external_branch_name = branch.split("/")[-1] + registry_file = Path( + str( + ilr.files("xclim").joinpath( + f"testing/registry.{external_repo_name}.{external_branch_name}.txt" + ) + ) + ) + urlretrieve(remote_registry, registry_file) # noqa: S310 + + elif branch != default_testdata_version: custom_registry_folder = Path( str(ilr.files("xclim").joinpath(f"testing/{branch}")) ) @@ -450,11 +465,9 @@ def load_registry( registry_file = custom_registry_folder.joinpath("registry.txt") urlretrieve(remote_registry, registry_file) # noqa: S310 - elif repo != default_testdata_repo_url: + else: registry_file = Path(str(ilr.files("xclim").joinpath("testing/registry.txt"))) - urlretrieve(remote_registry, registry_file) # noqa: S310 - registry_file = Path(str(ilr.files("xclim").joinpath("testing/registry.txt"))) if not registry_file.exists(): raise FileNotFoundError(f"Registry file not found: {registry_file}") @@ -516,10 +529,13 @@ def nimbus( # noqa: PR01 "The `pooch` package is required to fetch the xclim testing data. " "You can install it with `pip install pooch` or `pip install xclim[dev]`." ) + if not repo.endswith("/"): + repo = f"{repo}/" remote = audit_url( urljoin(urljoin(repo, branch if branch.endswith("/") else f"{branch}/"), "data") ) - return pooch.create( + + _nimbus = pooch.create( path=cache_dir, base_url=remote, version=default_testdata_version, @@ -528,6 +544,35 @@ def nimbus( # noqa: PR01 registry=load_registry(branch=branch, repo=repo), ) + # Add a custom fetch method to the Pooch instance + # Needed to address: https://github.com/readthedocs/readthedocs.org/issues/11763 + # Fix inspired by @bjlittle (https://github.com/bjlittle/geovista/pull/1202) + _nimbus.fetch_diversion = _nimbus.fetch + + # Overload the fetch method to add user-agent headers + @wraps(_nimbus.fetch_diversion) + def _fetch(*args: str, **kwargs: bool | Callable) -> str: # numpydoc ignore=GL08 + + def _downloader( + url: str, + output_file: str | IO, + poocher: pooch.Pooch, + check_only: bool | None = False, + ) -> None: + """Download the file from the URL and save it to the save_path.""" + headers = {"User-Agent": f"xclim ({__xclim_version__})"} + downloader = pooch.HTTPDownloader(headers=headers) + return downloader(url, output_file, poocher, check_only=check_only) + + # default to our http/s downloader with user-agent headers + kwargs.setdefault("downloader", _downloader) + return _nimbus.fetch_diversion(*args, **kwargs) + + # Replace the fetch method with the custom fetch method + _nimbus.fetch = _fetch + + return _nimbus + # idea copied from raven that it borrowed from xclim that borrowed it from xarray that was borrowed from Seaborn def open_dataset(