Passed
Branch BonHowi (1af4d4)
by Bartosz
64:59
created

build.cogs.databasecog   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 502
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 43
eloc 403
dl 0
loc 502
rs 8.96
c 0
b 0
f 0

29 Methods

Rating   Name   Duplication   Size   Complexity  
A DatabaseCog.db_update_loop() 0 4 1
A DatabaseCog.db_count_monster_spot() 0 25 3
A DatabaseCog.db_count_spot() 0 6 1
A DatabaseCog.add_update_member() 0 8 1
A DatabaseCog.before_db_update_loop() 0 4 1
A DatabaseCog.db_save_coords() 0 6 1
A DatabaseCog.__init__() 0 8 1
A DatabaseCog.db_clear_spots_temp_table() 0 6 1
A DatabaseCog.db_get_spots_df() 0 11 1
A DatabaseCog.db_add_update_member() 0 10 1
A DatabaseCog.db_get_total_spots_df() 0 13 3
A DatabaseCog.change_member_spots() 0 11 1
A DatabaseCog.db_add_warn() 0 6 1
A DatabaseCog.db_get_warns() 0 16 2
A DatabaseCog.db_remove_warns() 0 6 1
A DatabaseCog.db_update() 0 8 2
A DatabaseCog.db_get_coords() 0 7 1
A DatabaseCog.db_get_common_sum() 0 10 2
A DatabaseCog.db_count_spot_table() 0 23 4
A DatabaseCog.db_get_member_monsters() 0 13 1
A DatabaseCog.db_add_update_spots() 0 7 1
A DatabaseCog.db_backup_database() 0 29 2
A DatabaseCog.on_member_join() 0 3 1
A DatabaseCog.db_count_spot_table_event() 0 14 2
A DatabaseCog.db_count_spot_table_monster() 0 11 2
A DatabaseCog.db_get_member_stats() 0 10 1
A DatabaseCog.cog_unload() 0 2 1
A DatabaseCog.db_get_monster_spots_df() 0 14 1
A DatabaseCog.db_get_member_names() 0 8 1

1 Function

Rating   Name   Duplication   Size   Complexity  
A setup() 0 2 1

How to fix   Complexity   

Complexity

Complex classes like build.cogs.databasecog often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import os
2
from datetime import datetime
3
4
import discord
5
import pandas as pd
6
from discord.ext import commands, tasks
7
from discord_slash import cog_ext, SlashContext
8
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey, \
9
    BigInteger, update, select, DateTime, delete
10
from sqlalchemy.dialects.mysql import insert
11
from sqlalchemy.sql import func
12
13
import cogs.cogbase as cogbase
14
from modules.get_settings import get_settings
15
16
metadata_obj = MetaData()
17
18
member = Table('member', metadata_obj,
19
               Column('id', BigInteger, primary_key=True),
20
               Column('name', String(50), nullable=False),
21
               Column('display_name', String(50), nullable=False)
22
               )
23
fk_member_id = "member.id"
24
25
warn = Table('warn', metadata_obj,
26
             Column('id', Integer, primary_key=True),
27
             Column('member_id', BigInteger, ForeignKey(fk_member_id)),
28
             Column('reason', String(120), nullable=False),
29
             Column('date', DateTime, nullable=False),
30
             )
31
32
coords = Table('coords', metadata_obj,
33
               Column('id', Integer, primary_key=True),
34
               Column('coords', String(100), nullable=False),
35
               Column('monster_type', String(20), nullable=False)
36
               )
37
38
spots = Table('spots', metadata_obj,
39
              Column('member_id', BigInteger, ForeignKey(fk_member_id), primary_key=True),
40
              Column('legendary', Integer, default=0),
41
              Column('rare', Integer, default=0),
42
              Column('common', Integer, default=0),
43
              Column('event1', Integer, default=0),
44
              Column('event2', Integer, default=0)
45
              )
46
47
spots_temp = Table('spots_temp', metadata_obj,
48
                   Column('member_id', BigInteger, ForeignKey(fk_member_id), primary_key=True),
49
                   Column('legendary', Integer, default=0),
50
                   Column('rare', Integer, default=0),
51
                   Column('common', Integer, default=0),
52
                   Column('event1', Integer, default=0),
53
                   Column('event2', Integer, default=0)
54
                   )
55
56
spots_lege = Table('spots_lege', metadata_obj,
57
                   Column('member_id', BigInteger, ForeignKey(fk_member_id), primary_key=True),
58
                   Column('AncientLeshen', Integer, default=0),
59
                   Column('Archgriffin', Integer, default=0),
60
                   Column('CopperWyvern', Integer, default=0),
61
                   Column('D\'jinni', Integer, default=0),
62
                   Column('DungShaelmaar', Integer, default=0),
63
                   Column('Erynia', Integer, default=0),
64
                   Column('Frightener', Integer, default=0),
65
                   Column('GraphiteSlyzard', Integer, default=0),
66
                   Column('GrimHag', Integer, default=0),
67
                   Column('Hym', Integer, default=0),
68
                   Column('IceElemental', Integer, default=0),
69
                   Column('IceGiant', Integer, default=0),
70
                   Column('IceTroll', Integer, default=0),
71
                   Column('Katakan', Integer, default=0),
72
                   Column('MottledGarkain', Integer, default=0),
73
                   Column('Penitent', Integer, default=0),
74
                   Column('PlagueMaiden', Integer, default=0),
75
                   Column('Sandcrab', Integer, default=0),
76
                   Column('SilverBasilisk', Integer, default=0),
77
                   Column('SwampHag', Integer, default=0),
78
                   Column('TarryChort', Integer, default=0),
79
                   Column('Thundster', Integer, default=0),
80
                   Column('Tormented', Integer, default=0),
81
                   Column('Ulfhedinn', Integer, default=0),
82
                   Column('UnseenElder', Integer, default=0),
83
                   Column('WaterDevil', Integer, default=0),
84
                   Column('WhiteStriga', Integer, default=0)
85
                   )
86
87
spots_rare = Table('spots_rare', metadata_obj,
88
                   Column('member_id', BigInteger, ForeignKey(fk_member_id), primary_key=True),
89
                   Column('Beann\'Shie', Integer, default=0),
90
                   Column('BlueForktail', Integer, default=0),
91
                   Column('Bruxa', Integer, default=0),
92
                   Column('Burier', Integer, default=0),
93
                   Column('Cockatrice', Integer, default=0),
94
                   Column('DepthLurker', Integer, default=0),
95
                   Column('Devourer', Integer, default=0),
96
                   Column('DrownedDead', Integer, default=0),
97
                   Column('EndregaCharger', Integer, default=0),
98
                   Column('EndregaWarrior', Integer, default=0),
99
                   Column('Farbaut', Integer, default=0),
100
                   Column('FireElemental', Integer, default=0),
101
                   Column('GarkainAlpha', Integer, default=0),
102
                   Column('Gernichora', Integer, default=0),
103
                   Column('GraySlyzard', Integer, default=0),
104
                   Column('GreenHarpy', Integer, default=0),
105
                   Column('Grimnir', Integer, default=0),
106
                   Column('Grottore', Integer, default=0),
107
                   Column('Howler', Integer, default=0),
108
                   Column('IgnisFatuus', Integer, default=0),
109
                   Column('Katakan', Integer, default=0),
110
                   Column('KikimoreWarrior', Integer, default=0),
111
                   Column('Leshen', Integer, default=0),
112
                   Column('LeshenHound', Integer, default=0),
113
                   Column('Liho', Integer, default=0),
114
                   Column('Lycanthrope', Integer, default=0),
115
                   Column('MagmaTroll', Integer, default=0),
116
                   Column('NekkerShaman', Integer, default=0),
117
                   Column('Nightmare', Integer, default=0),
118
                   Column('NightSuccubus', Integer, default=0),
119
                   Column('Putrifier', Integer, default=0),
120
                   Column('RoyalFoglet', Integer, default=0),
121
                   Column('RoyalNekker', Integer, default=0),
122
                   Column('RoyalWyvern', Integer, default=0),
123
                   Column('RussetShaelmaar', Integer, default=0),
124
                   Column('Scurver', Integer, default=0),
125
                   Column('Shrieker', Integer, default=0),
126
                   Column('SpottedAlghoul', Integer, default=0),
127
                   Column('StoneGolem', Integer, default=0),
128
                   Column('Stinger', Integer, default=0),
129
                   Column('Striga', Integer, default=0),
130
                   Column('SylvanDearg', Integer, default=0),
131
                   Column('Wailwraith', Integer, default=0),
132
                   Column('VizimianArchespore', Integer, default=0)
133
                   )
134
135
136
class DatabaseCog(cogbase.BaseCog):
137
    user = get_settings("DB_U")
138
    password = get_settings("DB_P")
139
    conn_string = f"mysql+mysqldb://{user}:{password}@localhost/server_database?charset=utf8mb4"
140
    engine = create_engine(conn_string, pool_recycle=3600)
141
    metadata_obj.create_all(engine)
142
    conn = None
143
144
    def __init__(self, base):
145
        super().__init__(base)
146
147
        # Connect to database
148
        self.engine = DatabaseCog.engine
149
        metadata_obj.create_all(self.engine)
150
        self.db_update_loop.start()
151
        self.conn = DatabaseCog.conn
152
153
    def cog_unload(self) -> None:
154
        self.db_update_loop.cancel()
155
156
    # ----- BASE DATABASE OPERATIONS -----
157
158
    # Add or update member in member table
159
    def db_add_update_member(self, guild_member) -> None:
160
        self.conn = self.engine.connect()
161
        stmt = insert(member).values(
162
            id=guild_member.id, name=guild_member.name,
163
            display_name=guild_member.display_name)
164
        do_update_stmt = stmt.on_duplicate_key_update(
165
            name=stmt.inserted.name, display_name=stmt.inserted.display_name
166
        )
167
        self.conn.execute(do_update_stmt)
168
        self.conn.close()
169
170
    # Add or update spots in spots table
171
    def db_add_update_spots(self, spots_table, guild_member) -> None:
172
        self.conn = self.engine.connect()
173
        stmt = insert(spots_table).values(
174
            member_id=guild_member.id)
175
        do_update_stmt = stmt.on_duplicate_key_update(member_id=stmt.inserted.member_id)
176
        self.conn.execute(do_update_stmt)
177
        self.conn.close()
178
179
    def add_update_member(self, guild_member):
180
        # Member tables
181
        self.db_add_update_member(guild_member)
182
        # Spots tables
183
        self.db_add_update_spots(spots, guild_member)
184
        self.db_add_update_spots(spots_temp, guild_member)
185
        self.db_add_update_spots(spots_lege, guild_member)
186
        self.db_add_update_spots(spots_rare, guild_member)
187
188
    # Add or refresh all guild members and spots to database
189
    async def db_update(self) -> None:
190
        self.conn = self.engine.connect()
191
        guild = self.bot.get_guild(self.bot.guild[0])
192
        self.create_log_msg("Refreshing member and spots tables")
193
        for guild_member in guild.members:
194
            self.add_update_member(guild_member)
195
        self.create_log_msg("Member and spots tables refreshed")
196
        self.conn.close()
197
198
    @tasks.loop(hours=12)
199
    async def db_update_loop(self) -> None:
200
        await self.db_update()
201
        await self.db_backup_database()
202
203
    @db_update_loop.before_loop
204
    async def before_db_update_loop(self) -> None:
205
        self.create_log_msg("Waiting until Bot is ready")
206
        await self.bot.wait_until_ready()
207
208
    # Add member to database on member join
209
    @commands.Cog.listener()
210
    async def on_member_join(self, guild_member) -> None:
211
        self.add_update_member(guild_member)
212
213
    # Backup database
214
    async def db_backup_database(self) -> None:
215
        now = datetime.now()
216
        backup_name = f"backup-{now.strftime('%m-%d-%Y')}"
217
        # Save backup to file
218
        backup_query = f"mysqldump -u {get_settings('DB_U')} " \
219
                       f"--result-file=database_backup/{backup_name}.sql " \
220
                       f"-p{get_settings('DB_P')} server_database"
221
        os.system(backup_query)
222
223
        zip_query = f"zip database_backup/{backup_name}.zip database_backup/{backup_name}.sql"
224
        os.system(zip_query)
225
226
        # Delete backup file
227
        rm_query = f"rm database_backup/{backup_name}.sql"
228
        os.system(rm_query)
229
230
        # # Send backup trough e-mail
231
        # mail_to = ""
232
        # mail_query = f"mail -a database_backup/{backup_name}.zip -s \"Backup {backup_name}\" {mail_to} <<< \" \""
233
        # os.system(mail_query)
234
235
        # Send backup to google drive
236
        try:
237
            drive_query = f"gdrive upload -p 1nXYNibvd4u-nWfj1tvIm7sz3Udh1ZD1k database_backup/{backup_name}.zip"
238
            os.system(drive_query)
239
        except Exception as e:
240
            pass
241
242
        self.create_log_msg("Database backed up")
243
244
    # ----- SPOTTING OPERATIONS -----
245
246
    # Update spots tables
247
    @classmethod
248
    async def db_count_spot(cls, _id: int, monster_type: str, monster_name: str) -> None:
249
        cls.conn = cls.engine.connect()
250
        cls.db_count_spot_table(spots, _id, monster_type, monster_name, False)
251
        cls.db_count_spot_table(spots_temp, _id, monster_type, monster_name, True)
252
        cls.conn.close()
253
254
    @classmethod
255
    def db_count_spot_table(cls, table, _id: int, monster_type: str, monster_name: str,
256
                            temp_table: bool = True) -> None:
257
        cls.conn = cls.engine.connect()
258
        stmt = select(table.c.member_id, table.c.legendary, table.c.rare, table.c.common,
259
                      table.c.event1,
260
                      table.c.event2).where(
261
            table.c.member_id == _id)
262
        result = cls.conn.execute(stmt)
263
        cls.conn.close()
264
        counter = 0
265
        for nr_of_kills in result.columns(monster_type, 'legendary'):
266
            counter = nr_of_kills[0]
267
        if monster_type == "event1":
268
            values = cls.db_count_spot_table_event(table, _id, monster_type, counter)
269
        else:
270
            values = {f"{monster_type}": counter + 1}
271
        stmt = update(table).where(table.c.member_id == _id).values(values)
272
        cls.conn = cls.engine.connect()
273
        cls.conn.execute(stmt)
274
        cls.conn.close()
275
        if not temp_table:
276
            cls.db_count_monster_spot(_id, monster_type, monster_name)
277
278
    @classmethod
279
    def db_count_monster_spot(cls, _id: int, monster_type: str, monster_name: str) -> None:
280
        bot_id = 881167775635234877
281
        if monster_type == "legendary":
282
            values_lege_member = cls.db_count_spot_table_monster(spots_lege, _id, monster_name)
283
            stmt = update(spots_lege).where(spots_lege.c.member_id == _id).values(values_lege_member)
284
            cls.conn = cls.engine.connect()
285
            cls.conn.execute(stmt)
286
            cls.conn.close()
287
            values_lege_total = cls.db_count_spot_table_monster(spots_lege, bot_id, monster_name)
288
            stmt = update(spots_lege).where(spots_lege.c.member_id == bot_id).values(values_lege_total)
289
            cls.conn = cls.engine.connect()
290
            cls.conn.execute(stmt)
291
            cls.conn.close()
292
        elif monster_type == "rare":
293
            values_rare_member = cls.db_count_spot_table_monster(spots_rare, _id, monster_name)
294
            stmt = update(spots_rare).where(spots_rare.c.member_id == _id).values(values_rare_member)
295
            cls.conn = cls.engine.connect()
296
            cls.conn.execute(stmt)
297
            cls.conn.close()
298
            values_rare_total = cls.db_count_spot_table_monster(spots_rare, bot_id, monster_name)
299
            stmt = update(spots_rare).where(spots_rare.c.member_id == bot_id).values(values_rare_total)
300
            cls.conn = cls.engine.connect()
301
            cls.conn.execute(stmt)
302
            cls.conn.close()
303
304
    @classmethod
305
    def db_count_spot_table_event(cls, table, _id, monster_type: str, counter: int) -> dict:
306
        cls.conn = cls.engine.connect()
307
        stmt = select(table.c.member_id, table.c.legendary, table.c.rare, table.c.common,
308
                      table.c.event1,
309
                      table.c.event2).where(
310
            table.c.member_id == _id)
311
        result = cls.conn.execute(stmt)
312
        counter_leg = 0
313
        for nr_of_kills_leg in result.columns('legendary'):
314
            counter_leg = nr_of_kills_leg[0]
315
        values = {f"{monster_type}": counter + 1, "legendary": counter_leg + 1}
316
        cls.conn.close()
317
        return values
318
319
    @classmethod
320
    def db_count_spot_table_monster(cls, table, guild_member_id: int, monster_name: str) -> dict:
321
        cls.conn = cls.engine.connect()
322
        stmt = select(table).where(table.c.member_id == guild_member_id)
323
        result = cls.conn.execute(stmt)
324
        counter = 0
325
        for nr_of_kills_leg in result.columns(f"{monster_name}"):
326
            counter = nr_of_kills_leg[0]
327
        values = {f"{monster_name}": counter + 1}
328
        cls.conn.close()
329
        return values
330
331
    # Save coords from spotting channels to database
332
    @classmethod
333
    async def db_save_coords(cls, coord: str, monster_type: str) -> None:
334
        cls.conn = cls.engine.connect()
335
        stmt = insert(coords).values(coords=coord, monster_type=monster_type)
336
        cls.conn.execute(stmt)
337
        cls.conn.close()
338
339
    # Clear data from spots_temp table(for events etc)
340
    @classmethod
341
    async def db_clear_spots_temp_table(cls) -> None:
342
        cls.conn = cls.engine.connect()
343
        stmt = delete(spots_temp)
344
        cls.conn.execute(stmt)
345
        cls.conn.close()
346
347
    # ----- LEADERBOARD OPERATIONS -----
348
349
    # Return total spotting stats
350
    @classmethod
351
    async def db_get_total_spots_df(cls, member_id: int, leaderboard_type: int) -> pd.DataFrame:
352
        df = pd.DataFrame
353
        cls.conn = cls.engine.connect()
354
        if leaderboard_type == 1:
355
            stmt = select(spots_lege)
356
            df = pd.read_sql(stmt, cls.conn)
357
        elif leaderboard_type == 0:
358
            stmt = select(spots_rare)
359
            df = pd.read_sql(stmt, cls.conn)
360
        df = df.loc[df['member_id'] == member_id]
361
        cls.conn.close()
362
        return df
363
364
    # Return all members' spots
365
    @classmethod
366
    async def db_get_spots_df(cls) -> pd.DataFrame:
367
        cls.conn = cls.engine.connect()
368
        stmt = select(spots.c.member_id, member.c.display_name, spots.c.legendary, spots.c.rare,
369
                      spots.c.common, spots.c.event1, spots.c.event2
370
                      ).select_from(member
371
                                    ).join(spots, member.c.id == spots.c.member_id)
372
        cls.conn.execute(stmt)
373
        df = pd.read_sql(stmt, cls.conn)
374
        cls.conn.close()
375
        return df
376
377
    @classmethod
378
    async def db_get_common_sum(cls) -> int:
379
        cls.conn = cls.engine.connect()
380
        stmt = select(func.sum(spots.c.common).label("sum"))
381
        result = cls.conn.execute(stmt)
382
        sum_common = 0
383
        for nr_of_kills_leg in result.columns("sum"):
384
            sum_common = nr_of_kills_leg[0]
385
        cls.conn.close()
386
        return sum_common
387
388
    @classmethod
389
    async def db_get_monster_spots_df(cls) -> pd.DataFrame:
390
        # TODO: Why does tables join not work?
391
        cls.conn = cls.engine.connect()
392
        stmt = select(spots_lege)
393
        cls.conn.execute(stmt)
394
        df_lege = pd.read_sql(stmt, cls.conn)
395
        cls.conn.close()
396
        cls.conn = cls.engine.connect()
397
        stmt = select(spots_rare)
398
        cls.conn.execute(stmt)
399
        df_rare = pd.read_sql(stmt, cls.conn)
400
        cls.conn.close()
401
        return pd.merge(df_lege, df_rare, on=["member_id"])
402
403
    @classmethod
404
    async def db_get_member_names(cls) -> pd.DataFrame:
405
        cls.conn = cls.engine.connect()
406
        stmt = select(member.c.id.label("member_id"), member.c.display_name)
407
        cls.conn.execute(stmt)
408
        df_member_names = pd.read_sql(stmt, cls.conn)
409
        cls.conn.close()
410
        return df_member_names
411
412
    # ----- WARN OPERATIONS -----
413
414
    # Add member's warn to database
415
    @classmethod
416
    async def db_add_warn(cls, guild_member_id: int, reason: str) -> None:
417
        cls.conn = cls.engine.connect()
418
        stmt = insert(warn).values(member_id=guild_member_id, reason=reason, date=datetime.now())
419
        cls.conn.execute(stmt)
420
        cls.conn.close()
421
422
    # Get member's warns from database
423
    @classmethod
424
    async def db_get_warns(cls, guild_member_id: int) -> tuple:
425
        cls.conn = cls.engine.connect()
426
        stmt = select(warn.c.reason, warn.c.date).select_from(member).join(warn, member.c.id == warn.c.member_id).where(
427
            member.c.id == guild_member_id)
428
        result = cls.conn.execute(stmt)
429
        date_warn = []
430
        counter = 0
431
        for warns in result.columns("reason", "date"):
432
            reason_with_date = [warns[1], warns[0]]
433
            date_warn.append(reason_with_date)
434
            counter += 1
435
        warns_list = [': \t'.join(str(elem) for elem in sublist) for sublist in date_warn]
436
437
        cls.conn.close()
438
        return warns_list, counter
439
440
    # Remove all member's warns
441
    @classmethod
442
    async def db_remove_warns(cls, guild_member: int) -> None:
443
        cls.conn = cls.engine.connect()
444
        stmt = delete(warn).where(warn.c.member_id == guild_member)
445
        cls.conn.execute(stmt)
446
        cls.conn.close()
447
448
    # ----- MEMBER OPERATIONS -----
449
450
    # Return member's spots
451
    @classmethod
452
    async def db_get_member_stats(cls, guild_member: int) -> pd.DataFrame:
453
        cls.conn = cls.engine.connect()
454
        stmt = select(spots.c.member_id, member.c.display_name, spots.c.legendary, spots.c.rare, spots.c.common
455
                      ).select_from(member).join(spots,
456
                                                 member.c.id == spots.c.member_id) \
457
            .where(spots.c.member_id == guild_member)
458
        df = pd.read_sql(stmt, cls.conn)
459
        cls.conn.close()
460
        return df
461
462
    @classmethod
463
    async def db_get_member_monsters(cls, guild_member: int) -> pd.DataFrame:
464
        cls.conn = cls.engine.connect()
465
        stmt = select(spots_lege).where(spots_lege.c.member_id == guild_member)
466
        cls.conn.execute(stmt)
467
        df_lege = pd.read_sql(stmt, cls.conn)
468
        cls.conn.close()
469
        cls.conn = cls.engine.connect()
470
        stmt = select(spots_rare).where(spots_rare.c.member_id == guild_member)
471
        cls.conn.execute(stmt)
472
        df_rare = pd.read_sql(stmt, cls.conn)
473
        cls.conn.close()
474
        return pd.merge(df_lege, df_rare, on=["member_id"])
475
476
    @cog_ext.cog_slash(name="changeMemberSpots", guild_ids=cogbase.GUILD_IDS,
477
                       description="Change member spotting stats",
478
                       default_permission=False,
479
                       permissions=cogbase.PERMISSION_ADMINS)
480
    async def change_member_spots(self, ctx: SlashContext, user: discord.Member, spot_type: str, number: int) -> None:
481
        self.conn = self.engine.connect()
482
        stmt = f"""UPDATE server_database.spots SET {spot_type} = {number} """ \
483
               f"""WHERE (member_id = {user.id});"""
484
        self.conn.execute(stmt)
485
        await ctx.send(f"{user.display_name} spots changed", hidden=True)
486
        self.conn.close()
487
488
    # ----- COORDS OPERATIONS -----
489
490
    # Return coords
491
    @classmethod
492
    async def db_get_coords(cls) -> pd.DataFrame:
493
        cls.conn = cls.engine.connect()
494
        stmt = select(coords.c.id, coords.c.coords, coords.c.monster_type).select_from(coords)
495
        df = pd.read_sql(stmt, cls.conn)
496
        cls.conn.close()
497
        return df
498
499
500
def setup(bot: commands.Bot) -> None:
501
    bot.add_cog(DatabaseCog(bot))
502