Upgrade to SQLAlchemy 2.0

This has exposed an interesting amount of bugs and stopped throwing type errors every 5 seconds

It's worth noting that not all models are fully typed, that is, we
have `Mapped[Any]` in some places where a narrower type would be nice.

Upgrading to SQLA2 we don't *need* this, but it is helpful and
makes error checking reliable.
This commit is contained in:
justcool393 2023-08-09 02:27:55 -05:00
parent 65c50e4e4a
commit e040ed6708
27 changed files with 364 additions and 330 deletions

View file

@ -1,15 +1,17 @@
from sqlalchemy import *
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm.decl_api import DeclarativeBase, declared_attr
from files.classes.base import Base
class Alt(Base):
__tablename__ = "alts"
user1 = Column(Integer, ForeignKey("users.id"), primary_key=True)
user2 = Column(Integer, ForeignKey("users.id"), primary_key=True)
is_manual = Column(Boolean, nullable=False, default=False)
user1: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), primary_key=True)
user2: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), primary_key=True)
is_manual: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
Index('alts_user2_idx', user2)
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id})>"
return f"<{self.__class__.__name__}(user1={self.user1}, user2={self.user2})>"

View file

@ -1,9 +1,11 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import Base
from files.helpers.config.const import AWARDS
from files.helpers.lazy import lazy
class AwardRelationship(Base):
__tablename__ = "award_relationships"
@ -11,11 +13,11 @@ class AwardRelationship(Base):
UniqueConstraint('user_id', 'submission_id', 'comment_id', name='award_constraint'),
)
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
submission_id = Column(Integer, ForeignKey("submissions.id"))
comment_id = Column(Integer, ForeignKey("comments.id"))
kind = Column(String, nullable=False)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
submission_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("submissions.id"))
comment_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("comments.id"))
kind: Mapped[str] = mapped_column(String, nullable=False)
user = relationship("User", primaryjoin="AwardRelationship.user_id==User.id", viewonly=True)
post = relationship("Submission", primaryjoin="AwardRelationship.submission_id==Submission.id", viewonly=True)

View file

@ -3,6 +3,7 @@ from sqlalchemy.orm import relationship
from files.classes.base import Base
from files.helpers.lazy import lazy
from files.helpers.config.const import *
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.helpers.assetcache import assetcache_path
class BadgeDef(Base):
@ -11,9 +12,9 @@ class BadgeDef(Base):
UniqueConstraint('name', name='badge_def_name_unique'),
)
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
description = Column(String)
id = mapped_column(Integer, primary_key=True, autoincrement=True)
name = mapped_column(String, nullable=False)
description = mapped_column(String)
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id})>"
@ -22,10 +23,10 @@ class Badge(Base):
__tablename__ = "badges"
user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)
badge_id = Column(Integer, ForeignKey('badge_defs.id'), primary_key=True)
description = Column(String)
url = Column(String)
user_id = mapped_column(Integer, ForeignKey('users.id'), primary_key=True)
badge_id = mapped_column(Integer, ForeignKey('badge_defs.id'), primary_key=True)
description = mapped_column(String)
url = mapped_column(String)
Index('badges_badge_id_idx', badge_id)

View file

@ -2,14 +2,16 @@ import time
from datetime import datetime, timedelta, timezone
from sqlalchemy import text
from sqlalchemy.orm import declarative_base, declared_attr
from sqlalchemy.schema import Column
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm.decl_api import DeclarativeBase, declared_attr
from sqlalchemy.sql.functions import now
from sqlalchemy.sql.sqltypes import Integer, DateTime
from sqlalchemy.sql.sqltypes import DateTime, Integer
from files.helpers.time import format_age, format_datetime
Base = declarative_base()
class Base(DeclarativeBase):
pass
class CreatedBase(Base):
@ -21,8 +23,8 @@ class CreatedBase(Base):
super().__init__(*args, **kwargs)
@declared_attr
def created_utc(self):
return Column(Integer, nullable=False)
def created_utc(self) -> Mapped[int]:
return mapped_column(Integer, nullable=False)
@property
def created_date(self) -> str:
@ -67,13 +69,13 @@ class CreatedDateTimeBase(Base):
super().__init__(*args, **kwargs)
@declared_attr
def created_datetimez(self):
def created_datetimez(self) -> Mapped[datetime]:
"""
Declare default column for classes/tables inheriting `CreatedDateTimeBase`.
Retrieving `created_datetimez` will return a `datetime` object with `tzinfo` for UTC.
"""
return Column(DateTime(timezone=True), nullable=False, server_default=now())
return mapped_column(DateTime(timezone=True), nullable=False, server_default=now())
@property
def created_utc(self):

View file

@ -1,11 +1,14 @@
from flask import g
from sqlalchemy import *
from sqlalchemy.orm import relationship
from .submission import Submission
from .comment import Comment
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import Base
from files.helpers.lazy import lazy
from files.helpers.config.const import *
from files.helpers.lazy import lazy
from .comment import Comment
from .submission import Submission
class OauthApp(Base):
@ -14,12 +17,12 @@ class OauthApp(Base):
UniqueConstraint('client_id', name='unique_id'),
)
id = Column(Integer, primary_key=True)
client_id = Column(String(length=64))
app_name = Column(String(length=50), nullable=False)
redirect_uri = Column(String(length=50), nullable=False)
description = Column(String(length=256), nullable=False)
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
client_id: Mapped[str | None] = mapped_column(String(length=64))
app_name: Mapped[str] = mapped_column(String(length=50), nullable=False)
redirect_uri: Mapped[str] = mapped_column(String(length=50), nullable=False)
description: Mapped[str] = mapped_column(String(length=256), nullable=False)
author_id: Mapped[str] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
author = relationship("User", viewonly=True)
@ -51,9 +54,9 @@ class ClientAuth(Base):
UniqueConstraint('access_token', name='unique_access'),
)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
oauth_client = Column(Integer, ForeignKey("oauth_apps.id"), primary_key=True)
access_token = Column(String(128), nullable=False)
user_id = mapped_column(Integer, ForeignKey("users.id"), primary_key=True)
oauth_client = mapped_column(Integer, ForeignKey("oauth_apps.id"), primary_key=True)
access_token = mapped_column(String(128), nullable=False)
user = relationship("User", viewonly=True)
application = relationship("OauthApp", viewonly=True)

View file

@ -4,7 +4,7 @@ from urllib.parse import parse_qs, urlencode, urlparse
from flask import g
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import CreatedBase
from files.classes.visstate import StateMod, StateReport, VisibilityState
@ -24,35 +24,35 @@ CommentRenderContext = Literal['comments', 'volunteer']
class Comment(CreatedBase):
__tablename__ = "comments"
id = Column(Integer, primary_key=True)
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
parent_submission = Column(Integer, ForeignKey("submissions.id"))
edited_utc = Column(Integer, default=0, nullable=False)
ghost = Column(Boolean, default=False, nullable=False)
bannedfor = Column(Boolean)
distinguish_level = Column(Integer, default=0, nullable=False)
level = Column(Integer, default=1, nullable=False)
parent_comment_id = Column(Integer, ForeignKey("comments.id"))
top_comment_id = Column(Integer)
over_18 = Column(Boolean, default=False, nullable=False)
is_bot = Column(Boolean, default=False, nullable=False)
is_pinned = Column(String)
is_pinned_utc = Column(Integer)
sentto = Column(Integer, ForeignKey("users.id"))
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
upvotes = Column(Integer, default=1, nullable=False)
downvotes = Column(Integer, default=0, nullable=False)
realupvotes = Column(Integer, default=1, nullable=False)
descendant_count = Column(Integer, default=0, nullable=False)
body = Column(Text)
body_html = Column(Text, nullable=False)
volunteer_janitor_badness = Column(Float, default=0.5, nullable=False)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
author_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
parent_submission = mapped_column(Integer, ForeignKey("submissions.id"))
edited_utc: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
ghost: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
bannedfor = mapped_column(Boolean)
distinguish_level = mapped_column(Integer, default=0, nullable=False)
level = mapped_column(Integer, default=1, nullable=False)
parent_comment_id = mapped_column(Integer, ForeignKey("comments.id"))
top_comment_id = mapped_column(Integer)
over_18 = mapped_column(Boolean, default=False, nullable=False)
is_bot = mapped_column(Boolean, default=False, nullable=False)
is_pinned = mapped_column(String)
is_pinned_utc = mapped_column(Integer)
sentto = mapped_column(Integer, ForeignKey("users.id"))
app_id = mapped_column(Integer, ForeignKey("oauth_apps.id"))
upvotes = mapped_column(Integer, default=1, nullable=False)
downvotes = mapped_column(Integer, default=0, nullable=False)
realupvotes: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
descendant_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
body: Mapped[str | None] = mapped_column(Text)
body_html: Mapped[str] = mapped_column(Text, nullable=False)
volunteer_janitor_badness: Mapped[float] = mapped_column(Float, default=0.5, nullable=False)
# Visibility states here
state_user_deleted_utc = Column(DateTime(timezone=True), nullable=True) # null if it hasn't been deleted by the user
state_mod = Column(Enum(StateMod), default=StateMod.FILTERED, nullable=False) # default to Filtered just to partially neuter possible exploits
state_mod_set_by = Column(String, nullable=True) # This should *really* be a User.id, but I don't want to mess with the required refactoring at the moment - it's extra hard because it could potentially be a lot of extra either data or queries
state_report = Column(Enum(StateReport), default=StateReport.UNREPORTED, nullable=False)
state_user_deleted_utc = mapped_column(DateTime(timezone=True), nullable=True) # null if it hasn't been deleted by the user
state_mod = mapped_column(Enum(StateMod), default=StateMod.FILTERED, nullable=False) # default to Filtered just to partially neuter possible exploits
state_mod_set_by = mapped_column(String, nullable=True) # This should *really* be a User.id, but I don't want to mess with the required refactoring at the moment - it's extra hard because it could potentially be a lot of extra either data or queries
state_report = mapped_column(Enum(StateReport), default=StateReport.UNREPORTED, nullable=False)
Index('comment_parent_index', parent_comment_id)
Index('comment_post_id_index', parent_submission)
@ -61,11 +61,11 @@ class Comment(CreatedBase):
oauth_app = relationship("OauthApp", viewonly=True)
post = relationship("Submission", viewonly=True)
author = relationship("User", primaryjoin="User.id==Comment.author_id")
author: Mapped["User"] = relationship("User", primaryjoin="User.id==Comment.author_id")
senttouser = relationship("User", primaryjoin="User.id==Comment.sentto", viewonly=True)
parent_comment = relationship("Comment", remote_side=[id], viewonly=True)
parent_comment_writable = relationship("Comment", remote_side=[id])
child_comments = relationship("Comment", lazy="dynamic", remote_side=[parent_comment_id], viewonly=True)
parent_comment: Mapped["Comment"] = relationship("Comment", remote_side=[id], viewonly=True)
parent_comment_writable: Mapped["Comment"] = relationship("Comment", remote_side=[id])
child_comments: Mapped[list["Comment"]] = relationship("Comment", lazy="dynamic", remote_side=[parent_comment_id], viewonly=True)
awards = relationship("AwardRelationship",
primaryjoin="AwardRelationship.comment_id == Comment.id",
viewonly=True)

View file

@ -2,7 +2,8 @@ import importlib
from types import ModuleType
from typing import Callable
from sqlalchemy.schema import Column, ForeignKey
from sqlalchemy.schema import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql.sqltypes import Integer, String
from files.classes.cron.tasks import (RepeatableTask, ScheduledTaskType,
@ -40,9 +41,9 @@ class PythonCodeTask(RepeatableTask):
"polymorphic_identity": int(ScheduledTaskType.PYTHON_CALLABLE),
}
id = Column(Integer, ForeignKey(RepeatableTask.id), primary_key=True)
import_path = Column(String, nullable=False)
callable = Column(String, nullable=False)
id: Mapped[int] = mapped_column(Integer, ForeignKey(RepeatableTask.id), primary_key=True)
import_path: Mapped[str] = mapped_column(String, nullable=False)
callable: Mapped[str] = mapped_column(String, nullable=False)
def run_task(self, ctx:TaskRunContext):
self.get_function()(ctx)

View file

@ -1,8 +1,8 @@
import functools
from datetime import datetime, timezone
from sqlalchemy.orm import relationship
from sqlalchemy.schema import Column, ForeignKey
from sqlalchemy.orm import Mapped, Session, mapped_column, relationship
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql.sqltypes import Boolean, Integer, String, Text
from files.classes.cron.tasks import (RepeatableTask, ScheduledTaskType,
@ -24,18 +24,18 @@ class ScheduledSubmissionTask(RepeatableTask):
"polymorphic_identity": int(ScheduledTaskType.SCHEDULED_SUBMISSION),
}
id = Column(Integer, ForeignKey(RepeatableTask.id), primary_key=True)
author_id_submission = Column(Integer, ForeignKey("users.id"), nullable=False)
ghost = Column(Boolean, default=False, nullable=False)
private = Column(Boolean, default=False, nullable=False)
over_18 = Column(Boolean, default=False, nullable=False)
is_bot = Column(Boolean, default=False, nullable=False)
title = Column(String(SUBMISSION_TITLE_LENGTH_MAXIMUM), nullable=False)
url = Column(String)
body = Column(Text)
body_html = Column(Text)
flair = Column(String)
embed_url = Column(String)
id: Mapped[int] = mapped_column(Integer, ForeignKey(RepeatableTask.id), primary_key=True)
author_id_submission: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
ghost: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
private: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
over_18: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
is_bot: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
title: Mapped[str] = mapped_column(String(SUBMISSION_TITLE_LENGTH_MAXIMUM), nullable=False)
url: Mapped[str | None] = mapped_column(String)
body: Mapped[str | None] = mapped_column(Text)
body_html: Mapped[str | None] = mapped_column(Text)
flair: Mapped[str | None] = mapped_column(String)
embed_url: Mapped[str | None] = mapped_column(String)
author = relationship("User", foreign_keys=author_id_submission)
task = relationship(RepeatableTask)

View file

@ -10,10 +10,10 @@ import flask
import flask_caching
import flask_mail
import redis
from sqlalchemy.orm import relationship, Session
from sqlalchemy.schema import Column, ForeignKey
from sqlalchemy.orm import Mapped, Session, mapped_column, relationship
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql.sqltypes import (Boolean, DateTime, Integer, SmallInteger,
Text, Time, String)
String, Text, Time)
from files.classes.base import CreatedBase
from files.helpers.time import format_age, format_datetime
@ -237,18 +237,18 @@ _TABLE_NAME: Final[str] = "tasks_repeatable"
class RepeatableTask(CreatedBase):
__tablename__ = _TABLE_NAME
id = Column(Integer, primary_key=True, nullable=False)
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
type_id = Column(SmallInteger, nullable=False)
enabled = Column(Boolean, default=True, nullable=False)
run_state = Column(SmallInteger, default=int(ScheduledTaskState.WAITING), nullable=False)
run_time_last = Column(DateTime, default=None)
id: Mapped[int] = mapped_column(Integer, primary_key=True, nullable=False)
author_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
type_id: Mapped[int] = mapped_column(SmallInteger, nullable=False)
enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
run_state: Mapped[int] = mapped_column(SmallInteger, default=int(ScheduledTaskState.WAITING), nullable=False)
run_time_last: Mapped[datetime] = mapped_column(DateTime, default=None)
frequency_day = Column(SmallInteger, nullable=False)
time_of_day_utc = Column(Time, nullable=False)
frequency_day: Mapped[int] = mapped_column(SmallInteger, nullable=False)
time_of_day_utc = mapped_column(Time, nullable=False)
# used for the cron hardcoding system
label = Column(String, nullable=True, unique=True)
label = mapped_column(String, nullable=True, unique=True)
runs = relationship("RepeatableTaskRun", back_populates="task")
@ -351,12 +351,12 @@ class RepeatableTask(CreatedBase):
class RepeatableTaskRun(CreatedBase):
__tablename__ = "tasks_repeatable_runs"
id = Column(Integer, primary_key=True)
task_id = Column(Integer, ForeignKey(RepeatableTask.id), nullable=False)
manual = Column(Boolean, default=False, nullable=False)
traceback_str = Column(Text, nullable=True)
id = mapped_column(Integer, primary_key=True)
task_id = mapped_column(Integer, ForeignKey(RepeatableTask.id), nullable=False)
manual = mapped_column(Boolean, default=False, nullable=False)
traceback_str = mapped_column(Text, nullable=True)
completed_utc = Column(DateTime)
completed_utc = mapped_column(DateTime)
task = relationship(RepeatableTask, back_populates="runs")

View file

@ -1,10 +1,12 @@
from sqlalchemy import *
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql.sqltypes import String
from sqlalchemy.schema import Index
from files.classes.base import Base
class BannedDomain(Base):
__tablename__ = "banneddomains"
domain = Column(String, primary_key=True)
reason = Column(String, nullable=False)
domain: Mapped[str] = mapped_column(String, primary_key=True)
reason: Mapped[str | None] = mapped_column(String, nullable=False)
Index(
'domains_domain_trgm_idx',
domain,

View file

@ -1,11 +1,13 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import CreatedDateTimeBase
class Follow(CreatedDateTimeBase):
__tablename__ = "follows"
target_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
target_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), primary_key=True)
Index('follow_user_id_index', user_id)

View file

@ -3,6 +3,7 @@ from typing import Any, Callable, Final, Optional
from sqlalchemy import Column, func
from sqlalchemy.orm import Session, Query
from sqlalchemy.orm.attributes import InstrumentedAttribute
from files.helpers.config.const import LEADERBOARD_LIMIT
@ -51,11 +52,11 @@ class Leaderboard:
raise NotImplementedError()
class SimpleLeaderboard(Leaderboard):
def __init__(self, v:User, meta:LeaderboardMeta, db:Session, users_query:Query, column:Column):
def __init__(self, v:User, meta:LeaderboardMeta, db:Session, users_query:Query, column: InstrumentedAttribute):
super().__init__(v, meta)
self.db:Session = db
self.users_query:Query = users_query
self.column:Column = column
self.column: InstrumentedAttribute = column
self._calculate()
def _calculate(self) -> None:
@ -101,9 +102,9 @@ class BadgeMarseyLeaderboard(_CountedAndRankedLeaderboard):
def _calculate(self):
sq = self.db.query(self.column, self.count_and_label(self.column), self.rank_filtered_rank_label_by_desc(self.column)).group_by(self.column).subquery()
sq_criteria = None
if self.column == Badge.user_id:
if self.column is Badge.user_id:
sq_criteria = User.id == sq.c.user_id
elif self.column == Marsey.author_id:
elif self.column is Marsey.author_id:
sq_criteria = User.id == sq.c.author_id
else:
raise ValueError("This leaderboard function only supports Badge.user_id and Marsey.author_id")
@ -142,7 +143,7 @@ class UserBlockLeaderboard(_CountedAndRankedLeaderboard):
self._calculate()
def _calculate(self):
if self.column != UserBlock.target_id:
if self.column is not UserBlock.target_id:
raise ValueError("This leaderboard function only supports UserBlock.target_id")
sq = self.db.query(self.column, self.count_and_label(self.column)).group_by(self.column).subquery()
leaderboard = self.db.query(User, sq.c.count).join(User, User.id == sq.c.target_id).order_by(sq.c.count.desc())

View file

@ -1,13 +1,14 @@
from sqlalchemy import *
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import Base
class Marsey(Base):
__tablename__ = "marseys"
name = Column(String, primary_key=True)
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
tags = Column(String(length=200), nullable=False)
count = Column(Integer, default=0, nullable=False)
name: Mapped[str] = mapped_column(String, primary_key=True)
author_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
tags: Mapped[str] = mapped_column(String(length=200), nullable=False)
count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
Index('marseys_idx2', author_id)
Index('marseys_idx3', count.desc())

View file

@ -2,7 +2,7 @@ import logging
from copy import deepcopy
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import CreatedDateTimeBase
from files.helpers.config.const import *
@ -11,13 +11,13 @@ from files.helpers.lazy import lazy
class ModAction(CreatedDateTimeBase):
__tablename__ = "modactions"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"))
kind = Column(String)
target_user_id = Column(Integer, ForeignKey("users.id"))
target_submission_id = Column(Integer, ForeignKey("submissions.id"))
target_comment_id = Column(Integer, ForeignKey("comments.id"))
_note=Column(String)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("users.id"))
kind: Mapped[str | None] = mapped_column(String)
target_user_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("users.id"))
target_submission_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("submissions.id"))
target_comment_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("comments.id"))
_note: Mapped[str | None] = mapped_column(String)
Index('fki_modactions_user_fkey', target_user_id)
Index('modaction_action_idx', kind)

View file

@ -1,13 +1,15 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import CreatedDateTimeBase
class Notification(CreatedDateTimeBase):
__tablename__ = "notifications"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True)
read = Column(Boolean, default=False, nullable=False)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), primary_key=True)
comment_id: Mapped[int] = mapped_column(Integer, ForeignKey("comments.id"), primary_key=True)
read: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
Index('notification_read_idx', read)
Index('notifications_comment_idx', comment_id)

View file

@ -1,13 +1,13 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import Base
class SaveRelationship(Base):
__tablename__ = "save_relationship"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
submission_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), primary_key=True)
submission_id: Mapped[int] = mapped_column(Integer, ForeignKey("submissions.id"), primary_key=True)
Index('fki_save_relationship_submission_fkey', submission_id)
@ -15,7 +15,7 @@ class SaveRelationship(Base):
class CommentSaveRelationship(Base):
__tablename__ = "comment_save_relationship"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), primary_key=True)
comment_id: Mapped[int] = mapped_column(Integer, ForeignKey("comments.id"), primary_key=True)
Index('fki_comment_save_relationship_comment_fkey', comment_id)

View file

@ -1,8 +1,10 @@
from datetime import datetime
from urllib.parse import urlparse
from flask import g
from sqlalchemy import *
from sqlalchemy.orm import Session, declared_attr, deferred, relationship
from sqlalchemy.orm import (Mapped, Session, declared_attr, deferred,
mapped_column, relationship)
from files.classes.base import CreatedBase
from files.classes.flags import Flag
@ -20,40 +22,40 @@ from files.helpers.time import format_age, format_datetime
class Submission(CreatedBase):
__tablename__ = "submissions"
id = Column(Integer, primary_key=True)
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
edited_utc = Column(Integer, default=0, nullable=False)
thumburl = Column(String)
bannedfor = Column(Boolean)
ghost = Column(Boolean, default=False, nullable=False)
views = Column(Integer, default=0, nullable=False)
distinguish_level = Column(Integer, default=0, nullable=False)
stickied = Column(String)
stickied_utc = Column(Integer)
is_pinned = Column(Boolean, default=False, nullable=False)
private = Column(Boolean, default=False, nullable=False)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
author_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
edited_utc: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
thumburl = mapped_column(String)
bannedfor: Mapped[bool] = mapped_column(Boolean)
ghost: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
views: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
distinguish_level: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
stickied: Mapped[str | None] = mapped_column(String)
stickied_utc: Mapped[int | None] = mapped_column(Integer)
is_pinned: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
private: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
club = Column(Boolean, default=False, nullable=False)
comment_count = Column(Integer, default=0, nullable=False)
over_18 = Column(Boolean, default=False, nullable=False)
is_bot = Column(Boolean, default=False, nullable=False)
upvotes = Column(Integer, default=1, nullable=False)
downvotes = Column(Integer, default=0, nullable=False)
realupvotes = Column(Integer, default=1)
app_id=Column(Integer, ForeignKey("oauth_apps.id"))
title = Column(String, nullable=False)
title_html = Column(String, nullable=False)
url = Column(String)
body = Column(Text)
body_html = Column(Text)
flair = Column(String)
embed_url = Column(String)
task_id = Column(Integer, ForeignKey("tasks_repeatable_scheduled_submissions.id"))
comment_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
over_18: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
is_bot: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
upvotes: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
downvotes: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
realupvotes: Mapped[int] = mapped_column(Integer, default=1)
app_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("oauth_apps.id"))
title: Mapped[str] = mapped_column(String, nullable=False)
title_html: Mapped[str] = mapped_column(String, nullable=False)
url: Mapped[str | None] = mapped_column(String)
body: Mapped[str | None] = mapped_column(Text)
body_html: Mapped[str | None] = mapped_column(Text)
flair: Mapped[str | None] = mapped_column(String)
embed_url: Mapped[str | None] = mapped_column(String)
task_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("tasks_repeatable_scheduled_submissions.id"))
# Visibility states here
state_user_deleted_utc = Column(DateTime(timezone=True), nullable=True) # null if it hasn't been deleted by the user
state_mod = Column(Enum(StateMod), default=StateMod.FILTERED, nullable=False) # default to Filtered just to partially neuter possible exploits
state_mod_set_by = Column(String, nullable=True) # This should *really* be a User.id, but I don't want to mess with the required refactoring at the moment - it's extra hard because it could potentially be a lot of extra either data or queries
state_report = Column(Enum(StateReport), default=StateReport.UNREPORTED, nullable=False)
state_user_deleted_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) # null if it hasn't been deleted by the user
state_mod: Mapped[StateMod] = mapped_column(Enum(StateMod), default=StateMod.FILTERED, nullable=False) # default to Filtered just to partially neuter possible exploits
state_mod_set_by: Mapped[str | None] = mapped_column(String, nullable=True) # This should *really* be a User.id, but I don't want to mess with the required refactoring at the moment - it's extra hard because it could potentially be a lot of extra either data or queries
state_report: Mapped[StateReport] = mapped_column(Enum(StateReport), default=StateReport.UNREPORTED, nullable=False)
Index('post_app_id_idx', app_id)
Index('subimssion_binary_group_idx', state_mod, state_user_deleted_utc, over_18)
@ -85,7 +87,7 @@ class Submission(CreatedBase):
notes = relationship("UserNote", back_populates="post")
task = relationship("ScheduledSubmissionTask", back_populates="submissions")
bump_utc = deferred(Column(Integer, server_default=FetchedValue()))
bump_utc = deferred(mapped_column(Integer, server_default=FetchedValue()))
def submit(self, db: Session):
# create submission...

View file

@ -1,11 +1,12 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import Base
class Subscription(Base):
__tablename__ = "subscriptions"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
submission_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), primary_key=True)
submission_id: Mapped[int] = mapped_column(Integer, ForeignKey("submissions.id"), primary_key=True)
Index('subscription_user_index', user_id)

View file

@ -6,7 +6,8 @@ from typing import TYPE_CHECKING, Union
import pyotp
from flask import g, session
from sqlalchemy.orm import aliased, declared_attr, deferred, relationship
from sqlalchemy.orm import (Mapped, Session, aliased, declared_attr, deferred,
mapped_column, relationship)
from files.classes.alts import Alt
from files.classes.award import AwardRelationship
@ -42,78 +43,78 @@ class User(CreatedBase):
UniqueConstraint('original_username', name='users_original_username_key'),
UniqueConstraint('username', name='users_username_key'),
)
id = Column(Integer, primary_key=True)
username = Column(String(length=255), nullable=False)
namecolor = Column(String(length=6), default=DEFAULT_COLOR, nullable=False)
customtitle = Column(String)
customtitleplain = deferred(Column(String))
titlecolor = Column(String(length=6), default=DEFAULT_COLOR, nullable=False)
theme = Column(String, default=defaulttheme, nullable=False)
themecolor = Column(String, default=DEFAULT_COLOR, nullable=False)
cardview = Column(Boolean, default=CARD_VIEW, nullable=False)
highres = Column(String)
profileurl = Column(String)
bannerurl = Column(String)
house = Column(String)
patron = Column(Integer, default=0, nullable=False)
patron_utc = Column(Integer, default=0, nullable=False)
verified = Column(String)
verifiedcolor = Column(String)
winnings = Column(Integer, default=0, nullable=False)
email = deferred(Column(String))
css = deferred(Column(String(CSS_LENGTH_MAXIMUM)))
profilecss = deferred(Column(String(CSS_LENGTH_MAXIMUM)))
passhash = deferred(Column(String, nullable=False))
post_count = Column(Integer, default=0, nullable=False)
comment_count = Column(Integer, default=0, nullable=False)
received_award_count = Column(Integer, default=0, nullable=False)
admin_level = Column(Integer, default=0, nullable=False)
coins_spent = Column(Integer, default=0, nullable=False)
lootboxes_bought = Column(Integer, default=0, nullable=False)
agendaposter = Column(Integer, default=0, nullable=False)
changelogsub = Column(Boolean, default=False, nullable=False)
is_activated = Column(Boolean, default=False, nullable=False)
shadowbanned = Column(String)
over_18 = Column(Boolean, default=False, nullable=False)
hidevotedon = Column(Boolean, default=False, nullable=False)
highlightcomments = Column(Boolean, default=True, nullable=False)
slurreplacer = Column(Boolean, default=True, nullable=False)
flairchanged = Column(Integer)
newtab = Column(Boolean, default=False, nullable=False)
newtabexternal = Column(Boolean, default=True, nullable=False)
reddit = Column(String, default='old.reddit.com', nullable=False)
nitter = Column(Boolean)
frontsize = Column(Integer, default=25, nullable=False)
controversial = Column(Boolean, default=False, nullable=False)
bio = deferred(Column(String))
bio_html = Column(String)
fp = Column(String)
friends = deferred(Column(String))
friends_html = deferred(Column(String))
enemies = deferred(Column(String))
enemies_html = deferred(Column(String))
is_banned = Column(Integer, default=0, nullable=False)
unban_utc = Column(Integer, default=0, nullable=False)
ban_reason = deferred(Column(String))
club_allowed = Column(Boolean)
login_nonce = Column(Integer, default=0, nullable=False)
reserved = deferred(Column(String))
coins = Column(Integer, default=0, nullable=False)
truescore = Column(Integer, default=0, nullable=False)
procoins = Column(Integer, default=0, nullable=False)
mfa_secret = deferred(Column(String))
is_private = Column(Boolean, default=False, nullable=False)
stored_subscriber_count = Column(Integer, default=0, nullable=False)
defaultsortingcomments = Column(String, default="new", nullable=False)
defaultsorting = Column(String, default="new", nullable=False)
defaulttime = Column(String, default=DEFAULT_TIME_FILTER, nullable=False)
is_nofollow = Column(Boolean, default=False, nullable=False)
custom_filter_list = Column(String)
ban_evade = Column(Integer, default=0, nullable=False)
original_username = deferred(Column(String))
referred_by = Column(Integer, ForeignKey("users.id"))
volunteer_last_started_utc = Column(DateTime, nullable=True)
volunteer_janitor_correctness = Column(Float, default=0, nullable=False)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
username: Mapped[str] = mapped_column(String(length=255), nullable=False)
namecolor: Mapped[str] = mapped_column(String(length=6), default=DEFAULT_COLOR, nullable=False)
customtitle: Mapped[str | None] = mapped_column(String)
customtitleplain: Mapped[str | None] = deferred(mapped_column(String))
titlecolor = mapped_column(String(length=6), default=DEFAULT_COLOR, nullable=False)
theme = mapped_column(String, default=defaulttheme, nullable=False)
themecolor = mapped_column(String, default=DEFAULT_COLOR, nullable=False)
cardview = mapped_column(Boolean, default=CARD_VIEW, nullable=False)
highres = mapped_column(String)
profileurl = mapped_column(String)
bannerurl = mapped_column(String)
house = mapped_column(String)
patron = mapped_column(Integer, default=0, nullable=False)
patron_utc = mapped_column(Integer, default=0, nullable=False)
verified = mapped_column(String)
verifiedcolor = mapped_column(String)
winnings = mapped_column(Integer, default=0, nullable=False)
email = deferred(mapped_column(String))
css = deferred(mapped_column(String(CSS_LENGTH_MAXIMUM)))
profilecss = deferred(mapped_column(String(CSS_LENGTH_MAXIMUM)))
passhash = deferred(mapped_column(String, nullable=False))
post_count = mapped_column(Integer, default=0, nullable=False)
comment_count = mapped_column(Integer, default=0, nullable=False)
received_award_count = mapped_column(Integer, default=0, nullable=False)
admin_level = mapped_column(Integer, default=0, nullable=False)
coins_spent = mapped_column(Integer, default=0, nullable=False)
lootboxes_bought = mapped_column(Integer, default=0, nullable=False)
agendaposter = mapped_column(Integer, default=0, nullable=False)
changelogsub = mapped_column(Boolean, default=False, nullable=False)
is_activated = mapped_column(Boolean, default=False, nullable=False)
shadowbanned = mapped_column(String)
over_18 = mapped_column(Boolean, default=False, nullable=False)
hidevotedon = mapped_column(Boolean, default=False, nullable=False)
highlightcomments = mapped_column(Boolean, default=True, nullable=False)
slurreplacer = mapped_column(Boolean, default=True, nullable=False)
flairchanged = mapped_column(Integer)
newtab = mapped_column(Boolean, default=False, nullable=False)
newtabexternal = mapped_column(Boolean, default=True, nullable=False)
reddit = mapped_column(String, default='old.reddit.com', nullable=False)
nitter = mapped_column(Boolean)
frontsize = mapped_column(Integer, default=25, nullable=False)
controversial = mapped_column(Boolean, default=False, nullable=False)
bio = deferred(mapped_column(String))
bio_html = mapped_column(String)
fp = mapped_column(String)
friends = deferred(mapped_column(String))
friends_html = deferred(mapped_column(String))
enemies = deferred(mapped_column(String))
enemies_html = deferred(mapped_column(String))
is_banned = mapped_column(Integer, default=0, nullable=False)
unban_utc = mapped_column(Integer, default=0, nullable=False)
ban_reason = deferred(mapped_column(String))
club_allowed = mapped_column(Boolean)
login_nonce = mapped_column(Integer, default=0, nullable=False)
reserved = deferred(mapped_column(String))
coins = mapped_column(Integer, default=0, nullable=False)
truescore = mapped_column(Integer, default=0, nullable=False)
procoins = mapped_column(Integer, default=0, nullable=False)
mfa_secret = deferred(mapped_column(String))
is_private = mapped_column(Boolean, default=False, nullable=False)
stored_subscriber_count = mapped_column(Integer, default=0, nullable=False)
defaultsortingcomments = mapped_column(String, default="new", nullable=False)
defaultsorting = mapped_column(String, default="new", nullable=False)
defaulttime = mapped_column(String, default=DEFAULT_TIME_FILTER, nullable=False)
is_nofollow = mapped_column(Boolean, default=False, nullable=False)
custom_filter_list = mapped_column(String)
ban_evade = mapped_column(Integer, default=0, nullable=False)
original_username = deferred(mapped_column(String))
referred_by = mapped_column(Integer, ForeignKey("users.id"))
volunteer_last_started_utc = mapped_column(DateTime, nullable=True)
volunteer_janitor_correctness = mapped_column(Float, default=0, nullable=False)
Index(
'users_original_username_trgm_idx',

View file

@ -4,6 +4,7 @@ from files.classes.base import CreatedDateTimeBase
from files.helpers.config.const import *
from enum import Enum
from sqlalchemy import Enum as EnumType
from sqlalchemy.orm import Mapped, mapped_column, relationship
class UserTag(Enum):
Quality = 0
@ -18,13 +19,13 @@ class UserTag(Enum):
class UserNote(CreatedDateTimeBase):
__tablename__ = "usernotes"
id = Column(Integer, primary_key=True)
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
reference_user = Column(Integer, ForeignKey("users.id", ondelete='CASCADE'), nullable=False)
reference_comment = Column(Integer, ForeignKey("comments.id", ondelete='SET NULL'))
reference_post = Column(Integer, ForeignKey("submissions.id", ondelete='SET NULL'))
note = Column(String, nullable=False)
tag = Column(EnumType(UserTag), nullable=False)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
author_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
reference_user: Mapped[int] = mapped_column(Integer, ForeignKey("users.id", ondelete='CASCADE'), nullable=False)
reference_comment: Mapped[int | None] = mapped_column(Integer, ForeignKey("comments.id", ondelete='SET NULL'))
reference_post: Mapped[int | None] = mapped_column(Integer, ForeignKey("submissions.id", ondelete='SET NULL'))
note: Mapped[str] = mapped_column(String, nullable=False)
tag: Mapped[str] = mapped_column(EnumType(UserTag), nullable=False)
author = relationship("User", foreign_keys='UserNote.author_id')
user = relationship("User", foreign_keys='UserNote.reference_user', back_populates="notes")

View file

@ -1,7 +1,7 @@
import time
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import Base
from files.helpers.lazy import lazy
@ -11,9 +11,9 @@ from files.helpers.time import format_age
class ViewerRelationship(Base):
__tablename__ = "viewers"
user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)
viewer_id = Column(Integer, ForeignKey('users.id'), primary_key=True)
last_view_utc = Column(Integer, nullable=False)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey('users.id'), primary_key=True)
viewer_id: Mapped[int] = mapped_column(Integer, ForeignKey('users.id'), primary_key=True)
last_view_utc: Mapped[int] = mapped_column(Integer, nullable=False)
Index('fki_view_viewer_fkey', viewer_id)

View file

@ -1,8 +1,12 @@
from datetime import datetime
import enum
from files.classes.base import Base
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import Base
class VolunteerJanitorResult(enum.Enum):
Pending = 0
@ -16,11 +20,11 @@ class VolunteerJanitorResult(enum.Enum):
class VolunteerJanitorRecord(Base):
__tablename__ = "volunteer_janitor"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
comment_id = Column(Integer, ForeignKey("comments.id"), nullable=False)
recorded_utc = Column(DateTime, default=0, nullable=False)
result = Column(Enum(VolunteerJanitorResult), default=VolunteerJanitorResult.Pending, nullable=False)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
comment_id: Mapped[int] = mapped_column(Integer, ForeignKey("comments.id"), nullable=False)
recorded_utc: Mapped[datetime] = mapped_column(DateTime, default=0, nullable=False)
result: Mapped[VolunteerJanitorResult] = mapped_column(Enum(VolunteerJanitorResult), default=VolunteerJanitorResult.Pending, nullable=False)
Index('volunteer_comment_index', user_id, comment_id)

View file

@ -1,5 +1,5 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped, mapped_column, relationship
from files.classes.base import CreatedDateTimeBase
from files.helpers.lazy import lazy
@ -8,11 +8,11 @@ from files.helpers.lazy import lazy
class Vote(CreatedDateTimeBase):
__tablename__ = "votes"
submission_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
vote_type = Column(Integer, nullable=False)
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
real = Column(Boolean, default=True, nullable=False)
submission_id: Mapped[int] = mapped_column(Integer, ForeignKey("submissions.id"), primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), primary_key=True)
vote_type: Mapped[int] = mapped_column(Integer, nullable=False)
app_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("oauth_apps.id"))
real: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
Index('votes_type_index', vote_type)
Index('vote_user_index', user_id)
@ -45,11 +45,11 @@ class Vote(CreatedDateTimeBase):
class CommentVote(CreatedDateTimeBase):
__tablename__ = "commentvotes"
comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
vote_type = Column(Integer, nullable=False)
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
real = Column(Boolean, default=True, nullable=False)
comment_id = mapped_column(Integer, ForeignKey("comments.id"), primary_key=True)
user_id = mapped_column(Integer, ForeignKey("users.id"), primary_key=True)
vote_type = mapped_column(Integer, nullable=False)
app_id = mapped_column(Integer, ForeignKey("oauth_apps.id"))
real = mapped_column(Boolean, default=True, nullable=False)
Index('cvote_user_index', user_id)
Index('commentvotes_comments_type_index', vote_type)

View file

@ -2,6 +2,7 @@
import pprint
import sqlalchemy
import sqlalchemy.orm
from sqlalchemy.orm import Session
from alive_progress import alive_it

View file

@ -5,7 +5,8 @@ from typing import Callable, Iterable, List, Optional, Type, Union
from flask import abort, g
from sqlalchemy import and_, or_, func
from sqlalchemy.orm import Query, scoped_session, selectinload
from sqlalchemy.orm import Query, selectinload
from sqlalchemy.orm.session import Session
from files.classes import *
from files.helpers.config.const import AUTOJANNY_ID
@ -81,7 +82,7 @@ def get_account(
v:Optional[User]=None,
graceful:bool=False,
include_blocks:bool=False,
db:Optional[scoped_session]=None) -> Optional[User]:
db:Optional[Session]=None) -> Optional[User]:
try:
id = int(id)
except:
@ -102,7 +103,7 @@ def get_account(
def get_accounts_dict(ids:Union[Iterable[str], Iterable[int]],
v:Optional[User]=None, graceful=False,
include_shadowbanned=True,
db:Optional[scoped_session]=None) -> Optional[dict[int, User]]:
db:Optional[Session]=None) -> Optional[dict[int, User]]:
if not db: db = g.db
if not ids: return {}
try:

108
poetry.lock generated
View file

@ -2014,77 +2014,81 @@ files = [
[[package]]
name = "sqlalchemy"
version = "1.4.43"
version = "2.0.19"
description = "Database Abstraction Library"
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
python-versions = ">=3.7"
files = [
{file = "SQLAlchemy-1.4.43-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:491d94879f9ec0dea7e1cb053cd9cc65a28d2467960cf99f7b3c286590406060"},
{file = "SQLAlchemy-1.4.43-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eeb55a555eef1a9607c1635bbdddd0b8a2bb9713bcb5bc8da1e8fae8ee46d1d8"},
{file = "SQLAlchemy-1.4.43-cp27-cp27m-win32.whl", hash = "sha256:7d6293010aa0af8bd3b0c9993259f8979db2422d6abf85a31d70ec69cb2ee4dc"},
{file = "SQLAlchemy-1.4.43-cp27-cp27m-win_amd64.whl", hash = "sha256:27479b5a1e110e64c56b18ffbf8cf99e101572a3d1a43943ea02158f1304108e"},
{file = "SQLAlchemy-1.4.43-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:13ce4f3a068ec4ef7598d2a77f42adc3d90c76981f5a7c198756b25c4f4a22ea"},
{file = "SQLAlchemy-1.4.43-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:aa12e27cb465b4b006ffb777624fc6023363e01cfed2d3f89d33fb6da80f6de2"},
{file = "SQLAlchemy-1.4.43-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d16aca30fad4753aeb4ebde564bbd4a248b9673e4f879b940f4e806a17be87f"},
{file = "SQLAlchemy-1.4.43-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cde363fb5412ab178f1cc1e596e9cfc396464da8a4fe8e733cc6d6b4e2c23aa9"},
{file = "SQLAlchemy-1.4.43-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4abda3e693d24169221ffc7aa0444ccef3dc43dfeab6ad8665d3836751cd6af7"},
{file = "SQLAlchemy-1.4.43-cp310-cp310-win32.whl", hash = "sha256:fa46d86a17cccd48c6762df1a60aecf5aaa2e0c0973efacf146c637694b62ffd"},
{file = "SQLAlchemy-1.4.43-cp310-cp310-win_amd64.whl", hash = "sha256:962c7c80c54a42836c47cb0d8a53016986c8584e8d98e90e2ea723a4ed0ba85b"},
{file = "SQLAlchemy-1.4.43-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f6e036714a586f757a3e12ff0798ce9a90aa04a60cff392d8bcacc5ecf79c95e"},
{file = "SQLAlchemy-1.4.43-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05d7365c2d1df03a69d90157a3e9b3e7b62088cca8ee6686aed2598659a6e14"},
{file = "SQLAlchemy-1.4.43-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59bd0ae166253f7fed8c3f4f6265d2637f25d2f6614d00df34d7ee0d95d29c91"},
{file = "SQLAlchemy-1.4.43-cp311-cp311-win32.whl", hash = "sha256:0c8a174f23bc021aac97bcb27fbe2ae3d4652d3d23e5768bc2ec3d44e386c7eb"},
{file = "SQLAlchemy-1.4.43-cp311-cp311-win_amd64.whl", hash = "sha256:5d5937e1bf7921e4d1acdfad72dd98d9e7f9ea5c52aeb12b3b05b534b527692d"},
{file = "SQLAlchemy-1.4.43-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:ed1c950aba723b7a5b702b88f05d883607c587de918d7d8c2014fe7f55cf67e0"},
{file = "SQLAlchemy-1.4.43-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5438f6c768b7e928f0463777b545965648ba0d55877afd14a4e96d2a99702e7"},
{file = "SQLAlchemy-1.4.43-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:41df873cdae1d56fde97a1b4f6ffa118f40e4b2d6a6aa8c25c50eea31ecbeb08"},
{file = "SQLAlchemy-1.4.43-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22f46440e61d90100e0f378faac40335fb5bbf278472df0d83dc15b653b9896"},
{file = "SQLAlchemy-1.4.43-cp36-cp36m-win32.whl", hash = "sha256:529e2cc8af75811114e5ab2eb116fd71b6e252c6bdb32adbfcd5e0c5f6d5ab06"},
{file = "SQLAlchemy-1.4.43-cp36-cp36m-win_amd64.whl", hash = "sha256:c1ced2fae7a1177a36cf94d0a5567452d195d3b4d7d932dd61f123fb15ddf87b"},
{file = "SQLAlchemy-1.4.43-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:736d4e706adb3c95a0a7e660073a5213dfae78ff2df6addf8ff2918c83fbeebe"},
{file = "SQLAlchemy-1.4.43-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23a4569d3db1ce44370d05c5ad79be4f37915fcc97387aef9da232b95db7b695"},
{file = "SQLAlchemy-1.4.43-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:42bff29eaecbb284f614f4bb265bb0c268625f5b93ce6268f8017811e0afbdde"},
{file = "SQLAlchemy-1.4.43-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee9613b0460dce970414cfc990ca40afe518bc139e697243fcdf890285fb30ac"},
{file = "SQLAlchemy-1.4.43-cp37-cp37m-win32.whl", hash = "sha256:dc1e005d490c101d27657481a05765851ab795cc8aedeb8d9425595088b20736"},
{file = "SQLAlchemy-1.4.43-cp37-cp37m-win_amd64.whl", hash = "sha256:c9a6e878e63286392b262d86d21fe16e6eec12b95ccb0a92c392f2b1e0acca03"},
{file = "SQLAlchemy-1.4.43-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:c6de20de7c19b965c007c9da240268dde1451865099ca10f0f593c347041b845"},
{file = "SQLAlchemy-1.4.43-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fef01240d32ada9007387afd8e0b2230f99efdc4b57ca6f1d1192fca4fcf6a5"},
{file = "SQLAlchemy-1.4.43-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b6fd58e25e6cdd2a131d7e97f9713f8f2142360cd40c75af8aa5b83d535f811c"},
{file = "SQLAlchemy-1.4.43-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35dc0a5e934c41e282e019c889069b01ff4cd356b2ea452c9985e1542734cfb1"},
{file = "SQLAlchemy-1.4.43-cp38-cp38-win32.whl", hash = "sha256:fb9a44e7124f72b79023ab04e1c8fcd8f392939ef0d7a75beae8634e15605d30"},
{file = "SQLAlchemy-1.4.43-cp38-cp38-win_amd64.whl", hash = "sha256:4a791e7a1e5ac33f70a3598f8f34fdd3b60c68593bbb038baf58bc50e02d7468"},
{file = "SQLAlchemy-1.4.43-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:c9b59863e2b1f1e1ebf9ee517f86cdfa82d7049c8d81ad71ab58d442b137bbe9"},
{file = "SQLAlchemy-1.4.43-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd80300d81d92661e2488a4bf4383f0c5dc6e7b05fa46d2823e231af4e30539a"},
{file = "SQLAlchemy-1.4.43-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c3dde668edea70dc8d55a74d933d5446e5a97786cdd1c67c8e4971c73bd087ad"},
{file = "SQLAlchemy-1.4.43-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b462c070769f0ef06ea5fe65206b970bcf2b59cb3fda2bec2f4729e1be89c13"},
{file = "SQLAlchemy-1.4.43-cp39-cp39-win32.whl", hash = "sha256:c1f5bfffc3227d05d90c557b10604962f655b4a83c9f3ad507a81ac8d6847679"},
{file = "SQLAlchemy-1.4.43-cp39-cp39-win_amd64.whl", hash = "sha256:a7fa3e57a7b0476fbcba72b231150503d53dbcbdd23f4a86be5152912a923b6e"},
{file = "SQLAlchemy-1.4.43.tar.gz", hash = "sha256:c628697aad7a141da8fc3fd81b4874a711cc84af172e1b1e7bbfadf760446496"},
{file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9deaae357edc2091a9ed5d25e9ee8bba98bcfae454b3911adeaf159c2e9ca9e3"},
{file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bf0fd65b50a330261ec7fe3d091dfc1c577483c96a9fa1e4323e932961aa1b5"},
{file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d90ccc15ba1baa345796a8fb1965223ca7ded2d235ccbef80a47b85cea2d71a"},
{file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4e688f6784427e5f9479d1a13617f573de8f7d4aa713ba82813bcd16e259d1"},
{file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:584f66e5e1979a7a00f4935015840be627e31ca29ad13f49a6e51e97a3fb8cae"},
{file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c69ce70047b801d2aba3e5ff3cba32014558966109fecab0c39d16c18510f15"},
{file = "SQLAlchemy-2.0.19-cp310-cp310-win32.whl", hash = "sha256:96f0463573469579d32ad0c91929548d78314ef95c210a8115346271beeeaaa2"},
{file = "SQLAlchemy-2.0.19-cp310-cp310-win_amd64.whl", hash = "sha256:22bafb1da60c24514c141a7ff852b52f9f573fb933b1e6b5263f0daa28ce6db9"},
{file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d6894708eeb81f6d8193e996257223b6bb4041cb05a17cd5cf373ed836ef87a2"},
{file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8f2afd1aafded7362b397581772c670f20ea84d0a780b93a1a1529da7c3d369"},
{file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15afbf5aa76f2241184c1d3b61af1a72ba31ce4161013d7cb5c4c2fca04fd6e"},
{file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc05b59142445a4efb9c1fd75c334b431d35c304b0e33f4fa0ff1ea4890f92e"},
{file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5831138f0cc06b43edf5f99541c64adf0ab0d41f9a4471fd63b54ae18399e4de"},
{file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3afa8a21a9046917b3a12ffe016ba7ebe7a55a6fc0c7d950beb303c735c3c3ad"},
{file = "SQLAlchemy-2.0.19-cp311-cp311-win32.whl", hash = "sha256:c896d4e6ab2eba2afa1d56be3d0b936c56d4666e789bfc59d6ae76e9fcf46145"},
{file = "SQLAlchemy-2.0.19-cp311-cp311-win_amd64.whl", hash = "sha256:024d2f67fb3ec697555e48caeb7147cfe2c08065a4f1a52d93c3d44fc8e6ad1c"},
{file = "SQLAlchemy-2.0.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:89bc2b374ebee1a02fd2eae6fd0570b5ad897ee514e0f84c5c137c942772aa0c"},
{file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd4d410a76c3762511ae075d50f379ae09551d92525aa5bb307f8343bf7c2c12"},
{file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f469f15068cd8351826df4080ffe4cc6377c5bf7d29b5a07b0e717dddb4c7ea2"},
{file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cda283700c984e699e8ef0fcc5c61f00c9d14b6f65a4f2767c97242513fcdd84"},
{file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:43699eb3f80920cc39a380c159ae21c8a8924fe071bccb68fc509e099420b148"},
{file = "SQLAlchemy-2.0.19-cp37-cp37m-win32.whl", hash = "sha256:61ada5831db36d897e28eb95f0f81814525e0d7927fb51145526c4e63174920b"},
{file = "SQLAlchemy-2.0.19-cp37-cp37m-win_amd64.whl", hash = "sha256:57d100a421d9ab4874f51285c059003292433c648df6abe6c9c904e5bd5b0828"},
{file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:16a310f5bc75a5b2ce7cb656d0e76eb13440b8354f927ff15cbaddd2523ee2d1"},
{file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cf7b5e3856cbf1876da4e9d9715546fa26b6e0ba1a682d5ed2fc3ca4c7c3ec5b"},
{file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e7b69d9ced4b53310a87117824b23c509c6fc1f692aa7272d47561347e133b6"},
{file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9eb4575bfa5afc4b066528302bf12083da3175f71b64a43a7c0badda2be365"},
{file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6b54d1ad7a162857bb7c8ef689049c7cd9eae2f38864fc096d62ae10bc100c7d"},
{file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5d6afc41ca0ecf373366fd8e10aee2797128d3ae45eb8467b19da4899bcd1ee0"},
{file = "SQLAlchemy-2.0.19-cp38-cp38-win32.whl", hash = "sha256:430614f18443b58ceb9dedec323ecddc0abb2b34e79d03503b5a7579cd73a531"},
{file = "SQLAlchemy-2.0.19-cp38-cp38-win_amd64.whl", hash = "sha256:eb60699de43ba1a1f77363f563bb2c652f7748127ba3a774f7cf2c7804aa0d3d"},
{file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a752b7a9aceb0ba173955d4f780c64ee15a1a991f1c52d307d6215c6c73b3a4c"},
{file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7351c05db355da112e056a7b731253cbeffab9dfdb3be1e895368513c7d70106"},
{file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa51ce4aea583b0c6b426f4b0563d3535c1c75986c4373a0987d84d22376585b"},
{file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae7473a67cd82a41decfea58c0eac581209a0aa30f8bc9190926fbf628bb17f7"},
{file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:851a37898a8a39783aab603c7348eb5b20d83c76a14766a43f56e6ad422d1ec8"},
{file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539010665c90e60c4a1650afe4ab49ca100c74e6aef882466f1de6471d414be7"},
{file = "SQLAlchemy-2.0.19-cp39-cp39-win32.whl", hash = "sha256:f82c310ddf97b04e1392c33cf9a70909e0ae10a7e2ddc1d64495e3abdc5d19fb"},
{file = "SQLAlchemy-2.0.19-cp39-cp39-win_amd64.whl", hash = "sha256:8e712cfd2e07b801bc6b60fdf64853bc2bd0af33ca8fa46166a23fe11ce0dbb0"},
{file = "SQLAlchemy-2.0.19-py3-none-any.whl", hash = "sha256:314145c1389b021a9ad5aa3a18bac6f5d939f9087d7fc5443be28cba19d2c972"},
{file = "SQLAlchemy-2.0.19.tar.gz", hash = "sha256:77a14fa20264af73ddcdb1e2b9c5a829b8cc6b8304d0f093271980e36c200a3f"},
]
[package.dependencies]
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and platform_machine == \"aarch64\" or python_version >= \"3\" and platform_machine == \"ppc64le\" or python_version >= \"3\" and platform_machine == \"x86_64\" or python_version >= \"3\" and platform_machine == \"amd64\" or python_version >= \"3\" and platform_machine == \"AMD64\" or python_version >= \"3\" and platform_machine == \"win32\" or python_version >= \"3\" and platform_machine == \"WIN32\""}
greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""}
typing-extensions = ">=4.2.0"
[package.extras]
aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
mssql-pyodbc = ["pyodbc"]
mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"]
mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"]
mypy = ["mypy (>=0.910)"]
mysql = ["mysqlclient (>=1.4.0)"]
mysql-connector = ["mysql-connector-python"]
oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"]
oracle = ["cx-oracle (>=7)"]
oracle-oracledb = ["oracledb (>=1.0.1)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
postgresql-psycopg = ["psycopg (>=3.0.7)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
postgresql-psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql", "pymysql (<1)"]
postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"]
pymysql = ["pymysql"]
sqlcipher = ["sqlcipher3-binary"]
[[package]]
@ -2434,4 +2438,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "2.0"
python-versions = "~3.10" # updating to 3.11 causes instability; see https://github.com/themotte/rDrama/issues/446
content-hash = "b97cdf54a01257851bd007b5f8c52c4805dbd7f5b14b55f31df00bcce5376f51"
content-hash = "a34ff74a2c448f9de2fd58ae78a8ec275462cd690a23d0953cac0af4fdd6b8f2"

View file

@ -31,7 +31,7 @@ python-dotenv = "*"
qrcode = "*"
redis = "*"
requests = "*"
SQLAlchemy = "^1.4.43"
SQLAlchemy = "^2.0.19"
user-agents = "*"
psycopg2-binary = "*"
pusher_push_notifications = "*"