From 8ede2fc406eb134a14ace5cf08a5151c27338d36 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 31 Aug 2025 11:17:24 -0500 Subject: [PATCH 1/6] fix: fix 'import-outside-toplevel' for pylint --- .pylintrc | 1 - src/wuttjamaican/app.py | 22 ++++++++++++---------- src/wuttjamaican/auth.py | 5 +---- src/wuttjamaican/conf.py | 12 ++++++++---- src/wuttjamaican/install.py | 18 +++++++++++------- src/wuttjamaican/util.py | 17 ++++++++++------- 6 files changed, 42 insertions(+), 33 deletions(-) diff --git a/.pylintrc b/.pylintrc index 8ee7fa4..e88109b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -4,7 +4,6 @@ disable= attribute-defined-outside-init, fixme, - import-outside-toplevel, too-many-arguments, too-many-branches, too-many-locals, diff --git a/src/wuttjamaican/app.py b/src/wuttjamaican/app.py index bac0512..5bbe7ce 100644 --- a/src/wuttjamaican/app.py +++ b/src/wuttjamaican/app.py @@ -25,11 +25,12 @@ WuttJamaican - app handler """ # pylint: disable=too-many-lines -import importlib import logging import os import sys import warnings +import importlib +from importlib.metadata import version import humanize @@ -282,9 +283,13 @@ class AppHandler: # pylint: disable=too-many-public-methods pkgname = modpath.split(".")[0] try: - from importlib.metadata import packages_distributions + from importlib.metadata import ( # pylint: disable=import-outside-toplevel + packages_distributions, + ) except ImportError: # python < 3.10 - from importlib_metadata import packages_distributions + from importlib_metadata import ( # pylint: disable=import-outside-toplevel + packages_distributions, + ) pkgmap = packages_distributions() if pkgname in pkgmap: @@ -306,8 +311,6 @@ class AppHandler: # pylint: disable=too-many-public-methods :returns: Version as string. """ - from importlib.metadata import version - if not dist: dist = self.get_distribution(obj=obj) if dist: @@ -496,7 +499,7 @@ class AppHandler: # pylint: disable=too-many-public-methods :returns: SQLAlchemy session for the app DB. """ - from .db import Session + from .db import Session # pylint: disable=import-outside-toplevel return Session(**kwargs) @@ -582,7 +585,7 @@ class AppHandler: # pylint: disable=too-many-public-methods associated. Simple convenience wrapper around :func:`sqlalchemy:sqlalchemy.orm.object_session()`. """ - from sqlalchemy import orm + from sqlalchemy import orm # pylint: disable=import-outside-toplevel return orm.object_session(obj) @@ -597,7 +600,7 @@ class AppHandler: # pylint: disable=too-many-public-methods this method will provide a default factory in the form of :meth:`make_session`. """ - from .db import short_session + from .db import short_session # pylint: disable=import-outside-toplevel if "factory" not in kwargs and "config" not in kwargs: kwargs["factory"] = self.make_session @@ -625,7 +628,7 @@ class AppHandler: # pylint: disable=too-many-public-methods :returns: Setting value as string, or ``None``. """ - from .db import get_setting + from .db import get_setting # pylint: disable=import-outside-toplevel return get_setting(session, name) @@ -1114,7 +1117,6 @@ class AppProvider: # pylint: disable=too-few-public-methods """ def __init__(self, config): - if isinstance(config, AppHandler): warnings.warn( "passing app handler to app provider is deprecated; " diff --git a/src/wuttjamaican/auth.py b/src/wuttjamaican/auth.py index 26224cd..1fbce42 100644 --- a/src/wuttjamaican/auth.py +++ b/src/wuttjamaican/auth.py @@ -108,7 +108,7 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods :returns: :class:`~wuttjamaican.db.model.auth.User` instance, or ``None``. """ - from sqlalchemy import orm + from sqlalchemy import orm # pylint: disable=import-outside-toplevel model = self.app.model @@ -170,7 +170,6 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods return role else: # assuming it is a string - # try to match on Role.uuid try: role = session.get(model.Role, _uuid.UUID(key)) @@ -226,7 +225,6 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods # nb. these lookups require a db session if session: - # or maybe it is a uuid if isinstance(obj, _uuid.UUID): user = session.get(model.User, obj) @@ -235,7 +233,6 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods # or maybe it is a string elif isinstance(obj, str): - # try to match on User.uuid try: user = session.get(model.User, _uuid.UUID(obj)) diff --git a/src/wuttjamaican/conf.py b/src/wuttjamaican/conf.py index 985ac77..f083c01 100644 --- a/src/wuttjamaican/conf.py +++ b/src/wuttjamaican/conf.py @@ -240,7 +240,10 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes # configure main app DB if applicable, or disable usedb flag try: - from wuttjamaican.db import Session, get_engines + from wuttjamaican.db import ( # pylint: disable=import-outside-toplevel + Session, + get_engines, + ) except ImportError: if self.usedb: log.warning( @@ -448,7 +451,6 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes # read from defaults + INI files value = self.configuration.get(key) if value is not None: - # nb. if the "value" corresponding to the given key is in # fact a subset/dict of more config values, then we must # "ignore" that. so only return the value if it is *not* @@ -611,7 +613,6 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes os.remove(path) def _write_logging_config_file(self): - # load all current values into configparser parser = configparser.RawConfigParser() for section, values in self.configuration.items(): @@ -710,7 +711,10 @@ def generic_default_files(appname): if sys.platform == "win32": # use pywin32 to fetch official defaults try: - from win32com.shell import shell, shellcon + from win32com.shell import ( # pylint: disable=import-outside-toplevel + shell, + shellcon, + ) except ImportError: return [] diff --git a/src/wuttjamaican/install.py b/src/wuttjamaican/install.py index d0af8b1..688b285 100644 --- a/src/wuttjamaican/install.py +++ b/src/wuttjamaican/install.py @@ -240,7 +240,7 @@ class InstallHandler(GenericHandler): self, dbtype, dbhost, dbport, dbname, dbuser, dbpass ): # pylint: disable=empty-docstring """ """ - from sqlalchemy.engine import URL + from sqlalchemy.engine import URL # pylint: disable=import-outside-toplevel if dbtype == "mysql": drivername = "mysql+mysqlconnector" @@ -258,7 +258,7 @@ class InstallHandler(GenericHandler): def test_db_connection(self, url): # pylint: disable=empty-docstring """ """ - import sqlalchemy as sa + import sqlalchemy as sa # pylint: disable=import-outside-toplevel engine = sa.create_engine(url) @@ -442,7 +442,9 @@ class InstallHandler(GenericHandler): :param db_url: :class:`sqlalchemy:sqlalchemy.engine.URL` instance. """ - from alembic.util.messaging import obfuscate_url_pw + from alembic.util.messaging import ( # pylint: disable=import-outside-toplevel + obfuscate_url_pw, + ) if not self.prompt_bool("install db schema?", True): return False @@ -488,7 +490,7 @@ class InstallHandler(GenericHandler): def require_prompt_toolkit(self, answer=None): # pylint: disable=empty-docstring """ """ try: - import prompt_toolkit # pylint: disable=unused-import + import prompt_toolkit # pylint: disable=unused-import,import-outside-toplevel except ImportError: value = answer or input( "\nprompt_toolkit is not installed. shall i install it? [Yn] " @@ -503,7 +505,7 @@ class InstallHandler(GenericHandler): ) # nb. this should now succeed - import prompt_toolkit + import prompt_toolkit # pylint: disable=import-outside-toplevel def rprint(self, *args, **kwargs): """ @@ -513,7 +515,9 @@ class InstallHandler(GenericHandler): def get_prompt_style(self): # pylint: disable=empty-docstring """ """ - from prompt_toolkit.styles import Style + from prompt_toolkit.styles import ( # pylint: disable=import-outside-toplevel + Style, + ) # message formatting styles return Style.from_dict( @@ -554,7 +558,7 @@ class InstallHandler(GenericHandler): unless ``is_bool`` was requested in which case ``True`` or ``False``. """ - from prompt_toolkit import prompt + from prompt_toolkit import prompt # pylint: disable=import-outside-toplevel # build prompt message message = [ diff --git a/src/wuttjamaican/util.py b/src/wuttjamaican/util.py index 76c517b..6c03e2b 100644 --- a/src/wuttjamaican/util.py +++ b/src/wuttjamaican/util.py @@ -101,9 +101,9 @@ def load_entry_points(group, ignore_errors=False): try: # nb. this package was added in python 3.8 - import importlib.metadata as importlib_metadata + import importlib.metadata as importlib_metadata # pylint: disable=import-outside-toplevel except ImportError: - import importlib_metadata + import importlib_metadata # pylint: disable=import-outside-toplevel eps = importlib_metadata.entry_points() if not hasattr(eps, "select"): @@ -306,9 +306,7 @@ def progress_loop(func, items, factory, message=None): progress = factory(message, count) for i, item in enumerate(items, 1): - func(item, i) - if progress: progress.update(i) @@ -343,12 +341,17 @@ def resource_path(path): :returns: Absolute file path to the resource. """ if not os.path.isabs(path) and ":" in path: - try: # nb. these were added in python 3.9 - from importlib.resources import files, as_file + from importlib.resources import ( # pylint: disable=import-outside-toplevel + files, + as_file, + ) except ImportError: # python < 3.9 - from importlib_resources import files, as_file + from importlib_resources import ( # pylint: disable=import-outside-toplevel + files, + as_file, + ) package, filename = path.split(":") ref = files(package) / filename From 5d7dda8163f08c90019da347e628b6d3c6337032 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 31 Aug 2025 11:20:24 -0500 Subject: [PATCH 2/6] fix: fix 'too-many-arguments' for pylint --- .pylintrc | 1 - src/wuttjamaican/auth.py | 2 +- src/wuttjamaican/conf.py | 8 ++++---- src/wuttjamaican/email.py | 7 +++---- src/wuttjamaican/install.py | 4 ++-- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.pylintrc b/.pylintrc index e88109b..70f3ec2 100644 --- a/.pylintrc +++ b/.pylintrc @@ -4,7 +4,6 @@ disable= attribute-defined-outside-init, fixme, - too-many-arguments, too-many-branches, too-many-locals, too-many-positional-arguments, diff --git a/src/wuttjamaican/auth.py b/src/wuttjamaican/auth.py index 1fbce42..0c7bec3 100644 --- a/src/wuttjamaican/auth.py +++ b/src/wuttjamaican/auth.py @@ -512,7 +512,7 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods return cache - def has_permission( + def has_permission( # pylint: disable=too-many-arguments self, session, principal, diff --git a/src/wuttjamaican/conf.py b/src/wuttjamaican/conf.py index f083c01..15004b2 100644 --- a/src/wuttjamaican/conf.py +++ b/src/wuttjamaican/conf.py @@ -192,7 +192,7 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes default_app_handler_spec = "wuttjamaican.app:AppHandler" default_engine_maker_spec = "wuttjamaican.db.conf:make_engine_from_config" - def __init__( + def __init__( # pylint: disable=too-many-arguments self, files=None, defaults=None, @@ -336,7 +336,7 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes # get current value, sans db return self.get(key, usedb=False) - def get( + def get( # pylint: disable=too-many-arguments self, key, default=UNSPECIFIED, @@ -753,7 +753,7 @@ def generic_default_files(appname): ] -def get_config_paths( +def get_config_paths( # pylint: disable=too-many-arguments files=None, plus_files=None, appname="wutta", @@ -942,7 +942,7 @@ def get_config_paths( return files -def make_config( +def make_config( # pylint: disable=too-many-arguments files=None, plus_files=None, appname="wutta", diff --git a/src/wuttjamaican/email.py b/src/wuttjamaican/email.py index 2966f3c..d29a521 100644 --- a/src/wuttjamaican/email.py +++ b/src/wuttjamaican/email.py @@ -172,7 +172,7 @@ class Message: # pylint: disable=too-many-instance-attributes List of file attachments for the message. """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, key=None, sender=None, @@ -277,7 +277,6 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods # prefer configured list of template lookup paths, if set templates = self.config.get_list(f"{self.config.appname}.email.templates") if not templates: - # otherwise use all available paths, from app providers available = [] for provider in self.app.providers.values(): @@ -455,7 +454,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods # fall back to global default, if present return self.config.get(f"{self.config.appname}.email.default.replyto") - def get_auto_subject( + def get_auto_subject( # pylint: disable=too-many-arguments self, key, context=None, rendered=True, setting=None, default=None ): """ @@ -756,7 +755,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods f"{self.config.appname}.mail.send_emails", default=False ) - def send_email( + def send_email( # pylint: disable=too-many-arguments self, key=None, context=None, message=None, sender=None, recips=None, **kwargs ): """ diff --git a/src/wuttjamaican/install.py b/src/wuttjamaican/install.py index 688b285..b7dd39d 100644 --- a/src/wuttjamaican/install.py +++ b/src/wuttjamaican/install.py @@ -238,7 +238,7 @@ class InstallHandler(GenericHandler): def make_db_url( self, dbtype, dbhost, dbport, dbname, dbuser, dbpass - ): # pylint: disable=empty-docstring + ): # pylint: disable=empty-docstring,too-many-arguments """ """ from sqlalchemy.engine import URL # pylint: disable=import-outside-toplevel @@ -527,7 +527,7 @@ class InstallHandler(GenericHandler): } ) - def prompt_generic( + def prompt_generic( # pylint: disable=too-many-arguments self, info, default=None, From 8e67de4947a052aea5e15d8953acf6f9267bad76 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 31 Aug 2025 11:28:23 -0500 Subject: [PATCH 3/6] fix: fix 'too-many-positional-arguments' for pylint --- .pylintrc | 1 - src/wuttjamaican/auth.py | 2 +- src/wuttjamaican/conf.py | 8 ++++---- src/wuttjamaican/email.py | 6 +++--- src/wuttjamaican/install.py | 4 ++-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.pylintrc b/.pylintrc index 70f3ec2..7a86915 100644 --- a/.pylintrc +++ b/.pylintrc @@ -6,4 +6,3 @@ disable= fixme, too-many-branches, too-many-locals, - too-many-positional-arguments, diff --git a/src/wuttjamaican/auth.py b/src/wuttjamaican/auth.py index 0c7bec3..ac6dbe6 100644 --- a/src/wuttjamaican/auth.py +++ b/src/wuttjamaican/auth.py @@ -512,7 +512,7 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods return cache - def has_permission( # pylint: disable=too-many-arguments + def has_permission( # pylint: disable=too-many-arguments,too-many-positional-arguments self, session, principal, diff --git a/src/wuttjamaican/conf.py b/src/wuttjamaican/conf.py index 15004b2..ad834a6 100644 --- a/src/wuttjamaican/conf.py +++ b/src/wuttjamaican/conf.py @@ -192,7 +192,7 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes default_app_handler_spec = "wuttjamaican.app:AppHandler" default_engine_maker_spec = "wuttjamaican.db.conf:make_engine_from_config" - def __init__( # pylint: disable=too-many-arguments + def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments self, files=None, defaults=None, @@ -336,7 +336,7 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes # get current value, sans db return self.get(key, usedb=False) - def get( # pylint: disable=too-many-arguments + def get( # pylint: disable=too-many-arguments,too-many-positional-arguments self, key, default=UNSPECIFIED, @@ -753,7 +753,7 @@ def generic_default_files(appname): ] -def get_config_paths( # pylint: disable=too-many-arguments +def get_config_paths( # pylint: disable=too-many-arguments,too-many-positional-arguments files=None, plus_files=None, appname="wutta", @@ -942,7 +942,7 @@ def get_config_paths( # pylint: disable=too-many-arguments return files -def make_config( # pylint: disable=too-many-arguments +def make_config( # pylint: disable=too-many-arguments,too-many-positional-arguments files=None, plus_files=None, appname="wutta", diff --git a/src/wuttjamaican/email.py b/src/wuttjamaican/email.py index d29a521..8d398c5 100644 --- a/src/wuttjamaican/email.py +++ b/src/wuttjamaican/email.py @@ -172,7 +172,7 @@ class Message: # pylint: disable=too-many-instance-attributes List of file attachments for the message. """ - def __init__( # pylint: disable=too-many-arguments + def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments self, key=None, sender=None, @@ -454,7 +454,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods # fall back to global default, if present return self.config.get(f"{self.config.appname}.email.default.replyto") - def get_auto_subject( # pylint: disable=too-many-arguments + def get_auto_subject( # pylint: disable=too-many-arguments,too-many-positional-arguments self, key, context=None, rendered=True, setting=None, default=None ): """ @@ -755,7 +755,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods f"{self.config.appname}.mail.send_emails", default=False ) - def send_email( # pylint: disable=too-many-arguments + def send_email( # pylint: disable=too-many-arguments,too-many-positional-arguments self, key=None, context=None, message=None, sender=None, recips=None, **kwargs ): """ diff --git a/src/wuttjamaican/install.py b/src/wuttjamaican/install.py index b7dd39d..754b43a 100644 --- a/src/wuttjamaican/install.py +++ b/src/wuttjamaican/install.py @@ -238,7 +238,7 @@ class InstallHandler(GenericHandler): def make_db_url( self, dbtype, dbhost, dbport, dbname, dbuser, dbpass - ): # pylint: disable=empty-docstring,too-many-arguments + ): # pylint: disable=empty-docstring,too-many-arguments,too-many-positional-arguments """ """ from sqlalchemy.engine import URL # pylint: disable=import-outside-toplevel @@ -527,7 +527,7 @@ class InstallHandler(GenericHandler): } ) - def prompt_generic( # pylint: disable=too-many-arguments + def prompt_generic( # pylint: disable=too-many-arguments,too-many-positional-arguments self, info, default=None, From d9db40fddcaec5a8643d9a50de42a622a8bb8fba Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 31 Aug 2025 11:25:41 -0500 Subject: [PATCH 4/6] fix: fix 'too-many-locals' for pylint --- .pylintrc | 1 - src/wuttjamaican/conf.py | 36 +++++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.pylintrc b/.pylintrc index 7a86915..bd7e6f3 100644 --- a/.pylintrc +++ b/.pylintrc @@ -5,4 +5,3 @@ disable= attribute-defined-outside-init, fixme, too-many-branches, - too-many-locals, diff --git a/src/wuttjamaican/conf.py b/src/wuttjamaican/conf.py index ad834a6..fc8ed5c 100644 --- a/src/wuttjamaican/conf.py +++ b/src/wuttjamaican/conf.py @@ -275,20 +275,8 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes raise FileNotFoundError(f"could not read required config file: {path}") return - # load all values into (yet another) temp config - temp_config = configparser.RawConfigParser() - for section in config.sections(): - temp_config.add_section(section) - # nb. must interpolate most values but *not* for logging formatters - raw = section.startswith("formatter_") - for option in config.options(section): - temp_config.set(section, option, config.get(section, option, raw=raw)) - - # re-write as temp file with "final" values - fd, temp_path = tempfile.mkstemp(suffix=".ini") - os.close(fd) - with open(temp_path, "wt", encoding="utf_8") as f: - temp_config.write(f) + # write config to temp file + temp_path = self._write_temp_config_file(config) # and finally, load that into our main config config = configuration.config_from_ini(temp_path, read_from_file=True) @@ -308,6 +296,24 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes for p in self.parse_list(includes): self._load_ini_configs(p, configs, require=False) + def _write_temp_config_file(self, config): + # load all values into (yet another) temp config + temp_config = configparser.RawConfigParser() + for section in config.sections(): + temp_config.add_section(section) + # nb. must interpolate most values but *not* for logging formatters + raw = section.startswith("formatter_") + for option in config.options(section): + temp_config.set(section, option, config.get(section, option, raw=raw)) + + # re-write as temp file with "final" values + fd, temp_path = tempfile.mkstemp(suffix=".ini") + os.close(fd) + with open(temp_path, "wt", encoding="utf_8") as f: + temp_config.write(f) + + return temp_path + def get_prioritized_files(self): """ Returns list of config files in order of priority. @@ -942,7 +948,7 @@ def get_config_paths( # pylint: disable=too-many-arguments,too-many-positional- return files -def make_config( # pylint: disable=too-many-arguments,too-many-positional-arguments +def make_config( # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals files=None, plus_files=None, appname="wutta", From 957c334d1d77980d343bbfd38bb5d5f821454433 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 31 Aug 2025 11:42:30 -0500 Subject: [PATCH 5/6] fix: fix 'attribute-defined-outside-init' for pylint --- .pylintrc | 1 - src/wuttjamaican/app.py | 7 ++++--- src/wuttjamaican/conf.py | 3 ++- src/wuttjamaican/email.py | 8 ++++---- src/wuttjamaican/install.py | 35 +++++++++++++++++------------------ src/wuttjamaican/reports.py | 8 ++++---- tests/test_conf.py | 20 ++------------------ tests/test_install.py | 10 +--------- 8 files changed, 34 insertions(+), 58 deletions(-) diff --git a/.pylintrc b/.pylintrc index bd7e6f3..a8bc68a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -2,6 +2,5 @@ [MESSAGES CONTROL] disable= - attribute-defined-outside-init, fixme, too-many-branches, diff --git a/src/wuttjamaican/app.py b/src/wuttjamaican/app.py index 5bbe7ce..c10e6bc 100644 --- a/src/wuttjamaican/app.py +++ b/src/wuttjamaican/app.py @@ -143,7 +143,7 @@ class AppHandler: # pylint: disable=too-many-public-methods return self.get_enum() if name == "providers": - self.providers = self.get_all_providers() + self.__dict__["providers"] = self.get_all_providers() return self.providers for provider in self.providers.values(): @@ -340,7 +340,7 @@ class AppHandler: # pylint: disable=too-many-public-methods usedb=False, default=self.default_model_spec, ) - self.model = importlib.import_module(spec) + self.__dict__["model"] = importlib.import_module(spec) return self.model def get_enum(self): @@ -364,7 +364,7 @@ class AppHandler: # pylint: disable=too-many-public-methods spec = self.config.get( f"{self.appname}.enum_spec", usedb=False, default=self.default_enum_spec ) - self.enum = importlib.import_module(spec) + self.__dict__["enum"] = importlib.import_module(spec) return self.enum def load_object(self, spec): @@ -1154,6 +1154,7 @@ class GenericHandler: self.config = config self.app = self.config.get_app() self.modules = {} + self.classes = {} @property def appname(self): diff --git a/src/wuttjamaican/conf.py b/src/wuttjamaican/conf.py index fc8ed5c..b30a274 100644 --- a/src/wuttjamaican/conf.py +++ b/src/wuttjamaican/conf.py @@ -189,6 +189,7 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes See also :ref:`where-config-settings-come-from`. """ + _app = None default_app_handler_spec = "wuttjamaican.app:AppHandler" default_engine_maker_spec = "wuttjamaican.db.conf:make_engine_from_config" @@ -640,7 +641,7 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes See also :doc:`/narr/handlers/app`. """ - if not hasattr(self, "_app"): + if not self._app: spec = self.get( f"{self.appname}.app.handler", usedb=False, diff --git a/src/wuttjamaican/email.py b/src/wuttjamaican/email.py index 8d398c5..ab4040c 100644 --- a/src/wuttjamaican/email.py +++ b/src/wuttjamaican/email.py @@ -323,8 +323,8 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods This calls :meth:`get_email_modules()` and for each module, it discovers all the email settings it contains. """ - if not hasattr(self, "_email_settings"): - self._email_settings = {} + if "email_settings" not in self.classes: + self.classes["email_settings"] = {} for module in self.get_email_modules(): for name in dir(module): obj = getattr(module, name) @@ -333,9 +333,9 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods and obj is not EmailSetting and issubclass(obj, EmailSetting) ): - self._email_settings[obj.__name__] = obj + self.classes["email_settings"][obj.__name__] = obj - return self._email_settings + return self.classes["email_settings"] def get_email_setting(self, key, instance=True): """ diff --git a/src/wuttjamaican/install.py b/src/wuttjamaican/install.py index 754b43a..5077644 100644 --- a/src/wuttjamaican/install.py +++ b/src/wuttjamaican/install.py @@ -85,6 +85,7 @@ class InstallHandler(GenericHandler): app_title = None pypi_name = None egg_name = None + schema_installed = False def __init__(self, config, **kwargs): super().__init__(config) @@ -100,24 +101,6 @@ class InstallHandler(GenericHandler): if not self.egg_name: self.egg_name = self.pypi_name.replace("-", "_") - def run(self): - """ - Run the interactive command-line installer. - - This does the following: - - * check for ``prompt_toolkit`` and maybe ask to install it - * define the template lookup paths, for making config files - * call :meth:`show_welcome()` - * call :meth:`sanity_check()` - * call :meth:`do_install_steps()` - * call :meth:`show_goodbye()` - - Although if a problem is encountered then not all calls may - happen. - """ - self.require_prompt_toolkit() - paths = [ self.app.resource_path("wuttjamaican:templates/install"), ] @@ -131,6 +114,22 @@ class InstallHandler(GenericHandler): self.templates = TemplateLookup(directories=paths) + def run(self): + """ + Run the interactive command-line installer. + + This does the following: + + * check for ``prompt_toolkit`` and maybe ask to install it + * call :meth:`show_welcome()` + * call :meth:`sanity_check()` + * call :meth:`do_install_steps()` + * call :meth:`show_goodbye()` + + Although if a problem is encountered then not all calls may + happen. + """ + self.require_prompt_toolkit() self.show_welcome() self.sanity_check() self.schema_installed = False diff --git a/src/wuttjamaican/reports.py b/src/wuttjamaican/reports.py index 136ffa0..92b5705 100644 --- a/src/wuttjamaican/reports.py +++ b/src/wuttjamaican/reports.py @@ -157,8 +157,8 @@ class ReportHandler(GenericHandler): This calls :meth:`get_report_modules()` and for each module, it discovers all the reports it contains. """ - if not hasattr(self, "_reports"): - self._reports = {} + if "reports" not in self.classes: + self.classes["reports"] = {} for module in self.get_report_modules(): for name in dir(module): obj = getattr(module, name) @@ -167,9 +167,9 @@ class ReportHandler(GenericHandler): and obj is not Report and issubclass(obj, Report) ): - self._reports[obj.report_key] = obj + self.classes["reports"][obj.report_key] = obj - return self._reports + return self.classes["reports"] def get_report(self, key, instance=True): """ diff --git a/tests/test_conf.py b/tests/test_conf.py index 4ec5d97..18953b3 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -17,7 +17,6 @@ from wuttjamaican.testing import FileTestCase, ConfigTestCase class TestWuttaConfig(FileTestCase): - def make_config(self, **kwargs): return mod.WuttaConfig(**kwargs) @@ -291,7 +290,6 @@ configure_logging = true ) with patch.object(conf.WuttaConfig, "_configure_logging") as method: - # no logging config by default config = conf.WuttaConfig() method.assert_not_called() @@ -319,7 +317,6 @@ configure_logging = true ) with patch("wuttjamaican.conf.logging") as logging: - # basic constructor attempts logging config config = conf.WuttaConfig(configure_logging=True) logging.config.fileConfig.assert_called_once() @@ -338,14 +335,14 @@ configure_logging = true logging.config.fileConfig.assert_called_once() def test_config_has_no_app_after_init(self): - # initial config should *not* have an app yet, otherwise # extensions cannot specify a default app handler config = conf.WuttaConfig() - self.assertFalse(hasattr(config, "_app")) + self.assertIsNone(config._app) # but after that we can get an app okay app = config.get_app() + self.assertIsNotNone(app) self.assertIs(app, config._app) def test_setdefault(self): @@ -628,7 +625,6 @@ configure_logging = true self.assertEqual(value[2], "baz") def test_get_app(self): - # default handler config = conf.WuttaConfig() self.assertEqual(config.default_app_handler_spec, "wuttjamaican.app:AppHandler") @@ -686,7 +682,6 @@ def custom_make_engine_from_config(): class TestWuttaConfigExtension(TestCase): - def test_basic(self): config = conf.WuttaConfig() ext = conf.WuttaConfigExtension() @@ -695,7 +690,6 @@ class TestWuttaConfigExtension(TestCase): class TestGenericDefaultFiles(TestCase): - def test_linux(self): files = conf.generic_default_files("wuttatest") self.assertIsInstance(files, list) @@ -707,7 +701,6 @@ class TestGenericDefaultFiles(TestCase): win32com.shell.SHGetSpecialFolderPath.return_value = r"C:" + os.sep with patch.dict("sys.modules", **{"win32com.shell": win32com}): with patch("wuttjamaican.conf.sys", platform="win32"): - files = conf.generic_default_files("wuttatest") self.assertIsInstance(files, list) self.assertTrue(len(files) > 1) @@ -723,14 +716,12 @@ class TestGenericDefaultFiles(TestCase): with patch("builtins.__import__", side_effect=mock_import): with patch("wuttjamaican.conf.sys", platform="win32"): - files = conf.generic_default_files("wuttatest") self.assertIsInstance(files, list) self.assertEqual(len(files), 0) class TestGetConfigPaths(FileTestCase): - def test_winsvc(self): myconf = self.write_file( "my.conf", @@ -753,7 +744,6 @@ winsvc.RattailFileMonitor = /path/to/other/file class TestMakeConfig(FileTestCase): - # nb. we use appname='wuttatest' in this suite to avoid any # "valid" default config files, env vars etc. which may be present # on the dev machine @@ -763,7 +753,6 @@ class TestMakeConfig(FileTestCase): with patch("wuttjamaican.conf.generic_default_files") as generic_default_files: with patch("wuttjamaican.conf.WuttaConfig") as WuttaConfig: - # generic files are used if nothing is specified generic_default_files.return_value = [generic] config = conf.make_config(appname="wuttatest") @@ -788,7 +777,6 @@ class TestMakeConfig(FileTestCase): with patch("wuttjamaican.conf.generic_default_files") as generic_default_files: with patch("wuttjamaican.conf.WuttaConfig") as WuttaConfig: - # generic defaults are used if nothing specified generic_default_files.return_value = [generic] config = conf.make_config(appname="wuttatest") @@ -832,7 +820,6 @@ class TestMakeConfig(FileTestCase): with patch("wuttjamaican.conf.generic_default_files") as generic_default_files: with patch("wuttjamaican.conf.WuttaConfig") as WuttaConfig: - generic_default_files.return_value = [generic] # no plus files by default @@ -877,7 +864,6 @@ class TestMakeConfig(FileTestCase): with patch("wuttjamaican.conf.generic_default_files") as generic_default_files: with patch("wuttjamaican.conf.WuttaConfig") as WuttaConfig: - generic_default_files.return_value = [generic] # generic files by default @@ -922,7 +908,6 @@ class TestMakeConfig(FileTestCase): with patch.object(mod, "WuttaConfig") as WuttaConfig: with patch.object(mod, "load_entry_points") as load_entry_points: - # no entry points loaded if extend=False config = mod.make_config(appname="wuttatest", extend=False) WuttaConfig.assert_called_once_with( @@ -967,7 +952,6 @@ class TestMakeConfig(FileTestCase): class TestWuttaConfigProfile(ConfigTestCase): - def make_profile(self, key): return mod.WuttaConfigProfile(self.config, key) diff --git a/tests/test_install.py b/tests/test_install.py index f05ffea..a58725f 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -12,7 +12,6 @@ from wuttjamaican.testing import ConfigTestCase class TestInstallHandler(ConfigTestCase): - def make_handler(self, **kwargs): return mod.InstallHandler(self.config, **kwargs) @@ -38,7 +37,6 @@ class TestInstallHandler(ConfigTestCase): with patch.object(mod, "sys") as sys: with patch.object(handler, "rprint") as rprint: with patch.object(handler, "prompt_bool") as prompt_bool: - # user continues prompt_bool.return_value = True handler.show_welcome() @@ -54,7 +52,6 @@ class TestInstallHandler(ConfigTestCase): with patch.object(mod, "sys") as sys: with patch.object(mod, "os") as os: with patch.object(handler, "rprint") as rprint: - # pretend appdir does not exist os.path.exists.return_value = False handler.sanity_check() @@ -79,10 +76,9 @@ class TestInstallHandler(ConfigTestCase): with patch.object(handler, "get_dbinfo", return_value=dbinfo): with patch.object(handler, "make_appdir") as make_appdir: with patch.object(handler, "install_db_schema") as install_db_schema: - # nb. just for sanity/coverage install_db_schema.return_value = True - self.assertFalse(hasattr(handler, "schema_installed")) + self.assertFalse(handler.schema_installed) handler.do_install_steps() self.assertTrue(make_appdir.called) self.assertTrue(handler.schema_installed) @@ -109,7 +105,6 @@ class TestInstallHandler(ConfigTestCase): with patch.object(handler, "prompt_generic", side_effect=prompt_generic): with patch.object(handler, "test_db_connection") as test_db_connection: with patch.object(handler, "rprint") as rprint: - # bad dbinfo test_db_connection.return_value = "bad dbinfo" sys.exit.side_effect = RuntimeError @@ -220,7 +215,6 @@ default.url = {db_url} db_url = sa.create_engine(db_url).url with patch.object(mod, "subprocess") as subprocess: - # user declines offer to install schema with patch.object(handler, "prompt_bool", return_value=False): self.assertFalse(handler.install_db_schema(db_url, appdir=self.tempdir)) @@ -321,7 +315,6 @@ default.url = {db_url} with patch("builtins.__import__", side_effect=mock_import): with patch.object(handler, "get_prompt_style", return_value=style): with patch.object(handler, "rprint") as rprint: - # no input or default value mock_prompt.return_value = "" result = handler.prompt_generic("foo") @@ -452,7 +445,6 @@ default.url = {db_url} with patch("builtins.__import__", side_effect=mock_import): with patch.object(handler, "rprint") as rprint: - # no default; true input mock_prompt.reset_mock() mock_prompt.return_value = "Y" From 0688ed4dd1522dd3b230f910e227d5023f2b3af0 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 31 Aug 2025 11:51:59 -0500 Subject: [PATCH 6/6] fix: fix 'too-many-branches' for pylint --- .pylintrc | 4 +-- src/wuttjamaican/conf.py | 68 ++++++++++++++++++++++------------------ 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/.pylintrc b/.pylintrc index a8bc68a..7eb5e2c 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,6 +1,4 @@ # -*- mode: conf; -*- [MESSAGES CONTROL] -disable= - fixme, - too-many-branches, +disable=fixme diff --git a/src/wuttjamaican/conf.py b/src/wuttjamaican/conf.py index b30a274..d30f90c 100644 --- a/src/wuttjamaican/conf.py +++ b/src/wuttjamaican/conf.py @@ -884,28 +884,7 @@ def get_config_paths( # pylint: disable=too-many-arguments,too-many-positional- # first identify any "primary" config files if files is None: - if not env_files_name: - env_files_name = f"{appname.upper()}_CONFIG_FILES" - - files = env.get(env_files_name) - if files is not None: - files = files.split(os.pathsep) - - elif default_files: - if callable(default_files): - files = default_files(appname) or [] - elif isinstance(default_files, str): - files = [default_files] - else: - files = list(default_files) - files = [path for path in files if os.path.exists(path)] - - else: - files = [] - for path in generic_default_files(appname): - if os.path.exists(path): - files.append(path) - + files = _get_primary_config_files(appname, env, env_files_name, default_files) elif isinstance(files, str): files = [files] else: @@ -937,18 +916,47 @@ def get_config_paths( # pylint: disable=too-many-arguments,too-many-positional- # which config file as part of the actual service definition in # windows, so the service name is used for magic lookup here. if winsvc: - config = configparser.ConfigParser() - config.read(files) - section = f"{appname}.config" - if config.has_section(section): - option = f"winsvc.{winsvc}" - if config.has_option(section, option): - # replace file paths with whatever config value says - files = parse_list(config.get(section, option)) + files = _get_winsvc_config_files(appname, winsvc, files) return files +def _get_primary_config_files(appname, env, env_files_name, default_files): + if not env_files_name: + env_files_name = f"{appname.upper()}_CONFIG_FILES" + + files = env.get(env_files_name) + if files is not None: + return files.split(os.pathsep) + + if default_files: + if callable(default_files): + files = default_files(appname) or [] + elif isinstance(default_files, str): + files = [default_files] + else: + files = list(default_files) + return [path for path in files if os.path.exists(path)] + + files = [] + for path in generic_default_files(appname): + if os.path.exists(path): + files.append(path) + return files + + +def _get_winsvc_config_files(appname, winsvc, files): + config = configparser.ConfigParser() + config.read(files) + section = f"{appname}.config" + if config.has_section(section): + option = f"winsvc.{winsvc}" + if config.has_option(section, option): + # replace file paths with whatever config value says + files = parse_list(config.get(section, option)) + return files + + def make_config( # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals files=None, plus_files=None,