Add/remove/fix/change things
This commit is contained in:
parent
0a7b423ea3
commit
f59f29bc1e
3 changed files with 223 additions and 159 deletions
|
@ -13,12 +13,14 @@
|
|||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from typing import Awaitable
|
||||
from typing import Awaitable, Type
|
||||
|
||||
from sqlalchemy.engine.base import Engine
|
||||
|
||||
from maubot import Plugin, CommandSpec, Command, PassiveCommand, Argument, MessageEvent
|
||||
from mautrix.types import Event, StateEvent
|
||||
|
||||
from .db import make_tables
|
||||
from .db import make_tables, Karma, KarmaCache, Version
|
||||
|
||||
COMMAND_PASSIVE_UPVOTE = "xyz.maubot.karma.up"
|
||||
COMMAND_PASSIVE_DOWNVOTE = "xyz.maubot.karma.down"
|
||||
|
@ -26,28 +28,36 @@ COMMAND_PASSIVE_DOWNVOTE = "xyz.maubot.karma.down"
|
|||
ARG_LIST = "$list"
|
||||
COMMAND_KARMA_LIST = f"karma {ARG_LIST}"
|
||||
|
||||
COMMAND_OWN_KARMA = "karma"
|
||||
|
||||
COMMAND_UPVOTE = "upvote"
|
||||
COMMAND_DOWNVOTE = "downvote"
|
||||
|
||||
UPVOTE_EMOJI = r"(?:\x{1f44d}[\x{1f3fb}-\x{1f3ff}]?)"
|
||||
UPVOTE_EMOJI_SHORTHAND = r"(?:\:\+1?|-?\:)|(?:\:thumbsup\:)"
|
||||
UPVOTE_TEXT = r"(?:\+1?|\+?)"
|
||||
UPVOTE_EMOJI = r"(?:\U0001F44D[\U0001F3FB-\U0001F3FF]?)"
|
||||
UPVOTE_EMOJI_SHORTHAND = r"(?:\:\+1\:)|(?:\:thumbsup\:)"
|
||||
UPVOTE_TEXT = r"(?:\+(?:1|\+)?)"
|
||||
UPVOTE = f"{UPVOTE_EMOJI}|{UPVOTE_EMOJI_SHORTHAND}|{UPVOTE_TEXT}"
|
||||
|
||||
DOWNVOTE_EMOJI = r"(?:\x{1f44e}[\x{1f3fb}-\x{1f3ff}]?)"
|
||||
DOWNVOTE_EMOJI_SHORTHAND = r"(?:\:-1?|-?\:)|(?:\:thumbsdown\:)"
|
||||
DOWNVOTE_TEXT = r"(?:-1?|-?)"
|
||||
DOWNVOTE_EMOJI = r"(?:\U0001F44E[\U0001F3FB-\U0001F3FF]?)"
|
||||
DOWNVOTE_EMOJI_SHORTHAND = r"(?:\:-1\:)|(?:\:thumbsdown\:)"
|
||||
DOWNVOTE_TEXT = r"(?:-(?:1|-)?)"
|
||||
DOWNVOTE = f"{DOWNVOTE_EMOJI}|{DOWNVOTE_EMOJI_SHORTHAND}|{DOWNVOTE_TEXT}"
|
||||
|
||||
|
||||
class KarmaBot(Plugin):
|
||||
karma_cache: Type[KarmaCache]
|
||||
karma: Type[Karma]
|
||||
version: Type[Version]
|
||||
db: Engine
|
||||
|
||||
async def start(self) -> None:
|
||||
self.db = self.request_db_engine()
|
||||
self.KarmaCache, self.Karma = make_tables(self.db)
|
||||
self.karma_cache, self.karma, self.version = make_tables(self.db)
|
||||
self.set_command_spec(CommandSpec(commands=[
|
||||
Command(syntax=COMMAND_KARMA_LIST, description="View your karma or karma top lists",
|
||||
Command(syntax=COMMAND_KARMA_LIST, description="View the karma top lists",
|
||||
arguments={ARG_LIST: Argument(matches="(top|bot(tom)?|high(score)?|low)",
|
||||
required=False, description="The list to view")}),
|
||||
required=True, description="The list to view")}),
|
||||
Command(syntax=COMMAND_OWN_KARMA, description="View your karma"),
|
||||
Command(syntax=COMMAND_UPVOTE, description="Upvote a message"),
|
||||
Command(syntax=COMMAND_DOWNVOTE, description="Downvote a message"),
|
||||
], passive_commands=[
|
||||
|
@ -60,6 +70,7 @@ class KarmaBot(Plugin):
|
|||
self.client.add_command_handler(COMMAND_UPVOTE, self.upvote)
|
||||
self.client.add_command_handler(COMMAND_DOWNVOTE, self.downvote)
|
||||
self.client.add_command_handler(COMMAND_KARMA_LIST, self.view_karma_list)
|
||||
self.client.add_command_handler(COMMAND_OWN_KARMA, self.view_karma)
|
||||
|
||||
async def stop(self) -> None:
|
||||
self.client.remove_command_handler(COMMAND_PASSIVE_UPVOTE, self.upvote)
|
||||
|
@ -67,6 +78,7 @@ class KarmaBot(Plugin):
|
|||
self.client.remove_command_handler(COMMAND_UPVOTE, self.upvote)
|
||||
self.client.remove_command_handler(COMMAND_DOWNVOTE, self.downvote)
|
||||
self.client.remove_command_handler(COMMAND_KARMA_LIST, self.view_karma_list)
|
||||
self.client.remove_command_handler(COMMAND_OWN_KARMA, self.view_karma)
|
||||
|
||||
@staticmethod
|
||||
def parse_content(evt: Event) -> str:
|
||||
|
@ -94,14 +106,14 @@ class KarmaBot(Plugin):
|
|||
return
|
||||
karma_id = dict(given_to=karma_target.sender, given_by=evt.sender, given_in=evt.room_id,
|
||||
given_for=karma_target.event_id)
|
||||
existing = self.Karma.get(**karma_id)
|
||||
existing = self.karma.get(**karma_id)
|
||||
if existing is not None:
|
||||
if existing.value == value:
|
||||
await evt.reply(f"You already {self.sign(value)}'d that message.")
|
||||
return
|
||||
existing.update(new_value=value)
|
||||
else:
|
||||
karma = self.Karma(**karma_id, given_from=evt.event_id, value=value,
|
||||
karma = self.karma(**karma_id, given_from=evt.event_id, value=value,
|
||||
content=self.parse_content(karma_target))
|
||||
karma.insert()
|
||||
await evt.mark_read()
|
||||
|
@ -112,23 +124,25 @@ class KarmaBot(Plugin):
|
|||
def downvote(self, evt: MessageEvent) -> Awaitable[None]:
|
||||
return self.vote(evt, -1)
|
||||
|
||||
async def view_karma(self, evt: MessageEvent) -> None:
|
||||
karma = self.karma_cache.get_karma(evt.sender)
|
||||
if karma is None:
|
||||
await evt.reply("You don't have any karma :(")
|
||||
return
|
||||
index = self.karma_cache.find_index_from_top(evt.sender)
|
||||
await evt.reply(f"You have {karma} karma and are #{index} on the top list.")
|
||||
|
||||
async def view_karma_list(self, evt: MessageEvent) -> None:
|
||||
list_type = evt.content.command.arguments[ARG_LIST]
|
||||
if not list_type:
|
||||
karma = self.KarmaCache.get_karma(evt.sender)
|
||||
if karma is None:
|
||||
await evt.reply("You don't have any karma :(")
|
||||
return
|
||||
index = self.KarmaCache.find_index_from_top(evt.sender)
|
||||
await evt.reply(f"You have {karma} karma and are #{index} on the top list.")
|
||||
await evt.reply("**Usage**: !karma [top|bottom]")
|
||||
return
|
||||
|
||||
if list_type in ("top", "high", "highscore"):
|
||||
karma_list = self.KarmaCache.get_high()
|
||||
message = "### Highest karma\n\n"
|
||||
karma_list = self.karma_cache.get_high()
|
||||
message = "#### Highest karma\n\n"
|
||||
elif list_type in ("bot", "bottom", "low"):
|
||||
karma_list = self.KarmaCache.get_low()
|
||||
message = "### Lowest karma\n\n"
|
||||
karma_list = self.karma_cache.get_low()
|
||||
message = "#### Lowest karma\n\n"
|
||||
else:
|
||||
return
|
||||
message += "\n".join(f"{index + 1}. [{mxid}](https://matrix.to/#/{mxid}): {karma}"
|
||||
|
|
295
karma/db.py
295
karma/db.py
|
@ -13,10 +13,10 @@
|
|||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from typing import List, Tuple, Optional
|
||||
from typing import List, Tuple, Optional, Type
|
||||
from time import time
|
||||
|
||||
from sqlalchemy import Column, String, Integer, BigInteger, Text, Table, select
|
||||
from sqlalchemy import Column, String, Integer, BigInteger, Text, Table, select, and_
|
||||
from sqlalchemy.sql.base import ImmutableColumnCollection
|
||||
from sqlalchemy.engine.base import Engine, Connection
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
@ -24,146 +24,171 @@ from sqlalchemy.ext.declarative import declarative_base
|
|||
from mautrix.types import Event, UserID, EventID, RoomID
|
||||
|
||||
|
||||
def make_tables(engine: Engine):
|
||||
class KarmaCache:
|
||||
__tablename__ = "karma_cache"
|
||||
db: Engine = None
|
||||
t: Table = None
|
||||
c: ImmutableColumnCollection = None
|
||||
Karma: Type['Karma'] = None
|
||||
|
||||
user_id: UserID = Column(String(255), primary_key=True)
|
||||
karma: int = Column(Integer)
|
||||
|
||||
@classmethod
|
||||
def get_karma(cls, user_id: UserID) -> Optional[int]:
|
||||
rows = cls.db.execute(select([cls.c.karma]).where(cls.c.user_id == user_id))
|
||||
try:
|
||||
row = next(rows)
|
||||
return row[0]
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _set_karma(cls, user_id: UserID, karma: int, conn: Connection) -> None:
|
||||
conn.execute(cls.t.delete().where(cls.c.user_id == user_id))
|
||||
conn.execute(cls.t.insert().values(user_id=user_id, karma=karma))
|
||||
|
||||
@classmethod
|
||||
def set_karma(cls, user_id: UserID, karma: int, conn: Optional[Connection] = None) -> None:
|
||||
if conn:
|
||||
cls._set_karma(user_id, karma, conn)
|
||||
else:
|
||||
with cls.db.begin() as conn:
|
||||
cls._set_karma(user_id, karma, conn)
|
||||
|
||||
@classmethod
|
||||
def get_high(cls, limit: int = 10) -> List[Tuple[UserID, int]]:
|
||||
return list(cls.db.execute(cls.t.select().order_by(cls.c.karma.desc()).limit(limit)))
|
||||
|
||||
@classmethod
|
||||
def get_low(cls, limit: int = 10) -> List[Tuple[UserID, int]]:
|
||||
return list(cls.db.execute(cls.t.select().order_by(cls.c.karma.asc()).limit(limit)))
|
||||
|
||||
@classmethod
|
||||
def find_index_from_top(cls, user_id: UserID) -> int:
|
||||
i = 0
|
||||
for (found,) in cls.db.execute(select([cls.c.user_id]).order_by(cls.c.karma.desc())):
|
||||
i += 1
|
||||
if found == user_id:
|
||||
return i
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
def recalculate(cls, user_id: UserID) -> None:
|
||||
with cls.db.begin() as txn:
|
||||
cls.set_karma(user_id, sum(entry.value for entry in cls.Karma.all(user_id)), txn)
|
||||
|
||||
@classmethod
|
||||
def update(cls, user_id: UserID, value_diff: int, conn: Optional[Connection],
|
||||
ignore_if_not_exist: bool = False) -> None:
|
||||
if not conn:
|
||||
conn = cls.db
|
||||
existing = conn.execute(select([cls.c.karma]).where(cls.c.user_id == user_id))
|
||||
try:
|
||||
karma = next(existing)[0] + value_diff
|
||||
conn.execute(cls.t.update().where(cls.c.user_id == user_id).values(karma=karma))
|
||||
except StopIteration:
|
||||
if ignore_if_not_exist:
|
||||
return
|
||||
conn.execute(cls.t.insert().values(user_id=user_id, karma=value_diff))
|
||||
|
||||
|
||||
class Karma:
|
||||
__tablename__ = "karma"
|
||||
db: Engine = None
|
||||
t: Table = None
|
||||
c: ImmutableColumnCollection = None
|
||||
KarmaCache: Type[KarmaCache] = None
|
||||
|
||||
given_to: UserID = Column(String(255), primary_key=True)
|
||||
given_by: UserID = Column(String(255), primary_key=True)
|
||||
given_in: RoomID = Column(String(255), primary_key=True)
|
||||
given_for: EventID = Column(String(255), primary_key=True)
|
||||
|
||||
given_from: EventID = Column(String(255))
|
||||
given_at: int = Column(BigInteger)
|
||||
value: int = Column(Integer)
|
||||
content: str = Column(Text)
|
||||
|
||||
@classmethod
|
||||
def all(cls, user_id: UserID) -> List['Karma']:
|
||||
return [cls(given_to=given_to, given_by=given_by, given_in=given_in, given_for=given_for,
|
||||
given_from=given_from, given_at=given_at, value=value, content=content)
|
||||
for given_to, given_by, given_in, given_for, given_from, given_at, value, content
|
||||
in cls.db.execute(cls.t.select().where(cls.c.given_to == user_id))]
|
||||
|
||||
@classmethod
|
||||
def get(cls, given_to: UserID, given_by: UserID, given_in: RoomID, given_for: Event
|
||||
) -> Optional['Karma']:
|
||||
rows = cls.db.execute(cls.t.select().where(and_(
|
||||
cls.c.given_to == given_to, cls.c.given_by == given_by,
|
||||
cls.c.given_in == given_in, cls.c.given_for == given_for)))
|
||||
try:
|
||||
(given_to, given_by, given_in, given_for,
|
||||
given_from, given_at, value, content) = next(rows)
|
||||
except StopIteration:
|
||||
return None
|
||||
return cls(given_to=given_to, given_by=given_by, given_in=given_in, given_for=given_for,
|
||||
given_from=given_from, given_at=given_at, value=value, content=content)
|
||||
|
||||
def delete(self) -> None:
|
||||
with self.db.begin() as txn:
|
||||
txn.execute(self.t.delete().where(and_(
|
||||
self.c.given_to == self.given_to, self.c.given_by == self.given_by,
|
||||
self.c.given_in == self.given_in, self.c.given_for == self.given_for)))
|
||||
self.KarmaCache.update(self.given_to, self.value, txn, ignore_if_not_exist=True)
|
||||
|
||||
def insert(self) -> None:
|
||||
self.given_at = int(time() * 1000)
|
||||
with self.db.begin() as txn:
|
||||
txn.execute(self.t.insert().values(given_to=self.given_to, given_by=self.given_by,
|
||||
given_in=self.given_in, given_for=self.given_for,
|
||||
given_from=self.given_from, value=self.value,
|
||||
given_at=self.given_at, content=self.content))
|
||||
self.KarmaCache.update(self.given_to, self.value, txn)
|
||||
|
||||
def update(self, new_value: int) -> None:
|
||||
self.given_at = int(time() * 1000)
|
||||
value_diff = new_value - self.value
|
||||
self.value = new_value
|
||||
with self.db.begin() as txn:
|
||||
txn.execute(self.t.update().where(and_(
|
||||
self.c.given_to == self.given_to, self.c.given_by == self.given_by,
|
||||
self.c.given_in == self.given_in, self.c.given_for == self.given_for
|
||||
)).values(given_from=self.given_from, value=self.value, given_at=self.given_at))
|
||||
self.KarmaCache.update(self.given_to, value_diff, txn)
|
||||
|
||||
|
||||
class Version:
|
||||
__tablename__ = "version"
|
||||
db: Engine = None
|
||||
t: Table = None
|
||||
c: ImmutableColumnCollection = None
|
||||
|
||||
version: int = Column(Integer, primary_key=True)
|
||||
|
||||
|
||||
def make_tables(engine: Engine) -> Tuple[Type[KarmaCache], Type[Karma], Type[Version]]:
|
||||
base = declarative_base()
|
||||
|
||||
class KarmaCache(base):
|
||||
__tablename__ = "karma_cache"
|
||||
db: Engine = engine
|
||||
t: Table = None
|
||||
c: ImmutableColumnCollection = None
|
||||
class KarmaCacheImpl(KarmaCache, base):
|
||||
__table__: Table
|
||||
|
||||
user_id: UserID = Column(String(255), primary_key=True)
|
||||
karma: int = Column(Integer)
|
||||
class KarmaImpl(Karma, base):
|
||||
__table__: Table
|
||||
|
||||
@classmethod
|
||||
def get_karma(cls, user_id: UserID) -> Optional[int]:
|
||||
rows = cls.db.execute(select([cls.c.karma]).where(cls.c.user_id == user_id))
|
||||
try:
|
||||
row = next(rows)
|
||||
return row[0]
|
||||
except (StopIteration, IndexError):
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _set_karma(cls, user_id: UserID, karma: int, conn: Connection) -> None:
|
||||
conn.execute(cls.t.delete().where(cls.c.user_id == user_id))
|
||||
conn.execute(cls.t.insert().values(user_id=user_id, karma=karma))
|
||||
|
||||
@classmethod
|
||||
def set_karma(cls, user_id: UserID, karma: int, conn: Optional[Connection] = None) -> None:
|
||||
if conn:
|
||||
cls._set_karma(user_id, karma, conn)
|
||||
else:
|
||||
with cls.db.begin() as conn:
|
||||
cls._set_karma(user_id, karma, conn)
|
||||
|
||||
@classmethod
|
||||
def get_high(cls, limit: int = 10) -> List[Tuple[UserID, int]]:
|
||||
return list(cls.db.execute(cls.t.select().order_by(cls.c.karma.desc()).limit(limit)))
|
||||
|
||||
@classmethod
|
||||
def get_low(cls, limit: int = 10) -> List[Tuple[UserID, int]]:
|
||||
return list(cls.db.execute(cls.t.select().order_by(cls.c.karma.asc()).limit(limit)))
|
||||
|
||||
@classmethod
|
||||
def find_index_from_top(cls, user_id: UserID) -> int:
|
||||
i = 0
|
||||
for (found,) in cls.db.execute(select([cls.c.user_id]).order_by(cls.c.karma.desc())):
|
||||
i += 1
|
||||
if found == user_id:
|
||||
return i
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
def recalculate(cls, user_id: UserID) -> None:
|
||||
with cls.db.begin() as txn:
|
||||
cls.set_karma(user_id, sum(entry.value for entry in Karma.all(user_id)), txn)
|
||||
|
||||
@classmethod
|
||||
def update(cls, user_id: UserID, value_diff: int, conn: Optional[Connection],
|
||||
ignore_if_not_exist: bool = False) -> None:
|
||||
if not conn:
|
||||
conn = cls.db
|
||||
existing = conn.execute(select([cls.c.karma]).where(cls.c.user_id == user_id))
|
||||
try:
|
||||
karma = next(existing)[0] + value_diff
|
||||
conn.execute(cls.t.update().where(cls.c.user_id == user_id).values(karma=karma))
|
||||
except (StopIteration, IndexError):
|
||||
if ignore_if_not_exist:
|
||||
return
|
||||
conn.execute(cls.t.insert().values(user_id=user_id, karma=value_diff))
|
||||
|
||||
class Karma(base):
|
||||
__tablename__ = "karma"
|
||||
db: Engine = engine
|
||||
t: Table = None
|
||||
c: ImmutableColumnCollection = None
|
||||
|
||||
given_to: UserID = Column(String(255), primary_key=True)
|
||||
given_by: UserID = Column(String(255), primary_key=True)
|
||||
given_in: RoomID = Column(String(255), primary_key=True)
|
||||
given_for: EventID = Column(String(255), primary_key=True)
|
||||
|
||||
given_from: EventID = Column(String(255))
|
||||
given_at: int = Column(BigInteger)
|
||||
value: int = Column(Integer)
|
||||
content: str = Column(Text)
|
||||
|
||||
@classmethod
|
||||
def all(cls, user_id: UserID) -> List['Karma']:
|
||||
return [Karma(*row) for row in
|
||||
cls.db.execute(cls.t.select().where(cls.c.given_to == user_id))]
|
||||
|
||||
@classmethod
|
||||
def get(cls, given_to: UserID, given_by: UserID, given_in: RoomID, given_for: Event
|
||||
) -> Optional['Karma']:
|
||||
rows = cls.db.execute(cls.t.select()
|
||||
.where(cls.c.given_to == given_to, cls.c.given_by == given_by,
|
||||
cls.c.given_in == given_in, cls.c.given_for == given_for))
|
||||
try:
|
||||
given_to, given_by, given_in, given_for, given_at, value, content = next(rows)
|
||||
return Karma(given_to, given_by, given_in, given_for, given_at, value, content)
|
||||
except (StopIteration, ValueError):
|
||||
return None
|
||||
|
||||
def delete(self) -> None:
|
||||
with self.db.begin() as txn:
|
||||
txn.execute(self.t.delete().where(
|
||||
self.c.given_to == self.given_to, self.c.given_by == self.given_by,
|
||||
self.c.given_in == self.given_in, self.c.given_for == self.given_for))
|
||||
KarmaCache.update(self.given_to, self.value, txn, ignore_if_not_exist=True)
|
||||
|
||||
def insert(self) -> None:
|
||||
self.given_at = int(time() * 1000)
|
||||
with self.db.begin() as txn:
|
||||
txn.execute(self.t.insert().values(given_to=self.given_to, given_by=self.given_by,
|
||||
given_in=self.given_in, given_for=self.given_for,
|
||||
given_from=self.given_from, value=self.value,
|
||||
given_at=self.given_at, content=self.content))
|
||||
KarmaCache.update(self.given_to, self.value, txn)
|
||||
|
||||
def update(self, new_value: int) -> None:
|
||||
self.given_at = int(time() * 1000)
|
||||
value_diff = new_value - self.value
|
||||
self.value = new_value
|
||||
with self.db.begin() as txn:
|
||||
txn.execute(self.t.update()
|
||||
.where(self.c.given_to == self.given_to,
|
||||
self.c.given_by == self.given_by,
|
||||
self.c.given_in == self.given_in,
|
||||
self.c.given_for == self.given_for)
|
||||
.values(given_from=self.given_from, value=self.value,
|
||||
given_at=self.given_at))
|
||||
KarmaCache.update(self.given_to, value_diff, txn)
|
||||
class VersionImpl(Version, base):
|
||||
__table__: Table
|
||||
|
||||
base.metadata.bind = engine
|
||||
KarmaCache.t = KarmaCache.__table__
|
||||
KarmaCache.c = KarmaCache.t.c
|
||||
Karma.t = Karma.__table__
|
||||
Karma.c = Karma.t.c
|
||||
for table in KarmaCacheImpl, KarmaImpl, VersionImpl:
|
||||
table.db = engine
|
||||
table.t = table.__table__
|
||||
table.c = table.__table__.c
|
||||
table.Karma = KarmaImpl
|
||||
table.KarmaCache = KarmaCacheImpl
|
||||
|
||||
# TODO replace with alembic
|
||||
base.metadata.create_all()
|
||||
|
||||
return KarmaCache, Karma
|
||||
return KarmaCacheImpl, KarmaImpl, VersionImpl
|
||||
|
|
25
karma/migrations.py
Normal file
25
karma/migrations.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# karma - A maubot plugin to track the karma of users.
|
||||
# Copyright (C) 2018 Tulir Asokan
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.engine.base import Engine
|
||||
from alembic.migration import MigrationContext
|
||||
from alembic.operations import Operations
|
||||
|
||||
|
||||
def run(engine: Engine):
|
||||
conn = engine.connect()
|
||||
ctx = MigrationContext.configure(conn)
|
||||
op = Operations(ctx)
|
Loading…
Reference in a new issue