Skip to content

Commit 9035f7b

Browse files
committed
Use protocols to fix importing in CPython
1 parent 82898b9 commit 9035f7b

File tree

4 files changed

+117
-34
lines changed

4 files changed

+117
-34
lines changed

adafruit_requests.py

Lines changed: 114 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -37,35 +37,117 @@
3737
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Requests.git"
3838

3939
import errno
40+
import sys
41+
42+
if sys.implementation.name == "circuitpython":
43+
44+
def cast(_t, value):
45+
"""No-op shim for the typing.cast() function which is not available in CircuitPython."""
46+
return value
47+
48+
49+
else:
50+
from ssl import SSLContext
51+
from types import ModuleType, TracebackType
52+
from typing import Any, Dict, List, Optional, Tuple, Type, Union, cast
53+
from typing_extensions import Protocol
54+
55+
# Based on https://github.com/python/typeshed/blob/master/stdlib/_socket.pyi
56+
class CommonSocketType(Protocol):
57+
"""Describes the common structure every socket type must have."""
58+
59+
def send(self, data: bytes, flags: int = ...) -> None:
60+
"""Send data to the socket. The meaning of the optional flags kwarg is
61+
implementation-specific."""
62+
...
63+
64+
def settimeout(self, value: Optional[float]) -> None:
65+
"""Set a timeout on blocking socket operations."""
66+
...
67+
68+
def close(self) -> None:
69+
"""Close the socket."""
70+
...
71+
72+
class CommonCircuitPythonSocketType(CommonSocketType, Protocol):
73+
"""Describes the common structure every CircuitPython socket type must have."""
74+
75+
def connect(
76+
self,
77+
address: Tuple[str, int],
78+
conntype: Optional[int] = ...,
79+
) -> None:
80+
"""Connect to a remote socket at the provided (host, port) address. The conntype
81+
kwarg optionally may indicate SSL or not, depending on the underlying interface."""
82+
...
83+
84+
class LegacyCircuitPythonSocketType(CommonCircuitPythonSocketType, Protocol):
85+
"""Describes the structure a legacy CircuitPython socket type must have."""
86+
87+
def recv(self, bufsize: int = ...) -> bytes:
88+
"""Receive data from the socket. The return value is a bytes object representing
89+
the data received. The maximum amount of data to be received at once is specified
90+
by bufsize."""
91+
...
92+
93+
class SupportsRecvWithFlags(Protocol):
94+
"""Describes a type that posseses a socket recv() method supporting the flags kwarg."""
95+
96+
def recv(self, bufsize: int = ..., flags: int = ...) -> bytes:
97+
"""Receive data from the socket. The return value is a bytes object representing
98+
the data received. The maximum amount of data to be received at once is specified
99+
by bufsize. The meaning of the optional flags kwarg is implementation-specific."""
100+
...
101+
102+
class SupportsRecvInto(Protocol):
103+
"""Describes a type that possesses a socket recv_into() method."""
104+
105+
def recv_into(
106+
self, buffer: bytearray, nbytes: int = ..., flags: int = ...
107+
) -> int:
108+
"""Receive up to nbytes bytes from the socket, storing the data into the provided
109+
buffer. If nbytes is not specified (or 0), receive up to the size available in the
110+
given buffer. The meaning of the optional flags kwarg is implementation-specific.
111+
Returns the number of bytes received."""
112+
...
113+
114+
class CircuitPythonSocketType(
115+
CommonCircuitPythonSocketType,
116+
SupportsRecvInto,
117+
SupportsRecvWithFlags,
118+
Protocol,
119+
): # pylint: disable=too-many-ancestors
120+
"""Describes the structure every modern CircuitPython socket type must have."""
121+
122+
...
123+
124+
class StandardPythonSocketType(
125+
CommonSocketType, SupportsRecvInto, SupportsRecvWithFlags, Protocol
126+
):
127+
"""Describes the structure every standard Python socket type must have."""
40128

41-
try:
42-
from typing import Union, TypeVar, Optional, Dict, Any, List, Type
43-
import types
44-
from types import TracebackType
45-
import ssl
46-
import adafruit_esp32spi.adafruit_esp32spi_socket as esp32_socket
47-
import adafruit_wiznet5k.adafruit_wiznet5k_socket as wiznet_socket
48-
import adafruit_fona.adafruit_fona_socket as cellular_socket
49-
from adafruit_esp32spi.adafruit_esp32spi import ESP_SPIcontrol
50-
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K
51-
from adafruit_fona.adafruit_fona import FONA
52-
import socket as cpython_socket
53-
54-
SocketType = TypeVar(
55-
"SocketType",
56-
esp32_socket.socket,
57-
wiznet_socket.socket,
58-
cellular_socket.socket,
59-
cpython_socket.socket,
60-
)
61-
SocketpoolModuleType = types.ModuleType
62-
SSLContextType = (
63-
ssl.SSLContext
64-
) # Can use either CircuitPython or CPython ssl module
65-
InterfaceType = TypeVar("InterfaceType", ESP_SPIcontrol, WIZNET5K, FONA)
129+
def connect(self, address: Union[Tuple[Any, ...], str, bytes]) -> None:
130+
"""Connect to a remote socket at the provided address."""
131+
...
132+
133+
SocketType = Union[
134+
LegacyCircuitPythonSocketType,
135+
CircuitPythonSocketType,
136+
StandardPythonSocketType,
137+
]
138+
139+
SocketpoolModuleType = ModuleType
140+
141+
class InterfaceType(Protocol):
142+
"""Describes the structure every interface type must have."""
143+
144+
@property
145+
def TLS_MODE(self) -> int: # pylint: disable=invalid-name
146+
"""Constant representing that a socket's connection mode is TLS."""
147+
...
148+
149+
SSLContextType = Union[SSLContext, "_FakeSSLContext"]
66150

67-
except ImportError:
68-
pass
69151

70152
# CircuitPython 6.0 does not have the bytearray.split method.
71153
# This function emulates buf.split(needle)[0], which is the functionality
@@ -157,7 +239,7 @@ def _recv_into(self, buf: bytearray, size: int = 0) -> int:
157239
read_size = len(b)
158240
buf[:read_size] = b
159241
return read_size
160-
return self.socket.recv_into(buf, size)
242+
return cast("SupportsRecvInto", self.socket).recv_into(buf, size)
161243

162244
@staticmethod
163245
def _find(buf: bytes, needle: bytes, start: int, end: int) -> int:
@@ -440,7 +522,7 @@ def _free_sockets(self) -> None:
440522

441523
def _get_socket(
442524
self, host: str, port: int, proto: str, *, timeout: float = 1
443-
) -> SocketType:
525+
) -> CircuitPythonSocketType:
444526
# pylint: disable=too-many-branches
445527
key = (host, port, proto)
446528
if key in self._open_sockets:
@@ -693,15 +775,15 @@ def delete(self, url: str, **kw) -> Response:
693775

694776

695777
class _FakeSSLSocket:
696-
def __init__(self, socket: SocketType, tls_mode: int) -> None:
778+
def __init__(self, socket: CircuitPythonSocketType, tls_mode: int) -> None:
697779
self._socket = socket
698780
self._mode = tls_mode
699781
self.settimeout = socket.settimeout
700782
self.send = socket.send
701783
self.recv = socket.recv
702784
self.close = socket.close
703785

704-
def connect(self, address: Union[bytes, str]) -> None:
786+
def connect(self, address: Tuple[str, int]) -> None:
705787
"""connect wrapper to add non-standard mode parameter"""
706788
try:
707789
return self._socket.connect(address, self._mode)
@@ -714,7 +796,7 @@ def __init__(self, iface: InterfaceType) -> None:
714796
self._iface = iface
715797

716798
def wrap_socket(
717-
self, socket: SocketType, server_hostname: Optional[str] = None
799+
self, socket: CircuitPythonSocketType, server_hostname: Optional[str] = None
718800
) -> _FakeSSLSocket:
719801
"""Return the same socket"""
720802
# pylint: disable=unused-argument

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# Uncomment the below if you use native CircuitPython modules such as
2626
# digitalio, micropython and busio. List the modules you use. Without it, the
2727
# autodoc module docs will fail to generate with a warning.
28-
autodoc_mock_imports = ["adafruit_esp32spi", "adafruit_wiznet5k", "adafruit_fona"]
28+
# autodoc_mock_imports = ["digitalio", "busio"]
2929

3030

3131
intersphinx_mapping = {

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
# SPDX-License-Identifier: Unlicense
44

55
Adafruit-Blinka
6+
typing-extensions

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
# Author details
3434
author="Adafruit Industries",
3535
author_email="[email protected]",
36-
install_requires=["Adafruit-Blinka"],
36+
install_requires=["Adafruit-Blinka", "typing-extensions"],
3737
# Choose your license
3838
license="MIT",
3939
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers

0 commit comments

Comments
 (0)