3
0
Fork 0

Compare commits

...

26 commits

Author SHA1 Message Date
f34678b305 fix: more cleanup for pylint
for some reason these weren't caught with local run but found some
more issues running under tox
2025-08-30 20:44:39 -05:00
faa12005fb infra: explicitly disable checks for pylint
finally tackled enough we can assume checks *should* run except for
those we opt out of.  and those can be cleaned up too hopefully but
i'm out of gas for the moment
2025-08-30 20:36:27 -05:00
9075159d0d fix: fix 'abstract-method' for pylint 2025-08-30 20:34:37 -05:00
79bab4f9e1 fix: fix 'no-member' for pylint 2025-08-30 20:32:56 -05:00
87af670df6 fix: fix 'redefined-outer-name' for pylint 2025-08-30 20:26:55 -05:00
a47dfbad01 fix: fix 'possibly-used-before-assignment' for pylint 2025-08-30 20:25:05 -05:00
da149e932d fix: fix 'no-self-argument' for pylint 2025-08-30 20:23:08 -05:00
f01f5911da fix: fix 'missing-module-docstring' for pylint 2025-08-30 20:20:33 -05:00
967c7b5948 fix: fix 'missing-function-docstring' for pylint 2025-08-30 20:19:26 -05:00
baa291f289 fix: fix 'line-too-long' for pylint 2025-08-30 20:15:03 -05:00
f86aeff788 fix: fix 'duplicate-code' for pylint 2025-08-30 20:11:10 -05:00
da395e1880 fix: fix 'consider-using-dict-comprehension' for pylint 2025-08-30 19:45:15 -05:00
7a0860363c fix: fix 'consider-using-set-comprehension' for pylint 2025-08-30 19:43:04 -05:00
ab01f552e8 fix: fix 'cyclic-import' for pylint 2025-08-30 19:43:01 -05:00
2969f3b7a5 fix: fix 'consider-using-f-string' for pylint 2025-08-30 19:29:52 -05:00
1f4f613b62 fix: fix 'wrong-import-order' for pylint 2025-08-30 19:12:54 -05:00
e845c66d86 fix: fix 'no-else-return' for pylint 2025-08-30 19:11:01 -05:00
6ac1cacdb3 fix: fix 'assignment-from-none' for pylint 2025-08-30 19:07:41 -05:00
3a263d272c fix: fix 'assignment-from-no-return' for pylint 2025-08-30 19:05:40 -05:00
99e6b98627 fix: fix 'empty-docstring' for pylint 2025-08-30 19:03:22 -05:00
3010c5f435 fix: fix 'disallowed-name' for pylint 2025-08-30 18:57:27 -05:00
74c3db6e3a fix: fix 'trailing-whitespace' for pylint 2025-08-30 18:56:04 -05:00
1a0fee3582 fix: fix 'broad-exception-caught' for pylint 2025-08-30 18:54:30 -05:00
3deab0b747 fix: fix 'bare-except' for pylint 2025-08-30 18:52:52 -05:00
dfe2b94f4f fix: fix 'too-few-public-methods' for pylint 2025-08-30 18:50:56 -05:00
3137f0a3da fix: fix 'invalid-name' for pylint 2025-08-30 18:46:06 -05:00
24 changed files with 217 additions and 150 deletions

View file

@ -1,11 +1,14 @@
# -*- mode: conf; -*-
[MESSAGES CONTROL]
disable=all
enable=anomalous-backslash-in-string,
dangerous-default-value,
inconsistent-return-statements,
redefined-argument-from-local,
unspecified-encoding,
unused-argument,
unused-import,
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,

View file

@ -30,6 +30,7 @@ dependencies = [
'importlib-metadata; python_version < "3.10"',
"importlib_resources ; python_version < '3.9'",
"Mako",
"packaging",
"progress",
"python-configuration",
"typer",

View file

@ -1,4 +1,7 @@
# -*- coding: utf-8; -*-
"""
Package Version
"""
from importlib.metadata import version

View file

@ -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:
class AppProvider: # pylint: disable=too-few-public-methods
"""
Base class for :term:`app providers<app provider>`.
@ -1123,6 +1123,7 @@ class GenericHandler:
def __init__(self, config):
self.config = config
self.app = self.config.get_app()
self.modules = {}
@property
def appname(self):
@ -1139,3 +1140,35 @@ 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 <provider>`, 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]

View file

@ -348,7 +348,7 @@ class BatchHandler(GenericHandler):
* :attr:`~wuttjamaican.db.model.batch.BatchMixin.status_text`
"""
def why_not_execute(self, batch, user=None, **kwargs):
def why_not_execute(self, batch, user=None, **kwargs): # pylint: disable=unused-argument
"""
Returns text indicating the reason (if any) that a given batch
should *not* be executed.
@ -385,6 +385,7 @@ 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):
"""
@ -467,16 +468,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)
reason = self.why_not_execute(batch, user=user, **kwargs) # pylint: disable=assignment-from-none
if reason:
raise RuntimeError(f"batch execution not allowed: {reason}")
result = self.execute(batch, user=user, progress=progress, **kwargs)
result = self.execute(batch, user=user, progress=progress, **kwargs) # pylint: disable=assignment-from-none
batch.executed = datetime.datetime.now()
batch.executed_by = user
return result
def execute(self, batch, user=None, progress=None, **kwargs):
def execute(self, batch, user=None, progress=None, **kwargs): # pylint: disable=unused-argument
"""
Execute the given batch.
@ -502,6 +503,7 @@ 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
"""

View file

@ -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[
problems: Annotated[ # pylint: disable=redefined-outer-name
List[str],
typer.Option('--problem', '-p',
help="Identify a particular problem check; can be specified "
@ -53,7 +53,8 @@ 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.

View file

@ -2,7 +2,7 @@
################################################################################
#
# WuttJamaican -- Base package for Wutta Framework
# Copyright © 2024 Lance Edgar
# Copyright © 2024-2025 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):
def get_dialect(self, bind): # pylint: disable=empty-docstring
""" """
return bind.url.get_dialect().name

View file

@ -45,10 +45,11 @@ import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.ext.associationproxy import association_proxy
from . import Base, uuid_column, uuid_fk_column
from wuttjamaican.db.util import uuid_column, uuid_fk_column
from wuttjamaican.db.model.base import Base
class Role(Base):
class Role(Base): # pylint: disable=too-few-public-methods
"""
Represents an authentication role within the system; used for
permission management.
@ -120,7 +121,7 @@ class Role(Base):
return self.name or ""
class Permission(Base):
class Permission(Base): # pylint: disable=too-few-public-methods
"""
Represents a permission granted to a role.
"""
@ -145,7 +146,7 @@ class Permission(Base):
return self.permission or ""
class User(Base):
class User(Base): # pylint: disable=too-few-public-methods
"""
Represents a user of the system.
@ -231,7 +232,7 @@ class User(Base):
return self.username or ""
class UserRole(Base):
class UserRole(Base): # pylint: disable=too-few-public-methods
"""
Represents the association between a user and a role; i.e. the
user "belongs" or "is assigned" to the role.
@ -260,7 +261,7 @@ class UserRole(Base):
""")
class UserAPIToken(Base):
class UserAPIToken(Base): # pylint: disable=too-few-public-methods
"""
User authentication token for use with HTTP API
"""
@ -285,9 +286,13 @@ class UserAPIToken(Base):
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 ""

View file

@ -39,7 +39,7 @@ from sqlalchemy.ext.associationproxy import association_proxy
from wuttjamaican.db.util import naming_convention, ModelBase, uuid_column
class WuttaModelBase(ModelBase):
class WuttaModelBase(ModelBase): # pylint: disable=too-few-public-methods
"""
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):
class Setting(Base): # pylint: disable=too-few-public-methods
"""
Represents a :term:`config setting`.
"""

View file

@ -31,7 +31,8 @@ from sqlalchemy import orm
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.orderinglist import ordering_list
from wuttjamaican.db.model import uuid_column, User
from wuttjamaican.db.model.base import uuid_column
from wuttjamaican.db.model.auth import User
from wuttjamaican.db.util import UUID
@ -185,7 +186,7 @@ class BatchMixin:
"""
@declared_attr
def __table_args__(cls):
def __table_args__(cls): # pylint: disable=no-self-argument
return cls.__default_table_args__()
@classmethod
@ -200,7 +201,8 @@ class BatchMixin:
)
@declared_attr
def batch_type(cls):
def batch_type(cls): # pylint: disable=empty-docstring,no-self-argument
""" """
return cls.__tablename__
uuid = uuid_column()
@ -226,7 +228,8 @@ class BatchMixin:
created_by_uuid = sa.Column(UUID(), nullable=False)
@declared_attr
def created_by(cls):
def created_by(cls): # pylint: disable=empty-docstring,no-self-argument
""" """
return orm.relationship(
User,
primaryjoin=lambda: User.uuid == cls.created_by_uuid,
@ -238,7 +241,8 @@ class BatchMixin:
executed_by_uuid = sa.Column(UUID(), nullable=True)
@declared_attr
def executed_by(cls):
def executed_by(cls): # pylint: disable=empty-docstring,no-self-argument
""" """
return orm.relationship(
User,
primaryjoin=lambda: User.uuid == cls.executed_by_uuid,
@ -266,7 +270,7 @@ class BatchMixin:
return None
class BatchRowMixin:
class BatchRowMixin: # pylint: disable=too-few-public-methods
"""
Mixin base class for :term:`data models <data model>` which
represent a :term:`batch row`.
@ -377,7 +381,7 @@ class BatchRowMixin:
uuid = uuid_column()
@declared_attr
def __table_args__(cls):
def __table_args__(cls): # pylint: disable=no-self-argument
return cls.__default_table_args__()
@classmethod
@ -394,7 +398,8 @@ class BatchRowMixin:
batch_uuid = sa.Column(UUID(), nullable=False)
@declared_attr
def batch(cls):
def batch(cls): # pylint: disable=empty-docstring,no-self-argument
""" """
batch_class = cls.__batch_class__
row_class = cls
batch_class.__row_class__ = row_class

View file

@ -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
from wuttjamaican.db.util import UUID, uuid_column, uuid_fk_column
from wuttjamaican.util import make_true_uuid
from wuttjamaican.db.model.base import Base
class Upgrade(Base):
class Upgrade(Base): # pylint: disable=too-few-public-methods
"""
Represents an app upgrade.
"""

View file

@ -2,7 +2,7 @@
################################################################################
#
# WuttJamaican -- Base package for Wutta Framework
# Copyright © 2023 Lance Edgar
# Copyright © 2023-2025 Lance Edgar
#
# This file is part of Wutta Framework.
#
@ -38,7 +38,7 @@ from sqlalchemy import orm
Session = orm.sessionmaker()
class short_session:
class short_session: # pylint: disable=invalid-name
"""
Context manager for a short-lived database session.

View file

@ -26,8 +26,8 @@ Database Utilities
import uuid as _uuid
from importlib.metadata import version
from packaging.version 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:
class ModelBase: # pylint: disable=empty-docstring
""" """
def __iter__(self):
@ -69,7 +69,7 @@ class ModelBase:
raise KeyError(f"model instance has no attr with key: {key}")
class UUID(sa.types.TypeDecorator):
class UUID(sa.types.TypeDecorator): # pylint: disable=abstract-method,too-many-ancestors
"""
Platform-independent UUID type.
@ -82,36 +82,35 @@ class UUID(sa.types.TypeDecorator):
"""
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):
def load_dialect_impl(self, dialect): # pylint: disable=empty-docstring
""" """
if dialect.name == "postgresql":
return dialect.type_descriptor(PGUUID())
else:
return dialect.type_descriptor(sa.CHAR(32))
return dialect.type_descriptor(sa.CHAR(32))
def process_bind_param(self, value, dialect):
def process_bind_param(self, value, dialect): # pylint: disable=empty-docstring
""" """
if value is None:
return value
elif dialect.name == "postgresql":
if 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
def process_result_value(self, value, dialect): # pylint: disable=unused-argument
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
""" """
if value is None:
return value
else:
if not isinstance(value, _uuid.UUID):
value = _uuid.UUID(value)
return value
if not isinstance(value, _uuid.UUID):
value = _uuid.UUID(value)
return value
def uuid_column(*args, **kwargs):
@ -150,8 +149,8 @@ def make_topo_sortkey(model):
containing model classes.
"""
metadata = model.Base.metadata
tables = dict([(table.name, i)
for i, table in enumerate(metadata.sorted_tables, 1)])
tables = {table.name: i
for i, table in enumerate(metadata.sorted_tables, 1)}
def sortkey(name):
cls = getattr(model, name)

View file

@ -24,7 +24,6 @@
Email Handler
"""
import importlib
import logging
import smtplib
from email.mime.multipart import MIMEMultipart
@ -41,7 +40,7 @@ from wuttjamaican.util import resource_path
log = logging.getLogger(__name__)
class EmailSetting:
class EmailSetting: # pylint: disable=too-few-public-methods
"""
Base class for all :term:`email settings <email setting>`.
@ -188,15 +187,15 @@ class Message:
self.key = key
self.sender = sender
self.subject = subject
self.set_recips('to', to)
self.set_recips('cc', cc)
self.set_recips('bcc', bcc)
self.to = self.get_recips(to)
self.cc = self.get_recips(cc)
self.bcc = self.get_recips(bcc)
self.replyto = replyto
self.txt_body = txt_body
self.html_body = html_body
self.attachments = attachments or []
def set_recips(self, name, value):
def get_recips(self, value): # pylint: disable=empty-docstring
""" """
if value:
if isinstance(value, str):
@ -205,7 +204,7 @@ class Message:
raise ValueError("must specify a string, tuple or list value")
else:
value = []
setattr(self, name, list(value))
return list(value)
def as_string(self):
"""
@ -305,20 +304,12 @@ class EmailHandler(GenericHandler):
This will discover all email modules exposed by the
:term:`app`, and/or its :term:`providers <provider>`.
"""
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
Calls
:meth:`~wuttjamaican.app.GenericHandler.get_provider_modules()`
under the hood, for ``email`` module type.
"""
return self.get_provider_modules('email')
def get_email_settings(self):
"""
@ -549,7 +540,7 @@ class EmailHandler(GenericHandler):
"""
return self.get_auto_recips(key, 'bcc')
def get_auto_recips(self, key, typ):
def get_auto_recips(self, key, typ): # pylint: disable=empty-docstring
""" """
typ = typ.lower()
if typ not in ('to', 'cc', 'bcc'):
@ -589,16 +580,15 @@ class EmailHandler(GenericHandler):
return template.render(**context)
return None
def get_auto_body_template(self, key, mode):
def get_auto_body_template(self, key, mode): # pylint: disable=empty-docstring
""" """
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')

View file

@ -141,9 +141,10 @@ class InstallHandler(GenericHandler):
This is normally called by :meth:`run()`.
"""
self.rprint("\n\t[blue]Welcome to {}![/blue]".format(self.app.get_title()))
self.rprint(f"\n\t[blue]Welcome to {self.app.get_title()}![/blue]")
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):
@ -221,14 +222,15 @@ class InstallHandler(GenericHandler):
error = self.test_db_connection(dbinfo['dburl'])
if error:
self.rprint("[bold red]cannot connect![/bold red] ..error was:")
self.rprint("\n{}".format(error))
self.rprint(f"\n{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):
def make_db_url(self, dbtype, dbhost, dbport, dbname, dbuser, dbpass): # pylint: disable=empty-docstring
""" """
from sqlalchemy.engine import URL
if dbtype == 'mysql':
@ -243,7 +245,8 @@ class InstallHandler(GenericHandler):
port=dbport,
database=dbname)
def test_db_connection(self, url):
def test_db_connection(self, url): # pylint: disable=empty-docstring
""" """
import sqlalchemy as sa
engine = sa.create_engine(url)
@ -252,7 +255,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:
except Exception as error: # pylint: disable=broad-exception-caught
return str(error)
return None
@ -446,8 +449,8 @@ class InstallHandler(GenericHandler):
'upgrade', 'heads']
subprocess.check_call(cmd)
self.rprint("\n\tdb schema installed to: [bold green]{}[/bold green]".format(
obfuscate_url_pw(db_url)))
self.rprint("\n\tdb schema installed to: "
f"[bold green]{obfuscate_url_pw(db_url)}[/bold green]")
return True
def show_goodbye(self):
@ -469,7 +472,8 @@ class InstallHandler(GenericHandler):
# console utility functions
##############################
def require_prompt_toolkit(self, answer=None):
def require_prompt_toolkit(self, answer=None): # pylint: disable=empty-docstring
""" """
try:
import prompt_toolkit # pylint: disable=unused-import
except ImportError:
@ -491,7 +495,8 @@ class InstallHandler(GenericHandler):
"""
rich.print(*args, **kwargs)
def get_prompt_style(self):
def get_prompt_style(self): # pylint: disable=empty-docstring
""" """
from prompt_toolkit.styles import Style
# message formatting styles
@ -540,9 +545,9 @@ class InstallHandler(GenericHandler):
]
if default is not None:
if is_bool:
message.append(('', ' [{}]: '.format('Y' if default else 'N')))
message.append(('', f' [{"Y" if default else "N"}]: '))
else:
message.append(('', ' [{}]: '.format(default)))
message.append(('', f' [{default}]: '))
else:
message.append(('', ': '))
@ -558,9 +563,9 @@ class InstallHandler(GenericHandler):
if is_bool:
if text == '':
return default
elif text.upper() == 'Y':
if text.upper() == 'Y':
return True
elif text.upper() == 'N':
if 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)

View file

@ -84,7 +84,7 @@ class PeopleHandler(GenericHandler):
person = obj
return person
elif isinstance(obj, model.User):
if isinstance(obj, model.User):
user = obj
if user.person:
return user.person

View file

@ -200,7 +200,7 @@ class ProblemHandler(GenericHandler):
:returns: List of system keys.
"""
checks = self.get_all_problem_checks()
return sorted(set([check.system_key for check in checks]))
return sorted({check.system_key for check in checks})
def get_system_title(self, system_key):
"""

View file

@ -2,7 +2,7 @@
################################################################################
#
# WuttJamaican -- Base package for Wutta Framework
# Copyright © 2023-2024 Lance Edgar
# Copyright © 2023-2025 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,
self.bar = Bar(message='', max=self.maximum, width=70, # pylint: disable=disallowed-name
suffix='%(index)d/%(max)d %(percent)d%% ETA %(eta)ds')
def update(self, value):
def update(self, value): # pylint: disable=empty-docstring
""" """
self.bar.next()
def finish(self):
def finish(self): # pylint: disable=empty-docstring
""" """
self.bar.finish()

View file

@ -24,8 +24,6 @@
Report Utilities
"""
import importlib
from wuttjamaican.app import GenericHandler
@ -143,20 +141,12 @@ class ReportHandler(GenericHandler):
This will discover all report modules exposed by the
:term:`app`, and/or its :term:`providers <provider>`.
"""
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
Calls
:meth:`~wuttjamaican.app.GenericHandler.get_provider_modules()`
under the hood, for ``report`` module type.
"""
return self.get_provider_modules('report')
def get_reports(self):
"""

View file

@ -53,7 +53,7 @@ class FileTestCase(TestCase):
class.
"""
def setUp(self):
def setUp(self): # pylint: disable=empty-docstring
""" """
self.setup_files()
@ -63,14 +63,14 @@ class FileTestCase(TestCase):
"""
self.tempdir = tempfile.mkdtemp()
def setup_file_config(self): # pragma: no cover
def setup_file_config(self): # pragma: no cover; pylint: disable=empty-docstring
""" """
warnings.warn("FileTestCase.setup_file_config() is deprecated; "
"please use setup_files() instead",
DeprecationWarning, stacklevel=2)
self.setup_files()
def tearDown(self):
def tearDown(self): # pylint: disable=empty-docstring
""" """
self.teardown_files()
@ -80,7 +80,7 @@ class FileTestCase(TestCase):
"""
shutil.rmtree(self.tempdir)
def teardown_file_config(self): # pragma: no cover
def teardown_file_config(self): # pragma: no cover; pylint: disable=empty-docstring
""" """
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
def mkdir(self, dirname): # pylint: disable=unused-argument,empty-docstring
""" """
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):
def setUp(self): # pylint: disable=empty-docstring
""" """
self.setup_config()
@ -155,7 +155,7 @@ class ConfigTestCase(FileTestCase):
self.config = self.make_config()
self.app = self.config.get_app()
def tearDown(self):
def tearDown(self): # pylint: disable=empty-docstring
""" """
self.teardown_config()
@ -165,7 +165,7 @@ class ConfigTestCase(FileTestCase):
"""
self.teardown_files()
def make_config(self, **kwargs):
def make_config(self, **kwargs): # pylint: disable=empty-docstring
""" """
return WuttaConfig(**kwargs)
@ -203,7 +203,7 @@ class DataTestCase(FileTestCase):
teardown methods, as this class handles that automatically.
"""
def setUp(self):
def setUp(self): # pylint: disable=empty-docstring
""" """
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):
def tearDown(self): # pylint: disable=empty-docstring
""" """
self.teardown_db()
@ -232,6 +232,6 @@ class DataTestCase(FileTestCase):
"""
self.teardown_files()
def make_config(self, **kwargs):
def make_config(self, **kwargs): # pylint: disable=empty-docstring
""" """
return WuttaConfig(**kwargs)

View file

@ -115,7 +115,7 @@ def load_entry_points(group, ignore_errors=False):
for entry_point in eps:
try:
ep = entry_point.load()
except:
except Exception: # pylint: disable=broad-exception-caught
if not ignore_errors:
raise
log.warning("failed to load entry point: %s", entry_point,

View file

@ -761,6 +761,9 @@ 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)
@ -769,3 +772,30 @@ 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)

View file

@ -30,28 +30,27 @@ class TestMessage(FileTestCase):
def make_message(self, **kwargs):
return mod.Message(**kwargs)
def test_set_recips(self):
def test_get_recips(self):
msg = self.make_message()
self.assertEqual(msg.to, [])
# set as list
msg.set_recips('to', ['sally@example.com'])
self.assertEqual(msg.to, ['sally@example.com'])
recips = msg.get_recips(['sally@example.com'])
self.assertEqual(recips, ['sally@example.com'])
# set as tuple
msg.set_recips('to', ('barney@example.com',))
self.assertEqual(msg.to, ['barney@example.com'])
recips = msg.get_recips(('barney@example.com',))
self.assertEqual(recips, ['barney@example.com'])
# set as string
msg.set_recips('to', 'wilma@example.com')
self.assertEqual(msg.to, ['wilma@example.com'])
recips = msg.get_recips('wilma@example.com')
self.assertEqual(recips, ['wilma@example.com'])
# set as null
msg.set_recips('to', None)
self.assertEqual(msg.to, [])
recips = msg.get_recips(None)
self.assertEqual(recips, [])
# otherwise error
self.assertRaises(ValueError, msg.set_recips, 'to', {'foo': 'foo@example.com'})
self.assertRaises(ValueError, msg.get_recips, {'foo': 'foo@example.com'})
def test_as_string(self):

View file

@ -12,8 +12,9 @@ extras = tests
[testenv:pylint]
basepython = python3.11
extras =
extras = db
deps = pylint
prompt_toolkit
commands = pylint wuttjamaican
[testenv:coverage]