|
1 | 1 | from __future__ import annotations
|
2 | 2 |
|
3 | 3 | import contextlib
|
| 4 | +import hashlib |
4 | 5 | import io
|
5 | 6 | import os
|
6 | 7 | import platform
|
@@ -33,7 +34,7 @@ def _base_cache_dir() -> str | None:
|
33 | 34 | return cache_dir
|
34 | 35 |
|
35 | 36 |
|
36 |
| -def _resolve_cache_dir(dirname: str = "downloads") -> str | None: |
| 37 | +def _resolve_cache_dir(dirname: str) -> str | None: |
37 | 38 | cache_dir = _base_cache_dir()
|
38 | 39 | if cache_dir:
|
39 | 40 | cache_dir = os.path.join(cache_dir, "check_jsonschema", dirname)
|
@@ -95,18 +96,32 @@ def _cache_hit(cachefile: str, response: requests.Response) -> bool:
|
95 | 96 | return local_mtime >= remote_mtime
|
96 | 97 |
|
97 | 98 |
|
| 99 | +def url_to_cache_filename(ref_url: str) -> str: |
| 100 | + """ |
| 101 | + Given a schema URL, convert it to a filename for caching in a cache dir. |
| 102 | +
|
| 103 | + Rules are as follows: |
| 104 | + - the base filename is an sha256 hash of the URL |
| 105 | + - if the filename ends in an extension (.json, .yaml, etc) that extension |
| 106 | + is appended to the hash |
| 107 | +
|
| 108 | + Preserving file extensions preserves the extension-based logic used for parsing, and |
| 109 | + it also helps a local editor (browsing the cache) identify filetypes. |
| 110 | + """ |
| 111 | + filename = hashlib.sha256(ref_url.encode()).hexdigest() |
| 112 | + if "." in (last_part := ref_url.rpartition("/")[-1]): |
| 113 | + _, _, extension = last_part.rpartition(".") |
| 114 | + filename = f"{filename}.{extension}" |
| 115 | + return filename |
| 116 | + |
| 117 | + |
98 | 118 | class FailedDownloadError(Exception):
|
99 | 119 | pass
|
100 | 120 |
|
101 | 121 |
|
102 | 122 | class CacheDownloader:
|
103 |
| - def __init__( |
104 |
| - self, cache_dir: str | None = None, disable_cache: bool = False |
105 |
| - ) -> None: |
106 |
| - if cache_dir is None: |
107 |
| - self._cache_dir = _resolve_cache_dir() |
108 |
| - else: |
109 |
| - self._cache_dir = _resolve_cache_dir(cache_dir) |
| 123 | + def __init__(self, cache_dir: str, *, disable_cache: bool = False) -> None: |
| 124 | + self._cache_dir = _resolve_cache_dir(cache_dir) |
110 | 125 | self._disable_cache = disable_cache
|
111 | 126 |
|
112 | 127 | def _download(
|
@@ -160,21 +175,21 @@ def bind(
|
160 | 175 | validation_callback: t.Callable[[bytes], t.Any] | None = None,
|
161 | 176 | ) -> BoundCacheDownloader:
|
162 | 177 | return BoundCacheDownloader(
|
163 |
| - file_url, filename, self, validation_callback=validation_callback |
| 178 | + file_url, self, filename=filename, validation_callback=validation_callback |
164 | 179 | )
|
165 | 180 |
|
166 | 181 |
|
167 | 182 | class BoundCacheDownloader:
|
168 | 183 | def __init__(
|
169 | 184 | self,
|
170 | 185 | file_url: str,
|
171 |
| - filename: str | None, |
172 | 186 | downloader: CacheDownloader,
|
173 | 187 | *,
|
| 188 | + filename: str | None = None, |
174 | 189 | validation_callback: t.Callable[[bytes], t.Any] | None = None,
|
175 | 190 | ) -> None:
|
176 | 191 | self._file_url = file_url
|
177 |
| - self._filename = filename or file_url.split("/")[-1] |
| 192 | + self._filename = filename or url_to_cache_filename(file_url) |
178 | 193 | self._downloader = downloader
|
179 | 194 | self._validation_callback = validation_callback
|
180 | 195 |
|
|
0 commit comments