calagoapi 1.0.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- calagoapi/__init__.py +190 -0
- calagoapi/account/__init__.py +14 -0
- calagoapi/account/account.py +292 -0
- calagoapi/account/auth.py +72 -0
- calagoapi/account/models.py +221 -0
- calagoapi/admin/__init__.py +48 -0
- calagoapi/admin/admin.py +341 -0
- calagoapi/admin/managers.py +490 -0
- calagoapi/admin/resources.py +970 -0
- calagoapi/aio/__init__.py +9 -0
- calagoapi/aio/console.py +289 -0
- calagoapi/aio/panel.py +176 -0
- calagoapi/api/__init__.py +63 -0
- calagoapi/api/account.py +195 -0
- calagoapi/api/admin.py +402 -0
- calagoapi/api/attachments.py +27 -0
- calagoapi/api/auth.py +52 -0
- calagoapi/api/backups.py +99 -0
- calagoapi/api/client.py +198 -0
- calagoapi/api/databases.py +217 -0
- calagoapi/api/files.py +210 -0
- calagoapi/api/network.py +84 -0
- calagoapi/api/public.py +13 -0
- calagoapi/api/schedules.py +79 -0
- calagoapi/api/servers.py +114 -0
- calagoapi/api/users.py +27 -0
- calagoapi/api/versions.py +29 -0
- calagoapi/backups/__init__.py +8 -0
- calagoapi/backups/backup.py +292 -0
- calagoapi/backups/manager.py +177 -0
- calagoapi/console/__init__.py +6 -0
- calagoapi/console/console.py +392 -0
- calagoapi/databases/__init__.py +12 -0
- calagoapi/databases/database.py +445 -0
- calagoapi/databases/explorer.py +159 -0
- calagoapi/databases/manager.py +122 -0
- calagoapi/files/__init__.py +9 -0
- calagoapi/files/file.py +196 -0
- calagoapi/files/manager.py +437 -0
- calagoapi/network/__init__.py +8 -0
- calagoapi/network/allocation.py +99 -0
- calagoapi/network/manager.py +132 -0
- calagoapi/nodes/__init__.py +6 -0
- calagoapi/nodes/node.py +477 -0
- calagoapi/panel.py +162 -0
- calagoapi/schedules/__init__.py +14 -0
- calagoapi/schedules/builders.py +461 -0
- calagoapi/schedules/manager.py +107 -0
- calagoapi/schedules/schedule.py +214 -0
- calagoapi/servers/__init__.py +13 -0
- calagoapi/servers/admin.py +430 -0
- calagoapi/servers/generic.py +785 -0
- calagoapi/servers/manager.py +167 -0
- calagoapi/servers/minecraft.py +207 -0
- calagoapi/tunnels/__init__.py +5 -0
- calagoapi/tunnels/tunnel.py +112 -0
- calagoapi/users/__init__.py +11 -0
- calagoapi/users/activity.py +45 -0
- calagoapi/users/subuser.py +127 -0
- calagoapi/users/user.py +267 -0
- calagoapi/utils/__init__.py +43 -0
- calagoapi/utils/convert.py +113 -0
- calagoapi/utils/exceptions.py +72 -0
- calagoapi/utils/model.py +142 -0
- calagoapi/utils/resource.py +100 -0
- calagoapi/utils/time.py +68 -0
- calagoapi-1.0.dev1.dist-info/METADATA +266 -0
- calagoapi-1.0.dev1.dist-info/RECORD +71 -0
- calagoapi-1.0.dev1.dist-info/WHEEL +5 -0
- calagoapi-1.0.dev1.dist-info/licenses/LICENSE +674 -0
- calagoapi-1.0.dev1.dist-info/top_level.txt +1 -0
calagoapi/aio/console.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from websockets.asyncio.client import connect
|
|
6
|
+
from websockets.exceptions import ConnectionClosed, WebSocketException
|
|
7
|
+
|
|
8
|
+
from calagoapi.console.console import (
|
|
9
|
+
ConsoleEvent,
|
|
10
|
+
socketUrl,
|
|
11
|
+
sslContext,
|
|
12
|
+
encode,
|
|
13
|
+
decode,
|
|
14
|
+
logger,
|
|
15
|
+
POWER_ACTIONS,
|
|
16
|
+
TOKEN_EVENTS,
|
|
17
|
+
ERROR_EVENTS,
|
|
18
|
+
)
|
|
19
|
+
from calagoapi.utils.exceptions import CalagoAPIError, ConsoleError, InvalidActionError, WaitTimeoutError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AsyncConsole:
|
|
23
|
+
'''
|
|
24
|
+
The live console of a server for asyncio code, available as `server.console`
|
|
25
|
+
on servers from AsyncPanel.
|
|
26
|
+
|
|
27
|
+
Same methods as Console, but awaitable. Callbacks may be plain functions
|
|
28
|
+
or coroutines.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
async with server.console as console:
|
|
32
|
+
await console.sendCommand('list')
|
|
33
|
+
async for line in console.lines():
|
|
34
|
+
print(line)
|
|
35
|
+
|
|
36
|
+
server.console.onConsoleLine(print)
|
|
37
|
+
await server.console.listen()
|
|
38
|
+
...
|
|
39
|
+
await server.console.stop()
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
server: The (regular) GenericServer whose console to open.
|
|
43
|
+
'''
|
|
44
|
+
def __init__(self, server):
|
|
45
|
+
self.server = server
|
|
46
|
+
self.socket = None
|
|
47
|
+
self.permissions = []
|
|
48
|
+
self.handlers = {}
|
|
49
|
+
self.running = False
|
|
50
|
+
self.task = None
|
|
51
|
+
|
|
52
|
+
def __repr__(self):
|
|
53
|
+
state = 'connected' if self.isConnected() else 'closed'
|
|
54
|
+
return f'<AsyncConsole {self.server.name!r} {state}>'
|
|
55
|
+
|
|
56
|
+
async def __aenter__(self):
|
|
57
|
+
return await self.connect()
|
|
58
|
+
|
|
59
|
+
async def __aexit__(self, *exc):
|
|
60
|
+
await self.stop()
|
|
61
|
+
|
|
62
|
+
# connection
|
|
63
|
+
async def connect(self, timeout: int = 10) -> 'AsyncConsole':
|
|
64
|
+
'''
|
|
65
|
+
Opens the websocket and authenticates. Does nothing if already connected.
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
ConsoleError: The daemon could not be reached or refused the token.
|
|
69
|
+
'''
|
|
70
|
+
if self.socket:
|
|
71
|
+
return self
|
|
72
|
+
credentials = await asyncio.to_thread(self.server.getWebsocket)
|
|
73
|
+
url = socketUrl(credentials['url'])
|
|
74
|
+
try:
|
|
75
|
+
self.socket = await connect(
|
|
76
|
+
url,
|
|
77
|
+
origin=self.server.panel.url,
|
|
78
|
+
ssl=sslContext(url, self.server.panel.client.session.verify),
|
|
79
|
+
open_timeout=timeout,
|
|
80
|
+
user_agent_header='CalagoAPI',
|
|
81
|
+
)
|
|
82
|
+
except (OSError, WebSocketException, asyncio.TimeoutError) as e:
|
|
83
|
+
raise ConsoleError(f'Could not connect to the console of {self.server.name}: {e}') from e
|
|
84
|
+
self.server.panel.consoles.add(self)
|
|
85
|
+
|
|
86
|
+
await self._send('auth', credentials['token'])
|
|
87
|
+
deadline = time.monotonic() + timeout
|
|
88
|
+
while True:
|
|
89
|
+
event = await self._receive(max(deadline - time.monotonic(), 0.01))
|
|
90
|
+
if event is None:
|
|
91
|
+
await self.close()
|
|
92
|
+
raise ConsoleError(f'The console of {self.server.name} did not confirm authentication')
|
|
93
|
+
if event.getEvent() == 'auth success':
|
|
94
|
+
self.permissions = event.getArgs()
|
|
95
|
+
return self
|
|
96
|
+
if event.getEvent() in ERROR_EVENTS:
|
|
97
|
+
await self.close()
|
|
98
|
+
raise ConsoleError(f'Console authentication failed: {event.getArg()}')
|
|
99
|
+
|
|
100
|
+
async def close(self) -> None:
|
|
101
|
+
'''Closes the websocket. Use stop() to also end a running listen().'''
|
|
102
|
+
socket, self.socket = self.socket, None
|
|
103
|
+
self.server.panel.consoles.discard(self)
|
|
104
|
+
if socket:
|
|
105
|
+
await socket.close()
|
|
106
|
+
|
|
107
|
+
def isConnected(self) -> bool:
|
|
108
|
+
'''Returns True while the websocket is open.'''
|
|
109
|
+
return self.socket is not None
|
|
110
|
+
|
|
111
|
+
def getPermissions(self) -> list:
|
|
112
|
+
'''Returns the console permissions the daemon granted.'''
|
|
113
|
+
return self.permissions
|
|
114
|
+
|
|
115
|
+
# sending
|
|
116
|
+
async def _send(self, event, *args):
|
|
117
|
+
if not self.socket:
|
|
118
|
+
raise ConsoleError('The console is not connected, call connect() first')
|
|
119
|
+
try:
|
|
120
|
+
await self.socket.send(encode(event, *args))
|
|
121
|
+
except ConnectionClosed as e:
|
|
122
|
+
self.socket = None
|
|
123
|
+
raise ConsoleError(f'The console connection was closed: {e}') from e
|
|
124
|
+
|
|
125
|
+
async def sendCommand(self, command: str) -> None:
|
|
126
|
+
'''Sends a console command over the websocket.'''
|
|
127
|
+
await self._send('send command', command)
|
|
128
|
+
|
|
129
|
+
async def setState(self, action: str) -> None:
|
|
130
|
+
'''Sends a power action: start, restart, stop or kill.'''
|
|
131
|
+
if action not in POWER_ACTIONS:
|
|
132
|
+
raise InvalidActionError(f'Invalid power action "{action}", expected one of: {", ".join(POWER_ACTIONS)}')
|
|
133
|
+
await self._send('set state', action)
|
|
134
|
+
|
|
135
|
+
async def requestLogs(self) -> None:
|
|
136
|
+
'''Asks the daemon to resend recent console lines.'''
|
|
137
|
+
await self._send('send logs')
|
|
138
|
+
|
|
139
|
+
async def requestStats(self) -> None:
|
|
140
|
+
'''Asks the daemon for resource usage now.'''
|
|
141
|
+
await self._send('send stats')
|
|
142
|
+
|
|
143
|
+
async def requestStatus(self) -> None:
|
|
144
|
+
'''Asks the daemon for the power state now.'''
|
|
145
|
+
await self._send('send status')
|
|
146
|
+
|
|
147
|
+
# receiving
|
|
148
|
+
async def _receive(self, timeout=None):
|
|
149
|
+
if not self.socket:
|
|
150
|
+
return None
|
|
151
|
+
try:
|
|
152
|
+
raw = await asyncio.wait_for(self.socket.recv(), timeout)
|
|
153
|
+
except asyncio.TimeoutError:
|
|
154
|
+
return None
|
|
155
|
+
except ConnectionClosed:
|
|
156
|
+
await self.close()
|
|
157
|
+
return None
|
|
158
|
+
return decode(raw) or ConsoleEvent('unknown')
|
|
159
|
+
|
|
160
|
+
async def _renewToken(self):
|
|
161
|
+
credentials = await asyncio.to_thread(self.server.getWebsocket)
|
|
162
|
+
await self._send('auth', credentials['token'])
|
|
163
|
+
|
|
164
|
+
async def events(self, timeout: float = None):
|
|
165
|
+
'''
|
|
166
|
+
Yields every event from the daemon until the connection closes.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
timeout: Stop when no event arrives for this many seconds. None waits forever.
|
|
170
|
+
'''
|
|
171
|
+
while self.socket:
|
|
172
|
+
event = await self._receive(timeout)
|
|
173
|
+
if event is None:
|
|
174
|
+
return
|
|
175
|
+
if event.getEvent() in TOKEN_EVENTS:
|
|
176
|
+
await self._renewToken()
|
|
177
|
+
continue
|
|
178
|
+
yield event
|
|
179
|
+
|
|
180
|
+
async def lines(self, history: bool = True, timeout: float = None):
|
|
181
|
+
'''
|
|
182
|
+
Yields console lines as they are printed.
|
|
183
|
+
|
|
184
|
+
Example:
|
|
185
|
+
async for line in console.lines(history=False):
|
|
186
|
+
print(line)
|
|
187
|
+
'''
|
|
188
|
+
await self.connect()
|
|
189
|
+
if history:
|
|
190
|
+
await self.requestLogs()
|
|
191
|
+
async for event in self.events(timeout):
|
|
192
|
+
if event.getEvent() == 'console output':
|
|
193
|
+
yield event.getArg(0, '')
|
|
194
|
+
|
|
195
|
+
async def waitFor(self, text: str, timeout: float = 60) -> str:
|
|
196
|
+
'''
|
|
197
|
+
Waits until a console line contains `text` and returns that line.
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
WaitTimeoutError: No matching line within `timeout` seconds.
|
|
201
|
+
'''
|
|
202
|
+
await self.connect()
|
|
203
|
+
deadline = time.monotonic() + timeout
|
|
204
|
+
while True:
|
|
205
|
+
remaining = deadline - time.monotonic()
|
|
206
|
+
event = await self._receive(remaining) if remaining > 0 else None
|
|
207
|
+
if event is None:
|
|
208
|
+
raise WaitTimeoutError(f'No console line containing {text!r} within {timeout}s')
|
|
209
|
+
if event.getEvent() in TOKEN_EVENTS:
|
|
210
|
+
await self._renewToken()
|
|
211
|
+
elif event.getEvent() == 'console output' and text in event.getArg(0, ''):
|
|
212
|
+
return event.getArg()
|
|
213
|
+
|
|
214
|
+
# callbacks
|
|
215
|
+
def on(self, event: str, callback) -> 'AsyncConsole':
|
|
216
|
+
'''Calls `callback(ConsoleEvent)` for an event while listen() runs. Use '*' for every event.'''
|
|
217
|
+
self.handlers.setdefault(event, []).append(callback)
|
|
218
|
+
return self
|
|
219
|
+
|
|
220
|
+
def onConsoleLine(self, callback) -> 'AsyncConsole':
|
|
221
|
+
'''Calls `callback(line)` for every console line.'''
|
|
222
|
+
return self.on('console output', lambda event: callback(event.getArg(0, '')))
|
|
223
|
+
|
|
224
|
+
def onStatus(self, callback) -> 'AsyncConsole':
|
|
225
|
+
'''Calls `callback(state)` when the power state changes.'''
|
|
226
|
+
return self.on('status', lambda event: callback(event.getArg()))
|
|
227
|
+
|
|
228
|
+
def onStats(self, callback) -> 'AsyncConsole':
|
|
229
|
+
'''Calls `callback(resources)` with a resource usage dict.'''
|
|
230
|
+
return self.on('stats', lambda event: callback(event.getData()))
|
|
231
|
+
|
|
232
|
+
async def _dispatch(self, event):
|
|
233
|
+
for callback in self.handlers.get(event.getEvent(), []) + self.handlers.get('*', []):
|
|
234
|
+
try:
|
|
235
|
+
result = callback(event)
|
|
236
|
+
if inspect.isawaitable(result):
|
|
237
|
+
await result
|
|
238
|
+
except Exception:
|
|
239
|
+
logger.exception('console callback for %r failed', event.getEvent())
|
|
240
|
+
|
|
241
|
+
async def listen(self, background: bool = True, history: bool = False, reconnect: bool = True) -> 'AsyncConsole':
|
|
242
|
+
'''
|
|
243
|
+
Delivers events to the registered callbacks.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
background: Run as an asyncio task and return immediately. False waits until stop().
|
|
247
|
+
history: Replay recent console lines first.
|
|
248
|
+
reconnect: Reconnect automatically (with backoff) if the connection drops.
|
|
249
|
+
'''
|
|
250
|
+
if self.running:
|
|
251
|
+
return self
|
|
252
|
+
await self.connect()
|
|
253
|
+
self.running = True
|
|
254
|
+
if background:
|
|
255
|
+
self.task = asyncio.create_task(self._loop(history, reconnect))
|
|
256
|
+
else:
|
|
257
|
+
await self._loop(history, reconnect)
|
|
258
|
+
return self
|
|
259
|
+
|
|
260
|
+
async def _loop(self, history, reconnect):
|
|
261
|
+
delay = 1
|
|
262
|
+
while self.running:
|
|
263
|
+
try:
|
|
264
|
+
await self.connect()
|
|
265
|
+
delay = 1
|
|
266
|
+
if history:
|
|
267
|
+
await self.requestLogs()
|
|
268
|
+
history = False
|
|
269
|
+
async for event in self.events():
|
|
270
|
+
await self._dispatch(event)
|
|
271
|
+
except CalagoAPIError as e:
|
|
272
|
+
logger.warning('console of %s disconnected: %s', self.server.name, e)
|
|
273
|
+
if not self.running or not reconnect:
|
|
274
|
+
break
|
|
275
|
+
await asyncio.sleep(delay)
|
|
276
|
+
delay = min(delay * 2, 30)
|
|
277
|
+
self.running = False
|
|
278
|
+
|
|
279
|
+
async def stop(self) -> None:
|
|
280
|
+
'''Stops listen() and closes the connection.'''
|
|
281
|
+
self.running = False
|
|
282
|
+
await self.close()
|
|
283
|
+
task, self.task = self.task, None
|
|
284
|
+
if task and task is not asyncio.current_task():
|
|
285
|
+
task.cancel()
|
|
286
|
+
try:
|
|
287
|
+
await task
|
|
288
|
+
except asyncio.CancelledError:
|
|
289
|
+
pass
|
calagoapi/aio/panel.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import functools
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
from calagoapi.panel import Panel
|
|
6
|
+
from calagoapi.console import Console
|
|
7
|
+
from calagoapi.servers.generic import GenericServer
|
|
8
|
+
from calagoapi.utils.model import Page
|
|
9
|
+
from calagoapi.aio.console import AsyncConsole
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def wrap(value):
|
|
13
|
+
'''Wraps CalagoAPI objects returned by a call so their methods are awaitable too'''
|
|
14
|
+
if isinstance(value, Page):
|
|
15
|
+
return value.map(wrap)
|
|
16
|
+
if isinstance(value, list):
|
|
17
|
+
return [wrap(v) for v in value]
|
|
18
|
+
if isinstance(value, GenericServer):
|
|
19
|
+
return AsyncServer(value)
|
|
20
|
+
if isinstance(value, Console):
|
|
21
|
+
return AsyncConsole(value.server)
|
|
22
|
+
if type(value).__module__.startswith('calagoapi.') and not isinstance(value, BaseException):
|
|
23
|
+
return AsyncWrapper(value)
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def unwrap(value):
|
|
28
|
+
'''Turns wrapped objects passed as arguments back into the regular objects'''
|
|
29
|
+
if isinstance(value, AsyncWrapper):
|
|
30
|
+
return value.target
|
|
31
|
+
if isinstance(value, list):
|
|
32
|
+
return [unwrap(v) for v in value]
|
|
33
|
+
if isinstance(value, tuple):
|
|
34
|
+
return tuple(unwrap(v) for v in value)
|
|
35
|
+
if isinstance(value, dict):
|
|
36
|
+
return {unwrap(k): unwrap(v) for k, v in value.items()}
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AsyncWrapper:
|
|
41
|
+
'''
|
|
42
|
+
The async version of any CalagoAPI object.
|
|
43
|
+
|
|
44
|
+
Every method of the wrapped object becomes a coroutine with the same name,
|
|
45
|
+
arguments and docstring. Calls run in a worker thread, so many can run at
|
|
46
|
+
once with asyncio.gather(). Plain attributes (like `server.name`) are read
|
|
47
|
+
directly. Objects returned by a method are wrapped as well.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
backups = await server.backups.getBackups()
|
|
51
|
+
await backups[0].lock()
|
|
52
|
+
print(await backups[0].getName()) # getters are awaitable too
|
|
53
|
+
print(backups[0].data['name']) # plain attributes are not
|
|
54
|
+
'''
|
|
55
|
+
def __init__(self, target):
|
|
56
|
+
object.__setattr__(self, 'target', target)
|
|
57
|
+
|
|
58
|
+
def __getattr__(self, name):
|
|
59
|
+
value = getattr(self.target, name)
|
|
60
|
+
if not inspect.ismethod(value):
|
|
61
|
+
return wrap(value)
|
|
62
|
+
|
|
63
|
+
@functools.wraps(value)
|
|
64
|
+
async def method(*args, **kwargs):
|
|
65
|
+
args = [unwrap(a) for a in args]
|
|
66
|
+
kwargs = {k: unwrap(v) for k, v in kwargs.items()}
|
|
67
|
+
return wrap(await asyncio.to_thread(value, *args, **kwargs))
|
|
68
|
+
|
|
69
|
+
return method
|
|
70
|
+
|
|
71
|
+
def __setattr__(self, name, value):
|
|
72
|
+
setattr(self.target, name, value)
|
|
73
|
+
|
|
74
|
+
def __getitem__(self, key):
|
|
75
|
+
return self.target[key]
|
|
76
|
+
|
|
77
|
+
def __contains__(self, key):
|
|
78
|
+
return key in self.target
|
|
79
|
+
|
|
80
|
+
def __eq__(self, other):
|
|
81
|
+
return self.target == unwrap(other)
|
|
82
|
+
|
|
83
|
+
def __hash__(self):
|
|
84
|
+
return hash(self.target)
|
|
85
|
+
|
|
86
|
+
def __repr__(self):
|
|
87
|
+
return f'<async {self.target!r}>'
|
|
88
|
+
|
|
89
|
+
def __str__(self):
|
|
90
|
+
return str(self.target)
|
|
91
|
+
|
|
92
|
+
def __dir__(self):
|
|
93
|
+
return sorted(set(dir(self.target)) | set(super().__dir__()))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class AsyncServer(AsyncWrapper):
|
|
97
|
+
'''
|
|
98
|
+
The async version of GenericServer / MinecraftServer.
|
|
99
|
+
|
|
100
|
+
Every server method is awaitable, and `server.console` is an AsyncConsole.
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
server = await panel.servers.getServer('abc123')
|
|
104
|
+
await server.restart()
|
|
105
|
+
async for line in server.streamConsole():
|
|
106
|
+
print(line)
|
|
107
|
+
'''
|
|
108
|
+
def __init__(self, server):
|
|
109
|
+
super().__init__(server)
|
|
110
|
+
object.__setattr__(self, 'console', AsyncConsole(server))
|
|
111
|
+
|
|
112
|
+
async def streamConsole(self, history: bool = True, timeout: float = None):
|
|
113
|
+
'''
|
|
114
|
+
Yields console lines live, as they are printed.
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
async for line in server.streamConsole(history=False):
|
|
118
|
+
print(line)
|
|
119
|
+
'''
|
|
120
|
+
async with AsyncConsole(self.target) as console:
|
|
121
|
+
async for line in console.lines(history, timeout):
|
|
122
|
+
yield line
|
|
123
|
+
|
|
124
|
+
async def onConsoleLine(self, callback, history: bool = False) -> AsyncConsole:
|
|
125
|
+
'''
|
|
126
|
+
Calls `callback(line)` for every console line in a background task.
|
|
127
|
+
The callback may be a plain function or a coroutine.
|
|
128
|
+
|
|
129
|
+
Example:
|
|
130
|
+
await server.onConsoleLine(print)
|
|
131
|
+
'''
|
|
132
|
+
self.console.onConsoleLine(callback)
|
|
133
|
+
return await self.console.listen(history=history)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class AsyncPanel(AsyncWrapper):
|
|
137
|
+
'''
|
|
138
|
+
The async version of Panel, for asyncio applications such as Discord bots.
|
|
139
|
+
|
|
140
|
+
Takes the same arguments as Panel. Every method of every object is
|
|
141
|
+
awaitable and keeps its name, so the documentation of the regular API
|
|
142
|
+
applies unchanged, just add `await`.
|
|
143
|
+
|
|
144
|
+
Requests run in worker threads on top of the regular client, so slow
|
|
145
|
+
calls never block the event loop and independent calls run concurrently.
|
|
146
|
+
The live console uses a native asyncio websocket.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
import asyncio
|
|
150
|
+
from calagoapi import AsyncPanel
|
|
151
|
+
|
|
152
|
+
async def main():
|
|
153
|
+
async with AsyncPanel(url='https://panel.example.com', api_key='...') as panel:
|
|
154
|
+
servers = await panel.servers.getServers()
|
|
155
|
+
statuses = await asyncio.gather(*(server.getStatus() for server in servers))
|
|
156
|
+
for server, status in zip(servers, statuses):
|
|
157
|
+
print(server.name, status)
|
|
158
|
+
|
|
159
|
+
asyncio.run(main())
|
|
160
|
+
'''
|
|
161
|
+
def __init__(self, url: str, api_key: str, name: str = None, timeout: int = 10, verify: bool = True,
|
|
162
|
+
max_retries: int = 3):
|
|
163
|
+
super().__init__(Panel(url, api_key, name, timeout, verify, max_retries))
|
|
164
|
+
|
|
165
|
+
async def __aenter__(self):
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
async def __aexit__(self, *exc):
|
|
169
|
+
await self.close()
|
|
170
|
+
|
|
171
|
+
async def close(self) -> None:
|
|
172
|
+
'''Closes open consoles and the pooled HTTP connections.'''
|
|
173
|
+
for console in list(self.target.consoles):
|
|
174
|
+
if inspect.iscoroutinefunction(console.close):
|
|
175
|
+
await console.close()
|
|
176
|
+
await asyncio.to_thread(self.target.close)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from .client import Client
|
|
2
|
+
from .servers import ServersAPI
|
|
3
|
+
from .files import FilesAPI
|
|
4
|
+
from .backups import BackupsAPI
|
|
5
|
+
from .databases import DatabasesAPI
|
|
6
|
+
from .schedules import SchedulesAPI
|
|
7
|
+
from .network import NetworkAPI
|
|
8
|
+
from .users import UsersAPI
|
|
9
|
+
from .attachments import AttachmentsAPI
|
|
10
|
+
from .versions import VersionsAPI
|
|
11
|
+
from .account import AccountAPI
|
|
12
|
+
from .auth import AuthAPI
|
|
13
|
+
from .public import PublicAPI
|
|
14
|
+
from .admin import AdminAPI, ResourceAPI
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class API:
|
|
18
|
+
'''
|
|
19
|
+
Bundle of every internal API module, available as `panel.api`.
|
|
20
|
+
|
|
21
|
+
These modules are the only place CalagoAPI talks HTTP. You normally
|
|
22
|
+
never need them, but they are handy for calls the high-level objects
|
|
23
|
+
do not wrap yet.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
panel.api.servers.resources(server_id)
|
|
27
|
+
panel.api.client.get('/api/client/permissions')
|
|
28
|
+
'''
|
|
29
|
+
def __init__(self, client: Client):
|
|
30
|
+
self.client = client
|
|
31
|
+
self.servers = ServersAPI(client)
|
|
32
|
+
self.files = FilesAPI(client)
|
|
33
|
+
self.backups = BackupsAPI(client)
|
|
34
|
+
self.databases = DatabasesAPI(client)
|
|
35
|
+
self.schedules = SchedulesAPI(client)
|
|
36
|
+
self.network = NetworkAPI(client)
|
|
37
|
+
self.users = UsersAPI(client)
|
|
38
|
+
self.attachments = AttachmentsAPI(client)
|
|
39
|
+
self.versions = VersionsAPI(client)
|
|
40
|
+
self.account = AccountAPI(client)
|
|
41
|
+
self.auth = AuthAPI(client)
|
|
42
|
+
self.public = PublicAPI(client)
|
|
43
|
+
self.admin = AdminAPI(client)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
'API',
|
|
48
|
+
'Client',
|
|
49
|
+
'ServersAPI',
|
|
50
|
+
'FilesAPI',
|
|
51
|
+
'BackupsAPI',
|
|
52
|
+
'DatabasesAPI',
|
|
53
|
+
'SchedulesAPI',
|
|
54
|
+
'NetworkAPI',
|
|
55
|
+
'UsersAPI',
|
|
56
|
+
'AttachmentsAPI',
|
|
57
|
+
'VersionsAPI',
|
|
58
|
+
'AccountAPI',
|
|
59
|
+
'AuthAPI',
|
|
60
|
+
'PublicAPI',
|
|
61
|
+
'AdminAPI',
|
|
62
|
+
'ResourceAPI',
|
|
63
|
+
]
|