From 2cda75ff2c75a4a85300338e9e902ceb3c480190 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Wed, 22 Feb 2023 08:19:39 -0500 Subject: [PATCH] utils: simplify sentinel object Use an enum to represent the sentinel rather than a singleton object. Signed-off-by: Eric Callahan --- moonraker/components/database.py | 18 +++++++--------- moonraker/components/klippy_apis.py | 25 +++++++++++----------- moonraker/confighelper.py | 33 ++++++++++++++--------------- moonraker/server.py | 27 ++++++++++++----------- moonraker/utils/__init__.py | 11 +++------- moonraker/websockets.py | 23 ++++++++++---------- 6 files changed, 63 insertions(+), 74 deletions(-) diff --git a/moonraker/components/database.py b/moonraker/components/database.py index 137d299..afe08c7 100644 --- a/moonraker/components/database.py +++ b/moonraker/components/database.py @@ -15,7 +15,7 @@ from io import BytesIO from functools import reduce from threading import Lock as ThreadLock import lmdb -from ..utils import SentinelClass, ServerError +from ..utils import Sentinel, ServerError # Annotation imports from typing import ( @@ -61,8 +61,6 @@ RECORD_DECODE_FUNCS = { ord("{"): lambda x: json.loads(bytes(x)), } -SENTINEL = SentinelClass.get_instance() - def getitem_with_default(item: Dict, field: Any) -> Any: if not isinstance(item, Dict): raise ServerError( @@ -346,14 +344,14 @@ class MoonrakerDatabase: def get_item(self, namespace: str, key: Optional[Union[List[str], str]] = None, - default: Any = SENTINEL + default: Any = Sentinel.MISSING ) -> Future[Any]: return self._run_command(self._get_impl, namespace, key, default) def _get_impl(self, namespace: str, key: Optional[Union[List[str], str]] = None, - default: Any = SENTINEL + default: Any = Sentinel.MISSING ) -> Any: try: if key is None: @@ -363,7 +361,7 @@ class MoonrakerDatabase: val = reduce(operator.getitem, # type: ignore key_list[1:], ns) except Exception as e: - if not isinstance(default, SentinelClass): + if default is not Sentinel.MISSING: return default if isinstance(e, self.server.error): raise @@ -875,7 +873,7 @@ class NamespaceWrapper: return self.db._get_namespace(self.namespace) def __getitem__(self, key: Union[List[str], str]) -> Future[Any]: - return self.get(key, default=SENTINEL) + return self.get(key, default=Sentinel.MISSING) def __setitem__(self, key: Union[List[str], str], @@ -908,13 +906,13 @@ class NamespaceWrapper: def pop(self, key: Union[List[str], str], - default: Any = SENTINEL + default: Any = Sentinel.MISSING ) -> Union[Future[Any], Task[Any]]: if not self.server.is_running(): try: val = self.delete(key).result() except Exception: - if isinstance(default, SentinelClass): + if default is Sentinel.MISSING: raise val = default fut = self.eventloop.create_future() @@ -925,7 +923,7 @@ class NamespaceWrapper: try: val = await self.delete(key) except Exception: - if isinstance(default, SentinelClass): + if default is Sentinel.MISSING: raise val = default return val diff --git a/moonraker/components/klippy_apis.py b/moonraker/components/klippy_apis.py index 876c163..a668a4c 100644 --- a/moonraker/components/klippy_apis.py +++ b/moonraker/components/klippy_apis.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. from __future__ import annotations -from ..utils import SentinelClass +from ..utils import Sentinel from ..websockets import WebRequest, Subscribable # Annotation imports @@ -35,7 +35,6 @@ SUBSCRIPTION_ENDPOINT = "objects/subscribe" STATUS_ENDPOINT = "objects/query" OBJ_LIST_ENDPOINT = "objects/list" REG_METHOD_ENDPOINT = "register_remote_method" -SENTINEL = SentinelClass.get_instance() class KlippyAPI(Subscribable): def __init__(self, config: ConfigHelper) -> None: @@ -84,20 +83,20 @@ class KlippyAPI(Subscribable): self, method: str, params: Dict[str, Any], - default: Any = SENTINEL + default: Any = Sentinel.MISSING ) -> Any: try: req = WebRequest(method, params, conn=self) result = await self.klippy.request(req) except self.server.error: - if isinstance(default, SentinelClass): + if default is Sentinel.MISSING: raise result = default return result async def run_gcode(self, script: str, - default: Any = SENTINEL + default: Any = Sentinel.MISSING ) -> str: params = {'script': script} result = await self._send_klippy_request( @@ -123,21 +122,21 @@ class KlippyAPI(Subscribable): return await self.run_gcode(script) async def pause_print( - self, default: Union[SentinelClass, _T] = SENTINEL + self, default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[_T, str]: self.server.send_event("klippy_apis:pause_requested") return await self._send_klippy_request( "pause_resume/pause", {}, default) async def resume_print( - self, default: Union[SentinelClass, _T] = SENTINEL + self, default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[_T, str]: self.server.send_event("klippy_apis:resume_requested") return await self._send_klippy_request( "pause_resume/resume", {}, default) async def cancel_print( - self, default: Union[SentinelClass, _T] = SENTINEL + self, default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[_T, str]: self.server.send_event("klippy_apis:cancel_requested") return await self._send_klippy_request( @@ -163,7 +162,7 @@ class KlippyAPI(Subscribable): return result async def list_endpoints(self, - default: Union[SentinelClass, _T] = SENTINEL + default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[_T, Dict[str, List[str]]]: return await self._send_klippy_request( LIST_EPS_ENDPOINT, {}, default) @@ -173,7 +172,7 @@ class KlippyAPI(Subscribable): async def get_klippy_info(self, send_id: bool = False, - default: Union[SentinelClass, _T] = SENTINEL + default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[_T, Dict[str, Any]]: params = {} if send_id: @@ -182,7 +181,7 @@ class KlippyAPI(Subscribable): return await self._send_klippy_request(INFO_ENDPOINT, params, default) async def get_object_list(self, - default: Union[SentinelClass, _T] = SENTINEL + default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[_T, List[str]]: result = await self._send_klippy_request( OBJ_LIST_ENDPOINT, {}, default) @@ -192,7 +191,7 @@ class KlippyAPI(Subscribable): async def query_objects(self, objects: Mapping[str, Optional[List[str]]], - default: Union[SentinelClass, _T] = SENTINEL + default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[_T, Dict[str, Any]]: params = {'objects': objects} result = await self._send_klippy_request( @@ -203,7 +202,7 @@ class KlippyAPI(Subscribable): async def subscribe_objects(self, objects: Mapping[str, Optional[List[str]]], - default: Union[SentinelClass, _T] = SENTINEL + default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[_T, Dict[str, Any]]: for obj, items in objects.items(): if obj in self.host_subscription: diff --git a/moonraker/confighelper.py b/moonraker/confighelper.py index c83d274..c0ca373 100644 --- a/moonraker/confighelper.py +++ b/moonraker/confighelper.py @@ -14,7 +14,7 @@ import threading import copy import logging from io import StringIO -from .utils import SentinelClass +from .utils import Sentinel from .components.template import JinjaTemplate # Annotation imports @@ -41,7 +41,6 @@ if TYPE_CHECKING: _T = TypeVar("_T") ConfigVal = Union[None, int, float, bool, str, dict, list] -SENTINEL = SentinelClass.get_instance() DOCS_URL = "https://moonraker.readthedocs.io/en/latest" class ConfigError(Exception): @@ -120,7 +119,7 @@ class ConfigHelper: def _get_option(self, func: Callable[..., Any], option: str, - default: Union[SentinelClass, _T], + default: Union[Sentinel, _T], above: Optional[Union[int, float]] = None, below: Optional[Union[int, float]] = None, minval: Optional[Union[int, float]] = None, @@ -138,7 +137,7 @@ class ConfigHelper: try: val = func(section, option) except (configparser.NoOptionError, configparser.NoSectionError) as e: - if isinstance(default, SentinelClass): + if default is Sentinel.MISSING: raise ConfigError(str(e)) from None val = default section = self.section @@ -198,7 +197,7 @@ class ConfigHelper: def get(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, deprecate: bool = False ) -> Union[str, _T]: return self._get_option( @@ -207,7 +206,7 @@ class ConfigHelper: def getint(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, above: Optional[int] = None, below: Optional[int] = None, minval: Optional[int] = None, @@ -220,7 +219,7 @@ class ConfigHelper: def getboolean(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, deprecate: bool = False ) -> Union[bool, _T]: return self._get_option( @@ -229,7 +228,7 @@ class ConfigHelper: def getfloat(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, above: Optional[float] = None, below: Optional[float] = None, minval: Optional[float] = None, @@ -242,7 +241,7 @@ class ConfigHelper: def getlists(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, list_type: Type = str, separators: Tuple[Optional[str], ...] = ('\n',), count: Optional[Tuple[Optional[int], ...]] = None, @@ -292,7 +291,7 @@ class ConfigHelper: def getlist(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, separator: Optional[str] = '\n', count: Optional[int] = None, deprecate: bool = False @@ -302,7 +301,7 @@ class ConfigHelper: def getintlist(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, separator: Optional[str] = '\n', count: Optional[int] = None, deprecate: bool = False @@ -312,7 +311,7 @@ class ConfigHelper: def getfloatlist(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, separator: Optional[str] = '\n', count: Optional[int] = None, deprecate: bool = False @@ -322,7 +321,7 @@ class ConfigHelper: def getdict(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, separators: Tuple[Optional[str], Optional[str]] = ('\n', '='), dict_type: Type = str, allow_empty_fields: bool = False, @@ -356,7 +355,7 @@ class ConfigHelper: def getgpioout(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, initial_value: int = 0, deprecate: bool = False ) -> Union[GpioOutputPin, _T]: @@ -375,7 +374,7 @@ class ConfigHelper: def gettemplate(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, is_async: bool = False, deprecate: bool = False ) -> Union[JinjaTemplate, _T]: @@ -396,7 +395,7 @@ class ConfigHelper: def load_template(self, option: str, - default: Union[SentinelClass, str] = SENTINEL, + default: Union[Sentinel, str] = Sentinel.MISSING, is_async: bool = False, deprecate: bool = False ) -> JinjaTemplate: @@ -409,7 +408,7 @@ class ConfigHelper: def getpath(self, option: str, - default: Union[SentinelClass, _T] = SENTINEL, + default: Union[Sentinel, _T] = Sentinel.MISSING, deprecate: bool = False ) -> Union[pathlib.Path, _T]: val = self.gettemplate(option, default, deprecate=deprecate) diff --git a/moonraker/server.py b/moonraker/server.py index f8fa7dd..05c77c7 100755 --- a/moonraker/server.py +++ b/moonraker/server.py @@ -21,7 +21,7 @@ from . import confighelper from .eventloop import EventLoop from .app import MoonrakerApp from .klippy_connection import KlippyConnection -from .utils import ServerError, SentinelClass, get_software_version +from .utils import ServerError, Sentinel, get_software_version from .loghelper import LogManager # Annotation imports @@ -43,7 +43,7 @@ if TYPE_CHECKING: from .components.machine import Machine from .components.extensions import ExtensionManager FlexCallback = Callable[..., Optional[Coroutine]] - _T = TypeVar("_T") + _T = TypeVar("_T", Sentinel, Any) API_VERSION = (1, 2, 1) CORE_COMPONENTS = [ @@ -53,7 +53,6 @@ CORE_COMPONENTS = [ 'webcam', 'extensions', ] -SENTINEL = SentinelClass.get_instance() class Server: error = ServerError @@ -245,11 +244,12 @@ class Server: config.validate_config() self._is_configured = True - def load_component(self, - config: confighelper.ConfigHelper, - component_name: str, - default: Union[SentinelClass, _T] = SENTINEL - ) -> Union[_T, Any]: + def load_component( + self, + config: confighelper.ConfigHelper, + component_name: str, + default: _T = Sentinel.MISSING + ) -> Union[_T, Any]: if component_name in self.components: return self.components[component_name] try: @@ -265,19 +265,18 @@ class Server: logging.exception(msg) if component_name not in self.failed_components: self.failed_components.append(component_name) - if isinstance(default, SentinelClass): + if default is Sentinel.MISSING: raise return default self.components[component_name] = component logging.info(f"Component ({component_name}) loaded") return component - def lookup_component(self, - component_name: str, - default: Union[SentinelClass, _T] = SENTINEL - ) -> Union[_T, Any]: + def lookup_component( + self, component_name: str, default: _T = Sentinel.MISSING + ) -> Union[_T, Any]: component = self.components.get(component_name, default) - if isinstance(component, SentinelClass): + if component is Sentinel.MISSING: raise ServerError(f"Component ({component_name}) not found") return component diff --git a/moonraker/utils/__init__.py b/moonraker/utils/__init__.py index 8c7796b..cdf17fe 100644 --- a/moonraker/utils/__init__.py +++ b/moonraker/utils/__init__.py @@ -19,6 +19,7 @@ import shlex import re import struct import socket +import enum from . import source_info # Annotation imports @@ -45,14 +46,8 @@ class ServerError(Exception): self.status_code = status_code -class SentinelClass: - _instance: ClassVar[Optional[SentinelClass]] = None - - @staticmethod - def get_instance() -> SentinelClass: - if SentinelClass._instance is None: - SentinelClass._instance = SentinelClass() - return SentinelClass._instance +class Sentinel(enum.Enum): + MISSING = object() def _run_git_command(cmd: str) -> str: prog = shlex.split(cmd) diff --git a/moonraker/websockets.py b/moonraker/websockets.py index fa055d7..9c8053c 100644 --- a/moonraker/websockets.py +++ b/moonraker/websockets.py @@ -12,7 +12,7 @@ import asyncio import copy from tornado.websocket import WebSocketHandler, WebSocketClosedError from tornado.web import HTTPError -from .utils import ServerError, SentinelClass +from .utils import ServerError, Sentinel # Annotation imports from typing import ( @@ -44,7 +44,6 @@ if TYPE_CHECKING: AuthComp = Optional[Authorization] CLIENT_TYPES = ["web", "mobile", "desktop", "display", "bot", "agent", "other"] -SENTINEL = SentinelClass.get_instance() class Subscribable: def send_status(self, @@ -98,11 +97,11 @@ class WebRequest: def _get_converted_arg(self, key: str, - default: Union[SentinelClass, _T], + default: Union[Sentinel, _T], dtype: Type[_C] ) -> Union[_C, _T]: if key not in self.args: - if isinstance(default, SentinelClass): + if default is Sentinel.MISSING: raise ServerError(f"No data for argument: {key}") return default val = self.args[key] @@ -124,34 +123,34 @@ class WebRequest: def get(self, key: str, - default: Union[SentinelClass, _T] = SENTINEL + default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[_T, Any]: val = self.args.get(key, default) - if isinstance(val, SentinelClass): + if val is Sentinel.MISSING: raise ServerError(f"No data for argument: {key}") return val def get_str(self, key: str, - default: Union[SentinelClass, _T] = SENTINEL + default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[str, _T]: return self._get_converted_arg(key, default, str) def get_int(self, key: str, - default: Union[SentinelClass, _T] = SENTINEL + default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[int, _T]: return self._get_converted_arg(key, default, int) def get_float(self, key: str, - default: Union[SentinelClass, _T] = SENTINEL + default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[float, _T]: return self._get_converted_arg(key, default, float) def get_boolean(self, key: str, - default: Union[SentinelClass, _T] = SENTINEL + default: Union[Sentinel, _T] = Sentinel.MISSING ) -> Union[bool, _T]: return self._get_converted_arg(key, default, bool) @@ -245,8 +244,8 @@ class JsonRPC: rpc_version: str = obj.get('jsonrpc', "") if rpc_version != "2.0": return self.build_error(-32600, "Invalid Request", req_id) - method_name = obj.get('method', SENTINEL) - if method_name is SENTINEL: + method_name = obj.get('method', Sentinel.MISSING) + if method_name is Sentinel.MISSING: self.process_response(obj, conn) return None if not isinstance(method_name, str):