python 57 lines · 7 steps

Parsing and comparing semantic versions

A Version class parses semver strings with regex and orders them correctly, including prerelease precedence.

Explained by highlit
1import re
2from functools import total_ordering
3from typing import Optional
4 
5_SEMVER_RE = re.compile(
6 r"^(?P<major>0|[1-9]\d*)"
7 r"\.(?P<minor>0|[1-9]\d*)"
8 r"\.(?P<patch>0|[1-9]\d*)"
9 r"(?:-(?P<prerelease>[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?"
10 r"(?:\+(?P<build>[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$"
11)
12 
13 
14@total_ordering
15class Version:
16 def __init__(self, major, minor, patch, prerelease=None):
17 self.major = major
18 self.minor = minor
19 self.patch = patch
20 self.prerelease = prerelease
21 
22 @classmethod
23 def parse(cls, text: str) -> "Version":
24 match = _SEMVER_RE.match(text.strip())
25 if not match:
26 raise ValueError(f"invalid semantic version: {text!r}")
27 return cls(
28 int(match["major"]),
29 int(match["minor"]),
30 int(match["patch"]),
31 match["prerelease"],
32 )
33 
34 @staticmethod
35 def _prerelease_key(prerelease: Optional[str]):
36 if prerelease is None:
37 return (1,)
38 parts = []
39 for ident in prerelease.split("."):
40 if ident.isdigit():
41 parts.append((0, int(ident), ""))
42 else:
43 parts.append((1, 0, ident))
44 return (0, tuple(parts))
45 
46 def _key(self):
47 return (self.major, self.minor, self.patch, self._prerelease_key(self.prerelease))
48 
49 def __eq__(self, other):
50 if not isinstance(other, Version):
51 return NotImplemented
52 return self._key() == other._key()
53 
54 def __lt__(self, other):
55 if not isinstance(other, Version):
56 return NotImplemented
57 return self._key() < other._key()
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Encoding comparison logic as a tuple key lets Python's built-in ordering do the hard work for you.
  2. 2@total_ordering derives the full set of comparison operators from just __eq__ and __lt__.
  3. 3Semver's rule that any prerelease is lower than its release needs deliberate handling, not naive string comparison.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Parsing and comparing semantic versions — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code