From af2c674ce445d17214f8f53e0fa7b05b2b2c0b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Gr=C3=B6ger?= Date: Sun, 9 Apr 2023 20:57:15 +0200 Subject: [PATCH] fixed most deprecations from flask and sqlalchemy --- flaschengeist/app.py | 29 ++++++++++++++++++++--- flaschengeist/config.py | 2 +- flaschengeist/models/image.py | 1 + flaschengeist/models/notification.py | 1 + flaschengeist/models/plugin.py | 10 ++++---- flaschengeist/models/session.py | 1 + flaschengeist/models/user.py | 17 ++++++++----- flaschengeist/plugins/__init__.py | 11 ++++----- flaschengeist/plugins/balance/__init__.py | 3 ++- flaschengeist/plugins/balance/models.py | 1 + setup.cfg | 2 +- 11 files changed, 56 insertions(+), 22 deletions(-) diff --git a/flaschengeist/app.py b/flaschengeist/app.py index be7d6fd..0e2031c 100644 --- a/flaschengeist/app.py +++ b/flaschengeist/app.py @@ -1,9 +1,12 @@ import enum +import json from flask import Flask from flask_cors import CORS 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 werkzeug.exceptions import HTTPException @@ -11,6 +14,9 @@ from flaschengeist import logger from flaschengeist.controller import pluginController from flaschengeist.utils.hook import Hook from flaschengeist.config import configure_app +from flaschengeist.plugins import Plugin + +from flaschengeist.database import db class CustomJSONEncoder(JSONEncoder): @@ -33,6 +39,19 @@ class CustomJSONEncoder(JSONEncoder): return list(iterable) 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") def load_plugins(app: Flask): @@ -43,7 +62,9 @@ def load_plugins(app: Flask): try: # Load class 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 plugin.load() # Register blueprint @@ -60,7 +81,9 @@ def load_plugins(app: Flask): def create_app(test_config=None, cli=False): app = Flask("flaschengeist") - app.json_encoder = CustomJSONEncoder + # app.json_encoder = CustomJSONEncoder + app.json_provider_class = CustomJSONProvider + app.json = CustomJSONProvider(app) CORS(app) with app.app_context(): diff --git a/flaschengeist/config.py b/flaschengeist/config.py index 712d5d1..fb1963e 100644 --- a/flaschengeist/config.py +++ b/flaschengeist/config.py @@ -28,7 +28,7 @@ def read_configuration(test_config): if not test_config: paths.append(Path.home() / ".config") 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: try: diff --git a/flaschengeist/models/image.py b/flaschengeist/models/image.py index 101a19a..dffd275 100644 --- a/flaschengeist/models/image.py +++ b/flaschengeist/models/image.py @@ -8,6 +8,7 @@ from ..database.types import ModelSerializeMixin, Serial class Image(db.Model, ModelSerializeMixin): + __allow_unmapped__ = True __tablename__ = "image" id: int = db.Column(Serial, primary_key=True) filename_: str = db.Column("filename", db.String(255), nullable=False) diff --git a/flaschengeist/models/notification.py b/flaschengeist/models/notification.py index 55e9640..33b1d8d 100644 --- a/flaschengeist/models/notification.py +++ b/flaschengeist/models/notification.py @@ -8,6 +8,7 @@ from ..database.types import Serial, UtcDateTime, ModelSerializeMixin class Notification(db.Model, ModelSerializeMixin): + __allow_unmapped__ = True __tablename__ = "notification" id: int = db.Column("id", Serial, primary_key=True) text: str = db.Column(db.Text) diff --git a/flaschengeist/models/plugin.py b/flaschengeist/models/plugin.py index a912afd..4254cc8 100644 --- a/flaschengeist/models/plugin.py +++ b/flaschengeist/models/plugin.py @@ -1,6 +1,6 @@ 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 ..database import db @@ -8,6 +8,7 @@ from ..database.types import Serial class PluginSetting(db.Model): + __allow_unmapped__ = True __tablename__ = "plugin_setting" id = db.Column("id", Serial, primary_key=True) plugin_id: int = db.Column("plugin", Serial, db.ForeignKey("plugin.id")) @@ -16,6 +17,7 @@ class PluginSetting(db.Model): class BasePlugin(db.Model): + __allow_unmapped__ = True __tablename__ = "plugin" id: int = db.Column("id", Serial, primary_key=True) name: str = db.Column(db.String(127), nullable=False) @@ -24,7 +26,7 @@ class BasePlugin(db.Model): """The latest installed version""" enabled: bool = db.Column(db.Boolean, default=False) """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" ) """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*. """ - __settings: dict[str, "PluginSetting"] = db.relationship( + __settings: Dict[str, "PluginSetting"] = db.relationship( "PluginSetting", collection_class=attribute_mapped_collection("name"), cascade="all, delete, delete-orphan", - lazy="select", + lazy="subquery", ) def get_setting(self, name: str, **kwargs): diff --git a/flaschengeist/models/session.py b/flaschengeist/models/session.py index 1dac1a3..668622d 100644 --- a/flaschengeist/models/session.py +++ b/flaschengeist/models/session.py @@ -17,6 +17,7 @@ class Session(db.Model, ModelSerializeMixin): token: String to verify access later. """ + __allow_unmapped__ = True __tablename__ = "session" expires: datetime = db.Column(UtcDateTime) token: str = db.Column(db.String(32), unique=True) diff --git a/flaschengeist/models/user.py b/flaschengeist/models/user.py index e12db4a..a51cedc 100644 --- a/flaschengeist/models/user.py +++ b/flaschengeist/models/user.py @@ -1,6 +1,6 @@ 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 sqlalchemy.orm.collections import attribute_mapped_collection @@ -21,6 +21,7 @@ role_permission_association_table = db.Table( class Permission(db.Model, ModelSerializeMixin): + __allow_unmapped__ = True __tablename__ = "permission" name: str = db.Column(db.String(30), unique=True) @@ -30,10 +31,11 @@ class Permission(db.Model, ModelSerializeMixin): class Role(db.Model, ModelSerializeMixin): + __allow_unmapped__ = True __tablename__ = "role" id: int = db.Column(Serial, primary_key=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): @@ -51,6 +53,7 @@ class User(db.Model, ModelSerializeMixin): birthday: Birthday of the user """ + __allow_unmapped__ = True __tablename__ = "user" userid: str = db.Column(db.String(30), unique=True, nullable=False) 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) birthday: Optional[date] = db.Column(db.Date) mail: str = db.Column(db.String(60)) - roles: list[str] = [] permissions: Optional[list[str]] = None + roles: List[str] = [] # Protected stuff for backend use only id_ = db.Column("id", Serial, primary_key=True) - 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") + 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") 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 _avatar_id = db.Column("avatar", Serial, db.ForeignKey("image.id")) @@ -107,6 +110,7 @@ class User(db.Model, ModelSerializeMixin): class _UserAttribute(db.Model, ModelSerializeMixin): + __allow_unmapped__ = True __tablename__ = "user_attribute" id = db.Column("id", Serial, primary_key=True) 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): """Table containing password reset requests""" + __allow_unmapped__ = True __tablename__ = "password_reset" _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]) diff --git a/flaschengeist/plugins/__init__.py b/flaschengeist/plugins/__init__.py index 0c38c38..dadcb85 100644 --- a/flaschengeist/plugins/__init__.py +++ b/flaschengeist/plugins/__init__.py @@ -100,7 +100,7 @@ class Plugin(BasePlugin): @property 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] def load(self): @@ -158,14 +158,13 @@ class Plugin(BasePlugin): Args: 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) new_perms = all_perm - cur_perm - self.permissions = list(filter(lambda x: x.name in permissions, self.permissions)) + [ - Permission(name=x, plugin_=self) for x in new_perms - ] - + _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): """Base class for all authentification plugins diff --git a/flaschengeist/plugins/balance/__init__.py b/flaschengeist/plugins/balance/__init__.py index 4ccfe20..0f923c4 100644 --- a/flaschengeist/plugins/balance/__init__.py +++ b/flaschengeist/plugins/balance/__init__.py @@ -56,6 +56,7 @@ def service_debit(): class BalancePlugin(Plugin): + #id = "dev.flaschengeist.balance" models = models def install(self): @@ -71,7 +72,7 @@ class BalancePlugin(Plugin): add_scheduled(f"{id}.service_debit", service_debit, minutes=1) @before_update_user - def set_default_limit(user): + def set_default_limit(user, *args): from . import balance_controller try: diff --git a/flaschengeist/plugins/balance/models.py b/flaschengeist/plugins/balance/models.py index d5d0061..2d9525b 100644 --- a/flaschengeist/plugins/balance/models.py +++ b/flaschengeist/plugins/balance/models.py @@ -8,6 +8,7 @@ from flaschengeist.models import ModelSerializeMixin, UtcDateTime, Serial class Transaction(db.Model, ModelSerializeMixin): + __allow_unmapped__ = True __tablename__ = "balance_transaction" # Protected foreign key properties _receiver_id = db.Column("receiver_id", Serial, db.ForeignKey("user.id")) diff --git a/setup.cfg b/setup.cfg index b98fc08..27dc3d4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,7 @@ install_requires = sqlalchemy_utils>=0.38.3 # Importlib requirement can be dropped when python requirement is >= 3.10 importlib_metadata>=4.3 - sqlalchemy>=1.4.40, <2.0 + sqlalchemy >= 2.0 toml werkzeug>=2.2.2