diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c79012bf..e5736f33 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,6 +23,7 @@ Bug Fixes --------- * :gh:`276` (:pr:`277`): VersionInfo.parse should be a class method +* :gh:`274` (:pr:`275`): Py2 vs. Py3 incompatibility TypeError Additions diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 4460512c..a4b61fa3 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -28,6 +28,7 @@ Significant contributors * Carles Barrobés * Craig Blaszczyk * Damien Nadé +* Eli Bishop * George Sakkis * Jan Pieter Waagmeester * Jelo Agnasin diff --git a/docs/usage.rst b/docs/usage.rst index 2f23e571..010f1c0b 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -45,10 +45,17 @@ creating a version: A :class:`semver.VersionInfo` instance can be created in different ways: -* From a string:: +* From a string (a Unicode string in Python 2):: >>> semver.VersionInfo.parse("3.4.5-pre.2+build.4") VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + >>> semver.VersionInfo.parse(u"5.3.1") + VersionInfo(major=5, minor=3, patch=1, prerelease=None, build=None) + +* From a byte string:: + + >>> semver.VersionInfo.parse(b"2.3.4") + VersionInfo(major=2, minor=3, patch=4, prerelease=None, build=None) * From individual parts by a dictionary:: diff --git a/semver.py b/semver.py index f6e5bffc..4797d61d 100644 --- a/semver.py +++ b/semver.py @@ -10,6 +10,10 @@ import warnings +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + + __version__ = "2.10.2" __author__ = "Kostiantyn Rybnikov" __author_email__ = "k-bx@k-bx.com" @@ -60,6 +64,53 @@ def cmp(a, b): return (a > b) - (a < b) +if PY3: # pragma: no cover + string_types = str, bytes + text_type = str + binary_type = bytes + + def b(s): + return s.encode("latin-1") + + def u(s): + return s + + +else: # pragma: no cover + string_types = unicode, str + text_type = unicode + binary_type = str + + def b(s): + return s + + # Workaround for standalone backslash + def u(s): + return unicode(s.replace(r"\\", r"\\\\"), "unicode_escape") + + +def ensure_str(s, encoding="utf-8", errors="strict"): + # Taken from six project + """ + Coerce *s* to `str`. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + if PY2 and isinstance(s, text_type): + s = s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + s = s.decode(encoding, errors) + return s + + def deprecated(func=None, replace=None, version=None, category=DeprecationWarning): """ Decorates a function to output a deprecation warning. @@ -144,7 +195,7 @@ def comparator(operator): @wraps(operator) def wrapper(self, other): - comparable_types = (VersionInfo, dict, tuple, list, str) + comparable_types = (VersionInfo, dict, tuple, list, text_type, binary_type) if not isinstance(other, comparable_types): raise TypeError( "other type %r must be in %r" % (type(other), comparable_types) @@ -423,7 +474,7 @@ def compare(self, other): 0 """ cls = type(self) - if isinstance(other, str): + if isinstance(other, string_types): other = cls.parse(other) elif isinstance(other, dict): other = cls(**other) @@ -651,7 +702,7 @@ def parse(cls, version): VersionInfo(major=3, minor=4, patch=5, \ prerelease='pre.2', build='build.4') """ - match = cls._REGEX.match(version) + match = cls._REGEX.match(ensure_str(version)) if match is None: raise ValueError("%s is not valid SemVer string" % version) @@ -825,7 +876,7 @@ def max_ver(ver1, ver2): >>> semver.max_ver("1.0.0", "2.0.0") '2.0.0' """ - if isinstance(ver1, str): + if isinstance(ver1, string_types): ver1 = VersionInfo.parse(ver1) elif not isinstance(ver1, VersionInfo): raise TypeError() diff --git a/setup.cfg b/setup.cfg index 1cefc4bf..a22f03a3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,6 +13,7 @@ addopts = [flake8] max-line-length = 88 +ignore = F821,W503 exclude = .env, .eggs, diff --git a/test_typeerror-274.py b/test_typeerror-274.py new file mode 100644 index 00000000..2ed03d61 --- /dev/null +++ b/test_typeerror-274.py @@ -0,0 +1,102 @@ +import pytest +import sys + +import semver + + +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + + +def ensure_binary(s, encoding="utf-8", errors="strict"): + """Coerce **s** to six.binary_type. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, semver.text_type): + return s.encode(encoding, errors) + elif isinstance(s, semver.binary_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +def test_should_work_with_string_and_unicode(): + result = semver.compare(semver.u("1.1.0"), semver.b("1.2.2")) + assert result == -1 + result = semver.compare(semver.b("1.1.0"), semver.u("1.2.2")) + assert result == -1 + + +class TestEnsure: + # From six project + # grinning face emoji + UNICODE_EMOJI = semver.u("\U0001F600") + BINARY_EMOJI = b"\xf0\x9f\x98\x80" + + def test_ensure_binary_raise_type_error(self): + with pytest.raises(TypeError): + semver.ensure_str(8) + + def test_errors_and_encoding(self): + ensure_binary(self.UNICODE_EMOJI, encoding="latin-1", errors="ignore") + with pytest.raises(UnicodeEncodeError): + ensure_binary(self.UNICODE_EMOJI, encoding="latin-1", errors="strict") + + def test_ensure_binary_raise(self): + converted_unicode = ensure_binary( + self.UNICODE_EMOJI, encoding="utf-8", errors="strict" + ) + converted_binary = ensure_binary( + self.BINARY_EMOJI, encoding="utf-8", errors="strict" + ) + if semver.PY2: + # PY2: unicode -> str + assert converted_unicode == self.BINARY_EMOJI and isinstance( + converted_unicode, str + ) + # PY2: str -> str + assert converted_binary == self.BINARY_EMOJI and isinstance( + converted_binary, str + ) + else: + # PY3: str -> bytes + assert converted_unicode == self.BINARY_EMOJI and isinstance( + converted_unicode, bytes + ) + # PY3: bytes -> bytes + assert converted_binary == self.BINARY_EMOJI and isinstance( + converted_binary, bytes + ) + + def test_ensure_str(self): + converted_unicode = semver.ensure_str( + self.UNICODE_EMOJI, encoding="utf-8", errors="strict" + ) + converted_binary = semver.ensure_str( + self.BINARY_EMOJI, encoding="utf-8", errors="strict" + ) + if PY2: + # PY2: unicode -> str + assert converted_unicode == self.BINARY_EMOJI and isinstance( + converted_unicode, str + ) + # PY2: str -> str + assert converted_binary == self.BINARY_EMOJI and isinstance( + converted_binary, str + ) + else: + # PY3: str -> str + assert converted_unicode == self.UNICODE_EMOJI and isinstance( + converted_unicode, str + ) + # PY3: bytes -> str + assert converted_binary == self.UNICODE_EMOJI and isinstance( + converted_unicode, str + ) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy