Repository: john-kurkowski/tldextract

Issue:
"update" caches public suffix list to wrong directory
Hi!

First off, I love `tldextract`, thanks for building it!

The way we use `tldextract` is slightly special, but used to be fully supported by the public API. Our docker containers don't have internet access, so when we build them, we cache the latest public suffix list. When our applications use `tldextract`, we configure it so that it uses the cache, and never needs an internet connection.
Upon upgrading to any 3.* version of `tldextract`, I noticed that the cache was no longer being used to look up information from the public suffix list.

Problem reproduction steps
----------------------------
First, run the command: `tldextract --update --private_domains`
Then create a basic test file:
```python
import os
from tldextract import TLDExtract

extractor = TLDExtract(cache_dir=os.environ["TLDEXTRACT_CACHE"])
extractor("www.google.com")
```

Now, create a conditional breakpoint [here](https://github.com/john-kurkowski/tldextract/blob/master/tldextract/cache.py#L93), where the condition is that `namespace` equals `publicsuffix.org-tlds`.

### Expected behaviour

When running the above program, the break point should be hit, but should not throw a `KeyError`.

### Actual behaviour

The breakpoint is hit once during the `__call__(…)`, and immediately throws a `KeyError` because it can't find the cache file.

Explanation
------------

The method `run_and_cache` accepts a namespace, which is used to calculate the cache file path. But when the file is downloaded, it uses the hardcoded namespace "urls", which places the file in the wrong location.

I'll write a PR that fixes this problem.

Discussion:

Patch:
diff --git a/tldextract/cache.py b/tldextract/cache.py
index b714e77..fbd02c9 100644
--- a/tldextract/cache.py
+++ b/tldextract/cache.py
@@ -7,7 +7,7 @@ import os
 import os.path
 import sys
 from hashlib import md5
-from typing import Callable, Dict, Hashable, List, Optional, TypeVar, Union
+from typing import Callable, Dict, Hashable, Iterable, Optional, TypeVar, Union
 
 from filelock import FileLock
 import requests
@@ -166,7 +166,7 @@ class DiskCache:
         func: Callable[..., T],
         namespace: str,
         kwargs: Dict[str, Hashable],
-        hashed_argnames: List[str],
+        hashed_argnames: Iterable[str],
     ) -> T:
         """Get a url but cache the response"""
         if not self.enabled:
@@ -203,7 +203,7 @@ class DiskCache:
                 result: T = self.get(namespace=namespace, key=key_args)
             except KeyError:
                 result = func(**kwargs)
-                self.set(namespace="urls", key=key_args, value=result)
+                self.set(namespace=namespace, key=key_args, value=result)
 
             return result
 


Test patch:
diff --git a/tests/test_cache.py b/tests/test_cache.py
index cb82f4f..3ae30d3 100644
--- a/tests/test_cache.py
+++ b/tests/test_cache.py
@@ -2,7 +2,8 @@
 import os.path
 import sys
 import types
-from typing import Any, cast
+from typing import Any, Dict, Hashable, cast
+from unittest.mock import Mock
 
 import pytest
 import tldextract.cache
@@ -72,3 +73,29 @@ def test_get_cache_dir(monkeypatch):
     monkeypatch.setenv("TLDEXTRACT_CACHE", "/alt-tld-cache")
 
     assert get_cache_dir() == "/alt-tld-cache"
+
+
+def test_run_and_cache(tmpdir):
+    cache = DiskCache(tmpdir)
+
+    return_value1 = "unique return value"
+    some_fn = Mock(return_value=return_value1)
+    kwargs1: Dict[str, Hashable] = {"value": 1}
+
+    assert some_fn.call_count == 0
+
+    call1 = cache.run_and_cache(some_fn, "test_namespace", kwargs1, kwargs1.keys())
+    assert call1 == return_value1
+    assert some_fn.call_count == 1
+
+    call2 = cache.run_and_cache(some_fn, "test_namespace", kwargs1, kwargs1.keys())
+    assert call2 == return_value1
+    assert some_fn.call_count == 1
+
+    kwargs2: Dict[str, Hashable] = {"value": 2}
+    return_value2 = "another return value"
+    some_fn.return_value = return_value2
+
+    call3 = cache.run_and_cache(some_fn, "test_namespace", kwargs2, kwargs2.keys())
+    assert call3 == return_value2
+    assert some_fn.call_count == 2