utils: simplify sentinel object

Use an enum to represent the sentinel rather than a singleton object.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2023-02-22 08:19:39 -05:00
parent 0e80e301f0
commit 2cda75ff2c
No known key found for this signature in database
GPG Key ID: 5A1EB336DFB4C71B
6 changed files with 63 additions and 74 deletions

View File

@ -15,7 +15,7 @@ from io import BytesIO
from functools import reduce from functools import reduce
from threading import Lock as ThreadLock from threading import Lock as ThreadLock
import lmdb import lmdb
from ..utils import SentinelClass, ServerError from ..utils import Sentinel, ServerError
# Annotation imports # Annotation imports
from typing import ( from typing import (
@ -61,8 +61,6 @@ RECORD_DECODE_FUNCS = {
ord("{"): lambda x: json.loads(bytes(x)), ord("{"): lambda x: json.loads(bytes(x)),
} }
SENTINEL = SentinelClass.get_instance()
def getitem_with_default(item: Dict, field: Any) -> Any: def getitem_with_default(item: Dict, field: Any) -> Any:
if not isinstance(item, Dict): if not isinstance(item, Dict):
raise ServerError( raise ServerError(
@ -346,14 +344,14 @@ class MoonrakerDatabase:
def get_item(self, def get_item(self,
namespace: str, namespace: str,
key: Optional[Union[List[str], str]] = None, key: Optional[Union[List[str], str]] = None,
default: Any = SENTINEL default: Any = Sentinel.MISSING
) -> Future[Any]: ) -> Future[Any]:
return self._run_command(self._get_impl, namespace, key, default) return self._run_command(self._get_impl, namespace, key, default)
def _get_impl(self, def _get_impl(self,
namespace: str, namespace: str,
key: Optional[Union[List[str], str]] = None, key: Optional[Union[List[str], str]] = None,
default: Any = SENTINEL default: Any = Sentinel.MISSING
) -> Any: ) -> Any:
try: try:
if key is None: if key is None:
@ -363,7 +361,7 @@ class MoonrakerDatabase:
val = reduce(operator.getitem, # type: ignore val = reduce(operator.getitem, # type: ignore
key_list[1:], ns) key_list[1:], ns)
except Exception as e: except Exception as e:
if not isinstance(default, SentinelClass): if default is not Sentinel.MISSING:
return default return default
if isinstance(e, self.server.error): if isinstance(e, self.server.error):
raise raise
@ -875,7 +873,7 @@ class NamespaceWrapper:
return self.db._get_namespace(self.namespace) return self.db._get_namespace(self.namespace)
def __getitem__(self, key: Union[List[str], str]) -> Future[Any]: 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, def __setitem__(self,
key: Union[List[str], str], key: Union[List[str], str],
@ -908,13 +906,13 @@ class NamespaceWrapper:
def pop(self, def pop(self,
key: Union[List[str], str], key: Union[List[str], str],
default: Any = SENTINEL default: Any = Sentinel.MISSING
) -> Union[Future[Any], Task[Any]]: ) -> Union[Future[Any], Task[Any]]:
if not self.server.is_running(): if not self.server.is_running():
try: try:
val = self.delete(key).result() val = self.delete(key).result()
except Exception: except Exception:
if isinstance(default, SentinelClass): if default is Sentinel.MISSING:
raise raise
val = default val = default
fut = self.eventloop.create_future() fut = self.eventloop.create_future()
@ -925,7 +923,7 @@ class NamespaceWrapper:
try: try:
val = await self.delete(key) val = await self.delete(key)
except Exception: except Exception:
if isinstance(default, SentinelClass): if default is Sentinel.MISSING:
raise raise
val = default val = default
return val return val

View File

@ -5,7 +5,7 @@
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
from __future__ import annotations from __future__ import annotations
from ..utils import SentinelClass from ..utils import Sentinel
from ..websockets import WebRequest, Subscribable from ..websockets import WebRequest, Subscribable
# Annotation imports # Annotation imports
@ -35,7 +35,6 @@ SUBSCRIPTION_ENDPOINT = "objects/subscribe"
STATUS_ENDPOINT = "objects/query" STATUS_ENDPOINT = "objects/query"
OBJ_LIST_ENDPOINT = "objects/list" OBJ_LIST_ENDPOINT = "objects/list"
REG_METHOD_ENDPOINT = "register_remote_method" REG_METHOD_ENDPOINT = "register_remote_method"
SENTINEL = SentinelClass.get_instance()
class KlippyAPI(Subscribable): class KlippyAPI(Subscribable):
def __init__(self, config: ConfigHelper) -> None: def __init__(self, config: ConfigHelper) -> None:
@ -84,20 +83,20 @@ class KlippyAPI(Subscribable):
self, self,
method: str, method: str,
params: Dict[str, Any], params: Dict[str, Any],
default: Any = SENTINEL default: Any = Sentinel.MISSING
) -> Any: ) -> Any:
try: try:
req = WebRequest(method, params, conn=self) req = WebRequest(method, params, conn=self)
result = await self.klippy.request(req) result = await self.klippy.request(req)
except self.server.error: except self.server.error:
if isinstance(default, SentinelClass): if default is Sentinel.MISSING:
raise raise
result = default result = default
return result return result
async def run_gcode(self, async def run_gcode(self,
script: str, script: str,
default: Any = SENTINEL default: Any = Sentinel.MISSING
) -> str: ) -> str:
params = {'script': script} params = {'script': script}
result = await self._send_klippy_request( result = await self._send_klippy_request(
@ -123,21 +122,21 @@ class KlippyAPI(Subscribable):
return await self.run_gcode(script) return await self.run_gcode(script)
async def pause_print( async def pause_print(
self, default: Union[SentinelClass, _T] = SENTINEL self, default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[_T, str]: ) -> Union[_T, str]:
self.server.send_event("klippy_apis:pause_requested") self.server.send_event("klippy_apis:pause_requested")
return await self._send_klippy_request( return await self._send_klippy_request(
"pause_resume/pause", {}, default) "pause_resume/pause", {}, default)
async def resume_print( async def resume_print(
self, default: Union[SentinelClass, _T] = SENTINEL self, default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[_T, str]: ) -> Union[_T, str]:
self.server.send_event("klippy_apis:resume_requested") self.server.send_event("klippy_apis:resume_requested")
return await self._send_klippy_request( return await self._send_klippy_request(
"pause_resume/resume", {}, default) "pause_resume/resume", {}, default)
async def cancel_print( async def cancel_print(
self, default: Union[SentinelClass, _T] = SENTINEL self, default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[_T, str]: ) -> Union[_T, str]:
self.server.send_event("klippy_apis:cancel_requested") self.server.send_event("klippy_apis:cancel_requested")
return await self._send_klippy_request( return await self._send_klippy_request(
@ -163,7 +162,7 @@ class KlippyAPI(Subscribable):
return result return result
async def list_endpoints(self, async def list_endpoints(self,
default: Union[SentinelClass, _T] = SENTINEL default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[_T, Dict[str, List[str]]]: ) -> Union[_T, Dict[str, List[str]]]:
return await self._send_klippy_request( return await self._send_klippy_request(
LIST_EPS_ENDPOINT, {}, default) LIST_EPS_ENDPOINT, {}, default)
@ -173,7 +172,7 @@ class KlippyAPI(Subscribable):
async def get_klippy_info(self, async def get_klippy_info(self,
send_id: bool = False, send_id: bool = False,
default: Union[SentinelClass, _T] = SENTINEL default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[_T, Dict[str, Any]]: ) -> Union[_T, Dict[str, Any]]:
params = {} params = {}
if send_id: if send_id:
@ -182,7 +181,7 @@ class KlippyAPI(Subscribable):
return await self._send_klippy_request(INFO_ENDPOINT, params, default) return await self._send_klippy_request(INFO_ENDPOINT, params, default)
async def get_object_list(self, async def get_object_list(self,
default: Union[SentinelClass, _T] = SENTINEL default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[_T, List[str]]: ) -> Union[_T, List[str]]:
result = await self._send_klippy_request( result = await self._send_klippy_request(
OBJ_LIST_ENDPOINT, {}, default) OBJ_LIST_ENDPOINT, {}, default)
@ -192,7 +191,7 @@ class KlippyAPI(Subscribable):
async def query_objects(self, async def query_objects(self,
objects: Mapping[str, Optional[List[str]]], objects: Mapping[str, Optional[List[str]]],
default: Union[SentinelClass, _T] = SENTINEL default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[_T, Dict[str, Any]]: ) -> Union[_T, Dict[str, Any]]:
params = {'objects': objects} params = {'objects': objects}
result = await self._send_klippy_request( result = await self._send_klippy_request(
@ -203,7 +202,7 @@ class KlippyAPI(Subscribable):
async def subscribe_objects(self, async def subscribe_objects(self,
objects: Mapping[str, Optional[List[str]]], objects: Mapping[str, Optional[List[str]]],
default: Union[SentinelClass, _T] = SENTINEL default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[_T, Dict[str, Any]]: ) -> Union[_T, Dict[str, Any]]:
for obj, items in objects.items(): for obj, items in objects.items():
if obj in self.host_subscription: if obj in self.host_subscription:

View File

@ -14,7 +14,7 @@ import threading
import copy import copy
import logging import logging
from io import StringIO from io import StringIO
from .utils import SentinelClass from .utils import Sentinel
from .components.template import JinjaTemplate from .components.template import JinjaTemplate
# Annotation imports # Annotation imports
@ -41,7 +41,6 @@ if TYPE_CHECKING:
_T = TypeVar("_T") _T = TypeVar("_T")
ConfigVal = Union[None, int, float, bool, str, dict, list] ConfigVal = Union[None, int, float, bool, str, dict, list]
SENTINEL = SentinelClass.get_instance()
DOCS_URL = "https://moonraker.readthedocs.io/en/latest" DOCS_URL = "https://moonraker.readthedocs.io/en/latest"
class ConfigError(Exception): class ConfigError(Exception):
@ -120,7 +119,7 @@ class ConfigHelper:
def _get_option(self, def _get_option(self,
func: Callable[..., Any], func: Callable[..., Any],
option: str, option: str,
default: Union[SentinelClass, _T], default: Union[Sentinel, _T],
above: Optional[Union[int, float]] = None, above: Optional[Union[int, float]] = None,
below: Optional[Union[int, float]] = None, below: Optional[Union[int, float]] = None,
minval: Optional[Union[int, float]] = None, minval: Optional[Union[int, float]] = None,
@ -138,7 +137,7 @@ class ConfigHelper:
try: try:
val = func(section, option) val = func(section, option)
except (configparser.NoOptionError, configparser.NoSectionError) as e: except (configparser.NoOptionError, configparser.NoSectionError) as e:
if isinstance(default, SentinelClass): if default is Sentinel.MISSING:
raise ConfigError(str(e)) from None raise ConfigError(str(e)) from None
val = default val = default
section = self.section section = self.section
@ -198,7 +197,7 @@ class ConfigHelper:
def get(self, def get(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
deprecate: bool = False deprecate: bool = False
) -> Union[str, _T]: ) -> Union[str, _T]:
return self._get_option( return self._get_option(
@ -207,7 +206,7 @@ class ConfigHelper:
def getint(self, def getint(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
above: Optional[int] = None, above: Optional[int] = None,
below: Optional[int] = None, below: Optional[int] = None,
minval: Optional[int] = None, minval: Optional[int] = None,
@ -220,7 +219,7 @@ class ConfigHelper:
def getboolean(self, def getboolean(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
deprecate: bool = False deprecate: bool = False
) -> Union[bool, _T]: ) -> Union[bool, _T]:
return self._get_option( return self._get_option(
@ -229,7 +228,7 @@ class ConfigHelper:
def getfloat(self, def getfloat(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
above: Optional[float] = None, above: Optional[float] = None,
below: Optional[float] = None, below: Optional[float] = None,
minval: Optional[float] = None, minval: Optional[float] = None,
@ -242,7 +241,7 @@ class ConfigHelper:
def getlists(self, def getlists(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
list_type: Type = str, list_type: Type = str,
separators: Tuple[Optional[str], ...] = ('\n',), separators: Tuple[Optional[str], ...] = ('\n',),
count: Optional[Tuple[Optional[int], ...]] = None, count: Optional[Tuple[Optional[int], ...]] = None,
@ -292,7 +291,7 @@ class ConfigHelper:
def getlist(self, def getlist(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
separator: Optional[str] = '\n', separator: Optional[str] = '\n',
count: Optional[int] = None, count: Optional[int] = None,
deprecate: bool = False deprecate: bool = False
@ -302,7 +301,7 @@ class ConfigHelper:
def getintlist(self, def getintlist(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
separator: Optional[str] = '\n', separator: Optional[str] = '\n',
count: Optional[int] = None, count: Optional[int] = None,
deprecate: bool = False deprecate: bool = False
@ -312,7 +311,7 @@ class ConfigHelper:
def getfloatlist(self, def getfloatlist(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
separator: Optional[str] = '\n', separator: Optional[str] = '\n',
count: Optional[int] = None, count: Optional[int] = None,
deprecate: bool = False deprecate: bool = False
@ -322,7 +321,7 @@ class ConfigHelper:
def getdict(self, def getdict(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
separators: Tuple[Optional[str], Optional[str]] = ('\n', '='), separators: Tuple[Optional[str], Optional[str]] = ('\n', '='),
dict_type: Type = str, dict_type: Type = str,
allow_empty_fields: bool = False, allow_empty_fields: bool = False,
@ -356,7 +355,7 @@ class ConfigHelper:
def getgpioout(self, def getgpioout(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
initial_value: int = 0, initial_value: int = 0,
deprecate: bool = False deprecate: bool = False
) -> Union[GpioOutputPin, _T]: ) -> Union[GpioOutputPin, _T]:
@ -375,7 +374,7 @@ class ConfigHelper:
def gettemplate(self, def gettemplate(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
is_async: bool = False, is_async: bool = False,
deprecate: bool = False deprecate: bool = False
) -> Union[JinjaTemplate, _T]: ) -> Union[JinjaTemplate, _T]:
@ -396,7 +395,7 @@ class ConfigHelper:
def load_template(self, def load_template(self,
option: str, option: str,
default: Union[SentinelClass, str] = SENTINEL, default: Union[Sentinel, str] = Sentinel.MISSING,
is_async: bool = False, is_async: bool = False,
deprecate: bool = False deprecate: bool = False
) -> JinjaTemplate: ) -> JinjaTemplate:
@ -409,7 +408,7 @@ class ConfigHelper:
def getpath(self, def getpath(self,
option: str, option: str,
default: Union[SentinelClass, _T] = SENTINEL, default: Union[Sentinel, _T] = Sentinel.MISSING,
deprecate: bool = False deprecate: bool = False
) -> Union[pathlib.Path, _T]: ) -> Union[pathlib.Path, _T]:
val = self.gettemplate(option, default, deprecate=deprecate) val = self.gettemplate(option, default, deprecate=deprecate)

View File

@ -21,7 +21,7 @@ from . import confighelper
from .eventloop import EventLoop from .eventloop import EventLoop
from .app import MoonrakerApp from .app import MoonrakerApp
from .klippy_connection import KlippyConnection 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 from .loghelper import LogManager
# Annotation imports # Annotation imports
@ -43,7 +43,7 @@ if TYPE_CHECKING:
from .components.machine import Machine from .components.machine import Machine
from .components.extensions import ExtensionManager from .components.extensions import ExtensionManager
FlexCallback = Callable[..., Optional[Coroutine]] FlexCallback = Callable[..., Optional[Coroutine]]
_T = TypeVar("_T") _T = TypeVar("_T", Sentinel, Any)
API_VERSION = (1, 2, 1) API_VERSION = (1, 2, 1)
CORE_COMPONENTS = [ CORE_COMPONENTS = [
@ -53,7 +53,6 @@ CORE_COMPONENTS = [
'webcam', 'extensions', 'webcam', 'extensions',
] ]
SENTINEL = SentinelClass.get_instance()
class Server: class Server:
error = ServerError error = ServerError
@ -245,10 +244,11 @@ class Server:
config.validate_config() config.validate_config()
self._is_configured = True self._is_configured = True
def load_component(self, def load_component(
self,
config: confighelper.ConfigHelper, config: confighelper.ConfigHelper,
component_name: str, component_name: str,
default: Union[SentinelClass, _T] = SENTINEL default: _T = Sentinel.MISSING
) -> Union[_T, Any]: ) -> Union[_T, Any]:
if component_name in self.components: if component_name in self.components:
return self.components[component_name] return self.components[component_name]
@ -265,19 +265,18 @@ class Server:
logging.exception(msg) logging.exception(msg)
if component_name not in self.failed_components: if component_name not in self.failed_components:
self.failed_components.append(component_name) self.failed_components.append(component_name)
if isinstance(default, SentinelClass): if default is Sentinel.MISSING:
raise raise
return default return default
self.components[component_name] = component self.components[component_name] = component
logging.info(f"Component ({component_name}) loaded") logging.info(f"Component ({component_name}) loaded")
return component return component
def lookup_component(self, def lookup_component(
component_name: str, self, component_name: str, default: _T = Sentinel.MISSING
default: Union[SentinelClass, _T] = SENTINEL
) -> Union[_T, Any]: ) -> Union[_T, Any]:
component = self.components.get(component_name, default) component = self.components.get(component_name, default)
if isinstance(component, SentinelClass): if component is Sentinel.MISSING:
raise ServerError(f"Component ({component_name}) not found") raise ServerError(f"Component ({component_name}) not found")
return component return component

View File

@ -19,6 +19,7 @@ import shlex
import re import re
import struct import struct
import socket import socket
import enum
from . import source_info from . import source_info
# Annotation imports # Annotation imports
@ -45,14 +46,8 @@ class ServerError(Exception):
self.status_code = status_code self.status_code = status_code
class SentinelClass: class Sentinel(enum.Enum):
_instance: ClassVar[Optional[SentinelClass]] = None MISSING = object()
@staticmethod
def get_instance() -> SentinelClass:
if SentinelClass._instance is None:
SentinelClass._instance = SentinelClass()
return SentinelClass._instance
def _run_git_command(cmd: str) -> str: def _run_git_command(cmd: str) -> str:
prog = shlex.split(cmd) prog = shlex.split(cmd)

View File

@ -12,7 +12,7 @@ import asyncio
import copy import copy
from tornado.websocket import WebSocketHandler, WebSocketClosedError from tornado.websocket import WebSocketHandler, WebSocketClosedError
from tornado.web import HTTPError from tornado.web import HTTPError
from .utils import ServerError, SentinelClass from .utils import ServerError, Sentinel
# Annotation imports # Annotation imports
from typing import ( from typing import (
@ -44,7 +44,6 @@ if TYPE_CHECKING:
AuthComp = Optional[Authorization] AuthComp = Optional[Authorization]
CLIENT_TYPES = ["web", "mobile", "desktop", "display", "bot", "agent", "other"] CLIENT_TYPES = ["web", "mobile", "desktop", "display", "bot", "agent", "other"]
SENTINEL = SentinelClass.get_instance()
class Subscribable: class Subscribable:
def send_status(self, def send_status(self,
@ -98,11 +97,11 @@ class WebRequest:
def _get_converted_arg(self, def _get_converted_arg(self,
key: str, key: str,
default: Union[SentinelClass, _T], default: Union[Sentinel, _T],
dtype: Type[_C] dtype: Type[_C]
) -> Union[_C, _T]: ) -> Union[_C, _T]:
if key not in self.args: if key not in self.args:
if isinstance(default, SentinelClass): if default is Sentinel.MISSING:
raise ServerError(f"No data for argument: {key}") raise ServerError(f"No data for argument: {key}")
return default return default
val = self.args[key] val = self.args[key]
@ -124,34 +123,34 @@ class WebRequest:
def get(self, def get(self,
key: str, key: str,
default: Union[SentinelClass, _T] = SENTINEL default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[_T, Any]: ) -> Union[_T, Any]:
val = self.args.get(key, default) val = self.args.get(key, default)
if isinstance(val, SentinelClass): if val is Sentinel.MISSING:
raise ServerError(f"No data for argument: {key}") raise ServerError(f"No data for argument: {key}")
return val return val
def get_str(self, def get_str(self,
key: str, key: str,
default: Union[SentinelClass, _T] = SENTINEL default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[str, _T]: ) -> Union[str, _T]:
return self._get_converted_arg(key, default, str) return self._get_converted_arg(key, default, str)
def get_int(self, def get_int(self,
key: str, key: str,
default: Union[SentinelClass, _T] = SENTINEL default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[int, _T]: ) -> Union[int, _T]:
return self._get_converted_arg(key, default, int) return self._get_converted_arg(key, default, int)
def get_float(self, def get_float(self,
key: str, key: str,
default: Union[SentinelClass, _T] = SENTINEL default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[float, _T]: ) -> Union[float, _T]:
return self._get_converted_arg(key, default, float) return self._get_converted_arg(key, default, float)
def get_boolean(self, def get_boolean(self,
key: str, key: str,
default: Union[SentinelClass, _T] = SENTINEL default: Union[Sentinel, _T] = Sentinel.MISSING
) -> Union[bool, _T]: ) -> Union[bool, _T]:
return self._get_converted_arg(key, default, bool) return self._get_converted_arg(key, default, bool)
@ -245,8 +244,8 @@ class JsonRPC:
rpc_version: str = obj.get('jsonrpc', "") rpc_version: str = obj.get('jsonrpc', "")
if rpc_version != "2.0": if rpc_version != "2.0":
return self.build_error(-32600, "Invalid Request", req_id) return self.build_error(-32600, "Invalid Request", req_id)
method_name = obj.get('method', SENTINEL) method_name = obj.get('method', Sentinel.MISSING)
if method_name is SENTINEL: if method_name is Sentinel.MISSING:
self.process_response(obj, conn) self.process_response(obj, conn)
return None return None
if not isinstance(method_name, str): if not isinstance(method_name, str):