Skip to content

wake.core.solidity_version module #

SemanticVersion class #

Bases: VersionAbc

A class representing a single Solidity version (not a range of versions). Prerelease and build tags are parsed but ignored (even in comparison). As of solc version 0.8.11 there is no use for them.

Source code in wake/core/solidity_version.py
class SemanticVersion(VersionAbc):
    """
    A class representing a single Solidity version (not a range of versions).
    Prerelease and build tags are parsed but ignored (even in comparison). As of `solc` version 0.8.11 there is no use for them.
    """

    NUMBER = r"0|[1-9][0-9]*"
    PRERELEASE_OR_BUILD_PART = r"[-0-9A-Za-z]+"
    PRERELEASE_OR_BUILD = r"{part}(?:\.{part})*".format(part=PRERELEASE_OR_BUILD_PART)
    RE = re.compile(
        r"^(?P<major>{number})\.(?P<minor>{number})\.(?P<patch>{number})(?:-(?P<prerelease>{prerelease}))?(?:\+(?P<build>{build}))?$".format(
            number=NUMBER, prerelease=PRERELEASE_OR_BUILD, build=PRERELEASE_OR_BUILD
        )
    )
    __major: int
    __minor: int
    __patch: int
    __prerelease: Optional[str]
    __build: Optional[str]

    def __init__(
        self,
        major: int,
        minor: int,
        patch: int,
        prerelease: Optional[str] = None,
        build: Optional[str] = None,
    ):
        """
        Create a new instance of `SemanticVersion`.

        Args:
            major: The major version number.
            minor: The minor version number.
            patch: The patch version number.
            prerelease: The prerelease tag.
            build: The build tag.
        """
        self.__major = major
        self.__minor = minor
        self.__patch = patch
        self.__prerelease = prerelease
        self.__build = build

    def __str__(self) -> str:
        """
        Return a user-friendly string representation of the version.
        """
        s = f"{self.major}.{self.minor}.{self.patch}"
        if self.prerelease is not None:
            s += f"-{self.prerelease}"
        if self.build is not None:
            s += f"+{self.build}"
        return s

    def __repr__(self) -> str:
        """
        Return a string representation of the version.
        """
        prerelease = (
            '"' + self.prerelease + '"' if self.prerelease is not None else None
        )
        build = '"' + self.build + '"' if self.build is not None else None
        return f"{self.__class__.__name__}({self.major}, {self.minor}, {self.patch}, {prerelease}, {build})"

    def __hash__(self) -> int:
        """
        Return a hash of the version. Prerelease and build tags are ignored.
        """
        return hash((self.major, self.minor, self.patch))

    def __eq__(self, other) -> bool:
        """
        Return `True` if the given version is equal to this version, `False` otherwise.
        Prerelease and build tags are ignored.
        """
        cls = self.__class__

        if isinstance(other, str):
            other = cls.fromstring(other)
        elif not isinstance(other, cls):
            return NotImplemented
        return (
            self.major == other.major
            and self.minor == other.minor
            and self.patch == other.patch
        )

    def __lt__(self, other) -> bool:
        """
        Return `True` if the given version is less than this version, `False` otherwise.
        Prerelease and build tags are ignored.
        """
        cls = self.__class__

        if isinstance(other, str):
            other = cls.fromstring(other)
        elif not isinstance(other, self.__class__):
            return NotImplemented
        return (self.major, self.minor, self.patch) < (
            other.major,
            other.minor,
            other.patch,
        )

    def __le__(self, other) -> bool:
        """
        Return `True` if the given version is less than or equal to this version, `False` otherwise.
        Prerelease and build tags are ignored.
        """
        return self < other or self == other

    def __gt__(self, other):
        """
        Return `True` if the given version is greater than this version, `False` otherwise.
        Prerelease and build tags are ignored.
        """
        lt = self < other
        if lt is NotImplemented:
            return NotImplemented
        return not lt and self != other

    def __ge__(self, other):
        """
        Return `True` if the given version is greater than or equal to this version, `False` otherwise.
        Prerelease and build tags are ignored.
        """
        lt = self < other
        if lt is NotImplemented:
            return NotImplemented
        return not lt

    @classmethod
    def fromstring(cls: Type[T], version_str: str) -> T:
        """
        Create a new instance of `SemanticVersion` from a string.

        Args:
            version_str: The string to parse.
        """
        match = cls.RE.match(version_str)
        if not match:
            raise ValueError(f"Invalid Solidity version: `{version_str}`")
        groups = match.groupdict()
        major = int(groups["major"])
        minor = int(groups["minor"])
        patch = int(groups["patch"])
        prerelease = groups["prerelease"]
        build = groups["build"]
        return cls(major, minor, patch, prerelease, build)

    @property
    def major(self) -> int:
        """
        Return the major version number.
        """
        return self.__major

    @property
    def minor(self) -> int:
        """
        Return the minor version number.
        """
        return self.__minor

    @property
    def patch(self) -> int:
        """
        Return the patch version number.
        """
        return self.__patch

    @property
    def prerelease(self) -> Optional[str]:
        """
        Return the prerelease tag.
        """
        return self.__prerelease

    @property
    def build(self) -> Optional[str]:
        """
        Return the build tag.
        """
        return self.__build

build: Optional[str] property #

Return the build tag.

major: int property #

Return the major version number.

minor: int property #

Return the minor version number.

patch: int property #

Return the patch version number.

prerelease: Optional[str] property #

Return the prerelease tag.

__eq__(other) #

Return True if the given version is equal to this version, False otherwise. Prerelease and build tags are ignored.

Source code in wake/core/solidity_version.py
def __eq__(self, other) -> bool:
    """
    Return `True` if the given version is equal to this version, `False` otherwise.
    Prerelease and build tags are ignored.
    """
    cls = self.__class__

    if isinstance(other, str):
        other = cls.fromstring(other)
    elif not isinstance(other, cls):
        return NotImplemented
    return (
        self.major == other.major
        and self.minor == other.minor
        and self.patch == other.patch
    )

__ge__(other) #

Return True if the given version is greater than or equal to this version, False otherwise. Prerelease and build tags are ignored.

Source code in wake/core/solidity_version.py
def __ge__(self, other):
    """
    Return `True` if the given version is greater than or equal to this version, `False` otherwise.
    Prerelease and build tags are ignored.
    """
    lt = self < other
    if lt is NotImplemented:
        return NotImplemented
    return not lt

__gt__(other) #

Return True if the given version is greater than this version, False otherwise. Prerelease and build tags are ignored.

Source code in wake/core/solidity_version.py
def __gt__(self, other):
    """
    Return `True` if the given version is greater than this version, `False` otherwise.
    Prerelease and build tags are ignored.
    """
    lt = self < other
    if lt is NotImplemented:
        return NotImplemented
    return not lt and self != other

__hash__() #

Return a hash of the version. Prerelease and build tags are ignored.

Source code in wake/core/solidity_version.py
def __hash__(self) -> int:
    """
    Return a hash of the version. Prerelease and build tags are ignored.
    """
    return hash((self.major, self.minor, self.patch))

__init__(major, minor, patch, prerelease=None, build=None) #

Create a new instance of SemanticVersion.

Parameters:

Name Type Description Default
major int

The major version number.

required
minor int

The minor version number.

required
patch int

The patch version number.

required
prerelease Optional[str]

The prerelease tag.

None
build Optional[str]

The build tag.

None
Source code in wake/core/solidity_version.py
def __init__(
    self,
    major: int,
    minor: int,
    patch: int,
    prerelease: Optional[str] = None,
    build: Optional[str] = None,
):
    """
    Create a new instance of `SemanticVersion`.

    Args:
        major: The major version number.
        minor: The minor version number.
        patch: The patch version number.
        prerelease: The prerelease tag.
        build: The build tag.
    """
    self.__major = major
    self.__minor = minor
    self.__patch = patch
    self.__prerelease = prerelease
    self.__build = build

__le__(other) #

Return True if the given version is less than or equal to this version, False otherwise. Prerelease and build tags are ignored.

Source code in wake/core/solidity_version.py
def __le__(self, other) -> bool:
    """
    Return `True` if the given version is less than or equal to this version, `False` otherwise.
    Prerelease and build tags are ignored.
    """
    return self < other or self == other

__lt__(other) #

Return True if the given version is less than this version, False otherwise. Prerelease and build tags are ignored.

Source code in wake/core/solidity_version.py
def __lt__(self, other) -> bool:
    """
    Return `True` if the given version is less than this version, `False` otherwise.
    Prerelease and build tags are ignored.
    """
    cls = self.__class__

    if isinstance(other, str):
        other = cls.fromstring(other)
    elif not isinstance(other, self.__class__):
        return NotImplemented
    return (self.major, self.minor, self.patch) < (
        other.major,
        other.minor,
        other.patch,
    )

__repr__() #

Return a string representation of the version.

Source code in wake/core/solidity_version.py
def __repr__(self) -> str:
    """
    Return a string representation of the version.
    """
    prerelease = (
        '"' + self.prerelease + '"' if self.prerelease is not None else None
    )
    build = '"' + self.build + '"' if self.build is not None else None
    return f"{self.__class__.__name__}({self.major}, {self.minor}, {self.patch}, {prerelease}, {build})"

__str__() #

Return a user-friendly string representation of the version.

Source code in wake/core/solidity_version.py
def __str__(self) -> str:
    """
    Return a user-friendly string representation of the version.
    """
    s = f"{self.major}.{self.minor}.{self.patch}"
    if self.prerelease is not None:
        s += f"-{self.prerelease}"
    if self.build is not None:
        s += f"+{self.build}"
    return s

fromstring(version_str) classmethod #

Create a new instance of SemanticVersion from a string.

Parameters:

Name Type Description Default
version_str str

The string to parse.

required
Source code in wake/core/solidity_version.py
@classmethod
def fromstring(cls: Type[T], version_str: str) -> T:
    """
    Create a new instance of `SemanticVersion` from a string.

    Args:
        version_str: The string to parse.
    """
    match = cls.RE.match(version_str)
    if not match:
        raise ValueError(f"Invalid Solidity version: `{version_str}`")
    groups = match.groupdict()
    major = int(groups["major"])
    minor = int(groups["minor"])
    patch = int(groups["patch"])
    prerelease = groups["prerelease"]
    build = groups["build"]
    return cls(major, minor, patch, prerelease, build)

SolidityVersion class #

Bases: SemanticVersion

A class representing a single Solidity version. Just an alias for SemanticVersion.

Source code in wake/core/solidity_version.py
class SolidityVersion(SemanticVersion):
    """
    A class representing a single Solidity version. Just an alias for [SemanticVersion](#semanticversion).
    """

SolidityVersionExpr class #

A class representing a Solidity version expression. It keeps the original expression string and a SolidityVersionRanges instance that represents the expression.

Source code in wake/core/solidity_version.py
class SolidityVersionExpr:
    """
    A class representing a Solidity version expression.
    It keeps the original expression string and a `SolidityVersionRanges` instance that represents the expression.
    """

    ERROR_MSG = r"Invalid Solidity version expression: `{value}`"
    NUMBER = r"x|X|\*|0|[1-9][0-9]*"
    PARTIAL = r"(?P<major>{number})\s*(?:\.\s*(?P<minor>{number}))?\s*(?:\.\s*(?P<patch>{number}))?".format(
        number=NUMBER
    )
    PARTIAL_RE = re.compile(r"^\s*{partial}\s*$".format(partial=PARTIAL))
    PART = r"(?P<operator>\^|~|<|<=|>|>=|=)?\s*{partial}".format(partial=PARTIAL)
    RANGE_RE = re.compile(r"\s*{part}\s*".format(part=PART))
    RANGES_RE = re.compile(r"^(\s*{part}\s*)+$".format(part=PART))

    __expression: str
    __ranges: SolidityVersionRanges

    def __init__(self, expr: str):
        """
        Create a new instance of `SolidityVersionExpr`.

        Args:
            expr: The Solidity version expression to parse.
        """
        cls = self.__class__
        self.__expression = expr
        evaluated_ranges = []

        ranges = expr.split("||")
        for r in ranges:
            if "-" in r:
                evaluated_ranges.append(cls.__parse_hyphen_range(r))
            else:
                evaluated_ranges.append(cls.__parse_range(r))
        self.__ranges = SolidityVersionRanges(evaluated_ranges)

    @classmethod
    def __parse_range(cls, range_str: str) -> SolidityVersionRange:
        check = cls.RANGES_RE.match(range_str)
        if not check:
            raise ValueError(cls.ERROR_MSG.format(value=range_str))

        matches = cls.RANGE_RE.finditer(range_str)
        ret = SolidityVersionRange(None, None, None, None)
        for match in matches:
            ret &= cls.__parse_simple(match.groupdict(), match.string.strip())
        return ret

    @classmethod
    def __parse_hyphen_range(cls, hyphen_range: str) -> SolidityVersionRange:
        partials = hyphen_range.split("-")
        if len(partials) != 2:
            raise ValueError(cls.ERROR_MSG.format(value=hyphen_range))
        match_left = cls.PARTIAL_RE.match(partials[0])
        match_right = cls.PARTIAL_RE.match(partials[1])
        if not match_left or not match_right:
            raise ValueError(cls.ERROR_MSG.format(value=hyphen_range))

        partial_left = cls.__parse_partial(
            match_left.groupdict(), match_left.string.strip()
        )
        left = cls.__evaluate_ge(*partial_left)
        partial_right = cls.__parse_partial(
            match_right.groupdict(), match_right.string.strip()
        )
        right = cls.__evaluate_le(*partial_right, match_right.string.strip())
        return left & right

    @classmethod
    def __parse_partial(
        cls, match_dict: Dict[str, Any], match_str: str
    ) -> Tuple[Optional[int], Optional[int], Optional[int]]:
        major = match_dict["major"]
        minor = match_dict["minor"]
        patch = match_dict["patch"]
        if major in {None, "x", "X", "*"}:
            major = None
        else:
            major = int(major)
        if minor in {None, "x", "X", "*"}:
            minor = None
        else:
            minor = int(minor)
        if patch in {None, "x", "X", "*"}:
            patch = None
        else:
            patch = int(patch)

        # partials should be in ascending order, i.e.: 1.0.x, 1.x.x, x.x.x, not x.0.1 or 1.x.5
        if (major is None and not all(x is None for x in (minor, patch))) or (
            minor is None and patch is not None
        ):
            raise ValueError(cls.ERROR_MSG.format(value=match_str))

        return major, minor, patch

    @classmethod
    def __evaluate_caret(
        cls,
        major: Optional[int],
        minor: Optional[int],
        patch: Optional[int],
        match_str: str,
    ) -> SolidityVersionRange:
        if major is None:
            raise ValueError(cls.ERROR_MSG.format(value=match_str))
        elif minor is None:
            # ^1.x.x := >=1.0.0 < 2.0.0
            v1 = SolidityVersion(major, 0, 0)
            v2 = SolidityVersion(major + 1, 0, 0)
            return SolidityVersionRange(v1, True, v2, False)
        elif patch is None:
            if major != 0:
                # ^1.2.x := >=1.2.0 < 2.0.0
                v1 = SolidityVersion(major, minor, 0)
                v2 = SolidityVersion(major + 1, 0, 0)
                return SolidityVersionRange(v1, True, v2, False)
            else:
                # ^0.2.x := >=0.2.0 <0.3.0
                # ^0.0.x := >=0.0.0 <0.1.0
                v1 = SolidityVersion(major, minor, 0)
                v2 = SolidityVersion(major, minor + 1, 0)
                return SolidityVersionRange(v1, True, v2, False)
        elif major != 0:
            # ^1.2.3 := >=1.2.3 <2.0.0
            v1 = SolidityVersion(major, minor, patch)
            v2 = SolidityVersion(major + 1, 0, 0)
            return SolidityVersionRange(v1, True, v2, False)
        elif minor != 0:
            # ^0.2.3 := >=0.2.3 <0.3.0
            v1 = SolidityVersion(major, minor, patch)
            v2 = SolidityVersion(major, minor + 1, 0)
            return SolidityVersionRange(v1, True, v2, False)
        elif patch != 0:
            # ^0.0.3 := >=0.0.3 <0.0.4
            v1 = SolidityVersion(major, minor, patch)
            v2 = SolidityVersion(major, minor, patch + 1)
            return SolidityVersionRange(v1, True, v2, False)
        else:
            raise ValueError(cls.ERROR_MSG.format(value=match_str))

    @classmethod
    def __evaluate_tilde(
        cls,
        major: Optional[int],
        minor: Optional[int],
        patch: Optional[int],
        match_str: str,
    ) -> SolidityVersionRange:
        if major is None:
            raise ValueError(cls.ERROR_MSG.format(value=match_str))
        elif minor is None:
            # ~1.x.x := >=1.0.0 <2.0.0
            v1 = SolidityVersion(major, 0, 0)
            v2 = SolidityVersion(major + 1, 0, 0)
            return SolidityVersionRange(v1, True, v2, False)
        elif patch is None:
            # ~1.2.x := >=1.2.0 <1.3.0
            v1 = SolidityVersion(major, minor, 0)
            v2 = SolidityVersion(major, minor + 1, 0)
            return SolidityVersionRange(v1, True, v2, False)
        else:
            # ~1.2.3 := >=1.2.3 <1.3.0
            v1 = SolidityVersion(major, minor, patch)
            v2 = SolidityVersion(major, minor + 1, 0)
            return SolidityVersionRange(v1, True, v2, False)

    @classmethod
    def __evaluate_lt(
        cls,
        major: Optional[int],
        minor: Optional[int],
        patch: Optional[int],
        match_str: str,
    ) -> SolidityVersionRange:
        if major is None:
            raise ValueError(cls.ERROR_MSG.format(value=match_str))
        # <1.x.x := <1.0.0
        # <1.2.x := <1.2.0
        # <1.2.3 := <1.2.3
        v2 = SolidityVersion(major, minor or 0, patch or 0)
        return SolidityVersionRange(None, None, v2, False)

    @classmethod
    def __evaluate_le(
        cls,
        major: Optional[int],
        minor: Optional[int],
        patch: Optional[int],
        match_str: str,
    ) -> SolidityVersionRange:
        if major is None:
            raise ValueError(cls.ERROR_MSG.format(value=match_str))
        elif minor is None:
            # <=1.x.x := <2.0.0
            v2 = SolidityVersion(major + 1, 0, 0)
            return SolidityVersionRange(None, None, v2, False)
        elif patch is None:
            # <=1.2.x := <1.3.0
            v2 = SolidityVersion(major, minor + 1, 0)
            return SolidityVersionRange(None, None, v2, False)
        else:
            # <=1.2.3 := <=1.2.3
            v2 = SolidityVersion(major, minor, patch)
            return SolidityVersionRange(None, None, v2, True)

    @classmethod
    def __evaluate_gt(
        cls,
        major: Optional[int],
        minor: Optional[int],
        patch: Optional[int],
        match_str: str,
    ) -> SolidityVersionRange:
        if major is None:
            raise ValueError(cls.ERROR_MSG.format(value=match_str))
        elif minor is None:
            # >1.x.x := >=2.0.0
            v1 = SolidityVersion(major + 1, 0, 0)
            return SolidityVersionRange(v1, True, None, None)
        elif patch is None:
            # >1.2.x := >=1.3.0
            v1 = SolidityVersion(major, minor + 1, 0)
            return SolidityVersionRange(v1, True, None, None)
        else:
            # >1.2.3 := >1.2.3
            v1 = SolidityVersion(major, minor, patch)
            return SolidityVersionRange(v1, False, None, None)

    @classmethod
    def __evaluate_ge(
        cls, major: Optional[int], minor: Optional[int], patch: Optional[int]
    ) -> SolidityVersionRange:
        # >=x.x.x := >=0.0.0
        # >=1.x.x := >=1.0.0
        # >=1.2.x := >=1.2.0
        # >=1.2.3 := >=1.2.3
        v1 = SolidityVersion(major or 0, minor or 0, patch or 0)
        return SolidityVersionRange(v1, True, None, None)

    @classmethod
    def __evaluate_eq(
        cls, major: Optional[int], minor: Optional[int], patch: Optional[int]
    ) -> SolidityVersionRange:
        # x.x.x := >=0.0.0
        if major is None:
            return SolidityVersionRange("0.0.0", True, None, None)
        # 1.x.x := >=1.0.0 <2.0.0
        elif minor is None:
            v1 = SolidityVersion(major, 0, 0)
            v2 = SolidityVersion(major + 1, 0, 0)
            return SolidityVersionRange(v1, True, v2, False)
        # 1.2.x := >=1.2.0 <1.3.0
        elif patch is None:
            v1 = SolidityVersion(major, minor, 0)
            v2 = SolidityVersion(major, minor + 1, 0)
            return SolidityVersionRange(v1, True, v2, False)
        # 1.2.3 := >=1.2.3 <=1.2.3
        else:
            v = SolidityVersion(major, minor, patch)
            return SolidityVersionRange(v, True, v, True)

    @classmethod
    def __parse_simple(cls, match_dict: dict, match_str: str) -> SolidityVersionRange:
        operator: Optional[str] = match_dict["operator"]
        major, minor, patch = cls.__parse_partial(match_dict, match_str)

        if operator == "^":
            return cls.__evaluate_caret(major, minor, patch, match_str)
        elif operator == "~":
            return cls.__evaluate_tilde(major, minor, patch, match_str)
        elif operator == "<":
            return cls.__evaluate_lt(major, minor, patch, match_str)
        elif operator == "<=":
            return cls.__evaluate_le(major, minor, patch, match_str)
        elif operator == ">":
            return cls.__evaluate_gt(major, minor, patch, match_str)
        elif operator == ">=":
            return cls.__evaluate_ge(major, minor, patch)
        elif operator == "=" or operator is None:
            return cls.__evaluate_eq(major, minor, patch)
        else:
            raise ValueError(cls.ERROR_MSG.format(value=match_str))

    def __contains__(self, item: Any) -> bool:
        """
        Return `True` if the given Solidity version is contained in this range, `False` otherwise.

        Args:
            item: The Solidity version to check for containment. Can be either a `SolidityVersion` instance or a string.
        """
        if isinstance(item, str):
            item = SolidityVersion.fromstring(item)
        if not isinstance(item, SolidityVersion):
            return NotImplemented
        for r in self.__ranges:
            if item in r:
                return True
        return False

    def __str__(self) -> str:
        """
        Return a user-friendly string representation of the range.
        """
        return self.__expression

    def __repr__(self) -> str:
        """
        Return a string representation of the range.
        """
        return f'{self.__class__.__name__}("{str(self)}")'

    @property
    def version_ranges(self) -> SolidityVersionRanges:
        """
        Return the parsed Solidity version ranges.
        """
        return self.__ranges

version_ranges: SolidityVersionRanges property #

Return the parsed Solidity version ranges.

__contains__(item) #

Return True if the given Solidity version is contained in this range, False otherwise.

Parameters:

Name Type Description Default
item Any

The Solidity version to check for containment. Can be either a SolidityVersion instance or a string.

required
Source code in wake/core/solidity_version.py
def __contains__(self, item: Any) -> bool:
    """
    Return `True` if the given Solidity version is contained in this range, `False` otherwise.

    Args:
        item: The Solidity version to check for containment. Can be either a `SolidityVersion` instance or a string.
    """
    if isinstance(item, str):
        item = SolidityVersion.fromstring(item)
    if not isinstance(item, SolidityVersion):
        return NotImplemented
    for r in self.__ranges:
        if item in r:
            return True
    return False

__init__(expr) #

Create a new instance of SolidityVersionExpr.

Parameters:

Name Type Description Default
expr str

The Solidity version expression to parse.

required
Source code in wake/core/solidity_version.py
def __init__(self, expr: str):
    """
    Create a new instance of `SolidityVersionExpr`.

    Args:
        expr: The Solidity version expression to parse.
    """
    cls = self.__class__
    self.__expression = expr
    evaluated_ranges = []

    ranges = expr.split("||")
    for r in ranges:
        if "-" in r:
            evaluated_ranges.append(cls.__parse_hyphen_range(r))
        else:
            evaluated_ranges.append(cls.__parse_range(r))
    self.__ranges = SolidityVersionRanges(evaluated_ranges)

__repr__() #

Return a string representation of the range.

Source code in wake/core/solidity_version.py
def __repr__(self) -> str:
    """
    Return a string representation of the range.
    """
    return f'{self.__class__.__name__}("{str(self)}")'

__str__() #

Return a user-friendly string representation of the range.

Source code in wake/core/solidity_version.py
def __str__(self) -> str:
    """
    Return a user-friendly string representation of the range.
    """
    return self.__expression

SolidityVersionRange class #

A class representing a range of Solidity versions by keeping the lower and the higher bound. Both bounds can be inclusive or non-inclusive. In case the lower bound is unspecified, the default value 0.0.0 (inclusive) is used. If the lower bound is semantically greater than the higher bound, create an empty range.

Source code in wake/core/solidity_version.py
class SolidityVersionRange:
    """
    A class representing a range of Solidity versions by keeping the lower and the higher bound.
    Both bounds can be inclusive or non-inclusive.
    In case the lower bound is unspecified, the default value 0.0.0 (inclusive) is used.
    If the lower bound is semantically greater than the higher bound, create an empty range.
    """

    __lower: SolidityVersion
    __lower_inclusive: bool
    __higher: Optional[SolidityVersion]
    __higher_inclusive: Optional[bool]

    def __init__(
        self,
        lower_bound: Optional[Union[SolidityVersion, str]],
        lower_inclusive: Optional[bool],
        higher_bound: Optional[Union[SolidityVersion, str]],
        higher_inclusive: Optional[bool],
    ):
        """
        Create a new instance of `SolidityVersionRange`.

        Args:
            lower_bound: The lower bound of the range. If `None`, the default value 0.0.0 is used.
            lower_inclusive: If `True`, the lower bound is inclusive, otherwise it is non-inclusive.
                May only be `None` if `lower_bound` is `None`.
            higher_bound: The higher bound of the range. If `None`, the range is unbounded.
            higher_inclusive: If `True`, the higher bound is inclusive, otherwise it is non-inclusive.
                May only be `None` if `higher_bound` is `None`.
        """
        if (lower_bound is None) != (lower_inclusive is None):
            raise ValueError(
                "Both arguments lower_bound and lower_inclusive must be either set or unset."
            )
        if (higher_bound is None) != (higher_inclusive is None):
            raise ValueError(
                "Both arguments higher_bound and higher_inclusive must be either set or unset."
            )

        self.__lower_inclusive = True if lower_inclusive is None else lower_inclusive
        if lower_bound is None:
            self.__lower = SolidityVersion(0, 0, 0)
        else:
            self.__lower = SolidityVersion.fromstring(str(lower_bound))

        self.__higher_inclusive = higher_inclusive
        if higher_bound is None:
            self.__higher = None
        else:
            self.__higher = SolidityVersion.fromstring(str(higher_bound))

            if (
                self.lower > self.higher
                or self.lower == self.higher
                and (not lower_inclusive or not higher_inclusive)
            ):
                # create an empty range
                self.__lower = SolidityVersion(0, 0, 0)
                self.__lower_inclusive = False
                self.__higher = self.lower
                self.__higher_inclusive = False

    def __contains__(self, item: Any) -> bool:
        """
        Return `True` if the given Solidity version is contained in this range, `False` otherwise.

        Args:
            item: The Solidity version to check for containment. Can be either a `SolidityVersion` instance or a string.
        """
        if isinstance(item, str):
            item = SolidityVersion.fromstring(item)
        if not isinstance(item, SolidityVersion):
            return NotImplemented
        if self.isempty():
            return False

        lower_check = item >= self.lower if self.lower_inclusive else item > self.lower
        if not lower_check or self.higher is None:
            return lower_check
        higher_check = (
            item <= self.higher if self.higher_inclusive else item < self.higher
        )
        return lower_check and higher_check

    def __hash__(self) -> int:
        """
        Return a hash of the range.
        """
        return hash(
            (
                self.lower,
                self.lower_inclusive,
                self.higher,
                self.higher_inclusive,
            )
        )

    def __eq__(self, other) -> bool:
        """
        Return `True` if the given range is equal to this range, `False` otherwise.
        """
        if not isinstance(other, SolidityVersionRange):
            return NotImplemented
        self_attr = (
            self.lower,
            self.lower_inclusive,
            self.higher,
            self.higher_inclusive,
        )
        other_attr = (
            other.lower,
            other.lower_inclusive,
            other.higher,
            other.higher_inclusive,
        )
        return self_attr == other_attr

    def __str__(self) -> str:
        """
        Return a user-friendly string representation of the range.
        """
        s = f"{'>=' if self.lower_inclusive else '>'}{self.lower}"
        if self.higher is not None:
            s = s + f" {'<=' if self.higher_inclusive else '<'}{self.higher}"
        return s

    def __repr__(self) -> str:
        """
        Return a string representation of the range.
        """
        lower = '"' + str(self.lower) + '"'
        higher = '"' + str(self.higher) + '"' if self.higher is not None else None
        return f"{self.__class__.__name__}({lower}, {self.lower_inclusive}, {higher}, {self.higher_inclusive})"

    def __and__(self, other: "SolidityVersionRange") -> "SolidityVersionRange":
        """
        Perform an intersection of two Solidity version ranges and return a new instance of `SolidityVersionRange`.
        """
        if self.lower < other.lower:
            lower_bound = other.lower
            lower_inclusive = other.lower_inclusive
        elif self.lower > other.lower:
            lower_bound = self.lower
            lower_inclusive = self.lower_inclusive
        else:
            lower_bound = self.lower
            if not self.lower_inclusive:
                lower_inclusive = self.lower_inclusive
            else:
                lower_inclusive = other.lower_inclusive

        if self.higher is None:
            higher_bound = other.higher
            higher_inclusive = other.higher_inclusive
        elif other.higher is None:
            higher_bound = self.higher
            higher_inclusive = self.higher_inclusive
        else:
            if self.higher < other.higher:
                higher_bound = self.higher
                higher_inclusive = self.higher_inclusive
            elif self.higher > other.higher:
                higher_bound = other.higher
                higher_inclusive = other.higher_inclusive
            else:
                higher_bound = self.higher
                if not self.higher_inclusive:
                    higher_inclusive = self.higher_inclusive
                else:
                    higher_inclusive = other.higher_inclusive

        return SolidityVersionRange(
            lower_bound, lower_inclusive, higher_bound, higher_inclusive
        )

    @classmethod
    def intersection(cls, *args: "SolidityVersionRange") -> "SolidityVersionRange":
        """
        Perform an intersection of all `SolidityVersionRange` arguments and return a new instance of `SolidityVersionRange`.
        """
        ret = cls(None, None, None, None)
        for r in args:
            ret &= r
        return ret

    def isempty(self) -> bool:
        """
        Return `True` if the range is empty (no Solidity version can be contained in this range), `False` otherwise.
        """
        return (
            self.lower == SolidityVersion(0, 0, 0)
            and not self.lower_inclusive
            and self.higher == SolidityVersion(0, 0, 0)
            and not self.higher_inclusive
        )

    @property
    def lower(self) -> SolidityVersion:
        """
        Return the lower bound of the range.
        """
        return self.__lower

    @property
    def lower_inclusive(self) -> bool:
        """
        Return `True` if the lower bound is inclusive, `False` otherwise.
        """
        return self.__lower_inclusive

    @property
    def higher(self) -> Optional[SolidityVersion]:
        """
        Return the higher bound of the range, if any.
        """
        return self.__higher

    @property
    def higher_inclusive(self) -> Optional[bool]:
        """
        Return `True` if the higher bound is inclusive, `False` if it is non-inclusive or `None` if the range is unbounded.
        """
        return self.__higher_inclusive

higher: Optional[SolidityVersion] property #

Return the higher bound of the range, if any.

higher_inclusive: Optional[bool] property #

Return True if the higher bound is inclusive, False if it is non-inclusive or None if the range is unbounded.

lower: SolidityVersion property #

Return the lower bound of the range.

lower_inclusive: bool property #

Return True if the lower bound is inclusive, False otherwise.

__and__(other) #

Perform an intersection of two Solidity version ranges and return a new instance of SolidityVersionRange.

Source code in wake/core/solidity_version.py
def __and__(self, other: "SolidityVersionRange") -> "SolidityVersionRange":
    """
    Perform an intersection of two Solidity version ranges and return a new instance of `SolidityVersionRange`.
    """
    if self.lower < other.lower:
        lower_bound = other.lower
        lower_inclusive = other.lower_inclusive
    elif self.lower > other.lower:
        lower_bound = self.lower
        lower_inclusive = self.lower_inclusive
    else:
        lower_bound = self.lower
        if not self.lower_inclusive:
            lower_inclusive = self.lower_inclusive
        else:
            lower_inclusive = other.lower_inclusive

    if self.higher is None:
        higher_bound = other.higher
        higher_inclusive = other.higher_inclusive
    elif other.higher is None:
        higher_bound = self.higher
        higher_inclusive = self.higher_inclusive
    else:
        if self.higher < other.higher:
            higher_bound = self.higher
            higher_inclusive = self.higher_inclusive
        elif self.higher > other.higher:
            higher_bound = other.higher
            higher_inclusive = other.higher_inclusive
        else:
            higher_bound = self.higher
            if not self.higher_inclusive:
                higher_inclusive = self.higher_inclusive
            else:
                higher_inclusive = other.higher_inclusive

    return SolidityVersionRange(
        lower_bound, lower_inclusive, higher_bound, higher_inclusive
    )

__contains__(item) #

Return True if the given Solidity version is contained in this range, False otherwise.

Parameters:

Name Type Description Default
item Any

The Solidity version to check for containment. Can be either a SolidityVersion instance or a string.

required
Source code in wake/core/solidity_version.py
def __contains__(self, item: Any) -> bool:
    """
    Return `True` if the given Solidity version is contained in this range, `False` otherwise.

    Args:
        item: The Solidity version to check for containment. Can be either a `SolidityVersion` instance or a string.
    """
    if isinstance(item, str):
        item = SolidityVersion.fromstring(item)
    if not isinstance(item, SolidityVersion):
        return NotImplemented
    if self.isempty():
        return False

    lower_check = item >= self.lower if self.lower_inclusive else item > self.lower
    if not lower_check or self.higher is None:
        return lower_check
    higher_check = (
        item <= self.higher if self.higher_inclusive else item < self.higher
    )
    return lower_check and higher_check

__eq__(other) #

Return True if the given range is equal to this range, False otherwise.

Source code in wake/core/solidity_version.py
def __eq__(self, other) -> bool:
    """
    Return `True` if the given range is equal to this range, `False` otherwise.
    """
    if not isinstance(other, SolidityVersionRange):
        return NotImplemented
    self_attr = (
        self.lower,
        self.lower_inclusive,
        self.higher,
        self.higher_inclusive,
    )
    other_attr = (
        other.lower,
        other.lower_inclusive,
        other.higher,
        other.higher_inclusive,
    )
    return self_attr == other_attr

__hash__() #

Return a hash of the range.

Source code in wake/core/solidity_version.py
def __hash__(self) -> int:
    """
    Return a hash of the range.
    """
    return hash(
        (
            self.lower,
            self.lower_inclusive,
            self.higher,
            self.higher_inclusive,
        )
    )

__init__(lower_bound, lower_inclusive, higher_bound, higher_inclusive) #

Create a new instance of SolidityVersionRange.

Parameters:

Name Type Description Default
lower_bound Optional[Union[SolidityVersion, str]]

The lower bound of the range. If None, the default value 0.0.0 is used.

required
lower_inclusive Optional[bool]

If True, the lower bound is inclusive, otherwise it is non-inclusive. May only be None if lower_bound is None.

required
higher_bound Optional[Union[SolidityVersion, str]]

The higher bound of the range. If None, the range is unbounded.

required
higher_inclusive Optional[bool]

If True, the higher bound is inclusive, otherwise it is non-inclusive. May only be None if higher_bound is None.

required
Source code in wake/core/solidity_version.py
def __init__(
    self,
    lower_bound: Optional[Union[SolidityVersion, str]],
    lower_inclusive: Optional[bool],
    higher_bound: Optional[Union[SolidityVersion, str]],
    higher_inclusive: Optional[bool],
):
    """
    Create a new instance of `SolidityVersionRange`.

    Args:
        lower_bound: The lower bound of the range. If `None`, the default value 0.0.0 is used.
        lower_inclusive: If `True`, the lower bound is inclusive, otherwise it is non-inclusive.
            May only be `None` if `lower_bound` is `None`.
        higher_bound: The higher bound of the range. If `None`, the range is unbounded.
        higher_inclusive: If `True`, the higher bound is inclusive, otherwise it is non-inclusive.
            May only be `None` if `higher_bound` is `None`.
    """
    if (lower_bound is None) != (lower_inclusive is None):
        raise ValueError(
            "Both arguments lower_bound and lower_inclusive must be either set or unset."
        )
    if (higher_bound is None) != (higher_inclusive is None):
        raise ValueError(
            "Both arguments higher_bound and higher_inclusive must be either set or unset."
        )

    self.__lower_inclusive = True if lower_inclusive is None else lower_inclusive
    if lower_bound is None:
        self.__lower = SolidityVersion(0, 0, 0)
    else:
        self.__lower = SolidityVersion.fromstring(str(lower_bound))

    self.__higher_inclusive = higher_inclusive
    if higher_bound is None:
        self.__higher = None
    else:
        self.__higher = SolidityVersion.fromstring(str(higher_bound))

        if (
            self.lower > self.higher
            or self.lower == self.higher
            and (not lower_inclusive or not higher_inclusive)
        ):
            # create an empty range
            self.__lower = SolidityVersion(0, 0, 0)
            self.__lower_inclusive = False
            self.__higher = self.lower
            self.__higher_inclusive = False

__repr__() #

Return a string representation of the range.

Source code in wake/core/solidity_version.py
def __repr__(self) -> str:
    """
    Return a string representation of the range.
    """
    lower = '"' + str(self.lower) + '"'
    higher = '"' + str(self.higher) + '"' if self.higher is not None else None
    return f"{self.__class__.__name__}({lower}, {self.lower_inclusive}, {higher}, {self.higher_inclusive})"

__str__() #

Return a user-friendly string representation of the range.

Source code in wake/core/solidity_version.py
def __str__(self) -> str:
    """
    Return a user-friendly string representation of the range.
    """
    s = f"{'>=' if self.lower_inclusive else '>'}{self.lower}"
    if self.higher is not None:
        s = s + f" {'<=' if self.higher_inclusive else '<'}{self.higher}"
    return s

intersection(*args) classmethod #

Perform an intersection of all SolidityVersionRange arguments and return a new instance of SolidityVersionRange.

Source code in wake/core/solidity_version.py
@classmethod
def intersection(cls, *args: "SolidityVersionRange") -> "SolidityVersionRange":
    """
    Perform an intersection of all `SolidityVersionRange` arguments and return a new instance of `SolidityVersionRange`.
    """
    ret = cls(None, None, None, None)
    for r in args:
        ret &= r
    return ret

isempty() #

Return True if the range is empty (no Solidity version can be contained in this range), False otherwise.

Source code in wake/core/solidity_version.py
def isempty(self) -> bool:
    """
    Return `True` if the range is empty (no Solidity version can be contained in this range), `False` otherwise.
    """
    return (
        self.lower == SolidityVersion(0, 0, 0)
        and not self.lower_inclusive
        and self.higher == SolidityVersion(0, 0, 0)
        and not self.higher_inclusive
    )

SolidityVersionRanges class #

Helper class implementing intersection on List[SolidityVersionRange]. No normalization is performed, i.e. the ranges are taken as is without merging.

Source code in wake/core/solidity_version.py
class SolidityVersionRanges:
    """
    Helper class implementing intersection on List[SolidityVersionRange].
    No normalization is performed, i.e. the ranges are taken as is without merging.
    """

    __version_ranges: Tuple[SolidityVersionRange, ...]

    def __init__(self, version_ranges: Iterable[SolidityVersionRange]):
        """
        Create a new instance of `SolidityVersionRanges`.

        Args:
            version_ranges: The Solidity version ranges that this instance should represent.
        """
        self.__version_ranges = tuple(version_ranges)

    def __and__(self, other: Any):
        """
        Perform an intersection of two `SolidityVersionRanges` and return a new instance of `SolidityVersionRanges`.

        Args:
            other: The other `SolidityVersionRanges` instance to intersect with.
        """
        if not isinstance(other, SolidityVersionRanges):
            return NotImplemented
        ret = []
        for r1, r2 in itertools.product(self.version_ranges, other.version_ranges):
            new_range = r1 & r2
            if not new_range.isempty():
                ret.append(new_range)
        return SolidityVersionRanges(ret)

    def __iter__(self):
        for version_range in self.__version_ranges:
            yield version_range

    def __len__(self):
        return len(self.__version_ranges)

    def __str__(self):
        return " || ".join(
            str(version_range) for version_range in self.__version_ranges
        )

    def __contains__(self, item):
        if isinstance(item, str):
            item = SolidityVersion.fromstring(item)
        if not isinstance(item, SolidityVersion):
            return NotImplemented
        return any(item in version_range for version_range in self.__version_ranges)

    @property
    def version_ranges(self) -> Tuple[SolidityVersionRange, ...]:
        """
        Return the Solidity version ranges that this instance represents.
        """
        return self.__version_ranges

version_ranges: Tuple[SolidityVersionRange, ...] property #

Return the Solidity version ranges that this instance represents.

__and__(other) #

Perform an intersection of two SolidityVersionRanges and return a new instance of SolidityVersionRanges.

Parameters:

Name Type Description Default
other Any

The other SolidityVersionRanges instance to intersect with.

required
Source code in wake/core/solidity_version.py
def __and__(self, other: Any):
    """
    Perform an intersection of two `SolidityVersionRanges` and return a new instance of `SolidityVersionRanges`.

    Args:
        other: The other `SolidityVersionRanges` instance to intersect with.
    """
    if not isinstance(other, SolidityVersionRanges):
        return NotImplemented
    ret = []
    for r1, r2 in itertools.product(self.version_ranges, other.version_ranges):
        new_range = r1 & r2
        if not new_range.isempty():
            ret.append(new_range)
    return SolidityVersionRanges(ret)

__init__(version_ranges) #

Create a new instance of SolidityVersionRanges.

Parameters:

Name Type Description Default
version_ranges Iterable[SolidityVersionRange]

The Solidity version ranges that this instance should represent.

required
Source code in wake/core/solidity_version.py
def __init__(self, version_ranges: Iterable[SolidityVersionRange]):
    """
    Create a new instance of `SolidityVersionRanges`.

    Args:
        version_ranges: The Solidity version ranges that this instance should represent.
    """
    self.__version_ranges = tuple(version_ranges)