From dd4c4a041b3e549c922ddc7ad019e301cb11dc67 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 12:43:10 -0500 Subject: [PATCH 01/19] Add type hints for _RawResponse and Response classes --- adafruit_requests.py | 51 +++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index aaa56cb..57cae84 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -38,10 +38,21 @@ import errno +try: + from typing import Union, Optional, Dict, List, Any + import types + import adafruit_esp32spi.adafruit_esp32spi_socket as esp32_socket + import adafruit_wiznet5k.adafruit_wiznet5k_socket as wiznet_socket + import adafruit_fona.adafruit_fona_socket as cellular_socket + SocketType = Union[esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket] + SocketPoolType = types.MethodType +except ImportError: + pass + # CircuitPython 6.0 does not have the bytearray.split method. # This function emulates buf.split(needle)[0], which is the functionality # required. -def _buffer_split0(buf, needle): +def _buffer_split0(buf, needle): # TODO: add typing index = buf.find(needle) if index == -1: return buf @@ -49,10 +60,10 @@ def _buffer_split0(buf, needle): class _RawResponse: - def __init__(self, response): + def __init__(self, response: 'Response') -> None: self._response = response - def read(self, size=-1): + def read(self, size: int = -1) -> bytes: """Read as much as available or up to size and return it in a byte string. Do NOT use this unless you really need to. Reusing memory with `readinto` is much better. @@ -61,7 +72,7 @@ def read(self, size=-1): return self._response.content return self._response.socket.recv(size) - def readinto(self, buf): + def readinto(self, buf: bytearray) -> int: """Read as much as available into buf or until it is full. Returns the number of bytes read into buf.""" return self._response._readinto(buf) # pylint: disable=protected-access @@ -82,7 +93,7 @@ class Response: encoding = None - def __init__(self, sock, session=None): + def __init__(self, sock: SocketType, session: Optional['Session'] = None) -> None: self.socket = sock self.encoding = "utf-8" self._cached = None @@ -110,13 +121,13 @@ def __init__(self, sock, session=None): self._raw = None self._session = session - def __enter__(self): + def __enter__(self) -> 'Response': return self - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type, exc_value, traceback) -> None: # TODO: Add type hints for arguments self.close() - def _recv_into(self, buf, size=0): + def _recv_into(self, buf: bytearray, size: int = 0) -> int: if self._backwards_compatible: size = len(buf) if size == 0 else size b = self.socket.recv(size) @@ -126,7 +137,7 @@ def _recv_into(self, buf, size=0): return self.socket.recv_into(buf, size) @staticmethod - def _find(buf, needle, start, end): + def _find(buf: bytes, needle: bytes, start: int, end: int) -> int: if hasattr(buf, "find"): return buf.find(needle, start, end) result = -1 @@ -142,7 +153,7 @@ def _find(buf, needle, start, end): return result - def _readto(self, first, second=b""): + def _readto(self, first: bytes, second: bytes = b"") -> bytes: buf = self._receive_buffer end = self._received_length while True: @@ -187,7 +198,7 @@ def _readto(self, first, second=b""): return b"" - def _read_from_buffer(self, buf=None, nbytes=None): + def _read_from_buffer(self, buf: Optional[bytearray] = None, nbytes: Optional[int] = None) -> int: if self._received_length == 0: return 0 read = self._received_length @@ -204,7 +215,7 @@ def _read_from_buffer(self, buf=None, nbytes=None): self._received_length = 0 return read - def _readinto(self, buf): + def _readinto(self, buf: bytearray) -> int: if not self.socket: raise RuntimeError( "Newer Response closed this one. Use Responses immediately." @@ -237,7 +248,7 @@ def _readinto(self, buf): return read - def _throw_away(self, nbytes): + def _throw_away(self, nbytes: int) -> None: nbytes -= self._read_from_buffer(nbytes=nbytes) buf = self._receive_buffer @@ -247,7 +258,7 @@ def _throw_away(self, nbytes): if remaining: self._recv_into(buf, remaining) - def close(self): + def close(self) -> None: """Drain the remaining ESP socket buffers. We assume we already got what we wanted.""" if not self.socket: return @@ -269,7 +280,7 @@ def close(self): self.socket.close() self.socket = None - def _parse_headers(self): + def _parse_headers(self) -> None: """ Parses the header portion of an HTTP request/response from the socket. Expects first line of HTTP request/response to have been read already. @@ -291,7 +302,7 @@ def _parse_headers(self): self._headers[title] = content @property - def headers(self): + def headers(self) -> Dict[str, str]: """ The response headers. Does not include headers from the trailer until the content has been read. @@ -299,7 +310,7 @@ def headers(self): return self._headers @property - def content(self): + def content(self) -> bytes: """The HTTP content direct from the socket, as bytes""" if self._cached is not None: if isinstance(self._cached, bytes): @@ -310,7 +321,7 @@ def content(self): return self._cached @property - def text(self): + def text(self) -> str: """The HTTP content, encoded into a string according to the HTTP header encoding""" if self._cached is not None: @@ -320,7 +331,7 @@ def text(self): self._cached = str(self.content, self.encoding) return self._cached - def json(self): + def json(self) -> Union[List[Dict[str, Any]], Dict[str, Any]]: """The HTTP content, parsed into a json dictionary""" # pylint: disable=import-outside-toplevel import json @@ -344,7 +355,7 @@ def json(self): self.close() return obj - def iter_content(self, chunk_size=1, decode_unicode=False): + def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> bytes: """An iterator that will stream data by only reading 'chunk_size' bytes and yielding them, when we can't buffer the whole datastream""" if decode_unicode: From 2f81228a42dfa9742142eae9f613529ea9cb07fa Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 13:00:48 -0500 Subject: [PATCH 02/19] Add TODO for fixing josn() return type hint --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 57cae84..09ff2b6 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -331,7 +331,7 @@ def text(self) -> str: self._cached = str(self.content, self.encoding) return self._cached - def json(self) -> Union[List[Dict[str, Any]], Dict[str, Any]]: + def json(self) -> Union[List[Dict[str, Any]], Dict[str, Any]]: # TODO: Fix typing to match all possible JSON returns """The HTTP content, parsed into a json dictionary""" # pylint: disable=import-outside-toplevel import json From d200444d7db151e493d302a5f116b776cf2b9b8c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 14:09:34 -0500 Subject: [PATCH 03/19] Type json() return as Any --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 09ff2b6..d0ed289 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -331,7 +331,7 @@ def text(self) -> str: self._cached = str(self.content, self.encoding) return self._cached - def json(self) -> Union[List[Dict[str, Any]], Dict[str, Any]]: # TODO: Fix typing to match all possible JSON returns + def json(self) -> Any: """The HTTP content, parsed into a json dictionary""" # pylint: disable=import-outside-toplevel import json From c14ab4f666d2f7e7abb4c79bb2917d7b199a5b46 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 15:07:50 -0500 Subject: [PATCH 04/19] Add types for Session class --- adafruit_requests.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index d0ed289..d9745b7 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -39,13 +39,15 @@ import errno try: - from typing import Union, Optional, Dict, List, Any + from typing import Union, TypeVar, Optional, Dict, Any, List import types + import ssl import adafruit_esp32spi.adafruit_esp32spi_socket as esp32_socket import adafruit_wiznet5k.adafruit_wiznet5k_socket as wiznet_socket import adafruit_fona.adafruit_fona_socket as cellular_socket - SocketType = Union[esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket] - SocketPoolType = types.MethodType + SocketType = TypeVar("SocketType", esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket) + SocketpoolModuleType = TypeVar("SocketpoolModuleType", types.ModuleType("socket"), types.ModuleType("socketpool")) + SSLContextType = TypeVar("SSLContextType", ssl.SSLContext) # Can use either CircuitPython or CPython ssl module except ImportError: pass @@ -377,7 +379,7 @@ def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> byt class Session: """HTTP session that shares sockets and ssl context.""" - def __init__(self, socket_pool, ssl_context=None): + def __init__(self, socket_pool: SocketpoolModuleType, ssl_context: Optional[SSLContextType] = None) -> None: self._socket_pool = socket_pool self._ssl_context = ssl_context # Hang onto open sockets so that we can reuse them. @@ -385,12 +387,12 @@ def __init__(self, socket_pool, ssl_context=None): self._socket_free = {} self._last_response = None - def _free_socket(self, socket): + def _free_socket(self, socket: SocketType) -> None: if socket not in self._open_sockets.values(): raise RuntimeError("Socket not from session") self._socket_free[socket] = True - def _close_socket(self, sock): + def _close_socket(self, sock: SocketType) -> None: sock.close() del self._socket_free[sock] key = None @@ -401,7 +403,7 @@ def _close_socket(self, sock): if key: del self._open_sockets[key] - def _free_sockets(self): + def _free_sockets(self) -> None: free_sockets = [] for sock, val in self._socket_free.items(): if val: @@ -409,7 +411,7 @@ def _free_sockets(self): for sock in free_sockets: self._close_socket(sock) - def _get_socket(self, host, port, proto, *, timeout=1): + def _get_socket(self, host:str, port: int, proto: str, *, timeout: float = 1) -> SocketType: # pylint: disable=too-many-branches key = (host, port, proto) if key in self._open_sockets: @@ -464,7 +466,7 @@ def _get_socket(self, host, port, proto, *, timeout=1): return sock @staticmethod - def _send(socket, data): + def _send(socket: SocketType, data: bytes): total_sent = 0 while total_sent < len(data): # ESP32SPI sockets raise a RuntimeError when unable to send. @@ -478,7 +480,7 @@ def _send(socket, data): raise _SendFailed() total_sent += sent - def _send_request(self, socket, host, method, path, headers, data, json): + def _send_request(self, socket: SocketType, host: str, method: str, path: str, headers: List[Dict[str, str]], data: Any, json: Dict[Any, Any]): # pylint: disable=too-many-arguments self._send(socket, bytes(method, "utf-8")) self._send(socket, b" /") @@ -524,8 +526,8 @@ def _send_request(self, socket, host, method, path, headers, data, json): # pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals def request( - self, method, url, data=None, json=None, headers=None, stream=False, timeout=60 - ): + self, method: str, url: str, data: Optional[Any] = None, json: Optional[Dict[Any, Any]] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 60 + ) -> Response: """Perform an HTTP request to the given url which we will parse to determine whether to use SSL ('https://') or not. We can also send some provided 'data' or a json dictionary which we will stringify. 'headers' is optional HTTP headers @@ -615,27 +617,27 @@ def request( self._last_response = resp return resp - def head(self, url, **kw): + def head(self, url: str, **kw) -> Response: """Send HTTP HEAD request""" return self.request("HEAD", url, **kw) - def get(self, url, **kw): + def get(self, url: str, **kw) -> Response: """Send HTTP GET request""" return self.request("GET", url, **kw) - def post(self, url, **kw): + def post(self, url: str, **kw) -> Response: """Send HTTP POST request""" return self.request("POST", url, **kw) - def put(self, url, **kw): + def put(self, url: str, **kw) -> Response: """Send HTTP PUT request""" return self.request("PUT", url, **kw) - def patch(self, url, **kw): + def patch(self, url: str, **kw) -> Response: """Send HTTP PATCH request""" return self.request("PATCH", url, **kw) - def delete(self, url, **kw): + def delete(self, url: str, **kw) -> Response: """Send HTTP DELETE request""" return self.request("DELETE", url, **kw) From cd04925922081ea08f60c6ff287a592d4cc10018 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 15:38:33 -0500 Subject: [PATCH 05/19] Finalize prototype of type hints --- adafruit_requests.py | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index d9745b7..11f106d 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -37,6 +37,10 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Requests.git" import errno +from ssl import SSLSocket +from types import ModuleType + +from tests.mocket import SSLContext try: from typing import Union, TypeVar, Optional, Dict, Any, List @@ -45,9 +49,14 @@ import adafruit_esp32spi.adafruit_esp32spi_socket as esp32_socket import adafruit_wiznet5k.adafruit_wiznet5k_socket as wiznet_socket import adafruit_fona.adafruit_fona_socket as cellular_socket - SocketType = TypeVar("SocketType", esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket) + from adafruit_esp32spi.adafruit_esp32spi import ESP_SPIcontrol + from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K + from adafruit_fona.adafruit_fona import FONA + import socket + SocketType = TypeVar("SocketType", esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket, socket.socket) SocketpoolModuleType = TypeVar("SocketpoolModuleType", types.ModuleType("socket"), types.ModuleType("socketpool")) SSLContextType = TypeVar("SSLContextType", ssl.SSLContext) # Can use either CircuitPython or CPython ssl module + InterfaceType = TypeVar("InterfaceType", ESP_SPIcontrol, WIZNET5K, FONA) except ImportError: pass @@ -480,7 +489,7 @@ def _send(socket: SocketType, data: bytes): raise _SendFailed() total_sent += sent - def _send_request(self, socket: SocketType, host: str, method: str, path: str, headers: List[Dict[str, str]], data: Any, json: Dict[Any, Any]): + def _send_request(self, socket: SocketType, host: str, method: str, path: str, headers: List[Dict[str, str]], data: Any, json: Any): # pylint: disable=too-many-arguments self._send(socket, bytes(method, "utf-8")) self._send(socket, b" /") @@ -526,7 +535,7 @@ def _send_request(self, socket: SocketType, host: str, method: str, path: str, h # pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals def request( - self, method: str, url: str, data: Optional[Any] = None, json: Optional[Dict[Any, Any]] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 60 + self, method: str, url: str, data: Optional[Any] = None, json: Optional[Any] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 60 ) -> Response: """Perform an HTTP request to the given url which we will parse to determine whether to use SSL ('https://') or not. We can also send some provided 'data' @@ -648,7 +657,7 @@ def delete(self, url: str, **kw) -> Response: class _FakeSSLSocket: - def __init__(self, socket, tls_mode): + def __init__(self, socket: SocketType, tls_mode: int) -> None: self._socket = socket self._mode = tls_mode self.settimeout = socket.settimeout @@ -656,25 +665,23 @@ def __init__(self, socket, tls_mode): self.recv = socket.recv self.close = socket.close - def connect(self, address): + def connect(self, address: Union[bytes, str]) -> None: """connect wrapper to add non-standard mode parameter""" try: return self._socket.connect(address, self._mode) except RuntimeError as error: raise OSError(errno.ENOMEM) from error - class _FakeSSLContext: - def __init__(self, iface): + def __init__(self, iface: InterfaceType) -> None: self._iface = iface - def wrap_socket(self, socket, server_hostname=None): + def wrap_socket(self, socket: SocketType, server_hostname: Optional[str] = None) -> _FakeSSLSocket: """Return the same socket""" # pylint: disable=unused-argument return _FakeSSLSocket(socket, self._iface.TLS_MODE) - -def set_socket(sock, iface=None): +def set_socket(sock, iface: Optional[InterfaceType] = None) -> None: """Legacy API for setting the socket and network interface. Use a `Session` instead.""" global _default_session # pylint: disable=global-statement,invalid-name if not iface: @@ -685,7 +692,7 @@ def set_socket(sock, iface=None): sock.set_interface(iface) -def request(method, url, data=None, json=None, headers=None, stream=False, timeout=1): +def request(method, url: str, data: Optional[Any] = None, json: Optional[Any] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 1) -> None: """Send HTTP request""" # pylint: disable=too-many-arguments _default_session.request( @@ -699,31 +706,31 @@ def request(method, url, data=None, json=None, headers=None, stream=False, timeo ) -def head(url, **kw): +def head(url: str, **kw): """Send HTTP HEAD request""" return _default_session.request("HEAD", url, **kw) -def get(url, **kw): +def get(url: str, **kw): """Send HTTP GET request""" return _default_session.request("GET", url, **kw) -def post(url, **kw): +def post(url: str, **kw): """Send HTTP POST request""" return _default_session.request("POST", url, **kw) -def put(url, **kw): +def put(url: str, **kw): """Send HTTP PUT request""" return _default_session.request("PUT", url, **kw) -def patch(url, **kw): +def patch(url: str, **kw): """Send HTTP PATCH request""" return _default_session.request("PATCH", url, **kw) -def delete(url, **kw): +def delete(url: str, **kw): """Send HTTP DELETE request""" return _default_session.request("DELETE", url, **kw) From 0fe980f0e3e5ffbb8cea70dc9b0509e2bbddca49 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:12:48 -0500 Subject: [PATCH 06/19] Reformatted per pre-commit --- adafruit_requests.py | 81 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 11f106d..e1f12ba 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -53,9 +53,22 @@ from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K from adafruit_fona.adafruit_fona import FONA import socket - SocketType = TypeVar("SocketType", esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket, socket.socket) - SocketpoolModuleType = TypeVar("SocketpoolModuleType", types.ModuleType("socket"), types.ModuleType("socketpool")) - SSLContextType = TypeVar("SSLContextType", ssl.SSLContext) # Can use either CircuitPython or CPython ssl module + + SocketType = TypeVar( + "SocketType", + esp32_socket.socket, + wiznet_socket.socket, + cellular_socket.socket, + socket.socket, + ) + SocketpoolModuleType = TypeVar( + "SocketpoolModuleType", + types.ModuleType("socket"), + types.ModuleType("socketpool"), + ) + SSLContextType = TypeVar( + "SSLContextType", ssl.SSLContext + ) # Can use either CircuitPython or CPython ssl module InterfaceType = TypeVar("InterfaceType", ESP_SPIcontrol, WIZNET5K, FONA) except ImportError: pass @@ -63,7 +76,7 @@ # CircuitPython 6.0 does not have the bytearray.split method. # This function emulates buf.split(needle)[0], which is the functionality # required. -def _buffer_split0(buf, needle): # TODO: add typing +def _buffer_split0(buf, needle): # TODO: add typing index = buf.find(needle) if index == -1: return buf @@ -71,7 +84,7 @@ def _buffer_split0(buf, needle): # TODO: add typing class _RawResponse: - def __init__(self, response: 'Response') -> None: + def __init__(self, response: "Response") -> None: self._response = response def read(self, size: int = -1) -> bytes: @@ -104,7 +117,7 @@ class Response: encoding = None - def __init__(self, sock: SocketType, session: Optional['Session'] = None) -> None: + def __init__(self, sock: SocketType, session: Optional["Session"] = None) -> None: self.socket = sock self.encoding = "utf-8" self._cached = None @@ -132,10 +145,12 @@ def __init__(self, sock: SocketType, session: Optional['Session'] = None) -> Non self._raw = None self._session = session - def __enter__(self) -> 'Response': + def __enter__(self) -> "Response": return self - def __exit__(self, exc_type, exc_value, traceback) -> None: # TODO: Add type hints for arguments + def __exit__( + self, exc_type, exc_value, traceback + ) -> None: # TODO: Add type hints for arguments self.close() def _recv_into(self, buf: bytearray, size: int = 0) -> int: @@ -209,7 +224,9 @@ def _readto(self, first: bytes, second: bytes = b"") -> bytes: return b"" - def _read_from_buffer(self, buf: Optional[bytearray] = None, nbytes: Optional[int] = None) -> int: + def _read_from_buffer( + self, buf: Optional[bytearray] = None, nbytes: Optional[int] = None + ) -> int: if self._received_length == 0: return 0 read = self._received_length @@ -388,7 +405,11 @@ def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> byt class Session: """HTTP session that shares sockets and ssl context.""" - def __init__(self, socket_pool: SocketpoolModuleType, ssl_context: Optional[SSLContextType] = None) -> None: + def __init__( + self, + socket_pool: SocketpoolModuleType, + ssl_context: Optional[SSLContextType] = None, + ) -> None: self._socket_pool = socket_pool self._ssl_context = ssl_context # Hang onto open sockets so that we can reuse them. @@ -420,7 +441,9 @@ def _free_sockets(self) -> None: for sock in free_sockets: self._close_socket(sock) - def _get_socket(self, host:str, port: int, proto: str, *, timeout: float = 1) -> SocketType: + def _get_socket( + self, host: str, port: int, proto: str, *, timeout: float = 1 + ) -> SocketType: # pylint: disable=too-many-branches key = (host, port, proto) if key in self._open_sockets: @@ -489,7 +512,16 @@ def _send(socket: SocketType, data: bytes): raise _SendFailed() total_sent += sent - def _send_request(self, socket: SocketType, host: str, method: str, path: str, headers: List[Dict[str, str]], data: Any, json: Any): + def _send_request( + self, + socket: SocketType, + host: str, + method: str, + path: str, + headers: List[Dict[str, str]], + data: Any, + json: Any, + ): # pylint: disable=too-many-arguments self._send(socket, bytes(method, "utf-8")) self._send(socket, b" /") @@ -535,7 +567,14 @@ def _send_request(self, socket: SocketType, host: str, method: str, path: str, h # pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals def request( - self, method: str, url: str, data: Optional[Any] = None, json: Optional[Any] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 60 + self, + method: str, + url: str, + data: Optional[Any] = None, + json: Optional[Any] = None, + headers: Optional[List[Dict[str, str]]] = None, + stream: bool = False, + timeout: float = 60, ) -> Response: """Perform an HTTP request to the given url which we will parse to determine whether to use SSL ('https://') or not. We can also send some provided 'data' @@ -672,15 +711,19 @@ def connect(self, address: Union[bytes, str]) -> None: except RuntimeError as error: raise OSError(errno.ENOMEM) from error + class _FakeSSLContext: def __init__(self, iface: InterfaceType) -> None: self._iface = iface - def wrap_socket(self, socket: SocketType, server_hostname: Optional[str] = None) -> _FakeSSLSocket: + def wrap_socket( + self, socket: SocketType, server_hostname: Optional[str] = None + ) -> _FakeSSLSocket: """Return the same socket""" # pylint: disable=unused-argument return _FakeSSLSocket(socket, self._iface.TLS_MODE) + def set_socket(sock, iface: Optional[InterfaceType] = None) -> None: """Legacy API for setting the socket and network interface. Use a `Session` instead.""" global _default_session # pylint: disable=global-statement,invalid-name @@ -692,7 +735,15 @@ def set_socket(sock, iface: Optional[InterfaceType] = None) -> None: sock.set_interface(iface) -def request(method, url: str, data: Optional[Any] = None, json: Optional[Any] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 1) -> None: +def request( + method, + url: str, + data: Optional[Any] = None, + json: Optional[Any] = None, + headers: Optional[List[Dict[str, str]]] = None, + stream: bool = False, + timeout: float = 1, +) -> None: """Send HTTP request""" # pylint: disable=too-many-arguments _default_session.request( From cac592f8e2baa30e40e6f396e9857c9a19494328 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:30:28 -0500 Subject: [PATCH 07/19] Fix imports and typing types --- adafruit_requests.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index e1f12ba..0601971 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -37,13 +37,10 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Requests.git" import errno -from ssl import SSLSocket -from types import ModuleType - -from tests.mocket import SSLContext +from types import TracebackType try: - from typing import Union, TypeVar, Optional, Dict, Any, List + from typing import Union, TypeVar, Optional, Dict, Any, List, Type import types import ssl import adafruit_esp32spi.adafruit_esp32spi_socket as esp32_socket @@ -52,14 +49,14 @@ from adafruit_esp32spi.adafruit_esp32spi import ESP_SPIcontrol from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K from adafruit_fona.adafruit_fona import FONA - import socket + import socket as cpython_socket SocketType = TypeVar( "SocketType", esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket, - socket.socket, + cpython_socket.socket, ) SocketpoolModuleType = TypeVar( "SocketpoolModuleType", From 205afed9d45c906e9dd429e753c6a387381945b1 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:30:49 -0500 Subject: [PATCH 08/19] Add type hints for _buffer_split0() --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 0601971..0156462 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -73,7 +73,7 @@ # CircuitPython 6.0 does not have the bytearray.split method. # This function emulates buf.split(needle)[0], which is the functionality # required. -def _buffer_split0(buf, needle): # TODO: add typing +def _buffer_split0(buf, needle: bytes): index = buf.find(needle) if index == -1: return buf From 97a45b0eab4e99b7dc195d4e108143b8a9325845 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:31:03 -0500 Subject: [PATCH 09/19] Add type hints for Response.__exit__() --- adafruit_requests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 0156462..2b0d24b 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -146,8 +146,8 @@ def __enter__(self) -> "Response": return self def __exit__( - self, exc_type, exc_value, traceback - ) -> None: # TODO: Add type hints for arguments + self, exc_type: Optional[Type[type]], exc_value: Optional[BaseException], traceback: Optional[TracebackType] + ) -> None: self.close() def _recv_into(self, buf: bytearray, size: int = 0) -> int: From 3c3ff9393a9d28b17abed97573641311b834924d Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:32:00 -0500 Subject: [PATCH 10/19] Reformatted per pre-commit --- adafruit_requests.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 2b0d24b..0d87066 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -146,7 +146,10 @@ def __enter__(self) -> "Response": return self def __exit__( - self, exc_type: Optional[Type[type]], exc_value: Optional[BaseException], traceback: Optional[TracebackType] + self, + exc_type: Optional[Type[type]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], ) -> None: self.close() From fd82f9418d18911610f8ca50cd6d37e05d923305 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:47:16 -0500 Subject: [PATCH 11/19] Added hardware drivers to mock imports --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index c0e0389..4a485bc 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -# autodoc_mock_imports = ["digitalio", "busio"] +autodoc_mock_imports = ["adafruit_esp32spi", "adafruit_wiznet5k", "adafruit_fona"] intersphinx_mapping = { From 89e8aaecb29f7cdb2035e4cee8687cc7e38cedbf Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:47:26 -0500 Subject: [PATCH 12/19] Reformatted per pre-commit --- adafruit_requests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_requests.py b/adafruit_requests.py index 0d87066..75e2abd 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -67,6 +67,7 @@ "SSLContextType", ssl.SSLContext ) # Can use either CircuitPython or CPython ssl module InterfaceType = TypeVar("InterfaceType", ESP_SPIcontrol, WIZNET5K, FONA) + except ImportError: pass From 0d4396cb4e06b5d89152e0ce6942d7712e2c2026 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:59:25 -0500 Subject: [PATCH 13/19] Change SocketpoolModuleType to Union --- adafruit_requests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 75e2abd..d67b1ce 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -58,11 +58,10 @@ cellular_socket.socket, cpython_socket.socket, ) - SocketpoolModuleType = TypeVar( - "SocketpoolModuleType", + SocketpoolModuleType = Union[ types.ModuleType("socket"), types.ModuleType("socketpool"), - ) + ] SSLContextType = TypeVar( "SSLContextType", ssl.SSLContext ) # Can use either CircuitPython or CPython ssl module From 080fecd8cc9c09ecc58629643598b33400cb9984 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 17:24:40 -0500 Subject: [PATCH 14/19] Fix SocketpoolModuleType to use types.ModuleType --- adafruit_requests.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index d67b1ce..7a9f0f2 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -58,10 +58,7 @@ cellular_socket.socket, cpython_socket.socket, ) - SocketpoolModuleType = Union[ - types.ModuleType("socket"), - types.ModuleType("socketpool"), - ] + SocketpoolModuleType = types.ModuleType SSLContextType = TypeVar( "SSLContextType", ssl.SSLContext ) # Can use either CircuitPython or CPython ssl module From 72a300f9e339f4c9cd2e2364517bbf9aa997474d Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 17:28:39 -0500 Subject: [PATCH 15/19] Removed TypeVar use for SSLContextType assignment --- adafruit_requests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 7a9f0f2..c0632c0 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -59,8 +59,8 @@ cpython_socket.socket, ) SocketpoolModuleType = types.ModuleType - SSLContextType = TypeVar( - "SSLContextType", ssl.SSLContext + SSLContextType = ( + ssl.SSLContext ) # Can use either CircuitPython or CPython ssl module InterfaceType = TypeVar("InterfaceType", ESP_SPIcontrol, WIZNET5K, FONA) From 73a9428b53cf68fb40e4ebb811f8aa57e868d20e Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 14 Dec 2021 13:58:31 -0500 Subject: [PATCH 16/19] Added SocketpoolModule type to sock param of set_socket() --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index c0632c0..81c08a0 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -721,7 +721,7 @@ def wrap_socket( return _FakeSSLSocket(socket, self._iface.TLS_MODE) -def set_socket(sock, iface: Optional[InterfaceType] = None) -> None: +def set_socket(sock: SocketpoolModuleType, iface: Optional[InterfaceType] = None) -> None: """Legacy API for setting the socket and network interface. Use a `Session` instead.""" global _default_session # pylint: disable=global-statement,invalid-name if not iface: From 531c18ce72fbf83b5092270efb741634b010117c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 14 Dec 2021 13:58:46 -0500 Subject: [PATCH 17/19] Add str type to method param of request() --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 81c08a0..835a115 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -733,7 +733,7 @@ def set_socket(sock: SocketpoolModuleType, iface: Optional[InterfaceType] = None def request( - method, + method: str, url: str, data: Optional[Any] = None, json: Optional[Any] = None, From 36fb70b037ca5248a181e86456964cf574fdab91 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 14 Dec 2021 13:59:57 -0500 Subject: [PATCH 18/19] Reformatted per pre-commit --- adafruit_requests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 835a115..b80faa7 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -721,7 +721,9 @@ def wrap_socket( return _FakeSSLSocket(socket, self._iface.TLS_MODE) -def set_socket(sock: SocketpoolModuleType, iface: Optional[InterfaceType] = None) -> None: +def set_socket( + sock: SocketpoolModuleType, iface: Optional[InterfaceType] = None +) -> None: """Legacy API for setting the socket and network interface. Use a `Session` instead.""" global _default_session # pylint: disable=global-statement,invalid-name if not iface: From 9dc3304c1cabe936a0e993e0f7625d33c520f77c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 14 Dec 2021 14:02:15 -0500 Subject: [PATCH 19/19] Added/corrected type hints for _buffer_split0() --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index b80faa7..f59b2f4 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -70,7 +70,7 @@ # CircuitPython 6.0 does not have the bytearray.split method. # This function emulates buf.split(needle)[0], which is the functionality # required. -def _buffer_split0(buf, needle: bytes): +def _buffer_split0(buf: Union[bytes, bytearray], needle: Union[bytes, bytearray]): index = buf.find(needle) if index == -1: return buf