forked from ImSwitch/ImSwitch
-
Notifications
You must be signed in to change notification settings - Fork 15
/
api.py
103 lines (79 loc) · 3.45 KB
/
api.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import inspect
import asyncio
from imswitch import IS_HEADLESS
from imswitch.imcommon.framework import Mutex, Signal, SignalInterface
class APIExport:
""" Decorator for methods that should be exported to API. """
def __init__(self, *, runOnUIThread=False, asyncExecution=False):
self._APIExport = True
self._APIRunOnUIThread = runOnUIThread
self._APIAsyncExecution = asyncExecution
def __call__(self, func):
func._APIExport = self._APIExport
func._APIRunOnUIThread = self._APIRunOnUIThread
func._APIAsyncExecution = self._APIAsyncExecution
return func
def generateAPI(objs, *, missingAttributeErrorMsg=None):
""" Generates an API from APIExport-decorated methods in the objects in the
passed array objs. Must be called from the main thread. """
from imswitch.imcommon.model import pythontools
exportedFuncs = {}
for obj in objs:
for subObjName in dir(obj):
subObj = getattr(obj, subObjName)
if not callable(subObj):
continue
if not hasattr(subObj, '_APIExport') or not subObj._APIExport:
continue
if subObjName in exportedFuncs:
raise NameError(f'API method name "{subObjName}" is already in use')
runOnUIThread = hasattr(subObj, '_APIRunOnUIThread') and subObj._APIRunOnUIThread
if runOnUIThread and not IS_HEADLESS:
wrapper = _UIThreadExecWrapper(subObj)
exportedFuncs[subObjName] = wrapper
wrapper.module = subObj.__module__.split('.')[-1]
else:
exportedFuncs[subObjName] = subObj
return pythontools.dictToROClass(exportedFuncs,
missingAttributeErrorMsg=missingAttributeErrorMsg)
class _UIThreadExecWrapper(SignalInterface):
""" Wrapper for executing the specified function on the UI thread. """
wrappingSignal = Signal()
def __init__(self, apiFunc):
super().__init__()
self.__name__ = apiFunc.__name__
self.__signature__ = inspect.signature(apiFunc)
self.__doc__ = apiFunc.__doc__
self._apiFunc = apiFunc
self._execMutex = Mutex()
self.wrappingSignal.connect(self._apiCall)
def __call__(self, *args, **kwargs):
self._execMutex.lock()
self._args = args
self._kwargs = kwargs
self.wrappingSignal.emit()
def _apiCall(self):
try:
if asyncio.iscoroutinefunction(self._apiFunc):
self._execAsync()
else:
self._apiFunc(*self._args, **self._kwargs)
finally:
self._execMutex.unlock()
async def _execAsync(self):
await self._apiFunc(*self._args, **self._kwargs)
# Copyright (C) 2020-2023 ImSwitch developers
# This file is part of ImSwitch.
#
# ImSwitch is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ImSwitch is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.