smdb-dbm 0.1.0__tar.gz
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.
- smdb_dbm-0.1.0/PKG-INFO +19 -0
- smdb_dbm-0.1.0/README.md +4 -0
- smdb_dbm-0.1.0/pyproject.toml +9 -0
- smdb_dbm-0.1.0/setup.cfg +25 -0
- smdb_dbm-0.1.0/smdb_db_manager/SMDB_DBM.py +313 -0
- smdb_dbm-0.1.0/smdb_db_manager/__init__.py +1 -0
- smdb_dbm-0.1.0/smdb_db_manager/utils/ClosedException.py +4 -0
- smdb_dbm-0.1.0/smdb_db_manager/utils/DBStatus.py +17 -0
- smdb_dbm-0.1.0/smdb_db_manager/utils/DefaultTableNames.py +5 -0
- smdb_dbm-0.1.0/smdb_db_manager/utils/Timer.py +13 -0
- smdb_dbm-0.1.0/smdb_db_manager/utils/Version.py +36 -0
- smdb_dbm-0.1.0/smdb_db_manager/utils/__init__.py +5 -0
- smdb_dbm-0.1.0/smdb_dbm.egg-info/PKG-INFO +19 -0
- smdb_dbm-0.1.0/smdb_dbm.egg-info/SOURCES.txt +15 -0
- smdb_dbm-0.1.0/smdb_dbm.egg-info/dependency_links.txt +1 -0
- smdb_dbm-0.1.0/smdb_dbm.egg-info/top_level.txt +1 -0
smdb_dbm-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smdb_dbm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Database Manager for Server Monitoring Discord bot and other projects
|
|
5
|
+
Home-page: https://github.com/NightKey/smdb-dbm
|
|
6
|
+
Author: Janthó Dávid
|
|
7
|
+
Author-email: davidjantho@gmail.com
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/NightKey/smdb-dbm/issues
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: Freeware
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.14
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# Server Monitoring Discord Bot - Data Base Manager
|
|
17
|
+
|
|
18
|
+
This is a database manager used by the [Server Monitoring Discord Bot](https://github.com/NightKey/Server-monitoring-discord-bot) and other projects created by me.
|
|
19
|
+
|
smdb_dbm-0.1.0/README.md
ADDED
smdb_dbm-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = smdb_dbm
|
|
3
|
+
version = 0.1.0
|
|
4
|
+
author = Janthó Dávid
|
|
5
|
+
author_email = davidjantho@gmail.com
|
|
6
|
+
description = Database Manager for Server Monitoring Discord bot and other projects
|
|
7
|
+
long_description = file: README.md
|
|
8
|
+
long_description_content_type = text/markdown
|
|
9
|
+
url = https://github.com/NightKey/smdb-dbm
|
|
10
|
+
project_urls =
|
|
11
|
+
Bug Tracker = https://github.com/NightKey/smdb-dbm/issues
|
|
12
|
+
classifiers =
|
|
13
|
+
Development Status :: 4 - Beta
|
|
14
|
+
Programming Language :: Python :: 3
|
|
15
|
+
License :: Freeware
|
|
16
|
+
Operating System :: OS Independent
|
|
17
|
+
|
|
18
|
+
[options]
|
|
19
|
+
packages = find:
|
|
20
|
+
python_requires = >=3.14
|
|
21
|
+
|
|
22
|
+
[egg_info]
|
|
23
|
+
tag_build =
|
|
24
|
+
tag_date = 0
|
|
25
|
+
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from abc import abstractmethod, ABC
|
|
3
|
+
from asyncio import Lock
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Any, List, Tuple
|
|
6
|
+
from os import path, mkdir
|
|
7
|
+
|
|
8
|
+
import aiosqlite
|
|
9
|
+
from aiosqlite import Connection
|
|
10
|
+
from smdb_logger import Logger
|
|
11
|
+
|
|
12
|
+
from smdb_db_manager.utils import Timer, DBStatus, ClosedException, Version, DefaultTableNames
|
|
13
|
+
|
|
14
|
+
class DBManager(ABC):
|
|
15
|
+
lock: Lock = Lock()
|
|
16
|
+
db: Connection | None = None
|
|
17
|
+
status: DBStatus
|
|
18
|
+
logger: Logger
|
|
19
|
+
data_path: str
|
|
20
|
+
db_name: str
|
|
21
|
+
|
|
22
|
+
# <editor-fold desc="Wrappers">
|
|
23
|
+
@staticmethod
|
|
24
|
+
def async_timed(func):
|
|
25
|
+
"""
|
|
26
|
+
Times the provided async function's runtime.
|
|
27
|
+
"""
|
|
28
|
+
@wraps(func)
|
|
29
|
+
async def async_timed_wrapper(self: DBManager, *args, **kwargs):
|
|
30
|
+
timer = Timer()
|
|
31
|
+
result = await func(self, *args, **kwargs)
|
|
32
|
+
self.logger.debug(f"Function {func.__name__} returned {result}")
|
|
33
|
+
self.logger.trace(f"Function {func.__name__} finished under {timer.stop(1_000_000)} ms")
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
return async_timed_wrapper
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def async_required_argument(arguments: List[str]):
|
|
40
|
+
"""
|
|
41
|
+
Ensures at least one provided argument is present of the 'arguments' list provided.
|
|
42
|
+
Should only be used with functions using multiple optional arguments, but requiring at least one to be present.
|
|
43
|
+
"""
|
|
44
|
+
def async_required_argument_decorator(func):
|
|
45
|
+
@wraps(func)
|
|
46
|
+
async def wrapper(self: DBManager, *args, **kwargs):
|
|
47
|
+
for argument in arguments:
|
|
48
|
+
if argument in kwargs:
|
|
49
|
+
break
|
|
50
|
+
else:
|
|
51
|
+
raise KeyError(f"At least one of the following argument needs to be present: {arguments}")
|
|
52
|
+
return await func(self, *args, **kwargs)
|
|
53
|
+
|
|
54
|
+
return wrapper
|
|
55
|
+
|
|
56
|
+
return async_required_argument_decorator
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def async_database_safe(func):
|
|
60
|
+
"""
|
|
61
|
+
Handles waiting for the database to be running, and lock to be available to run the function provided,
|
|
62
|
+
handling the database connection's closure when the function finishes or fails.
|
|
63
|
+
This wrapper will fail the call when database is in 'stopping', 'stopped' or 'failed' status, so no threads will be hanging.
|
|
64
|
+
"""
|
|
65
|
+
@wraps(func)
|
|
66
|
+
async def async_database_safe_wrapper(cls: DBManager, *args, **kwargs):
|
|
67
|
+
during_init: bool = getattr(func, "during_init", False)
|
|
68
|
+
fail_value: Any = getattr(func, "fail_value", False)
|
|
69
|
+
fail_exception_index: int | str | None = getattr(func, "fail_exception_index", None)
|
|
70
|
+
if cls.status > DBStatus.RUNNING:
|
|
71
|
+
cls.logger.exception(
|
|
72
|
+
ClosedException(f"Function {func.__name__} called after database stop was called or the database failed!"))
|
|
73
|
+
return fail_value
|
|
74
|
+
if cls.status != DBStatus.RUNNING and not during_init:
|
|
75
|
+
cls.logger.trace(f"Called {func.__name__} called while db not ready, waiting")
|
|
76
|
+
while cls.status < DBStatus.RUNNING:
|
|
77
|
+
await asyncio.sleep(0.5)
|
|
78
|
+
cls.logger.trace(f"Acquiring lock for function {func.__name__}")
|
|
79
|
+
await cls.lock.acquire()
|
|
80
|
+
if cls.status > DBStatus.STOPPING:
|
|
81
|
+
cls.logger.exception(
|
|
82
|
+
ClosedException(f"Function {func.__name__} called after database was stopped or failed!"))
|
|
83
|
+
cls.lock.release()
|
|
84
|
+
return fail_value
|
|
85
|
+
cls.logger.debug(f"Running {func.__name__} with keyword arguments: {kwargs}")
|
|
86
|
+
cls.db = await aiosqlite.connect(cls.db_path).__aenter__()
|
|
87
|
+
await cls.db.execute("PRAGMA foreign_keys = ON")
|
|
88
|
+
try:
|
|
89
|
+
return await func(cls, *args, **kwargs)
|
|
90
|
+
except BaseException as ex:
|
|
91
|
+
cls.logger.error(f"Exception during {func.__name__}", ex)
|
|
92
|
+
if fail_exception_index is not None:
|
|
93
|
+
if isinstance(fail_value, (list, tuple, dict)):
|
|
94
|
+
fail_value[fail_exception_index] = ex
|
|
95
|
+
else:
|
|
96
|
+
fail_value = ex
|
|
97
|
+
return fail_value
|
|
98
|
+
finally:
|
|
99
|
+
cls.logger.debug(f"Closing db connection for {func.__name__}")
|
|
100
|
+
await cls.db.close()
|
|
101
|
+
cls.db = None
|
|
102
|
+
cls.lock.release()
|
|
103
|
+
cls.logger.trace(f"Lock released by {func.__name__}")
|
|
104
|
+
|
|
105
|
+
return async_database_safe_wrapper
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def with_fail_value(fail_value: Any):
|
|
109
|
+
"""
|
|
110
|
+
When provided before 'async_database_safe' wrapper, will ensure the fail case will be the provided value
|
|
111
|
+
"""
|
|
112
|
+
def with_fail_value_decorator(func):
|
|
113
|
+
func.fail_value = fail_value
|
|
114
|
+
return func
|
|
115
|
+
|
|
116
|
+
return with_fail_value_decorator
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def fail_with_exception(func):
|
|
120
|
+
"""
|
|
121
|
+
When provided before 'async_database_safe' wrapper, will ensure the fail case will contain the raised exception - if present - on the -1st index or as the return value.
|
|
122
|
+
The wrapper 'with_fail_value' is not needed for this to overwrite the result on a failure.
|
|
123
|
+
"""
|
|
124
|
+
func.fail_exception_index = -1
|
|
125
|
+
return func
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def async_during_init(func):
|
|
129
|
+
"""
|
|
130
|
+
Overrides the "async_database_safe" wrapper's safety net of waiting for database to be running, so it can be used during initialization
|
|
131
|
+
"""
|
|
132
|
+
func.during_init = True
|
|
133
|
+
return func
|
|
134
|
+
|
|
135
|
+
# </editor-fold>
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def db_path(self) -> str:
|
|
139
|
+
return path.join(self.data_path, self.db_name)
|
|
140
|
+
|
|
141
|
+
def __init__(self, logger: Logger, data_path: str, db_name: str = "database.db", version: Version = Version(0, 0, 1)):
|
|
142
|
+
"""
|
|
143
|
+
:param logger: 'smdb_logger' for detailed logging when using the wrappers
|
|
144
|
+
:param data_path: The path for the data folder where the database will be held:
|
|
145
|
+
:param db_name: The name of the database file.
|
|
146
|
+
:param version: The version of the database to use. Will be used to migrate to newer versions.
|
|
147
|
+
"""
|
|
148
|
+
self.data_path = data_path
|
|
149
|
+
self.db_name = db_name
|
|
150
|
+
self.logger = logger
|
|
151
|
+
self.status = DBStatus.STARTING
|
|
152
|
+
self.init_task: asyncio.Task | None = None
|
|
153
|
+
loop = asyncio.get_event_loop()
|
|
154
|
+
if path.exists(self.db_path):
|
|
155
|
+
self.init_task = loop.create_task(self.__ensure_version(version))
|
|
156
|
+
return
|
|
157
|
+
self.init_task = loop.create_task(self.__prepare_versioned_db(version))
|
|
158
|
+
|
|
159
|
+
async def ensure_ready(self) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Will wait while the database is starting, then clean up the init_task, raising any exception that was raised during init.
|
|
162
|
+
:return:None
|
|
163
|
+
"""
|
|
164
|
+
while self.status == DBStatus.STARTING:
|
|
165
|
+
await asyncio.sleep(0.1)
|
|
166
|
+
if self.init_task is None: return
|
|
167
|
+
_exception = self.init_task.exception()
|
|
168
|
+
self.init_task = None
|
|
169
|
+
if _exception is not None: raise _exception
|
|
170
|
+
|
|
171
|
+
async def __ensure_version(self, version: Version):
|
|
172
|
+
correct, current_version = await self.__check_version(version)
|
|
173
|
+
if not correct:
|
|
174
|
+
if isinstance(current_version, BaseException):
|
|
175
|
+
self.status = DBStatus.FAILED
|
|
176
|
+
raise current_version
|
|
177
|
+
result = await self.migrate_db(current_version, version)
|
|
178
|
+
if not result:
|
|
179
|
+
self.status = DBStatus.FAILED
|
|
180
|
+
raise Exception(f"Failed to migrate DB to version {version}")
|
|
181
|
+
self.logger.info(f"Database at {path.abspath(self.db_path)} ready")
|
|
182
|
+
self.status = DBStatus.RUNNING
|
|
183
|
+
|
|
184
|
+
@async_timed
|
|
185
|
+
async def __prepare_versioned_db(self, version: Version):
|
|
186
|
+
await self.lock.acquire()
|
|
187
|
+
if not path.exists(self.data_path):
|
|
188
|
+
mkdir(self.data_path)
|
|
189
|
+
async with aiosqlite.connect(self.db_path) as db:
|
|
190
|
+
self.logger.trace(f"Creating {DefaultTableNames.Version.value} table with value {version}")
|
|
191
|
+
await db.execute(
|
|
192
|
+
f"""
|
|
193
|
+
CREATE TABLE IF NOT EXISTS {DefaultTableNames.Version.value} (
|
|
194
|
+
major INTEGER NOT NULL,
|
|
195
|
+
minor INTEGER NOT NULL,
|
|
196
|
+
patch INTEGER NOT NULL,
|
|
197
|
+
PRIMARY KEY (major, minor, patch)
|
|
198
|
+
) STRICT;
|
|
199
|
+
"""
|
|
200
|
+
)
|
|
201
|
+
self.logger.trace(f"Inserting version to {DefaultTableNames.Version.value} table")
|
|
202
|
+
await db.execute(
|
|
203
|
+
f"""
|
|
204
|
+
INSERT INTO {DefaultTableNames.Version.value} (major, minor, patch)
|
|
205
|
+
VALUES (?, ?, ?);
|
|
206
|
+
""",
|
|
207
|
+
version.to_db()
|
|
208
|
+
)
|
|
209
|
+
await db.commit()
|
|
210
|
+
self.lock.release()
|
|
211
|
+
await self.init_db()
|
|
212
|
+
self.status = DBStatus.RUNNING
|
|
213
|
+
|
|
214
|
+
@async_database_safe
|
|
215
|
+
@async_during_init
|
|
216
|
+
@with_fail_value([None, None])
|
|
217
|
+
@fail_with_exception
|
|
218
|
+
@async_timed
|
|
219
|
+
async def __check_version(self, version: Version) -> Tuple[bool, Version]:
|
|
220
|
+
rows = await self.db.execute_fetchall(
|
|
221
|
+
f"""
|
|
222
|
+
SELECT major, minor, patch FROM {DefaultTableNames.Version.name}
|
|
223
|
+
"""
|
|
224
|
+
)
|
|
225
|
+
if len(rows) != 1:
|
|
226
|
+
raise ValueError(f"Database version contained {len(rows)} element, but 1 was expected!\nElements: {rows}")
|
|
227
|
+
db_version = Version.from_db(rows[0])
|
|
228
|
+
if db_version > version:
|
|
229
|
+
raise ValueError(f"DB version {db_version} is bigger than required version {version}")
|
|
230
|
+
return db_version == version, db_version
|
|
231
|
+
|
|
232
|
+
@async_database_safe
|
|
233
|
+
@async_timed
|
|
234
|
+
async def update_version(self, version: Version) -> bool:
|
|
235
|
+
"""
|
|
236
|
+
Updates the database version to the new version.
|
|
237
|
+
:param version:
|
|
238
|
+
:return: True if the update was successful otherwise False
|
|
239
|
+
"""
|
|
240
|
+
await self.db.execute(
|
|
241
|
+
f"""
|
|
242
|
+
UPDATE {DefaultTableNames.Version.value}
|
|
243
|
+
SET major = ?, minor = ?, patch = ?
|
|
244
|
+
""",
|
|
245
|
+
version.to_db()
|
|
246
|
+
)
|
|
247
|
+
await self.db.commit()
|
|
248
|
+
return True
|
|
249
|
+
|
|
250
|
+
@async_timed
|
|
251
|
+
async def close(self) -> None:
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
:return:
|
|
255
|
+
"""
|
|
256
|
+
if self.status > DBStatus.RUNNING:
|
|
257
|
+
self.logger.warning(f"Close called while status is {self.status.name}")
|
|
258
|
+
return
|
|
259
|
+
self.logger.info("Close called!")
|
|
260
|
+
self.status = DBStatus.STOPPING
|
|
261
|
+
self.logger.trace("Acquiring lock for close")
|
|
262
|
+
await self.lock.acquire()
|
|
263
|
+
self.status = DBStatus.STOPPED
|
|
264
|
+
self.lock.release()
|
|
265
|
+
self.logger.trace("Close lock released")
|
|
266
|
+
|
|
267
|
+
@classmethod
|
|
268
|
+
async def create[T: DBManager](cls: type[T], logger: Logger, data_path: str, db_name: str = "database.db", version: Version = Version(0, 0, 1)) -> T:
|
|
269
|
+
"""Async wrapper for creating the class.
|
|
270
|
+
:param logger: 'smdb_logger' for detailed logging when using the wrappers
|
|
271
|
+
:param data_path: The path for the data folder where the database will be held
|
|
272
|
+
:param db_name: The name of the database file.
|
|
273
|
+
:param version: The version of the database to use. Will be used to migrate to newer versions.
|
|
274
|
+
:return: The class created"""
|
|
275
|
+
result = cls(logger, data_path, db_name, version)
|
|
276
|
+
await result.ensure_ready()
|
|
277
|
+
return result
|
|
278
|
+
|
|
279
|
+
@async_database_safe
|
|
280
|
+
@async_during_init
|
|
281
|
+
@async_timed
|
|
282
|
+
@fail_with_exception
|
|
283
|
+
@abstractmethod
|
|
284
|
+
async def migrate_db(self, current: Version, target: Version) -> bool:
|
|
285
|
+
"""
|
|
286
|
+
Will be called when version change was detected and migration is needed.
|
|
287
|
+
The following decorators are needed for ease of use and safety:
|
|
288
|
+
* @DBManager.async_database_safe
|
|
289
|
+
* @DBManager.async_during_init
|
|
290
|
+
* @DBManager.async_timed
|
|
291
|
+
* @DBManager.fail_with_exception
|
|
292
|
+
:param current: Version: The current version of the DB.
|
|
293
|
+
:param target: Version: The target version to migrate to.
|
|
294
|
+
:return: True if migration was successful otherwise False.
|
|
295
|
+
"""
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
@async_database_safe
|
|
299
|
+
@async_during_init
|
|
300
|
+
@async_timed
|
|
301
|
+
@fail_with_exception
|
|
302
|
+
@abstractmethod
|
|
303
|
+
async def init_db(self) -> None:
|
|
304
|
+
"""
|
|
305
|
+
Will be called to initialize the db, create all the default tables and fill in any default values. Version table already created.
|
|
306
|
+
The following decorators are needed for ease of use and safety:
|
|
307
|
+
* @DBManager.async_database_safe
|
|
308
|
+
* @DBManager.async_during_init
|
|
309
|
+
* @DBManager.async_timed
|
|
310
|
+
* @DBManager.fail_with_exception
|
|
311
|
+
:return:None
|
|
312
|
+
"""
|
|
313
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .SMDB_DBM import DBManager
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DBStatus(Enum):
|
|
5
|
+
STARTING = 0
|
|
6
|
+
RUNNING = 1
|
|
7
|
+
STOPPING = 2
|
|
8
|
+
STOPPED = 3
|
|
9
|
+
FAILED = 4
|
|
10
|
+
|
|
11
|
+
def __lt__(self, other) -> bool:
|
|
12
|
+
if not isinstance(other, DBStatus): return False
|
|
13
|
+
return self.value < other.value
|
|
14
|
+
|
|
15
|
+
def __gt__(self, other) -> bool:
|
|
16
|
+
if not isinstance(other, DBStatus): return False
|
|
17
|
+
return self.value > other.value
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class Version:
|
|
7
|
+
major: int
|
|
8
|
+
minor: int
|
|
9
|
+
patch: int
|
|
10
|
+
|
|
11
|
+
def __eq__(self, other) -> bool:
|
|
12
|
+
if not isinstance(other, Version): return False
|
|
13
|
+
return self.major == other.major and self.minor == other.minor and self.patch == other.patch
|
|
14
|
+
|
|
15
|
+
def __gt__(self, other):
|
|
16
|
+
if not isinstance(other, Version): return False
|
|
17
|
+
return (self.major > other.major or
|
|
18
|
+
(self.major == other.major and self.minor > other.minor) or
|
|
19
|
+
(self.major == other.major and self.minor == other.minor and self.patch > other.patch))
|
|
20
|
+
|
|
21
|
+
def __lt__(self, other):
|
|
22
|
+
if not isinstance(other, Version): return False
|
|
23
|
+
return (self.major < other.major or
|
|
24
|
+
(self.major == other.major and self.minor < other.minor) or
|
|
25
|
+
(self.major == other.major and self.minor == other.minor and self.patch < other.patch))
|
|
26
|
+
|
|
27
|
+
def to_db(self) -> Tuple[int, int, int]:
|
|
28
|
+
return self.major, self.minor, self.patch
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def from_db(row: Tuple[int, int, int]) -> 'Version':
|
|
32
|
+
return Version(
|
|
33
|
+
int(row[0]),
|
|
34
|
+
int(row[1]),
|
|
35
|
+
int(row[2])
|
|
36
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smdb_dbm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Database Manager for Server Monitoring Discord bot and other projects
|
|
5
|
+
Home-page: https://github.com/NightKey/smdb-dbm
|
|
6
|
+
Author: Janthó Dávid
|
|
7
|
+
Author-email: davidjantho@gmail.com
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/NightKey/smdb-dbm/issues
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: Freeware
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.14
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# Server Monitoring Discord Bot - Data Base Manager
|
|
17
|
+
|
|
18
|
+
This is a database manager used by the [Server Monitoring Discord Bot](https://github.com/NightKey/Server-monitoring-discord-bot) and other projects created by me.
|
|
19
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.cfg
|
|
4
|
+
smdb_db_manager/SMDB_DBM.py
|
|
5
|
+
smdb_db_manager/__init__.py
|
|
6
|
+
smdb_db_manager/utils/ClosedException.py
|
|
7
|
+
smdb_db_manager/utils/DBStatus.py
|
|
8
|
+
smdb_db_manager/utils/DefaultTableNames.py
|
|
9
|
+
smdb_db_manager/utils/Timer.py
|
|
10
|
+
smdb_db_manager/utils/Version.py
|
|
11
|
+
smdb_db_manager/utils/__init__.py
|
|
12
|
+
smdb_dbm.egg-info/PKG-INFO
|
|
13
|
+
smdb_dbm.egg-info/SOURCES.txt
|
|
14
|
+
smdb_dbm.egg-info/dependency_links.txt
|
|
15
|
+
smdb_dbm.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
smdb_db_manager
|