Skip to content

Commit 42fd668

Browse files
committed
Fix #309: Make some functions private
Make private function in semver.version module: * Rename comparator -> _comparator, cmd -> _cmd * Remove ensure_str and integrate it into Version.parse * Rework tests in test_typeerror-274.py * Move _nat_cmp to Version and make it a class method * Remove private functions from API documentation
1 parent dc1e110 commit 42fd668

File tree

4 files changed

+64
-158
lines changed

4 files changed

+64
-158
lines changed

changelog.d/309.trivial.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Some (private) functions from the :mod:`semver.version`
2+
module has been changed.
3+
4+
The following functions got renamed:
5+
6+
* function ``semver.version.comparator`` got renamed to
7+
:func:`semver.version._comparator` as it is only useful
8+
inside the :class:`~semver.version.Version` class.
9+
* function ``semver.version.cmp`` got renamed to
10+
:func:`semver.version._cmp` as it is only useful
11+
inside the :class:`~semver.version.Version` class.
12+
13+
The following functions got integrated into the
14+
:class:`~semver.version.Version` class:
15+
16+
* function ``semver.version._nat_cmd`` as a classmethod
17+
* function ``semver.version.ensure_str``

docs/api.rst

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,6 @@ Version Handling :mod:`semver.version`
5050

5151
.. automodule:: semver.version
5252

53-
.. autofunction:: semver.version.cmp
54-
55-
.. autofunction:: semver.version.ensure_str
56-
57-
.. autofunction:: semver.version.comparator
58-
5953
.. autoclass:: semver.version.VersionInfo
6054

6155
.. autoclass:: semver.version.Version

src/semver/version.py

Lines changed: 43 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -29,37 +29,7 @@
2929
Comparator = Callable[["Version", Comparable], bool]
3030

3131

32-
def cmp(a, b): # TODO: type hints
33-
"""Return negative if a<b, zero if a==b, positive if a>b."""
34-
return (a > b) - (a < b)
35-
36-
37-
def ensure_str(s: String, encoding="utf-8", errors="strict") -> str:
38-
# Taken from six project
39-
"""
40-
Coerce *s* to `str`.
41-
42-
* `str` -> `str`
43-
* `bytes` -> decoded to `str`
44-
45-
:param s: the string to convert
46-
:param encoding: the encoding to apply, defaults to "utf-8"
47-
:param errors: set a different error handling scheme,
48-
defaults to "strict".
49-
Other possible values are `ignore`, `replace`, and
50-
`xmlcharrefreplace` as well as any other name
51-
registered with :func:`codecs.register_error`.
52-
:raises TypeError: if ``s`` is not str or bytes type
53-
:return: the converted string
54-
"""
55-
if isinstance(s, bytes):
56-
s = s.decode(encoding, errors)
57-
elif not isinstance(s, String.__args__): # type: ignore
58-
raise TypeError("not expecting type '%s'" % type(s))
59-
return s
60-
61-
62-
def comparator(operator: Comparator) -> Comparator:
32+
def _comparator(operator: Comparator) -> Comparator:
6333
"""Wrap a Version binary op method in a type-check."""
6434

6535
@wraps(operator)
@@ -78,31 +48,9 @@ def wrapper(self: "Version", other: Comparable) -> bool:
7848
return wrapper
7949

8050

81-
def _nat_cmp(a, b): # TODO: type hints
82-
def convert(text):
83-
return int(text) if re.match("^[0-9]+$", text) else text
84-
85-
def split_key(key):
86-
return [convert(c) for c in key.split(".")]
87-
88-
def cmp_prerelease_tag(a, b):
89-
if isinstance(a, int) and isinstance(b, int):
90-
return cmp(a, b)
91-
elif isinstance(a, int):
92-
return -1
93-
elif isinstance(b, int):
94-
return 1
95-
else:
96-
return cmp(a, b)
97-
98-
a, b = a or "", b or ""
99-
a_parts, b_parts = split_key(a), split_key(b)
100-
for sub_a, sub_b in zip(a_parts, b_parts):
101-
cmp_result = cmp_prerelease_tag(sub_a, sub_b)
102-
if cmp_result != 0:
103-
return cmp_result
104-
else:
105-
return cmp(len(a), len(b))
51+
def _cmd(a, b): # TODO: type hints
52+
"""Return negative if a<b, zero if a==b, positive if a>b."""
53+
return (a > b) - (a < b)
10654

10755

10856
class Version:
@@ -165,6 +113,29 @@ def __init__(
165113
self._prerelease = None if prerelease is None else str(prerelease)
166114
self._build = None if build is None else str(build)
167115

116+
@classmethod
117+
def _nat_cmp(cls, a, b): # TODO: type hints
118+
def cmp_prerelease_tag(a, b):
119+
if isinstance(a, int) and isinstance(b, int):
120+
return _cmd(a, b)
121+
elif isinstance(a, int):
122+
return -1
123+
elif isinstance(b, int):
124+
return 1
125+
else:
126+
return _cmd(a, b)
127+
128+
a, b = a or "", b or ""
129+
a_parts, b_parts = a.split("."), b.split(".")
130+
a_parts = [int(x) if re.match(r"^\d+$", x) else x for x in a_parts]
131+
b_parts = [int(x) if re.match(r"^\d+$", x) else x for x in b_parts]
132+
for sub_a, sub_b in zip(a_parts, b_parts):
133+
cmp_result = cmp_prerelease_tag(sub_a, sub_b)
134+
if cmp_result != 0:
135+
return cmp_result
136+
else:
137+
return _cmd(len(a), len(b))
138+
168139
@property
169140
def major(self) -> int:
170141
"""The major part of a version (read-only)."""
@@ -381,12 +352,12 @@ def compare(self, other: Comparable) -> int:
381352

382353
v1 = self.to_tuple()[:3]
383354
v2 = other.to_tuple()[:3]
384-
x = cmp(v1, v2)
355+
x = _cmd(v1, v2)
385356
if x:
386357
return x
387358

388359
rc1, rc2 = self.prerelease, other.prerelease
389-
rccmp = _nat_cmp(rc1, rc2)
360+
rccmp = self._nat_cmp(rc1, rc2)
390361

391362
if not rccmp:
392363
return 0
@@ -444,27 +415,27 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "Version":
444415
version = version.bump_patch()
445416
return version.bump_prerelease(prerelease_token)
446417

447-
@comparator
418+
@_comparator
448419
def __eq__(self, other: Comparable) -> bool: # type: ignore
449420
return self.compare(other) == 0
450421

451-
@comparator
422+
@_comparator
452423
def __ne__(self, other: Comparable) -> bool: # type: ignore
453424
return self.compare(other) != 0
454425

455-
@comparator
426+
@_comparator
456427
def __lt__(self, other: Comparable) -> bool:
457428
return self.compare(other) < 0
458429

459-
@comparator
430+
@_comparator
460431
def __le__(self, other: Comparable) -> bool:
461432
return self.compare(other) <= 0
462433

463-
@comparator
434+
@_comparator
464435
def __gt__(self, other: Comparable) -> bool:
465436
return self.compare(other) > 0
466437

467-
@comparator
438+
@_comparator
468439
def __ge__(self, other: Comparable) -> bool:
469440
return self.compare(other) >= 0
470441

@@ -593,15 +564,20 @@ def parse(cls, version: String) -> "Version":
593564
:param version: version string
594565
:return: a new :class:`Version` instance
595566
:raises ValueError: if version is invalid
567+
:raises TypeError: if version contains the wrong type
596568
597569
>>> semver.Version.parse('3.4.5-pre.2+build.4')
598570
Version(major=3, minor=4, patch=5, \
599571
prerelease='pre.2', build='build.4')
600572
"""
601-
version_str = ensure_str(version)
602-
match = cls._REGEX.match(version_str)
573+
if isinstance(version, bytes):
574+
version = version.decode("UTF-8")
575+
elif not isinstance(version, String.__args__): # type: ignore
576+
raise TypeError("not expecting type '%s'" % type(version))
577+
578+
match = cls._REGEX.match(version)
603579
if match is None:
604-
raise ValueError(f"{version_str} is not valid SemVer string")
580+
raise ValueError(f"{version} is not valid SemVer string")
605581

606582
matched_version_parts: Dict[str, Any] = match.groupdict()
607583

tests/test_typeerror-274.py

Lines changed: 4 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,14 @@
1-
import sys
2-
31
import pytest
4-
52
import semver
6-
import semver.version
7-
8-
PY2 = sys.version_info[0] == 2
9-
PY3 = sys.version_info[0] == 3
10-
11-
12-
def ensure_binary(s, encoding="utf-8", errors="strict"):
13-
"""
14-
Coerce ``s`` to bytes.
15-
16-
* `str` -> encoded to `bytes`
17-
* `bytes` -> `bytes`
18-
19-
:param s: the string to convert
20-
:type s: str | bytes
21-
:param encoding: the encoding to apply, defaults to "utf-8"
22-
:type encoding: str
23-
:param errors: set a different error handling scheme;
24-
other possible values are `ignore`, `replace`, and
25-
`xmlcharrefreplace` as well as any other name
26-
registered with :func:`codecs.register_error`.
27-
Defaults to "strict".
28-
:type errors: str
29-
:raises TypeError: if ``s`` is not str or bytes type
30-
:return: the converted string
31-
:rtype: str
32-
"""
33-
if isinstance(s, str):
34-
return s.encode(encoding, errors)
35-
elif isinstance(s, bytes):
36-
return s
37-
else:
38-
raise TypeError("not expecting type '%s'" % type(s))
393

404

41-
def test_should_work_with_string_and_unicode():
5+
def test_should_work_with_string_and_bytes():
426
result = semver.compare("1.1.0", b"1.2.2")
437
assert result == -1
448
result = semver.compare(b"1.1.0", "1.2.2")
459
assert result == -1
4610

4711

48-
class TestEnsure:
49-
# From six project
50-
# grinning face emoji
51-
UNICODE_EMOJI = "\U0001F600"
52-
BINARY_EMOJI = b"\xf0\x9f\x98\x80"
53-
54-
def test_ensure_binary_raise_type_error(self):
55-
with pytest.raises(TypeError):
56-
semver.version.ensure_str(8)
57-
58-
def test_errors_and_encoding(self):
59-
ensure_binary(self.UNICODE_EMOJI, encoding="latin-1", errors="ignore")
60-
with pytest.raises(UnicodeEncodeError):
61-
ensure_binary(self.UNICODE_EMOJI, encoding="latin-1", errors="strict")
62-
63-
def test_ensure_binary_raise(self):
64-
converted_unicode = ensure_binary(
65-
self.UNICODE_EMOJI, encoding="utf-8", errors="strict"
66-
)
67-
converted_binary = ensure_binary(
68-
self.BINARY_EMOJI, encoding="utf-8", errors="strict"
69-
)
70-
71-
# PY3: str -> bytes
72-
assert converted_unicode == self.BINARY_EMOJI and isinstance(
73-
converted_unicode, bytes
74-
)
75-
# PY3: bytes -> bytes
76-
assert converted_binary == self.BINARY_EMOJI and isinstance(
77-
converted_binary, bytes
78-
)
79-
80-
def test_ensure_str(self):
81-
converted_unicode = semver.version.ensure_str(
82-
self.UNICODE_EMOJI, encoding="utf-8", errors="strict"
83-
)
84-
converted_binary = semver.version.ensure_str(
85-
self.BINARY_EMOJI, encoding="utf-8", errors="strict"
86-
)
87-
88-
# PY3: str -> str
89-
assert converted_unicode == self.UNICODE_EMOJI and isinstance(
90-
converted_unicode, str
91-
)
92-
# PY3: bytes -> str
93-
assert converted_binary == self.UNICODE_EMOJI and isinstance(
94-
converted_unicode, str
95-
)
12+
def test_should_not_work_with_invalid_args():
13+
with pytest.raises(TypeError):
14+
semver.version.Version.parse(8)

0 commit comments

Comments
 (0)