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.
Files changed (71) hide show
  1. calagoapi/__init__.py +190 -0
  2. calagoapi/account/__init__.py +14 -0
  3. calagoapi/account/account.py +292 -0
  4. calagoapi/account/auth.py +72 -0
  5. calagoapi/account/models.py +221 -0
  6. calagoapi/admin/__init__.py +48 -0
  7. calagoapi/admin/admin.py +341 -0
  8. calagoapi/admin/managers.py +490 -0
  9. calagoapi/admin/resources.py +970 -0
  10. calagoapi/aio/__init__.py +9 -0
  11. calagoapi/aio/console.py +289 -0
  12. calagoapi/aio/panel.py +176 -0
  13. calagoapi/api/__init__.py +63 -0
  14. calagoapi/api/account.py +195 -0
  15. calagoapi/api/admin.py +402 -0
  16. calagoapi/api/attachments.py +27 -0
  17. calagoapi/api/auth.py +52 -0
  18. calagoapi/api/backups.py +99 -0
  19. calagoapi/api/client.py +198 -0
  20. calagoapi/api/databases.py +217 -0
  21. calagoapi/api/files.py +210 -0
  22. calagoapi/api/network.py +84 -0
  23. calagoapi/api/public.py +13 -0
  24. calagoapi/api/schedules.py +79 -0
  25. calagoapi/api/servers.py +114 -0
  26. calagoapi/api/users.py +27 -0
  27. calagoapi/api/versions.py +29 -0
  28. calagoapi/backups/__init__.py +8 -0
  29. calagoapi/backups/backup.py +292 -0
  30. calagoapi/backups/manager.py +177 -0
  31. calagoapi/console/__init__.py +6 -0
  32. calagoapi/console/console.py +392 -0
  33. calagoapi/databases/__init__.py +12 -0
  34. calagoapi/databases/database.py +445 -0
  35. calagoapi/databases/explorer.py +159 -0
  36. calagoapi/databases/manager.py +122 -0
  37. calagoapi/files/__init__.py +9 -0
  38. calagoapi/files/file.py +196 -0
  39. calagoapi/files/manager.py +437 -0
  40. calagoapi/network/__init__.py +8 -0
  41. calagoapi/network/allocation.py +99 -0
  42. calagoapi/network/manager.py +132 -0
  43. calagoapi/nodes/__init__.py +6 -0
  44. calagoapi/nodes/node.py +477 -0
  45. calagoapi/panel.py +162 -0
  46. calagoapi/schedules/__init__.py +14 -0
  47. calagoapi/schedules/builders.py +461 -0
  48. calagoapi/schedules/manager.py +107 -0
  49. calagoapi/schedules/schedule.py +214 -0
  50. calagoapi/servers/__init__.py +13 -0
  51. calagoapi/servers/admin.py +430 -0
  52. calagoapi/servers/generic.py +785 -0
  53. calagoapi/servers/manager.py +167 -0
  54. calagoapi/servers/minecraft.py +207 -0
  55. calagoapi/tunnels/__init__.py +5 -0
  56. calagoapi/tunnels/tunnel.py +112 -0
  57. calagoapi/users/__init__.py +11 -0
  58. calagoapi/users/activity.py +45 -0
  59. calagoapi/users/subuser.py +127 -0
  60. calagoapi/users/user.py +267 -0
  61. calagoapi/utils/__init__.py +43 -0
  62. calagoapi/utils/convert.py +113 -0
  63. calagoapi/utils/exceptions.py +72 -0
  64. calagoapi/utils/model.py +142 -0
  65. calagoapi/utils/resource.py +100 -0
  66. calagoapi/utils/time.py +68 -0
  67. calagoapi-1.0.dev1.dist-info/METADATA +266 -0
  68. calagoapi-1.0.dev1.dist-info/RECORD +71 -0
  69. calagoapi-1.0.dev1.dist-info/WHEEL +5 -0
  70. calagoapi-1.0.dev1.dist-info/licenses/LICENSE +674 -0
  71. calagoapi-1.0.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,445 @@
1
+ import os
2
+
3
+ from calagoapi.utils.model import Model, idOf
4
+ from calagoapi.utils.convert import formatBytes
5
+ from calagoapi.databases.explorer import DatabaseExplorer
6
+
7
+
8
+ class Database(Model):
9
+ '''
10
+ A database on a database host, attached to a server.
11
+
12
+ Args:
13
+ server: The owning server.
14
+ data: Raw database data from Calagopus.
15
+
16
+ Example:
17
+ db = server.createDatabase('stats')
18
+ print(db.getHost(), db.getPort(), db.getUsername(), db.getPassword())
19
+ print(db.getConnectionString())
20
+ db.explorer.query('SELECT 1')
21
+ '''
22
+ def __init__(self, server, data: dict):
23
+ super().__init__(server.panel, data)
24
+ self.server = server
25
+ self.explorer = DatabaseExplorer(self.panel, self.panel.api.databases.databasePath(server.server_id, self.getUUID()))
26
+
27
+ # getters
28
+ def getName(self) -> str:
29
+ '''Returns the database name.'''
30
+ return self.data.get('name')
31
+
32
+ def getType(self) -> str:
33
+ '''Returns the database type: mysql, postgres or mongodb.'''
34
+ return self.data.get('type')
35
+
36
+ def getHost(self) -> str:
37
+ '''Returns the hostname to connect to.'''
38
+ return self.data.get('host')
39
+
40
+ def getPort(self) -> int:
41
+ '''Returns the port to connect to.'''
42
+ return self.data.get('port')
43
+
44
+ def getAddress(self) -> str:
45
+ '''Returns "host:port".'''
46
+ return f'{self.getHost()}:{self.getPort()}'
47
+
48
+ def getUsername(self) -> str:
49
+ '''Returns the database username.'''
50
+ return self.data.get('username')
51
+
52
+ def getPassword(self) -> str:
53
+ '''
54
+ Returns the password.
55
+
56
+ Only available when the database was fetched with include_password=True,
57
+ right after creation, or after rotatePassword().
58
+ '''
59
+ return self.data.get('password')
60
+
61
+ def getConnectionString(self) -> str:
62
+ '''
63
+ Returns a connection URI like "mysql://user:pass@host:3306/name".
64
+
65
+ The password is omitted if it is not known.
66
+ '''
67
+ scheme = {'postgres': 'postgresql'}.get(self.getType(), self.getType())
68
+ auth = self.getUsername()
69
+ if self.getPassword():
70
+ auth = f'{auth}:{self.getPassword()}'
71
+ return f'{scheme}://{auth}@{self.getAddress()}/{self.getName()}'
72
+
73
+ def getSize(self, formatted: bool = False):
74
+ '''Returns the current database size in bytes (or formatted with formatted=True).'''
75
+ size = self.panel.api.databases.size(self.server.server_id, self.getUUID())
76
+ return formatBytes(size) if formatted else size
77
+
78
+ def isLocked(self) -> bool:
79
+ '''Returns True if the database is locked against deletion.'''
80
+ return bool(self.data.get('is_locked'))
81
+
82
+ # actions
83
+ def refresh(self, include_password: bool = False) -> 'Database':
84
+ '''Re-fetches the database from the panel.'''
85
+ self.data = self.panel.api.databases.get(self.server.server_id, self.getUUID(), include_password or None)
86
+ return self
87
+
88
+ def rotatePassword(self) -> str:
89
+ '''
90
+ Generates a new password.
91
+
92
+ Returns:
93
+ The new password.
94
+ '''
95
+ password = self.panel.api.databases.rotatePassword(self.server.server_id, self.getUUID())
96
+ self.data['password'] = password
97
+ return password
98
+
99
+ def lock(self) -> 'Database':
100
+ '''Locks the database against deletion.'''
101
+ self.panel.api.databases.update(self.server.server_id, self.getUUID(), locked=True)
102
+ return self._update({'is_locked': True})
103
+
104
+ def unlock(self) -> 'Database':
105
+ '''Unlocks the database.'''
106
+ self.panel.api.databases.update(self.server.server_id, self.getUUID(), locked=False)
107
+ return self._update({'is_locked': False})
108
+
109
+ def recreate(self) -> None:
110
+ '''Drops and recreates the database, deleting all of its data.'''
111
+ return self.panel.api.databases.recreate(self.server.server_id, self.getUUID())
112
+
113
+ def delete(self) -> None:
114
+ '''Deletes the database.'''
115
+ return self.panel.api.databases.delete(self.server.server_id, self.getUUID())
116
+
117
+
118
+ class DatabaseInstance(Model):
119
+ '''
120
+ A dedicated database server (container) owned by a server.
121
+
122
+ Database instances run their own engine (PostgreSQL, MariaDB, MongoDB or Redis)
123
+ and can hold several databases and users.
124
+
125
+ Example:
126
+ instance = server.databases.createInstance(template, 'main-db')
127
+ instance.start()
128
+ db = instance.createDatabase('players')
129
+ user = instance.createUser('app', databases={db: 'read_write'})
130
+ '''
131
+ def __init__(self, server, data: dict):
132
+ super().__init__(server.panel, data.get('instance', data))
133
+ self.server = server
134
+ self.live = data.get('live')
135
+
136
+ @property
137
+ def instance_id(self):
138
+ return self.getUUID()
139
+
140
+ def _api(self):
141
+ return self.panel.api.databases
142
+
143
+ # getters
144
+ def getName(self) -> str:
145
+ '''Returns the instance name.'''
146
+ return self.data.get('name')
147
+
148
+ def getType(self) -> str:
149
+ '''Returns the engine type, e.g. postgres, mariadb, mongodb, redis.'''
150
+ return self.data.get('type')
151
+
152
+ def getHost(self) -> str:
153
+ '''Returns the hostname to connect to.'''
154
+ return self.data.get('host')
155
+
156
+ def getPort(self) -> int:
157
+ '''Returns the port to connect to.'''
158
+ return self.data.get('port')
159
+
160
+ def getStatus(self) -> str:
161
+ '''Returns the instance status as reported by the panel.'''
162
+ return self.data.get('status')
163
+
164
+ def getMemory(self) -> int:
165
+ '''Returns the memory limit in MiB.'''
166
+ return self.data.get('memory')
167
+
168
+ def getDisk(self) -> int:
169
+ '''Returns the disk limit in MiB.'''
170
+ return self.data.get('disk')
171
+
172
+ def getCPU(self) -> int:
173
+ '''Returns the CPU limit in percent.'''
174
+ return self.data.get('cpu')
175
+
176
+ def getLiveData(self) -> dict:
177
+ '''Returns live state information, if it was included when fetching.'''
178
+ return self.live
179
+
180
+ def isLocked(self) -> bool:
181
+ '''Returns True if the instance is locked against deletion.'''
182
+ return bool(self.data.get('is_locked'))
183
+
184
+ def hasUpdate(self) -> bool:
185
+ '''Returns True if a newer image is available, see upgrade().'''
186
+ return bool(self.data.get('update_available'))
187
+
188
+ # actions
189
+ def refresh(self) -> 'DatabaseInstance':
190
+ '''Re-fetches the instance from the panel.'''
191
+ result = self._api().instance(self.server.server_id, self.instance_id)
192
+ self.data = result['instance']
193
+ self.live = result.get('live')
194
+ return self
195
+
196
+ def powerAction(self, action: str) -> None:
197
+ '''Sends a power action: start, stop, restart or kill.'''
198
+ return self._api().instancePower(self.server.server_id, self.instance_id, action)
199
+
200
+ def start(self) -> None:
201
+ '''Starts the instance.'''
202
+ return self.powerAction('start')
203
+
204
+ def stop(self) -> None:
205
+ '''Stops the instance.'''
206
+ return self.powerAction('stop')
207
+
208
+ def restart(self) -> None:
209
+ '''Restarts the instance.'''
210
+ return self.powerAction('restart')
211
+
212
+ def kill(self) -> None:
213
+ '''Kills the instance.'''
214
+ return self.powerAction('kill')
215
+
216
+ def getResources(self) -> dict:
217
+ '''Returns live resource usage of the instance.'''
218
+ return self._api().instanceResources(self.server.server_id, self.instance_id)
219
+
220
+ def getLogs(self, lines: int = None) -> str:
221
+ '''Returns the instance console log.'''
222
+ return self._api().instanceLogs(self.server.server_id, self.instance_id, lines)
223
+
224
+ def rename(self, name: str) -> 'DatabaseInstance':
225
+ '''Renames the instance.'''
226
+ self._api().updateInstance(self.server.server_id, self.instance_id, name=name)
227
+ return self._update({'name': name})
228
+
229
+ def lock(self) -> 'DatabaseInstance':
230
+ '''Locks the instance against deletion.'''
231
+ self._api().updateInstance(self.server.server_id, self.instance_id, locked=True)
232
+ return self._update({'is_locked': True})
233
+
234
+ def unlock(self) -> 'DatabaseInstance':
235
+ '''Unlocks the instance.'''
236
+ self._api().updateInstance(self.server.server_id, self.instance_id, locked=False)
237
+ return self._update({'is_locked': False})
238
+
239
+ def upgrade(self) -> 'DatabaseInstance':
240
+ '''Updates the instance to the latest image.'''
241
+ self.data = self._api().upgradeInstance(self.server.server_id, self.instance_id)
242
+ return self
243
+
244
+ def export(self, destination: str = None):
245
+ '''
246
+ Exports a dump of the whole instance.
247
+
248
+ Args:
249
+ destination: Optional local file to write the dump to.
250
+ '''
251
+ dump = self._api().exportInstance(self.server.server_id, self.instance_id)
252
+ return _save(dump, destination)
253
+
254
+ def importDump(self, dump, wipe: bool = False) -> None:
255
+ '''
256
+ Imports a dump into the instance.
257
+
258
+ Args:
259
+ dump: Local file path, bytes or str.
260
+ wipe: Delete existing data first.
261
+ '''
262
+ return self._api().importInstance(self.server.server_id, self.instance_id, _load(dump), wipe)
263
+
264
+ def cancelOperation(self, operation_id: str) -> None:
265
+ '''Cancels a running import/export operation.'''
266
+ return self._api().cancelInstanceOperation(self.server.server_id, self.instance_id, operation_id)
267
+
268
+ def delete(self, force: bool = None) -> None:
269
+ '''Deletes the instance and all its databases.'''
270
+ return self._api().deleteInstance(self.server.server_id, self.instance_id, force)
271
+
272
+ # databases
273
+ def getDatabases(self) -> list:
274
+ '''Returns the databases inside this instance.'''
275
+ return [InstanceDatabase(self, data) for data in self._api().instanceDatabases(self.server.server_id, self.instance_id)]
276
+
277
+ def getDatabase(self, name_or_id: str) -> 'InstanceDatabase':
278
+ '''Returns a database by name or UUID, or None.'''
279
+ for database in self.getDatabases():
280
+ if name_or_id in (database.getUUID(), database.getName()):
281
+ return database
282
+ return None
283
+
284
+ def createDatabase(self, name: str) -> 'InstanceDatabase':
285
+ '''Creates a database inside the instance.'''
286
+ return InstanceDatabase(self, self._api().createInstanceDatabase(self.server.server_id, self.instance_id, name))
287
+
288
+ # users
289
+ def getUsers(self) -> list:
290
+ '''Returns the database users of this instance.'''
291
+ return [InstanceUser(self, data) for data in self._api().instanceUsers(self.server.server_id, self.instance_id)]
292
+
293
+ def createUser(self, username: str, databases: dict = None) -> 'InstanceUser':
294
+ '''
295
+ Creates a database user.
296
+
297
+ Args:
298
+ username: The username.
299
+ databases: Optional {database: permission} grants, permission is
300
+ 'none', 'read_only' or 'read_write'.
301
+
302
+ Returns:
303
+ The new user. `user.getPassword()` holds the generated password.
304
+
305
+ Example:
306
+ user = instance.createUser('app', databases={db: 'read_write'})
307
+ '''
308
+ grants = _grants(databases)
309
+ return InstanceUser(self, self._api().createInstanceUser(self.server.server_id, self.instance_id, username, grants))
310
+
311
+
312
+ class InstanceDatabase(Model):
313
+ '''
314
+ A database inside a DatabaseInstance.
315
+
316
+ Example:
317
+ db = instance.getDatabase('players')
318
+ db.explorer.getTables()
319
+ db.export('players.sql')
320
+ '''
321
+ def __init__(self, instance: DatabaseInstance, data: dict):
322
+ super().__init__(instance.panel, data)
323
+ self.instance = instance
324
+ self.server = instance.server
325
+ self.explorer = DatabaseExplorer(
326
+ self.panel,
327
+ self.panel.api.databases.instanceDatabasePath(self.server.server_id, instance.instance_id, self.getUUID())
328
+ )
329
+
330
+ def _args(self):
331
+ return self.server.server_id, self.instance.instance_id, self.getUUID()
332
+
333
+ def getName(self) -> str:
334
+ '''Returns the database name.'''
335
+ return self.data.get('name')
336
+
337
+ def getSize(self, formatted: bool = False):
338
+ '''Returns the current size in bytes (or formatted with formatted=True).'''
339
+ size = self.panel.api.databases.instanceDatabaseSize(*self._args())
340
+ return formatBytes(size) if formatted else size
341
+
342
+ def export(self, destination: str = None):
343
+ '''Exports a dump of this database, optionally saving it to `destination`.'''
344
+ return _save(self.panel.api.databases.exportInstanceDatabase(*self._args()), destination)
345
+
346
+ def importDump(self, dump, source_db: str = None, wipe: bool = False) -> None:
347
+ '''
348
+ Imports a dump into this database.
349
+
350
+ Args:
351
+ dump: Local file path, bytes or str.
352
+ source_db: Name of the database inside the dump to import, if it contains several.
353
+ wipe: Delete existing data first.
354
+ '''
355
+ return self.panel.api.databases.importInstanceDatabase(*self._args(), _load(dump), source_db, wipe)
356
+
357
+ def importFromUrl(self, url: str, source_db: str = None, wipe: bool = False) -> dict:
358
+ '''Imports a dump from a remote URL. Returns the background operation.'''
359
+ return self.panel.api.databases.importInstanceDatabaseRemote(*self._args(), url, source_db, wipe)
360
+
361
+ def recreate(self) -> None:
362
+ '''Drops and recreates the database, deleting all of its data.'''
363
+ return self.panel.api.databases.recreateInstanceDatabase(*self._args())
364
+
365
+ def delete(self) -> None:
366
+ '''Deletes the database.'''
367
+ return self.panel.api.databases.deleteInstanceDatabase(*self._args())
368
+
369
+
370
+ class InstanceUser(Model):
371
+ '''
372
+ A database user inside a DatabaseInstance.
373
+
374
+ Example:
375
+ user = instance.createUser('app')
376
+ user.setPermission(db, 'read_only')
377
+ new_password = user.rotatePassword()
378
+ '''
379
+ def __init__(self, instance: DatabaseInstance, data: dict):
380
+ super().__init__(instance.panel, data)
381
+ self.instance = instance
382
+ self.server = instance.server
383
+
384
+ def _args(self):
385
+ return self.server.server_id, self.instance.instance_id, self.getUUID()
386
+
387
+ def getUsername(self) -> str:
388
+ '''Returns the username.'''
389
+ return self.data.get('username')
390
+
391
+ def getPassword(self) -> str:
392
+ '''Returns the password, only known right after creation or rotation.'''
393
+ return self.data.get('password')
394
+
395
+ def getDatabases(self) -> list:
396
+ '''Returns the database grants of this user.'''
397
+ return self.data.get('databases', [])
398
+
399
+ def setDatabases(self, databases: dict) -> list:
400
+ '''
401
+ Replaces every grant of this user.
402
+
403
+ Args:
404
+ databases: {database: permission}, permission is 'none', 'read_only' or 'read_write'.
405
+ '''
406
+ result = self.panel.api.databases.setInstanceUserDatabases(*self._args(), _grants(databases))
407
+ self.data['databases'] = result
408
+ return result
409
+
410
+ def setPermission(self, database, permission: str) -> dict:
411
+ '''Sets the permission on one database: 'none', 'read_only' or 'read_write'.'''
412
+ return self.panel.api.databases.setInstanceUserPermission(*self._args(), idOf(database), permission)
413
+
414
+ def rotatePassword(self) -> str:
415
+ '''Generates and returns a new password.'''
416
+ password = self.panel.api.databases.rotateInstanceUserPassword(*self._args())
417
+ self.data['password'] = password
418
+ return password
419
+
420
+ def delete(self) -> None:
421
+ '''Deletes the user.'''
422
+ return self.panel.api.databases.deleteInstanceUser(*self._args())
423
+
424
+
425
+ # helpers
426
+ def _grants(databases):
427
+ if not databases:
428
+ return None
429
+ return [{'database_uuid': idOf(db), 'permission': permission} for db, permission in databases.items()]
430
+
431
+
432
+ def _load(dump):
433
+ # a local file path, otherwise the dump contents themselves
434
+ if isinstance(dump, (str, os.PathLike)) and os.path.isfile(dump):
435
+ with open(dump, 'rb') as f:
436
+ return f.read()
437
+ return dump
438
+
439
+
440
+ def _save(dump, destination):
441
+ if destination is None:
442
+ return dump
443
+ with open(destination, 'w', encoding='utf-8') as f:
444
+ f.write(dump)
445
+ return destination
@@ -0,0 +1,159 @@
1
+ def _values(row: dict) -> list:
2
+ # {'name': 'Steve'} -> [{'column': 'name', 'value': 'Steve'}]
3
+ return [{'column': column, 'value': None if value is None else str(value)} for column, value in row.items()]
4
+
5
+
6
+ class DatabaseExplorer:
7
+ '''
8
+ Browse and edit a database's tables and rows, available as `database.explorer`.
9
+
10
+ Works the same for hosted databases and databases inside a database instance.
11
+
12
+ Args:
13
+ panel: The Panel.
14
+ base: Internal API path of the database.
15
+
16
+ Example:
17
+ explorer = server.databases.getDatabase('...').explorer
18
+ print(explorer.getTables())
19
+ rows = explorer.getRows('players', limit=10)
20
+ explorer.insertRow('players', {'name': 'Steve', 'score': 10})
21
+ explorer.query('SELECT COUNT(*) FROM players')
22
+ '''
23
+ def __init__(self, panel, base: str):
24
+ self.panel = panel
25
+ self.api = panel.api.databases
26
+ self.base = base
27
+
28
+ # schema
29
+ def getSchema(self) -> dict:
30
+ '''Returns every table with its columns. `truncated` is True for very large schemas.'''
31
+ return self.api.schema(self.base)
32
+
33
+ def getTables(self) -> list:
34
+ '''Returns the list of tables.'''
35
+ return self.getSchema().get('tables', [])
36
+
37
+ def getTableTypes(self) -> list:
38
+ '''Returns the column types this database supports, e.g. ['INT', 'VARCHAR', ...].'''
39
+ return self.api.tableTypes(self.base)
40
+
41
+ # querying
42
+ def query(self, sql: str, rows: int = None, read_only: bool = False) -> list:
43
+ '''
44
+ Runs a raw SQL query.
45
+
46
+ Args:
47
+ sql: The query.
48
+ rows: Maximum rows to return.
49
+ read_only: Run inside a read-only transaction.
50
+
51
+ Returns:
52
+ List of result sets.
53
+
54
+ Example:
55
+ explorer.query('SELECT * FROM players WHERE score > 100', read_only=True)
56
+ '''
57
+ return self.api.query(self.base, sql, rows, read_only)
58
+
59
+ def getRows(self, table: str, limit: int = None, offset: int = None, order_by: str = None,
60
+ descending: bool = None, filters: list = None, schema: str = None) -> dict:
61
+ '''
62
+ Browses rows of a table.
63
+
64
+ Args:
65
+ table: Table name.
66
+ limit: Maximum rows.
67
+ offset: Rows to skip.
68
+ order_by: Column to sort by.
69
+ descending: Sort descending.
70
+ filters: List of {'column': ..., 'operator': ..., 'value': ...}. Operators:
71
+ eq, ne, gt, gte, lt, lte, contains, starts_with, ends_with, is_null, not_null.
72
+ schema: Schema name (PostgreSQL).
73
+
74
+ Example:
75
+ explorer.getRows('players', filters=[{'column': 'name', 'operator': 'eq', 'value': 'Steve'}])
76
+ '''
77
+ return self.api.rows(self.base, table, schema, order_by, descending, limit, offset, filters)
78
+
79
+ # rows
80
+ def insertRow(self, table: str, values: dict, schema: str = None) -> int:
81
+ '''
82
+ Inserts a row.
83
+
84
+ Example:
85
+ explorer.insertRow('players', {'name': 'Steve', 'score': 10})
86
+
87
+ Returns:
88
+ Number of affected rows.
89
+ '''
90
+ return self.api.insertRows(self.base, table, [{'values': _values(values)}], schema)
91
+
92
+ def insertRows(self, table: str, rows: list, schema: str = None) -> int:
93
+ '''Inserts several rows, each given as a dict of column -> value.'''
94
+ return self.api.insertRows(self.base, table, [{'values': _values(row)} for row in rows], schema)
95
+
96
+ def updateRow(self, table: str, where: dict, values: dict, schema: str = None) -> int:
97
+ '''
98
+ Updates the row matching `where`.
99
+
100
+ Example:
101
+ explorer.updateRow('players', where={'id': 1}, values={'score': 20})
102
+ '''
103
+ return self.api.updateRows(self.base, table, [{'keys': _values(where), 'values': _values(values)}], schema)
104
+
105
+ def deleteRow(self, table: str, where: dict, schema: str = None) -> int:
106
+ '''
107
+ Deletes the row matching `where`.
108
+
109
+ Example:
110
+ explorer.deleteRow('players', where={'id': 1})
111
+ '''
112
+ return self.api.deleteRows(self.base, table, [{'keys': _values(where)}], schema)
113
+
114
+ def deleteRows(self, table: str, rows: list, schema: str = None) -> int:
115
+ '''Deletes several rows, each identified by a dict of key columns.'''
116
+ return self.api.deleteRows(self.base, table, [{'keys': _values(row)} for row in rows], schema)
117
+
118
+ # tables
119
+ def createTable(self, table: str, columns: list, schema: str = None) -> None:
120
+ '''
121
+ Creates a table.
122
+
123
+ Args:
124
+ columns: List of {'name', 'type', 'nullable', 'primary_key', 'auto_increment'} dicts.
125
+
126
+ Example:
127
+ explorer.createTable('players', [
128
+ {'name': 'id', 'type': 'INT', 'primary_key': True, 'auto_increment': True},
129
+ {'name': 'name', 'type': 'VARCHAR(32)'},
130
+ ])
131
+ '''
132
+ return self.api.createTable(self.base, table, columns, schema)
133
+
134
+ def renameTable(self, table: str, name: str, schema: str = None) -> None:
135
+ '''Renames a table.'''
136
+ return self.api.renameTable(self.base, table, name, schema)
137
+
138
+ def deleteTable(self, table: str, schema: str = None) -> None:
139
+ '''Drops a table.'''
140
+ return self.api.deleteTable(self.base, table, schema)
141
+
142
+ def addColumn(self, table: str, name: str, type: str, nullable: bool = True, primary_key: bool = False,
143
+ auto_increment: bool = False, schema: str = None) -> None:
144
+ '''
145
+ Adds a column to a table.
146
+
147
+ Example:
148
+ explorer.addColumn('players', 'last_seen', 'TIMESTAMP')
149
+ '''
150
+ column = {'name': name, 'type': type, 'nullable': nullable, 'primary_key': primary_key, 'auto_increment': auto_increment}
151
+ return self.api.addColumn(self.base, table, column, schema)
152
+
153
+ def renameColumn(self, table: str, column: str, name: str, schema: str = None) -> None:
154
+ '''Renames a column.'''
155
+ return self.api.renameColumn(self.base, table, column, name, schema)
156
+
157
+ def deleteColumn(self, table: str, column: str, schema: str = None) -> None:
158
+ '''Drops a column.'''
159
+ return self.api.deleteColumn(self.base, table, column, schema)