Skip to content

Commit c434799

Browse files
Kilo59mindflayer
andauthored
Begin mypy type-checking (#229)
* mypy config * narrow initial type-checking * add type stubs * allow any generics * compat typing * make types * add type check step to `make test` * pre-commit fixes * remove explicit override this would require depending on typing_extensions * exceptions * implicity rexport * type ignores * remove unused * formatting --------- Co-authored-by: Giorgio Salluzzo <[email protected]>
1 parent 389d95e commit c434799

File tree

4 files changed

+69
-25
lines changed

4 files changed

+69
-25
lines changed

Makefile

+6-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ setup: develop
1818

1919
develop: install-dev-requirements install-test-requirements
2020

21-
test:
21+
types:
22+
@echo "Type checking Python files"
23+
.venv/bin/mypy --pretty
24+
@echo ""
25+
26+
test: types
2227
@echo "Running Python tests"
2328
export VIRTUAL_ENV=.venv; .venv/bin/wait-for-it --service httpbin.local:443 --service localhost:6379 --timeout 5 -- .venv/bin/pytest tests/ || exit 1
2429
@echo ""

mocket/compat.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
1+
from __future__ import annotations
2+
13
import codecs
24
import os
35
import shlex
6+
from typing import Any, Final
47

5-
ENCODING = os.getenv("MOCKET_ENCODING", "utf-8")
8+
ENCODING: Final[str] = os.getenv("MOCKET_ENCODING", "utf-8")
69

710
text_type = str
811
byte_type = bytes
912
basestring = (str,)
1013

1114

12-
def encode_to_bytes(s, encoding=ENCODING):
15+
def encode_to_bytes(s: str | bytes, encoding: str = ENCODING) -> bytes:
1316
if isinstance(s, text_type):
1417
s = s.encode(encoding)
1518
return byte_type(s)
1619

1720

18-
def decode_from_bytes(s, encoding=ENCODING):
21+
def decode_from_bytes(s: str | bytes, encoding: str = ENCODING) -> str:
1922
if isinstance(s, byte_type):
2023
s = codecs.decode(s, encoding, "ignore")
2124
return text_type(s)
2225

2326

24-
def shsplit(s):
27+
def shsplit(s: str | bytes) -> list[str]:
2528
s = decode_from_bytes(s)
2629
return shlex.split(s)
2730

mocket/utils.py

+30-20
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
1+
from __future__ import annotations
2+
13
import binascii
24
import io
35
import os
46
import ssl
5-
from typing import Tuple, Union
7+
from typing import TYPE_CHECKING, Any, Callable, ClassVar
68

79
from .compat import decode_from_bytes, encode_to_bytes
810
from .exceptions import StrictMocketException
911

12+
if TYPE_CHECKING:
13+
from _typeshed import ReadableBuffer
14+
from typing_extensions import NoReturn
15+
1016
SSL_PROTOCOL = ssl.PROTOCOL_TLSv1_2
1117

1218

1319
class MocketSocketCore(io.BytesIO):
14-
def write(self, content):
20+
def write( # type: ignore[override] # BytesIO returns int
21+
self,
22+
content: ReadableBuffer,
23+
) -> None:
1524
super(MocketSocketCore, self).write(content)
1625

1726
from mocket import Mocket
@@ -20,7 +29,7 @@ def write(self, content):
2029
os.write(Mocket.w_fd, content)
2130

2231

23-
def hexdump(binary_string):
32+
def hexdump(binary_string: bytes) -> str:
2433
r"""
2534
>>> hexdump(b"bar foobar foo") == decode_from_bytes(encode_to_bytes("62 61 72 20 66 6F 6F 62 61 72 20 66 6F 6F"))
2635
True
@@ -29,7 +38,7 @@ def hexdump(binary_string):
2938
return " ".join(a + b for a, b in zip(bs[::2], bs[1::2]))
3039

3140

32-
def hexload(string):
41+
def hexload(string: str) -> bytes:
3342
r"""
3443
>>> hexload("62 61 72 20 66 6F 6F 62 61 72 20 66 6F 6F") == encode_to_bytes("bar foobar foo")
3544
True
@@ -38,39 +47,40 @@ def hexload(string):
3847
return encode_to_bytes(binascii.unhexlify(string_no_spaces))
3948

4049

41-
def get_mocketize(wrapper_):
50+
def get_mocketize(wrapper_: Callable) -> Callable:
4251
import decorator
4352

44-
if decorator.__version__ < "5": # pragma: no cover
53+
if decorator.__version__ < "5": # type: ignore[attr-defined] # pragma: no cover
4554
return decorator.decorator(wrapper_)
46-
return decorator.decorator(wrapper_, kwsyntax=True)
55+
return decorator.decorator( # type: ignore[call-arg] # kwsyntax
56+
wrapper_,
57+
kwsyntax=True,
58+
)
4759

4860

4961
class MocketMode:
50-
__shared_state = {}
51-
STRICT = None
52-
STRICT_ALLOWED = None
62+
__shared_state: ClassVar[dict[str, Any]] = {}
63+
STRICT: ClassVar = None
64+
STRICT_ALLOWED: ClassVar = None
5365

54-
def __init__(self):
66+
def __init__(self) -> None:
5567
self.__dict__ = self.__shared_state
5668

57-
def is_allowed(self, location: Union[str, Tuple[str, int]]) -> bool:
69+
def is_allowed(self, location: str | tuple[str, int]) -> bool:
5870
"""
5971
Checks if (`host`, `port`) or at least `host`
6072
are allowed locations to perform real `socket` calls
6173
"""
6274
if not self.STRICT:
6375
return True
64-
try:
65-
host, _ = location
66-
except ValueError:
67-
host = None
68-
return location in self.STRICT_ALLOWED or (
69-
host is not None and host in self.STRICT_ALLOWED
70-
)
76+
77+
host_allowed = False
78+
if isinstance(location, tuple):
79+
host_allowed = location[0] in self.STRICT_ALLOWED
80+
return host_allowed or location in self.STRICT_ALLOWED
7181

7282
@staticmethod
73-
def raise_not_allowed():
83+
def raise_not_allowed() -> NoReturn:
7484
from .mocket import Mocket
7585

7686
current_entries = [

pyproject.toml

+26
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ test = [
5555
"twine",
5656
"fastapi",
5757
"wait-for-it",
58+
"mypy",
59+
"types-decorator",
5860
]
5961
speedups = [
6062
"xxhash;platform_python_implementation=='CPython'",
@@ -81,3 +83,27 @@ include = [
8183
exclude = [
8284
".*",
8385
]
86+
87+
[tool.mypy]
88+
python_version = "3.8"
89+
files = [
90+
"mocket/exceptions.py",
91+
"mocket/compat.py",
92+
"mocket/utils.py",
93+
# "tests/"
94+
]
95+
strict = true
96+
warn_unused_configs = true
97+
ignore_missing_imports = true
98+
warn_redundant_casts = true
99+
warn_unused_ignores = true
100+
show_error_codes = true
101+
implicit_reexport = true
102+
disallow_any_generics = false
103+
follow_imports = "silent" # enable this once majority is typed
104+
enable_error_code = ['ignore-without-code']
105+
disable_error_code = ["no-untyped-def"] # enable this once full type-coverage is reached
106+
107+
[[tool.mypy.overrides]]
108+
module = "tests.*"
109+
disable_error_code = ['type-arg', 'no-untyped-def']

0 commit comments

Comments
 (0)