fixed most deprecations from flask and sqlalchemy

This commit is contained in:
Tim Gröger 2023-04-09 20:57:15 +02:00
parent cfbb557539
commit af2c674ce4
11 changed files with 56 additions and 22 deletions

View File

@ -1,9 +1,12 @@
import enum import enum
import json
from flask import Flask from flask import Flask
from flask_cors import CORS from flask_cors import CORS
from datetime import datetime, date from datetime import datetime, date
from flask.json import JSONEncoder, jsonify from flask.json import jsonify
from json import JSONEncoder
from flask.json.provider import JSONProvider
from sqlalchemy.exc import OperationalError from sqlalchemy.exc import OperationalError
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
@ -11,6 +14,9 @@ from flaschengeist import logger
from flaschengeist.controller import pluginController from flaschengeist.controller import pluginController
from flaschengeist.utils.hook import Hook from flaschengeist.utils.hook import Hook
from flaschengeist.config import configure_app from flaschengeist.config import configure_app
from flaschengeist.plugins import Plugin
from flaschengeist.database import db
class CustomJSONEncoder(JSONEncoder): class CustomJSONEncoder(JSONEncoder):
@ -33,6 +39,19 @@ class CustomJSONEncoder(JSONEncoder):
return list(iterable) return list(iterable)
return JSONEncoder.default(self, o) return JSONEncoder.default(self, o)
class CustomJSONProvider(JSONProvider):
ensure_ascii: bool = True
sort_keys: bool = True
def dumps(self, obj, **kwargs):
kwargs.setdefault("ensure_ascii", self.ensure_ascii)
kwargs.setdefault("sort_keys", self.sort_keys)
return json.dumps(obj, **kwargs, cls=CustomJSONEncoder)
def loads(self, s: str | bytes, **kwargs):
return json.loads(s, **kwargs)
@Hook("plugins.loaded") @Hook("plugins.loaded")
def load_plugins(app: Flask): def load_plugins(app: Flask):
@ -43,7 +62,9 @@ def load_plugins(app: Flask):
try: try:
# Load class # Load class
cls = plugin.entry_point.load() cls = plugin.entry_point.load()
plugin = cls.query.get(plugin.id) if plugin.id is not None else plugin #plugin = cls.query.get(plugin.id) if plugin.id is not None else plugin
#plugin = db.session.query(cls).get(plugin.id) if plugin.id is not None else plugin
plugin = db.session.get(cls, plugin.id) if plugin.id is not None else plugin
# Custom loading tasks # Custom loading tasks
plugin.load() plugin.load()
# Register blueprint # Register blueprint
@ -60,7 +81,9 @@ def load_plugins(app: Flask):
def create_app(test_config=None, cli=False): def create_app(test_config=None, cli=False):
app = Flask("flaschengeist") app = Flask("flaschengeist")
app.json_encoder = CustomJSONEncoder # app.json_encoder = CustomJSONEncoder
app.json_provider_class = CustomJSONProvider
app.json = CustomJSONProvider(app)
CORS(app) CORS(app)
with app.app_context(): with app.app_context():

View File

@ -28,7 +28,7 @@ def read_configuration(test_config):
if not test_config: if not test_config:
paths.append(Path.home() / ".config") paths.append(Path.home() / ".config")
if "FLASCHENGEIST_CONF" in os.environ: if "FLASCHENGEIST_CONF" in os.environ:
paths.append(Path(os.environ.get("FLASCHENGEIST_CONF"))) paths.append(Path(str(os.environ.get("FLASCHENGEIST_CONF"))))
for loc in paths: for loc in paths:
try: try:

View File

@ -8,6 +8,7 @@ from ..database.types import ModelSerializeMixin, Serial
class Image(db.Model, ModelSerializeMixin): class Image(db.Model, ModelSerializeMixin):
__allow_unmapped__ = True
__tablename__ = "image" __tablename__ = "image"
id: int = db.Column(Serial, primary_key=True) id: int = db.Column(Serial, primary_key=True)
filename_: str = db.Column("filename", db.String(255), nullable=False) filename_: str = db.Column("filename", db.String(255), nullable=False)

View File

@ -8,6 +8,7 @@ from ..database.types import Serial, UtcDateTime, ModelSerializeMixin
class Notification(db.Model, ModelSerializeMixin): class Notification(db.Model, ModelSerializeMixin):
__allow_unmapped__ = True
__tablename__ = "notification" __tablename__ = "notification"
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column("id", Serial, primary_key=True)
text: str = db.Column(db.Text) text: str = db.Column(db.Text)

View File

@ -1,6 +1,6 @@
from __future__ import annotations # TODO: Remove if python requirement is >= 3.12 (? PEP 563 is defered) from __future__ import annotations # TODO: Remove if python requirement is >= 3.12 (? PEP 563 is defered)
from typing import Any from typing import Any, List, Dict
from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.collections import attribute_mapped_collection
from ..database import db from ..database import db
@ -8,6 +8,7 @@ from ..database.types import Serial
class PluginSetting(db.Model): class PluginSetting(db.Model):
__allow_unmapped__ = True
__tablename__ = "plugin_setting" __tablename__ = "plugin_setting"
id = db.Column("id", Serial, primary_key=True) id = db.Column("id", Serial, primary_key=True)
plugin_id: int = db.Column("plugin", Serial, db.ForeignKey("plugin.id")) plugin_id: int = db.Column("plugin", Serial, db.ForeignKey("plugin.id"))
@ -16,6 +17,7 @@ class PluginSetting(db.Model):
class BasePlugin(db.Model): class BasePlugin(db.Model):
__allow_unmapped__ = True
__tablename__ = "plugin" __tablename__ = "plugin"
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column("id", Serial, primary_key=True)
name: str = db.Column(db.String(127), nullable=False) name: str = db.Column(db.String(127), nullable=False)
@ -24,7 +26,7 @@ class BasePlugin(db.Model):
"""The latest installed version""" """The latest installed version"""
enabled: bool = db.Column(db.Boolean, default=False) enabled: bool = db.Column(db.Boolean, default=False)
"""Enabled state of the plugin""" """Enabled state of the plugin"""
permissions: list = db.relationship( permissions: List["Permission"] = db.relationship(
"Permission", cascade="all, delete, delete-orphan", back_populates="plugin_", lazy="select" "Permission", cascade="all, delete, delete-orphan", back_populates="plugin_", lazy="select"
) )
"""Optional list of custom permissions used by the plugin """Optional list of custom permissions used by the plugin
@ -33,11 +35,11 @@ class BasePlugin(db.Model):
to prevent clashes with other plugins. E. g. instead of *delete* use *plugin_delete*. to prevent clashes with other plugins. E. g. instead of *delete* use *plugin_delete*.
""" """
__settings: dict[str, "PluginSetting"] = db.relationship( __settings: Dict[str, "PluginSetting"] = db.relationship(
"PluginSetting", "PluginSetting",
collection_class=attribute_mapped_collection("name"), collection_class=attribute_mapped_collection("name"),
cascade="all, delete, delete-orphan", cascade="all, delete, delete-orphan",
lazy="select", lazy="subquery",
) )
def get_setting(self, name: str, **kwargs): def get_setting(self, name: str, **kwargs):

View File

@ -17,6 +17,7 @@ class Session(db.Model, ModelSerializeMixin):
token: String to verify access later. token: String to verify access later.
""" """
__allow_unmapped__ = True
__tablename__ = "session" __tablename__ = "session"
expires: datetime = db.Column(UtcDateTime) expires: datetime = db.Column(UtcDateTime)
token: str = db.Column(db.String(32), unique=True) token: str = db.Column(db.String(32), unique=True)

View File

@ -1,6 +1,6 @@
from __future__ import annotations # TODO: Remove if python requirement is >= 3.12 (? PEP 563 is defered) from __future__ import annotations # TODO: Remove if python requirement is >= 3.12 (? PEP 563 is defered)
from typing import Optional from typing import Optional, Union, List
from datetime import date, datetime from datetime import date, datetime
from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.collections import attribute_mapped_collection
@ -21,6 +21,7 @@ role_permission_association_table = db.Table(
class Permission(db.Model, ModelSerializeMixin): class Permission(db.Model, ModelSerializeMixin):
__allow_unmapped__ = True
__tablename__ = "permission" __tablename__ = "permission"
name: str = db.Column(db.String(30), unique=True) name: str = db.Column(db.String(30), unique=True)
@ -30,10 +31,11 @@ class Permission(db.Model, ModelSerializeMixin):
class Role(db.Model, ModelSerializeMixin): class Role(db.Model, ModelSerializeMixin):
__allow_unmapped__ = True
__tablename__ = "role" __tablename__ = "role"
id: int = db.Column(Serial, primary_key=True) id: int = db.Column(Serial, primary_key=True)
name: str = db.Column(db.String(30), unique=True) name: str = db.Column(db.String(30), unique=True)
permissions: list[Permission] = db.relationship("Permission", secondary=role_permission_association_table) permissions: List[Permission] = db.relationship("Permission", secondary=role_permission_association_table)
class User(db.Model, ModelSerializeMixin): class User(db.Model, ModelSerializeMixin):
@ -51,6 +53,7 @@ class User(db.Model, ModelSerializeMixin):
birthday: Birthday of the user birthday: Birthday of the user
""" """
__allow_unmapped__ = True
__tablename__ = "user" __tablename__ = "user"
userid: str = db.Column(db.String(30), unique=True, nullable=False) userid: str = db.Column(db.String(30), unique=True, nullable=False)
display_name: str = db.Column(db.String(30)) display_name: str = db.Column(db.String(30))
@ -59,15 +62,15 @@ class User(db.Model, ModelSerializeMixin):
deleted: bool = db.Column(db.Boolean(), default=False) deleted: bool = db.Column(db.Boolean(), default=False)
birthday: Optional[date] = db.Column(db.Date) birthday: Optional[date] = db.Column(db.Date)
mail: str = db.Column(db.String(60)) mail: str = db.Column(db.String(60))
roles: list[str] = []
permissions: Optional[list[str]] = None permissions: Optional[list[str]] = None
roles: List[str] = []
# Protected stuff for backend use only # Protected stuff for backend use only
id_ = db.Column("id", Serial, primary_key=True) id_ = db.Column("id", Serial, primary_key=True)
roles_: list[Role] = db.relationship("Role", secondary=association_table, cascade="save-update, merge") roles_: List[Role] = db.relationship("Role", secondary=association_table, cascade="save-update, merge")
sessions_: list[Session] = db.relationship("Session", back_populates="user_", cascade="all, delete, delete-orphan") sessions_: List[Session] = db.relationship("Session", back_populates="user_", cascade="all, delete, delete-orphan")
avatar_: Optional[Image] = db.relationship("Image", cascade="all, delete, delete-orphan", single_parent=True) avatar_: Optional[Image] = db.relationship("Image", cascade="all, delete, delete-orphan", single_parent=True)
reset_requests_: list["_PasswordReset"] = db.relationship("_PasswordReset", cascade="all, delete, delete-orphan") reset_requests_: List["_PasswordReset"] = db.relationship("_PasswordReset", cascade="all, delete, delete-orphan")
# Private stuff for internal use # Private stuff for internal use
_avatar_id = db.Column("avatar", Serial, db.ForeignKey("image.id")) _avatar_id = db.Column("avatar", Serial, db.ForeignKey("image.id"))
@ -107,6 +110,7 @@ class User(db.Model, ModelSerializeMixin):
class _UserAttribute(db.Model, ModelSerializeMixin): class _UserAttribute(db.Model, ModelSerializeMixin):
__allow_unmapped__ = True
__tablename__ = "user_attribute" __tablename__ = "user_attribute"
id = db.Column("id", Serial, primary_key=True) id = db.Column("id", Serial, primary_key=True)
user: User = db.Column("user", Serial, db.ForeignKey("user.id"), nullable=False) user: User = db.Column("user", Serial, db.ForeignKey("user.id"), nullable=False)
@ -117,6 +121,7 @@ class _UserAttribute(db.Model, ModelSerializeMixin):
class _PasswordReset(db.Model): class _PasswordReset(db.Model):
"""Table containing password reset requests""" """Table containing password reset requests"""
__allow_unmapped__ = True
__tablename__ = "password_reset" __tablename__ = "password_reset"
_user_id: User = db.Column("user", Serial, db.ForeignKey("user.id"), primary_key=True) _user_id: User = db.Column("user", Serial, db.ForeignKey("user.id"), primary_key=True)
user: User = db.relationship("User", back_populates="reset_requests_", foreign_keys=[_user_id]) user: User = db.relationship("User", back_populates="reset_requests_", foreign_keys=[_user_id])

View File

@ -100,7 +100,7 @@ class Plugin(BasePlugin):
@property @property
def entry_point(self): def entry_point(self):
ep = entry_points(group="flaschengeist.plugins", name=self.name) ep = tuple(entry_points(group="flaschengeist.plugins", name=self.name))
return ep[0] return ep[0]
def load(self): def load(self):
@ -158,14 +158,13 @@ class Plugin(BasePlugin):
Args: Args:
permissions: List of permissions to install permissions: List of permissions to install
""" """
cur_perm = set(x.name for x in self.permissions) cur_perm = set(x.name for x in self.permissions or [])
all_perm = set(permissions) all_perm = set(permissions)
new_perms = all_perm - cur_perm new_perms = all_perm - cur_perm
self.permissions = list(filter(lambda x: x.name in permissions, self.permissions)) + [ _perms = [ Permission(name=x, plugin_=self) for x in new_perms ]
Permission(name=x, plugin_=self) for x in new_perms #self.permissions = list(filter(lambda x: x.name in permissions, self.permissions and isinstance(self.permissions, list) or []))
] self.permissions.extend(_perms)
class AuthPlugin(Plugin): class AuthPlugin(Plugin):
"""Base class for all authentification plugins """Base class for all authentification plugins

View File

@ -56,6 +56,7 @@ def service_debit():
class BalancePlugin(Plugin): class BalancePlugin(Plugin):
#id = "dev.flaschengeist.balance"
models = models models = models
def install(self): def install(self):
@ -71,7 +72,7 @@ class BalancePlugin(Plugin):
add_scheduled(f"{id}.service_debit", service_debit, minutes=1) add_scheduled(f"{id}.service_debit", service_debit, minutes=1)
@before_update_user @before_update_user
def set_default_limit(user): def set_default_limit(user, *args):
from . import balance_controller from . import balance_controller
try: try:

View File

@ -8,6 +8,7 @@ from flaschengeist.models import ModelSerializeMixin, UtcDateTime, Serial
class Transaction(db.Model, ModelSerializeMixin): class Transaction(db.Model, ModelSerializeMixin):
__allow_unmapped__ = True
__tablename__ = "balance_transaction" __tablename__ = "balance_transaction"
# Protected foreign key properties # Protected foreign key properties
_receiver_id = db.Column("receiver_id", Serial, db.ForeignKey("user.id")) _receiver_id = db.Column("receiver_id", Serial, db.ForeignKey("user.id"))

View File

@ -30,7 +30,7 @@ install_requires =
sqlalchemy_utils>=0.38.3 sqlalchemy_utils>=0.38.3
# Importlib requirement can be dropped when python requirement is >= 3.10 # Importlib requirement can be dropped when python requirement is >= 3.10
importlib_metadata>=4.3 importlib_metadata>=4.3
sqlalchemy>=1.4.40, <2.0 sqlalchemy >= 2.0
toml toml
werkzeug>=2.2.2 werkzeug>=2.2.2