# Copyright (c) 2021, GeoVista Contributors.
#
# This file is part of GeoVista and is distributed under the 3-Clause BSD license.
# See the LICENSE file in the package root directory for licensing details.
"""Convenience functions to access, download and cache :mod:`geovista` resources.
Notes
-----
.. versionadded:: 0.1.0
"""
from __future__ import annotations
from functools import wraps
import os
from pathlib import Path
import stat
from typing import TYPE_CHECKING, Any
import pooch
from geovista.config import resources
__all__ = [
"BASE_URL",
"CACHE",
"DATA_VERSION",
"GEOVISTA_CACHEDIR",
"GEOVISTA_DATA_VERSION",
"GEOVISTA_POOCH_MUTE",
"RETRY_ATTEMPTS",
"pooch_mute",
"reload_registry",
]
if TYPE_CHECKING:
from collections.abc import Callable
type FileLike = str | os.PathLike[str]
"""Type alias for filename or file-like object."""
BASE_URL: str = "https://github.com/bjlittle/geovista-data/raw/{version}/assets/"
"""Base URL for :mod:`geovista` resources."""
DATA_VERSION: str = "2026.05.2"
"""The ``geovista-data`` repository version for :mod:`geovista` resources."""
GEOVISTA_CACHEDIR: str = "GEOVISTA_CACHEDIR"
"""Environment variable to override :mod:`pooch` cache manager path."""
GEOVISTA_DATA_VERSION: str = os.environ.get("GEOVISTA_DATA_VERSION", DATA_VERSION)
"""Environment variable to override default :attr:`DATA_VERSION`."""
READ_MODE: int = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
"""Enable all read permission mode bits."""
RETRY_ATTEMPTS: int = 3
"""The number of retry attempts to download a resource."""
CACHE: pooch.Pooch = pooch.create(
path=resources["cache_dir"],
base_url=BASE_URL,
version=GEOVISTA_DATA_VERSION,
version_dev="main",
registry=None,
retry_if_failed=RETRY_ATTEMPTS,
env=GEOVISTA_CACHEDIR,
)
"""Cache manager for :mod:`geovista` resources."""
GEOVISTA_POOCH_MUTE: bool = (
os.environ.get("GEOVISTA_POOCH_MUTE", "false").lower() == "true"
)
"""Verbosity status of the :mod:`pooch` cache manager logger."""
# configure the cache with the registry
with (Path(__file__).parent / "registry.txt").open(
"r", encoding="utf-8", errors="strict"
) as text_io:
CACHE.load_registry(text_io)
# maintain the original Pooch.fetch method prior to wrapping
# with user-agent headers version
CACHE._fetch = CACHE.fetch # noqa: SLF001
@wraps(CACHE._fetch) # noqa: SLF001
def _fetch(
*args: str, **kwargs: bool | Callable[..., Any]
) -> str: # numpydoc ignore=GL08
# default to our http/s downloader with user-agent headers
kwargs.setdefault("downloader", _downloader)
result: str = CACHE._fetch(*args, **kwargs) # noqa: SLF001
return result
# override the original Pooch.fetch method with our
# user-agent headers version
CACHE.fetch = _fetch
def _downloader(
url: str,
output_file: FileLike,
poocher: pooch.Pooch,
/,
*,
check_only: bool | None = False,
) -> bool | None:
"""Download the `url` asset over HTTP/S to the target file.
Uses :func:`requests.get` with ``User-Agent`` configured
within the request headers.
Parameters
----------
url : str
The URL of the asset to be downloaded.
output_file : FileLike
The target file for the asset.
poocher : Pooch
The :class:`~pooch.Pooch` instance requesting the asset download.
check_only : bool, optional
If ``True``, will only check if the asset exists on the server
without downloading it. Will return ``True`` if the asset exists
and ``False`` otherwise.
Returns
-------
bool or None
If `check_only` is ``True``, returns a boolean indicating whether
the requested asset is available. Otherwise, returns ``None``.
Notes
-----
.. versionadded:: 0.6.0
"""
import geovista # noqa: PLC0415
# see https://github.com/readthedocs/readthedocs.org/issues/11763
headers = {"User-Agent": f"geovista ({geovista.__version__})"}
downloader: Callable[..., bool | None] = pooch.HTTPDownloader(headers=headers)
result = downloader(url, output_file, poocher, check_only=check_only)
if not check_only:
# perform best endevours to make downloaded asset community readable
try:
asset = Path(output_file)
# get current mode
mode = asset.stat().st_mode
# compute desired mode by OR-ing read bits for user, group, other
expected = mode | READ_MODE
if mode != expected:
asset.chmod(expected)
except (FileNotFoundError, OSError, PermissionError, ValueError):
pass
return result
[ドキュメント]
def pooch_mute(*, silent: bool | None = None) -> bool:
"""Control the :mod:`pooch` cache manager logger verbosity.
Updates the status variable :data:`GEOVISTA_POOCH_MUTE`.
Parameters
----------
silent : bool, optional
Whether to silence or activate the :mod:`pooch` cache manager logger messages
to the console. Defaults to ``True``.
Returns
-------
bool
The previous value of :data:`GEOVISTA_POOCH_MUTE`.
Notes
-----
.. versionadded:: 0.5.0
"""
global GEOVISTA_POOCH_MUTE # noqa: PLW0603
if silent is None:
silent = True
level = "WARNING" if silent else "NOTSET"
pooch.utils.get_logger().setLevel(level)
original = GEOVISTA_POOCH_MUTE
GEOVISTA_POOCH_MUTE = silent
return original
[ドキュメント]
def reload_registry(fname: str | None = None) -> None:
"""Refresh the registry of the :data:`CACHE`.
See :meth:`pooch.Pooch.load_registry` for more details.
Parameters
----------
fname : str, optional
The filename of the registry to be loaded. If ``None``, defaults to
the ``cache/registry.txt`` resource file packaged with :mod:`geovista`.
Notes
-----
.. versionadded:: 0.1.0
"""
if fname is None:
text_io = (Path(__file__).parent / "registry.txt").open(
"r", encoding="utf-8", errors="strict"
)
CACHE.load_registry(text_io)
# configure the pooch cache manager logger verbosity
pooch_mute(silent=GEOVISTA_POOCH_MUTE)