diff --git a/src/anycastd/healthcheck/_cabourotte/main.py b/src/anycastd/healthcheck/_cabourotte/main.py index 4e85417..499bacf 100644 --- a/src/anycastd/healthcheck/_cabourotte/main.py +++ b/src/anycastd/healthcheck/_cabourotte/main.py @@ -9,13 +9,15 @@ class CabourotteHealthcheck(Healthcheck): url: str def __init__(self, name: str, *, url: str, interval: datetime.timedelta): - super().__init__(interval) + if not isinstance(interval, datetime.timedelta): + raise TypeError("Interval must be a timedelta.") if not isinstance(name, str): raise TypeError("Name must be a string.") if not isinstance(url, str): raise TypeError("URL must be a string.") self.name = name self.url = url + self.__interval = interval def __repr__(self) -> str: return ( @@ -23,6 +25,10 @@ def __repr__(self) -> str: f"interval={self.interval!r})" ) + @property + def interval(self) -> datetime.timedelta: + return self.__interval + async def _check(self) -> bool: """Return whether the healthcheck is healthy or not.""" result = await get_result(self.name, url=self.url) diff --git a/src/anycastd/healthcheck/_main.py b/src/anycastd/healthcheck/_main.py index 8b751fd..d94eb2f 100644 --- a/src/anycastd/healthcheck/_main.py +++ b/src/anycastd/healthcheck/_main.py @@ -6,15 +6,14 @@ class Healthcheck(ABC): """A healthcheck that represents a status.""" - interval: datetime.timedelta - _last_checked: datetime.datetime | None = None _last_healthy: bool = False - def __init__(self, interval: datetime.timedelta): - if not isinstance(interval, datetime.timedelta): - raise TypeError("Interval must be a timedelta.") - self.interval = interval + @property + @abstractmethod + def interval(self) -> datetime.timedelta: + """The interval between checks.""" + raise NotImplementedError @final async def is_healthy(self) -> bool: diff --git a/src/anycastd/prefix/_frrouting/main.py b/src/anycastd/prefix/_frrouting/main.py index f987976..d06a40b 100644 --- a/src/anycastd/prefix/_frrouting/main.py +++ b/src/anycastd/prefix/_frrouting/main.py @@ -34,7 +34,9 @@ def __init__( to validate the prefix against the FRRouting configuration, avoiding potential errors later in runtime. """ - super().__init__(prefix) + if not any((isinstance(prefix, IPv4Network), isinstance(prefix, IPv6Network))): + raise TypeError("Prefix must be an IPv4 or IPv6 network.") + self.__prefix = prefix self.vrf = vrf self.vtysh = vtysh self.executor = executor @@ -45,6 +47,10 @@ def __repr__(self) -> str: f"vtysh={self.vtysh!r}, executor={self.executor!r})" ) + @property + def prefix(self) -> IPv4Network | IPv6Network: + return self.__prefix + async def is_announced(self) -> bool: """Returns True if the prefix is announced. diff --git a/src/anycastd/prefix/_main.py b/src/anycastd/prefix/_main.py index b96d4c5..842eff5 100644 --- a/src/anycastd/prefix/_main.py +++ b/src/anycastd/prefix/_main.py @@ -8,12 +8,11 @@ class Prefix(ABC): """An IP prefix that can be announced or denounced.""" - prefix: IPv4Network | IPv6Network - - def __init__(self, prefix: IPv4Network | IPv6Network): - if not any((isinstance(prefix, IPv4Network), isinstance(prefix, IPv6Network))): - raise TypeError("Prefix must be an IPv4 or IPv6 network.") - self.prefix = prefix + @property + @abstractmethod + def prefix(self) -> IPv4Network | IPv6Network: + """The IP prefix.""" + raise NotImplementedError @abstractmethod async def is_announced(self) -> bool: diff --git a/tests/dummy.py b/tests/dummy.py index d6e6606..98181ad 100644 --- a/tests/dummy.py +++ b/tests/dummy.py @@ -1,3 +1,6 @@ +import datetime +from ipaddress import IPv4Network, IPv6Network + from anycastd.healthcheck import Healthcheck from anycastd.prefix import Prefix @@ -5,6 +8,13 @@ class DummyHealthcheck(Healthcheck): """A dummy healthcheck to test the abstract base class.""" + def __init__(self, interval: datetime.timedelta, *args, **kwargs): + self.__interval = interval + + @property + def interval(self) -> datetime.timedelta: + return self.__interval + async def _check(self) -> bool: """Always healthy.""" return True @@ -13,6 +23,13 @@ async def _check(self) -> bool: class DummyPrefix(Prefix): """A dummy prefix to test the abstract base class.""" + def __init__(self, prefix: IPv4Network | IPv6Network, *args, **kwargs): + self.__prefix = prefix + + @property + def prefix(self) -> IPv4Network | IPv6Network: + return self.__prefix + async def is_announced(self) -> bool: """Always announced.""" return True diff --git a/tests/prefix/test_base.py b/tests/prefix/test_base.py index 701ab3d..0ee0cb3 100644 --- a/tests/prefix/test_base.py +++ b/tests/prefix/test_base.py @@ -1,16 +1,8 @@ from ipaddress import IPv4Network, IPv6Network -import pytest - from tests.dummy import DummyPrefix -def test__init___non_network_raises_type_error(): - """Passing a non network prefix raises a TypeError.""" - with pytest.raises(TypeError): - DummyPrefix("2001:db8::/32") # type: ignore - - def test__init__ip_network(example_networks: IPv4Network | IPv6Network): """IPv4Network and IPv6Network prefixes are accepted.""" DummyPrefix(example_networks)