3232from data_designer .config .utils .io_helpers import load_config_file , save_config_file
3333
3434
35+ class _PluginCatalogSourceUnavailableError (PluginCatalogError ):
36+ pass
37+
38+
39+ class _PluginCatalogContentError (PluginCatalogError ):
40+ pass
41+
42+
3543class PluginCatalogRepository (ConfigRepository [PluginCatalogRegistry ]):
3644 """Repository for plugin catalog aliases and cached catalog payloads."""
3745
@@ -139,7 +147,7 @@ def load_catalog(self, alias: str | None = None, *, refresh: bool = False) -> Pl
139147
140148 try :
141149 payload = self ._fetch_catalog_payload (catalog_config .url )
142- except ( PluginCatalogError , OSError , ValueError ) :
150+ except _PluginCatalogSourceUnavailableError :
143151 if not refresh :
144152 cached_catalog = self ._load_cached_catalog (catalog_config , require_fresh = False )
145153 if cached_catalog is not None :
@@ -257,7 +265,7 @@ def _normalize_catalog_url(url: str) -> str:
257265 segments = [segment for segment in parsed .path .split ("/" ) if segment ]
258266
259267 if hostname in {"github.com" , "www.github.com" } and len (segments ) >= 2 :
260- owner , repo = segments [0 ], segments [1 ]
268+ owner , repo = segments [0 ], segments [1 ]. removesuffix ( ".git" )
261269 if len (segments ) == 2 :
262270 return f"https://raw.githubusercontent.com/{ owner } /{ repo } /main/catalog/plugins.json"
263271 if len (segments ) >= 5 and segments [2 ] == "blob" :
@@ -289,21 +297,26 @@ def _catalog_plugins_url_path(catalog_root: str) -> str:
289297
290298def _fetch_local_catalog (location : str ) -> dict [str , object ]:
291299 path = Path (location ).expanduser ()
292- if not path .exists ():
293- raise PluginCatalogError (f"Plugin catalog file not found: { path } " )
294- if path .stat ().st_size > MAX_PLUGIN_CATALOG_SIZE_BYTES :
295- raise PluginCatalogError (
296- f"Plugin catalog at { path } exceeds maximum size of { MAX_PLUGIN_CATALOG_SIZE_BYTES } bytes"
297- )
300+ try :
301+ if not path .exists ():
302+ raise _PluginCatalogSourceUnavailableError (f"Plugin catalog file not found: { path } " )
303+ if path .stat ().st_size > MAX_PLUGIN_CATALOG_SIZE_BYTES :
304+ raise _PluginCatalogContentError (
305+ f"Plugin catalog at { path } exceeds maximum size of { MAX_PLUGIN_CATALOG_SIZE_BYTES } bytes"
306+ )
307+ except OSError as e :
308+ raise _PluginCatalogSourceUnavailableError (f"Failed to read plugin catalog file { path } : { e } " ) from e
298309
299310 try :
300311 with open (path ) as f :
301312 payload = json .load (f )
313+ except OSError as e :
314+ raise _PluginCatalogSourceUnavailableError (f"Failed to read plugin catalog file { path } : { e } " ) from e
302315 except json .JSONDecodeError as e :
303- raise PluginCatalogError (f"Failed to parse plugin catalog JSON at { path } : { e } " ) from e
316+ raise _PluginCatalogContentError (f"Failed to parse plugin catalog JSON at { path } : { e } " ) from e
304317
305318 if not isinstance (payload , dict ):
306- raise PluginCatalogError (f"Plugin catalog at { path } must be a JSON object" )
319+ raise _PluginCatalogContentError (f"Plugin catalog at { path } must be a JSON object" )
307320 return payload
308321
309322
@@ -313,27 +326,29 @@ def _fetch_remote_catalog(url: str) -> dict[str, object]:
313326 with urlopen (request , timeout = 10 ) as response :
314327 status = getattr (response , "status" , 200 )
315328 if isinstance (status , int ) and status >= 400 :
316- raise PluginCatalogError (f"Failed to fetch plugin catalog { url !r} : HTTP { status } " )
329+ raise _PluginCatalogSourceUnavailableError (f"Failed to fetch plugin catalog { url !r} : HTTP { status } " )
317330 # Read one byte past the limit so oversized chunked responses are
318331 # rejected without keeping the full response body in memory.
319332 content = response .read (MAX_PLUGIN_CATALOG_SIZE_BYTES + 1 )
320333 except HTTPError as e :
321- raise PluginCatalogError (f"Failed to fetch plugin catalog { url !r} : HTTP { e .code } " ) from e
334+ raise _PluginCatalogSourceUnavailableError (f"Failed to fetch plugin catalog { url !r} : HTTP { e .code } " ) from e
322335 except URLError as e :
323- raise PluginCatalogError (f"Failed to fetch plugin catalog { url !r} : { e .reason } " ) from e
336+ raise _PluginCatalogSourceUnavailableError (f"Failed to fetch plugin catalog { url !r} : { e .reason } " ) from e
337+ except OSError as e :
338+ raise _PluginCatalogSourceUnavailableError (f"Failed to read plugin catalog { url !r} : { e } " ) from e
324339
325340 if len (content ) > MAX_PLUGIN_CATALOG_SIZE_BYTES :
326- raise PluginCatalogError (
341+ raise _PluginCatalogContentError (
327342 f"Plugin catalog at { url !r} exceeds maximum size of { MAX_PLUGIN_CATALOG_SIZE_BYTES } bytes"
328343 )
329344
330345 try :
331346 payload = json .loads (content .decode ("utf-8" ))
332347 except (UnicodeDecodeError , json .JSONDecodeError ) as e :
333- raise PluginCatalogError (f"Failed to parse plugin catalog JSON at { url !r} : { e } " ) from e
348+ raise _PluginCatalogContentError (f"Failed to parse plugin catalog JSON at { url !r} : { e } " ) from e
334349
335350 if not isinstance (payload , dict ):
336- raise PluginCatalogError (f"Plugin catalog at { url !r} must be a JSON object" )
351+ raise _PluginCatalogContentError (f"Plugin catalog at { url !r} must be a JSON object" )
337352 return payload
338353
339354
0 commit comments