From 501ea80deae8fb789939e9d681eda8970e8aa495 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:04:14 -0400 Subject: [PATCH 01/15] update SystemManager.listen and listenDevice logic --- server/python/plugin_remote.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/server/python/plugin_remote.py b/server/python/plugin_remote.py index 72c54283a3..1b4935360d 100644 --- a/server/python/plugin_remote.py +++ b/server/python/plugin_remote.py @@ -143,13 +143,11 @@ def getDeviceByName(self, name: str) -> scrypted_python.scrypted_sdk.ScryptedDev if checkName.get('value', None) == name: return self.getDeviceById(check) - # TODO async def listen(self, callback: scrypted_python.scrypted_sdk.EventListener) -> scrypted_python.scrypted_sdk.EventListenerRegister: - return super().listen(callback) + self.api.listen(callback) - # TODO async def listenDevice(self, id: str, event: str | scrypted_python.scrypted_sdk.EventListenerOptions, callback: scrypted_python.scrypted_sdk.EventListener) -> scrypted_python.scrypted_sdk.EventListenerRegister: - return super().listenDevice(id, event, callback) + return self.api.listenDevice(id, event, callback) async def removeDevice(self, id: str) -> None: return await self.api.removeDevice(id) From 153c32babcd9bd1a5731e74b3c8cf453157c4ede Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:24:41 -0400 Subject: [PATCH 02/15] Add additional stubs --- .../scrypted_python/scrypted_sdk/other.py | 83 ++++++++++++++++++- server/python/plugin_remote.py | 8 +- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/sdk/types/scrypted_python/scrypted_sdk/other.py b/sdk/types/scrypted_python/scrypted_sdk/other.py index f6c095e444..802737db6e 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/other.py +++ b/sdk/types/scrypted_python/scrypted_sdk/other.py @@ -1,12 +1,16 @@ from __future__ import annotations from enum import Enum -from typing import AbstractSet, Any, Callable, Literal, Union +from typing import AbstractSet, Any, Callable, Literal, Union, TYPE_CHECKING try: from typing import TypedDict except: from typing_extensions import TypedDict + +if TYPE_CHECKING: + from .types import DeviceManifest, Device, EventDetails, EventListenerOptions, EventListenerRegister, MediaManager, ScryptedDevice, ScryptedInterfaceProperty + SettingValue = str EventListener = Callable[[Any, Any, Any], None] VibratePattern = list[int] @@ -86,4 +90,79 @@ def removeEventListener(self, type: str, listener: Callable[[dict], None], optio pass def send(self, data: str | bytes | bytearray | int | float | bool) -> None: - pass \ No newline at end of file + pass + + +class ScryptedInterfaceDescriptor: + name: str + properties: list[str] + methods: list[str] + + +class PluginLogger: + async def log(level: str, message: str) -> None: + pass + + async def clear() -> None: + pass + + async def clearAlert(message: str) -> None: + pass + + async def clearAlerts() -> None: + pass + + + +class PluginAPI: + async def setState(nativeId: str | None, key: str, value: Any) -> None: + pass + + async def onDevicesChanged(deviceManifest: "DeviceManifest") -> None: + pass + + async def onDeviceDiscovered(device: "Device") -> None: + pass + + async def onDeviceEvent(nativeId: str | None, eventInterface: str, eventData: Any) -> None: + pass + + async def onMixinEvent(id: str, nativeId: str | None, eventInterface: str, eventData: Any) -> None: + pass + + async def onDeviceRemoved(nativeId: str) -> None: + pass + + async def setStorage(nativeId: str, storage: dict[str, Any]) -> None: + pass + + async def getDeviceById(id: str) -> "ScryptedDevice": + pass + + async def setDeviceProperty(id: str, property: "ScryptedInterfaceProperty", value: Any) -> None: + pass + + async def removeDevice(id: str) -> None: + pass + + async def listen(callback: Callable[[str, "EventDetails", Any], None]) -> "EventListenerRegister": + pass + + async def listenDevice(id: str, event: str | "EventListenerOptions", callback: Callable[["EventDetails", Any], None]) -> EventListenerRegister: + pass + + + async def getLogger(nativeId: str | None) -> PluginLogger: + pass + + async def getComponent(id: str) -> Any: + pass + + async def getMediaManager() -> "MediaManager": + pass + + async def requestRestart() -> None: + pass + + async def setScryptedInterfaceDescriptors(typesVersion: str, descriptors: dict[str, ScryptedInterfaceDescriptor]) -> None: + pass diff --git a/server/python/plugin_remote.py b/server/python/plugin_remote.py index 1b4935360d..5494dd8094 100644 --- a/server/python/plugin_remote.py +++ b/server/python/plugin_remote.py @@ -22,7 +22,7 @@ import scrypted_python.scrypted_sdk.types from scrypted_python.scrypted_sdk import PluginFork, ScryptedStatic from scrypted_python.scrypted_sdk.types import (Device, DeviceManifest, - EventDetails, + EventDetails, PluginAPI, ScryptedInterfaceMethods, ScryptedInterfaceProperty, Storage) @@ -88,7 +88,7 @@ async def apply(): class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager): deviceProxies: Mapping[str, DeviceProxy] - def __init__(self, api: Any, systemState: Mapping[str, Mapping[str, SystemDeviceState]]) -> None: + def __init__(self, api: PluginAPI, systemState: Mapping[str, Mapping[str, SystemDeviceState]]) -> None: super().__init__() self.api = api self.systemState = systemState @@ -144,10 +144,10 @@ def getDeviceByName(self, name: str) -> scrypted_python.scrypted_sdk.ScryptedDev return self.getDeviceById(check) async def listen(self, callback: scrypted_python.scrypted_sdk.EventListener) -> scrypted_python.scrypted_sdk.EventListenerRegister: - self.api.listen(callback) + return await self.api.listen(callback) async def listenDevice(self, id: str, event: str | scrypted_python.scrypted_sdk.EventListenerOptions, callback: scrypted_python.scrypted_sdk.EventListener) -> scrypted_python.scrypted_sdk.EventListenerRegister: - return self.api.listenDevice(id, event, callback) + return await self.api.listenDevice(id, event, callback) async def removeDevice(self, id: str) -> None: return await self.api.removeDevice(id) From 7d6be3425298886325a420c4eb623b6897acfd9e Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:26:53 -0400 Subject: [PATCH 03/15] add missing quotes --- sdk/types/scrypted_python/scrypted_sdk/other.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/types/scrypted_python/scrypted_sdk/other.py b/sdk/types/scrypted_python/scrypted_sdk/other.py index 802737db6e..d46b91620d 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/other.py +++ b/sdk/types/scrypted_python/scrypted_sdk/other.py @@ -148,7 +148,7 @@ async def removeDevice(id: str) -> None: async def listen(callback: Callable[[str, "EventDetails", Any], None]) -> "EventListenerRegister": pass - async def listenDevice(id: str, event: str | "EventListenerOptions", callback: Callable[["EventDetails", Any], None]) -> EventListenerRegister: + async def listenDevice(id: str, event: str | "EventListenerOptions", callback: Callable[["EventDetails", Any], None]) -> "EventListenerRegister": pass From 1863ca2866f35fe2ae1029754fcc2b0c8750a6bb Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:28:44 -0400 Subject: [PATCH 04/15] Fix func defs --- .../scrypted_python/scrypted_sdk/other.py | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/sdk/types/scrypted_python/scrypted_sdk/other.py b/sdk/types/scrypted_python/scrypted_sdk/other.py index d46b91620d..48932f52f7 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/other.py +++ b/sdk/types/scrypted_python/scrypted_sdk/other.py @@ -100,16 +100,16 @@ class ScryptedInterfaceDescriptor: class PluginLogger: - async def log(level: str, message: str) -> None: + async def log(self, level: str, message: str) -> None: pass - async def clear() -> None: + async def clear(self) -> None: pass - async def clearAlert(message: str) -> None: + async def clearAlert(self, message: str) -> None: pass - async def clearAlerts() -> None: + async def clearAlerts(self) -> None: pass @@ -118,51 +118,51 @@ class PluginAPI: async def setState(nativeId: str | None, key: str, value: Any) -> None: pass - async def onDevicesChanged(deviceManifest: "DeviceManifest") -> None: + async def onDevicesChanged(self, deviceManifest: "DeviceManifest") -> None: pass - async def onDeviceDiscovered(device: "Device") -> None: + async def onDeviceDiscovered(self, device: "Device") -> None: pass - async def onDeviceEvent(nativeId: str | None, eventInterface: str, eventData: Any) -> None: + async def onDeviceEvent(self, nativeId: str | None, eventInterface: str, eventData: Any) -> None: pass - async def onMixinEvent(id: str, nativeId: str | None, eventInterface: str, eventData: Any) -> None: + async def onMixinEvent(self, id: str, nativeId: str | None, eventInterface: str, eventData: Any) -> None: pass - async def onDeviceRemoved(nativeId: str) -> None: + async def onDeviceRemoved(self, nativeId: str) -> None: pass - async def setStorage(nativeId: str, storage: dict[str, Any]) -> None: + async def setStorage(self, nativeId: str, storage: dict[str, Any]) -> None: pass - async def getDeviceById(id: str) -> "ScryptedDevice": + async def getDeviceById(self, id: str) -> "ScryptedDevice": pass - async def setDeviceProperty(id: str, property: "ScryptedInterfaceProperty", value: Any) -> None: + async def setDeviceProperty(self, id: str, property: "ScryptedInterfaceProperty", value: Any) -> None: pass - async def removeDevice(id: str) -> None: + async def removeDevice(self, id: str) -> None: pass - async def listen(callback: Callable[[str, "EventDetails", Any], None]) -> "EventListenerRegister": + async def listen(self, callback: Callable[[str, "EventDetails", Any], None]) -> "EventListenerRegister": pass - async def listenDevice(id: str, event: str | "EventListenerOptions", callback: Callable[["EventDetails", Any], None]) -> "EventListenerRegister": + async def listenDevice(self, id: str, event: str | "EventListenerOptions", callback: Callable[["EventDetails", Any], None]) -> "EventListenerRegister": pass - async def getLogger(nativeId: str | None) -> PluginLogger: + async def getLogger(self, nativeId: str | None) -> PluginLogger: pass - async def getComponent(id: str) -> Any: + async def getComponent(self, id: str) -> Any: pass - async def getMediaManager() -> "MediaManager": + async def getMediaManager(self) -> "MediaManager": pass - async def requestRestart() -> None: + async def requestRestart(self) -> None: pass - async def setScryptedInterfaceDescriptors(typesVersion: str, descriptors: dict[str, ScryptedInterfaceDescriptor]) -> None: + async def setScryptedInterfaceDescriptors(self, typesVersion: str, descriptors: dict[str, ScryptedInterfaceDescriptor]) -> None: pass From 9ab7a62fb031aeeedf3708cbe20c42cabfcbffa5 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:30:26 -0400 Subject: [PATCH 05/15] adjust type path --- server/python/plugin_remote.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/python/plugin_remote.py b/server/python/plugin_remote.py index 5494dd8094..5d729bdcf4 100644 --- a/server/python/plugin_remote.py +++ b/server/python/plugin_remote.py @@ -22,7 +22,7 @@ import scrypted_python.scrypted_sdk.types from scrypted_python.scrypted_sdk import PluginFork, ScryptedStatic from scrypted_python.scrypted_sdk.types import (Device, DeviceManifest, - EventDetails, PluginAPI, + EventDetails, ScryptedInterfaceMethods, ScryptedInterfaceProperty, Storage) @@ -88,7 +88,7 @@ async def apply(): class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager): deviceProxies: Mapping[str, DeviceProxy] - def __init__(self, api: PluginAPI, systemState: Mapping[str, Mapping[str, SystemDeviceState]]) -> None: + def __init__(self, api: scrypted_python.scrypted_sdk.PluginAPI, systemState: Mapping[str, Mapping[str, SystemDeviceState]]) -> None: super().__init__() self.api = api self.systemState = systemState From 4b90808fdad3d9d6f2f3e3f6923faa341c9fdaf7 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:33:38 -0400 Subject: [PATCH 06/15] fix more types --- sdk/types/scrypted_python/scrypted_sdk/other.py | 9 +++++---- server/python/plugin_remote.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/sdk/types/scrypted_python/scrypted_sdk/other.py b/sdk/types/scrypted_python/scrypted_sdk/other.py index 48932f52f7..96754cfdbe 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/other.py +++ b/sdk/types/scrypted_python/scrypted_sdk/other.py @@ -12,8 +12,9 @@ from .types import DeviceManifest, Device, EventDetails, EventListenerOptions, EventListenerRegister, MediaManager, ScryptedDevice, ScryptedInterfaceProperty SettingValue = str -EventListener = Callable[[Any, Any, Any], None] -VibratePattern = list[int] +EventListener = Callable[[str, EventDetails, Any], None] +DeviceEventListener = Callable[[EventDetails, Any], None] +VibratePattern = Union[int, list[int]] class Console: @@ -145,10 +146,10 @@ async def setDeviceProperty(self, id: str, property: "ScryptedInterfaceProperty" async def removeDevice(self, id: str) -> None: pass - async def listen(self, callback: Callable[[str, "EventDetails", Any], None]) -> "EventListenerRegister": + async def listen(self, callback: EventListener) -> "EventListenerRegister": pass - async def listenDevice(self, id: str, event: str | "EventListenerOptions", callback: Callable[["EventDetails", Any], None]) -> "EventListenerRegister": + async def listenDevice(self, id: str, event: str | "EventListenerOptions", callback: DeviceEventListener) -> "EventListenerRegister": pass diff --git a/server/python/plugin_remote.py b/server/python/plugin_remote.py index 5d729bdcf4..1704771909 100644 --- a/server/python/plugin_remote.py +++ b/server/python/plugin_remote.py @@ -146,7 +146,7 @@ def getDeviceByName(self, name: str) -> scrypted_python.scrypted_sdk.ScryptedDev async def listen(self, callback: scrypted_python.scrypted_sdk.EventListener) -> scrypted_python.scrypted_sdk.EventListenerRegister: return await self.api.listen(callback) - async def listenDevice(self, id: str, event: str | scrypted_python.scrypted_sdk.EventListenerOptions, callback: scrypted_python.scrypted_sdk.EventListener) -> scrypted_python.scrypted_sdk.EventListenerRegister: + async def listenDevice(self, id: str, event: str | scrypted_python.scrypted_sdk.EventListenerOptions, callback: scrypted_python.scrypted_sdk.DeviceEventListener) -> scrypted_python.scrypted_sdk.EventListenerRegister: return await self.api.listenDevice(id, event, callback) async def removeDevice(self, id: str) -> None: From c857518a642e716029958339f579eb3fa08c1efc Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 18 Jul 2023 02:12:07 -0400 Subject: [PATCH 07/15] fix type --- packages/python-client/test.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/python-client/test.py b/packages/python-client/test.py index ce43a69cad..9be480e837 100644 --- a/packages/python-client/test.py +++ b/packages/python-client/test.py @@ -131,7 +131,7 @@ async def main(): transport = EioRpcTransport(asyncio.get_event_loop()) sdk = await connect_scrypted_client( transport, - "https://localhost:10443", + os.environ.get("SCRYPTED_URL", "https://localhost:10443"), os.environ["SCRYPTED_USERNAME"], os.environ["SCRYPTED_PASSWORD"], ) @@ -147,5 +147,4 @@ async def main(): loop = asyncio.new_event_loop() -asyncio.run_coroutine_threadsafe(main(), loop) -loop.run_forever() +loop.run_until_complete(main()) From de14cab5366c4d63e36337bdc7124e3541255162 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 18 Jul 2023 02:13:56 -0400 Subject: [PATCH 08/15] fix typing --- sdk/types/scrypted_python/scrypted_sdk/other.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/types/scrypted_python/scrypted_sdk/other.py b/sdk/types/scrypted_python/scrypted_sdk/other.py index 96754cfdbe..0b93298acf 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/other.py +++ b/sdk/types/scrypted_python/scrypted_sdk/other.py @@ -12,8 +12,8 @@ from .types import DeviceManifest, Device, EventDetails, EventListenerOptions, EventListenerRegister, MediaManager, ScryptedDevice, ScryptedInterfaceProperty SettingValue = str -EventListener = Callable[[str, EventDetails, Any], None] -DeviceEventListener = Callable[[EventDetails, Any], None] +EventListener = Callable[[str, "EventDetails", Any], None] +DeviceEventListener = Callable[["EventDetails", Any], None] VibratePattern = Union[int, list[int]] From 281f40d731b36bc5c130f25985ca5f6ede2ada28 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 9 Aug 2023 02:56:05 -0400 Subject: [PATCH 09/15] Revert typing changes --- .../scrypted_python/scrypted_sdk/other.py | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/sdk/types/scrypted_python/scrypted_sdk/other.py b/sdk/types/scrypted_python/scrypted_sdk/other.py index 0b93298acf..c406c6acb8 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/other.py +++ b/sdk/types/scrypted_python/scrypted_sdk/other.py @@ -1,20 +1,29 @@ from __future__ import annotations from enum import Enum -from typing import AbstractSet, Any, Callable, Literal, Union, TYPE_CHECKING +from typing import AbstractSet, Any, Callable, Literal, TYPE_CHECKING + try: from typing import TypedDict -except: +except ImportError: from typing_extensions import TypedDict if TYPE_CHECKING: - from .types import DeviceManifest, Device, EventDetails, EventListenerOptions, EventListenerRegister, MediaManager, ScryptedDevice, ScryptedInterfaceProperty + from .types import ( + DeviceManifest, + Device, + EventListenerOptions, + EventListenerRegister, + MediaManager, + ScryptedDevice, + ScryptedInterfaceProperty, + ) SettingValue = str -EventListener = Callable[[str, "EventDetails", Any], None] -DeviceEventListener = Callable[["EventDetails", Any], None] -VibratePattern = Union[int, list[int]] +EventListener = Callable[[Any, Any, Any], None] +DeviceEventListener = Callable[[Any, Any], None] +VibratePattern = list[int] class Console: @@ -47,21 +56,18 @@ class RTCSessionDescriptionInit(TypedDict): class NotificationAction(TypedDict, total=False): - action: str title: str icon: str # optional class NotificationDirection(str, Enum): - auto = "auto" ltr = "ltr" rtl = "rtl" class WebSocket: - CLOSED: int CLOSING: int CONNECTING: int @@ -78,7 +84,9 @@ class WebSocket: readyState: int url: str - def addEventListener(self, type: str, listener: Callable[[dict], None], options: dict = None) -> None: + def addEventListener( + self, type: str, listener: Callable[[dict], None], options: dict = None + ) -> None: pass def close(self, code: int = None, reason: str = None) -> None: @@ -87,7 +95,9 @@ def close(self, code: int = None, reason: str = None) -> None: def dispatchEvent(self, event: dict) -> bool: pass - def removeEventListener(self, type: str, listener: Callable[[dict], None], options: dict = None) -> None: + def removeEventListener( + self, type: str, listener: Callable[[dict], None], options: dict = None + ) -> None: pass def send(self, data: str | bytes | bytearray | int | float | bool) -> None: @@ -114,7 +124,6 @@ async def clearAlerts(self) -> None: pass - class PluginAPI: async def setState(nativeId: str | None, key: str, value: Any) -> None: pass @@ -125,10 +134,14 @@ async def onDevicesChanged(self, deviceManifest: "DeviceManifest") -> None: async def onDeviceDiscovered(self, device: "Device") -> None: pass - async def onDeviceEvent(self, nativeId: str | None, eventInterface: str, eventData: Any) -> None: + async def onDeviceEvent( + self, nativeId: str | None, eventInterface: str, eventData: Any + ) -> None: pass - async def onMixinEvent(self, id: str, nativeId: str | None, eventInterface: str, eventData: Any) -> None: + async def onMixinEvent( + self, id: str, nativeId: str | None, eventInterface: str, eventData: Any + ) -> None: pass async def onDeviceRemoved(self, nativeId: str) -> None: @@ -140,7 +153,9 @@ async def setStorage(self, nativeId: str, storage: dict[str, Any]) -> None: async def getDeviceById(self, id: str) -> "ScryptedDevice": pass - async def setDeviceProperty(self, id: str, property: "ScryptedInterfaceProperty", value: Any) -> None: + async def setDeviceProperty( + self, id: str, property: "ScryptedInterfaceProperty", value: Any + ) -> None: pass async def removeDevice(self, id: str) -> None: @@ -149,10 +164,14 @@ async def removeDevice(self, id: str) -> None: async def listen(self, callback: EventListener) -> "EventListenerRegister": pass - async def listenDevice(self, id: str, event: str | "EventListenerOptions", callback: DeviceEventListener) -> "EventListenerRegister": + async def listenDevice( + self, + id: str, + event: str | "EventListenerOptions", + callback: DeviceEventListener, + ) -> "EventListenerRegister": pass - async def getLogger(self, nativeId: str | None) -> PluginLogger: pass @@ -165,5 +184,7 @@ async def getMediaManager(self) -> "MediaManager": async def requestRestart(self) -> None: pass - async def setScryptedInterfaceDescriptors(self, typesVersion: str, descriptors: dict[str, ScryptedInterfaceDescriptor]) -> None: + async def setScryptedInterfaceDescriptors( + self, typesVersion: str, descriptors: dict[str, ScryptedInterfaceDescriptor] + ) -> None: pass From 278e5b644d15ddfbcc6a42cf551d7b765326cca6 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:24:41 -0400 Subject: [PATCH 10/15] Add additional stubs --- sdk/types/scrypted_python/scrypted_sdk/other.py | 5 ++--- server/python/plugin_remote.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/sdk/types/scrypted_python/scrypted_sdk/other.py b/sdk/types/scrypted_python/scrypted_sdk/other.py index c406c6acb8..6018ff0f51 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/other.py +++ b/sdk/types/scrypted_python/scrypted_sdk/other.py @@ -1,8 +1,7 @@ from __future__ import annotations from enum import Enum -from typing import AbstractSet, Any, Callable, Literal, TYPE_CHECKING - +from typing import AbstractSet, Any, Callable, Literal, Union, TYPE_CHECKING try: from typing import TypedDict except ImportError: @@ -125,7 +124,7 @@ async def clearAlerts(self) -> None: class PluginAPI: - async def setState(nativeId: str | None, key: str, value: Any) -> None: + async def setState(self, nativeId: str | None, key: str, value: Any) -> None: pass async def onDevicesChanged(self, deviceManifest: "DeviceManifest") -> None: diff --git a/server/python/plugin_remote.py b/server/python/plugin_remote.py index 1704771909..895efab8c5 100644 --- a/server/python/plugin_remote.py +++ b/server/python/plugin_remote.py @@ -22,7 +22,7 @@ import scrypted_python.scrypted_sdk.types from scrypted_python.scrypted_sdk import PluginFork, ScryptedStatic from scrypted_python.scrypted_sdk.types import (Device, DeviceManifest, - EventDetails, + EventDetails, PluginAPI, ScryptedInterfaceMethods, ScryptedInterfaceProperty, Storage) From 4f4904c85fbae9205be055eea6229f49f9230463 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 9 Aug 2023 02:56:05 -0400 Subject: [PATCH 11/15] Revert typing changes --- sdk/types/scrypted_python/scrypted_sdk/other.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/types/scrypted_python/scrypted_sdk/other.py b/sdk/types/scrypted_python/scrypted_sdk/other.py index 6018ff0f51..92f135d962 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/other.py +++ b/sdk/types/scrypted_python/scrypted_sdk/other.py @@ -1,7 +1,8 @@ from __future__ import annotations from enum import Enum -from typing import AbstractSet, Any, Callable, Literal, Union, TYPE_CHECKING +from typing import AbstractSet, Any, Callable, Literal, TYPE_CHECKING + try: from typing import TypedDict except ImportError: From d1d955f80c412bddb89c41a432e9e1bac3dea6c2 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 22 Sep 2023 01:18:42 -0400 Subject: [PATCH 12/15] more changes --- packages/python-client/test.py | 28 ++++++-- .../scrypted_python/scrypted_sdk/types.py | 4 +- sdk/types/src/types.input.ts | 2 +- server/python/plugin_remote.py | 72 +++++++++---------- server/python/rpc-iterator-test.py | 2 +- server/python/rpc_reader.py | 20 +++--- 6 files changed, 67 insertions(+), 61 deletions(-) diff --git a/packages/python-client/test.py b/packages/python-client/test.py index 9be480e837..5a6a7307ac 100644 --- a/packages/python-client/test.py +++ b/packages/python-client/test.py @@ -26,7 +26,7 @@ def __init__(self, loop: asyncio.AbstractEventLoop): def on_message(data): self.read_queue.put_nowait(data) - asyncio.run_coroutine_threadsafe(self.send_loop(), self.loop) + asyncio.create_task(self.send_loop()) async def read(self): return await self.read_queue.get() @@ -50,6 +50,17 @@ async def send(): except Exception as e: reject(e) + asyncio.create_task(send()) + + def writeJSON(self, json, reject): + async def send(): + try: + if self.write_error: + raise self.write_error + self.write_queue.put_nowait(json) + except Exception as e: + reject(e) + asyncio.run_coroutine_threadsafe(send(), self.loop) def writeJSON(self, json, reject): @@ -90,9 +101,7 @@ async def connect_scrypted_client( ) ret = asyncio.Future[ScryptedStatic](loop=transport.loop) - peer, peerReadLoop = await rpc_reader.prepare_peer_readloop( - transport.loop, transport - ) + peer, peerReadLoop = await rpc_reader.prepare_peer_readloop(transport) peer.params["print"] = print def callback(api, pluginId, hostInfo): @@ -115,13 +124,13 @@ async def resolve(): sdk.mediaManager = MediaManager(await api.getMediaManager()) ret.set_result(sdk) - asyncio.run_coroutine_threadsafe(resolve(), transport.loop) + asyncio.create_task(resolve()) remote.setSystemState = remoteSetSystemState return remote peer.params["getRemote"] = callback - asyncio.run_coroutine_threadsafe(peerReadLoop(), transport.loop) + asyncio.create_task(peerReadLoop()) sdk = await ret return sdk @@ -138,7 +147,14 @@ async def main(): for id in sdk.systemManager.getSystemState(): device = sdk.systemManager.getDeviceById(id) + if ScryptedInterface.Camera not in device.interfaces: + continue print(device.name) + print(device.id) + print(device.interfaces) + # print(device.providedInterfaces) + stream = await device.getVideoStreamOptions() + print(stream) if ScryptedInterface.OnOff.value in device.interfaces: print(f"OnOff: device is {device.on}") diff --git a/sdk/types/scrypted_python/scrypted_sdk/types.py b/sdk/types/scrypted_python/scrypted_sdk/types.py index 60b094aca7..1690b6604e 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/types.py +++ b/sdk/types/scrypted_python/scrypted_sdk/types.py @@ -1530,7 +1530,7 @@ class SystemManager: async def getComponent(self, id: str) -> Any: pass - def getDeviceById(self, id: str) -> ScryptedDevice: + async def getDeviceById(self, id: str) -> ScryptedDevice: pass def getDeviceByName(self, name: str) -> ScryptedDevice: @@ -2993,7 +2993,7 @@ def applicationInfo(self, value: LauncherApplicationInfo): class EventListenerRegister: """Returned when an event listener is attached to an EventEmitter. Call removeListener to unregister from events.""" - def removeListener(self) -> None: + async def removeListener(self) -> None: pass diff --git a/sdk/types/src/types.input.ts b/sdk/types/src/types.input.ts index 96da2265b5..c6aecc9ad7 100644 --- a/sdk/types/src/types.input.ts +++ b/sdk/types/src/types.input.ts @@ -82,7 +82,7 @@ export interface EventDetails { * @category Core Reference */ export interface EventListenerRegister { - removeListener(): void; + removeListener(): Promise; } diff --git a/server/python/plugin_remote.py b/server/python/plugin_remote.py index 895efab8c5..a3e0890120 100644 --- a/server/python/plugin_remote.py +++ b/server/python/plugin_remote.py @@ -2,10 +2,11 @@ import asyncio import gc +import aiofiles +from aiofiles import os as aios import os import platform import shutil -import subprocess import sys import threading import time @@ -411,18 +412,16 @@ async def resolveObject(id: str, sourcePeerPort: int): return return sourcePeer.localProxyMap.get(id, None) - clusterPeers: Mapping[int, asyncio.Future[rpc.RpcPeer]] = {} + clusterPeers: Mapping[int, rpc.RpcPeer] = {} async def handleClusterClient(reader: asyncio.StreamReader, writer: asyncio.StreamWriter): _, clusterPeerPort = writer.get_extra_info('peername') rpcTransport = rpc_reader.RpcStreamTransport(reader, writer) peer: rpc.RpcPeer - peer, peerReadLoop = await rpc_reader.prepare_peer_readloop(self.loop, rpcTransport) + peer, peerReadLoop = await rpc_reader.prepare_peer_readloop(rpcTransport) peer.onProxySerialization = lambda value, proxyId: onProxySerialization( value, proxyId, clusterPeerPort) - future: asyncio.Future[rpc.RpcPeer] = asyncio.Future() - future.set_result(peer) - clusterPeers[clusterPeerPort] = future + clusterPeers[clusterPeerPort] = peer async def connectRPCObject(id: str, secret: str, sourcePeerPort: int = None): m = hashlib.sha256() @@ -445,16 +444,16 @@ async def connectRPCObject(id: str, secret: str, sourcePeerPort: int = None): clusterRpcServer = await asyncio.start_server(handleClusterClient, '127.0.0.1', 0) clusterPort = clusterRpcServer.sockets[0].getsockname()[1] - def ensureClusterPeer(port: int): - clusterPeerPromise = clusterPeers.get(port) - if not clusterPeerPromise: + async def ensureClusterPeer(port: int): + clusterPeer = clusterPeers.get(port) + if not clusterPeer: async def connectClusterPeer(): reader, writer = await asyncio.open_connection( '127.0.0.1', port) _, clusterPeerPort = writer.get_extra_info('sockname') rpcTransport = rpc_reader.RpcStreamTransport( reader, writer) - clusterPeer, peerReadLoop = await rpc_reader.prepare_peer_readloop(self.loop, rpcTransport) + clusterPeer, peerReadLoop = await rpc_reader.prepare_peer_readloop(rpcTransport) clusterPeer.tags['localPort'] = clusterPeerPort clusterPeer.onProxySerialization = lambda value, proxyId: onProxySerialization( value, proxyId, clusterPeerPort) @@ -466,12 +465,11 @@ async def run_loop(): pass finally: clusterPeers.pop(port) - asyncio.run_coroutine_threadsafe(run_loop(), self.loop) + asyncio.create_task(run_loop()) return clusterPeer - clusterPeerPromise = self.loop.create_task( - connectClusterPeer()) - clusterPeers[port] = clusterPeerPromise - return clusterPeerPromise + clusterPeer = await connectClusterPeer() + clusterPeers[port] = clusterPeer + return clusterPeer async def connectRPCObject(value): clusterObject = getattr(value, '__cluster') @@ -487,10 +485,8 @@ async def connectRPCObject(value): if port == clusterPort: return await resolveObject(proxyId, source) - clusterPeerPromise = ensureClusterPeer(port) - try: - clusterPeer = await clusterPeerPromise + clusterPeer = await ensureClusterPeer(port) if clusterPeer.tags.get('localPort') == source: return value c = await clusterPeer.getParam('connectRPCObject') @@ -520,9 +516,8 @@ async def connectRPCObject(value): shutil.copyfile(zipData, zipPath) else: zipPath = options['filename'] - f = open(zipPath, 'wb') - f.write(zipData) - f.close() + async with aiofiles.open(zipPath, 'wb') as f: + await f.write(zipData) zipData = None @@ -559,8 +554,7 @@ async def connectRPCObject(value): print('python prefix: %s' % python_prefix) - if not os.path.exists(python_prefix): - os.makedirs(python_prefix) + await aios.makedirs(python_prefix, exist_ok=True) if 'requirements.txt' in zip.namelist(): requirements = zip.open('requirements.txt').read() @@ -580,7 +574,7 @@ async def connectRPCObject(value): if need_pip: try: - for de in os.listdir(plugin_volume): + for de in await aios.listdir(plugin_volume): if de.startswith('linux') or de.startswith('darwin') or de.startswith('win32') or de.startswith('python') or de.startswith('node'): filePath = os.path.join(plugin_volume, de) print('Removing old dependencies: %s' % @@ -592,7 +586,7 @@ async def connectRPCObject(value): except: pass - os.makedirs(python_prefix) + await aios.makedirs(python_prefix) print('requirements.txt (outdated)') print(str_requirements) @@ -618,22 +612,23 @@ async def connectRPCObject(value): # force reinstall even if it exists in system packages. pipArgs.append('--force-reinstall') - p = subprocess.Popen(pipArgs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + p = await asyncio.create_subprocess_exec(sys.executable, pipArgs, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) + + # p = subprocess.Popen(pipArgs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: - line = p.stdout.readline() + line = await p.stdout.readline() if not line: break line = line.decode('utf8').rstrip('\r\n') print(line) - result = p.wait() + result = await p.wait() print('pip install result %s' % result) if result: raise Exception('non-zero result from pip %s' % result) - f = open(installed_requirementstxt, 'wb') - f.write(requirements) - f.close() + async with aiofiles.open(installed_requirementstxt, 'wb') as f: + await f.write(requirements) else: print('requirements.txt (up to date)') print(str_requirements) @@ -644,7 +639,7 @@ async def connectRPCObject(value): # TODO: find a way to programatically get this value, or switch to venv. dist_packages = os.path.join( python_prefix, 'local', 'lib', python_version, 'dist-packages') - if os.path.exists(dist_packages): + if await aios.path.exists(dist_packages): site_packages = dist_packages else: site_packages = os.path.join( @@ -694,7 +689,7 @@ def exit_check(): async def getFork(): rpcTransport = rpc_reader.RpcConnectionTransport( parent_conn) - forkPeer, readLoop = await rpc_reader.prepare_peer_readloop(self.loop, rpcTransport) + forkPeer, readLoop = await rpc_reader.prepare_peer_readloop(rpcTransport) forkPeer.peerName = 'thread' async def updateStats(stats): @@ -713,8 +708,7 @@ async def forkReadLoop(): parent_conn.close() rpcTransport.executor.shutdown() pluginFork.worker.kill() - asyncio.run_coroutine_threadsafe( - forkReadLoop(), loop=self.loop) + await forkReadLoop() getRemote = await forkPeer.getParam('getRemote') remote: PluginRemote = await getRemote(self.api, self.pluginId, self.hostInfo) await remote.setSystemState(self.systemManager.getSystemState()) @@ -798,7 +792,7 @@ async def start_stats_runner(self): print('host did not provide update_stats') return - def stats_runner(): + async def stats_runner(): ptime = round(time.process_time() * 1000000) + self.ptimeSum try: import psutil @@ -824,14 +818,14 @@ def stats_runner(): 'heapTotal': heapTotal, }, } - asyncio.run_coroutine_threadsafe(update_stats(stats), self.loop) + await update_stats(stats) self.loop.call_later(10, stats_runner) - stats_runner() + await stats_runner() async def plugin_async_main(loop: AbstractEventLoop, rpcTransport: rpc_reader.RpcTransport): - peer, readLoop = await rpc_reader.prepare_peer_readloop(loop, rpcTransport) + peer, readLoop = await rpc_reader.prepare_peer_readloop(rpcTransport) peer.params['print'] = print peer.params['getRemote'] = lambda api, pluginId, hostInfo: PluginRemote( peer, api, pluginId, hostInfo, loop) diff --git a/server/python/rpc-iterator-test.py b/server/python/rpc-iterator-test.py index ac333aff5f..9738ee7374 100644 --- a/server/python/rpc-iterator-test.py +++ b/server/python/rpc-iterator-test.py @@ -7,7 +7,7 @@ class Bar: pass async def main(): - peer, peerReadLoop = await prepare_peer_readloop(loop, RpcFileTransport(4, 3)) + peer, peerReadLoop = await prepare_peer_readloop(RpcFileTransport(4, 3)) peer.params['foo'] = 3 jsoncopy = {} jsoncopy[rpc.RpcPeer.PROPERTY_JSON_COPY_SERIALIZE_CHILDREN] = True diff --git a/server/python/rpc_reader.py b/server/python/rpc_reader.py index 23e3522795..48bd740f67 100644 --- a/server/python/rpc_reader.py +++ b/server/python/rpc_reader.py @@ -6,8 +6,7 @@ import os import threading from concurrent.futures import ThreadPoolExecutor -from asyncio.events import AbstractEventLoop -from typing import List, Any +from typing import List import multiprocessing.connection import rpc import concurrent.futures @@ -159,7 +158,7 @@ def writeBuffer(self, buffer, reject): return self.writeMessage(bytes(buffer), reject) -async def readLoop(loop, peer: rpc.RpcPeer, rpcTransport: RpcTransport): +async def readLoop(peer: rpc.RpcPeer, rpcTransport: RpcTransport): deserializationContext = { 'buffers': [] } @@ -171,26 +170,23 @@ async def readLoop(loop, peer: rpc.RpcPeer, rpcTransport: RpcTransport): deserializationContext['buffers'].append(message) continue - asyncio.run_coroutine_threadsafe( - peer.handleMessage(message, deserializationContext), loop) + await peer.handleMessage(message, deserializationContext) deserializationContext = { 'buffers': [] } -async def prepare_peer_readloop(loop: AbstractEventLoop, rpcTransport: RpcTransport): +async def prepare_peer_readloop(rpcTransport: RpcTransport): await rpcTransport.prepare() mutex = threading.Lock() def send(message, reject=None, serializationContext=None): with mutex: - if serializationContext: - buffers = serializationContext.get('buffers', None) - if buffers: - for buffer in buffers: - rpcTransport.writeBuffer(buffer, reject) + if serializationContext and (buffers := serializationContext.get('buffers')): + for buffer in buffers: + rpcTransport.writeBuffer(buffer, reject) rpcTransport.writeJSON(message, reject) @@ -202,7 +198,7 @@ def send(message, reject=None, serializationContext=None): async def peerReadLoop(): try: - await readLoop(loop, peer, rpcTransport) + await readLoop(peer, rpcTransport) except: peer.kill() raise From d817fd118baeef462cc5fc75f39448eac7538c3d Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 22 Sep 2023 01:41:00 -0400 Subject: [PATCH 13/15] fix --- packages/python-client/test.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/python-client/test.py b/packages/python-client/test.py index 5a6a7307ac..6c79bcfcce 100644 --- a/packages/python-client/test.py +++ b/packages/python-client/test.py @@ -52,17 +52,6 @@ async def send(): asyncio.create_task(send()) - def writeJSON(self, json, reject): - async def send(): - try: - if self.write_error: - raise self.write_error - self.write_queue.put_nowait(json) - except Exception as e: - reject(e) - - asyncio.run_coroutine_threadsafe(send(), self.loop) - def writeJSON(self, json, reject): return self.writeBuffer(json, reject) From dd30be15c744a2f13cd3cd6502938c6a973df1c0 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 22 Sep 2023 01:45:43 -0400 Subject: [PATCH 14/15] fix --- sdk/types/scrypted_python/scrypted_sdk/types.py | 6 +++--- sdk/types/src/types.input.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/types/scrypted_python/scrypted_sdk/types.py b/sdk/types/scrypted_python/scrypted_sdk/types.py index 1690b6604e..81b26c5e04 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/types.py +++ b/sdk/types/scrypted_python/scrypted_sdk/types.py @@ -1530,7 +1530,7 @@ class SystemManager: async def getComponent(self, id: str) -> Any: pass - async def getDeviceById(self, id: str) -> ScryptedDevice: + def getDeviceById(self, id: str) -> ScryptedDevice: pass def getDeviceByName(self, name: str) -> ScryptedDevice: @@ -1542,10 +1542,10 @@ def getDeviceState(self, id: str) -> Any: def getSystemState(self) -> Any: pass - def listen(self, callback: EventListener) -> EventListenerRegister: + async def listen(self, callback: EventListener) -> EventListenerRegister: pass - def listenDevice(self, id: str, event: str | EventListenerOptions, callback: EventListener) -> EventListenerRegister: + async def listenDevice(self, id: str, event: str | EventListenerOptions, callback: EventListener) -> EventListenerRegister: pass async def removeDevice(self, id: str) -> None: diff --git a/sdk/types/src/types.input.ts b/sdk/types/src/types.input.ts index c6aecc9ad7..905a8c63c9 100644 --- a/sdk/types/src/types.input.ts +++ b/sdk/types/src/types.input.ts @@ -1783,12 +1783,12 @@ export interface SystemManager { /** * Passively (without polling) listen to property changed events. */ - listen(callback: EventListener): EventListenerRegister; + listen(callback: EventListener): Promise; /** * Subscribe to events from a specific interface on a device id, such as 'OnOff' or 'Brightness'. This is a convenience method for ScryptedDevice.listen. */ - listenDevice(id: string, event: ScryptedInterface | string | EventListenerOptions, callback: EventListener): EventListenerRegister; + listenDevice(id: string, event: ScryptedInterface | string | EventListenerOptions, callback: EventListener): Promise /** * Remove a device from Scrypted. Plugins should use DeviceManager.onDevicesChanged or DeviceManager.onDeviceRemoved to remove their own devices From fc27358b7951fda1ab95099295dea2a42d5ee217 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 22 Sep 2023 21:53:55 -0400 Subject: [PATCH 15/15] Revert some changes --- sdk/types/scrypted_python/scrypted_sdk/types.py | 4 ++-- sdk/types/src/types.input.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/types/scrypted_python/scrypted_sdk/types.py b/sdk/types/scrypted_python/scrypted_sdk/types.py index 81b26c5e04..468ff0b3f5 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/types.py +++ b/sdk/types/scrypted_python/scrypted_sdk/types.py @@ -1542,10 +1542,10 @@ def getDeviceState(self, id: str) -> Any: def getSystemState(self) -> Any: pass - async def listen(self, callback: EventListener) -> EventListenerRegister: + def listen(self, callback: EventListener) -> EventListenerRegister: pass - async def listenDevice(self, id: str, event: str | EventListenerOptions, callback: EventListener) -> EventListenerRegister: + def listenDevice(self, id: str, event: str | EventListenerOptions, callback: EventListener) -> EventListenerRegister: pass async def removeDevice(self, id: str) -> None: diff --git a/sdk/types/src/types.input.ts b/sdk/types/src/types.input.ts index 905a8c63c9..c6aecc9ad7 100644 --- a/sdk/types/src/types.input.ts +++ b/sdk/types/src/types.input.ts @@ -1783,12 +1783,12 @@ export interface SystemManager { /** * Passively (without polling) listen to property changed events. */ - listen(callback: EventListener): Promise; + listen(callback: EventListener): EventListenerRegister; /** * Subscribe to events from a specific interface on a device id, such as 'OnOff' or 'Brightness'. This is a convenience method for ScryptedDevice.listen. */ - listenDevice(id: string, event: ScryptedInterface | string | EventListenerOptions, callback: EventListener): Promise + listenDevice(id: string, event: ScryptedInterface | string | EventListenerOptions, callback: EventListener): EventListenerRegister; /** * Remove a device from Scrypted. Plugins should use DeviceManager.onDevicesChanged or DeviceManager.onDeviceRemoved to remove their own devices