1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
"""Defines characteristics of a Balrog release name.
Balrog is the server that delivers Firefox and Thunderbird updates. Release names follow
the pattern "{product}-{version}-build{build_number}"
Examples:
.. code-block:: python
from mozilla_version.balrog import BalrogReleaseName
balrog_release = BalrogReleaseName.parse('firefox-60.0.1-build1')
balrog_release.product # firefox
balrog_release.version.major_number # 60
str(balrog_release) # 'firefox-60.0.1-build1'
previous_release = BalrogReleaseName.parse('firefox-60.0-build2')
previous_release < balrog_release # True
BalrogReleaseName.parse('60.0.1') # raises PatternNotMatchedError
BalrogReleaseName.parse('firefox-60.0.1') # raises PatternNotMatchedError
# Releases can be built thanks to version classes like FirefoxVersion
BalrogReleaseName('firefox', FirefoxVersion(60, 0, 1)) # 'firefox-60.0-build1'
"""
import re
import attr
from mozilla_version.errors import PatternNotMatchedError
from mozilla_version.gecko import (
DeveditionVersion,
FennecVersion,
FirefoxVersion,
GeckoVersion,
ThunderbirdVersion,
)
from mozilla_version.parser import get_value_matched_by_regex
_VALID_ENOUGH_BALROG_RELEASE_PATTERN = re.compile(
r"^(?P<product>[a-z]+)-(?P<version>.+)$", re.IGNORECASE
)
_SUPPORTED_PRODUCTS = {
"firefox": FirefoxVersion,
"devedition": DeveditionVersion,
"fennec": FennecVersion,
"thunderbird": ThunderbirdVersion,
}
def _supported_product(string):
product = string.lower()
if product not in _SUPPORTED_PRODUCTS:
raise PatternNotMatchedError(string, patterns=("unknown product",))
return product
def _products_must_be_identical(method):
def checker(this, other):
if this.product != other.product:
raise ValueError(f'Cannot compare "{this.product}" and "{other.product}"')
return method(this, other)
return checker
@attr.s(frozen=True, eq=False, hash=True)
class BalrogReleaseName:
"""Class that validates and handles Balrog release names.
Raises:
PatternNotMatchedError: if a parsed string doesn't match the pattern of a valid
release
MissingFieldError: if a mandatory field is missing in the string. Mandatory
fields are
`product`, `major_number`, `minor_number`, and `build_number`
ValueError: if an integer can't be cast or is not (strictly) positive
TooManyTypesError: if the string matches more than 1 `VersionType`
NoVersionTypeError: if the string matches none.
"""
product = attr.ib(type=str, converter=_supported_product)
version = attr.ib(type=GeckoVersion)
def __attrs_post_init__(self):
"""Ensure attributes are sane all together."""
if self.version.build_number is None:
raise PatternNotMatchedError(self, patterns=("build_number must exist",))
@classmethod
def parse(cls, release_string):
"""Construct an object representing a valid Firefox version number."""
regex_matches = _VALID_ENOUGH_BALROG_RELEASE_PATTERN.match(release_string)
if regex_matches is None:
raise PatternNotMatchedError(
release_string, (_VALID_ENOUGH_BALROG_RELEASE_PATTERN,)
)
product = get_value_matched_by_regex("product", regex_matches, release_string)
try:
version_class = _SUPPORTED_PRODUCTS[product.lower()]
except KeyError:
raise PatternNotMatchedError(
release_string, patterns=("unknown product",)
) from None
version_string = get_value_matched_by_regex(
"version", regex_matches, release_string
)
version = version_class.parse(version_string)
return cls(product, version)
def __str__(self):
"""Implement string representation.
Computes a new string based on the given attributes.
"""
version_string = str(self.version).replace("build", "-build")
return f"{self.product}-{version_string}"
@_products_must_be_identical
def __eq__(self, other):
"""Implement `==` operator."""
return self.version == other.version
@_products_must_be_identical
def __ne__(self, other):
"""Implement `!=` operator."""
return self.version != other.version
@_products_must_be_identical
def __lt__(self, other):
"""Implement `<` operator."""
return self.version < other.version
@_products_must_be_identical
def __le__(self, other):
"""Implement `<=` operator."""
return self.version <= other.version
@_products_must_be_identical
def __gt__(self, other):
"""Implement `>` operator."""
return self.version > other.version
@_products_must_be_identical
def __ge__(self, other):
"""Implement `>=` operator."""
return self.version >= other.version
|