From c0f958d990a6595b7f0844a9a001f7e214fb9c8e Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Thu, 31 Oct 2024 18:39:53 -0700 Subject: [PATCH] Add UserSettingRepository, SystemSettingRepository. --- lute/app_factory.py | 12 +- lute/backup/routes.py | 9 +- lute/backup/service.py | 10 +- lute/book/routes.py | 5 +- lute/book/stats.py | 5 +- lute/db/demo.py | 14 ++- lute/db/management.py | 5 +- lute/dev_api/routes.py | 5 +- lute/language/routes.py | 8 +- lute/models/setting.py | 139 ++++++++++++++---------- lute/parse/mecab_parser.py | 12 +- lute/read/routes.py | 5 +- lute/settings/routes.py | 24 ++-- lute/term/routes.py | 5 +- lute/themes/routes.py | 10 +- lute/themes/service.py | 13 ++- lute/utils/formutils.py | 7 +- tests/unit/backup/test_backup.py | 5 +- tests/unit/db/test_management.py | 2 +- tests/unit/models/test_Setting.py | 96 +++++++++------- tests/unit/parse/test_JapaneseParser.py | 5 +- tests/unit/themes/test_service.py | 24 ++-- tests/unit/utils/test_formutils.py | 10 +- 23 files changed, 254 insertions(+), 176 deletions(-) diff --git a/lute/app_factory.py b/lute/app_factory.py index 6b127ab91..a7f822728 100644 --- a/lute/app_factory.py +++ b/lute/app_factory.py @@ -34,7 +34,7 @@ from lute.models.book import Book from lute.models.language import Language -from lute.models.setting import BackupSettings, UserSetting +from lute.models.setting import BackupSettings, UserSettingRepository from lute.book.stats import mark_stale from lute.book.routes import bp as book_bp @@ -111,22 +111,23 @@ def inject_menu_bar_vars(): """ Inject backup settings into the all templates for the menu bar. """ - bs = BackupSettings.get_backup_settings() + bs = BackupSettings(db.session) have_languages = len(db.session.query(Language).all()) > 0 + usersetting_repo = UserSettingRepository(db.session) ret = { "have_languages": have_languages, "backup_enabled": bs.backup_enabled, "backup_directory": bs.backup_dir, "backup_last_display_date": bs.last_backup_display_date, "backup_time_since": bs.time_since_last_backup, - "user_settings": json.dumps(UserSetting.all_settings()), + "user_settings": json.dumps(usersetting_repo.all_settings()), } return ret @app.route("/") def index(): is_production = not lute.db.demo.contains_demo_data() - bkp_settings = BackupSettings.get_backup_settings() + bkp_settings = BackupSettings(db.session) have_books = len(db.session.query(Book).all()) > 0 have_languages = len(db.session.query(Language).all()) > 0 @@ -305,7 +306,8 @@ def _pragmas_on_connect(dbapi_con, con_record): # pylint: disable=unused-argume with app.app_context(): db.create_all() - UserSetting.load() + usersetting_repo = UserSettingRepository(db.session) + usersetting_repo.load() # TODO valid parsers: do parser check, mark valid as active, invalid as inactive. clean_data() app.db = db diff --git a/lute/backup/routes.py b/lute/backup/routes.py index ffaf647d2..0c47c919f 100644 --- a/lute/backup/routes.py +++ b/lute/backup/routes.py @@ -16,6 +16,7 @@ send_file, flash, ) +from lute.db import db from lute.models.setting import BackupSettings from lute.backup.service import create_backup, skip_this_backup, list_backups @@ -28,7 +29,7 @@ def index(): """ List all backups. """ - settings = BackupSettings.get_backup_settings() + settings = BackupSettings(db.session) backups = list_backups(settings.backup_dir) backups.sort(reverse=True) @@ -40,7 +41,7 @@ def index(): @bp.route("/download/") def download_backup(filename): "Download the given backup file." - settings = BackupSettings.get_backup_settings() + settings = BackupSettings(db.session) fullpath = os.path.join(settings.backup_dir, filename) return send_file(fullpath, as_attachment=True) @@ -56,7 +57,7 @@ def backup(): if "type" in request.args: backuptype = "manual" - settings = BackupSettings.get_backup_settings() + settings = BackupSettings(db.session) return render_template( "backup/backup.html", backup_folder=settings.backup_dir, backuptype=backuptype ) @@ -73,7 +74,7 @@ def do_backup(): backuptype = prms["type"] c = current_app.env_config - settings = BackupSettings.get_backup_settings() + settings = BackupSettings(db.session) is_manual = backuptype.lower() == "manual" try: f = create_backup(c, settings, is_manual=is_manual) diff --git a/lute/backup/service.py b/lute/backup/service.py index 60cf95ce0..bf5db0f80 100644 --- a/lute/backup/service.py +++ b/lute/backup/service.py @@ -11,7 +11,7 @@ from typing import List, Union from lute.db import db -from lute.models.setting import SystemSetting +from lute.models.setting import SystemSettingRepository from lute.models.book import Book from lute.models.term import Term @@ -79,7 +79,7 @@ def create_backup(app_config, settings, is_manual=False, suffix=None): suffix can be specified for test. - settings are from Setting.get_backup_settings(). + settings are from BackupSettings. - backup_enabled - backup_dir - backup_auto @@ -157,13 +157,15 @@ def _create_db_backup(dbfilename, backupfile): ) as out_file: shutil.copyfileobj(in_file, out_file) os.remove(backupfile) - SystemSetting.set_last_backup_datetime(int(time.time())) + r = SystemSettingRepository(db.session) + r.set_last_backup_datetime(int(time.time())) return f def skip_this_backup(): "Set the last backup time to today." - SystemSetting.set_last_backup_datetime(int(time.time())) + r = SystemSettingRepository(db.session) + r.set_last_backup_datetime(int(time.time())) def _remove_excess_backups(count, outdir): diff --git a/lute/book/routes.py b/lute/book/routes.py index a80925765..7d043fc80 100644 --- a/lute/book/routes.py +++ b/lute/book/routes.py @@ -21,7 +21,7 @@ from lute.models.language import Language, LanguageRepository from lute.models.book import BookRepository -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository from lute.book.model import Book, Repository @@ -120,7 +120,8 @@ def new(): flash(e.message, "notice") # Don't set the current language before submit. - current_language_id = int(UserSetting.get_value("current_language_id")) + usrepo = UserSettingRepository(db.session) + current_language_id = int(usrepo.get_value("current_language_id")) form.language_id.data = current_language_id return render_template( diff --git a/lute/book/stats.py b/lute/book/stats.py index 22ea68f7c..8efa97b55 100644 --- a/lute/book/stats.py +++ b/lute/book/stats.py @@ -6,7 +6,7 @@ from lute.read.render.service import get_multiword_indexer, get_textitems from lute.db import db from lute.models.book import Book -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository # from lute.utils.debug_helpers import DebugTimer @@ -37,7 +37,8 @@ def calc_status_distribution(book): break txindex += 1 - sample_size = int(UserSetting.get_value("stats_calc_sample_size") or 5) + repo = UserSettingRepository(db.session) + sample_size = int(repo.get_value("stats_calc_sample_size") or 5) texts = _last_n_pages(book, txindex, sample_size) # Getting the individual paragraphs per page, and then combining, diff --git a/lute/db/demo.py b/lute/db/demo.py index 7b63e68e7..8e27de416 100644 --- a/lute/db/demo.py +++ b/lute/db/demo.py @@ -12,7 +12,7 @@ import lute.language.service from lute.book.model import Repository from lute.book.stats import refresh_stats -from lute.models.setting import SystemSetting +from lute.models.setting import SystemSettingRepository from lute.db import db import lute.db.management @@ -43,7 +43,8 @@ def contains_demo_data(): """ True if IsDemoData setting is present. """ - ss = SystemSetting.get_value("IsDemoData") + repo = SystemSettingRepository(db.session) + ss = repo.get_value("IsDemoData") if ss is None: return False return True @@ -56,7 +57,8 @@ def remove_flag(): if not contains_demo_data(): raise RuntimeError("Can't delete non-demo data.") - SystemSetting.delete_key("IsDemoData") + repo = SystemSettingRepository(db.session) + repo.delete_key("IsDemoData") db.session.commit() @@ -120,7 +122,8 @@ def load_demo_stories(): r.add(b) r.commit() - SystemSetting.set_value("IsDemoData", True) + repo = SystemSettingRepository(db.session) + repo.set_value("IsDemoData", True) db.session.commit() refresh_stats() @@ -131,5 +134,6 @@ def load_demo_data(): """ load_demo_languages() load_demo_stories() - SystemSetting.set_value("IsDemoData", True) + repo = SystemSettingRepository(db.session) + repo.set_value("IsDemoData", True) db.session.commit() diff --git a/lute/db/management.py b/lute/db/management.py index 81bd3ccf4..86e071266 100644 --- a/lute/db/management.py +++ b/lute/db/management.py @@ -4,7 +4,7 @@ from sqlalchemy import text from lute.db import db -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository def delete_all_data(): @@ -25,4 +25,5 @@ def delete_all_data(): for s in statements: db.session.execute(text(s)) db.session.commit() - UserSetting.load() + repo = UserSettingRepository(db.session) + repo.load() diff --git a/lute/dev_api/routes.py b/lute/dev_api/routes.py index 3f3016c63..88e9f1b25 100644 --- a/lute/dev_api/routes.py +++ b/lute/dev_api/routes.py @@ -18,7 +18,7 @@ from sqlalchemy import text from flask import Blueprint, current_app, Response, jsonify, redirect, flash from lute.models.language import Language -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository import lute.parse.registry from lute.db import db import lute.db.management @@ -148,7 +148,8 @@ def disable_parser(parsername, renameto): @bp.route("/disable_backup", methods=["GET"]) def disable_backup(): "Disables backup -- tests don't need to back up." - UserSetting.set_value("backup_enabled", False) + repo = UserSettingRepository(db.session) + repo.set_value("backup_enabled", False) db.session.commit() flash("backup disabled") return redirect("/", 302) diff --git a/lute/language/routes.py b/lute/language/routes.py index c877f98d9..fd845f89d 100644 --- a/lute/language/routes.py +++ b/lute/language/routes.py @@ -6,7 +6,7 @@ from sqlalchemy.exc import IntegrityError from flask import Blueprint, current_app, render_template, redirect, url_for, flash from lute.models.language import Language -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository import lute.language.service from lute.language.forms import LanguageForm from lute.db import db @@ -135,7 +135,8 @@ def new(langname): # adds language Y, the filter stays on X, which may be # disconcerting/confusing. Forcing a reselect is painless and # unambiguous. - UserSetting.set_value("current_language_id", 0) + repo = UserSettingRepository(db.session) + repo.set_value("current_language_id", 0) db.session.commit() return redirect("/") @@ -172,7 +173,8 @@ def list_predefined(): def load_predefined(langname): "Load a predefined language and its stories." lang_id = lute.language.service.load_language_def(langname) - UserSetting.set_value("current_language_id", lang_id) + repo = UserSettingRepository(db.session) + repo.set_value("current_language_id", lang_id) db.session.commit() flash(f"Loaded {langname} and sample book(s)") return redirect("/") diff --git a/lute/models/setting.py b/lute/models/setting.py index 118363cc3..f9f716090 100644 --- a/lute/models/setting.py +++ b/lute/models/setting.py @@ -23,52 +23,70 @@ class SettingBase(db.Model): value = db.Column("StValue", db.String, nullable=False) __mapper_args__ = {"polymorphic_on": keytype} - @classmethod - def key_exists_precheck(cls, keyname): + +class SettingRepositoryBase: + "Repository." + + def __init__(self, session, classtype): + self.session = session + self.classtype = classtype + + def key_exists_precheck(self, keyname): """ Check key validity for certain actions. """ - @classmethod - def set_value_post(cls, keyname, keyvalue): + def set_value_post(self, keyname, keyvalue): """ Post-setting value for certain keys." """ - @classmethod - def set_value(cls, keyname, keyvalue): + def set_value(self, keyname, keyvalue): "Set, but don't save, a setting." - cls.key_exists_precheck(keyname) - s = db.session.query(cls).filter(cls.key == keyname).first() + self.key_exists_precheck(keyname) + s = ( + self.session.query(self.classtype) + .filter(self.classtype.key == keyname) + .first() + ) if s is None: - s = cls() + s = self.classtype() s.key = keyname s.value = keyvalue - db.session.add(s) - cls.set_value_post(keyname, keyvalue) + self.session.add(s) + self.set_value_post(keyname, keyvalue) - @classmethod - def key_exists(cls, keyname): + def key_exists(self, keyname): "True if exists." - s = db.session.query(cls).filter(cls.key == keyname).first() + s = ( + self.session.query(self.classtype) + .filter(self.classtype.key == keyname) + .first() + ) no_key = s is None return not no_key - @classmethod - def get_value(cls, keyname): + def get_value(self, keyname): "Get the saved key, or None if it doesn't exist." - cls.key_exists_precheck(keyname) - s = db.session.query(cls).filter(cls.key == keyname).first() + self.key_exists_precheck(keyname) + s = ( + self.session.query(self.classtype) + .filter(self.classtype.key == keyname) + .first() + ) if s is None: return None return s.value - @classmethod - def delete_key(cls, keyname): + def delete_key(self, keyname): "Delete a key." - s = db.session.query(cls).filter(cls.key == keyname).first() + s = ( + self.session.query(self.classtype) + .filter(self.classtype.key == keyname) + .first() + ) if s is not None: - db.session.delete(s) + self.session.delete(s) class MissingUserSettingKeyException(Exception): @@ -82,16 +100,21 @@ class UserSetting(SettingBase): __tablename__ = None __mapper_args__ = {"polymorphic_identity": "user"} - @classmethod - def key_exists_precheck(cls, keyname): + +class UserSettingRepository(SettingRepositoryBase): + "Repository." + + def __init__(self, session): + super().__init__(session, UserSetting) + + def key_exists_precheck(self, keyname): """ User keys must exist. """ - if not UserSetting.key_exists(keyname): + if not self.key_exists(keyname): raise MissingUserSettingKeyException(keyname) - @classmethod - def set_value_post(cls, keyname, keyvalue): + def set_value_post(self, keyname, keyvalue): """ Setting some keys runs other code. """ @@ -103,8 +126,7 @@ def set_value_post(cls, keyname, keyvalue): else: os.environ[mp] = keyvalue.strip() - @staticmethod - def _revised_mecab_path(): + def _revised_mecab_path(self): """ Change the mecab_path if it's not found, and a replacement is found. @@ -118,7 +140,7 @@ def _revised_mecab_path(): new one found, otherwise just return the old one. """ - mp = UserSetting.get_value("mecab_path") + mp = self.get_value("mecab_path") if mp is not None and os.path.exists(mp): return mp @@ -137,8 +159,7 @@ def _revised_mecab_path(): # Replacement not found, leave current value as-is. return mp - @staticmethod - def load(): + def load(self): """ Load missing user settings with default values. """ @@ -199,28 +220,27 @@ def load(): "hotkey_NextSentence": "", } for k, v in keys_and_defaults.items(): - if not UserSetting.key_exists(k): + if not self.key_exists(k): s = UserSetting() s.key = k s.value = v - db.session.add(s) - db.session.commit() + self.session.add(s) + self.session.commit() # Revise the mecab path if necessary. # Note this is done _after_ the defaults are loaded, # because the user may have already loaded the defaults # (e.g. on machine upgrade) and stored them in the db, # so we may have to _update_ the existing setting. - revised_mecab_path = UserSetting._revised_mecab_path() - UserSetting.set_value("mecab_path", revised_mecab_path) - db.session.commit() + revised_mecab_path = self._revised_mecab_path() + self.set_value("mecab_path", revised_mecab_path) + self.session.commit() - @staticmethod - def all_settings(): + def all_settings(self): """ Get dict of all settings, for rendering into Javascript global space. """ - settings = db.session.query(UserSetting).all() + settings = self.session.query(UserSetting).all() ret = {} for s in settings: ret[s.key] = s.value @@ -240,19 +260,24 @@ class SystemSetting(SettingBase): # Helpers for certain sys settings. - @classmethod - def get_last_backup_datetime(cls): + +class SystemSettingRepository(SettingRepositoryBase): + "Repository." + + def __init__(self, session): + super().__init__(session, SystemSetting) + + def get_last_backup_datetime(self): "Get the last_backup_datetime as int, or None." - v = cls.get_value("lastbackup") + v = self.get_value("lastbackup") if v is None: return None return int(v) - @classmethod - def set_last_backup_datetime(cls, v): + def set_last_backup_datetime(self, v): "Set and save the last backup time." - cls.set_value("lastbackup", v) - db.session.commit() + self.set_value("lastbackup", v) + self.session.commit() class BackupSettings: @@ -261,17 +286,20 @@ class BackupSettings: Getter only. """ - def __init__(self): + def __init__(self, session): + us_repo = UserSettingRepository(session) + ss_repo = SystemSettingRepository(session) + def _bool(k): - v = UserSetting.get_value(k) + v = us_repo.get_value(k) return v in (1, "1", "y", True) self.backup_enabled = _bool("backup_enabled") self.backup_auto = _bool("backup_auto") self.backup_warn = _bool("backup_warn") - self.backup_dir = UserSetting.get_value("backup_dir") - self.backup_count = int(UserSetting.get_value("backup_count") or 5) - self.last_backup_datetime = SystemSetting.get_last_backup_datetime() + self.backup_dir = us_repo.get_value("backup_dir") + self.backup_count = int(us_repo.get_value("backup_count") or 5) + self.last_backup_datetime = ss_repo.get_last_backup_datetime() @property def last_backup_display_date(self): @@ -281,11 +309,6 @@ def last_backup_display_date(self): return None return datetime.datetime.fromtimestamp(t).strftime("%Y-%m-%d %H:%M:%S") - @staticmethod - def get_backup_settings(): - "Get BackupSettings." - return BackupSettings() - @property def time_since_last_backup(self): """ diff --git a/lute/parse/mecab_parser.py b/lute/parse/mecab_parser.py index dbe0b0b87..25c09cd6c 100644 --- a/lute/parse/mecab_parser.py +++ b/lute/parse/mecab_parser.py @@ -17,8 +17,9 @@ from typing import List from natto import MeCab import jaconv +from lute.db import db from lute.parse.base import ParsedToken, AbstractParser -from lute.models.setting import UserSetting, MissingUserSettingKeyException +from lute.models.setting import UserSettingRepository, MissingUserSettingKeyException class JapaneseParser(AbstractParser): @@ -30,7 +31,7 @@ class JapaneseParser(AbstractParser): The parser uses natto-py library, and so should be able to find mecab automatically; if it can't, you may need to set the MECAB_PATH env variable, - managed by UserSetting.set_value("mecab_path", p) + managed by UserSettingRepository.set_value("mecab_path", p) """ _is_supported = None @@ -136,8 +137,13 @@ def get_reading(self, text: str): return None jp_reading_setting = "" + # TODO fix_implicit_dependency: this is pretty trashy. The + # settings are necessary, but the dependencies here need to + # change, as getting the reading shouldn't require the + # db.session. + repo = UserSettingRepository(db.session) try: - jp_reading_setting = UserSetting.get_value("japanese_reading") + jp_reading_setting = repo.get_value("japanese_reading") except MissingUserSettingKeyException: # During loading of demo data, the key isn't set, but the # reading isn't needed either, as this is only called when diff --git a/lute/read/routes.py b/lute/read/routes.py index bd9b6a965..db9187a6e 100644 --- a/lute/read/routes.py +++ b/lute/read/routes.py @@ -9,7 +9,7 @@ from lute.term.model import Repository from lute.term.routes import handle_term_form from lute.models.book import Text, BookRepository -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository from lute.db import db @@ -21,7 +21,8 @@ def _render_book_page(book, pagenum): Render a particular book page. """ lang = book.language - show_highlights = bool(int(UserSetting.get_value("show_highlights"))) + usrepo = UserSettingRepository(db.session) + show_highlights = bool(int(usrepo.get_value("show_highlights"))) term_dicts = lang.all_dictionaries()[lang.id]["term"] return render_template( diff --git a/lute/settings/routes.py b/lute/settings/routes.py index 43d61432a..b72dbb9fd 100644 --- a/lute/settings/routes.py +++ b/lute/settings/routes.py @@ -17,7 +17,7 @@ from wtforms.validators import InputRequired, NumberRange from wtforms import ValidationError from lute.models.language import Language -from lute.models.setting import UserSetting +from lute.models.setting import UserSetting, UserSettingRepository from lute.themes.service import list_themes from lute.db import db from lute.parse.mecab_parser import JapaneseParser @@ -98,11 +98,12 @@ def edit_settings(): # Backup dir gets mounted from host. form.backup_dir.render_kw = kw + repo = UserSettingRepository(db.session) if form.validate_on_submit(): # Update the settings in the database for field in form: if field.id not in ("csrf_token", "submit"): - UserSetting.set_value(field.id, field.data) + repo.set_value(field.id, field.data) db.session.commit() flash("Settings updated", "success") @@ -111,7 +112,7 @@ def edit_settings(): # Load current settings from the database for field in form: if field.id != "csrf_token": - field.data = UserSetting.get_value(field.id) + field.data = repo.get_value(field.id) if isinstance(field, BooleanField): # Hack: set boolean settings to ints, otherwise they're always checked. field.data = int(field.data or 0) @@ -129,10 +130,11 @@ def test_parse(): """ mecab_path = request.args.get("mecab_path", None) - old_setting = UserSetting.get_value("mecab_path") + repo = UserSettingRepository(db.session) + old_setting = repo.get_value("mecab_path") result = {"failure": "tbd"} try: - UserSetting.set_value("mecab_path", mecab_path) + repo.set_value("mecab_path", mecab_path) # Parsing requires a language, even if it's a dummy. lang = Language() p = JapaneseParser() @@ -145,7 +147,7 @@ def test_parse(): message = f"{type(e).__name__}: { str(e) }" result = {"result": "failure", "message": message} finally: - UserSetting.set_value("mecab_path", old_setting) + repo.set_value("mecab_path", old_setting) return jsonify(result) @@ -153,13 +155,14 @@ def test_parse(): @bp.route("/set//", methods=["POST"]) def set_key_value(key, value): "Set a UserSetting key to value." - old_value = UserSetting.get_value(key) + repo = UserSettingRepository(db.session) + old_value = repo.get_value(key) try: - UserSetting.set_value(key, value) + repo.set_value(key, value) result = {"result": "success", "message": "OK"} except Exception as e: # pylint: disable=broad-exception-caught message = f"{type(e).__name__}: { str(e) }" - UserSetting.set_value(key, old_value) + repo.set_value(key, old_value) result = {"result": "failure", "message": message} db.session.commit() return jsonify(result) @@ -236,13 +239,14 @@ def _get_categorized_hotkeys(): @bp.route("/shortcuts", methods=["GET", "POST"]) def edit_shortcuts(): "Edit shortcuts." + repo = UserSettingRepository(db.session) form = UserShortcutsForm() if form.validate_on_submit(): # print(request.form, flush=True) # Update the settings in the database for k, v in request.form.items(): # print(f"{k} = {v}", flush=True) - UserSetting.set_value(k, v) + repo.set_value(k, v) db.session.commit() flash("Shortcuts updated", "success") return redirect("/") diff --git a/lute/term/routes.py b/lute/term/routes.py index 80e443d62..45063b97a 100644 --- a/lute/term/routes.py +++ b/lute/term/routes.py @@ -15,7 +15,7 @@ ) from lute.models.language import Language, LanguageRepository from lute.models.term import TermRepository, Status -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository from lute.utils.data_tables import DataTablesFlaskParamParser from lute.term.datatables import get_data_tables_list from lute.term.model import Repository, Term @@ -154,7 +154,8 @@ def handle_term_form( else: # The language select control is shown and this is a new term, # so use the default value. - current_language_id = int(UserSetting.get_value("current_language_id")) + us_repo = UserSettingRepository(db.session) + current_language_id = int(us_repo.get_value("current_language_id")) form.language_id.data = current_language_id return render_template( diff --git a/lute/themes/routes.py b/lute/themes/routes.py index dc1312555..89a8ade75 100644 --- a/lute/themes/routes.py +++ b/lute/themes/routes.py @@ -3,7 +3,7 @@ from flask import Blueprint, Response, jsonify from lute.themes.service import get_current_css, next_theme -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository from lute.db import db bp = Blueprint("themes", __name__, url_prefix="/theme") @@ -22,7 +22,8 @@ def custom_styles(): """ Return the custom settings for inclusion in the base.html. """ - css = UserSetting.get_value("custom_styles") + repo = UserSettingRepository(db.session) + css = repo.get_value("custom_styles") response = Response(css, 200) response.content_type = "text/css; charset=utf-8" return response @@ -38,7 +39,8 @@ def set_next_theme(): @bp.route("/toggle_highlight", methods=["POST"]) def toggle_highlight(): "Fix the highlight." - b = bool(int(UserSetting.get_value("show_highlights"))) - UserSetting.set_value("show_highlights", not b) + repo = UserSettingRepository(db.session) + b = bool(int(repo.get_value("show_highlights"))) + repo.set_value("show_highlights", not b) db.session.commit() return jsonify("ok") diff --git a/lute/themes/service.py b/lute/themes/service.py index 8676aa850..9ebcf30d3 100644 --- a/lute/themes/service.py +++ b/lute/themes/service.py @@ -1,14 +1,13 @@ """ Theming service. -Themes are stored in the css folder. Current theme is saved in -UserSetting. +Themes are stored in the css folder, current theme in UserSetting. """ import os from glob import glob from flask import current_app -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository from lute.db import db default_entry = ("-", "(default)") @@ -53,7 +52,8 @@ def get_current_css(): """ Return the current css pointed at by the current_theme user setting. """ - current_theme = UserSetting.get_value("current_theme") + repo = UserSettingRepository(db.session) + current_theme = repo.get_value("current_theme") if current_theme == default_entry[0]: return "" @@ -76,12 +76,13 @@ def next_theme(): """ Move to the next theme in the list of themes. """ - current_theme = UserSetting.get_value("current_theme") + repo = UserSettingRepository(db.session) + current_theme = repo.get_value("current_theme") themes = [t[0] for t in list_themes()] themes.append(default_entry[0]) for i in range(0, len(themes)): # pylint: disable=consider-using-enumerate if themes[i] == current_theme: new_index = i + 1 break - UserSetting.set_value("current_theme", themes[new_index]) + repo.set_value("current_theme", themes[new_index]) db.session.commit() diff --git a/lute/utils/formutils.py b/lute/utils/formutils.py index 3607eafce..827ddf82b 100644 --- a/lute/utils/formutils.py +++ b/lute/utils/formutils.py @@ -3,7 +3,7 @@ """ from lute.models.language import Language -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository from lute.db import db @@ -28,7 +28,8 @@ def valid_current_language_id(): Get the current language id from UserSetting, ensuring it's still valid. If not, change it. """ - current_language_id = UserSetting.get_value("current_language_id") + repo = UserSettingRepository(db.session) + current_language_id = repo.get_value("current_language_id") current_language_id = int(current_language_id) valid_language_ids = [int(p[0]) for p in language_choices()] @@ -36,6 +37,6 @@ def valid_current_language_id(): return current_language_id current_language_id = valid_language_ids[0] - UserSetting.set_value("current_language_id", current_language_id) + repo.set_value("current_language_id", current_language_id) db.session.commit() return current_language_id diff --git a/tests/unit/backup/test_backup.py b/tests/unit/backup/test_backup.py index 64f06d092..863166bba 100644 --- a/tests/unit/backup/test_backup.py +++ b/tests/unit/backup/test_backup.py @@ -16,6 +16,7 @@ list_backups, ) from lute.models.setting import BackupSettings +from lute.db import db # pylint: disable=missing-function-docstring # Test method names are pretty descriptive already. @@ -53,7 +54,7 @@ def cleanup_directory(directory): @pytest.fixture(name="backup_settings") def fixture_backup_settings(app_context, bkp_dir): # app_context is passed so that the db session is available. - ret = BackupSettings.get_backup_settings() + ret = BackupSettings(db.session) ret.backup_dir = bkp_dir ret.backup_enabled = True yield ret @@ -141,7 +142,7 @@ def test_last_import_setting_is_updated_on_successful_backup( ): assert backup_settings.last_backup_datetime is None, "no backup" create_backup(testconfig, backup_settings) - updated = BackupSettings.get_backup_settings() + updated = BackupSettings(db.session) assert updated.last_backup_datetime is not None, "set" diff --git a/tests/unit/db/test_management.py b/tests/unit/db/test_management.py index d9966bb91..d64cf8575 100644 --- a/tests/unit/db/test_management.py +++ b/tests/unit/db/test_management.py @@ -48,6 +48,6 @@ def test_wiping_db_clears_out_all_tables(app_context): def test_can_get_backup_settings_when_db_is_wiped(app_context): "The backupsettings struct assumes certain things about the data." delete_all_data() - bs = BackupSettings.get_backup_settings() + bs = BackupSettings(db.session) assert bs.backup_enabled, "backup is back to being enabled" assert bs.backup_dir is not None, "default restored" diff --git a/tests/unit/models/test_Setting.py b/tests/unit/models/test_Setting.py index 3a2f83897..e91b2a3b7 100644 --- a/tests/unit/models/test_Setting.py +++ b/tests/unit/models/test_Setting.py @@ -8,60 +8,74 @@ from sqlalchemy import text from lute.db import db from lute.models.setting import ( - UserSetting, + UserSettingRepository, + SystemSettingRepository, MissingUserSettingKeyException, - SystemSetting, BackupSettings, ) from tests.dbasserts import assert_sql_result -def test_user_and_system_settings_do_not_intersect(app_context): +@pytest.fixture(name="us_repo") +def fixture_usersetting_repo(app_context): + "Repo" + r = UserSettingRepository(db.session) + return r + + +@pytest.fixture(name="ss_repo") +def fixture_systemsetting_repo(app_context): + "Repo" + r = SystemSettingRepository(db.session) + return r + + +def test_user_and_system_settings_do_not_intersect(us_repo, ss_repo): "A UserSetting is not available as a system setting." - UserSetting.set_value("backup_count", 42) + us_repo.set_value("backup_count", 42) db.session.commit() sql = "select StValue, StKeyType from settings where StKey = 'backup_count'" assert_sql_result(sql, ["42; user"], "loaded") - u = UserSetting.get_value("backup_count") + u = us_repo.get_value("backup_count") assert u == "42", "found user setting" - assert SystemSetting.get_value("backup_count") is None, "not in system settings" + assert ss_repo.get_value("backup_count") is None, "not in system settings" -def test_save_and_retrieve_user_setting(app_context): +def test_save_and_retrieve_user_setting(us_repo): "Smoke tests." - UserSetting.set_value("backup_count", 42) + us_repo.set_value("backup_count", 42) sql = "select StValue from settings where StKey = 'backup_count'" assert_sql_result(sql, ["5"], "still default") db.session.commit() assert_sql_result(sql, ["42"], "now set") - v = UserSetting.get_value("backup_count") + v = us_repo.get_value("backup_count") assert v == "42", "is string" -def test_missing_value_value_is_nullapp_context(app_context): +def test_missing_value_value_is_null(ss_repo): "Missing key = None." - assert SystemSetting.get_value("missing") is None, "missing key" + assert ss_repo.get_value("missing") is None, "missing key" -def test_smoke_last_backup(app_context): +def test_smoke_last_backup(ss_repo): "Check syntax only." - v = SystemSetting.get_last_backup_datetime() + v = ss_repo.get_last_backup_datetime() assert v is None, "not set" - SystemSetting.set_last_backup_datetime(42) - v = SystemSetting.get_last_backup_datetime() + ss_repo.set_last_backup_datetime(42) + v = ss_repo.get_last_backup_datetime() assert v == 42, "set _and_ saved" -def test_get_backup_settings(app_context): +def test_get_backup_settings(us_repo): "Smoke test." - UserSetting.set_value("backup_dir", "blah") - UserSetting.set_value("backup_count", 12) - UserSetting.set_value("backup_warn", 0) + us_repo.set_value("backup_dir", "blah") + us_repo.set_value("backup_count", 12) + us_repo.set_value("backup_warn", 0) db.session.commit() - b = BackupSettings.get_backup_settings() + b = BackupSettings(db.session) assert b.backup_dir == "blah" assert b.backup_auto is True # initial defaults assert b.backup_warn is False # set to 0 above @@ -75,7 +89,7 @@ def test_time_since_last_backup_future(app_context): current time = 600, backup time = 900 """ - b = BackupSettings.get_backup_settings() + b = BackupSettings(db.session) with patch("time.time", return_value=600): b.last_backup_datetime = 900 assert b.time_since_last_backup is None @@ -87,7 +101,7 @@ def test_time_since_last_backup_none(app_context): current time = 600, backup time = None """ - b = BackupSettings.get_backup_settings() + b = BackupSettings(db.session) with patch("time.time", return_value=600): b.last_backup_datetime = None assert b.time_since_last_backup is None @@ -99,7 +113,7 @@ def test_time_since_last_backup_right_now(app_context): current time = 600, backup time = 600 """ - b = BackupSettings.get_backup_settings() + b = BackupSettings(db.session) with patch("time.time", return_value=600): b.last_backup_datetime = 600 assert b.time_since_last_backup == "0 seconds ago" @@ -111,7 +125,7 @@ def test_time_since_last_backup_in_past(app_context): current time = 62899200, backup time = various """ - b = BackupSettings.get_backup_settings() + b = BackupSettings(db.session) now = 62899200 with patch("time.time", return_value=now): b.last_backup_datetime = now - 45 @@ -136,16 +150,16 @@ def test_time_since_last_backup_in_past(app_context): assert b.time_since_last_backup == "75 weeks ago" -def test_user_settings_loaded_with_defaults(app_context): +def test_user_settings_loaded_with_defaults(us_repo): "Called on load." db.session.execute(text("delete from settings")) db.session.commit() - assert UserSetting.key_exists("backup_dir") is False, "key removed" - UserSetting.load() - assert UserSetting.key_exists("backup_dir") is True, "key created" + assert us_repo.key_exists("backup_dir") is False, "key removed" + us_repo.load() + assert us_repo.key_exists("backup_dir") is True, "key created" # Check defaults - b = BackupSettings.get_backup_settings() + b = BackupSettings(db.session) assert b.backup_enabled is True assert b.backup_dir is not None assert b.backup_auto is True @@ -153,29 +167,29 @@ def test_user_settings_loaded_with_defaults(app_context): assert b.backup_count == 5 -def test_user_settings_load_leaves_existing_values(app_context): +def test_user_settings_load_leaves_existing_values(us_repo): "Called on load." - UserSetting.set_value("backup_count", 17) + us_repo.set_value("backup_count", 17) db.session.commit() - assert UserSetting.get_value("backup_count") == "17" - UserSetting.load() - b = BackupSettings.get_backup_settings() + assert us_repo.get_value("backup_count") == "17" + us_repo.load() + b = BackupSettings(db.session) assert b.backup_count == 17, "still 17" -def test_get_or_set_user_setting_unknown_key_throws(app_context): +def test_get_or_set_user_setting_unknown_key_throws(us_repo): "Safety, ensure no typo for user settings." with pytest.raises(MissingUserSettingKeyException): - UserSetting.get_value("bad_key") + us_repo.get_value("bad_key") with pytest.raises(MissingUserSettingKeyException): - UserSetting.set_value("bad_key", 17) + us_repo.set_value("bad_key", 17) -def test_setting_mecab_path_sets_env_var(app_context): +def test_setting_mecab_path_sets_env_var(us_repo): "Natto-py needs an env var." - UserSetting.set_value("mecab_path", "blah") + us_repo.set_value("mecab_path", "blah") assert os.environ["MECAB_PATH"] == "blah", "was set" - UserSetting.set_value("mecab_path", None) + us_repo.set_value("mecab_path", None) assert os.environ.get("MECAB_PATH", "X") == "X", "not set" - UserSetting.set_value("mecab_path", "") + us_repo.set_value("mecab_path", "") assert os.environ.get("MECAB_PATH", "Y") == "Y", "not set" diff --git a/tests/unit/parse/test_JapaneseParser.py b/tests/unit/parse/test_JapaneseParser.py index b00dd49d8..b3cdde0c8 100644 --- a/tests/unit/parse/test_JapaneseParser.py +++ b/tests/unit/parse/test_JapaneseParser.py @@ -4,7 +4,7 @@ from lute.parse.mecab_parser import JapaneseParser from lute.models.term import Term -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository from lute.db import db from lute.parse.base import ParsedToken @@ -104,7 +104,8 @@ def test_reading_setting(app_context): "alphabet": "tsuyoi", } p = JapaneseParser() + repo = UserSettingRepository(db.session) for k, v in cases.items(): - UserSetting.set_value("japanese_reading", k) + repo.set_value("japanese_reading", k) db.session.commit() assert p.get_reading("強い") == v, k diff --git a/tests/unit/themes/test_service.py b/tests/unit/themes/test_service.py index 6b5016c59..bd39132ae 100644 --- a/tests/unit/themes/test_service.py +++ b/tests/unit/themes/test_service.py @@ -5,7 +5,7 @@ import os import lute.themes.service as svc from lute.db import db -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository def test_list_themes(app_context): @@ -20,20 +20,23 @@ def test_list_themes(app_context): def test_default_theme_is_blank_css(app_context): "UserSetting starts off with blank css." - assert UserSetting.get_value("current_theme") == "-" + repo = UserSettingRepository(db.session) + assert repo.get_value("current_theme") == "-" assert svc.get_current_css() == "", "Default = empty string." def test_bad_setting_returns_blank_css(app_context): "Just in case." - UserSetting.set_value("current_theme", "_missing_file.css") + repo = UserSettingRepository(db.session) + repo.set_value("current_theme", "_missing_file.css") db.session.commit() assert svc.get_current_css() == "", "Missing = empty string." def test_setting_a_theme_returns_its_css(app_context): "User choice is used." - UserSetting.set_value("current_theme", "Apple_Books.css") + repo = UserSettingRepository(db.session) + repo.set_value("current_theme", "Apple_Books.css") db.session.commit() assert "Georgia" in svc.get_current_css(), "font specified" @@ -44,9 +47,10 @@ def test_next_theme_cycles_themes(app_context): while reading, via a hotkey. """ lst = svc.list_themes() - assert UserSetting.get_value("current_theme") == lst[0][0] + repo = UserSettingRepository(db.session) + assert repo.get_value("current_theme") == lst[0][0] svc.next_theme() - assert UserSetting.get_value("current_theme") == lst[1][0] + assert repo.get_value("current_theme") == lst[1][0] for _ in range(0, len(lst) + 10): # pylint: disable=consider-using-enumerate svc.next_theme() svc.next_theme() @@ -74,7 +78,8 @@ def test_custom_theme_in_theme_dir_is_available(app, app_context): lst = svc.list_themes() assert ("my_theme.css", "my theme") in lst, "Have my theme" - UserSetting.set_value("current_theme", "my_theme.css") + repo = UserSettingRepository(db.session) + repo.set_value("current_theme", "my_theme.css") db.session.commit() assert mytheme_content in svc.get_current_css(), "my theme used" @@ -86,7 +91,8 @@ def test_custom_theme_in_theme_dir_appends_to_existing_theme(app, app_context): lst = svc.list_themes() assert ("Apple_Books.css", "Apple Books") in lst - UserSetting.set_value("current_theme", "Apple_Books.css") + repo = UserSettingRepository(db.session) + repo.set_value("current_theme", "Apple_Books.css") db.session.commit() old_content = svc.get_current_css() @@ -98,7 +104,7 @@ def test_custom_theme_in_theme_dir_appends_to_existing_theme(app, app_context): lst = svc.list_themes() assert ("Apple_Books.css", "Apple Books") in lst, "Have my theme" - UserSetting.set_value("current_theme", "Apple_Books.css") + repo.set_value("current_theme", "Apple_Books.css") db.session.commit() new_css = old_content + "\n\n/* Additional user css */\n\n" + mytheme_content diff --git a/tests/unit/utils/test_formutils.py b/tests/unit/utils/test_formutils.py index 4ca281932..5bf448768 100644 --- a/tests/unit/utils/test_formutils.py +++ b/tests/unit/utils/test_formutils.py @@ -2,8 +2,9 @@ DataTables sqlite tests. """ +from lute.db import db from lute.utils.formutils import language_choices, valid_current_language_id -from lute.models.setting import UserSetting +from lute.models.setting import UserSettingRepository def test_language_choices(app_context): @@ -16,8 +17,9 @@ def test_language_choices(app_context): def test_valid_current_language_id(app_context): "Sanity check only." - UserSetting.set_value("current_language_id", 9999) - assert int(UserSetting.get_value("current_language_id")) == 9999, "pre-check" + repo = UserSettingRepository(db.session) + repo.set_value("current_language_id", 9999) + assert int(repo.get_value("current_language_id")) == 9999, "pre-check" curr_lang_id = int(valid_current_language_id()) assert curr_lang_id == 0, "set back to 0" - assert int(UserSetting.get_value("current_language_id")) == 0, "re-set to 0" + assert int(repo.get_value("current_language_id")) == 0, "re-set to 0"