Skip to content

Commit 474b371

Browse files
authored
Merge pull request #8 from makermelissa/main
PortalBase refactoring to work with PyPortal better
2 parents d876a2a + ee8add0 commit 474b371

File tree

3 files changed

+146
-62
lines changed

3 files changed

+146
-62
lines changed

adafruit_portalbase/__init__.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class PortalBase:
4949
on-board NeoPixel. Defaults to ``None``, to not use the status LED
5050
:param json_transform: A function or a list of functions to call with the parsed JSON.
5151
Changes and additions are permitted for the ``dict`` object.
52+
:param success_callback: A function we'll call if you like, when we fetch data successfully.
53+
Defaults to ``None``.
5254
:param debug: Turn on debug print outs. Defaults to False.
5355
5456
"""
@@ -64,6 +66,7 @@ def __init__(
6466
json_path=None,
6567
regexp_path=None,
6668
json_transform=None,
69+
success_callback=None,
6770
debug=False,
6871
):
6972
self.network = network
@@ -88,6 +91,7 @@ def __init__(
8891
self.json_path = json_path
8992

9093
self._regexp_path = regexp_path
94+
self._success_callback = success_callback
9195

9296
# Add any JSON translators
9397
if json_transform:
@@ -362,6 +366,17 @@ def fetch(self, refresh_url=None, timeout=10):
362366
timeout=timeout,
363367
)
364368

369+
# if we have a callback registered, call it now
370+
if self._success_callback:
371+
self._success_callback(values)
372+
373+
self._fill_text_labels(values)
374+
375+
if len(values) == 1:
376+
return values[0]
377+
return values
378+
379+
def _fill_text_labels(self, values):
365380
# fill out all the text blocks
366381
if self._text:
367382
value_index = 0 # In case values and text is not the same
@@ -379,9 +394,6 @@ def fetch(self, refresh_url=None, timeout=10):
379394
string = values[value_index] # ok it's a string
380395
self._fetch_set_text(string, index=i)
381396
value_index += 1
382-
if len(values) == 1:
383-
return values[0]
384-
return values
385397

386398
def get_local_time(self, location=None):
387399
"""Accessor function for get_local_time()"""

adafruit_portalbase/graphics.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def set_background(self, file_or_color, position=None):
108108
def qrcode(
109109
self, qr_data, *, qr_size=1, x=0, y=0, qr_color=0x000000
110110
): # pylint: disable=invalid-name
111-
"""Display a QR code on the eInk
111+
"""Display a QR code
112112
113113
:param qr_data: The data for the QR code.
114114
:param int qr_size: The scale of the QR code.

adafruit_portalbase/network.py

+130-58
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)