/
__main__.py
158 lines (124 loc) · 4.56 KB
/
__main__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import asyncio
import contextlib
import functools
import logging
import os
import sys
import time
import traceback
import types
import warnings
import asyncpg
import click
import uvloop
from ruamel import yaml
from bot import Bot
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
os.chdir(os.path.dirname(os.path.abspath(__file__)))
def get_config():
with open('config.yaml') as file:
config = yaml.YAML().load(file)
def convert(item):
if isinstance(item, dict):
for key, value in item.items():
item[key] = convert(value)
return types.SimpleNamespace(**item)
return item
return convert(config)
@contextlib.contextmanager
def setup_logging():
logger_discord = logging.getLogger('discord')
logger_discord.setLevel(logging.WARNING)
logger_warnings = logging.getLogger('py.warnings')
logger_warnings.setLevel(logging.WARNING)
logging.captureWarnings(True)
warnings.filterwarnings('always')
warnings.filterwarnings(
'ignore', message='Not importing directory .*: missing __init__',
category=ImportWarning, module='importlib')
handler = logging.FileHandler(filename='discord.log', encoding='utf-8')
formatter = logging.Formatter(
'[{asctime}] [{levelname}] {name}: {message}', style='{')
formatter.datefmt = '%Y-%m-%d %H:%M:%S'
formatter.converter = time.gmtime
handler.setFormatter(formatter)
logger_discord.addHandler(handler)
logger_warnings.addHandler(handler)
yield
logging.shutdown()
@click.group(invoke_without_command=True)
@click.pass_context
@setup_logging()
def main(ctx):
if ctx.invoked_subcommand is not None:
return
config = get_config()
bot = Bot(config=config)
for extension in config.extensions:
try:
bot.load_extension(f'extensions.{extension}')
except Exception:
with contextlib.redirect_stdout(sys.stderr):
print(f'Failed to load extension {extension}.')
# Try to only show the traceback starting from the extension
tb = sys.exc_info()[2]
frames = traceback.extract_tb(tb)
for i, frame in enumerate(frames):
filename = os.path.normpath(frame.filename)
ext_path = os.path.join('extensions', extension)
if filename.startswith(ext_path):
traceback.print_exc(limit=i - len(frames))
break
else:
traceback.print_exc()
print()
bot.run(config.settings.token)
@main.group()
@click.pass_context
def db(ctx):
config = get_config()
loop = asyncio.get_event_loop()
connection = loop.run_until_complete(asyncpg.connect(config.settings.dsn))
ctx.obj = types.SimpleNamespace(loop=loop, connection=connection)
ctx.call_on_close(functools.partial(
loop.run_until_complete, connection.close()))
async def execute_file(path, *, connection):
with open(path) as file:
query = file.read()
await connection.execute(query)
async def execute_extensions(extensions, *, all=False, file, connection):
if all:
extensions = [extension for extension in os.listdir('extensions')
if os.path.exists(os.path.join(
'extensions', extension, 'sql', file))]
async with connection.transaction():
for extension in extensions:
path = os.path.join('extensions', extension, 'sql', file)
await execute_file(path, connection=connection)
@db.command()
@click.argument('extensions', nargs=-1)
@click.option('--all', is_flag=True)
@click.pass_obj
def create(obj, *args, **kwargs):
obj.loop.run_until_complete(execute_extensions(
*args, **kwargs, file='create.sql', connection=obj.connection))
@db.command()
@click.argument('extensions', nargs=-1)
@click.option('--all', is_flag=True)
@click.confirmation_option()
@click.pass_obj
def drop(obj, *args, **kwargs):
obj.loop.run_until_complete(execute_extensions(
*args, **kwargs, file='drop.sql', connection=obj.connection))
@db.command()
@click.argument('extension')
@click.pass_obj
def migrate(obj, extension):
path = os.path.join('extensions', extension, 'sql', 'migrations')
migrations = os.listdir(path)
migration = sorted(migrations, reverse=True)[0]
click.confirm(f"Is {migration} the right migration?", abort=True, err=True)
obj.loop.run_until_complete(execute_file(
migration, connection=obj.connection))
if __name__ == '__main__':
main()