Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Fix #274: String Types Py2 vs. Py3 compatibility #275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 1 CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions 1 CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Significant contributors
* Carles Barrobés <carles@barrobes.com>
* Craig Blaszczyk <masterjakul@gmail.com>
* Damien Nadé <anvil@users.noreply.github.com>
* Eli Bishop <eli-darkly@users.noreply.github.com>
* George Sakkis <gsakkis@users.noreply.github.com>
* Jan Pieter Waagmeester <jieter@jieter.nl>
* Jelo Agnasin <jelo@icannhas.com>
Expand Down
9 changes: 8 additions & 1 deletion 9 docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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::

Expand Down
59 changes: 55 additions & 4 deletions 59 semver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions 1 setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ addopts =

[flake8]
max-line-length = 88
ignore = F821,W503
exclude =
.env,
.eggs,
Expand Down
102 changes: 102 additions & 0 deletions 102 test_typeerror-274.py
Original file line number Diff line number Diff line change
@@ -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
)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.