Skip to content

Commit 42d30b4

Browse files
authored
Merge pull request #80 from python-hyper/mypy
Use Mypy
2 parents 6fa88f8 + a6e027e commit 42d30b4

12 files changed

Lines changed: 304 additions & 56 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ tmp.py
33
htmlcov/
44
.coverage.*
55
*.py[cod]
6+
.mypy_cache
67

78
# emacs
89
*~
@@ -33,7 +34,7 @@ pip-log.txt
3334

3435
# Unit test / coverage reports
3536
.coverage
36-
.tox
37+
.tox/
3738
nosetests.xml
3839

3940
# Translations

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
zip_safe=False,
3131
license=__license__,
3232
platforms='any',
33-
install_requires=['idna>=2.5'],
33+
install_requires=[
34+
'idna>=2.5',
35+
'typing ; python_version<"3.5"',
36+
],
3437
classifiers=[
3538
'Topic :: Utilities',
3639
'Intended Audience :: Developers',

src/hyperlink/_socket.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
try:
2+
from socket import inet_pton
3+
except ImportError:
4+
from typing import TYPE_CHECKING
5+
if TYPE_CHECKING: # pragma: no cover
6+
pass
7+
else:
8+
# based on https://gist.github.com/nnemkin/4966028
9+
# this code only applies on Windows Python 2.7
10+
import ctypes
11+
import socket
12+
13+
class SockAddr(ctypes.Structure):
14+
_fields_ = [
15+
("sa_family", ctypes.c_short),
16+
("__pad1", ctypes.c_ushort),
17+
("ipv4_addr", ctypes.c_byte * 4),
18+
("ipv6_addr", ctypes.c_byte * 16),
19+
("__pad2", ctypes.c_ulong),
20+
]
21+
22+
WSAStringToAddressA = ctypes.windll.ws2_32.WSAStringToAddressA
23+
WSAAddressToStringA = ctypes.windll.ws2_32.WSAAddressToStringA
24+
25+
def inet_pton(address_family, ip_string):
26+
# type: (int, str) -> bytes
27+
addr = SockAddr()
28+
ip_string_bytes = ip_string.encode('ascii')
29+
addr.sa_family = address_family
30+
addr_size = ctypes.c_int(ctypes.sizeof(addr))
31+
32+
try:
33+
attribute, size = {
34+
socket.AF_INET: ("ipv4_addr", 4),
35+
socket.AF_INET6: ("ipv6_addr", 16),
36+
}[address_family]
37+
except KeyError:
38+
raise socket.error("unknown address family")
39+
40+
if WSAStringToAddressA(
41+
ip_string_bytes, address_family, None,
42+
ctypes.byref(addr), ctypes.byref(addr_size)
43+
) != 0:
44+
raise socket.error(ctypes.FormatError())
45+
46+
return ctypes.string_at(getattr(addr, attribute), size)

src/hyperlink/_url.py

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,9 @@
1919
import sys
2020
import string
2121
import socket
22+
from typing import Callable, Text, Type
2223
from unicodedata import normalize
23-
try:
24-
from socket import inet_pton
25-
except ImportError:
26-
inet_pton = None # defined below
24+
from ._socket import inet_pton
2725
try:
2826
from collections.abc import Mapping
2927
except ImportError: # Python 2
@@ -32,47 +30,13 @@
3230
from idna import encode as idna_encode, decode as idna_decode
3331

3432

35-
if inet_pton is None:
36-
# based on https://gist.github.com/nnemkin/4966028
37-
# this code only applies on Windows Python 2.7
38-
import ctypes
39-
40-
class SockAddr(ctypes.Structure):
41-
_fields_ = [("sa_family", ctypes.c_short),
42-
("__pad1", ctypes.c_ushort),
43-
("ipv4_addr", ctypes.c_byte * 4),
44-
("ipv6_addr", ctypes.c_byte * 16),
45-
("__pad2", ctypes.c_ulong)]
46-
47-
WSAStringToAddressA = ctypes.windll.ws2_32.WSAStringToAddressA
48-
WSAAddressToStringA = ctypes.windll.ws2_32.WSAAddressToStringA
49-
50-
def inet_pton(address_family, ip_string):
51-
addr = SockAddr()
52-
ip_string = ip_string.encode('ascii')
53-
addr.sa_family = address_family
54-
addr_size = ctypes.c_int(ctypes.sizeof(addr))
55-
56-
if WSAStringToAddressA(
57-
ip_string, address_family, None,
58-
ctypes.byref(addr), ctypes.byref(addr_size)
59-
) != 0:
60-
raise socket.error(ctypes.FormatError())
61-
62-
if address_family == socket.AF_INET:
63-
return ctypes.string_at(addr.ipv4_addr, 4)
64-
if address_family == socket.AF_INET6:
65-
return ctypes.string_at(addr.ipv6_addr, 16)
66-
raise socket.error('unknown address family')
67-
68-
6933
PY2 = (sys.version_info[0] == 2)
7034
unicode = type(u'')
7135
try:
7236
unichr
73-
except NameError:
74-
unichr = chr # py3
75-
NoneType = type(None)
37+
except NameError: # Py3
38+
unichr = chr # type: Callable[[int], Text]
39+
NoneType = type(None) # type: Type[None]
7640

7741

7842
# from boltons.typeutils

src/hyperlink/test/common.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
from typing import Any, Callable, Optional, Type
12
from unittest import TestCase
23

34

45
class HyperlinkTestCase(TestCase):
56
"""This type mostly exists to provide a backwards-compatible
67
assertRaises method for Python 2.6 testing.
78
"""
8-
def assertRaises(self, excClass, callableObj=None, *args, **kwargs):
9-
"""Fail unless an exception of class excClass is raised
9+
def assertRaises( # type: ignore[override] Doesn't match superclass, meh
10+
self, expected_exception, callableObj=None, *args, **kwargs
11+
):
12+
# type: (Type[BaseException], Optional[Callable], Any, Any) -> Any
13+
"""Fail unless an exception of class expected_exception is raised
1014
by callableObj when invoked with arguments args and keyword
1115
arguments kwargs. If a different type of exception is
1216
raised, it will not be caught, and the test case will be
@@ -28,7 +32,7 @@ def assertRaises(self, excClass, callableObj=None, *args, **kwargs):
2832
the_exception = cm.exception
2933
self.assertEqual(the_exception.error_code, 3)
3034
"""
31-
context = _AssertRaisesContext(excClass, self)
35+
context = _AssertRaisesContext(expected_exception, self)
3236
if callableObj is None:
3337
return context
3438
with context:
@@ -39,13 +43,16 @@ class _AssertRaisesContext(object):
3943
"A context manager used to implement HyperlinkTestCase.assertRaises."
4044

4145
def __init__(self, expected, test_case):
46+
# type: (Type[BaseException], TestCase) -> None
4247
self.expected = expected
4348
self.failureException = test_case.failureException
4449

4550
def __enter__(self):
51+
# type: () -> "_AssertRaisesContext"
4652
return self
4753

4854
def __exit__(self, exc_type, exc_value, tb):
55+
# type: (Optional[Type[BaseException]], Any, Any) -> bool
4956
if exc_type is None:
5057
exc_name = self.expected.__name__
5158
raise self.failureException("%s not raised" % (exc_name,))

src/hyperlink/test/test_common.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Tests for hyperlink.test.common
33
"""
4+
from typing import Any
45
from unittest import TestCase
56
from .common import HyperlinkTestCase
67

@@ -21,9 +22,11 @@ class TestHyperlink(TestCase):
2122
"""Tests for HyperlinkTestCase"""
2223

2324
def setUp(self):
25+
# type: () -> None
2426
self.hyperlink_test = HyperlinkTestCase("run")
2527

2628
def test_assertRaisesWithCallable(self):
29+
# type: () -> None
2730
"""HyperlinkTestCase.assertRaises does not raise an AssertionError
2831
when given a callable that, when called with the provided
2932
arguments, raises the expected exception.
@@ -32,6 +35,7 @@ def test_assertRaisesWithCallable(self):
3235
called_with = []
3336

3437
def raisesExpected(*args, **kwargs):
38+
# type: (Any, Any) -> None
3539
called_with.append((args, kwargs))
3640
raise _ExpectedException
3741

@@ -40,12 +44,14 @@ def raisesExpected(*args, **kwargs):
4044
self.assertEqual(called_with, [((1,), {"keyword": True})])
4145

4246
def test_assertRaisesWithCallableUnexpectedException(self):
47+
# type: () -> None
4348
"""When given a callable that raises an unexpected exception,
4449
HyperlinkTestCase.assertRaises raises that exception.
4550
4651
"""
4752

4853
def doesNotRaiseExpected(*args, **kwargs):
54+
# type: (Any, Any) -> None
4955
raise _UnexpectedException
5056

5157
try:
@@ -55,13 +61,15 @@ def doesNotRaiseExpected(*args, **kwargs):
5561
pass
5662

5763
def test_assertRaisesWithCallableDoesNotRaise(self):
64+
# type: () -> None
5865
"""HyperlinkTestCase.assertRaises raises an AssertionError when given
5966
a callable that, when called, does not raise any exception.
6067
6168
"""
6269

6370
def doesNotRaise(*args, **kwargs):
64-
return True
71+
# type: (Any, Any) -> None
72+
pass
6573

6674
try:
6775
self.hyperlink_test.assertRaises(_ExpectedException,
@@ -70,6 +78,7 @@ def doesNotRaise(*args, **kwargs):
7078
pass
7179

7280
def test_assertRaisesContextManager(self):
81+
# type: () -> None
7382
"""HyperlinkTestCase.assertRaises does not raise an AssertionError
7483
when used as a context manager with a suite that raises the
7584
expected exception. The context manager stores the exception
@@ -79,9 +88,12 @@ def test_assertRaisesContextManager(self):
7988
with self.hyperlink_test.assertRaises(_ExpectedException) as cm:
8089
raise _ExpectedException
8190

82-
self.assertTrue(isinstance(cm.exception, _ExpectedException))
91+
self.assertTrue( # type: ignore[misc] unreachable
92+
isinstance(cm.exception, _ExpectedException)
93+
)
8394

8495
def test_assertRaisesContextManagerUnexpectedException(self):
96+
# type: () -> None
8597
"""When used as a context manager with a block that raises an
8698
unexpected exception, HyperlinkTestCase.assertRaises raises
8799
that unexpected exception.
@@ -94,6 +106,7 @@ def test_assertRaisesContextManagerUnexpectedException(self):
94106
pass
95107

96108
def test_assertRaisesContextManagerDoesNotRaise(self):
109+
# type: () -> None
97110
"""HyperlinkTestcase.assertRaises raises an AssertionError when used
98111
as a context manager with a block that does not raise any
99112
exception.

src/hyperlink/test/test_decoded_url.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
class TestURL(HyperlinkTestCase):
1717

1818
def test_durl_basic(self):
19+
# type: () -> None
1920
bdurl = DecodedURL.from_text(BASIC_URL)
2021
assert bdurl.scheme == 'http'
2122
assert bdurl.host == 'example.com'
@@ -36,6 +37,8 @@ def test_durl_basic(self):
3637
assert durl.userinfo == ('user', '\0\0\0\0')
3738

3839
def test_passthroughs(self):
40+
# type: () -> None
41+
3942
# just basic tests for the methods that more or less pass straight
4043
# through to the underlying URL
4144

@@ -72,10 +75,12 @@ def test_passthroughs(self):
7275
assert durl != 1
7376

7477
def test_repr(self):
78+
# type: () -> None
7579
durl = DecodedURL.from_text(TOTAL_URL)
7680
assert repr(durl) == 'DecodedURL(url=' + repr(durl._url) + ')'
7781

7882
def test_query_manipulation(self):
83+
# type: () -> None
7984
durl = DecodedURL.from_text(TOTAL_URL)
8085

8186
assert durl.get('zot') == ['23%']
@@ -115,6 +120,7 @@ def test_query_manipulation(self):
115120
)
116121

117122
def test_equality_and_hashability(self):
123+
# type: () -> None
118124
durl = DecodedURL.from_text(TOTAL_URL)
119125
durl2 = DecodedURL.from_text(TOTAL_URL)
120126
burl = DecodedURL.from_text(BASIC_URL)
@@ -141,6 +147,7 @@ def test_equality_and_hashability(self):
141147
assert len(durl_map) == 3
142148

143149
def test_replace_roundtrip(self):
150+
# type: () -> None
144151
durl = DecodedURL.from_text(TOTAL_URL)
145152

146153
durl2 = durl.replace(scheme=durl.scheme,
@@ -156,12 +163,14 @@ def test_replace_roundtrip(self):
156163
assert durl == durl2
157164

158165
def test_replace_userinfo(self):
166+
# type: () -> None
159167
durl = DecodedURL.from_text(TOTAL_URL)
160168
with self.assertRaises(ValueError):
161169
durl.replace(userinfo=['user', 'pw', 'thiswillcauseafailure'])
162170
return
163171

164172
def test_twisted_compat(self):
173+
# type: () -> None
165174
durl = DecodedURL.from_text(TOTAL_URL)
166175

167176
assert durl == DecodedURL.fromText(TOTAL_URL)
@@ -170,9 +179,12 @@ def test_twisted_compat(self):
170179
assert durl.to_text() == durl.asText()
171180

172181
def test_percent_decode_bytes(self):
182+
# type: () -> None
173183
assert _percent_decode('%00', subencoding=False) == b'\0'
174184

175185
def test_percent_decode_mixed(self):
186+
# type: () -> None
187+
176188
# See https://github.com/python-hyper/hyperlink/pull/59 for a
177189
# nice discussion of the possibilities
178190
assert _percent_decode('abcdé%C3%A9éfg') == 'abcdéééfg'
@@ -191,6 +203,7 @@ def test_percent_decode_mixed(self):
191203
assert _percent_decode('é%25é', subencoding='ascii') == 'é%25é'
192204

193205
def test_click_decoded_url(self):
206+
# type: () -> None
194207
durl = DecodedURL.from_text(TOTAL_URL)
195208
durl_dest = DecodedURL.from_text('/tëst')
196209

src/hyperlink/test/test_parse.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
class TestURL(HyperlinkTestCase):
1919

2020
def test_parse(self):
21+
# type: () -> None
2122
purl = parse(TOTAL_URL)
2223
assert isinstance(purl, DecodedURL)
2324
assert purl.user == 'user'
@@ -35,5 +36,3 @@ def test_parse(self):
3536

3637
with self.assertRaises(UnicodeDecodeError):
3738
purl3.fragment
38-
39-
return

0 commit comments

Comments
 (0)