@@ -96,6 +96,12 @@ def __init__(
9696 self .json_transform = []
9797 self ._extract_values = extract_values
9898
99+ self ._json_types = [
100+ "application/json" ,
101+ "application/javascript" ,
102+ "application/geo+json" ,
103+ ]
104+
99105 # This may be removed. Using for testing
100106 self .requests = None
101107
@@ -185,7 +191,7 @@ def get_local_time(self, location=None):
185191 "Error connection to Adafruit IO. The response was: "
186192 + response .text
187193 )
188- raise ValueError (error_message )
194+ raise RuntimeError (error_message )
189195 if self ._debug :
190196 print ("Time request: " , api_url )
191197 print ("Time reply: " , response .text )
@@ -427,47 +433,40 @@ def fetch(self, url, *, headers=None, timeout=10):
427433
428434 return response
429435
430- def fetch_data (
431- self ,
432- url ,
433- * ,
434- headers = None ,
435- json_path = None ,
436- regexp_path = None ,
437- timeout = 10 ,
438- ):
439- """Fetch data from the specified url and perfom any parsing
436+ def add_json_content_type (self , content_type ):
437+ """
438+ Add a JSON content type
440439
441- :param str url: The URL to fetch from.
442- :param list headers: Extra headers to include in the request.
443- :param json_path: The path to drill down into the JSON data.
444- :param regexp_path: The path formatted as a regular expression to drill down
445- into the JSON data.
446- :param int timeout: The timeout period in seconds.
440+ :param str type: The content JSON type like 'application/json'
447441
448442 """
449- json_out = None
450- values = []
451- content_type = CONTENT_TEXT
443+ if isinstance (content_type , str ):
444+ self ._json_types .append (content_type )
445+
446+ def _detect_content_type (self , headers ):
447+ if "content-type" in headers :
448+ if "image/" in headers ["content-type" ]:
449+ return CONTENT_IMAGE
450+ for json_type in self ._json_types :
451+ if json_type in headers ["content-type" ]:
452+ return CONTENT_JSON
453+ return CONTENT_TEXT
454+
455+ def check_response (self , response ):
456+ """
457+ Check the response object status code, change the lights, and return content type
452458
453- response = self .fetch (url , headers = headers , timeout = timeout )
459+ :param response: The response object from a network call
460+
461+ """
462+ headers = self ._get_headers (response )
454463
455- headers = {}
456- for title , content in response .headers .items ():
457- headers [title .lower ()] = content
458- gc .collect ()
459464 if self ._debug :
460465 print ("Headers:" , headers )
461466 if response .status_code == 200 :
462467 print ("Reply is OK!" )
463468 self .neo_status (STATUS_DATA_RECEIVED ) # green = got data
464- if "content-type" in headers :
465- if "image/" in headers ["content-type" ]:
466- content_type = CONTENT_IMAGE
467- elif "application/json" in headers ["content-type" ]:
468- content_type = CONTENT_JSON
469- elif "application/javascript" in headers ["content-type" ]:
470- content_type = CONTENT_JSON
469+ content_type = self ._detect_content_type (headers )
471470 else :
472471 if self ._debug :
473472 if "content-length" in headers :
@@ -481,11 +480,56 @@ def fetch_data(
481480 )
482481 )
483482
484- if content_type == CONTENT_JSON and json_path is not None :
485- if isinstance (json_path , (list , tuple )) and (
486- not json_path or not isinstance (json_path [0 ], (list , tuple ))
487- ):
488- json_path = (json_path ,)
483+ return content_type
484+
485+ @staticmethod
486+ def _get_headers (response ):
487+ headers = {}
488+ for title , content in response .headers .items ():
489+ headers [title .lower ()] = content
490+ gc .collect ()
491+ return headers
492+
493+ def fetch_data (
494+ self ,
495+ url ,
496+ * ,
497+ headers = None ,
498+ json_path = None ,
499+ regexp_path = None ,
500+ timeout = 10 ,
501+ ):
502+ """Fetch data from the specified url and perfom any parsing
503+
504+ :param str url: The URL to fetch from.
505+ :param list headers: Extra headers to include in the request.
506+ :param json_path: The path to drill down into the JSON data.
507+ :param regexp_path: The path formatted as a regular expression to search
508+ the text data.
509+ :param int timeout: The timeout period in seconds.
510+
511+ """
512+ response = self .fetch (url , headers = headers , timeout = timeout )
513+ return self ._parse_data (response , json_path = json_path , regexp_path = regexp_path )
514+
515+ def _parse_data (
516+ self ,
517+ response ,
518+ * ,
519+ json_path = None ,
520+ regexp_path = None ,
521+ ):
522+
523+ json_out = None
524+ content_type = self .check_response (response )
525+
526+ if content_type == CONTENT_JSON :
527+ if json_path is not None :
528+ # Drill down to the json path and set json_out as that node
529+ if isinstance (json_path , (list , tuple )) and (
530+ not json_path or not isinstance (json_path [0 ], (list , tuple ))
531+ ):
532+ json_path = (json_path ,)
489533 try :
490534 gc .collect ()
491535 json_out = response .json ()
@@ -498,43 +542,71 @@ def fetch_data(
498542 except MemoryError :
499543 supervisor .reload ()
500544
545+ if content_type == CONTENT_JSON :
546+ values = self .process_json (json_out , json_path )
547+ elif content_type == CONTENT_TEXT :
548+ values = self .process_text (response .text , regexp_path )
549+
550+ # Clean up
551+ json_out = None
552+ response = None
553+ if self ._extract_values and len (values ) == 1 :
554+ values = values [0 ]
555+
556+ gc .collect ()
557+
558+ return values
559+
560+ @staticmethod
561+ def process_text (text , regexp_path ):
562+ """
563+ Process text content
564+
565+ :param str text: The entire text content
566+ :param regexp_path: The path formatted as a regular expression to search
567+ the text data.
568+
569+ """
570+ values = []
501571 if regexp_path :
502572 import re # pylint: disable=import-outside-toplevel
503573
574+ for regexp in regexp_path :
575+ values .append (re .search (regexp , text ).group (1 ))
576+ else :
577+ values = text
578+ return values
579+
580+ def process_json (self , json_data , json_path ):
581+ """
582+ Process JSON content
583+
584+ :param dict json_data: The JSON data as a dict
585+ :param json_path: The path to drill down into the JSON data.
586+
587+ """
588+ values = []
589+
504590 # optional JSON post processing, apply any transformations
505591 # these MAY change/add element
506592 for idx , json_transform in enumerate (self .json_transform ):
507593 try :
508- json_transform (json_out )
594+ json_transform (json_data )
509595 except Exception as error :
510596 print ("Exception from json_transform: " , idx , error )
511597 raise
512598
513599 # extract desired text/values from json
514- if json_out is not None and json_path :
600+ if json_data is not None and json_path :
515601 for path in json_path :
516602 try :
517- values .append (self .json_traverse (json_out , path ))
603+ values .append (self .json_traverse (json_data , path ))
518604 except KeyError :
519- print (json_out )
605+ print (json_data )
520606 raise
521- elif content_type == CONTENT_TEXT and regexp_path :
522- for regexp in regexp_path :
523- values .append (re .search (regexp , response .text ).group (1 ))
524607 else :
525- if json_out :
526- # No path given, so return JSON as string for compatibility
527- import json # pylint: disable=import-outside-toplevel
528-
529- values = json .dumps (response .json ())
530- else :
531- values = response .text
532-
533- # we're done with the requests object, lets delete it so we can do more!
534- json_out = None
535- response = None
536- gc .collect ()
537- if self ._extract_values and len (values ) == 1 :
538- return values [0 ]
608+ # No path given, so return JSON as string for compatibility
609+ import json # pylint: disable=import-outside-toplevel
539610
611+ values = json .dumps (json_data )
540612 return values
0 commit comments