Skip to content

Commit

Permalink
chore: Split up interface and implementation for controller
Browse files Browse the repository at this point in the history
The interface file now has no internal dependencies except for typing
  • Loading branch information
Amund211 committed Sep 10, 2024
1 parent a1c54a7 commit c41f79b
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 133 deletions.
2 changes: 1 addition & 1 deletion src/prism/overlay/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ def main() -> None: # pragma: nocover
nick_database = NickDatabase.from_disk([], default_database=default_database)

# Import late so we can patch ssl certs in requests
from prism.overlay.controller import RealOverlayController
from prism.overlay.process_loglines import process_loglines, prompt_and_read_logfile
from prism.overlay.real_controller import RealOverlayController

controller = RealOverlayController(
state=OverlayState(),
Expand Down
129 changes: 4 additions & 125 deletions src/prism/overlay/controller.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,17 @@
import logging
import threading
import time
from abc import abstractmethod
from collections.abc import Callable, Mapping
from enum import Enum
from typing import TYPE_CHECKING, Protocol

from prism.hypixel import (
HypixelAPIError,
HypixelAPIKeyError,
HypixelAPIThrottleError,
HypixelPlayerNotFoundError,
)
from prism.mojang import MojangAPIError, get_uuid
from prism.overlay.antisniper_api import (
AntiSniperAPIKeyHolder,
get_estimated_winstreaks,
get_playerdata,
)
from prism.overlay.player import MISSING_WINSTREAKS
from prism.ratelimiting import RateLimiter
from prism.ssl_errors import MissingLocalIssuerSSLError

if TYPE_CHECKING: # pragma: no cover
from prism.overlay.antisniper_api import AntiSniperAPIKeyHolder
from prism.overlay.nick_database import NickDatabase
from prism.overlay.player import Winstreaks
from prism.overlay.player_cache import PlayerCache
from prism.overlay.settings import Settings
from prism.overlay.state import OverlayState

logger = logging.getLogger(__name__)

API_REQUEST_LIMIT = 120
API_REQUEST_WINDOW = 60
from prism.ratelimiting import RateLimiter


class ProcessingError(Enum):
Expand All @@ -48,8 +27,8 @@ class OverlayController(Protocol): # pragma: no cover
api_key_invalid: bool
api_key_throttled: bool
missing_local_issuer_certificate: bool
antisniper_key_holder: AntiSniperAPIKeyHolder | None
api_limiter: RateLimiter
antisniper_key_holder: "AntiSniperAPIKeyHolder | None"
api_limiter: "RateLimiter"

wants_shown: bool | None
state: "OverlayState"
Expand Down Expand Up @@ -80,103 +59,3 @@ def get_estimated_winstreaks(self) -> Callable[[str], tuple["Winstreaks", bool]]
@abstractmethod
def store_settings(self) -> Callable[[], None]:
raise NotImplementedError


class RealOverlayController:
def __init__(
self,
state: "OverlayState",
settings: "Settings",
nick_database: "NickDatabase",
) -> None:
from prism.overlay.player_cache import PlayerCache

self.api_key_invalid = False
self.api_key_throttled = False
self.missing_local_issuer_certificate = False
self.api_limiter = RateLimiter(
limit=API_REQUEST_LIMIT, window=API_REQUEST_WINDOW
)

self.wants_shown: bool | None = None
self.player_cache = PlayerCache()
self.state = state
self.settings = settings
self.nick_database = nick_database
self.redraw_event = threading.Event()
self.update_presence_event = threading.Event()

self.antisniper_key_holder: AntiSniperAPIKeyHolder | None = (
AntiSniperAPIKeyHolder(settings.antisniper_api_key)
if settings.antisniper_api_key is not None
else None
)

def get_uuid(self, username: str) -> str | None | ProcessingError:
try:
uuid = get_uuid(username)
except MissingLocalIssuerSSLError:
logger.exception("get_uuid: missing local issuer cert")
self.missing_local_issuer_certificate = True
return ERROR_DURING_PROCESSING
except MojangAPIError as e:
logger.debug(f"Failed getting uuid for username {username}.", exc_info=e)
# TODO: RETURN SOMETHING ELSE
return ERROR_DURING_PROCESSING
else:
self.missing_local_issuer_certificate = False
return uuid

def get_playerdata(
self, uuid: str
) -> tuple[int, Mapping[str, object] | None | ProcessingError]:
# TODO: set api key flags
try:
playerdata = get_playerdata(
uuid,
self.settings.user_id,
self.antisniper_key_holder,
self.api_limiter,
)
except MissingLocalIssuerSSLError:
logger.exception("get_playerdata: missing local issuer cert")
self.missing_local_issuer_certificate = True
return 0, ERROR_DURING_PROCESSING
except HypixelPlayerNotFoundError as e:
logger.debug(f"Player not found on Hypixel: {uuid=}", exc_info=e)
self.api_key_invalid = False
self.api_key_throttled = False
self.missing_local_issuer_certificate = False
return 0, None
except HypixelAPIError as e:
logger.error(f"Hypixel API error getting stats for {uuid=}", exc_info=e)
return 0, ERROR_DURING_PROCESSING
except HypixelAPIKeyError as e:
logger.warning(f"Invalid API key getting stats for {uuid=}", exc_info=e)
self.api_key_invalid = True
self.api_key_throttled = False
self.missing_local_issuer_certificate = False
return 0, ERROR_DURING_PROCESSING
except HypixelAPIThrottleError as e:
logger.warning(f"API key throttled getting stats for {uuid=}", exc_info=e)
self.api_key_invalid = False
self.api_key_throttled = True
self.missing_local_issuer_certificate = False
return 0, ERROR_DURING_PROCESSING
else:
dataReceivedAtMs = time.time_ns() // 1_000_000
self.api_key_invalid = False
self.api_key_throttled = False
self.missing_local_issuer_certificate = False
return dataReceivedAtMs, playerdata

def get_estimated_winstreaks(
self, uuid: str
) -> tuple["Winstreaks", bool]: # pragma: no cover
if not self.settings.use_antisniper_api or self.antisniper_key_holder is None:
return MISSING_WINSTREAKS, False

return get_estimated_winstreaks(uuid, self.antisniper_key_holder)

def store_settings(self) -> None:
self.settings.flush_to_disk()
6 changes: 3 additions & 3 deletions src/prism/overlay/process_loglines.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from collections.abc import Iterable

from prism.overlay.commandline import Options
from prism.overlay.controller import OverlayController, RealOverlayController
from prism.overlay.controller import OverlayController
from prism.overlay.directories import DEFAULT_LOGFILE_CACHE_PATH
from prism.overlay.file_utils import watch_file_with_reopen
from prism.overlay.output.overlay.run_overlay import run_overlay
Expand All @@ -17,7 +17,7 @@


def prompt_and_read_logfile(
controller: RealOverlayController, options: Options, settings: Settings
controller: OverlayController, options: Options, settings: Settings
) -> Iterable[str]: # pragma: nocover
if options.logfile_path is None:
logfile_path = prompt_for_logfile_path(
Expand Down Expand Up @@ -94,7 +94,7 @@ def get_stat_list() -> list[Player] | None:


def process_loglines(
controller: RealOverlayController,
controller: OverlayController,
loglines: Iterable[str],
overlay: bool,
console: bool,
Expand Down
134 changes: 134 additions & 0 deletions src/prism/overlay/real_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import logging
import threading
import time
from collections.abc import Mapping
from typing import TYPE_CHECKING

from prism.hypixel import (
HypixelAPIError,
HypixelAPIKeyError,
HypixelAPIThrottleError,
HypixelPlayerNotFoundError,
)
from prism.mojang import MojangAPIError, get_uuid
from prism.overlay.antisniper_api import (
AntiSniperAPIKeyHolder,
get_estimated_winstreaks,
get_playerdata,
)
from prism.overlay.controller import ERROR_DURING_PROCESSING, ProcessingError
from prism.overlay.player import MISSING_WINSTREAKS
from prism.ratelimiting import RateLimiter
from prism.ssl_errors import MissingLocalIssuerSSLError

if TYPE_CHECKING: # pragma: no cover
from prism.overlay.nick_database import NickDatabase
from prism.overlay.player import Winstreaks
from prism.overlay.settings import Settings
from prism.overlay.state import OverlayState

logger = logging.getLogger(__name__)

API_REQUEST_LIMIT = 120
API_REQUEST_WINDOW = 60


class RealOverlayController:
def __init__(
self,
state: "OverlayState",
settings: "Settings",
nick_database: "NickDatabase",
) -> None:
from prism.overlay.player_cache import PlayerCache

self.api_key_invalid = False
self.api_key_throttled = False
self.missing_local_issuer_certificate = False
self.api_limiter = RateLimiter(
limit=API_REQUEST_LIMIT, window=API_REQUEST_WINDOW
)

self.wants_shown: bool | None = None
self.player_cache = PlayerCache()
self.state = state
self.settings = settings
self.nick_database = nick_database
self.redraw_event = threading.Event()
self.update_presence_event = threading.Event()
self.autowho_event = threading.Event()

self.antisniper_key_holder: AntiSniperAPIKeyHolder | None = (
AntiSniperAPIKeyHolder(settings.antisniper_api_key)
if settings.antisniper_api_key is not None
else None
)

def get_uuid(self, username: str) -> str | None | ProcessingError:
try:
uuid = get_uuid(username)
except MissingLocalIssuerSSLError:
logger.exception("get_uuid: missing local issuer cert")
self.missing_local_issuer_certificate = True
return ERROR_DURING_PROCESSING
except MojangAPIError as e:
logger.debug(f"Failed getting uuid for username {username}.", exc_info=e)
# TODO: RETURN SOMETHING ELSE
return ERROR_DURING_PROCESSING
else:
self.missing_local_issuer_certificate = False
return uuid

def get_playerdata(
self, uuid: str
) -> tuple[int, Mapping[str, object] | None | ProcessingError]:
# TODO: set api key flags
try:
playerdata = get_playerdata(
uuid,
self.settings.user_id,
self.antisniper_key_holder,
self.api_limiter,
)
except MissingLocalIssuerSSLError:
logger.exception("get_playerdata: missing local issuer cert")
self.missing_local_issuer_certificate = True
return 0, ERROR_DURING_PROCESSING
except HypixelPlayerNotFoundError as e:
logger.debug(f"Player not found on Hypixel: {uuid=}", exc_info=e)
self.api_key_invalid = False
self.api_key_throttled = False
self.missing_local_issuer_certificate = False
return 0, None
except HypixelAPIError as e:
logger.error(f"Hypixel API error getting stats for {uuid=}", exc_info=e)
return 0, ERROR_DURING_PROCESSING
except HypixelAPIKeyError as e:
logger.warning(f"Invalid API key getting stats for {uuid=}", exc_info=e)
self.api_key_invalid = True
self.api_key_throttled = False
self.missing_local_issuer_certificate = False
return 0, ERROR_DURING_PROCESSING
except HypixelAPIThrottleError as e:
logger.warning(f"API key throttled getting stats for {uuid=}", exc_info=e)
self.api_key_invalid = False
self.api_key_throttled = True
self.missing_local_issuer_certificate = False
return 0, ERROR_DURING_PROCESSING
else:
dataReceivedAtMs = time.time_ns() // 1_000_000
self.api_key_invalid = False
self.api_key_throttled = False
self.missing_local_issuer_certificate = False
return dataReceivedAtMs, playerdata

def get_estimated_winstreaks(
self, uuid: str
) -> tuple["Winstreaks", bool]: # pragma: no cover
if not self.settings.use_antisniper_api or self.antisniper_key_holder is None:
return MISSING_WINSTREAKS, False

return get_estimated_winstreaks(uuid, self.antisniper_key_holder)

def store_settings(self) -> None:
self.settings.flush_to_disk()
7 changes: 4 additions & 3 deletions tests/prism/overlay/test_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
)
from prism.mojang import MojangAPIError
from prism.overlay.antisniper_api import AntiSniperAPIKeyHolder
from prism.overlay.controller import ERROR_DURING_PROCESSING, RealOverlayController
from prism.overlay.controller import ERROR_DURING_PROCESSING
from prism.overlay.nick_database import NickDatabase
from prism.overlay.real_controller import RealOverlayController
from prism.ratelimiting import RateLimiter
from prism.ssl_errors import MissingLocalIssuerSSLError
from tests.prism.overlay.utils import create_state, make_settings
Expand Down Expand Up @@ -66,7 +67,7 @@ def get_uuid(username: str) -> str | None:
return returned_uuid

with unittest.mock.patch(
"prism.overlay.controller.get_uuid",
"prism.overlay.real_controller.get_uuid",
get_uuid,
):
error = MojangAPIError()
Expand Down Expand Up @@ -118,7 +119,7 @@ def get_playerdata(
return returned_playerdata

with unittest.mock.patch(
"prism.overlay.controller.get_playerdata",
"prism.overlay.real_controller.get_playerdata",
get_playerdata,
):
error = HypixelAPIError()
Expand Down
2 changes: 1 addition & 1 deletion tests/prism/overlay/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ class FakeUUID:


def test_flush_settings_from_controller(tmp_path: Path) -> None:
from prism.overlay.controller import RealOverlayController
from prism.overlay.nick_database import NickDatabase
from prism.overlay.real_controller import RealOverlayController
from tests.prism.overlay.utils import create_state

settings = make_settings(path=tmp_path / "settings.toml")
Expand Down

0 comments on commit c41f79b

Please sign in to comment.