1010from collections import deque
1111from datetime import datetime
1212from threading import Condition , Lock
13- from typing import Any , Callable , Deque , Dict , List , Optional , TYPE_CHECKING , Tuple
13+ from typing import Any , Callable , Deque , Dict , List , Optional , TYPE_CHECKING , Tuple , Union
1414from urllib .parse import quote as urlescape
1515
1616import requests
17+ from lxml .etree import ElementTree
1718from requests .adapters import Response
1819from pydantic import BaseModel , Field
1920
@@ -159,11 +160,11 @@ def to_dict(self) -> Dict[str, Any]:
159160class Resource (Watchable ):
160161 def __init__ (self , url : Optional [str ], opts : ResourceOpts ):
161162 super ().__init__ ()
162- self .url : str = url
163- self .opts = opts
164- self .t = None
165- self .type = "text/plain"
166- self .etag = None
163+ self .url : Optional [ str ] = url
164+ self .opts : ResourceOpts = opts
165+ self .t : Optional [ ElementTree ] = None
166+ self .type : str = "text/plain"
167+ self .etag : Optional [ str ] = None
167168 self .expire_time : Optional [datetime ] = None
168169 self .never_expires : bool = False
169170 self .last_seen : Optional [datetime ] = None
@@ -304,12 +305,15 @@ def errors(self):
304305 else :
305306 return []
306307
307- def load_backup (self ):
308+ def load_backup (self ) -> Optional [ str ] :
308309 if config .local_copy_dir is None :
309310 return None
310311
311312 try :
312- return resource_string (self .local_copy_fn )
313+ res = resource_string (self .local_copy_fn )
314+ if isinstance (res , bytes ):
315+ return res .decode ('utf-8' )
316+ return res
313317 except IOError as ex :
314318 log .warning (
315319 "Caught an exception trying to load local backup for {} via {}: {}" .format (
@@ -318,7 +322,7 @@ def load_backup(self):
318322 )
319323 return None
320324
321- def save_backup (self , data ) :
325+ def save_backup (self , data : Optional [ str ]) -> None :
322326 if config .local_copy_dir is not None :
323327 try :
324328 safe_write (self .local_copy_fn , data , True )
@@ -332,6 +336,10 @@ def load_resource(self, getter: Callable[[str], Response]) -> Tuple[Optional[str
332336
333337 log .debug ("Loading resource {}" .format (self .url ))
334338
339+ if not self .url :
340+ log .error (f'No URL for resource { self } ' )
341+ return data , status , info
342+
335343 try :
336344 r = getter (self .url )
337345
@@ -346,7 +354,10 @@ def load_resource(self, getter: Callable[[str], Response]) -> Tuple[Optional[str
346354
347355 if r .ok :
348356 data = r .text
349- self .etag = r .headers .get ('ETag' , None ) or hex_digest (r .text , 'sha256' )
357+ _etag = r .headers .get ('ETag' , None )
358+ if not _etag :
359+ _etag = hex_digest (r .text , 'sha256' )
360+ self .etag = _etag
350361 elif self .local_copy_fn is not None :
351362 log .warning (
352363 "Got status={:d} while getting {}. Attempting fallback to local copy." .format (
@@ -379,12 +390,16 @@ def load_resource(self, getter: Callable[[str], Response]) -> Tuple[Optional[str
379390
380391 def parse (self , getter : Callable [[str ], Response ]) -> Deque [Resource ]:
381392 data , status , info = self .load_resource (getter )
393+
394+ if not data :
395+ raise ResourceException (f'Nothing to parse when loading resource { self } ' )
396+
382397 info ['State' ] = 'Parsing'
383398 # local import to avoid circular import
384399 from .parse import parse_resource
385400
386401 parse_info = parse_resource (self , data )
387- if parse_info is not None and isinstance ( parse_info , dict ) :
402+ if parse_info is not None :
388403 info .update (parse_info )
389404
390405 if status != 218 : # write backup unless we just loaded from backup
0 commit comments