Repository: fatiando/pooch

Issue:
Local-only/strict mode
The problem I'm trying to solve is that for a CI build, if the PR is changing a file but neglects to update the hash in the registry, the behavior is to silently (with default logging) re-download the file from the remote source. If there are no other tests depending on the datafile (😱) then the PR passes CI, but will fail future builds (because the committed registry and datafile are out of sync).

Would there be any interest in adding a flag to disable updating the file? My hack right now is to (on my CI builds) set `base_url` to something like `'NODOWNLOAD:'`. This is both simply just terrible and also results in errors complaining about the URL protocol rather than the true source: the bad hash.

I'd be happy to take a shot at implementing, if someone can point me to what the best approach is for this here.

Discussion:
welcome[bot]: 💖 **Thank you for opening your first pull request in this repository!** 💖

A few things to keep in mind:

* [Authorship Guidelines](https://github.com/fatiando/community/blob/main/AUTHORSHIP.md): Our rules for giving you credit for your contributions, including authorship on publications and [Zenodo](https://zenodo.org/communities/fatiando) archives.
* [Code of Conduct](https://github.com/fatiando/community/blob/main/CODE_OF_CONDUCT.md): How we expect people to interact in our projects.

⭐ **No matter what, we are really grateful that you put in the effort to do this!** ⭐


Patch:
diff --git a/pooch/core.py b/pooch/core.py
index 6e29758..b9cb127 100644
--- a/pooch/core.py
+++ b/pooch/core.py
@@ -263,6 +263,7 @@ def create(
     registry=None,
     urls=None,
     retry_if_failed=0,
+    allow_updates=True,
 ):
     """
     Create a :class:`~pooch.Pooch` with sensible defaults to fetch data files.
@@ -327,6 +328,11 @@ def create(
         attempted once (``retry_if_failed=0``). Initially, will wait for 1s
         between retries and then increase the wait time by 1s with each retry
         until a maximum of 10s.
+    allow_updates : bool or str
+        Whether existing files in local storage that have a hash mismatch with
+        the registry are allowed to update from the remote URL. If a string,
+        this is the name of an environment variable that should be checked
+        for the value. Defaults to ``True``.
 
     Returns
     -------
@@ -414,6 +420,8 @@ def create(
     # to import at the same time (which would try to create the folder several
     # times at once).
     path = cache_location(path, env, version)
+    if isinstance(allow_updates, str):
+        allow_updates = os.environ.get(allow_updates, "true") != "false"
     pup = Pooch(
         path=path,
         base_url=base_url,
@@ -457,10 +465,22 @@ class Pooch:
         attempted once (``retry_if_failed=0``). Initially, will wait for 1s
         between retries and then increase the wait time by 1s with each retry
         until a maximum of 10s.
+    allow_updates : bool
+        Whether existing files in local storage that have a hash mismatch with
+        the registry are allowed to update from the remote URL. Defaults to
+        ``True``.
 
     """
 
-    def __init__(self, path, base_url, registry=None, urls=None, retry_if_failed=0):
+    def __init__(
+        self,
+        path,
+        base_url,
+        registry=None,
+        urls=None,
+        retry_if_failed=0,
+        allow_updates=True,
+    ):
         self.path = path
         self.base_url = base_url
         if registry is None:
@@ -470,6 +490,7 @@ class Pooch:
             urls = dict()
         self.urls = dict(urls)
         self.retry_if_failed = retry_if_failed
+        self.allow_updates = allow_updates
 
     @property
     def abspath(self):
@@ -542,6 +563,11 @@ class Pooch:
         known_hash = self.registry[fname]
         action, verb = download_action(full_path, known_hash)
 
+        if action == "update" and not self.allow_updates:
+            raise ValueError(
+                f"{fname} needs to update {full_path} but updates are disallowed."
+            )
+
         if action in ("download", "update"):
             get_logger().info(
                 "%s file '%s' from '%s' to '%s'.",


Test patch:
diff --git a/pooch/tests/test_core.py b/pooch/tests/test_core.py
index 7000736..b1686a5 100644
--- a/pooch/tests/test_core.py
+++ b/pooch/tests/test_core.py
@@ -328,6 +328,26 @@ def test_pooch_update():
             assert log_file.getvalue() == ""
 
 
+def test_pooch_update_disallowed():
+    "Test that disallowing updates works."
+    with TemporaryDirectory() as local_store:
+        path = Path(local_store)
+        # Create a dummy version of tiny-data.txt that is different from the
+        # one in the remote storage
+        true_path = str(path / "tiny-data.txt")
+        with open(true_path, "w") as fin:
+            fin.write("different data")
+        # Setup a pooch in a temp dir
+        pup = Pooch(
+            path=path,
+            base_url=BASEURL,
+            registry=REGISTRY,
+            allow_updates=False,
+        )
+        with pytest.raises(ValueError):
+            pup.fetch("tiny-data.txt")
+
+
 @pytest.mark.network
 def test_pooch_corrupted(data_dir_mirror):
     "Raise an exception if the file hash doesn't match the registry"