@@ -96,6 +96,12 @@ def __init__(
96
96
self .json_transform = []
97
97
self ._extract_values = extract_values
98
98
99
+ self ._json_types = [
100
+ "application/json" ,
101
+ "application/javascript" ,
102
+ "application/geo+json" ,
103
+ ]
104
+
99
105
# This may be removed. Using for testing
100
106
self .requests = None
101
107
@@ -185,7 +191,7 @@ def get_local_time(self, location=None):
185
191
"Error connection to Adafruit IO. The response was: "
186
192
+ response .text
187
193
)
188
- raise ValueError (error_message )
194
+ raise RuntimeError (error_message )
189
195
if self ._debug :
190
196
print ("Time request: " , api_url )
191
197
print ("Time reply: " , response .text )
@@ -427,47 +433,40 @@ def fetch(self, url, *, headers=None, timeout=10):
427
433
428
434
return response
429
435
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
440
439
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'
447
441
448
442
"""
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
452
458
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 )
454
463
455
- headers = {}
456
- for title , content in response .headers .items ():
457
- headers [title .lower ()] = content
458
- gc .collect ()
459
464
if self ._debug :
460
465
print ("Headers:" , headers )
461
466
if response .status_code == 200 :
462
467
print ("Reply is OK!" )
463
468
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 )
471
470
else :
472
471
if self ._debug :
473
472
if "content-length" in headers :
@@ -481,11 +480,56 @@ def fetch_data(
481
480
)
482
481
)
483
482
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 ,)
489
533
try :
490
534
gc .collect ()
491
535
json_out = response .json ()
@@ -498,43 +542,71 @@ def fetch_data(
498
542
except MemoryError :
499
543
supervisor .reload ()
500
544
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 = []
501
571
if regexp_path :
502
572
import re # pylint: disable=import-outside-toplevel
503
573
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
+
504
590
# optional JSON post processing, apply any transformations
505
591
# these MAY change/add element
506
592
for idx , json_transform in enumerate (self .json_transform ):
507
593
try :
508
- json_transform (json_out )
594
+ json_transform (json_data )
509
595
except Exception as error :
510
596
print ("Exception from json_transform: " , idx , error )
511
597
raise
512
598
513
599
# 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 :
515
601
for path in json_path :
516
602
try :
517
- values .append (self .json_traverse (json_out , path ))
603
+ values .append (self .json_traverse (json_data , path ))
518
604
except KeyError :
519
- print (json_out )
605
+ print (json_data )
520
606
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 ))
524
607
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
539
610
611
+ values = json .dumps (json_data )
540
612
return values
0 commit comments