diff --git a/.pylintrc b/.pylintrc index 893bde4..bcf5b0b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,14 +1,11 @@ # -*- mode: conf; -*- [MESSAGES CONTROL] -disable= - attribute-defined-outside-init, - fixme, - import-outside-toplevel, - too-many-arguments, - too-many-branches, - too-many-instance-attributes, - too-many-lines, - too-many-locals, - too-many-positional-arguments, - too-many-public-methods, +disable=all +enable=anomalous-backslash-in-string, + dangerous-default-value, + inconsistent-return-statements, + redefined-argument-from-local, + unspecified-encoding, + unused-argument, + unused-import, diff --git a/pyproject.toml b/pyproject.toml index 25fddc0..6e75b48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,6 @@ dependencies = [ 'importlib-metadata; python_version < "3.10"', "importlib_resources ; python_version < '3.9'", "Mako", - "packaging", "progress", "python-configuration", "typer", diff --git a/src/wuttjamaican/_version.py b/src/wuttjamaican/_version.py index 6a6bb9c..9cd05c1 100644 --- a/src/wuttjamaican/_version.py +++ b/src/wuttjamaican/_version.py @@ -1,7 +1,4 @@ # -*- coding: utf-8; -*- -""" -Package Version -""" from importlib.metadata import version diff --git a/src/wuttjamaican/app.py b/src/wuttjamaican/app.py index 4e68a87..aba810b 100644 --- a/src/wuttjamaican/app.py +++ b/src/wuttjamaican/app.py @@ -929,8 +929,8 @@ class AppHandler: # registered via entry points registered = [] - for handler in load_entry_points(f'{self.appname}.batch.{key}').values(): - spec = handler.get_spec() + for Handler in load_entry_points(f'{self.appname}.batch.{key}').values(): + spec = Handler.get_spec() if spec not in handlers: registered.append(spec) if registered: @@ -1045,7 +1045,7 @@ class AppHandler: self.get_email_handler().send_email(*args, **kwargs) -class AppProvider: # pylint: disable=too-few-public-methods +class AppProvider: """ Base class for :term:`app providers`. @@ -1123,7 +1123,6 @@ class GenericHandler: def __init__(self, config): self.config = config self.app = self.config.get_app() - self.modules = {} @property def appname(self): @@ -1140,35 +1139,3 @@ class GenericHandler: Returns the class :term:`spec` string for the handler. """ return f'{cls.__module__}:{cls.__name__}' - - def get_provider_modules(self, module_type): - """ - Returns a list of all available modules of the given type. - - Not all handlers would need such a thing, but notable ones - which do are the :term:`email handler` and :term:`report - handler`. Both can obtain classes (emails or reports) from - arbitrary modules, and this method is used to locate them. - - This will discover all modules exposed by the app - :term:`providers `, which expose an attribute with - name like ``f"{module_type}_modules"``. - - :param module_type: Unique name referring to a particular - "type" of modules to locate, e.g. ``'email'``. - - :returns: List of module objects. - """ - if module_type not in self.modules: - self.modules[module_type] = [] - for provider in self.app.providers.values(): - name = f'{module_type}_modules' - if hasattr(provider, name): - modules = getattr(provider, name) - if modules: - if isinstance(modules, str): - modules = [modules] - for modpath in modules: - module = importlib.import_module(modpath) - self.modules[module_type].append(module) - return self.modules[module_type] diff --git a/src/wuttjamaican/batch.py b/src/wuttjamaican/batch.py index d87d70e..653df8b 100644 --- a/src/wuttjamaican/batch.py +++ b/src/wuttjamaican/batch.py @@ -348,7 +348,7 @@ class BatchHandler(GenericHandler): * :attr:`~wuttjamaican.db.model.batch.BatchMixin.status_text` """ - def why_not_execute(self, batch, user=None, **kwargs): # pylint: disable=unused-argument + def why_not_execute(self, batch, user=None, **kwargs): """ Returns text indicating the reason (if any) that a given batch should *not* be executed. @@ -385,7 +385,6 @@ class BatchHandler(GenericHandler): raise a ``RuntimeError`` if text was returned. This is done out of safety, to avoid relying on the user interface. """ - return None def describe_execution(self, batch, user=None, **kwargs): """ @@ -468,16 +467,16 @@ class BatchHandler(GenericHandler): if batch.executed: raise ValueError(f"batch has already been executed: {batch}") - reason = self.why_not_execute(batch, user=user, **kwargs) # pylint: disable=assignment-from-none + reason = self.why_not_execute(batch, user=user, **kwargs) if reason: raise RuntimeError(f"batch execution not allowed: {reason}") - result = self.execute(batch, user=user, progress=progress, **kwargs) # pylint: disable=assignment-from-none + result = self.execute(batch, user=user, progress=progress, **kwargs) batch.executed = datetime.datetime.now() batch.executed_by = user return result - def execute(self, batch, user=None, progress=None, **kwargs): # pylint: disable=unused-argument + def execute(self, batch, user=None, progress=None, **kwargs): """ Execute the given batch. @@ -503,7 +502,6 @@ class BatchHandler(GenericHandler): whatever it likes, in which case that will be also returned to the caller from :meth:`do_execute()`. """ - return None def do_delete(self, batch, user, dry_run=False, progress=None, **kwargs): # pylint: disable=unused-argument """ diff --git a/src/wuttjamaican/cli/problems.py b/src/wuttjamaican/cli/problems.py index 0638fd1..4135065 100644 --- a/src/wuttjamaican/cli/problems.py +++ b/src/wuttjamaican/cli/problems.py @@ -44,7 +44,7 @@ def problems( help="System for which to perform checks; can be specified more " "than once. If not specified, all systems are assumed.")] = None, - problems: Annotated[ # pylint: disable=redefined-outer-name + problems: Annotated[ List[str], typer.Option('--problem', '-p', help="Identify a particular problem check; can be specified " @@ -53,8 +53,7 @@ def problems( list_checks: Annotated[ bool, typer.Option('--list', '-l', - help="List available problem checks; optionally filtered " - "per --system and --problem")] = False, + help="List available problem checks; optionally filtered per --system and --problem")] = False, ): """ Find and report on problems with the data or system. diff --git a/src/wuttjamaican/db/handler.py b/src/wuttjamaican/db/handler.py index 5d8d8af..849f954 100644 --- a/src/wuttjamaican/db/handler.py +++ b/src/wuttjamaican/db/handler.py @@ -2,7 +2,7 @@ ################################################################################ # # WuttJamaican -- Base package for Wutta Framework -# Copyright © 2024-2025 Lance Edgar +# Copyright © 2024 Lance Edgar # # This file is part of Wutta Framework. # @@ -34,7 +34,7 @@ class DatabaseHandler(GenericHandler): Base class and default implementation for the :term:`db handler`. """ - def get_dialect(self, bind): # pylint: disable=empty-docstring + def get_dialect(self, bind): """ """ return bind.url.get_dialect().name diff --git a/src/wuttjamaican/db/model/auth.py b/src/wuttjamaican/db/model/auth.py index 8ff9fc5..b776d04 100644 --- a/src/wuttjamaican/db/model/auth.py +++ b/src/wuttjamaican/db/model/auth.py @@ -45,11 +45,10 @@ import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.ext.associationproxy import association_proxy -from wuttjamaican.db.util import uuid_column, uuid_fk_column -from wuttjamaican.db.model.base import Base +from . import Base, uuid_column, uuid_fk_column -class Role(Base): # pylint: disable=too-few-public-methods +class Role(Base): """ Represents an authentication role within the system; used for permission management. @@ -121,7 +120,7 @@ class Role(Base): # pylint: disable=too-few-public-methods return self.name or "" -class Permission(Base): # pylint: disable=too-few-public-methods +class Permission(Base): """ Represents a permission granted to a role. """ @@ -146,7 +145,7 @@ class Permission(Base): # pylint: disable=too-few-public-methods return self.permission or "" -class User(Base): # pylint: disable=too-few-public-methods +class User(Base): """ Represents a user of the system. @@ -232,7 +231,7 @@ class User(Base): # pylint: disable=too-few-public-methods return self.username or "" -class UserRole(Base): # pylint: disable=too-few-public-methods +class UserRole(Base): """ Represents the association between a user and a role; i.e. the user "belongs" or "is assigned" to the role. @@ -261,7 +260,7 @@ class UserRole(Base): # pylint: disable=too-few-public-methods """) -class UserAPIToken(Base): # pylint: disable=too-few-public-methods +class UserAPIToken(Base): """ User authentication token for use with HTTP API """ @@ -286,13 +285,9 @@ class UserAPIToken(Base): # pylint: disable=too-few-public-methods Raw token string, to be used by API clients. """) - created = sa.Column( - sa.DateTime(timezone=True), - nullable=False, - default=datetime.datetime.now, - doc=""" - Date/time when the token was created. - """) + created = sa.Column(sa.DateTime(timezone=True), nullable=False, default=datetime.datetime.now, doc=""" + Date/time when the token was created. + """) def __str__(self): return self.description or "" diff --git a/src/wuttjamaican/db/model/base.py b/src/wuttjamaican/db/model/base.py index b2858ff..9dc49ae 100644 --- a/src/wuttjamaican/db/model/base.py +++ b/src/wuttjamaican/db/model/base.py @@ -39,7 +39,7 @@ from sqlalchemy.ext.associationproxy import association_proxy from wuttjamaican.db.util import naming_convention, ModelBase, uuid_column -class WuttaModelBase(ModelBase): # pylint: disable=too-few-public-methods +class WuttaModelBase(ModelBase): """ Base class for data models, from which :class:`Base` inherits. @@ -123,7 +123,7 @@ metadata = sa.MetaData(naming_convention=naming_convention) Base = orm.declarative_base(metadata=metadata, cls=WuttaModelBase) -class Setting(Base): # pylint: disable=too-few-public-methods +class Setting(Base): """ Represents a :term:`config setting`. """ diff --git a/src/wuttjamaican/db/model/batch.py b/src/wuttjamaican/db/model/batch.py index 0a24c8d..e253e84 100644 --- a/src/wuttjamaican/db/model/batch.py +++ b/src/wuttjamaican/db/model/batch.py @@ -31,8 +31,7 @@ from sqlalchemy import orm from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.orderinglist import ordering_list -from wuttjamaican.db.model.base import uuid_column -from wuttjamaican.db.model.auth import User +from wuttjamaican.db.model import uuid_column, User from wuttjamaican.db.util import UUID @@ -186,7 +185,7 @@ class BatchMixin: """ @declared_attr - def __table_args__(cls): # pylint: disable=no-self-argument + def __table_args__(cls): return cls.__default_table_args__() @classmethod @@ -201,8 +200,7 @@ class BatchMixin: ) @declared_attr - def batch_type(cls): # pylint: disable=empty-docstring,no-self-argument - """ """ + def batch_type(cls): return cls.__tablename__ uuid = uuid_column() @@ -228,8 +226,7 @@ class BatchMixin: created_by_uuid = sa.Column(UUID(), nullable=False) @declared_attr - def created_by(cls): # pylint: disable=empty-docstring,no-self-argument - """ """ + def created_by(cls): return orm.relationship( User, primaryjoin=lambda: User.uuid == cls.created_by_uuid, @@ -241,8 +238,7 @@ class BatchMixin: executed_by_uuid = sa.Column(UUID(), nullable=True) @declared_attr - def executed_by(cls): # pylint: disable=empty-docstring,no-self-argument - """ """ + def executed_by(cls): return orm.relationship( User, primaryjoin=lambda: User.uuid == cls.executed_by_uuid, @@ -270,7 +266,7 @@ class BatchMixin: return None -class BatchRowMixin: # pylint: disable=too-few-public-methods +class BatchRowMixin: """ Mixin base class for :term:`data models ` which represent a :term:`batch row`. @@ -381,7 +377,7 @@ class BatchRowMixin: # pylint: disable=too-few-public-methods uuid = uuid_column() @declared_attr - def __table_args__(cls): # pylint: disable=no-self-argument + def __table_args__(cls): return cls.__default_table_args__() @classmethod @@ -398,8 +394,7 @@ class BatchRowMixin: # pylint: disable=too-few-public-methods batch_uuid = sa.Column(UUID(), nullable=False) @declared_attr - def batch(cls): # pylint: disable=empty-docstring,no-self-argument - """ """ + def batch(cls): batch_class = cls.__batch_class__ row_class = cls batch_class.__row_class__ = row_class diff --git a/src/wuttjamaican/db/model/upgrades.py b/src/wuttjamaican/db/model/upgrades.py index 010e26e..8e94abd 100644 --- a/src/wuttjamaican/db/model/upgrades.py +++ b/src/wuttjamaican/db/model/upgrades.py @@ -29,13 +29,13 @@ import datetime import sqlalchemy as sa from sqlalchemy import orm +from . import Base, uuid_column, uuid_fk_column from wuttjamaican.enum import UpgradeStatus -from wuttjamaican.db.util import UUID, uuid_column, uuid_fk_column +from wuttjamaican.db.util import UUID from wuttjamaican.util import make_true_uuid -from wuttjamaican.db.model.base import Base -class Upgrade(Base): # pylint: disable=too-few-public-methods +class Upgrade(Base): """ Represents an app upgrade. """ diff --git a/src/wuttjamaican/db/sess.py b/src/wuttjamaican/db/sess.py index 9033832..7fd7a62 100644 --- a/src/wuttjamaican/db/sess.py +++ b/src/wuttjamaican/db/sess.py @@ -2,7 +2,7 @@ ################################################################################ # # WuttJamaican -- Base package for Wutta Framework -# Copyright © 2023-2025 Lance Edgar +# Copyright © 2023 Lance Edgar # # This file is part of Wutta Framework. # @@ -38,7 +38,7 @@ from sqlalchemy import orm Session = orm.sessionmaker() -class short_session: # pylint: disable=invalid-name +class short_session: """ Context manager for a short-lived database session. diff --git a/src/wuttjamaican/db/util.py b/src/wuttjamaican/db/util.py index 24e508f..70be40a 100644 --- a/src/wuttjamaican/db/util.py +++ b/src/wuttjamaican/db/util.py @@ -26,8 +26,8 @@ Database Utilities import uuid as _uuid from importlib.metadata import version - from packaging.version import Version + import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.dialects.postgresql import UUID as PGUUID @@ -51,7 +51,7 @@ if Version(version('SQLAlchemy')) < Version('2'): # pragma: no cover SA2 = False -class ModelBase: # pylint: disable=empty-docstring +class ModelBase: """ """ def __iter__(self): @@ -69,7 +69,7 @@ class ModelBase: # pylint: disable=empty-docstring raise KeyError(f"model instance has no attr with key: {key}") -class UUID(sa.types.TypeDecorator): # pylint: disable=abstract-method,too-many-ancestors +class UUID(sa.types.TypeDecorator): """ Platform-independent UUID type. @@ -82,35 +82,36 @@ class UUID(sa.types.TypeDecorator): # pylint: disable=abstract-method,too-many-a """ impl = sa.CHAR cache_ok = True - """ """ # nb. suppress sphinx autodoc for cache_ok + """ """ # nb. suppress sphinx autodoc for cache_ok - def load_dialect_impl(self, dialect): # pylint: disable=empty-docstring + def load_dialect_impl(self, dialect): """ """ if dialect.name == "postgresql": return dialect.type_descriptor(PGUUID()) - return dialect.type_descriptor(sa.CHAR(32)) + else: + return dialect.type_descriptor(sa.CHAR(32)) - def process_bind_param(self, value, dialect): # pylint: disable=empty-docstring + def process_bind_param(self, value, dialect): """ """ if value is None: return value - - if dialect.name == "postgresql": + elif dialect.name == "postgresql": return str(value) + else: + if not isinstance(value, _uuid.UUID): + return "%.32x" % _uuid.UUID(value).int + else: + # hexstring + return "%.32x" % value.int - if not isinstance(value, _uuid.UUID): - value = _uuid.UUID(value) - - # hexstring - return f"{value.int:032x}" - - def process_result_value(self, value, dialect): # pylint: disable=unused-argument,empty-docstring + def process_result_value(self, value, dialect): # pylint: disable=unused-argument """ """ if value is None: return value - if not isinstance(value, _uuid.UUID): - value = _uuid.UUID(value) - return value + else: + if not isinstance(value, _uuid.UUID): + value = _uuid.UUID(value) + return value def uuid_column(*args, **kwargs): @@ -149,8 +150,8 @@ def make_topo_sortkey(model): containing model classes. """ metadata = model.Base.metadata - tables = {table.name: i - for i, table in enumerate(metadata.sorted_tables, 1)} + tables = dict([(table.name, i) + for i, table in enumerate(metadata.sorted_tables, 1)]) def sortkey(name): cls = getattr(model, name) diff --git a/src/wuttjamaican/email.py b/src/wuttjamaican/email.py index 77e702b..14c5c87 100644 --- a/src/wuttjamaican/email.py +++ b/src/wuttjamaican/email.py @@ -24,6 +24,7 @@ Email Handler """ +import importlib import logging import smtplib from email.mime.multipart import MIMEMultipart @@ -40,7 +41,7 @@ from wuttjamaican.util import resource_path log = logging.getLogger(__name__) -class EmailSetting: # pylint: disable=too-few-public-methods +class EmailSetting: """ Base class for all :term:`email settings `. @@ -187,15 +188,15 @@ class Message: self.key = key self.sender = sender self.subject = subject - self.to = self.get_recips(to) - self.cc = self.get_recips(cc) - self.bcc = self.get_recips(bcc) + self.set_recips('to', to) + self.set_recips('cc', cc) + self.set_recips('bcc', bcc) self.replyto = replyto self.txt_body = txt_body self.html_body = html_body self.attachments = attachments or [] - def get_recips(self, value): # pylint: disable=empty-docstring + def set_recips(self, name, value): """ """ if value: if isinstance(value, str): @@ -204,7 +205,7 @@ class Message: raise ValueError("must specify a string, tuple or list value") else: value = [] - return list(value) + setattr(self, name, list(value)) def as_string(self): """ @@ -304,12 +305,20 @@ class EmailHandler(GenericHandler): This will discover all email modules exposed by the :term:`app`, and/or its :term:`providers `. - - Calls - :meth:`~wuttjamaican.app.GenericHandler.get_provider_modules()` - under the hood, for ``email`` module type. """ - return self.get_provider_modules('email') + if not hasattr(self, '_email_modules'): + self._email_modules = [] + for provider in self.app.providers.values(): + if hasattr(provider, 'email_modules'): + modules = provider.email_modules + if modules: + if isinstance(modules, str): + modules = [modules] + for module in modules: + module = importlib.import_module(module) + self._email_modules.append(module) + + return self._email_modules def get_email_settings(self): """ @@ -540,7 +549,7 @@ class EmailHandler(GenericHandler): """ return self.get_auto_recips(key, 'bcc') - def get_auto_recips(self, key, typ): # pylint: disable=empty-docstring + def get_auto_recips(self, key, typ): """ """ typ = typ.lower() if typ not in ('to', 'cc', 'bcc'): @@ -580,15 +589,16 @@ class EmailHandler(GenericHandler): return template.render(**context) return None - def get_auto_body_template(self, key, mode): # pylint: disable=empty-docstring + def get_auto_body_template(self, key, mode): """ """ mode = mode.lower() + if mode not in ('txt', 'html'): + raise ValueError("requested mode not supported") + if mode == 'txt': templates = self.txt_templates elif mode == 'html': templates = self.html_templates - else: - raise ValueError("requested mode not supported") try: return templates.get_template(f'{key}.{mode}.mako') diff --git a/src/wuttjamaican/install.py b/src/wuttjamaican/install.py index 61be2d3..28035f1 100644 --- a/src/wuttjamaican/install.py +++ b/src/wuttjamaican/install.py @@ -141,10 +141,9 @@ class InstallHandler(GenericHandler): This is normally called by :meth:`run()`. """ - self.rprint(f"\n\t[blue]Welcome to {self.app.get_title()}![/blue]") + self.rprint("\n\t[blue]Welcome to {}![/blue]".format(self.app.get_title())) self.rprint("\n\tThis tool will install and configure the app.") - self.rprint("\n\t[italic]NB. You should already have created " - "the database in PostgreSQL or MySQL.[/italic]") + self.rprint("\n\t[italic]NB. You should already have created the database in PostgreSQL or MySQL.[/italic]") # shall we continue? if not self.prompt_bool("continue?", True): @@ -222,15 +221,14 @@ class InstallHandler(GenericHandler): error = self.test_db_connection(dbinfo['dburl']) if error: self.rprint("[bold red]cannot connect![/bold red] ..error was:") - self.rprint(f"\n{error}") + self.rprint("\n{}".format(error)) self.rprint("\n\t[bold yellow]aborting mission[/bold yellow]\n") sys.exit(1) self.rprint("[bold green]good[/bold green]") return dbinfo - def make_db_url(self, dbtype, dbhost, dbport, dbname, dbuser, dbpass): # pylint: disable=empty-docstring - """ """ + def make_db_url(self, dbtype, dbhost, dbport, dbname, dbuser, dbpass): from sqlalchemy.engine import URL if dbtype == 'mysql': @@ -245,8 +243,7 @@ class InstallHandler(GenericHandler): port=dbport, database=dbname) - def test_db_connection(self, url): # pylint: disable=empty-docstring - """ """ + def test_db_connection(self, url): import sqlalchemy as sa engine = sa.create_engine(url) @@ -255,7 +252,7 @@ class InstallHandler(GenericHandler): # just need to test interaction and this is a neutral way try: sa.inspect(engine).has_table('whatever') - except Exception as error: # pylint: disable=broad-exception-caught + except Exception as error: return str(error) return None @@ -449,8 +446,8 @@ class InstallHandler(GenericHandler): 'upgrade', 'heads'] subprocess.check_call(cmd) - self.rprint("\n\tdb schema installed to: " - f"[bold green]{obfuscate_url_pw(db_url)}[/bold green]") + self.rprint("\n\tdb schema installed to: [bold green]{}[/bold green]".format( + obfuscate_url_pw(db_url))) return True def show_goodbye(self): @@ -472,8 +469,7 @@ class InstallHandler(GenericHandler): # console utility functions ############################## - def require_prompt_toolkit(self, answer=None): # pylint: disable=empty-docstring - """ """ + def require_prompt_toolkit(self, answer=None): try: import prompt_toolkit # pylint: disable=unused-import except ImportError: @@ -495,8 +491,7 @@ class InstallHandler(GenericHandler): """ rich.print(*args, **kwargs) - def get_prompt_style(self): # pylint: disable=empty-docstring - """ """ + def get_prompt_style(self): from prompt_toolkit.styles import Style # message formatting styles @@ -545,9 +540,9 @@ class InstallHandler(GenericHandler): ] if default is not None: if is_bool: - message.append(('', f' [{"Y" if default else "N"}]: ')) + message.append(('', ' [{}]: '.format('Y' if default else 'N'))) else: - message.append(('', f' [{default}]: ')) + message.append(('', ' [{}]: '.format(default))) else: message.append(('', ': ')) @@ -563,9 +558,9 @@ class InstallHandler(GenericHandler): if is_bool: if text == '': return default - if text.upper() == 'Y': + elif text.upper() == 'Y': return True - if text.upper() == 'N': + elif text.upper() == 'N': return False self.rprint("\n\t[bold yellow]ambiguous, please try again[/bold yellow]\n") return self.prompt_generic(info, default, is_bool=True) diff --git a/src/wuttjamaican/people.py b/src/wuttjamaican/people.py index 9513b1b..3f51240 100644 --- a/src/wuttjamaican/people.py +++ b/src/wuttjamaican/people.py @@ -84,7 +84,7 @@ class PeopleHandler(GenericHandler): person = obj return person - if isinstance(obj, model.User): + elif isinstance(obj, model.User): user = obj if user.person: return user.person diff --git a/src/wuttjamaican/problems.py b/src/wuttjamaican/problems.py index 3394c23..b401ed9 100644 --- a/src/wuttjamaican/problems.py +++ b/src/wuttjamaican/problems.py @@ -200,7 +200,7 @@ class ProblemHandler(GenericHandler): :returns: List of system keys. """ checks = self.get_all_problem_checks() - return sorted({check.system_key for check in checks}) + return sorted(set([check.system_key for check in checks])) def get_system_title(self, system_key): """ diff --git a/src/wuttjamaican/progress.py b/src/wuttjamaican/progress.py index 00d718a..712675c 100644 --- a/src/wuttjamaican/progress.py +++ b/src/wuttjamaican/progress.py @@ -2,7 +2,7 @@ ################################################################################ # # WuttJamaican -- Base package for Wutta Framework -# Copyright © 2023-2025 Lance Edgar +# Copyright © 2023-2024 Lance Edgar # # This file is part of Wutta Framework. # @@ -97,17 +97,17 @@ class ConsoleProgress(ProgressBase): def __init__(self, *args, **kwargs): super().__init__(*args) - + self.stderr = kwargs.get('stderr', sys.stderr) self.stderr.write(f"\n{self.message}...\n") - self.bar = Bar(message='', max=self.maximum, width=70, # pylint: disable=disallowed-name + self.bar = Bar(message='', max=self.maximum, width=70, suffix='%(index)d/%(max)d %(percent)d%% ETA %(eta)ds') - def update(self, value): # pylint: disable=empty-docstring + def update(self, value): """ """ self.bar.next() - def finish(self): # pylint: disable=empty-docstring + def finish(self): """ """ self.bar.finish() diff --git a/src/wuttjamaican/reports.py b/src/wuttjamaican/reports.py index 0f84f87..7a55a19 100644 --- a/src/wuttjamaican/reports.py +++ b/src/wuttjamaican/reports.py @@ -24,6 +24,8 @@ Report Utilities """ +import importlib + from wuttjamaican.app import GenericHandler @@ -141,12 +143,20 @@ class ReportHandler(GenericHandler): This will discover all report modules exposed by the :term:`app`, and/or its :term:`providers `. - - Calls - :meth:`~wuttjamaican.app.GenericHandler.get_provider_modules()` - under the hood, for ``report`` module type. """ - return self.get_provider_modules('report') + if not hasattr(self, '_report_modules'): + self._report_modules = [] + for provider in self.app.providers.values(): + if hasattr(provider, 'report_modules'): + modules = provider.report_modules + if modules: + if isinstance(modules, str): + modules = [modules] + for module in modules: + module = importlib.import_module(module) + self._report_modules.append(module) + + return self._report_modules def get_reports(self): """ diff --git a/src/wuttjamaican/testing.py b/src/wuttjamaican/testing.py index dcdfe2e..f566d91 100644 --- a/src/wuttjamaican/testing.py +++ b/src/wuttjamaican/testing.py @@ -53,7 +53,7 @@ class FileTestCase(TestCase): class. """ - def setUp(self): # pylint: disable=empty-docstring + def setUp(self): """ """ self.setup_files() @@ -63,14 +63,14 @@ class FileTestCase(TestCase): """ self.tempdir = tempfile.mkdtemp() - def setup_file_config(self): # pragma: no cover; pylint: disable=empty-docstring + def setup_file_config(self): # pragma: no cover """ """ warnings.warn("FileTestCase.setup_file_config() is deprecated; " "please use setup_files() instead", DeprecationWarning, stacklevel=2) self.setup_files() - def tearDown(self): # pylint: disable=empty-docstring + def tearDown(self): """ """ self.teardown_files() @@ -80,7 +80,7 @@ class FileTestCase(TestCase): """ shutil.rmtree(self.tempdir) - def teardown_file_config(self): # pragma: no cover; pylint: disable=empty-docstring + def teardown_file_config(self): # pragma: no cover """ """ warnings.warn("FileTestCase.teardown_file_config() is deprecated; " "please use teardown_files() instead", @@ -99,7 +99,7 @@ class FileTestCase(TestCase): f.write(content) return path - def mkdir(self, dirname): # pylint: disable=unused-argument,empty-docstring + def mkdir(self, dirname): # pylint: disable=unused-argument """ """ warnings.warn("FileTestCase.mkdir() is deprecated; " "please use FileTestCase.mkdtemp() instead", @@ -143,7 +143,7 @@ class ConfigTestCase(FileTestCase): methods for this class. """ - def setUp(self): # pylint: disable=empty-docstring + def setUp(self): """ """ self.setup_config() @@ -155,7 +155,7 @@ class ConfigTestCase(FileTestCase): self.config = self.make_config() self.app = self.config.get_app() - def tearDown(self): # pylint: disable=empty-docstring + def tearDown(self): """ """ self.teardown_config() @@ -165,7 +165,7 @@ class ConfigTestCase(FileTestCase): """ self.teardown_files() - def make_config(self, **kwargs): # pylint: disable=empty-docstring + def make_config(self, **kwargs): """ """ return WuttaConfig(**kwargs) @@ -203,7 +203,7 @@ class DataTestCase(FileTestCase): teardown methods, as this class handles that automatically. """ - def setUp(self): # pylint: disable=empty-docstring + def setUp(self): """ """ self.setup_db() @@ -222,7 +222,7 @@ class DataTestCase(FileTestCase): model.Base.metadata.create_all(bind=self.config.appdb_engine) self.session = self.app.make_session() - def tearDown(self): # pylint: disable=empty-docstring + def tearDown(self): """ """ self.teardown_db() @@ -232,6 +232,6 @@ class DataTestCase(FileTestCase): """ self.teardown_files() - def make_config(self, **kwargs): # pylint: disable=empty-docstring + def make_config(self, **kwargs): """ """ return WuttaConfig(**kwargs) diff --git a/src/wuttjamaican/util.py b/src/wuttjamaican/util.py index 6897fe1..843a87c 100644 --- a/src/wuttjamaican/util.py +++ b/src/wuttjamaican/util.py @@ -115,7 +115,7 @@ def load_entry_points(group, ignore_errors=False): for entry_point in eps: try: ep = entry_point.load() - except Exception: # pylint: disable=broad-exception-caught + except: if not ignore_errors: raise log.warning("failed to load entry point: %s", entry_point, diff --git a/tests/test_app.py b/tests/test_app.py index e75b527..e05334d 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -761,9 +761,6 @@ class TestGenericHandler(ConfigTestCase): kw.setdefault('appname', 'wuttatest') return super().make_config(**kw) - def make_handler(self, **kwargs): - return mod.GenericHandler(self.config, **kwargs) - def test_constructor(self): handler = mod.GenericHandler(self.config) self.assertIs(handler.config, self.config) @@ -772,30 +769,3 @@ class TestGenericHandler(ConfigTestCase): def test_get_spec(self): self.assertEqual(mod.GenericHandler.get_spec(), 'wuttjamaican.app:GenericHandler') - - def test_get_provider_modules(self): - - # no providers, no email modules - with patch.object(self.app, 'providers', new={}): - handler = self.make_handler() - self.assertEqual(handler.get_provider_modules('email'), []) - - # provider may specify modules as list - providers = { - 'wuttatest': MagicMock(email_modules=['wuttjamaican.app']), - } - with patch.object(self.app, 'providers', new=providers): - handler = self.make_handler() - modules = handler.get_provider_modules('email') - self.assertEqual(len(modules), 1) - self.assertIs(modules[0], mod) - - # provider may specify modules as string - providers = { - 'wuttatest': MagicMock(email_modules='wuttjamaican.app'), - } - with patch.object(self.app, 'providers', new=providers): - handler = self.make_handler() - modules = handler.get_provider_modules('email') - self.assertEqual(len(modules), 1) - self.assertIs(modules[0], mod) diff --git a/tests/test_email.py b/tests/test_email.py index 6e1a72d..8cf1623 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -30,27 +30,28 @@ class TestMessage(FileTestCase): def make_message(self, **kwargs): return mod.Message(**kwargs) - def test_get_recips(self): + def test_set_recips(self): msg = self.make_message() + self.assertEqual(msg.to, []) # set as list - recips = msg.get_recips(['sally@example.com']) - self.assertEqual(recips, ['sally@example.com']) + msg.set_recips('to', ['sally@example.com']) + self.assertEqual(msg.to, ['sally@example.com']) # set as tuple - recips = msg.get_recips(('barney@example.com',)) - self.assertEqual(recips, ['barney@example.com']) + msg.set_recips('to', ('barney@example.com',)) + self.assertEqual(msg.to, ['barney@example.com']) # set as string - recips = msg.get_recips('wilma@example.com') - self.assertEqual(recips, ['wilma@example.com']) + msg.set_recips('to', 'wilma@example.com') + self.assertEqual(msg.to, ['wilma@example.com']) # set as null - recips = msg.get_recips(None) - self.assertEqual(recips, []) + msg.set_recips('to', None) + self.assertEqual(msg.to, []) # otherwise error - self.assertRaises(ValueError, msg.get_recips, {'foo': 'foo@example.com'}) + self.assertRaises(ValueError, msg.set_recips, 'to', {'foo': 'foo@example.com'}) def test_as_string(self): diff --git a/tox.ini b/tox.ini index 4d28869..2d8728a 100644 --- a/tox.ini +++ b/tox.ini @@ -12,9 +12,8 @@ extras = tests [testenv:pylint] basepython = python3.11 -extras = db +extras = deps = pylint - prompt_toolkit commands = pylint wuttjamaican [testenv:coverage]