mautrix-python/mautrix/types/versions.py

141 lines
4.6 KiB
Python

# Copyright (c) 2022 Tulir Asokan
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import Dict, List, NamedTuple, Optional, Union
from enum import IntEnum
import re
from attr import dataclass
import attr
from . import JSON
from .util import Serializable, SerializableAttrs
class VersionFormat(IntEnum):
UNKNOWN = -1
LEGACY = 0
MODERN = 1
def __repr__(self) -> str:
return f"VersionFormat.{self.name}"
legacy_version_regex = re.compile(r"^r(\d+)\.(\d+)\.(\d+)$")
modern_version_regex = re.compile(r"^v(\d+)\.(\d+)$")
@attr.dataclass(frozen=True)
class Version(Serializable):
format: VersionFormat
major: int
minor: int
patch: int
raw: str
def __str__(self) -> str:
if self.format == VersionFormat.MODERN:
return f"v{self.major}.{self.minor}"
elif self.format == VersionFormat.LEGACY:
return f"r{self.major}.{self.minor}.{self.patch}"
else:
return self.raw
def serialize(self) -> JSON:
return str(self)
@classmethod
def deserialize(cls, raw: JSON) -> "Version":
assert isinstance(raw, str), "versions must be strings"
if modern := modern_version_regex.fullmatch(raw):
major, minor = modern.groups()
return Version(VersionFormat.MODERN, int(major), int(minor), 0, raw)
elif legacy := legacy_version_regex.fullmatch(raw):
major, minor, patch = legacy.groups()
return Version(VersionFormat.LEGACY, int(major), int(minor), int(patch), raw)
else:
return Version(VersionFormat.UNKNOWN, 0, 0, 0, raw)
class SpecVersions:
R010 = Version.deserialize("r0.1.0")
R020 = Version.deserialize("r0.2.0")
R030 = Version.deserialize("r0.3.0")
R040 = Version.deserialize("r0.4.0")
R050 = Version.deserialize("r0.5.0")
R060 = Version.deserialize("r0.6.0")
R061 = Version.deserialize("r0.6.1")
V11 = Version.deserialize("v1.1")
V12 = Version.deserialize("v1.2")
V13 = Version.deserialize("v1.3")
V14 = Version.deserialize("v1.4")
V15 = Version.deserialize("v1.5")
V16 = Version.deserialize("v1.6")
V17 = Version.deserialize("v1.7")
@dataclass
class VersionsResponse(SerializableAttrs):
versions: List[Version]
unstable_features: Dict[str, bool] = attr.ib(factory=lambda: {})
def supports(self, thing: Union[Version, str]) -> Optional[bool]:
"""
Check if the versions response contains the given spec version or unstable feature.
Args:
thing: The spec version (as a :class:`Version` or string)
or unstable feature name (as a string) to check.
Returns:
``True`` if the exact version or unstable feature is supported,
``False`` if it's not supported,
``None`` for unstable features which are not included in the response at all.
"""
if isinstance(thing, Version):
return thing in self.versions
elif (parsed_version := Version.deserialize(thing)).format != VersionFormat.UNKNOWN:
return parsed_version in self.versions
return self.unstable_features.get(thing)
def supports_at_least(self, version: Union[Version, str]) -> bool:
"""
Check if the versions response contains the given spec version or any higher version.
Args:
version: The spec version as a :class:`Version` or a string.
Returns:
``True`` if a version equal to or higher than the given version is found,
``False`` otherwise.
"""
if isinstance(version, str):
version = Version.deserialize(version)
return any(v for v in self.versions if v > version)
@property
def latest_version(self) -> Version:
return max(self.versions)
@property
def has_legacy_versions(self) -> bool:
"""
Check if the response contains any legacy (r0.x.y) versions.
.. deprecated:: 0.16.10
:meth:`supports_at_least` and :meth:`supports` methods are now preferred.
"""
return any(v for v in self.versions if v.format == VersionFormat.LEGACY)
@property
def has_modern_versions(self) -> bool:
"""
Check if the response contains any modern (v1.1 or higher) versions.
.. deprecated:: 0.16.10
:meth:`supports_at_least` and :meth:`supports` methods are now preferred.
"""
return self.supports_at_least(SpecVersions.V11)