@dataclass
class BoundaryType(AbstractType):
NEGATIVE_INFINITY: ClassVar = "NegativeInfinity"
INFINITY: ClassVar = "Infinity"
base_type: str
min: Union[float, int, str]
max: Union[float, int, str]
min_inclusive: bool
max_inclusive: bool
full_match: str = ""
@classmethod
def _is_inclusive(cls, bracket: str) -> bool:
if bracket in ("(", ")"):
return False
if bracket in ("[", "]"):
return True
raise Exception(f"{bracket} is not one of []()")
@classmethod
def from_json(cls, json: Any) -> Optional[BoundaryType]:
if json["kind"] == cls.__name__:
return BoundaryType(
json["base_type"],
json["min"],
json["max"],
json["min_inclusive"],
json["max_inclusive"],
)
return None
@classmethod
def from_string(cls, string: str) -> Optional[BoundaryType]:
# language=PythonRegExp
pattern = r"""(?P<base_type>float|int)?[ ] # optional base type of either float or int
(in|of)[ ](the[ ])?(range|interval)[ ](of[ ])? # 'in' or 'of', optional 'the', 'range' or 'interval', optional 'of'
`?(?P<min_bracket>[\[(])(?P<min>[-+]?\d+(.\d*)?|negative_infinity),[ ] # left side of the range
(?P<max>[-+]?\d+(.\d*)?|infinity)(?P<max_bracket>[\])])`?""" # right side of the range
match = re.search(pattern, string, re.VERBOSE)
if match is not None:
base_type = match.group("base_type")
if base_type is None:
base_type = "float"
min_value: Union[str, int, float] = match.group("min")
if min_value != "negative_infinity":
if base_type == "int":
min_value = int(min_value)
else:
min_value = float(min_value)
else:
min_value = BoundaryType.NEGATIVE_INFINITY
max_value: Union[str, int, float] = match.group("max")
if max_value != "infinity":
if base_type == "int":
max_value = int(max_value)
else:
max_value = float(max_value)
else:
max_value = BoundaryType.INFINITY
min_bracket = match.group("min_bracket")
max_bracket = match.group("max_bracket")
min_inclusive = BoundaryType._is_inclusive(min_bracket)
max_inclusive = BoundaryType._is_inclusive(max_bracket)
return BoundaryType(
base_type=base_type,
min=min_value,
max=max_value,
min_inclusive=min_inclusive,
max_inclusive=max_inclusive,
full_match=match.group(0),
)
return None
def __eq__(self, __o: object) -> bool:
if isinstance(__o, BoundaryType):
eq = (
self.base_type == __o.base_type
and self.min == __o.min
and self.min_inclusive == __o.min_inclusive
and self.max == __o.max
)
if eq:
if self.max == BoundaryType.INFINITY:
return True
return self.max_inclusive == __o.max_inclusive
return False
def __hash__(self) -> int:
return hash(
(
self.base_type,
self.min,
self.min_inclusive,
self.max,
self.max_inclusive,
self.full_match,
)
)
def to_json(self) -> dict[str, Any]:
return {
"kind": self.__class__.__name__,
"base_type": self.base_type,
"min": self.min,
"max": self.max,
"min_inclusive": self.min_inclusive,
"max_inclusive": self.max_inclusive,
}