moonraker: add annotations
Annotate function definitions, class attributes, and local variables as necessary for type hinting. This is useful for IDEs with linters that support type hints and also can be used by GitHub Actions to detect erroneous code. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
9c76dbef7a
commit
ca69a10838
|
@ -4,6 +4,8 @@
|
||||||
# Copyright (C) 2020 Eric Callahan <arksine.code@gmail.com>
|
# Copyright (C) 2020 Eric Callahan <arksine.code@gmail.com>
|
||||||
#
|
#
|
||||||
# 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
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import importlib
|
import importlib
|
||||||
|
@ -22,7 +24,28 @@ from tornado.ioloop import IOLoop
|
||||||
from tornado.util import TimeoutError
|
from tornado.util import TimeoutError
|
||||||
from tornado.locks import Event
|
from tornado.locks import Event
|
||||||
from app import MoonrakerApp
|
from app import MoonrakerApp
|
||||||
from utils import ServerError
|
from utils import ServerError, SentinelClass
|
||||||
|
|
||||||
|
# Annotation imports
|
||||||
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Optional,
|
||||||
|
Callable,
|
||||||
|
Coroutine,
|
||||||
|
Tuple,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Union,
|
||||||
|
TypeVar,
|
||||||
|
)
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from websockets import WebRequest, WebSocket, Subscribable
|
||||||
|
from components.data_store import DataStore
|
||||||
|
from components.klippy_apis import KlippyAPI
|
||||||
|
from components.file_manager import FileManager
|
||||||
|
FlexCallback = Callable[..., Optional[Coroutine]]
|
||||||
|
_T = TypeVar("_T")
|
||||||
|
|
||||||
INIT_TIME = .25
|
INIT_TIME = .25
|
||||||
LOG_ATTEMPT_INTERVAL = int(2. / INIT_TIME + .5)
|
LOG_ATTEMPT_INTERVAL = int(2. / INIT_TIME + .5)
|
||||||
|
@ -32,12 +55,14 @@ CORE_COMPONENTS = [
|
||||||
'database', 'file_manager', 'klippy_apis', 'machine',
|
'database', 'file_manager', 'klippy_apis', 'machine',
|
||||||
'data_store', 'shell_command', 'proc_stats']
|
'data_store', 'shell_command', 'proc_stats']
|
||||||
|
|
||||||
class Sentinel:
|
SENTINEL = SentinelClass.get_instance()
|
||||||
pass
|
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
error = ServerError
|
error = ServerError
|
||||||
def __init__(self, args, file_logger):
|
def __init__(self,
|
||||||
|
args: argparse.Namespace,
|
||||||
|
file_logger: Optional[utils.MoonrakerLoggingHandler]
|
||||||
|
) -> None:
|
||||||
self.file_logger = file_logger
|
self.file_logger = file_logger
|
||||||
self.config = config = confighelper.get_configuration(self, args)
|
self.config = config = confighelper.get_configuration(self, args)
|
||||||
# log config file
|
# log config file
|
||||||
|
@ -48,29 +73,29 @@ class Server:
|
||||||
cfg_item += "#"*65
|
cfg_item += "#"*65
|
||||||
strio.close()
|
strio.close()
|
||||||
self.add_log_rollover_item('config', cfg_item)
|
self.add_log_rollover_item('config', cfg_item)
|
||||||
self.host = config.get('host', "0.0.0.0")
|
self.host: str = config.get('host', "0.0.0.0")
|
||||||
self.port = config.getint('port', 7125)
|
self.port: int = config.getint('port', 7125)
|
||||||
self.exit_reason = ""
|
self.exit_reason: str = ""
|
||||||
|
|
||||||
# Event initialization
|
# Event initialization
|
||||||
self.events = {}
|
self.events: Dict[str, List[FlexCallback]] = {}
|
||||||
|
|
||||||
# Klippy Connection Handling
|
# Klippy Connection Handling
|
||||||
self.klippy_address = config.get(
|
self.klippy_address: str = config.get(
|
||||||
'klippy_uds_address', "/tmp/klippy_uds")
|
'klippy_uds_address', "/tmp/klippy_uds")
|
||||||
self.klippy_connection = KlippyConnection(
|
self.klippy_connection = KlippyConnection(
|
||||||
self.process_command, self.on_connection_closed)
|
self.process_command, self.on_connection_closed)
|
||||||
self.klippy_info = {}
|
self.klippy_info: Dict[str, Any] = {}
|
||||||
self.init_list = []
|
self.init_list: List[str] = []
|
||||||
self.init_handle = None
|
self.init_handle: Optional[object] = None
|
||||||
self.init_attempts = 0
|
self.init_attempts: int = 0
|
||||||
self.klippy_state = "disconnected"
|
self.klippy_state: str = "disconnected"
|
||||||
self.klippy_disconnect_evt = None
|
self.klippy_disconnect_evt: Optional[Event] = None
|
||||||
self.subscriptions = {}
|
self.subscriptions: Dict[Subscribable, Dict[str, Any]] = {}
|
||||||
self.failed_components = []
|
self.failed_components: List[str] = []
|
||||||
|
|
||||||
# Server/IOLoop
|
# Server/IOLoop
|
||||||
self.server_running = False
|
self.server_running: bool = False
|
||||||
self.moonraker_app = app = MoonrakerApp(config)
|
self.moonraker_app = app = MoonrakerApp(config)
|
||||||
self.register_endpoint = app.register_local_handler
|
self.register_endpoint = app.register_local_handler
|
||||||
self.register_static_file_handler = app.register_static_file_handler
|
self.register_static_file_handler = app.register_static_file_handler
|
||||||
|
@ -93,9 +118,9 @@ class Server:
|
||||||
# Setup remote methods accessable to Klippy. Note that all
|
# Setup remote methods accessable to Klippy. Note that all
|
||||||
# registered remote methods should be of the notification type,
|
# registered remote methods should be of the notification type,
|
||||||
# they do not return a response to Klippy after execution
|
# they do not return a response to Klippy after execution
|
||||||
self.pending_requests = {}
|
self.pending_requests: Dict[int, BaseRequest] = {}
|
||||||
self.remote_methods = {}
|
self.remote_methods: Dict[str, FlexCallback] = {}
|
||||||
self.klippy_reg_methods = []
|
self.klippy_reg_methods: List[str] = []
|
||||||
self.register_remote_method(
|
self.register_remote_method(
|
||||||
'process_gcode_response', self._process_gcode_response,
|
'process_gcode_response', self._process_gcode_response,
|
||||||
need_klippy_reg=False)
|
need_klippy_reg=False)
|
||||||
|
@ -104,12 +129,12 @@ class Server:
|
||||||
need_klippy_reg=False)
|
need_klippy_reg=False)
|
||||||
|
|
||||||
# Component initialization
|
# Component initialization
|
||||||
self.components = {}
|
self.components: Dict[str, Any] = {}
|
||||||
self._load_components(config)
|
self._load_components(config)
|
||||||
self.klippy_apis = self.lookup_component('klippy_apis')
|
self.klippy_apis: KlippyAPI = self.lookup_component('klippy_apis')
|
||||||
config.validate_config()
|
config.validate_config()
|
||||||
|
|
||||||
def start(self):
|
def start(self) -> None:
|
||||||
hostname, hostport = self.get_host_info()
|
hostname, hostport = self.get_host_info()
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Starting Moonraker on ({self.host}, {hostport}), "
|
f"Starting Moonraker on ({self.host}, {hostport}), "
|
||||||
|
@ -119,19 +144,20 @@ class Server:
|
||||||
self.ioloop.spawn_callback(self._init_signals)
|
self.ioloop.spawn_callback(self._init_signals)
|
||||||
self.ioloop.spawn_callback(self._connect_klippy)
|
self.ioloop.spawn_callback(self._connect_klippy)
|
||||||
|
|
||||||
def _init_signals(self):
|
def _init_signals(self) -> None:
|
||||||
aioloop = asyncio.get_event_loop()
|
aioloop = asyncio.get_event_loop()
|
||||||
aioloop.add_signal_handler(
|
aioloop.add_signal_handler(
|
||||||
signal.SIGTERM, self._handle_term_signal)
|
signal.SIGTERM, self._handle_term_signal)
|
||||||
|
|
||||||
def add_log_rollover_item(self, name, item, log=True):
|
def add_log_rollover_item(self, name: str, item: str,
|
||||||
|
log: bool = True) -> None:
|
||||||
if self.file_logger is not None:
|
if self.file_logger is not None:
|
||||||
self.file_logger.set_rollover_info(name, item)
|
self.file_logger.set_rollover_info(name, item)
|
||||||
if log and item is not None:
|
if log and item is not None:
|
||||||
logging.info(item)
|
logging.info(item)
|
||||||
|
|
||||||
# ***** Component Management *****
|
# ***** Component Management *****
|
||||||
def _load_components(self, config):
|
def _load_components(self, config: confighelper.ConfigHelper) -> None:
|
||||||
# load core components
|
# load core components
|
||||||
for component in CORE_COMPONENTS:
|
for component in CORE_COMPONENTS:
|
||||||
self.load_component(config, component)
|
self.load_component(config, component)
|
||||||
|
@ -142,7 +168,11 @@ class Server:
|
||||||
for section in opt_sections:
|
for section in opt_sections:
|
||||||
self.load_component(config, section, None)
|
self.load_component(config, section, None)
|
||||||
|
|
||||||
def load_component(self, config, component_name, default=Sentinel):
|
def load_component(self,
|
||||||
|
config: confighelper.ConfigHelper,
|
||||||
|
component_name: str,
|
||||||
|
default: Union[SentinelClass, _T] = SENTINEL
|
||||||
|
) -> 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]
|
||||||
# Make sure component exists
|
# Make sure component exists
|
||||||
|
@ -152,7 +182,7 @@ class Server:
|
||||||
msg = f"Component ({component_name}) does not exist"
|
msg = f"Component ({component_name}) does not exist"
|
||||||
logging.info(msg)
|
logging.info(msg)
|
||||||
self.failed_components.append(component_name)
|
self.failed_components.append(component_name)
|
||||||
if default == Sentinel:
|
if isinstance(default, SentinelClass):
|
||||||
raise ServerError(msg)
|
raise ServerError(msg)
|
||||||
return default
|
return default
|
||||||
try:
|
try:
|
||||||
|
@ -169,32 +199,45 @@ class Server:
|
||||||
msg = f"Unable to load component: ({component_name})"
|
msg = f"Unable to load component: ({component_name})"
|
||||||
logging.exception(msg)
|
logging.exception(msg)
|
||||||
self.failed_components.append(component_name)
|
self.failed_components.append(component_name)
|
||||||
if default == Sentinel:
|
if isinstance(default, SentinelClass):
|
||||||
raise ServerError(msg)
|
raise ServerError(msg)
|
||||||
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, component_name, default=Sentinel):
|
def lookup_component(self,
|
||||||
|
component_name: str,
|
||||||
|
default: Union[SentinelClass, _T] = SENTINEL
|
||||||
|
) -> Union[_T, Any]:
|
||||||
component = self.components.get(component_name, default)
|
component = self.components.get(component_name, default)
|
||||||
if component == Sentinel:
|
if isinstance(component, SentinelClass):
|
||||||
raise ServerError(f"Component ({component_name}) not found")
|
raise ServerError(f"Component ({component_name}) not found")
|
||||||
return component
|
return component
|
||||||
|
|
||||||
def register_notification(self, event_name, notify_name=None):
|
def register_notification(self,
|
||||||
|
event_name: str,
|
||||||
|
notify_name: Optional[str] = None
|
||||||
|
) -> None:
|
||||||
wsm = self.moonraker_app.get_websocket_manager()
|
wsm = self.moonraker_app.get_websocket_manager()
|
||||||
wsm.register_notification(event_name, notify_name)
|
wsm.register_notification(event_name, notify_name)
|
||||||
|
|
||||||
def register_event_handler(self, event, callback):
|
def register_event_handler(self,
|
||||||
|
event: str,
|
||||||
|
callback: FlexCallback
|
||||||
|
) -> None:
|
||||||
self.events.setdefault(event, []).append(callback)
|
self.events.setdefault(event, []).append(callback)
|
||||||
|
|
||||||
def send_event(self, event, *args):
|
def send_event(self, event: str, *args) -> None:
|
||||||
events = self.events.get(event, [])
|
events = self.events.get(event, [])
|
||||||
for evt in events:
|
for evt in events:
|
||||||
self.ioloop.spawn_callback(evt, *args)
|
self.ioloop.spawn_callback(evt, *args)
|
||||||
|
|
||||||
def register_remote_method(self, method_name, cb, need_klippy_reg=True):
|
def register_remote_method(self,
|
||||||
|
method_name: str,
|
||||||
|
cb: FlexCallback,
|
||||||
|
need_klippy_reg: bool = True
|
||||||
|
) -> None:
|
||||||
if method_name in self.remote_methods:
|
if method_name in self.remote_methods:
|
||||||
# XXX - may want to raise an exception here
|
# XXX - may want to raise an exception here
|
||||||
logging.info(f"Remote method ({method_name}) already registered")
|
logging.info(f"Remote method ({method_name}) already registered")
|
||||||
|
@ -204,28 +247,28 @@ class Server:
|
||||||
# These methods need to be registered with Klippy
|
# These methods need to be registered with Klippy
|
||||||
self.klippy_reg_methods.append(method_name)
|
self.klippy_reg_methods.append(method_name)
|
||||||
|
|
||||||
def get_host_info(self):
|
def get_host_info(self) -> Tuple[str, int]:
|
||||||
hostname = socket.gethostname()
|
hostname = socket.gethostname()
|
||||||
return hostname, self.port
|
return hostname, self.port
|
||||||
|
|
||||||
def get_klippy_info(self):
|
def get_klippy_info(self) -> Dict[str, Any]:
|
||||||
return dict(self.klippy_info)
|
return dict(self.klippy_info)
|
||||||
|
|
||||||
def get_klippy_state(self):
|
def get_klippy_state(self) -> str:
|
||||||
return self.klippy_state
|
return self.klippy_state
|
||||||
|
|
||||||
# ***** Klippy Connection *****
|
# ***** Klippy Connection *****
|
||||||
async def _connect_klippy(self):
|
async def _connect_klippy(self) -> None:
|
||||||
if not self.server_running:
|
if not self.server_running:
|
||||||
return
|
return
|
||||||
ret = await self.klippy_connection.connect(self.klippy_address)
|
ret = await self.klippy_connection.connect(self.klippy_address)
|
||||||
if not ret:
|
if not ret:
|
||||||
self.ioloop.call_later(.25, self._connect_klippy)
|
self.ioloop.call_later(.25, self._connect_klippy) # type: ignore
|
||||||
return
|
return
|
||||||
# begin server iniialization
|
# begin server iniialization
|
||||||
self.ioloop.spawn_callback(self._initialize)
|
self.ioloop.spawn_callback(self._initialize)
|
||||||
|
|
||||||
def process_command(self, cmd):
|
def process_command(self, cmd: Dict[str, Any]) -> None:
|
||||||
method = cmd.get('method', None)
|
method = cmd.get('method', None)
|
||||||
if method is not None:
|
if method is not None:
|
||||||
# This is a remote method called from klippy
|
# This is a remote method called from klippy
|
||||||
|
@ -253,15 +296,15 @@ class Server:
|
||||||
result = ServerError(err, 400)
|
result = ServerError(err, 400)
|
||||||
request.notify(result)
|
request.notify(result)
|
||||||
|
|
||||||
async def _execute_method(self, method_name, **kwargs):
|
async def _execute_method(self, method_name: str, **kwargs) -> None:
|
||||||
try:
|
try:
|
||||||
ret = self.remote_methods[method_name](**kwargs)
|
ret = self.remote_methods[method_name](**kwargs)
|
||||||
if asyncio.iscoroutine(ret):
|
if ret is not None:
|
||||||
await ret
|
await ret
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception(f"Error running remote method: {method_name}")
|
logging.exception(f"Error running remote method: {method_name}")
|
||||||
|
|
||||||
def on_connection_closed(self):
|
def on_connection_closed(self) -> None:
|
||||||
self.init_list = []
|
self.init_list = []
|
||||||
self.klippy_state = "disconnected"
|
self.klippy_state = "disconnected"
|
||||||
for request in self.pending_requests.values():
|
for request in self.pending_requests.values():
|
||||||
|
@ -273,11 +316,11 @@ class Server:
|
||||||
if self.init_handle is not None:
|
if self.init_handle is not None:
|
||||||
self.ioloop.remove_timeout(self.init_handle)
|
self.ioloop.remove_timeout(self.init_handle)
|
||||||
if self.server_running:
|
if self.server_running:
|
||||||
self.ioloop.call_later(.25, self._connect_klippy)
|
self.ioloop.call_later(.25, self._connect_klippy) # type: ignore
|
||||||
if self.klippy_disconnect_evt is not None:
|
if self.klippy_disconnect_evt is not None:
|
||||||
self.klippy_disconnect_evt.set()
|
self.klippy_disconnect_evt.set()
|
||||||
|
|
||||||
async def _initialize(self):
|
async def _initialize(self) -> None:
|
||||||
if not self.server_running:
|
if not self.server_running:
|
||||||
return
|
return
|
||||||
await self._check_ready()
|
await self._check_ready()
|
||||||
|
@ -311,18 +354,19 @@ class Server:
|
||||||
else:
|
else:
|
||||||
self.init_attempts += 1
|
self.init_attempts += 1
|
||||||
self.init_handle = self.ioloop.call_later(
|
self.init_handle = self.ioloop.call_later(
|
||||||
INIT_TIME, self._initialize)
|
INIT_TIME, self._initialize) # type: ignore
|
||||||
|
|
||||||
async def _request_endpoints(self):
|
async def _request_endpoints(self) -> None:
|
||||||
result = await self.klippy_apis.list_endpoints(default=None)
|
result = await self.klippy_apis.list_endpoints(default=None)
|
||||||
if result is None:
|
if result is None:
|
||||||
return
|
return
|
||||||
endpoints = result.get('endpoints', {})
|
endpoints = result.get('endpoints', [])
|
||||||
for ep in endpoints:
|
for ep in endpoints:
|
||||||
self.moonraker_app.register_remote_handler(ep)
|
self.moonraker_app.register_remote_handler(ep)
|
||||||
|
|
||||||
async def _check_ready(self):
|
async def _check_ready(self) -> None:
|
||||||
send_id = "identified" not in self.init_list
|
send_id = "identified" not in self.init_list
|
||||||
|
result: Dict[str, Any]
|
||||||
try:
|
try:
|
||||||
result = await self.klippy_apis.get_klippy_info(send_id)
|
result = await self.klippy_apis.get_klippy_info(send_id)
|
||||||
except ServerError as e:
|
except ServerError as e:
|
||||||
|
@ -354,7 +398,7 @@ class Server:
|
||||||
msg = result.get('state_message', "Klippy Not Ready")
|
msg = result.get('state_message', "Klippy Not Ready")
|
||||||
logging.info("\n" + msg)
|
logging.info("\n" + msg)
|
||||||
|
|
||||||
async def _verify_klippy_requirements(self):
|
async def _verify_klippy_requirements(self) -> None:
|
||||||
result = await self.klippy_apis.get_object_list(default=None)
|
result = await self.klippy_apis.get_object_list(default=None)
|
||||||
if result is None:
|
if result is None:
|
||||||
logging.info(
|
logging.info(
|
||||||
|
@ -370,39 +414,43 @@ class Server:
|
||||||
f"to printer.cfg for full Moonraker functionality.")
|
f"to printer.cfg for full Moonraker functionality.")
|
||||||
if "virtual_sdcard" not in missing_objs:
|
if "virtual_sdcard" not in missing_objs:
|
||||||
# Update the gcode path
|
# Update the gcode path
|
||||||
result = await self.klippy_apis.query_objects(
|
query_res = await self.klippy_apis.query_objects(
|
||||||
{'configfile': None}, default=None)
|
{'configfile': None}, default=None)
|
||||||
if result is None:
|
if query_res is None:
|
||||||
logging.info(f"Unable to set SD Card path")
|
logging.info(f"Unable to set SD Card path")
|
||||||
else:
|
else:
|
||||||
config = result.get('configfile', {}).get('config', {})
|
config = query_res.get('configfile', {}).get('config', {})
|
||||||
vsd_config = config.get('virtual_sdcard', {})
|
vsd_config = config.get('virtual_sdcard', {})
|
||||||
vsd_path = vsd_config.get('path', None)
|
vsd_path = vsd_config.get('path', None)
|
||||||
if vsd_path is not None:
|
if vsd_path is not None:
|
||||||
file_manager = self.lookup_component('file_manager')
|
file_manager: FileManager = self.lookup_component(
|
||||||
|
'file_manager')
|
||||||
file_manager.register_directory('gcodes', vsd_path)
|
file_manager.register_directory('gcodes', vsd_path)
|
||||||
else:
|
else:
|
||||||
logging.info(
|
logging.info(
|
||||||
"Configuration for [virtual_sdcard] not found,"
|
"Configuration for [virtual_sdcard] not found,"
|
||||||
" unable to set SD Card path")
|
" unable to set SD Card path")
|
||||||
|
|
||||||
def _process_gcode_response(self, response):
|
def _process_gcode_response(self, response: str) -> None:
|
||||||
self.send_event("server:gcode_response", response)
|
self.send_event("server:gcode_response", response)
|
||||||
|
|
||||||
def _process_status_update(self, eventtime, status):
|
def _process_status_update(self,
|
||||||
|
eventtime: float,
|
||||||
|
status: Dict[str, Any]
|
||||||
|
) -> None:
|
||||||
if 'webhooks' in status:
|
if 'webhooks' in status:
|
||||||
# XXX - process other states (startup, ready, error, etc)?
|
# XXX - process other states (startup, ready, error, etc)?
|
||||||
state = status['webhooks'].get('state', None)
|
state: Optional[str] = status['webhooks'].get('state', None)
|
||||||
if state is not None:
|
if state is not None:
|
||||||
if state == "shutdown":
|
if state == "shutdown":
|
||||||
logging.info("Klippy has shutdown")
|
logging.info("Klippy has shutdown")
|
||||||
self.send_event("server:klippy_shutdown")
|
self.send_event("server:klippy_shutdown")
|
||||||
self.klippy_state = state
|
self.klippy_state = state
|
||||||
for conn, sub in self.subscriptions.items():
|
for conn, sub in self.subscriptions.items():
|
||||||
conn_status = {}
|
conn_status: Dict[str, Any] = {}
|
||||||
for name, fields in sub.items():
|
for name, fields in sub.items():
|
||||||
if name in status:
|
if name in status:
|
||||||
val = status[name]
|
val: Dict[str, Any] = status[name]
|
||||||
if fields is None:
|
if fields is None:
|
||||||
conn_status[name] = dict(val)
|
conn_status[name] = dict(val)
|
||||||
else:
|
else:
|
||||||
|
@ -410,18 +458,20 @@ class Server:
|
||||||
k: v for k, v in val.items() if k in fields}
|
k: v for k, v in val.items() if k in fields}
|
||||||
conn.send_status(conn_status)
|
conn.send_status(conn_status)
|
||||||
|
|
||||||
async def make_request(self, web_request):
|
async def make_request(self, web_request: WebRequest) -> Any:
|
||||||
rpc_method = web_request.get_endpoint()
|
rpc_method = web_request.get_endpoint()
|
||||||
if rpc_method == "objects/subscribe":
|
if rpc_method == "objects/subscribe":
|
||||||
return await self._request_subscripton(web_request)
|
return await self._request_subscripton(web_request)
|
||||||
else:
|
else:
|
||||||
if rpc_method == "gcode/script":
|
if rpc_method == "gcode/script":
|
||||||
script = web_request.get_str('script', "")
|
script = web_request.get_str('script', "")
|
||||||
data_store = self.lookup_component('data_store')
|
data_store: DataStore = self.lookup_component('data_store')
|
||||||
data_store.store_gcode_command(script)
|
data_store.store_gcode_command(script)
|
||||||
return await self._request_standard(web_request)
|
return await self._request_standard(web_request)
|
||||||
|
|
||||||
async def _request_subscripton(self, web_request):
|
async def _request_subscripton(self,
|
||||||
|
web_request: WebRequest
|
||||||
|
) -> Dict[str, Any]:
|
||||||
args = web_request.get_args()
|
args = web_request.get_args()
|
||||||
conn = web_request.get_connection()
|
conn = web_request.get_connection()
|
||||||
|
|
||||||
|
@ -432,7 +482,7 @@ class Server:
|
||||||
raise self.error(
|
raise self.error(
|
||||||
"No connection associated with subscription request")
|
"No connection associated with subscription request")
|
||||||
self.subscriptions[conn] = sub
|
self.subscriptions[conn] = sub
|
||||||
all_subs = {}
|
all_subs: Dict[str, Any] = {}
|
||||||
# request superset of all client subscriptions
|
# request superset of all client subscriptions
|
||||||
for sub in self.subscriptions.values():
|
for sub in self.subscriptions.values():
|
||||||
for obj, items in sub.items():
|
for obj, items in sub.items():
|
||||||
|
@ -465,7 +515,7 @@ class Server:
|
||||||
result['status'] = pruned_status
|
result['status'] = pruned_status
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def _request_standard(self, web_request):
|
async def _request_standard(self, web_request: WebRequest) -> Any:
|
||||||
rpc_method = web_request.get_endpoint()
|
rpc_method = web_request.get_endpoint()
|
||||||
args = web_request.get_args()
|
args = web_request.get_args()
|
||||||
# Create a base klippy request
|
# Create a base klippy request
|
||||||
|
@ -475,23 +525,23 @@ class Server:
|
||||||
self.klippy_connection.send_request, base_request)
|
self.klippy_connection.send_request, base_request)
|
||||||
return await base_request.wait()
|
return await base_request.wait()
|
||||||
|
|
||||||
def remove_subscription(self, conn):
|
def remove_subscription(self, conn: Subscribable) -> None:
|
||||||
self.subscriptions.pop(conn, None)
|
self.subscriptions.pop(conn, None)
|
||||||
|
|
||||||
def _handle_term_signal(self):
|
def _handle_term_signal(self) -> None:
|
||||||
logging.info(f"Exiting with signal SIGTERM")
|
logging.info(f"Exiting with signal SIGTERM")
|
||||||
self.ioloop.spawn_callback(self._stop_server, "terminate")
|
self.ioloop.spawn_callback(self._stop_server, "terminate")
|
||||||
|
|
||||||
async def _stop_server(self, exit_reason="restart"):
|
async def _stop_server(self, exit_reason: str = "restart") -> None:
|
||||||
self.server_running = False
|
self.server_running = False
|
||||||
for method in ["on_exit", "close"]:
|
for method in ["on_exit", "close"]:
|
||||||
for name, component in self.components.items():
|
for name, component in self.components.items():
|
||||||
if not hasattr(component, method):
|
if not hasattr(component, method):
|
||||||
continue
|
continue
|
||||||
func = getattr(component, method)
|
func: FlexCallback = getattr(component, method)
|
||||||
try:
|
try:
|
||||||
ret = func()
|
ret = func()
|
||||||
if asyncio.iscoroutine(ret):
|
if ret is not None:
|
||||||
await ret
|
await ret
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception(
|
logging.exception(
|
||||||
|
@ -517,12 +567,15 @@ class Server:
|
||||||
aioloop.remove_signal_handler(signal.SIGTERM)
|
aioloop.remove_signal_handler(signal.SIGTERM)
|
||||||
self.ioloop.stop()
|
self.ioloop.stop()
|
||||||
|
|
||||||
async def _handle_server_restart(self, web_request):
|
async def _handle_server_restart(self, web_request: WebRequest) -> str:
|
||||||
self.ioloop.spawn_callback(self._stop_server)
|
self.ioloop.spawn_callback(self._stop_server)
|
||||||
return "ok"
|
return "ok"
|
||||||
|
|
||||||
async def _handle_info_request(self, web_request):
|
async def _handle_info_request(self,
|
||||||
file_manager = self.lookup_component('file_manager', None)
|
web_request: WebRequest
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
file_manager: Optional[FileManager] = self.lookup_component(
|
||||||
|
'file_manager', None)
|
||||||
reg_dirs = []
|
reg_dirs = []
|
||||||
if file_manager is not None:
|
if file_manager is not None:
|
||||||
reg_dirs = file_manager.get_registered_dirs()
|
reg_dirs = file_manager.get_registered_dirs()
|
||||||
|
@ -535,19 +588,24 @@ class Server:
|
||||||
'failed_plugins': self.failed_components,
|
'failed_plugins': self.failed_components,
|
||||||
'registered_directories': reg_dirs}
|
'registered_directories': reg_dirs}
|
||||||
|
|
||||||
async def _handle_config_request(self, web_request):
|
async def _handle_config_request(self,
|
||||||
|
web_request: WebRequest
|
||||||
|
) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
'config': self.config.get_parsed_config()
|
'config': self.config.get_parsed_config()
|
||||||
}
|
}
|
||||||
|
|
||||||
class KlippyConnection:
|
class KlippyConnection:
|
||||||
def __init__(self, on_recd, on_close):
|
def __init__(self,
|
||||||
|
on_recd: Callable[[dict], None],
|
||||||
|
on_close: Callable[[], None]
|
||||||
|
) -> None:
|
||||||
self.ioloop = IOLoop.current()
|
self.ioloop = IOLoop.current()
|
||||||
self.iostream = None
|
self.iostream: Optional[iostream.IOStream] = None
|
||||||
self.on_recd = on_recd
|
self.on_recd = on_recd
|
||||||
self.on_close = on_close
|
self.on_close = on_close
|
||||||
|
|
||||||
async def connect(self, address):
|
async def connect(self, address: str) -> bool:
|
||||||
ksock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
ksock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
kstream = iostream.IOStream(ksock)
|
kstream = iostream.IOStream(ksock)
|
||||||
try:
|
try:
|
||||||
|
@ -560,7 +618,7 @@ class KlippyConnection:
|
||||||
self.ioloop.spawn_callback(self._read_stream, self.iostream)
|
self.ioloop.spawn_callback(self._read_stream, self.iostream)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _read_stream(self, stream):
|
async def _read_stream(self, stream: iostream.IOStream) -> None:
|
||||||
while not stream.closed():
|
while not stream.closed():
|
||||||
try:
|
try:
|
||||||
data = await stream.read_until(b'\x03')
|
data = await stream.read_until(b'\x03')
|
||||||
|
@ -576,7 +634,7 @@ class KlippyConnection:
|
||||||
logging.exception(
|
logging.exception(
|
||||||
f"Error processing Klippy Host Response: {data.decode()}")
|
f"Error processing Klippy Host Response: {data.decode()}")
|
||||||
|
|
||||||
async def send_request(self, request):
|
async def send_request(self, request: BaseRequest) -> None:
|
||||||
if self.iostream is None:
|
if self.iostream is None:
|
||||||
request.notify(ServerError("Klippy Host not connected", 503))
|
request.notify(ServerError("Klippy Host not connected", 503))
|
||||||
return
|
return
|
||||||
|
@ -586,24 +644,24 @@ class KlippyConnection:
|
||||||
except iostream.StreamClosedError:
|
except iostream.StreamClosedError:
|
||||||
request.notify(ServerError("Klippy Host not connected", 503))
|
request.notify(ServerError("Klippy Host not connected", 503))
|
||||||
|
|
||||||
def is_connected(self):
|
def is_connected(self) -> bool:
|
||||||
return self.iostream is not None and not self.iostream.closed()
|
return self.iostream is not None and not self.iostream.closed()
|
||||||
|
|
||||||
def close(self):
|
def close(self) -> None:
|
||||||
if self.iostream is not None and \
|
if self.iostream is not None and \
|
||||||
not self.iostream.closed():
|
not self.iostream.closed():
|
||||||
self.iostream.close()
|
self.iostream.close()
|
||||||
|
|
||||||
# Basic WebRequest class, easily converted to dict for json encoding
|
# Basic WebRequest class, easily converted to dict for json encoding
|
||||||
class BaseRequest:
|
class BaseRequest:
|
||||||
def __init__(self, rpc_method, params):
|
def __init__(self, rpc_method: str, params: Dict[str, Any]) -> None:
|
||||||
self.id = id(self)
|
self.id = id(self)
|
||||||
self.rpc_method = rpc_method
|
self.rpc_method = rpc_method
|
||||||
self.params = params
|
self.params = params
|
||||||
self._event = Event()
|
self._event = Event()
|
||||||
self.response = None
|
self.response: Any = None
|
||||||
|
|
||||||
async def wait(self):
|
async def wait(self) -> Any:
|
||||||
# Log pending requests every 60 seconds
|
# Log pending requests every 60 seconds
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
while True:
|
while True:
|
||||||
|
@ -622,15 +680,15 @@ class BaseRequest:
|
||||||
raise self.response
|
raise self.response
|
||||||
return self.response
|
return self.response
|
||||||
|
|
||||||
def notify(self, response):
|
def notify(self, response: Any) -> None:
|
||||||
self.response = response
|
self.response = response
|
||||||
self._event.set()
|
self._event.set()
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
return {'id': self.id, 'method': self.rpc_method,
|
return {'id': self.id, 'method': self.rpc_method,
|
||||||
'params': self.params}
|
'params': self.params}
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
# Parse start arguments
|
# Parse start arguments
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Moonraker - Klipper API Server")
|
description="Moonraker - Klipper API Server")
|
||||||
|
|
Loading…
Reference in New Issue