Skip to content

Commit 2fc8dcb

Browse files
committed
cleanup; update to CircuitPython 7
1 parent 2705656 commit 2fc8dcb

File tree

4 files changed

+50
-100
lines changed

4 files changed

+50
-100
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
repos:
66
- repo: https://github.com/python/black
7-
rev: 20.8b1
7+
rev: 22.1.0
88
hooks:
99
- id: black
1010
- repo: https://github.com/fsfe/reuse-tool

adafruit_requests.py

Lines changed: 45 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ def cast(_t, value):
4545
"""No-op shim for the typing.cast() function which is not available in CircuitPython."""
4646
return value
4747

48-
4948
else:
5049
from ssl import SSLContext
5150
from types import ModuleType, TracebackType
@@ -148,16 +147,6 @@ def TLS_MODE(self) -> int: # pylint: disable=invalid-name
148147
SSLContextType = Union[SSLContext, "_FakeSSLContext"]
149148

150149

151-
# CircuitPython 6.0 does not have the bytearray.split method.
152-
# This function emulates buf.split(needle)[0], which is the functionality
153-
# required.
154-
def _buffer_split0(buf: Union[bytes, bytearray], needle: Union[bytes, bytearray]):
155-
index = buf.find(needle)
156-
if index == -1:
157-
return buf
158-
return buf[:index]
159-
160-
161150
class _RawResponse:
162151
def __init__(self, response: "Response") -> None:
163152
self._response = response
@@ -177,10 +166,6 @@ def readinto(self, buf: bytearray) -> int:
177166
return self._response._readinto(buf) # pylint: disable=protected-access
178167

179168

180-
class _SendFailed(Exception):
181-
"""Custom exception to abort sending a request."""
182-
183-
184169
class OutOfRetries(Exception):
185170
"""Raised when requests has retried to make a request unsuccessfully."""
186171

@@ -240,56 +225,25 @@ def _recv_into(self, buf: bytearray, size: int = 0) -> int:
240225
return read_size
241226
return cast("SupportsRecvInto", self.socket).recv_into(buf, size)
242227

243-
@staticmethod
244-
def _find(buf: bytes, needle: bytes, start: int, end: int) -> int:
245-
if hasattr(buf, "find"):
246-
return buf.find(needle, start, end)
247-
result = -1
248-
i = start
249-
while i < end:
250-
j = 0
251-
while j < len(needle) and i + j < end and buf[i + j] == needle[j]:
252-
j += 1
253-
if j == len(needle):
254-
result = i
255-
break
256-
i += 1
257-
258-
return result
259-
260-
def _readto(self, first: bytes, second: bytes = b"") -> bytes:
228+
def _readto(self, stop: bytes) -> bytearray:
261229
buf = self._receive_buffer
262230
end = self._received_length
263231
while True:
264-
firsti = self._find(buf, first, 0, end)
265-
secondi = -1
266-
if second:
267-
secondi = self._find(buf, second, 0, end)
268-
269-
i = -1
270-
needle_len = 0
271-
if firsti >= 0:
272-
i = firsti
273-
needle_len = len(first)
274-
if secondi >= 0 and (firsti < 0 or secondi < firsti):
275-
i = secondi
276-
needle_len = len(second)
232+
i = buf.find(stop, 0, end)
277233
if i >= 0:
234+
# Stop was found. Return everything up to but not including stop.
278235
result = buf[:i]
279-
new_start = i + needle_len
280-
281-
if i + needle_len <= end:
282-
new_end = end - new_start
283-
buf[:new_end] = buf[new_start:end]
284-
self._received_length = new_end
236+
new_start = i + len(stop)
237+
# Remove everything up to and including stop from the buffer.
238+
new_end = end - new_start
239+
buf[:new_end] = buf[new_start:end]
240+
self._received_length = new_end
285241
return result
286242

287-
# Not found so load more.
288-
243+
# Not found so load more bytes.
289244
# If our buffer is full, then make it bigger to load more.
290245
if end == len(buf):
291-
new_size = len(buf) + 32
292-
new_buf = bytearray(new_size)
246+
new_buf = bytearray(len(buf) + 32)
293247
new_buf[: len(buf)] = buf
294248
buf = new_buf
295249
self._receive_buffer = buf
@@ -300,8 +254,6 @@ def _readto(self, first: bytes, second: bytes = b"") -> bytes:
300254
return buf[:end]
301255
end += read
302256

303-
return b""
304-
305257
def _read_from_buffer(
306258
self, buf: Optional[bytearray] = None, nbytes: Optional[int] = None
307259
) -> int:
@@ -333,7 +285,7 @@ def _readinto(self, buf: bytearray) -> int:
333285
# Consume trailing \r\n for chunks 2+
334286
if self._remaining == 0:
335287
self._throw_away(2)
336-
chunk_header = _buffer_split0(self._readto(b"\r\n"), b";")
288+
chunk_header = bytes(self._readto(b"\r\n")).split(b";", 1)[0]
337289
http_chunk_size = int(bytes(chunk_header), 16)
338290
if http_chunk_size == 0:
339291
self._chunked = False
@@ -374,7 +326,7 @@ def close(self) -> None:
374326
self._throw_away(self._remaining)
375327
elif self._chunked:
376328
while True:
377-
chunk_header = _buffer_split0(self._readto(b"\r\n"), b";")
329+
chunk_header = bytes(self._readto(b"\r\n")).split(b";", 1)[0]
378330
chunk_size = int(bytes(chunk_header), 16)
379331
if chunk_size == 0:
380332
break
@@ -392,11 +344,10 @@ def _parse_headers(self) -> None:
392344
Expects first line of HTTP request/response to have been read already.
393345
"""
394346
while True:
395-
title = self._readto(b": ", b"\r\n")
396-
if not title:
347+
header = self._readto(b"\r\n")
348+
if not header:
397349
break
398-
399-
content = self._readto(b"\r\n")
350+
title, content = bytes(header).split(b": ", 1)
400351
if title and content:
401352
# enforce that all headers are lowercase
402353
title = str(title, "utf-8").lower()
@@ -407,6 +358,17 @@ def _parse_headers(self) -> None:
407358
self._chunked = content.strip().lower() == "chunked"
408359
self._headers[title] = content
409360

361+
def _validate_not_gzip(self) -> None:
362+
"""gzip encoding is not supported. Raise an exception if found."""
363+
if (
364+
"content-encoding" in self.headers
365+
and self.headers["content-encoding"] == "gzip"
366+
):
367+
raise ValueError(
368+
"Content-encoding is gzip, data cannot be accessed as json or text. "
369+
"Use content property to access raw bytes."
370+
)
371+
410372
@property
411373
def headers(self) -> Dict[str, str]:
412374
"""
@@ -435,14 +397,8 @@ def text(self) -> str:
435397
return self._cached
436398
raise RuntimeError("Cannot access text after getting content or json")
437399

438-
if (
439-
"content-encoding" in self.headers
440-
and self.headers["content-encoding"] == "gzip"
441-
):
442-
raise ValueError(
443-
"Content-encoding is gzip, data cannot be accessed as json or text. "
444-
"Use content property to access raw bytes."
445-
)
400+
self._validate_not_gzip()
401+
446402
self._cached = str(self.content, self.encoding)
447403
return self._cached
448404

@@ -459,20 +415,9 @@ def json(self) -> Any:
459415
if not self._raw:
460416
self._raw = _RawResponse(self)
461417

462-
if (
463-
"content-encoding" in self.headers
464-
and self.headers["content-encoding"] == "gzip"
465-
):
466-
raise ValueError(
467-
"Content-encoding is gzip, data cannot be accessed as json or text. "
468-
"Use content property to access raw bytes."
469-
)
470-
try:
471-
obj = json.load(self._raw)
472-
except OSError:
473-
# <5.3.1 doesn't piecemeal load json from any object with readinto so load the whole
474-
# string.
475-
obj = json.loads(self._raw.read())
418+
self._validate_not_gzip()
419+
420+
obj = json.load(self._raw)
476421
if not self._cached:
477422
self._cached = obj
478423
self.close()
@@ -599,12 +544,19 @@ def _send(socket: SocketType, data: bytes):
599544
# ESP32SPI sockets raise a RuntimeError when unable to send.
600545
try:
601546
sent = socket.send(data[total_sent:])
602-
except RuntimeError:
603-
sent = 0
547+
except OSError as exc:
548+
if exc.errno == errno.EAGAIN:
549+
# Can't send right now (e.g., no buffer space), try again.
550+
continue
551+
# Some worse error.
552+
raise
553+
except RuntimeError as exc:
554+
raise OSError(errno.EIO) from exc
604555
if sent is None:
605556
sent = len(data)
606557
if sent == 0:
607-
raise _SendFailed()
558+
# Not EAGAIN; that was already handled.
559+
raise OSError(errno.EIO)
608560
total_sent += sent
609561

610562
def _send_request(
@@ -637,11 +589,9 @@ def _send_request(
637589
if json is not None:
638590
assert data is None
639591
# pylint: disable=import-outside-toplevel
640-
try:
641-
import json as json_module
642-
except ImportError:
643-
import ujson as json_module
644-
data = json_module.dumps(json)
592+
import json
593+
594+
data = json.dumps(json)
645595
self._send(socket, b"Content-Type: application/json\r\n")
646596
if data:
647597
if isinstance(data, dict):
@@ -711,7 +661,7 @@ def request(
711661
ok = True
712662
try:
713663
self._send_request(socket, host, method, path, headers, data, json)
714-
except (_SendFailed, OSError):
664+
except OSError:
715665
ok = False
716666
if ok:
717667
# Read the H of "HTTP/1.1" to make sure the socket is alive. send can appear to work

tests/legacy_mocket.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616

1717
class Mocket: # pylint: disable=too-few-public-methods
18-
""" Mock Socket """
18+
"""Mock Socket"""
1919

2020
def __init__(self, response):
2121
self.settimeout = mock.Mock()

tests/mocket.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
class MocketPool: # pylint: disable=too-few-public-methods
11-
""" Mock SocketPool """
11+
"""Mock SocketPool"""
1212

1313
SOCK_STREAM = 0
1414

@@ -18,7 +18,7 @@ def __init__(self):
1818

1919

2020
class Mocket: # pylint: disable=too-few-public-methods
21-
""" Mock Socket """
21+
"""Mock Socket"""
2222

2323
def __init__(self, response):
2424
self.settimeout = mock.Mock()
@@ -62,7 +62,7 @@ def _recv_into(self, buf, nbytes=0):
6262

6363

6464
class SSLContext: # pylint: disable=too-few-public-methods
65-
""" Mock SSL Context """
65+
"""Mock SSL Context"""
6666

6767
def __init__(self):
6868
self.wrap_socket = mock.Mock(side_effect=self._wrap_socket)

0 commit comments

Comments
 (0)