Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 282d185

Browse files
committed
Fix #274: Py2 vs. Py3 incompatibility
This fixes problems between different string types. In Python2 str vs. unicode and in Python3 str vs. bytes. * Add some code from six project * Suppress two flake8 issues (false positives)
1 parent e2532b2 commit 282d185

File tree

3 files changed

+185
-4
lines changed

3 files changed

+185
-4
lines changed

semver.py

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
import warnings
1111

1212

13+
PY2 = sys.version_info[0] == 2
14+
PY3 = sys.version_info[0] == 3
15+
16+
1317
__version__ = "2.10.2"
1418
__author__ = "Kostiantyn Rybnikov"
1519
__author_email__ = "[email protected]"
@@ -60,6 +64,53 @@ def cmp(a, b):
6064
return (a > b) - (a < b)
6165

6266

67+
if PY3: # pragma: no cover
68+
string_types = str, bytes
69+
text_type = str
70+
binary_type = bytes
71+
72+
def b(s):
73+
return s.encode("latin-1")
74+
75+
def u(s):
76+
return s
77+
78+
79+
else: # pragma: no cover
80+
string_types = unicode, str
81+
text_type = unicode
82+
binary_type = str
83+
84+
def b(s):
85+
return s
86+
87+
# Workaround for standalone backslash
88+
def u(s):
89+
return unicode(s.replace(r"\\", r"\\\\"), "unicode_escape")
90+
91+
92+
def ensure_str(s, encoding="utf-8", errors="strict"):
93+
# Taken from six project
94+
"""
95+
Coerce *s* to `str`.
96+
97+
For Python 2:
98+
- `unicode` -> encoded to `str`
99+
- `str` -> `str`
100+
101+
For Python 3:
102+
- `str` -> `str`
103+
- `bytes` -> decoded to `str`
104+
"""
105+
if not isinstance(s, (text_type, binary_type)):
106+
raise TypeError("not expecting type '%s'" % type(s))
107+
if PY2 and isinstance(s, text_type):
108+
s = s.encode(encoding, errors)
109+
elif PY3 and isinstance(s, binary_type):
110+
s = s.decode(encoding, errors)
111+
return s
112+
113+
63114
def deprecated(func=None, replace=None, version=None, category=DeprecationWarning):
64115
"""
65116
Decorates a function to output a deprecation warning.
@@ -144,7 +195,7 @@ def comparator(operator):
144195

145196
@wraps(operator)
146197
def wrapper(self, other):
147-
comparable_types = (VersionInfo, dict, tuple, list, str)
198+
comparable_types = (VersionInfo, dict, tuple, list, text_type, binary_type)
148199
if not isinstance(other, comparable_types):
149200
raise TypeError(
150201
"other type %r must be in %r" % (type(other), comparable_types)
@@ -423,7 +474,7 @@ def compare(self, other):
423474
0
424475
"""
425476
cls = type(self)
426-
if isinstance(other, str):
477+
if isinstance(other, string_types):
427478
other = cls.parse(other)
428479
elif isinstance(other, dict):
429480
other = cls(**other)
@@ -651,7 +702,7 @@ def parse(version):
651702
VersionInfo(major=3, minor=4, patch=5, \
652703
prerelease='pre.2', build='build.4')
653704
"""
654-
match = VersionInfo._REGEX.match(version)
705+
match = VersionInfo._REGEX.match(ensure_str(version))
655706
if match is None:
656707
raise ValueError("%s is not valid SemVer string" % version)
657708

@@ -825,7 +876,7 @@ def max_ver(ver1, ver2):
825876
>>> semver.max_ver("1.0.0", "2.0.0")
826877
'2.0.0'
827878
"""
828-
if isinstance(ver1, str):
879+
if isinstance(ver1, string_types):
829880
ver1 = VersionInfo.parse(ver1)
830881
elif not isinstance(ver1, VersionInfo):
831882
raise TypeError()

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ addopts =
1313
1414
[flake8]
1515
max-line-length = 88
16+
ignore = F821,W503
1617
exclude =
1718
.env,
1819
.eggs,

test_py2.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import pytest
2+
import sys
3+
4+
import semver
5+
6+
7+
PY2 = sys.version_info[0] == 2
8+
PY3 = sys.version_info[0] == 3
9+
10+
11+
if PY3:
12+
string_types = str, bytes
13+
text_type = str
14+
binary_type = bytes
15+
16+
# From six project:
17+
def b(s):
18+
return s.encode("utf-8") # latin-1
19+
20+
def u(s):
21+
return s
22+
23+
24+
else:
25+
string_types = unicode, str
26+
text_type = unicode
27+
binary_type = str
28+
29+
# From six project:
30+
def b(s):
31+
return s
32+
33+
# Workaround for standalone backslash
34+
def u(s):
35+
return unicode(s.replace(r"\\", r"\\\\"), "unicode_escape")
36+
37+
38+
def ensure_binary(s, encoding="utf-8", errors="strict"):
39+
"""Coerce **s** to six.binary_type.
40+
41+
For Python 2:
42+
- `unicode` -> encoded to `str`
43+
- `str` -> `str`
44+
45+
For Python 3:
46+
- `str` -> encoded to `bytes`
47+
- `bytes` -> `bytes`
48+
"""
49+
if isinstance(s, text_type):
50+
return s.encode(encoding, errors)
51+
elif isinstance(s, binary_type):
52+
return s
53+
else:
54+
raise TypeError("not expecting type '%s'" % type(s))
55+
56+
57+
def test_should_work_with_string_and_unicode():
58+
result = semver.compare(u("1.1.0"), b("1.2.2"))
59+
assert result == -1
60+
result = semver.compare(b("1.1.0"), u("1.2.2"))
61+
assert result == -1
62+
63+
64+
class TestEnsure:
65+
# From six project
66+
# grinning face emoji
67+
UNICODE_EMOJI = u("\U0001F600")
68+
BINARY_EMOJI = b"\xf0\x9f\x98\x80"
69+
70+
def test_ensure_binary_raise_type_error(self):
71+
with pytest.raises(TypeError):
72+
semver.ensure_str(8)
73+
74+
def test_errors_and_encoding(self):
75+
ensure_binary(self.UNICODE_EMOJI, encoding="latin-1", errors="ignore")
76+
with pytest.raises(UnicodeEncodeError):
77+
ensure_binary(self.UNICODE_EMOJI, encoding="latin-1", errors="strict")
78+
79+
def test_ensure_binary_raise(self):
80+
converted_unicode = ensure_binary(
81+
self.UNICODE_EMOJI, encoding="utf-8", errors="strict"
82+
)
83+
converted_binary = ensure_binary(
84+
self.BINARY_EMOJI, encoding="utf-8", errors="strict"
85+
)
86+
if semver.PY2:
87+
# PY2: unicode -> str
88+
assert converted_unicode == self.BINARY_EMOJI and isinstance(
89+
converted_unicode, str
90+
)
91+
# PY2: str -> str
92+
assert converted_binary == self.BINARY_EMOJI and isinstance(
93+
converted_binary, str
94+
)
95+
else:
96+
# PY3: str -> bytes
97+
assert converted_unicode == self.BINARY_EMOJI and isinstance(
98+
converted_unicode, bytes
99+
)
100+
# PY3: bytes -> bytes
101+
assert converted_binary == self.BINARY_EMOJI and isinstance(
102+
converted_binary, bytes
103+
)
104+
105+
def test_ensure_str(self):
106+
converted_unicode = semver.ensure_str(
107+
self.UNICODE_EMOJI, encoding="utf-8", errors="strict"
108+
)
109+
converted_binary = semver.ensure_str(
110+
self.BINARY_EMOJI, encoding="utf-8", errors="strict"
111+
)
112+
if PY2:
113+
# PY2: unicode -> str
114+
assert converted_unicode == self.BINARY_EMOJI and isinstance(
115+
converted_unicode, str
116+
)
117+
# PY2: str -> str
118+
assert converted_binary == self.BINARY_EMOJI and isinstance(
119+
converted_binary, str
120+
)
121+
else:
122+
# PY3: str -> str
123+
assert converted_unicode == self.UNICODE_EMOJI and isinstance(
124+
converted_unicode, str
125+
)
126+
# PY3: bytes -> str
127+
assert converted_binary == self.UNICODE_EMOJI and isinstance(
128+
converted_unicode, str
129+
)

0 commit comments

Comments
 (0)