From 7015137c0cba9c37db193d0ca9bc2a3e5604dc05 Mon Sep 17 00:00:00 2001 From: Julien Cristau Date: Wed, 29 May 2024 08:55:48 +0000 Subject: [PATCH] Bug 1899239 - update vendored mozilla_version to 3.1.0. r=mach-reviewers,releng-reviewers,bhearsum Differential Revision: https://phabricator.services.mozilla.com/D211816 --- .../mozilla_version-2.0.0.dist-info/RECORD | 22 - .../LICENSE | 1 - .../METADATA | 4 +- .../mozilla_version-3.1.0.dist-info/RECORD | 25 + .../WHEEL | 2 +- .../top_level.txt | 0 .../mozilla_version/__init__.py | 16 + .../mozilla_version/mozilla_version/balrog.py | 63 +- .../mozilla_version/mozilla_version/errors.py | 19 +- .../mozilla_version/mozilla_version/fenix.py | 1 + .../mozilla_version/mozilla_version/gecko.py | 881 ++++++++++++------ .../mozilla_version/mozilla_version/maven.py | 28 +- .../mozilla_version/mozilla_version/mobile.py | 236 +++-- .../mozilla_version/version.py | 142 ++- third_party/python/poetry.lock | 18 +- third_party/python/requirements.in | 2 +- third_party/python/requirements.txt | 16 +- 17 files changed, 950 insertions(+), 526 deletions(-) delete mode 100644 third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/RECORD rename third_party/python/mozilla_version/{mozilla_version-2.0.0.dist-info => mozilla_version-3.1.0.dist-info}/LICENSE (99%) rename third_party/python/mozilla_version/{mozilla_version-2.0.0.dist-info => mozilla_version-3.1.0.dist-info}/METADATA (90%) create mode 100644 third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/RECORD rename third_party/python/mozilla_version/{mozilla_version-2.0.0.dist-info => mozilla_version-3.1.0.dist-info}/WHEEL (65%) rename third_party/python/mozilla_version/{mozilla_version-2.0.0.dist-info => mozilla_version-3.1.0.dist-info}/top_level.txt (100%) diff --git a/third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/RECORD b/third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/RECORD deleted file mode 100644 index 8da74e9bde09..000000000000 --- a/third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/RECORD +++ /dev/null @@ -1,22 +0,0 @@ -mozilla_version/__init__.py,sha256=ro9IDUmjUco6GHJhqbgynResbswRnh6HL5Iv1ttDuWU,60 -mozilla_version/balrog.py,sha256=p75Ln9W5IiEzO8C-HIDmKsgdpN4hc9zbvTEOMZodNhQ,4961 -mozilla_version/errors.py,sha256=DvBsNaJdhpaT3wb4E3Rnl7KAuxnqXlElBllYOcijwbQ,2468 -mozilla_version/fenix.py,sha256=zruk3WsTMCeaRaaNi5ezxaSAb8t_8CATXpLhbVryOPM,199 -mozilla_version/gecko.py,sha256=t4JcuF7mehXqFhKIxvFM3hrEx-qZpCmF9YcwWTUuCHM,24783 -mozilla_version/maven.py,sha256=jH0F-Rq3tJJ_N3KbNE1KBi0i_BlXGZCYyjZ7K_CRxoM,1988 -mozilla_version/mobile.py,sha256=3VJgbC90NpQMUTfy75zWyK4kMKjb3E7MnE_cfdHZriM,9520 -mozilla_version/parser.py,sha256=kwaw3UeAbWgUFtCmCheY9grKwabmq9tc64JyTlPrHS8,1335 -mozilla_version/version.py,sha256=MNTbIWmRWlN4jofkt2wKmyq3z3MWGWFDqrJYN1nKxj0,7929 -mozilla_version/test/__init__.py,sha256=r9z_NrSZeN6vCBiocNFI00XPm2bveSogzO-jsLa7Q-I,87 -mozilla_version/test/test_balrog.py,sha256=olr3NBdF1wtsz2Rfnb1aT3-cD7YgWQlDMfszmgz-ZgM,7839 -mozilla_version/test/test_errors.py,sha256=oR6PZorSCYDWDRrye560gz6MCXD2E4J-eyfIVCVoenw,933 -mozilla_version/test/test_fenix.py,sha256=qs8sD39N_cM9rNEZxyCaLuxx53hIIeHZIrJe_EBpYoQ,193 -mozilla_version/test/test_gecko.py,sha256=TbIoRzfvCqtbrdIOw8aeNi-eieuZBSCE9c7nNghsOps,24494 -mozilla_version/test/test_maven.py,sha256=_KaMDq47nQNctmPfA8zbTSq35vUFtaHyLkjdP9HL0zk,3526 -mozilla_version/test/test_mobile.py,sha256=uMNZhPE1Go4vJ7hxzIs23T9qBVbNYQVs6gjN32NTP4U,11948 -mozilla_version/test/test_version.py,sha256=AeWRvkgW739mEbq3JBd1hlY9hQqHro4h9gaUuLAChqU,7441 -mozilla_version-2.0.0.dist-info/LICENSE,sha256=YCIsKMGn9qksffmOXF9EWeYk5uKF4Lm5RGevX2qzND0,15922 -mozilla_version-2.0.0.dist-info/METADATA,sha256=3ZeZKRMprBj6yz8xBbHQTvK5h3vk18joltdq29yj2gY,482 -mozilla_version-2.0.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92 -mozilla_version-2.0.0.dist-info/top_level.txt,sha256=K1r8SXa4ny0i7OTfimG0Ct33oHkXtLjuU1E5_aHBe94,16 -mozilla_version-2.0.0.dist-info/RECORD,, diff --git a/third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/LICENSE b/third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/LICENSE similarity index 99% rename from third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/LICENSE rename to third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/LICENSE index e87a115e462e..be2cc4dfb609 100644 --- a/third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/LICENSE +++ b/third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/LICENSE @@ -360,4 +360,3 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. - diff --git a/third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/METADATA b/third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/METADATA similarity index 90% rename from third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/METADATA rename to third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/METADATA index 3edee7bd3d3b..84920c2b3566 100644 --- a/third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/METADATA +++ b/third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: mozilla-version -Version: 2.0.0 +Version: 3.1.0 Summary: Process Firefox versions numbers. Tells whether they are valid or not, whether they are nightlies or regular releases, whether this version precedes that other. Home-page: https://github.com/mozilla-releng/mozilla-version Author: Mozilla Release Engineering @@ -8,5 +8,5 @@ Author-email: release+python@mozilla.com License: MPL2 Classifier: Programming Language :: Python :: 3 License-File: LICENSE -Requires-Dist: attrs (>=19.2) +Requires-Dist: attrs >=19.2 diff --git a/third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/RECORD b/third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/RECORD new file mode 100644 index 000000000000..8b5e54eb9ad3 --- /dev/null +++ b/third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/RECORD @@ -0,0 +1,25 @@ +mozilla_version/__init__.py,sha256=oITKXEftlJ9dVeCJgvhk39LIP3vnXuXoobkJj4mNAxU,391 +mozilla_version/balrog.py,sha256=WgnQEtMa16JvKisD4JTFnP2gd5e6_K9QcHxkOqQ9mJk,5072 +mozilla_version/errors.py,sha256=C8mutZqgu68eCLuD7sr4dggBym4QzsMsI1iKMNPOGQI,2401 +mozilla_version/fenix.py,sha256=10Z7jbST3xVgmLiCuJZH8GqffwChPIfJdBu1hig3jP8,200 +mozilla_version/gecko.py,sha256=8ifd1acKkanGOUn4Q0A841kr_68zAm6icrmouDZW6S8,31141 +mozilla_version/maven.py,sha256=H5bZ-9QitMdT89o4mRXkTaeb7iQc8yVTJnmauyxSImU,1971 +mozilla_version/mobile.py,sha256=09aPz1f1YyEnb4bGNeWF-rvPfnEIp4iK1DL5XK0EYU0,10146 +mozilla_version/parser.py,sha256=kwaw3UeAbWgUFtCmCheY9grKwabmq9tc64JyTlPrHS8,1335 +mozilla_version/version.py,sha256=wmB66af-0OkjPxZ-NRHraayY1ujAccIZ3q_8GAkTS-U,10212 +mozilla_version/test/__init__.py,sha256=ui4glNH_cCoz4Ex7hcZhHTcstOPJb2wcojFiNvvIALI,88 +mozilla_version/test/test_balrog.py,sha256=PY1b_q5Vhr6fhyrSB-sMwQC86CQQhLtAD2cIisbu51U,9431 +mozilla_version/test/test_default_imports.py,sha256=gj9fuHANTOQq6isXmVIoE1sXDKO46xmM_H3jxgDE7W0,303 +mozilla_version/test/test_errors.py,sha256=KLe6NiicS7aer7NURynsOWTZGZwKp8OQauX_nGJ_HdQ,1037 +mozilla_version/test/test_fenix.py,sha256=qs8sD39N_cM9rNEZxyCaLuxx53hIIeHZIrJe_EBpYoQ,193 +mozilla_version/test/test_gecko.py,sha256=vPwn1vo9iar8PD_2Mw-wi2tZaSVVzL5l3BYGOanUjqY,49787 +mozilla_version/test/test_maven.py,sha256=JnBLgIngC83roeKM4U_PL2_op_VZD9_MQT0KIhAWtxw,3904 +mozilla_version/test/test_mobile.py,sha256=2koHZxIf5hQpHcvWiuwiua7CmmBq0ADUPYhqJzpw1xM,12792 +mozilla_version/test/test_version.py,sha256=7mkWoAViZ8pto9UU6_BSLSa8anKB29cNF7lcgurMyPk,8308 +mozilla_version/test/integration/__init__.py,sha256=WbyyzEaWz3-1bSc9IOrFjBqzYJdVpLPcPy4PpJchpA8,345 +mozilla_version/test/integration/test_product_details.py,sha256=oUdCi3dbkMx_8zJLo_pjy86LCcZLUPDnVq3TVPIkarw,1369 +mozilla_version-3.1.0.dist-info/LICENSE,sha256=rxdbnZbuk8IaA2FS4bkFsLlTBNSujCySHHYJEAuo334,15921 +mozilla_version-3.1.0.dist-info/METADATA,sha256=zjWD9Nide8MuhW010gJ0VJ-snESn3wdZGk7TvwtTZTs,480 +mozilla_version-3.1.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92 +mozilla_version-3.1.0.dist-info/top_level.txt,sha256=K1r8SXa4ny0i7OTfimG0Ct33oHkXtLjuU1E5_aHBe94,16 +mozilla_version-3.1.0.dist-info/RECORD,, diff --git a/third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/WHEEL b/third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/WHEEL similarity index 65% rename from third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/WHEEL rename to third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/WHEEL index 57e3d840d59a..bab98d675883 100644 --- a/third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/WHEEL +++ b/third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/WHEEL @@ -1,5 +1,5 @@ Wheel-Version: 1.0 -Generator: bdist_wheel (0.38.4) +Generator: bdist_wheel (0.43.0) Root-Is-Purelib: true Tag: py3-none-any diff --git a/third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/top_level.txt b/third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/top_level.txt similarity index 100% rename from third_party/python/mozilla_version/mozilla_version-2.0.0.dist-info/top_level.txt rename to third_party/python/mozilla_version/mozilla_version-3.1.0.dist-info/top_level.txt diff --git a/third_party/python/mozilla_version/mozilla_version/__init__.py b/third_party/python/mozilla_version/mozilla_version/__init__.py index ba46ee264dab..4f880167d91c 100644 --- a/third_party/python/mozilla_version/mozilla_version/__init__.py +++ b/third_party/python/mozilla_version/mozilla_version/__init__.py @@ -1 +1,17 @@ """Defines characteristics of Mozilla's version numbers.""" + +from mozilla_version.gecko import ( + DeveditionVersion, + FirefoxVersion, + ThunderbirdVersion, +) +from mozilla_version.maven import MavenVersion +from mozilla_version.mobile import MobileVersion + +__all__ = [ + "DeveditionVersion", + "FirefoxVersion", + "MavenVersion", + "MobileVersion", + "ThunderbirdVersion", +] diff --git a/third_party/python/mozilla_version/mozilla_version/balrog.py b/third_party/python/mozilla_version/mozilla_version/balrog.py index ed2808314c71..c4727a115d90 100644 --- a/third_party/python/mozilla_version/mozilla_version/balrog.py +++ b/third_party/python/mozilla_version/mozilla_version/balrog.py @@ -17,23 +17,27 @@ Examples: previous_release = BalrogReleaseName.parse('firefox-60.0-build2') previous_release < balrog_release # True - invalid = BalrogReleaseName.parse('60.0.1') # raises PatternNotMatchedError - invalid = BalrogReleaseName.parse('firefox-60.0.1') # raises PatternNotMatchedError + 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, 1)) # 'firefox-60.0.1-build1' + BalrogReleaseName('firefox', FirefoxVersion(60, 0, 1)) # 'firefox-60.0-build1' """ -import attr import re -from mozilla_version.errors import PatternNotMatchedError -from mozilla_version.parser import get_value_matched_by_regex -from mozilla_version.gecko import ( - GeckoVersion, FirefoxVersion, DeveditionVersion, FennecVersion, ThunderbirdVersion -) +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[a-z]+)-(?P.+)$", re.IGNORECASE @@ -41,17 +45,17 @@ _VALID_ENOUGH_BALROG_RELEASE_PATTERN = re.compile( _SUPPORTED_PRODUCTS = { - 'firefox': FirefoxVersion, - 'devedition': DeveditionVersion, - 'fennec': FennecVersion, - 'thunderbird': ThunderbirdVersion, + "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',)) + raise PatternNotMatchedError(string, patterns=("unknown product",)) return product @@ -60,6 +64,7 @@ def _products_must_be_identical(method): if this.product != other.product: raise ValueError(f'Cannot compare "{this.product}" and "{other.product}"') return method(this, other) + return checker @@ -68,8 +73,10 @@ 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 + 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` @@ -83,23 +90,29 @@ class BalrogReleaseName: 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',)) + 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,)) + raise PatternNotMatchedError( + release_string, (_VALID_ENOUGH_BALROG_RELEASE_PATTERN,) + ) - product = get_value_matched_by_regex('product', regex_matches, release_string) + product = get_value_matched_by_regex("product", regex_matches, release_string) try: - VersionClass = _SUPPORTED_PRODUCTS[product.lower()] + version_class = _SUPPORTED_PRODUCTS[product.lower()] except KeyError: - raise PatternNotMatchedError(release_string, patterns=('unknown product',)) + raise PatternNotMatchedError( + release_string, patterns=("unknown product",) + ) from None - version_string = get_value_matched_by_regex('version', regex_matches, release_string) - version = VersionClass.parse(version_string) + version_string = get_value_matched_by_regex( + "version", regex_matches, release_string + ) + version = version_class.parse(version_string) return cls(product, version) @@ -108,8 +121,8 @@ class BalrogReleaseName: Computes a new string based on the given attributes. """ - version_string = str(self.version).replace('build', '-build') - return f'{self.product}-{version_string}' + version_string = str(self.version).replace("build", "-build") + return f"{self.product}-{version_string}" @_products_must_be_identical def __eq__(self, other): diff --git a/third_party/python/mozilla_version/mozilla_version/errors.py b/third_party/python/mozilla_version/mozilla_version/errors.py index 356fe16cc3e2..cd50d0edf192 100644 --- a/third_party/python/mozilla_version/mozilla_version/errors.py +++ b/third_party/python/mozilla_version/mozilla_version/errors.py @@ -13,13 +13,12 @@ class PatternNotMatchedError(ValueError): """Initialize error.""" number_of_patterns = len(patterns) if number_of_patterns == 0: - raise ValueError('At least one pattern must be provided') - elif number_of_patterns == 1: + raise ValueError("At least one pattern must be provided") + if number_of_patterns == 1: message = f'"{string}" does not match the pattern: {patterns[0]}' else: message = '"{}" does not match the patterns:\n - {}'.format( - string, - '\n - '.join(patterns) + string, "\n - ".join(patterns) ) super().__init__(message) @@ -35,10 +34,9 @@ class NoVersionTypeError(ValueError): def __init__(self, version_string): """Initialize error.""" super().__init__( - 'Version "{}" matched the pattern of a valid version, but it is unable to ' - 'find what type it is. This is likely a bug in mozilla-version'.format( - version_string - ) + f'Version "{version_string}" matched the pattern of a valid version, but ' + "it is unable to find what type it is. This is likely a bug in " + "mozilla-version" ) @@ -69,7 +67,6 @@ class TooManyTypesError(ValueError): def __init__(self, version_string, first_matched_type, second_matched_type): """Initialize error.""" super().__init__( - 'Release "{}" cannot match types "{}" and "{}"'.format( - version_string, first_matched_type, second_matched_type - ) + f'Release "{version_string}" cannot match types "{first_matched_type}" and ' + f'"{second_matched_type}"' ) diff --git a/third_party/python/mozilla_version/mozilla_version/fenix.py b/third_party/python/mozilla_version/mozilla_version/fenix.py index 038745aeffc5..97b067cd46bf 100644 --- a/third_party/python/mozilla_version/mozilla_version/fenix.py +++ b/third_party/python/mozilla_version/mozilla_version/fenix.py @@ -1,3 +1,4 @@ """Deprecated module for backwards compatibility.""" + # TODO remove in a future release - deprecated in favor of MobileVersion from mozilla_version.mobile import MobileVersion as FenixVersion # noqa diff --git a/third_party/python/mozilla_version/mozilla_version/gecko.py b/third_party/python/mozilla_version/mozilla_version/gecko.py index ab63b2c780de..74c77b376f2a 100644 --- a/third_party/python/mozilla_version/mozilla_version/gecko.py +++ b/third_party/python/mozilla_version/mozilla_version/gecko.py @@ -29,8 +29,8 @@ Examples: previous_version.is_release # False previous_version.is_nightly # False - invalid_version = FirefoxVersion.parse('60.1') # raises PatternNotMatchedError - invalid_version = FirefoxVersion.parse('60.0.0') # raises PatternNotMatchedError + FirefoxVersion.parse('60.1') # raises PatternNotMatchedError + FirefoxVersion.parse('60.0.0') # raises PatternNotMatchedError version = FirefoxVersion.parse('60.0') # valid # Versions can be built by raw values @@ -46,14 +46,17 @@ Examples: """ -import attr import re +import attr + from mozilla_version.errors import ( - PatternNotMatchedError, TooManyTypesError, NoVersionTypeError + NoVersionTypeError, + PatternNotMatchedError, + TooManyTypesError, ) from mozilla_version.parser import strictly_positive_int_or_none -from mozilla_version.version import BaseVersion, VersionType +from mozilla_version.version import BaseVersion, ShipItVersion, VersionType def _find_type(version): @@ -61,9 +64,7 @@ def _find_type(version): def ensure_version_type_is_not_already_defined(previous_type, candidate_type): if previous_type is not None: - raise TooManyTypesError( - str(version), previous_type, candidate_type - ) + raise TooManyTypesError(str(version), previous_type, candidate_type) if version.is_nightly: version_type = VersionType.NIGHTLY @@ -79,7 +80,9 @@ def _find_type(version): ensure_version_type_is_not_already_defined(version_type, VersionType.ESR) version_type = VersionType.ESR if version.is_release_candidate: - ensure_version_type_is_not_already_defined(version_type, VersionType.RELEASE_CANDIDATE) + ensure_version_type_is_not_already_defined( + version_type, VersionType.RELEASE_CANDIDATE + ) version_type = VersionType.RELEASE_CANDIDATE if version.is_release: ensure_version_type_is_not_already_defined(version_type, VersionType.RELEASE) @@ -92,26 +95,28 @@ def _find_type(version): @attr.s(frozen=True, eq=False, hash=True) -class GeckoVersion(BaseVersion): +class GeckoVersion(ShipItVersion): """Class that validates and handles version numbers for Gecko-based products. - You may want to use specific classes like FirefoxVersion. These classes define edge cases - that were shipped. + You may want to use specific classes like FirefoxVersion. These classes define edge + cases that were shipped. Raises: - PatternNotMatchedError: if the string doesn't match the pattern of a valid version number - MissingFieldError: if a mandatory field is missing in the string. Mandatory fields are - `major_number` and `minor_number` + PatternNotMatchedError: if the string doesn't match the pattern of a valid + version number + MissingFieldError: if a mandatory field is missing in the string. Mandatory + fields are `major_number` and `minor_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. """ - # XXX This pattern doesn't catch all subtleties of a Firefox version (like 32.5 isn't valid). - # This regex is intended to assign numbers. Then checks are done by attrs and - # __attrs_post_init__() - _VALID_ENOUGH_VERSION_PATTERN = re.compile(r""" + # XXX This pattern doesn't catch all subtleties of a Firefox version (like 32.5 + # isn't valid). This regex is intended to assign numbers. Then checks are done by + # attrs and __attrs_post_init__() + _VALID_ENOUGH_VERSION_PATTERN = re.compile( + r""" ^(?P\d+) \.(?P\d+) (\.(?P\d+))? @@ -123,123 +128,200 @@ class GeckoVersion(BaseVersion): |b(?P\d+) |(?Pesr) )? - -?(build(?P\d+))?$""", re.VERBOSE) + -?(build(?P\d+))?$""", + re.VERBOSE, + ) - _OPTIONAL_NUMBERS = BaseVersion._OPTIONAL_NUMBERS + ( - 'old_fourth_number', 'release_candidate_number', 'beta_number', 'build_number' + _OPTIONAL_NUMBERS = ( + *BaseVersion._OPTIONAL_NUMBERS, + "old_fourth_number", + "release_candidate_number", + "beta_number", + "build_number", ) _ALL_NUMBERS = BaseVersion._ALL_NUMBERS + _OPTIONAL_NUMBERS - _KNOWN_ESR_MAJOR_NUMBERS = (10, 17, 24, 31, 38, 45, 52, 60, 68, 78, 91, 102, 115) + _KNOWN_ESR_MAJOR_NUMBERS = ( + 10, + 17, + 24, + 31, + 38, + 45, + 52, + 60, + 68, + 78, + 91, + 102, + 115, + 128, + ) + + _BOOLEANS_NOT_INFERRED_BY_NUMBERS = ( + "is_nightly", + "is_aurora_or_devedition", + "is_esr", + ) _LAST_AURORA_DEVEDITION_AS_VERSION_TYPE = 54 - build_number = attr.ib(type=int, converter=strictly_positive_int_or_none, default=None) - beta_number = attr.ib(type=int, converter=strictly_positive_int_or_none, default=None) + build_number = attr.ib( + type=int, converter=strictly_positive_int_or_none, default=None + ) + beta_number = attr.ib( + type=int, converter=strictly_positive_int_or_none, default=None + ) is_nightly = attr.ib(type=bool, default=False) is_aurora_or_devedition = attr.ib(type=bool, default=False) is_esr = attr.ib(type=bool, default=False) - old_fourth_number = attr.ib(type=int, converter=strictly_positive_int_or_none, default=None) + old_fourth_number = attr.ib( + type=int, converter=strictly_positive_int_or_none, default=None + ) release_candidate_number = attr.ib( type=int, converter=strictly_positive_int_or_none, default=None ) - version_type = attr.ib(init=False, default=attr.Factory(_find_type, takes_self=True)) + version_type = attr.ib( + init=False, default=attr.Factory(_find_type, takes_self=True) + ) + + def _get_all_error_messages_for_attributes(self): + error_messages = super()._get_all_error_messages_for_attributes() - def __attrs_post_init__(self): - """Ensure attributes are sane all together.""" # General checks - error_messages = [ - pattern_message - for condition, pattern_message in (( - not self.is_four_digit_scheme and self.old_fourth_number is not None, - 'The old fourth number can only be defined on Gecko 1.5.x.y or 2.0.x.y', - ), ( - self.beta_number is not None and self.patch_number is not None, - 'Beta number and patch number cannot be both defined', - )) - if condition - ] - - # Firefox 5 is the first version to implement the rapid release model, which defines - # the scheme used so far. - if self.is_rapid_release_scheme: - error_messages.extend([ + error_messages.extend( + [ pattern_message - for condition, pattern_message in (( - self.release_candidate_number is not None, - 'Release candidate number cannot be defined starting Gecko 5', - ), ( - self.minor_number == 0 and self.patch_number == 0, - 'Minor number and patch number cannot be both equal to 0', - ), ( - self.minor_number != 0 and self.patch_number is None, - 'Patch number cannot be undefined if minor number is greater than 0', - ), ( - self.patch_number is not None and self.is_nightly, - 'Patch number cannot be defined on a nightly version', - ), ( - self.patch_number is not None and self.is_aurora_or_devedition, - 'Patch number cannot be defined on an aurora version', - ), ( - self.major_number > self._LAST_AURORA_DEVEDITION_AS_VERSION_TYPE and - self.is_aurora_or_devedition, - 'Last aurora/devedition version was 54.0a2. Please use the DeveditionVersion ' - 'class, past this version.', - ), ( - self.major_number not in self._KNOWN_ESR_MAJOR_NUMBERS and self.is_esr, - '"{}" is not a valid ESR major number. Valid ones are: {}'.format( - self.major_number, self._KNOWN_ESR_MAJOR_NUMBERS - ) - )) + for condition, pattern_message in ( + ( + not self.is_four_digit_scheme + and self.old_fourth_number is not None, + "The old fourth number can only be defined on Gecko 1.5.x.y or " + "2.0.x.y", + ), + ( + self.beta_number is not None and self.patch_number is not None, + "Beta number and patch number cannot be both defined", + ), + ( + self.is_major and self.is_esr, + "Version cannot be both a major and an ESR one", + ), + ( + self.is_stability and self.is_esr, + "Version cannot be both a stability and an ESR one", + ), + ( + self.is_development and self.is_esr, + "Version cannot be both a development and an ESR one", + ), + ) if condition - ]) + ] + ) + + # Firefox 5 is the first version to implement the rapid release model, which + # defines the scheme used so far. + if self.is_rapid_release_scheme: + error_messages.extend( + [ + pattern_message + for condition, pattern_message in ( + ( + self.release_candidate_number is not None, + "Release candidate number cannot be defined starting " + "Gecko 5", + ), + ( + self.minor_number == 0 and self.patch_number == 0, + "Minor number and patch number cannot be both equal to 0", + ), + ( + self.minor_number != 0 and self.patch_number is None, + "Patch number cannot be undefined if minor number is " + "greater than 0", + ), + ( + self.patch_number is not None and self.is_nightly, + "Patch number cannot be defined on a nightly version", + ), + ( + self.patch_number is not None + and self.is_aurora_or_devedition, + "Patch number cannot be defined on an aurora version", + ), + ( + self.major_number + > self._LAST_AURORA_DEVEDITION_AS_VERSION_TYPE + and self.is_aurora_or_devedition, + "Last aurora/devedition version was 54.0a2. Please use the " + "DeveditionVersion class, past this version.", + ), + ( + self.major_number not in self._KNOWN_ESR_MAJOR_NUMBERS + and self.is_esr, + f'"{self.major_number}" is not a valid ESR major number. ' + f"Valid ones are: {self._KNOWN_ESR_MAJOR_NUMBERS}", + ), + ) + if condition + ] + ) else: if self.release_candidate_number is not None: - error_messages.extend([ - pattern_message - for condition, pattern_message in (( - self.patch_number is not None, - 'Release candidate and patch number cannot be both defined', - ), ( - self.old_fourth_number is not None, - 'Release candidate and the old fourth number cannot be both defined', - ), ( - self.beta_number is not None, - 'Release candidate and beta number cannot be both defined', - )) - if condition - ]) + error_messages.extend( + [ + pattern_message + for condition, pattern_message in ( + ( + self.patch_number is not None, + "Release candidate and patch number cannot be both " + "defined", + ), + ( + self.old_fourth_number is not None, + "Release candidate and the old fourth number cannot be " + "both defined", + ), + ( + self.beta_number is not None, + "Release candidate and beta number cannot be both " + "defined", + ), + ) + if condition + ] + ) if self.old_fourth_number is not None and self.patch_number != 0: error_messages.append( - 'The old fourth number cannot be defined if the patch number is not 0 ' - '(we have never shipped a release that did so)' + "The old fourth number cannot be defined if the patch number is " + "not 0 (we have never shipped a release that did so)" ) - if error_messages: - raise PatternNotMatchedError(self, patterns=error_messages) + return error_messages @classmethod def parse(cls, version_string): """Construct an object representing a valid Firefox version number.""" return super().parse( - version_string, regex_groups=('is_nightly', 'is_aurora_or_devedition', 'is_esr') + version_string, regex_groups=cls._BOOLEANS_NOT_INFERRED_BY_NUMBERS ) @property def is_beta(self): - """Return `True` if `GeckoVersion` was built with a string matching a beta version.""" + """Return `True` if `GeckoVersion` was built with a `beta_number`.""" return self.beta_number is not None @property def is_release_candidate(self): - """Return `True` if `GeckoVersion` was built with a string matching an RC version.""" + """Return `True` if `GeckoVersion` was built with `release_candidate_number`.""" return self.release_candidate_number is not None @property def is_rapid_release_scheme(self): - """Return `True` if `GeckoVersion` was built with against the rapid release scheme.""" + """Return `True` if `GeckoVersion` was built with the rapid release scheme.""" return self.major_number >= 5 @property @@ -248,18 +330,53 @@ class GeckoVersion(BaseVersion): Only Firefox 1.5.x.y and 2.0.x.y were. """ - return ( - all((self.major_number == 1, self.minor_number == 5)) or - all((self.major_number == 2, self.minor_number == 0)) + return all((self.major_number == 1, self.minor_number == 5)) or all( + (self.major_number == 2, self.minor_number == 0) ) @property def is_release(self): - """Return `True` if `GeckoVersion` was built with a string matching a release version.""" - return not any(( - self.is_nightly, self.is_aurora_or_devedition, self.is_beta, - self.is_release_candidate, self.is_esr - )) + """Return `True` if `GeckoVersion` was built as a release version.""" + return not any( + ( + self.is_nightly, + self.is_aurora_or_devedition, + self.is_beta, + self.is_release_candidate, + self.is_esr, + ) + ) + + @property + def is_major(self): + """Return `True` if `GeckoVersion` is considered to be a major version. + + It's usually the .0 release but some exceptions may occur. ESR are not + considered major versions. + """ + return super().is_major and not self.is_esr + + @property + def is_stability(self): + """Return `True` if `GeckoVersion` is a version that fixed a major one.""" + conditions = [ + not self.is_esr, + ] + if self.is_four_digit_scheme: + # super().is_stability has an incompatible condition. So we must not use it + # in this specific case. + conditions.extend( + [ + not self.is_development, + not self.is_major, + self.patch_number == 0, + self.old_fourth_number != 0, + ] + ) + else: + conditions.append(super().is_stability) + + return all(conditions) def __str__(self): """Implement string representation. @@ -269,52 +386,54 @@ class GeckoVersion(BaseVersion): string = super().__str__() if self.old_fourth_number is not None: - string = f'{string}.{self.old_fourth_number}' + string = f"{string}.{self.old_fourth_number}" if self.is_nightly: - string = f'{string}a1' + string = f"{string}a1" elif self.is_aurora_or_devedition: - string = f'{string}a2' + string = f"{string}a2" elif self.is_beta: - string = f'{string}b{self.beta_number}' + string = f"{string}b{self.beta_number}" elif self.is_release_candidate: - string = f'{string}rc{self.release_candidate_number}' + string = f"{string}rc{self.release_candidate_number}" elif self.is_esr: - string = f'{string}esr' + string = f"{string}esr" if self.build_number is not None: - string = f'{string}build{self.build_number}' + string = f"{string}build{self.build_number}" return string def __eq__(self, other): """Implement `==` operator. - A version is considered equal to another if all numbers match and if they are of the same - `VersionType`. Like said in `VersionType`, release and ESR are considered equal (if they - share the same numbers). If a version contains a build number but not the other, the build - number won't be considered in the comparison. + A version is considered equal to another if all numbers match and if they are + of the same `VersionType`. Like said in `VersionType`, release and ESR are + considered equal (if they share the same numbers). If a version contains a + build number but not the other, the build number won't be considered in the + comparison. Examples: .. code-block:: python - assert GeckoVersion.parse('60.0') == GeckoVersion.parse('60.0') - assert GeckoVersion.parse('60.0') == GeckoVersion.parse('60.0esr') - assert GeckoVersion.parse('60.0') == GeckoVersion.parse('60.0build1') - assert GeckoVersion.parse('60.0build1') == GeckoVersion.parse('60.0build1') + GeckoVersion.parse('60.0') == GeckoVersion.parse('60.0') + GeckoVersion.parse('60.0') == GeckoVersion.parse('60.0esr') + GeckoVersion.parse('60.0') == GeckoVersion.parse('60.0build1') + GeckoVersion.parse('60.0build1') == GeckoVersion.parse('60.0build1') - assert GeckoVersion.parse('60.0') != GeckoVersion.parse('61.0') - assert GeckoVersion.parse('60.0') != GeckoVersion.parse('60.1.0') - assert GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0.1') - assert GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0a1') - assert GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0a2') - assert GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0b1') - assert GeckoVersion.parse('60.0build1') != GeckoVersion.parse('60.0build2') + GeckoVersion.parse('60.0') != GeckoVersion.parse('61.0') + GeckoVersion.parse('60.0') != GeckoVersion.parse('60.1.0') + GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0.1') + GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0a1') + GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0a2') + GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0b1') + GeckoVersion.parse('60.0build1') != GeckoVersion.parse('60.0build2') """ return super().__eq__(other) - def _compare(self, other): + # TODO: Make this function simpler to pass complexity check (C901) + def _compare(self, other): # noqa C901 """Compare this release with another. Returns: @@ -332,7 +451,9 @@ class GeckoVersion(BaseVersion): if difference != 0: return difference - difference = self._substract_other_number_from_this_number(other, 'old_fourth_number') + difference = self._substract_other_number_from_this_number( + other, "old_fourth_number" + ) if difference != 0: return difference @@ -346,13 +467,15 @@ class GeckoVersion(BaseVersion): return beta_difference if self.is_release_candidate and other.is_release_candidate: - rc_difference = self.release_candidate_number - other.release_candidate_number + rc_difference = ( + self.release_candidate_number - other.release_candidate_number + ) if rc_difference != 0: return rc_difference # Build numbers are a special case. We might compare a regular version number - # (like "32.0b8") versus a release build (as in "32.0b8build1"). As a consequence, - # we only compare build_numbers when we both have them. + # (like "32.0b8") versus a release build (as in "32.0b8build1"). As a + # consequence, we only compare build_numbers when we both have them. try: return self.build_number - other.build_number except TypeError: @@ -363,55 +486,61 @@ class GeckoVersion(BaseVersion): def _compare_version_type(self, other): return self.version_type.compare(other.version_type) - def _create_bump_kwargs(self, field): - if field == 'build_number' and self.build_number is None: - raise ValueError('Cannot bump the build number if it is not already set') + # TODO: Make this function simpler to pass complexity check (C901) + def _create_bump_kwargs(self, field): # noqa C901 + if field == "build_number" and self.build_number is None: + raise ValueError("Cannot bump the build number if it is not already set") bump_kwargs = super()._create_bump_kwargs(field) - if field == 'major_number' and self.is_esr: + if field == "major_number" and self.is_esr: current_esr_index = self._KNOWN_ESR_MAJOR_NUMBERS.index(self.major_number) try: - next_major_esr_number = self._KNOWN_ESR_MAJOR_NUMBERS[current_esr_index + 1] + next_major_esr_number = self._KNOWN_ESR_MAJOR_NUMBERS[ + current_esr_index + 1 + ] except IndexError: raise ValueError( - "Cannot bump the major number past last known major ESR. We don't know it yet." - ) - bump_kwargs['major_number'] = next_major_esr_number + "Cannot bump the major number past last known major ESR. We don't " + "know it yet." + ) from None + bump_kwargs["major_number"] = next_major_esr_number - if field != 'build_number' and bump_kwargs.get('build_number') == 0: - del bump_kwargs['build_number'] - if bump_kwargs.get('beta_number') == 0: + if field != "build_number" and bump_kwargs.get("build_number") == 0: + del bump_kwargs["build_number"] + if bump_kwargs.get("beta_number") == 0: if self.is_beta: - bump_kwargs['beta_number'] = 1 + bump_kwargs["beta_number"] = 1 else: - del bump_kwargs['beta_number'] + del bump_kwargs["beta_number"] - if field != 'old_fourth_number' and not self.is_four_digit_scheme: - del bump_kwargs['old_fourth_number'] - if bump_kwargs.get('minor_number') == 0 and bump_kwargs.get('patch_number') == 0: - del bump_kwargs['patch_number'] + if field != "old_fourth_number" and not self.is_four_digit_scheme: + del bump_kwargs["old_fourth_number"] + if ( + bump_kwargs.get("minor_number") == 0 + and bump_kwargs.get("patch_number") == 0 + ): + del bump_kwargs["patch_number"] if self.is_four_digit_scheme: - if ( - bump_kwargs.get('patch_number') == 0 and - bump_kwargs.get('old_fourth_number') in (0, None) - ): - del bump_kwargs['patch_number'] - del bump_kwargs['old_fourth_number'] + if bump_kwargs.get("patch_number") == 0 and bump_kwargs.get( + "old_fourth_number" + ) in (0, None): + del bump_kwargs["patch_number"] + del bump_kwargs["old_fourth_number"] elif ( - bump_kwargs.get('patch_number') is None and - bump_kwargs.get('old_fourth_number') is not None and - bump_kwargs.get('old_fourth_number') > 0 + bump_kwargs.get("patch_number") is None + and bump_kwargs.get("old_fourth_number") is not None + and bump_kwargs.get("old_fourth_number") > 0 ): - bump_kwargs['patch_number'] = 0 + bump_kwargs["patch_number"] = 0 - if field != 'release_candidate_number' and self.is_rapid_release_scheme: - del bump_kwargs['release_candidate_number'] + if field != "release_candidate_number" and self.is_rapid_release_scheme: + del bump_kwargs["release_candidate_number"] - bump_kwargs['is_nightly'] = self.is_nightly - bump_kwargs['is_aurora_or_devedition'] = self.is_aurora_or_devedition - bump_kwargs['is_esr'] = self.is_esr + bump_kwargs["is_nightly"] = self.is_nightly + bump_kwargs["is_aurora_or_devedition"] = self.is_aurora_or_devedition + bump_kwargs["is_esr"] = self.is_esr return bump_kwargs @@ -419,8 +548,8 @@ class GeckoVersion(BaseVersion): """Bump version type to the next one. Returns: - A new GeckoVersion with the version type set to the next one. Builds numbers are reset, - if originally set. + A new GeckoVersion with the version type set to the next one. Builds numbers + are reset, if originally set. For instance: * 32.0a1 is bumped to 32.0b1 @@ -434,33 +563,37 @@ class GeckoVersion(BaseVersion): return self.__class__(**self._create_bump_version_type_kwargs()) except (ValueError, PatternNotMatchedError) as e: raise ValueError( - 'Cannot bump version type for version "{}". New version number is not valid. ' - 'Cause: {}'.format(self, e) + f'Cannot bump version type for version "{self}". New version number is ' + f"not valid. Cause: {e}" ) from e def _create_bump_version_type_kwargs(self): bump_version_type_kwargs = { - 'major_number': self.major_number, - 'minor_number': self.minor_number, - 'patch_number': self.patch_number, + "major_number": self.major_number, + "minor_number": self.minor_number, + "patch_number": self.patch_number, } - if self.is_nightly and self.major_number <= self._LAST_AURORA_DEVEDITION_AS_VERSION_TYPE: - bump_version_type_kwargs['is_aurora_or_devedition'] = True - elif ( - self.is_nightly and self.major_number > self._LAST_AURORA_DEVEDITION_AS_VERSION_TYPE or - self.is_aurora_or_devedition + if ( + self.is_nightly + and self.major_number <= self._LAST_AURORA_DEVEDITION_AS_VERSION_TYPE ): - bump_version_type_kwargs['beta_number'] = 1 + bump_version_type_kwargs["is_aurora_or_devedition"] = True + elif ( + self.is_nightly + and self.major_number > self._LAST_AURORA_DEVEDITION_AS_VERSION_TYPE + or self.is_aurora_or_devedition + ): + bump_version_type_kwargs["beta_number"] = 1 elif self.is_beta and not self.is_rapid_release_scheme: - bump_version_type_kwargs['release_candidate_number'] = 1 + bump_version_type_kwargs["release_candidate_number"] = 1 elif self.is_release: - bump_version_type_kwargs['is_esr'] = True + bump_version_type_kwargs["is_esr"] = True elif self.is_esr: - raise ValueError('There is no higher version type than ESR.') + raise ValueError("There is no higher version type than ESR.") if self.build_number is not None: - bump_version_type_kwargs['build_number'] = 1 + bump_version_type_kwargs["build_number"] = 1 return bump_version_type_kwargs @@ -468,132 +601,236 @@ class GeckoVersion(BaseVersion): class _VersionWithEdgeCases(GeckoVersion): def __attrs_post_init__(self): for edge_case in self._RELEASED_EDGE_CASES: - if all( - getattr(self, number_type) == edge_case.get(number_type, None) - for number_type in self._ALL_NUMBERS - if number_type != 'build_number' - ): - if self.build_number is None: - return - elif self.build_number == edge_case.get('build_number', None): - return + if self._do_all_numbers_match(edge_case): + return super().__attrs_post_init__() + @property + def is_major(self): + for edge_case in self._RELEASED_EDGE_CASES_MAJOR: + if self._do_all_numbers_match(edge_case): + return True + + return super().is_major + + def _do_all_numbers_match(self, edge_case): + conditions = [ + getattr(self, number_type) == edge_case.get(number_type, None) + for number_type in self._ALL_NUMBERS + if number_type != "build_number" + ] + conditions.extend( + [ + getattr(self, boolean_attribute, False) + == edge_case.get(boolean_attribute, False) + for boolean_attribute in self._BOOLEANS_NOT_INFERRED_BY_NUMBERS + ] + ) + + if all(conditions): + if self.build_number is None: + return True + if self.build_number == edge_case.get("build_number", None): + return True + + return False + class FirefoxVersion(_VersionWithEdgeCases): """Class that validates and handles Firefox version numbers.""" - _RELEASED_EDGE_CASES = ({ - 'major_number': 1, - 'minor_number': 5, - 'patch_number': 0, - 'old_fourth_number': 1, - 'release_candidate_number': 1, - }, { - 'major_number': 33, - 'minor_number': 1, - 'build_number': 1, - }, { - 'major_number': 33, - 'minor_number': 1, - 'build_number': 2, - }, { - 'major_number': 33, - 'minor_number': 1, - 'build_number': 3, - }, { - 'major_number': 38, - 'minor_number': 0, - 'patch_number': 5, - 'beta_number': 1, - 'build_number': 1, - }, { - 'major_number': 38, - 'minor_number': 0, - 'patch_number': 5, - 'beta_number': 1, - 'build_number': 2, - }, { - 'major_number': 38, - 'minor_number': 0, - 'patch_number': 5, - 'beta_number': 2, - 'build_number': 1, - }, { - 'major_number': 38, - 'minor_number': 0, - 'patch_number': 5, - 'beta_number': 3, - 'build_number': 1, - }) + _RELEASED_EDGE_CASES = ( + { + "major_number": 1, + "minor_number": 5, + "patch_number": 0, + "old_fourth_number": 1, + "release_candidate_number": 1, + }, + { + "major_number": 33, + "minor_number": 1, + "build_number": 1, + }, + { + "major_number": 33, + "minor_number": 1, + "build_number": 2, + }, + { + "major_number": 33, + "minor_number": 1, + "build_number": 3, + }, + { + "major_number": 38, + "minor_number": 0, + "patch_number": 5, + "beta_number": 1, + "build_number": 1, + }, + { + "major_number": 38, + "minor_number": 0, + "patch_number": 5, + "beta_number": 1, + "build_number": 2, + }, + { + "major_number": 38, + "minor_number": 0, + "patch_number": 5, + "beta_number": 2, + "build_number": 1, + }, + { + "major_number": 38, + "minor_number": 0, + "patch_number": 5, + "beta_number": 3, + "build_number": 1, + }, + ) + + _RELEASED_EDGE_CASES_MAJOR = ( + { + "major_number": 1, + "minor_number": 5, + }, + { + "major_number": 3, + "minor_number": 5, + }, + { + "major_number": 3, + "minor_number": 6, + }, + { + "major_number": 14, + "minor_number": 0, + "patch_number": 1, + "build_number": 1, + }, + { + "major_number": 33, + "minor_number": 1, + "build_number": 1, + }, + { + "major_number": 33, + "minor_number": 1, + "build_number": 2, + }, + { + "major_number": 33, + "minor_number": 1, + "build_number": 3, + }, + { + "major_number": 125, + "minor_number": 0, + "patch_number": 1, + "build_number": 1, + }, + ) class DeveditionVersion(GeckoVersion): - """Class that validates and handles Devedition after it became an equivalent to beta.""" + """Class that validates and handles Devedition after it shipped alongside beta.""" # No edge case were shipped - def __attrs_post_init__(self): + def _get_all_error_messages_for_attributes(self): """Ensure attributes are sane all together.""" + error_messages = super()._get_all_error_messages_for_attributes() + if ( - (not self.is_beta) or - (self.major_number < 54) or - (self.major_number == 54 and self.beta_number < 11) + (not self.is_beta) + or (self.major_number < 54) + or (self.major_number == 54 and self.beta_number < 11) ): - raise PatternNotMatchedError( - self, patterns=('Devedition as a product must be a beta >= 54.0b11',) - ) + error_messages.append("Devedition as a product must be a beta >= 54.0b11") + + return error_messages class FennecVersion(_VersionWithEdgeCases): """Class that validates and handles Fennec (Firefox for Android) version numbers.""" - _RELEASED_EDGE_CASES = ({ - 'major_number': 33, - 'minor_number': 1, - 'build_number': 1, - }, { - 'major_number': 33, - 'minor_number': 1, - 'build_number': 2, - }, { - 'major_number': 38, - 'minor_number': 0, - 'patch_number': 5, - 'beta_number': 4, - 'build_number': 1, - }) + _RELEASED_EDGE_CASES = ( + { + "major_number": 33, + "minor_number": 1, + "build_number": 1, + }, + { + "major_number": 33, + "minor_number": 1, + "build_number": 2, + }, + { + "major_number": 38, + "minor_number": 0, + "patch_number": 5, + "beta_number": 4, + "build_number": 1, + }, + ) + + _RELEASED_EDGE_CASES_MAJOR = ( + { + "major_number": 1, + "minor_number": 1, + }, + { + "major_number": 14, + "minor_number": 0, + "patch_number": 1, + }, + { + "major_number": 33, + "minor_number": 1, + "build_number": 1, + }, + { + "major_number": 68, + "minor_number": 1, + "build_number": 1, + }, + ) _LAST_FENNEC_VERSION = 68 def __attrs_post_init__(self): """Ensure attributes are sane all together.""" - # Versions matching 68.Xa1, 68.XbN, or simply 68.X are expected since bug 1523402. The - # latter is needed because of the version.txt of beta + # Versions matching 68.Xa1, 68.XbN, or simply 68.X are expected since + # bug 1523402. The latter is needed because of the version.txt of beta if ( - self.major_number == self._LAST_FENNEC_VERSION and - self.minor_number > 0 and - self.patch_number is None + self.major_number == self._LAST_FENNEC_VERSION + and self.minor_number > 0 + and self.patch_number is None ): return - if self.major_number > self._LAST_FENNEC_VERSION: - raise PatternNotMatchedError( - self, patterns=(f'Last Fennec version is {self._LAST_FENNEC_VERSION}',) - ) - super().__attrs_post_init__() + def _get_all_error_messages_for_attributes(self): + error_messages = super()._get_all_error_messages_for_attributes() + if self.major_number > self._LAST_FENNEC_VERSION: + error_messages.append(f"Last Fennec version is {self._LAST_FENNEC_VERSION}") + + return error_messages + def _create_bump_kwargs(self, field): kwargs = super()._create_bump_kwargs(field) if ( - field != 'patch_number' and - kwargs['major_number'] == self._LAST_FENNEC_VERSION and - (kwargs['is_nightly'] or kwargs.get('beta_number')) + field != "patch_number" + and kwargs["major_number"] == self._LAST_FENNEC_VERSION + and (kwargs["is_nightly"] or kwargs.get("beta_number")) ): - del kwargs['patch_number'] + del kwargs["patch_number"] return kwargs @@ -601,48 +838,75 @@ class FennecVersion(_VersionWithEdgeCases): class ThunderbirdVersion(_VersionWithEdgeCases): """Class that validates and handles Thunderbird version numbers.""" - _RELEASED_EDGE_CASES = ({ - 'major_number': 1, - 'minor_number': 5, - 'beta_number': 1, - }, { - 'major_number': 1, - 'minor_number': 5, - 'beta_number': 2, - }, { - 'major_number': 3, - 'minor_number': 1, - 'beta_number': 1, - }, { - 'major_number': 3, - 'minor_number': 1, - }, { - 'major_number': 45, - 'minor_number': 1, - 'beta_number': 1, - 'build_number': 1, - }, { - 'major_number': 45, - 'minor_number': 2, - 'build_number': 1, - }, { - 'major_number': 45, - 'minor_number': 2, - 'build_number': 2, - }, { - 'major_number': 45, - 'minor_number': 2, - 'beta_number': 1, - 'build_number': 2, - }) + _RELEASED_EDGE_CASES = ( + { + "major_number": 1, + "minor_number": 5, + "beta_number": 1, + }, + { + "major_number": 1, + "minor_number": 5, + "beta_number": 2, + }, + { + "major_number": 3, + "minor_number": 1, + "beta_number": 1, + }, + { + "major_number": 3, + "minor_number": 1, + }, + { + "major_number": 45, + "minor_number": 1, + "beta_number": 1, + "build_number": 1, + }, + { + "major_number": 45, + "minor_number": 2, + "build_number": 1, + }, + { + "major_number": 45, + "minor_number": 2, + "build_number": 2, + }, + { + "major_number": 45, + "minor_number": 2, + "beta_number": 1, + "build_number": 2, + }, + ) + + _RELEASED_EDGE_CASES_MAJOR = ( + { + "major_number": 1, + "minor_number": 5, + }, + { + "major_number": 3, + "minor_number": 1, + }, + { + "major_number": 38, + "minor_number": 0, + "patch_number": 1, + "build_number": 1, + }, + ) class GeckoSnapVersion(GeckoVersion): """Class that validates and handles Gecko's Snap version numbers. - Snap is a Linux packaging format developped by Canonical. Valid numbers are like "63.0b7-1", - "1" stands for "build1". Release Engineering set this scheme at the beginning of Snap and now - we can't rename published snap to the regular pattern like "63.0b7-build1". + Snap is a Linux packaging format developed by Canonical. Valid numbers are like + "63.0b7-1", "1" stands for "build1". Release Engineering set this scheme at the + beginning of Snap and now we can't rename published snap to the regular pattern + like "63.0b7-build1". """ # Our Snaps are recent enough to not list any edge case, yet. @@ -652,7 +916,8 @@ class GeckoSnapVersion(GeckoVersion): # * no "build" # * but mandatory dash and build number. # Example: 63.0b7-1 - _VALID_ENOUGH_VERSION_PATTERN = re.compile(r""" + _VALID_ENOUGH_VERSION_PATTERN = re.compile( + r""" ^(?P\d+) \.(?P\d+) (\.(?P\d+))? @@ -661,7 +926,9 @@ class GeckoSnapVersion(GeckoVersion): |b(?P\d+) |(?Pesr) )? - -(?P\d+)$""", re.VERBOSE) + -(?P\d+)$""", + re.VERBOSE, + ) def __str__(self): """Implement string representation. @@ -669,4 +936,4 @@ class GeckoSnapVersion(GeckoVersion): Returns format like "63.0b7-1" """ string = super().__str__() - return string.replace('build', '-') + return string.replace("build", "-") diff --git a/third_party/python/mozilla_version/mozilla_version/maven.py b/third_party/python/mozilla_version/mozilla_version/maven.py index 19bc4f74c68d..d9cb7fb54a2c 100644 --- a/third_party/python/mozilla_version/mozilla_version/maven.py +++ b/third_party/python/mozilla_version/mozilla_version/maven.py @@ -1,8 +1,9 @@ """Defines characteristics of a Maven version at Mozilla.""" -import attr import re +import attr + from mozilla_version.version import BaseVersion @@ -10,23 +11,27 @@ from mozilla_version.version import BaseVersion class MavenVersion(BaseVersion): """Class that validates and handles Maven version numbers. - At Mozilla, Maven packages are used in projects like "GeckoView" or "Android-Components". + At Mozilla, Maven packages are used in projects like "GeckoView" or + "Android-Components". """ is_snapshot = attr.ib(type=bool, default=False) is_beta = attr.ib(type=bool, default=False, init=False) is_release_candidate = attr.ib(type=bool, default=False, init=False) - _VALID_ENOUGH_VERSION_PATTERN = re.compile(r""" + _VALID_ENOUGH_VERSION_PATTERN = re.compile( + r""" ^(?P\d+) \.(?P\d+) (\.(?P\d+))? - (?P-SNAPSHOT)?$""", re.VERBOSE) + (?P-SNAPSHOT)?$""", + re.VERBOSE, + ) @classmethod def parse(cls, version_string): """Construct an object representing a valid Maven version number.""" - return super().parse(version_string, regex_groups=('is_snapshot', )) + return super().parse(version_string, regex_groups=("is_snapshot",)) def __str__(self): """Implement string representation. @@ -36,14 +41,14 @@ class MavenVersion(BaseVersion): string = super().__str__() if self.is_snapshot: - string = f'{string}-SNAPSHOT' + string = f"{string}-SNAPSHOT" return string def _compare(self, other): if isinstance(other, str): other = MavenVersion.parse(other) - elif not isinstance(other, MavenVersion): + if not isinstance(other, MavenVersion): raise ValueError(f'Cannot compare "{other}", type not supported!') difference = super()._compare(other) @@ -52,14 +57,11 @@ class MavenVersion(BaseVersion): if not self.is_snapshot and other.is_snapshot: return 1 - elif self.is_snapshot and not other.is_snapshot: + if self.is_snapshot and not other.is_snapshot: return -1 - else: - return 0 + return 0 @property def is_release(self): """Return `True` if the others are both False.""" - return not any(( - self.is_beta, self.is_release_candidate, self.is_snapshot - )) + return not any((self.is_beta, self.is_release_candidate, self.is_snapshot)) diff --git a/third_party/python/mozilla_version/mozilla_version/mobile.py b/third_party/python/mozilla_version/mozilla_version/mobile.py index 97e0f5b6aa4c..1adc428c43e6 100644 --- a/third_party/python/mozilla_version/mozilla_version/mobile.py +++ b/third_party/python/mozilla_version/mozilla_version/mobile.py @@ -1,12 +1,17 @@ """Defines characteristics of a Mobile version at Mozilla.""" -import attr import re -from mozilla_version.errors import PatternNotMatchedError, TooManyTypesError, NoVersionTypeError +import attr + +from mozilla_version.errors import ( + NoVersionTypeError, + PatternNotMatchedError, + TooManyTypesError, +) from mozilla_version.gecko import GeckoVersion -from mozilla_version.version import BaseVersion, VersionType from mozilla_version.parser import strictly_positive_int_or_none +from mozilla_version.version import BaseVersion, ShipItVersion, VersionType def _find_type(version): @@ -14,9 +19,7 @@ def _find_type(version): def ensure_version_type_is_not_already_defined(previous_type, candidate_type): if previous_type is not None: - raise TooManyTypesError( - str(version), previous_type, candidate_type - ) + raise TooManyTypesError(str(version), previous_type, candidate_type) if version.is_nightly: version_type = VersionType.NIGHTLY @@ -24,7 +27,9 @@ def _find_type(version): ensure_version_type_is_not_already_defined(version_type, VersionType.BETA) version_type = VersionType.BETA if version.is_release_candidate: - ensure_version_type_is_not_already_defined(version_type, VersionType.RELEASE_CANDIDATE) + ensure_version_type_is_not_already_defined( + version_type, VersionType.RELEASE_CANDIDATE + ) version_type = VersionType.RELEASE_CANDIDATE if version.is_release: ensure_version_type_is_not_already_defined(version_type, VersionType.RELEASE) @@ -37,13 +42,14 @@ def _find_type(version): @attr.s(frozen=True, eq=False, hash=True) -class MobileVersion(BaseVersion): +class MobileVersion(ShipItVersion): """Validate and handle version numbers for mobile products. This covers applications such as Fenix and Focus for Android. """ - _VALID_ENOUGH_VERSION_PATTERN = re.compile(r""" + _VALID_ENOUGH_VERSION_PATTERN = re.compile( + r""" ^(?P\d+) \.(?P\d+) (\.(?P\d+))? @@ -52,10 +58,15 @@ class MobileVersion(BaseVersion): |(-beta\.|b)(?P\d+) |-rc\.(?P\d+) )? - -?(build(?P\d+))?$""", re.VERBOSE) + -?(build(?P\d+))?$""", + re.VERBOSE, + ) _OPTIONAL_NUMBERS = ( - 'patch_number', 'beta_number', 'release_candidate_number', 'build_number' + "patch_number", + "beta_number", + "release_candidate_number", + "build_number", ) _ALL_NUMBERS = BaseVersion._MANDATORY_NUMBERS + _OPTIONAL_NUMBERS @@ -66,87 +77,103 @@ class MobileVersion(BaseVersion): # Android-Components later (bug 1800611) _LAST_VERSION_TO_FOLLOW_MAVEN_PATTERN = 108 - build_number = attr.ib(type=int, converter=strictly_positive_int_or_none, default=None) - beta_number = attr.ib(type=int, converter=strictly_positive_int_or_none, default=None) + build_number = attr.ib( + type=int, converter=strictly_positive_int_or_none, default=None + ) + beta_number = attr.ib( + type=int, converter=strictly_positive_int_or_none, default=None + ) is_nightly = attr.ib(type=bool, default=False) release_candidate_number = attr.ib( type=int, converter=strictly_positive_int_or_none, default=None ) - version_type = attr.ib(init=False, default=attr.Factory(_find_type, takes_self=True)) + version_type = attr.ib( + init=False, default=attr.Factory(_find_type, takes_self=True) + ) - def __attrs_post_init__(self): - """Ensure attributes are sane all together.""" - error_messages = [] + def _get_all_error_messages_for_attributes(self): + error_messages = super()._get_all_error_messages_for_attributes() if self.is_gecko_pattern: - error_messages.extend([ - pattern_message - for condition, pattern_message in (( - self.beta_number is not None and self.patch_number is not None, - 'Beta number and patch number cannot be both defined', - ), ( - self.release_candidate_number is not None, - 'Release candidate number cannot be defined after Mobile v{}'.format( - self._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN - ), - ), ( - self.major_number > self._LAST_VERSION_TO_FOLLOW_MAVEN_PATTERN and - self.minor_number == 0 and - self.patch_number == 0, - 'Minor number and patch number cannot be both equal to 0 past ' - 'Mobile v{}'.format( - self._LAST_VERSION_TO_FOLLOW_MAVEN_PATTERN - ), - ), ( - self.minor_number != 0 and self.patch_number is None, - 'Patch number cannot be undefined if minor number is greater than 0', - )) - if condition - ]) + error_messages.extend( + [ + pattern_message + for condition, pattern_message in ( + ( + self.beta_number is not None + and self.patch_number is not None, + "Beta number and patch number cannot be both defined", + ), + ( + self.release_candidate_number is not None, + "Release candidate number cannot be defined after Mobile " + f"v{self._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN}", + ), + ( + self.major_number + > self._LAST_VERSION_TO_FOLLOW_MAVEN_PATTERN + and self.minor_number == 0 + and self.patch_number == 0, + "Minor number and patch number cannot be both equal to 0 " + f"past Mobile {self._LAST_VERSION_TO_FOLLOW_MAVEN_PATTERN}", + ), + ( + self.minor_number != 0 and self.patch_number is None, + "Patch number cannot be undefined if minor number is " + "greater than 0", + ), + ) + if condition + ] + ) else: - error_messages.extend([ - pattern_message - for condition, pattern_message in (( - self.patch_number is None, - 'Patch number must be defined before Mobile v{}'.format( - self._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN - ), - ), ( - self.is_nightly, - 'Nightlies are not supported until Mobile v{}'.format( - self._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN - ), - )) - if condition - ]) + error_messages.extend( + [ + pattern_message + for condition, pattern_message in ( + ( + self.patch_number is None, + "Patch number must be defined before Mobile " + f"v{self._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN}", + ), + ( + self.is_nightly, + "Nightlies are not supported until Mobile " + f"v{self._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN}", + ), + ) + if condition + ] + ) - if error_messages: - raise PatternNotMatchedError(self, patterns=error_messages) + return error_messages @classmethod def parse(cls, version_string): """Construct an object representing a valid Firefox version number.""" - mobile_version = super().parse( - version_string, regex_groups=('is_nightly',) - ) + mobile_version = super().parse(version_string, regex_groups=("is_nightly",)) # Betas are supported in both the old and the gecko pattern. Let's make sure # the string we got follows the right rules if mobile_version.is_beta: - if mobile_version.is_gecko_pattern and '-beta.' in version_string: + if mobile_version.is_gecko_pattern and "-beta." in version_string: raise PatternNotMatchedError( - mobile_version, ['"-beta." can only be used before Mobile v{}'.format( - cls._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN - )] + mobile_version, + [ + '"-beta." can only be used before Mobile ' + f"v{cls._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN}" + ], ) - if not mobile_version.is_gecko_pattern and re.search(r"\db\d", version_string): + if not mobile_version.is_gecko_pattern and re.search( + r"\db\d", version_string + ): raise PatternNotMatchedError( - mobile_version, [ - '"b" cannot be used before Mobile v{} to define a ' - 'beta version'.format( - cls._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN - ) - ] + mobile_version, + [ + '"b" cannot be used before Mobile ' + f"v{cls._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN} to define a " + "beta version" + ], ) return mobile_version @@ -158,20 +185,24 @@ class MobileVersion(BaseVersion): @property def is_beta(self): - """Return `True` if `MobileVersion` was built with a string matching a beta version.""" + """Return `True` if `MobileVersion` was built with a `beta_number`.""" return self.beta_number is not None @property def is_release_candidate(self): - """Return `True` if `MobileVersion` was built with a string matching an RC version.""" + """Return `True` if `MobilVersion` was built with `release_candidate_number`.""" return self.release_candidate_number is not None @property def is_release(self): - """Return `True` if `MobileVersion` was built with a string matching a release version.""" - return not any(( - self.is_nightly, self.is_beta, self.is_release_candidate, - )) + """Return `True` if `MobileVersion` was built as a release version.""" + return not any( + ( + self.is_nightly, + self.is_beta, + self.is_release_candidate, + ) + ) def __str__(self): """Implement string representation. @@ -179,20 +210,22 @@ class MobileVersion(BaseVersion): Computes a new string based on the given attributes. """ if self.is_gecko_pattern: - string = str(GeckoVersion( - major_number=self.major_number, - minor_number=self.minor_number, - patch_number=self.patch_number, - build_number=self.build_number, - beta_number=self.beta_number, - is_nightly=self.is_nightly, - )) + string = str( + GeckoVersion( + major_number=self.major_number, + minor_number=self.minor_number, + patch_number=self.patch_number, + build_number=self.build_number, + beta_number=self.beta_number, + is_nightly=self.is_nightly, + ) + ) else: string = super().__str__() if self.is_beta: - string = f'{string}-beta.{self.beta_number}' + string = f"{string}-beta.{self.beta_number}" elif self.is_release_candidate: - string = f'{string}-rc.{self.release_candidate_number}' + string = f"{string}-rc.{self.release_candidate_number}" return string @@ -216,7 +249,9 @@ class MobileVersion(BaseVersion): return beta_difference if self.is_release_candidate and other.is_release_candidate: - rc_difference = self.release_candidate_number - other.release_candidate_number + rc_difference = ( + self.release_candidate_number - other.release_candidate_number + ) if rc_difference != 0: return rc_difference @@ -228,23 +263,24 @@ class MobileVersion(BaseVersion): def _create_bump_kwargs(self, field): bump_kwargs = super()._create_bump_kwargs(field) - if field != 'build_number' and bump_kwargs.get('build_number') == 0: - del bump_kwargs['build_number'] - if bump_kwargs.get('beta_number') == 0: + if field != "build_number" and bump_kwargs.get("build_number") == 0: + del bump_kwargs["build_number"] + if bump_kwargs.get("beta_number") == 0: if self.is_beta: - bump_kwargs['beta_number'] = 1 + bump_kwargs["beta_number"] = 1 else: - del bump_kwargs['beta_number'] + del bump_kwargs["beta_number"] - if field != 'release_candidate_number': - del bump_kwargs['release_candidate_number'] + if field != "release_candidate_number": + del bump_kwargs["release_candidate_number"] if ( - field == 'major_number' - and bump_kwargs.get('major_number') == self._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN + field == "major_number" + and bump_kwargs.get("major_number") + == self._FIRST_VERSION_TO_FOLLOW_GECKO_PATTERN ): - del bump_kwargs['patch_number'] + del bump_kwargs["patch_number"] - bump_kwargs['is_nightly'] = self.is_nightly + bump_kwargs["is_nightly"] = self.is_nightly return bump_kwargs diff --git a/third_party/python/mozilla_version/mozilla_version/version.py b/third_party/python/mozilla_version/mozilla_version/version.py index 6f7a603a5cfe..15c406b65d01 100644 --- a/third_party/python/mozilla_version/mozilla_version/version.py +++ b/third_party/python/mozilla_version/mozilla_version/version.py @@ -1,16 +1,17 @@ """Defines common characteristics of a version at Mozilla.""" -import attr import re - +from contextlib import suppress from enum import Enum +import attr + from mozilla_version.errors import MissingFieldError, PatternNotMatchedError from mozilla_version.parser import ( - get_value_matched_by_regex, does_regex_have_group, + get_value_matched_by_regex, positive_int, - positive_int_or_none + positive_int_or_none, ) @@ -22,14 +23,17 @@ class BaseVersion: minor_number = attr.ib(type=int, converter=positive_int) patch_number = attr.ib(type=int, converter=positive_int_or_none, default=None) - _MANDATORY_NUMBERS = ('major_number', 'minor_number') - _OPTIONAL_NUMBERS = ('patch_number', ) + _MANDATORY_NUMBERS = ("major_number", "minor_number") + _OPTIONAL_NUMBERS = ("patch_number",) _ALL_NUMBERS = _MANDATORY_NUMBERS + _OPTIONAL_NUMBERS - _VALID_ENOUGH_VERSION_PATTERN = re.compile(r""" + _VALID_ENOUGH_VERSION_PATTERN = re.compile( + r""" ^(?P\d+) \.(?P\d+) - (\.(?P\d+))?$""", re.VERBOSE) + (\.(?P\d+))?$""", + re.VERBOSE, + ) @classmethod def parse(cls, version_string, regex_groups=()): @@ -37,17 +41,21 @@ class BaseVersion: regex_matches = cls._VALID_ENOUGH_VERSION_PATTERN.match(version_string) if regex_matches is None: - raise PatternNotMatchedError(version_string, (cls._VALID_ENOUGH_VERSION_PATTERN,)) + raise PatternNotMatchedError( + version_string, (cls._VALID_ENOUGH_VERSION_PATTERN,) + ) kwargs = {} for field in cls._MANDATORY_NUMBERS: - kwargs[field] = get_value_matched_by_regex(field, regex_matches, version_string) + kwargs[field] = get_value_matched_by_regex( + field, regex_matches, version_string + ) for field in cls._OPTIONAL_NUMBERS: - try: - kwargs[field] = get_value_matched_by_regex(field, regex_matches, version_string) - except MissingFieldError: - pass + with suppress(MissingFieldError): + kwargs[field] = get_value_matched_by_regex( + field, regex_matches, version_string + ) for regex_group in regex_groups: kwargs[regex_group] = does_regex_have_group(regex_matches, regex_group) @@ -63,7 +71,7 @@ class BaseVersion: if self.patch_number is not None: semvers.append(str(self.patch_number)) - return '.'.join(semvers) + return ".".join(semvers) def __eq__(self, other): """Implement `==` operator.""" @@ -103,7 +111,7 @@ class BaseVersion: elif not isinstance(other, BaseVersion): raise ValueError(f'Cannot compare "{other}", type not supported!') - for field in ('major_number', 'minor_number', 'patch_number'): + for field in ("major_number", "minor_number", "patch_number"): difference = self._substract_other_number_from_this_number(other, field) if difference != 0: return difference @@ -111,10 +119,10 @@ class BaseVersion: return 0 def _substract_other_number_from_this_number(self, other, field): - # BaseVersion sets unmatched numbers to None. E.g.: "32.0" sets the patch_number to None. - # Because of this behavior, `getattr(self, 'patch_number')` returns None too. That's why - # we can't call `getattr(self, field, 0)` directly, it will return None for all unmatched - # numbers + # BaseVersion sets unmatched numbers to None. E.g.: "32.0" sets the patch_number + # to None. Because of this behavior, `getattr(self, 'patch_number')` returns + # None too. That's why we can't call `getattr(self, field, 0)` directly, it will + # return None for all unmatched numbers this_number = getattr(self, field, None) this_number = 0 if this_number is None else this_number other_number = getattr(other, field, None) @@ -126,14 +134,14 @@ class BaseVersion: """Bump the number defined `field`. Returns: - A new BaseVersion with the right field bumped and the following ones set to 0, - if they exist or if they need to be set. + A new BaseVersion with the right field bumped and the following ones set to + 0, if they exist or if they need to be set. For instance: * 32.0 is bumped to 33.0, because the patch number does not exist * 32.0.1 is bumped to 33.0.0, because the patch number exists - * 32.0 is bumped to 32.1.0, because patch number must be defined if the minor number - is not 0. + * 32.0 is bumped to 32.1.0, because patch number must be defined if the + minor number is not 0. """ try: @@ -155,17 +163,14 @@ class BaseVersion: if current_field == field: has_requested_field_been_met = True new_number = 1 if current_number is None else current_number + 1 - if new_number == 1 and current_field == 'minor_number': + if new_number == 1 and current_field == "minor_number": should_set_optional_numbers = True kwargs[current_field] = new_number else: - if ( - has_requested_field_been_met and - ( - current_field not in self._OPTIONAL_NUMBERS or - should_set_optional_numbers or - current_number is not None - ) + if has_requested_field_been_met and ( + current_field not in self._OPTIONAL_NUMBERS + or should_set_optional_numbers + or current_number is not None ): new_number = 0 else: @@ -178,10 +183,11 @@ class BaseVersion: class VersionType(Enum): """Enum that sorts types of versions (e.g.: nightly, beta, release, esr). - Supports comparison. `ESR` is considered higher than `RELEASE` (even if they technically have - the same codebase). For instance: 60.0.1 < 60.0.1esr but 61.0 > 60.0.1esr. - This choice has a practical use case: if you have a list of Release and ESR version, you can - easily extract one kind or the other thanks to the VersionType. + Supports comparison. `ESR` is considered higher than `RELEASE` (even if they + technically have the same codebase). For instance: 60.0.1 < 60.0.1esr but + 61.0 > 60.0.1esr. This choice has a practical use case: if you have a list of + Release and ESR version, you can easily extract one kind or the other thanks to the + VersionType. Examples: .. code-block:: python @@ -234,3 +240,67 @@ class VersionType(Enum): return self.value - other.value __hash__ = Enum.__hash__ + + +@attr.s(frozen=True, eq=False, hash=True) +class ShipItVersion(BaseVersion): + """Class that represent a version in the way ShipIt intends it to be. + + ShipIt is Release Engineering's https://github.com/mozilla-releng/shipit/ + """ + + def __attrs_post_init__(self): + """Ensure attributes are sane all together.""" + error_messages = self._get_all_error_messages_for_attributes() + if error_messages: + raise PatternNotMatchedError(self, patterns=error_messages) + + def _get_all_error_messages_for_attributes(self): + return [ + pattern_message + for condition, pattern_message in ( + ( + self.is_major and self.is_stability, + "Version cannot be both a major and a stability one", + ), + ( + self.is_major and self.is_development, + "Version cannot be both a major and a development one", + ), + ( + self.is_stability and self.is_development, + "Version cannot be both a stability and a development one", + ), + ) + if condition + ] + + @property + def is_major(self): + """Return `True` if `ShipItVersion` is considered to be a major version. + + It's usually the .0 release but some exceptions may occur. + """ + return all( + (not self.is_development, self.minor_number == 0, self.patch_number is None) + ) + + @property + def is_stability(self): + """Return `True` if `ShipItVersion` is a version that fixed a major one.""" + return all( + ( + not self.is_development, + not self.is_major, + self.minor_number != 0 or self.patch_number != 0, + ) + ) + + @property + def is_development(self): + """Return `True` if `ShipItVersion` was known to require further development. + + It's usually a beta or before the rapid release scheme, a release candidate. + """ + # TODO Bubble up beta_number and release_candidate number in ShipItVersion + return self.is_beta or self.is_release_candidate diff --git a/third_party/python/poetry.lock b/third_party/python/poetry.lock index 84957a86801a..69dfc282d5b6 100644 --- a/third_party/python/poetry.lock +++ b/third_party/python/poetry.lock @@ -883,13 +883,13 @@ giturlparse = "*" [[package]] name = "mozilla-version" -version = "2.0.0" +version = "3.1.0" description = "Process Firefox versions numbers. Tells whether they are valid or not, whether they are nightlies or regular releases, whether this version precedes that other." optional = false python-versions = "*" files = [ - {file = "mozilla-version-2.0.0.tar.gz", hash = "sha256:09697ddc5f55ad8d76521bf3e37aaec4d5bfd7fd4c9018a1cbb0e8cf6c536538"}, - {file = "mozilla_version-2.0.0-py3-none-any.whl", hash = "sha256:50807a1f4000a7db6bfe95b0ffb1bade429cd8e56cbab70fd3eff5dd46ebb794"}, + {file = "mozilla-version-3.1.0.tar.gz", hash = "sha256:3a9463ebcf2249dc8bcf504e246b6b5977c902dfa819de31602e10bce032ed93"}, + {file = "mozilla_version-3.1.0-py3-none-any.whl", hash = "sha256:f798e716da9063608a0b49ca1ec0a51b73ac810c3cc8a4bcc2c461df902b147c"}, ] [package.dependencies] @@ -1131,6 +1131,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1138,8 +1139,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1156,6 +1164,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1163,6 +1172,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1584,4 +1594,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black ( [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "55e903bf1ca5b8f0a61312d34696ab18281f4f31399366b4b9ee911333e49d1d" +content-hash = "9d5a7fe02c75289433a471dfd831a195ff604744f787c6b2b91d4af159c98475" diff --git a/third_party/python/requirements.in b/third_party/python/requirements.in index b8f487a5ca2a..cc75eecbaaca 100644 --- a/third_party/python/requirements.in +++ b/third_party/python/requirements.in @@ -32,7 +32,7 @@ jsonschema==4.17.3 looseversion==1.0.1 mako==1.1.2 mozilla-repo-urls==0.1.1 -mozilla-version==2.0.0 +mozilla-version==3.1.0 packaging==23.1 pathspec==0.9.0 pip==24.0 diff --git a/third_party/python/requirements.txt b/third_party/python/requirements.txt index 824ee4b8c149..835e73aa312c 100644 --- a/third_party/python/requirements.txt +++ b/third_party/python/requirements.txt @@ -389,9 +389,9 @@ mohawk==0.3.4 ; python_version >= "3.8" and python_version < "4.0" \ mozilla-repo-urls==0.1.1 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:30510d3519479aa70211145d0ac9cf6e2fadcb8d30fa3b196bb957bd773502ba \ --hash=sha256:7364da790751db2a060eb45adbf1d7db89a145ed279ba235f3425db9dd255915 -mozilla-version==2.0.0 ; python_version >= "3.8" and python_version < "4.0" \ - --hash=sha256:09697ddc5f55ad8d76521bf3e37aaec4d5bfd7fd4c9018a1cbb0e8cf6c536538 \ - --hash=sha256:50807a1f4000a7db6bfe95b0ffb1bade429cd8e56cbab70fd3eff5dd46ebb794 +mozilla-version==3.1.0 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:3a9463ebcf2249dc8bcf504e246b6b5977c902dfa819de31602e10bce032ed93 \ + --hash=sha256:f798e716da9063608a0b49ca1ec0a51b73ac810c3cc8a4bcc2c461df902b147c multidict==5.1.0 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a \ --hash=sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93 \ @@ -469,7 +469,9 @@ python-slugify==8.0.1 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395 \ --hash=sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27 pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \ --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ + --hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \ --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \ --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \ --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \ @@ -477,7 +479,10 @@ pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \ --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \ --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \ + --hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \ + --hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \ --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \ + --hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \ --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \ --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \ --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \ @@ -485,9 +490,12 @@ pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \ --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \ --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \ + --hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \ --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \ --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \ --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \ + --hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \ + --hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \ --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \ --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \ --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \ @@ -502,7 +510,9 @@ pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \ --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \ --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \ + --hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \ --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \ + --hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \ --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \ --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \ --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \