From a6bb538ce94cb1f64e7408f3a4bd5c577c5ca403 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 30 Aug 2025 21:25:44 -0500 Subject: [PATCH] fix: format all code with black and from now on should not deviate from that... --- docs/conf.py | 53 +- pyproject.toml | 2 +- src/wuttjamaican/_version.py | 2 +- src/wuttjamaican/app.py | 255 ++++---- src/wuttjamaican/auth.py | 111 ++-- src/wuttjamaican/batch.py | 42 +- src/wuttjamaican/cli/__init__.py | 1 + src/wuttjamaican/cli/base.py | 33 +- src/wuttjamaican/cli/make_appdir.py | 19 +- src/wuttjamaican/cli/make_uuid.py | 2 +- src/wuttjamaican/cli/problems.py | 53 +- src/wuttjamaican/conf.py | 227 ++++--- src/wuttjamaican/db/alembic/env.py | 7 +- .../versions/3abcc44f7f91_add_people.py | 36 +- .../6bf900765500_add_user_prevent_edit.py | 9 +- .../versions/d686f7abe3e0_add_users_roles.py | 81 ++- .../versions/ebd75b9feaa7_add_upgrades.py | 65 +- .../efdcb2c75034_add_user_api_token.py | 28 +- .../fc3a3bcaa069_init_with_settings_table.py | 20 +- src/wuttjamaican/db/conf.py | 29 +- src/wuttjamaican/db/handler.py | 11 +- src/wuttjamaican/db/model/auth.py | 187 ++++-- src/wuttjamaican/db/model/base.py | 70 +- src/wuttjamaican/db/model/batch.py | 65 +- src/wuttjamaican/db/model/upgrades.py | 78 ++- src/wuttjamaican/db/sess.py | 2 +- src/wuttjamaican/db/util.py | 41 +- src/wuttjamaican/email.py | 200 +++--- src/wuttjamaican/enum.py | 9 +- src/wuttjamaican/install.py | 243 +++---- src/wuttjamaican/people.py | 12 +- src/wuttjamaican/problems.py | 58 +- src/wuttjamaican/progress.py | 14 +- src/wuttjamaican/reports.py | 15 +- src/wuttjamaican/testing.py | 59 +- src/wuttjamaican/util.py | 30 +- tasks.py | 14 +- tests/cli/test_base.py | 12 +- tests/cli/test_make_appdir.py | 9 +- tests/cli/test_make_uuid.py | 6 +- tests/cli/test_problems.py | 29 +- tests/db/model/test_auth.py | 7 +- tests/db/model/test_base.py | 23 +- tests/db/model/test_batch.py | 26 +- tests/db/test_conf.py | 111 ++-- tests/db/test_handler.py | 20 +- tests/db/test_util.py | 33 +- tests/test_app.py | 407 +++++++----- tests/test_auth.py | 153 ++--- tests/test_batch.py | 68 +- tests/test_conf.py | 613 ++++++++++-------- tests/test_email.py | 465 ++++++------- tests/test_install.py | 370 ++++++----- tests/test_people.py | 13 +- tests/test_problems.py | 115 ++-- tests/test_progress.py | 4 +- tests/test_reports.py | 46 +- tests/test_util.py | 174 ++--- tox.ini | 6 + 59 files changed, 2762 insertions(+), 2131 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9f62105..ad12c1a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,43 +8,46 @@ from importlib.metadata import version as get_version -project = 'WuttJamaican' -copyright = '2023-2024, Lance Edgar' -author = 'Lance Edgar' -release = get_version('WuttJamaican') +project = "WuttJamaican" +copyright = "2023-2024, Lance Edgar" +author = "Lance Edgar" +release = get_version("WuttJamaican") # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinxcontrib.programoutput', - 'sphinx.ext.viewcode', - 'sphinx.ext.todo', - 'enum_tools.autoenum', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinxcontrib.programoutput", + "sphinx.ext.viewcode", + "sphinx.ext.todo", + "enum_tools.autoenum", ] -templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] intersphinx_mapping = { - 'alembic': ('https://alembic.sqlalchemy.org/en/latest/', None), - 'humanize': ('https://humanize.readthedocs.io/en/stable/', None), - 'mako': ('https://docs.makotemplates.org/en/latest/', None), - 'packaging': ('https://packaging.python.org/en/latest/', None), - 'python': ('https://docs.python.org/3/', None), - 'python-configuration': ('https://python-configuration.readthedocs.io/en/latest/', None), - 'rattail': ('https://docs.wuttaproject.org/rattail/', None), - 'rattail-manual': ('https://docs.wuttaproject.org/rattail-manual/', None), - 'rich': ('https://rich.readthedocs.io/en/latest/', None), - 'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None), - 'wutta-continuum': ('https://docs.wuttaproject.org/wutta-continuum/', None), + "alembic": ("https://alembic.sqlalchemy.org/en/latest/", None), + "humanize": ("https://humanize.readthedocs.io/en/stable/", None), + "mako": ("https://docs.makotemplates.org/en/latest/", None), + "packaging": ("https://packaging.python.org/en/latest/", None), + "python": ("https://docs.python.org/3/", None), + "python-configuration": ( + "https://python-configuration.readthedocs.io/en/latest/", + None, + ), + "rattail": ("https://docs.wuttaproject.org/rattail/", None), + "rattail-manual": ("https://docs.wuttaproject.org/rattail-manual/", None), + "rich": ("https://rich.readthedocs.io/en/latest/", None), + "sqlalchemy": ("http://docs.sqlalchemy.org/en/latest/", None), + "wutta-continuum": ("https://docs.wuttaproject.org/wutta-continuum/", None), } # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'furo' -html_static_path = ['_static'] +html_theme = "furo" +html_static_path = ["_static"] diff --git a/pyproject.toml b/pyproject.toml index 8a16b1a..f7a4e53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ dependencies = [ [project.optional-dependencies] db = ["SQLAlchemy", "alembic", "alembic-postgresql-enum", "passlib"] docs = ["Sphinx", "sphinxcontrib-programoutput", "enum-tools[sphinx]", "furo"] -tests = ["pylint", "pytest-cov", "tox"] +tests = ["black", "pylint", "pytest", "pytest-cov", "tox"] [project.scripts] diff --git a/src/wuttjamaican/_version.py b/src/wuttjamaican/_version.py index 6a6bb9c..558c6a9 100644 --- a/src/wuttjamaican/_version.py +++ b/src/wuttjamaican/_version.py @@ -6,4 +6,4 @@ Package Version from importlib.metadata import version -__version__ = version('WuttJamaican') +__version__ = version("WuttJamaican") diff --git a/src/wuttjamaican/app.py b/src/wuttjamaican/app.py index d913605..bac0512 100644 --- a/src/wuttjamaican/app.py +++ b/src/wuttjamaican/app.py @@ -33,15 +33,23 @@ import warnings import humanize -from wuttjamaican.util import (load_entry_points, load_object, - make_title, make_full_name, make_uuid, make_true_uuid, - progress_loop, resource_path, simple_error) +from wuttjamaican.util import ( + load_entry_points, + load_object, + make_title, + make_full_name, + make_uuid, + make_true_uuid, + progress_loop, + resource_path, + simple_error, +) log = logging.getLogger(__name__) -class AppHandler: # pylint: disable=too-many-public-methods +class AppHandler: # pylint: disable=too-many-public-methods """ Base class and default implementation for top-level :term:`app handler`. @@ -84,16 +92,17 @@ class AppHandler: # pylint: disable=too-many-public-methods Dictionary of :class:`AppProvider` instances, as returned by :meth:`get_all_providers()`. """ + default_app_title = "WuttJamaican" - default_model_spec = 'wuttjamaican.db.model' - default_enum_spec = 'wuttjamaican.enum' - default_auth_handler_spec = 'wuttjamaican.auth:AuthHandler' - default_db_handler_spec = 'wuttjamaican.db.handler:DatabaseHandler' - default_email_handler_spec = 'wuttjamaican.email:EmailHandler' - default_install_handler_spec = 'wuttjamaican.install:InstallHandler' - default_people_handler_spec = 'wuttjamaican.people:PeopleHandler' - default_problem_handler_spec = 'wuttjamaican.problems:ProblemHandler' - default_report_handler_spec = 'wuttjamaican.reports:ReportHandler' + default_model_spec = "wuttjamaican.db.model" + default_enum_spec = "wuttjamaican.enum" + default_auth_handler_spec = "wuttjamaican.auth:AuthHandler" + default_db_handler_spec = "wuttjamaican.db.handler:DatabaseHandler" + default_email_handler_spec = "wuttjamaican.email:EmailHandler" + default_install_handler_spec = "wuttjamaican.install:InstallHandler" + default_people_handler_spec = "wuttjamaican.people:PeopleHandler" + default_problem_handler_spec = "wuttjamaican.problems:ProblemHandler" + default_report_handler_spec = "wuttjamaican.reports:ReportHandler" def __init__(self, config): self.config = config @@ -126,13 +135,13 @@ class AppHandler: # pylint: disable=too-many-public-methods providers. """ - if name == 'model': + if name == "model": return self.get_model() - if name == 'enum': + if name == "enum": return self.get_enum() - if name == 'providers': + if name == "providers": self.providers = self.get_all_providers() return self.providers @@ -164,7 +173,7 @@ class AppHandler: # pylint: disable=too-many-public-methods """ # nb. must use 'wutta' and not self.appname prefix here, or # else we can't find all providers with custom appname - providers = load_entry_points('wutta.app.providers') + providers = load_entry_points("wutta.app.providers") for key in list(providers): providers[key] = providers[key](self.config) return providers @@ -178,8 +187,9 @@ class AppHandler: # pylint: disable=too-many-public-methods :returns: Title for the app. """ - return self.config.get(f'{self.appname}.app_title', - default=default or self.default_app_title) + return self.config.get( + f"{self.appname}.app_title", default=default or self.default_app_title + ) def get_node_title(self, default=None): """ @@ -193,7 +203,7 @@ class AppHandler: # pylint: disable=too-many-public-methods :returns: Title for the local app node. """ - title = self.config.get(f'{self.appname}.node_title') + title = self.config.get(f"{self.appname}.node_title") if title: return title return self.get_title(default=default) @@ -217,8 +227,9 @@ class AppHandler: # pylint: disable=too-many-public-methods [wutta] node_type = warehouse """ - return self.config.get(f'{self.appname}.node_type', default=default, - usedb=False) + return self.config.get( + f"{self.appname}.node_type", default=default, usedb=False + ) def get_distribution(self, obj=None): """ @@ -259,20 +270,20 @@ class AppHandler: # pylint: disable=too-many-public-methods app_dist = My-Poser-Dist """ if obj is None: - dist = self.config.get(f'{self.appname}.app_dist') + dist = self.config.get(f"{self.appname}.app_dist") if dist: return dist # TODO: do we need a config setting for app_package ? - #modpath = self.config.get(f'{self.appname}.app_package') + # modpath = self.config.get(f'{self.appname}.app_package') modpath = None if not modpath: modpath = type(obj if obj is not None else self).__module__ - pkgname = modpath.split('.')[0] + pkgname = modpath.split(".")[0] try: from importlib.metadata import packages_distributions - except ImportError: # python < 3.10 + except ImportError: # python < 3.10 from importlib_metadata import packages_distributions pkgmap = packages_distributions() @@ -281,7 +292,7 @@ class AppHandler: # pylint: disable=too-many-public-methods return dist # fall back to configured dist, if obj lookup failed - return self.config.get(f'{self.appname}.app_dist') + return self.config.get(f"{self.appname}.app_dist") def get_version(self, dist=None, obj=None): """ @@ -320,10 +331,12 @@ class AppHandler: # pylint: disable=too-many-public-methods config.setdefault('wutta.model_spec', 'poser.db.model') """ - if 'model' not in self.__dict__: - spec = self.config.get(f'{self.appname}.model_spec', - usedb=False, - default=self.default_model_spec) + if "model" not in self.__dict__: + spec = self.config.get( + f"{self.appname}.model_spec", + usedb=False, + default=self.default_model_spec, + ) self.model = importlib.import_module(spec) return self.model @@ -344,10 +357,10 @@ class AppHandler: # pylint: disable=too-many-public-methods config.setdefault('wutta.enum_spec', 'poser.enum') """ - if 'enum' not in self.__dict__: - spec = self.config.get(f'{self.appname}.enum_spec', - usedb=False, - default=self.default_enum_spec) + if "enum" not in self.__dict__: + spec = self.config.get( + f"{self.appname}.enum_spec", usedb=False, default=self.default_enum_spec + ) self.enum = importlib.import_module(spec) return self.enum @@ -389,17 +402,17 @@ class AppHandler: # pylint: disable=too-many-public-methods app.get_appdir('data') # => /srv/envs/poser/app/data """ - configured_only = kwargs.pop('configured_only', False) - create = kwargs.pop('create', False) + configured_only = kwargs.pop("configured_only", False) + create = kwargs.pop("create", False) # maybe specify default path if not configured_only: - path = os.path.join(sys.prefix, 'app') - kwargs.setdefault('default', path) + path = os.path.join(sys.prefix, "app") + kwargs.setdefault("default", path) # get configured path - kwargs.setdefault('usedb', False) - path = self.config.get(f'{self.appname}.appdir', **kwargs) + kwargs.setdefault("usedb", False) + path = self.config.get(f"{self.appname}.appdir", **kwargs) # add any subpath info if path and args: @@ -436,7 +449,7 @@ class AppHandler: # pylint: disable=too-many-public-methods os.makedirs(appdir) if not subfolders: - subfolders = ['cache', 'data', 'log', 'work'] + subfolders = ["cache", "data", "log", "work"] for name in subfolders: path = os.path.join(appdir, name) @@ -444,10 +457,10 @@ class AppHandler: # pylint: disable=too-many-public-methods os.mkdir(path) def render_mako_template( - self, - template, - context, - output_path=None, + self, + template, + context, + output_path=None, ): """ Convenience method to render a Mako template. @@ -464,7 +477,7 @@ class AppHandler: # pylint: disable=too-many-public-methods """ output = template.render(**context) if output_path: - with open(output_path, 'wt', encoding='utf_8') as f: + with open(output_path, "wt", encoding="utf_8") as f: f.write(output) return output @@ -586,12 +599,12 @@ class AppHandler: # pylint: disable=too-many-public-methods """ from .db import short_session - if 'factory' not in kwargs and 'config' not in kwargs: - kwargs['factory'] = self.make_session + if "factory" not in kwargs and "config" not in kwargs: + kwargs["factory"] = self.make_session return short_session(**kwargs) - def get_setting(self, session, name, **kwargs): # pylint: disable=unused-argument + def get_setting(self, session, name, **kwargs): # pylint: disable=unused-argument """ Get a :term:`config setting` value from the DB. @@ -617,13 +630,8 @@ class AppHandler: # pylint: disable=too-many-public-methods return get_setting(session, name) def save_setting( - self, - session, - name, - value, - force_create=False, - **kwargs - ): # pylint: disable=unused-argument + self, session, name, value, force_create=False, **kwargs + ): # pylint: disable=unused-argument """ Save a :term:`config setting` value to the DB. @@ -664,7 +672,9 @@ class AppHandler: # pylint: disable=too-many-public-methods # set value setting.value = value - def delete_setting(self, session, name, **kwargs): # pylint: disable=unused-argument + def delete_setting( + self, session, name, **kwargs + ): # pylint: disable=unused-argument """ Delete a :term:`config setting` from the DB. @@ -692,7 +702,7 @@ class AppHandler: # pylint: disable=too-many-public-methods :doc:`wutta-continuum:narr/install`. """ for provider in self.providers.values(): - if hasattr(provider, 'continuum_is_enabled'): + if hasattr(provider, "continuum_is_enabled"): return provider.continuum_is_enabled() return False @@ -710,7 +720,7 @@ class AppHandler: # pylint: disable=too-many-public-methods :returns: Display string for the value. """ if value is None: - return '' + return "" return "Yes" if value else "No" @@ -727,7 +737,7 @@ class AppHandler: # pylint: disable=too-many-public-methods :returns: Display string for the value. """ if value is None: - return '' + return "" if value < 0: fmt = f"(${{:0,.{scale}f}})" @@ -736,13 +746,13 @@ class AppHandler: # pylint: disable=too-many-public-methods fmt = f"${{:0,.{scale}f}}" return fmt.format(value) - display_format_date = '%Y-%m-%d' + display_format_date = "%Y-%m-%d" """ Format string to use when displaying :class:`python:datetime.date` objects. See also :meth:`render_date()`. """ - display_format_datetime = '%Y-%m-%d %H:%M%z' + display_format_datetime = "%Y-%m-%d %H:%M%z" """ Format string to use when displaying :class:`python:datetime.datetime` objects. See also @@ -800,9 +810,9 @@ class AppHandler: # pylint: disable=too-many-public-methods """ if value is None: return "" - fmt = f'{{:0.{decimals}f}} %' + fmt = f"{{:0.{decimals}f}} %" if value < 0: - return f'({fmt.format(-value)})' + return f"({fmt.format(-value)})" return fmt.format(value) def render_quantity(self, value, empty_zero=False): @@ -819,13 +829,13 @@ class AppHandler: # pylint: disable=too-many-public-methods :returns: Display string for the quantity. """ if value is None: - return '' + return "" if int(value) == value: value = int(value) if empty_zero and value == 0: - return '' + return "" return str(value) - return str(value).rstrip('0') + return str(value).rstrip("0") def render_time_ago(self, value): """ @@ -852,12 +862,13 @@ class AppHandler: # pylint: disable=too-many-public-methods :rtype: :class:`~wuttjamaican.auth.AuthHandler` """ - if 'auth' not in self.handlers: - spec = self.config.get(f'{self.appname}.auth.handler', - default=self.default_auth_handler_spec) + if "auth" not in self.handlers: + spec = self.config.get( + f"{self.appname}.auth.handler", default=self.default_auth_handler_spec + ) factory = self.load_object(spec) - self.handlers['auth'] = factory(self.config, **kwargs) - return self.handlers['auth'] + self.handlers["auth"] = factory(self.config, **kwargs) + return self.handlers["auth"] def get_batch_handler(self, key, default=None, **kwargs): """ @@ -872,10 +883,11 @@ class AppHandler: # pylint: disable=too-many-public-methods for the requested type. If no spec can be determined, a ``KeyError`` is raised. """ - spec = self.config.get(f'{self.appname}.batch.{key}.handler.spec', - default=default) + spec = self.config.get( + f"{self.appname}.batch.{key}.handler.spec", default=default + ) if not spec: - spec = self.config.get(f'{self.appname}.batch.{key}.handler.default_spec') + spec = self.config.get(f"{self.appname}.batch.{key}.handler.default_spec") if not spec: raise KeyError(f"handler spec not found for batch key: {key}") factory = self.load_object(spec) @@ -924,13 +936,15 @@ class AppHandler: # pylint: disable=too-many-public-methods handlers.extend(default) # configured default, if applicable - default = self.config.get(f'{self.config.appname}.batch.{key}.handler.default_spec') + default = self.config.get( + f"{self.config.appname}.batch.{key}.handler.default_spec" + ) if default and default not in handlers: handlers.append(default) # registered via entry points registered = [] - for handler in load_entry_points(f'{self.appname}.batch.{key}').values(): + for handler in load_entry_points(f"{self.appname}.batch.{key}").values(): spec = handler.get_spec() if spec not in handlers: registered.append(spec) @@ -946,12 +960,13 @@ class AppHandler: # pylint: disable=too-many-public-methods :rtype: :class:`~wuttjamaican.db.handler.DatabaseHandler` """ - if 'db' not in self.handlers: - spec = self.config.get(f'{self.appname}.db.handler', - default=self.default_db_handler_spec) + if "db" not in self.handlers: + spec = self.config.get( + f"{self.appname}.db.handler", default=self.default_db_handler_spec + ) factory = self.load_object(spec) - self.handlers['db'] = factory(self.config, **kwargs) - return self.handlers['db'] + self.handlers["db"] = factory(self.config, **kwargs) + return self.handlers["db"] def get_email_handler(self, **kwargs): """ @@ -961,12 +976,13 @@ class AppHandler: # pylint: disable=too-many-public-methods :rtype: :class:`~wuttjamaican.email.EmailHandler` """ - if 'email' not in self.handlers: - spec = self.config.get(f'{self.appname}.email.handler', - default=self.default_email_handler_spec) + if "email" not in self.handlers: + spec = self.config.get( + f"{self.appname}.email.handler", default=self.default_email_handler_spec + ) factory = self.load_object(spec) - self.handlers['email'] = factory(self.config, **kwargs) - return self.handlers['email'] + self.handlers["email"] = factory(self.config, **kwargs) + return self.handlers["email"] def get_install_handler(self, **kwargs): """ @@ -974,12 +990,14 @@ class AppHandler: # pylint: disable=too-many-public-methods :rtype: :class:`~wuttjamaican.install.handler.InstallHandler` """ - if 'install' not in self.handlers: - spec = self.config.get(f'{self.appname}.install.handler', - default=self.default_install_handler_spec) + if "install" not in self.handlers: + spec = self.config.get( + f"{self.appname}.install.handler", + default=self.default_install_handler_spec, + ) factory = self.load_object(spec) - self.handlers['install'] = factory(self.config, **kwargs) - return self.handlers['install'] + self.handlers["install"] = factory(self.config, **kwargs) + return self.handlers["install"] def get_people_handler(self, **kwargs): """ @@ -987,12 +1005,14 @@ class AppHandler: # pylint: disable=too-many-public-methods :rtype: :class:`~wuttjamaican.people.PeopleHandler` """ - if 'people' not in self.handlers: - spec = self.config.get(f'{self.appname}.people.handler', - default=self.default_people_handler_spec) + if "people" not in self.handlers: + spec = self.config.get( + f"{self.appname}.people.handler", + default=self.default_people_handler_spec, + ) factory = self.load_object(spec) - self.handlers['people'] = factory(self.config, **kwargs) - return self.handlers['people'] + self.handlers["people"] = factory(self.config, **kwargs) + return self.handlers["people"] def get_problem_handler(self, **kwargs): """ @@ -1000,13 +1020,15 @@ class AppHandler: # pylint: disable=too-many-public-methods :rtype: :class:`~wuttjamaican.problems.ProblemHandler` """ - if 'problems' not in self.handlers: - spec = self.config.get(f'{self.appname}.problems.handler', - default=self.default_problem_handler_spec) + if "problems" not in self.handlers: + spec = self.config.get( + f"{self.appname}.problems.handler", + default=self.default_problem_handler_spec, + ) log.debug("problem_handler spec is: %s", spec) factory = self.load_object(spec) - self.handlers['problems'] = factory(self.config, **kwargs) - return self.handlers['problems'] + self.handlers["problems"] = factory(self.config, **kwargs) + return self.handlers["problems"] def get_report_handler(self, **kwargs): """ @@ -1014,12 +1036,14 @@ class AppHandler: # pylint: disable=too-many-public-methods :rtype: :class:`~wuttjamaican.reports.ReportHandler` """ - if 'reports' not in self.handlers: - spec = self.config.get(f'{self.appname}.reports.handler_spec', - default=self.default_report_handler_spec) + if "reports" not in self.handlers: + spec = self.config.get( + f"{self.appname}.reports.handler_spec", + default=self.default_report_handler_spec, + ) factory = self.load_object(spec) - self.handlers['reports'] = factory(self.config, **kwargs) - return self.handlers['reports'] + self.handlers["reports"] = factory(self.config, **kwargs) + return self.handlers["reports"] ############################## # convenience delegators @@ -1046,7 +1070,7 @@ class AppHandler: # pylint: disable=too-many-public-methods self.get_email_handler().send_email(*args, **kwargs) -class AppProvider: # pylint: disable=too-few-public-methods +class AppProvider: # pylint: disable=too-few-public-methods """ Base class for :term:`app providers`. @@ -1092,9 +1116,12 @@ 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; " - "must pass config object instead", - DeprecationWarning, stacklevel=2) + warnings.warn( + "passing app handler to app provider is deprecated; " + "must pass config object instead", + DeprecationWarning, + stacklevel=2, + ) config = config.config self.config = config @@ -1140,7 +1167,7 @@ class GenericHandler: """ Returns the class :term:`spec` string for the handler. """ - return f'{cls.__module__}:{cls.__name__}' + return f"{cls.__module__}:{cls.__name__}" def get_provider_modules(self, module_type): """ @@ -1163,7 +1190,7 @@ class GenericHandler: if module_type not in self.modules: self.modules[module_type] = [] for provider in self.app.providers.values(): - name = f'{module_type}_modules' + name = f"{module_type}_modules" if hasattr(provider, name): modules = getattr(provider, name) if modules: diff --git a/src/wuttjamaican/auth.py b/src/wuttjamaican/auth.py index 57a8924..26224cd 100644 --- a/src/wuttjamaican/auth.py +++ b/src/wuttjamaican/auth.py @@ -35,14 +35,13 @@ from wuttjamaican.app import GenericHandler # nb. this only works if passlib is installed (part of 'db' extra) try: from passlib.context import CryptContext -except ImportError: # pragma: no cover +except ImportError: # pragma: no cover pass else: - password_context = CryptContext(schemes=['bcrypt']) + password_context = CryptContext(schemes=["bcrypt"]) - -class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods +class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods """ Base class and default implementation for the :term:`auth handler`. @@ -114,9 +113,11 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods model = self.app.model try: - token = session.query(model.UserAPIToken)\ - .filter(model.UserAPIToken.token_string == token)\ - .one() + token = ( + session.query(model.UserAPIToken) + .filter(model.UserAPIToken.token_string == token) + .one() + ) except orm.exc.NoResultFound: pass else: @@ -168,7 +169,7 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods if role: return role - else: # assuming it is a string + else: # assuming it is a string # try to match on Role.uuid try: @@ -179,15 +180,12 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods pass # try to match on Role.name - role = session.query(model.Role)\ - .filter_by(name=key)\ - .first() + role = session.query(model.Role).filter_by(name=key).first() if role: return role # try settings; if value then recurse - key = self.config.get(f'{self.appname}.role.{key}', - session=session) + key = self.config.get(f"{self.appname}.role.{key}", session=session) if key: return self.get_role(session, key) return None @@ -247,9 +245,9 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods pass # try to match on User.username - user = session.query(model.User)\ - .filter(model.User.username == obj)\ - .first() + user = ( + session.query(model.User).filter(model.User.username == obj).first() + ) if user: return user @@ -297,8 +295,8 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods """ model = self.app.model - if session and 'username' not in kwargs: - kwargs['username'] = self.make_unique_username(session, **kwargs) + if session and "username" not in kwargs: + kwargs["username"] = self.make_unique_username(session, **kwargs) user = model.User(**kwargs) if session: @@ -320,7 +318,9 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods session = self.app.get_session(user) session.delete(user) - def make_preferred_username(self, session, **kwargs): # pylint: disable=unused-argument + def make_preferred_username( + self, session, **kwargs + ): # pylint: disable=unused-argument """ Generate a "preferred" username, using data from ``kwargs`` as hints. @@ -347,18 +347,18 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods :returns: Generated username as string. """ - person = kwargs.get('person') + person = kwargs.get("person") if person: - first = (person.first_name or '').strip().lower() - last = (person.last_name or '').strip().lower() + first = (person.first_name or "").strip().lower() + last = (person.last_name or "").strip().lower() if first and last: - return f'{first}.{last}' + return f"{first}.{last}" if first: return first if last: return last - return 'newuser' + return "newuser" def make_unique_username(self, session, **kwargs): """ @@ -398,9 +398,11 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods # check for unique username counter = 1 while True: - users = session.query(model.User)\ - .filter(model.User.username == username)\ - .count() + users = ( + session.query(model.User) + .filter(model.User.username == username) + .count() + ) if not users: break username = f"{original_username}{counter:02d}" @@ -426,22 +428,25 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods """ Returns the special "Administrator" role. """ - return self._special_role(session, _uuid.UUID('d937fa8a965611dfa0dd001143047286'), - "Administrator") + return self._special_role( + session, _uuid.UUID("d937fa8a965611dfa0dd001143047286"), "Administrator" + ) def get_role_anonymous(self, session): """ Returns the special "Anonymous" (aka. "Guest") role. """ - return self._special_role(session, _uuid.UUID('f8a27c98965a11dfaff7001143047286'), - "Anonymous") + return self._special_role( + session, _uuid.UUID("f8a27c98965a11dfaff7001143047286"), "Anonymous" + ) def get_role_authenticated(self, session): """ Returns the special "Authenticated" role. """ - return self._special_role(session, _uuid.UUID('b765a9cc331a11e6ac2a3ca9f40bc550'), - "Authenticated") + return self._special_role( + session, _uuid.UUID("b765a9cc331a11e6ac2a3ca9f40bc550"), "Authenticated" + ) def user_is_admin(self, user): """ @@ -457,9 +462,9 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods return False - def get_permissions(self, session, principal, - include_anonymous=True, - include_authenticated=True): + def get_permissions( + self, session, principal, include_anonymous=True, include_authenticated=True + ): """ Return a set of permission names, which represents all permissions effectively granted to the given user or role. @@ -483,10 +488,8 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods """ # we will use any `roles` attribute which may be present. in # practice we would be assuming a User in this case - if hasattr(principal, 'roles'): - roles = [role - for role in principal.roles - if self._role_is_pertinent(role)] + if hasattr(principal, "roles"): + roles = [role for role in principal.roles if self._role_is_pertinent(role)] # here our User assumption gets a little more explicit if include_authenticated: @@ -507,14 +510,19 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods # build the permissions cache cache = set() for role in roles: - if hasattr(role, 'permissions'): + if hasattr(role, "permissions"): cache.update(role.permissions) return cache - def has_permission(self, session, principal, permission, - include_anonymous=True, - include_authenticated=True): + def has_permission( + self, + session, + principal, + permission, + include_anonymous=True, + include_authenticated=True, + ): """ Check if the given user or role has been granted the given permission. @@ -551,9 +559,12 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods :returns: Boolean indicating if the permission is granted. """ - perms = self.get_permissions(session, principal, - include_anonymous=include_anonymous, - include_authenticated=include_authenticated) + perms = self.get_permissions( + session, + principal, + include_anonymous=include_anonymous, + include_authenticated=include_authenticated, + ) return permission in perms def grant_permission(self, role, permission): @@ -608,9 +619,7 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods token_string = self.generate_api_token_string() # persist token in DB - token = model.UserAPIToken( - description=description, - token_string=token_string) + token = model.UserAPIToken(description=description, token_string=token_string) user.api_tokens.append(token) session.add(token) @@ -642,7 +651,7 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods # internal methods ############################## - def _role_is_pertinent(self, role): # pylint: disable=unused-argument + def _role_is_pertinent(self, role): # pylint: disable=unused-argument """ Check the role to ensure it is "pertinent" for the current app. diff --git a/src/wuttjamaican/batch.py b/src/wuttjamaican/batch.py index 9340485..54c9ad7 100644 --- a/src/wuttjamaican/batch.py +++ b/src/wuttjamaican/batch.py @@ -31,7 +31,7 @@ import shutil from wuttjamaican.app import GenericHandler -class BatchHandler(GenericHandler): # pylint: disable=too-many-public-methods +class BatchHandler(GenericHandler): # pylint: disable=too-many-public-methods """ Base class and *partial* default implementation for :term:`batch handlers `. @@ -59,8 +59,10 @@ class BatchHandler(GenericHandler): # pylint: disable=too-many-public-methods Subclass must define this; default is not implemented. """ - raise NotImplementedError("You must set the 'model_class' attribute " - f"for class '{self.__class__.__name__}'") + raise NotImplementedError( + "You must set the 'model_class' attribute " + f"for class '{self.__class__.__name__}'" + ) @property def batch_type(self): @@ -99,8 +101,8 @@ class BatchHandler(GenericHandler): # pylint: disable=too-many-public-methods :returns: New batch; instance of :attr:`model_class`. """ # generate new ID unless caller specifies - if 'id' not in kwargs: - kwargs['id'] = self.consume_batch_id(session) + if "id" not in kwargs: + kwargs["id"] = self.consume_batch_id(session) # make batch batch = self.model_class(**kwargs) @@ -121,9 +123,9 @@ class BatchHandler(GenericHandler): # pylint: disable=too-many-public-methods :returns: Batch ID as integer, or zero-padded 8-char string. """ db = self.app.get_db_handler() - batch_id = db.next_counter_value(session, 'batch_id') + batch_id = db.next_counter_value(session, "batch_id") if as_str: - return f'{batch_id:08d}' + return f"{batch_id:08d}" return batch_id def init_batch(self, batch, session=None, progress=None, **kwargs): @@ -178,10 +180,10 @@ class BatchHandler(GenericHandler): # pylint: disable=too-many-public-methods :returns: Path to root data dir for handler's batch type. """ # get root storage path - rootdir = self.config.get(f'{self.config.appname}.batch.storage_path') + rootdir = self.config.get(f"{self.config.appname}.batch.storage_path") if not rootdir: appdir = self.app.get_appdir() - rootdir = os.path.join(appdir, 'data', 'batch') + rootdir = os.path.join(appdir, "data", "batch") # get path for this batch type path = os.path.join(rootdir, self.batch_type) @@ -204,7 +206,7 @@ class BatchHandler(GenericHandler): # pylint: disable=too-many-public-methods return path - def should_populate(self, batch): # pylint: disable=unused-argument + def should_populate(self, batch): # pylint: disable=unused-argument """ Must return true or false, indicating whether the given batch should be populated from initial data source(s). @@ -348,7 +350,9 @@ class BatchHandler(GenericHandler): # pylint: disable=too-many-public-methods * :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 + ): # pylint: disable=unused-argument """ Returns text indicating the reason (if any) that a given batch should *not* be executed. @@ -468,16 +472,22 @@ class BatchHandler(GenericHandler): # pylint: disable=too-many-public-methods 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( # pylint: disable=assignment-from-none + 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( # pylint: disable=assignment-from-none + 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 + ): # pylint: disable=unused-argument """ Execute the given batch. @@ -505,7 +515,9 @@ class BatchHandler(GenericHandler): # pylint: disable=too-many-public-methods """ return None - def do_delete(self, batch, user, dry_run=False, progress=None, **kwargs): # pylint: disable=unused-argument + def do_delete( + self, batch, user, dry_run=False, progress=None, **kwargs + ): # pylint: disable=unused-argument """ Delete the given batch entirely. diff --git a/src/wuttjamaican/cli/__init__.py b/src/wuttjamaican/cli/__init__.py index 048a3c4..9985bee 100644 --- a/src/wuttjamaican/cli/__init__.py +++ b/src/wuttjamaican/cli/__init__.py @@ -40,4 +40,5 @@ from . import problems # discover more commands, installed via other packages from .base import typer_eager_imports + typer_eager_imports(wutta_typer) diff --git a/src/wuttjamaican/cli/base.py b/src/wuttjamaican/cli/base.py index 888c39a..9a67768 100644 --- a/src/wuttjamaican/cli/base.py +++ b/src/wuttjamaican/cli/base.py @@ -59,18 +59,21 @@ def make_cli_config(ctx: typer.Context): :returns: :class:`~wuttjamaican.conf.WuttaConfig` instance """ logging.basicConfig() - return make_config(files=ctx.params.get('config_paths') or None) + return make_config(files=ctx.params.get("config_paths") or None) def typer_callback( - ctx: typer.Context, - - config_paths: Annotated[ - Optional[List[Path]], - typer.Option('--config', '-c', - exists=True, - help="Config path (may be specified more than once)")] = None, -): # pylint: disable=unused-argument + ctx: typer.Context, + config_paths: Annotated[ + Optional[List[Path]], + typer.Option( + "--config", + "-c", + exists=True, + help="Config path (may be specified more than once)", + ), + ] = None, +): # pylint: disable=unused-argument """ Generic callback for use with top-level commands. This adds some top-level args: @@ -85,8 +88,7 @@ def typer_callback( ctx.wutta_config = make_cli_config(ctx) -def typer_eager_imports( - group: [typer.Typer, str]): +def typer_eager_imports(group: [typer.Typer, str]): """ Eagerly import all modules which are registered as having :term:`subcommands ` belonging to the given group @@ -119,7 +121,7 @@ def typer_eager_imports( """ if isinstance(group, typer.Typer): group = group.info.name - load_entry_points(f'{group}.typer_imports') + load_entry_points(f"{group}.typer_imports") def make_typer(**kwargs): @@ -136,11 +138,8 @@ def make_typer(**kwargs): :returns: ``typer.Typer`` instance """ - kwargs.setdefault('callback', typer_callback) + kwargs.setdefault("callback", typer_callback) return typer.Typer(**kwargs) -wutta_typer = make_typer( - name='wutta', - help="Wutta Software Framework" -) +wutta_typer = make_typer(name="wutta", help="Wutta Software Framework") diff --git a/src/wuttjamaican/cli/make_appdir.py b/src/wuttjamaican/cli/make_appdir.py index e9f1506..33a7c81 100644 --- a/src/wuttjamaican/cli/make_appdir.py +++ b/src/wuttjamaican/cli/make_appdir.py @@ -35,13 +35,16 @@ from .base import wutta_typer @wutta_typer.command() def make_appdir( - ctx: typer.Context, - appdir_path: Annotated[ - Path, - typer.Option('--path', - help="Path to desired app dir; default is (usually) " - "`app` in the root of virtual environment.")] = None, -): # pylint: disable=unused-argument + ctx: typer.Context, + appdir_path: Annotated[ + Path, + typer.Option( + "--path", + help="Path to desired app dir; default is (usually) " + "`app` in the root of virtual environment.", + ), + ] = None, +): # pylint: disable=unused-argument """ Make the app dir for virtual environment @@ -49,6 +52,6 @@ def make_appdir( """ config = ctx.parent.wutta_config app = config.get_app() - appdir = ctx.params['appdir_path'] or app.get_appdir() + appdir = ctx.params["appdir_path"] or app.get_appdir() app.make_appdir(appdir) sys.stdout.write(f"established appdir: {appdir}\n") diff --git a/src/wuttjamaican/cli/make_uuid.py b/src/wuttjamaican/cli/make_uuid.py index 84f4182..e796a53 100644 --- a/src/wuttjamaican/cli/make_uuid.py +++ b/src/wuttjamaican/cli/make_uuid.py @@ -33,7 +33,7 @@ from .base import wutta_typer @wutta_typer.command() def make_uuid( - ctx: typer.Context, + ctx: typer.Context, ): """ Generate a new UUID diff --git a/src/wuttjamaican/cli/problems.py b/src/wuttjamaican/cli/problems.py index 0638fd1..269978d 100644 --- a/src/wuttjamaican/cli/problems.py +++ b/src/wuttjamaican/cli/problems.py @@ -36,25 +36,34 @@ from .base import wutta_typer @wutta_typer.command() def problems( - ctx: typer.Context, - - systems: Annotated[ - List[str], - typer.Option('--system', '-s', - 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 - List[str], - typer.Option('--problem', '-p', - help="Identify a particular problem check; can be specified " - "more than once. If not specified, all checks are assumed.")] = None, - - list_checks: Annotated[ - bool, - typer.Option('--list', '-l', - help="List available problem checks; optionally filtered " - "per --system and --problem")] = False, + ctx: typer.Context, + systems: Annotated[ + List[str], + typer.Option( + "--system", + "-s", + 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 + List[str], + typer.Option( + "--problem", + "-p", + help="Identify a particular problem check; can be specified " + "more than once. If not specified, all checks are assumed.", + ), + ] = None, + list_checks: Annotated[ + bool, + typer.Option( + "--list", + "-l", + help="List available problem checks; optionally filtered " + "per --system and --problem", + ), + ] = False, ): """ Find and report on problems with the data or system. @@ -65,9 +74,11 @@ def problems( # try to warn user if unknown system is specified; but otherwise ignore supported = handler.get_supported_systems() - for key in (systems or []): + for key in systems or []: if key not in supported: - rich.print(f"\n[bold yellow]No problem reports exist for system: {key}[/bold yellow]") + rich.print( + f"\n[bold yellow]No problem reports exist for system: {key}[/bold yellow]" + ) checks = handler.filter_problem_checks(systems=systems, problems=problems) diff --git a/src/wuttjamaican/conf.py b/src/wuttjamaican/conf.py index 984c4dc..985ac77 100644 --- a/src/wuttjamaican/conf.py +++ b/src/wuttjamaican/conf.py @@ -34,16 +34,20 @@ import tempfile import config as configuration -from wuttjamaican.util import (load_entry_points, load_object, - parse_bool, parse_list, - UNSPECIFIED) +from wuttjamaican.util import ( + load_entry_points, + load_object, + parse_bool, + parse_list, + UNSPECIFIED, +) from wuttjamaican.exc import ConfigurationError log = logging.getLogger(__name__) -class WuttaConfig: # pylint: disable=too-many-instance-attributes +class WuttaConfig: # pylint: disable=too-many-instance-attributes """ Configuration class for Wutta Framework @@ -184,17 +188,18 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes See also :ref:`where-config-settings-come-from`. """ - default_app_handler_spec = 'wuttjamaican.app:AppHandler' - default_engine_maker_spec = 'wuttjamaican.db.conf:make_engine_from_config' + + default_app_handler_spec = "wuttjamaican.app:AppHandler" + default_engine_maker_spec = "wuttjamaican.db.conf:make_engine_from_config" def __init__( - self, - files=None, - defaults=None, - appname='wutta', - usedb=None, - preferdb=None, - configure_logging=None, + self, + files=None, + defaults=None, + appname="wutta", + usedb=None, + preferdb=None, + configure_logging=None, ): self.appname = appname configs = [] @@ -213,36 +218,41 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes # establish logging if configure_logging is None: - configure_logging = self.get_bool(f'{self.appname}.config.configure_logging', - default=False, usedb=False) + configure_logging = self.get_bool( + f"{self.appname}.config.configure_logging", default=False, usedb=False + ) if configure_logging: self._configure_logging() # usedb flag self.usedb = usedb if self.usedb is None: - self.usedb = self.get_bool(f'{self.appname}.config.usedb', - default=False, usedb=False) + self.usedb = self.get_bool( + f"{self.appname}.config.usedb", default=False, usedb=False + ) # preferdb flag self.preferdb = preferdb if self.usedb and self.preferdb is None: - self.preferdb = self.get_bool(f'{self.appname}.config.preferdb', - default=False, usedb=False) + self.preferdb = self.get_bool( + f"{self.appname}.config.preferdb", default=False, usedb=False + ) # configure main app DB if applicable, or disable usedb flag try: from wuttjamaican.db import Session, get_engines except ImportError: if self.usedb: - log.warning("config created with `usedb = True`, but can't import " - "DB module(s), so setting `usedb = False` instead", - exc_info=True) + log.warning( + "config created with `usedb = True`, but can't import " + "DB module(s), so setting `usedb = False` instead", + exc_info=True, + ) self.usedb = False self.preferdb = False else: - self.appdb_engines = get_engines(self, f'{self.appname}.db') - self.appdb_engine = self.appdb_engines.get('default') + self.appdb_engines = get_engines(self, f"{self.appname}.db") + self.appdb_engine = self.appdb_engines.get("default") Session.configure(bind=self.appdb_engine) log.debug("config files read: %s", self.files_read) @@ -256,7 +266,7 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes # try to load config with standard parser, and default vars here = os.path.dirname(path) - config = configparser.ConfigParser(defaults={'here': here, '__file__': path}) + config = configparser.ConfigParser(defaults={"here": here, "__file__": path}) if not config.read(path): if require: raise FileNotFoundError(f"could not read required config file: {path}") @@ -267,14 +277,14 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes for section in config.sections(): temp_config.add_section(section) # nb. must interpolate most values but *not* for logging formatters - raw = section.startswith('formatter_') + 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') + fd, temp_path = tempfile.mkstemp(suffix=".ini") os.close(fd) - with open(temp_path, 'wt', encoding='utf_8') as f: + with open(temp_path, "wt", encoding="utf_8") as f: temp_config.write(f) # and finally, load that into our main config @@ -284,13 +294,13 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes os.remove(temp_path) # bring in any "required" files - requires = config.get(f'{self.appname}.config.require') + requires = config.get(f"{self.appname}.config.require") if requires: for p in self.parse_list(requires): self._load_ini_configs(p, configs, require=True) # bring in any "included" files - includes = config.get(f'{self.appname}.config.include') + includes = config.get(f"{self.appname}.config.include") if includes: for p in self.parse_list(includes): self._load_ini_configs(p, configs, require=False) @@ -304,10 +314,7 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes """ return self.files_read - def setdefault( - self, - key, - value): + def setdefault(self, key, value): """ Establish a default config value for the given key. @@ -327,16 +334,16 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes return self.get(key, usedb=False) def get( - self, - key, - default=UNSPECIFIED, - require=False, - ignore_ambiguous=False, - message=None, - usedb=None, - preferdb=None, - session=None, - **kwargs + self, + key, + default=UNSPECIFIED, + require=False, + ignore_ambiguous=False, + message=None, + usedb=None, + preferdb=None, + session=None, + **kwargs, ): """ Retrieve a string value from config. @@ -489,7 +496,7 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes config.require('foo') """ - kwargs['require'] = True + kwargs["require"] = True return self.get(*args, **kwargs) def get_bool(self, *args, **kwargs): @@ -613,9 +620,9 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes parser.set(section, option, value) # write INI file and return path - fd, path = tempfile.mkstemp(suffix='.conf') + fd, path = tempfile.mkstemp(suffix=".conf") os.close(fd) - with open(path, 'wt', encoding='utf_8') as f: + with open(path, "wt", encoding="utf_8") as f: parser.write(f) return path @@ -626,9 +633,12 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes See also :doc:`/narr/handlers/app`. """ - if not hasattr(self, '_app'): - spec = self.get(f'{self.appname}.app.handler', usedb=False, - default=self.default_app_handler_spec) + if not hasattr(self, "_app"): + spec = self.get( + f"{self.appname}.app.handler", + usedb=False, + default=self.default_app_handler_spec, + ) factory = load_object(spec) self._app = factory(self) return self._app @@ -656,13 +666,14 @@ class WuttaConfig: # pylint: disable=too-many-instance-attributes [wutta] production = true """ - return self.get_bool(f'{self.appname}.production', default=False) + return self.get_bool(f"{self.appname}.production", default=False) class WuttaConfigExtension: """ Base class for all :term:`config extensions `. """ + key = None def __repr__(self): @@ -696,7 +707,7 @@ def generic_default_files(appname): :returns: List of default file paths. """ - if sys.platform == 'win32': + if sys.platform == "win32": # use pywin32 to fetch official defaults try: from win32com.shell import shell, shellcon @@ -705,42 +716,49 @@ def generic_default_files(appname): return [ # e.g. C:\..?? TODO: what is the user-specific path on win32? - os.path.join(shell.SHGetSpecialFolderPath( - 0, shellcon.CSIDL_APPDATA), appname, f'{appname}.conf'), - os.path.join(shell.SHGetSpecialFolderPath( - 0, shellcon.CSIDL_APPDATA), f'{appname}.conf'), - + os.path.join( + shell.SHGetSpecialFolderPath(0, shellcon.CSIDL_APPDATA), + appname, + f"{appname}.conf", + ), + os.path.join( + shell.SHGetSpecialFolderPath(0, shellcon.CSIDL_APPDATA), + f"{appname}.conf", + ), # e.g. C:\ProgramData\wutta\wutta.conf - os.path.join(shell.SHGetSpecialFolderPath( - 0, shellcon.CSIDL_COMMON_APPDATA), appname, f'{appname}.conf'), - os.path.join(shell.SHGetSpecialFolderPath( - 0, shellcon.CSIDL_COMMON_APPDATA), f'{appname}.conf'), + os.path.join( + shell.SHGetSpecialFolderPath(0, shellcon.CSIDL_COMMON_APPDATA), + appname, + f"{appname}.conf", + ), + os.path.join( + shell.SHGetSpecialFolderPath(0, shellcon.CSIDL_COMMON_APPDATA), + f"{appname}.conf", + ), ] # default paths for *nix return [ - f'{sys.prefix}/app/{appname}.conf', - - os.path.expanduser(f'~/.{appname}/{appname}.conf'), - os.path.expanduser(f'~/.{appname}.conf'), - - f'/usr/local/etc/{appname}/{appname}.conf', - f'/usr/local/etc/{appname}.conf', - - f'/etc/{appname}/{appname}.conf', - f'/etc/{appname}.conf', + f"{sys.prefix}/app/{appname}.conf", + os.path.expanduser(f"~/.{appname}/{appname}.conf"), + os.path.expanduser(f"~/.{appname}.conf"), + f"/usr/local/etc/{appname}/{appname}.conf", + f"/usr/local/etc/{appname}.conf", + f"/etc/{appname}/{appname}.conf", + f"/etc/{appname}.conf", ] def get_config_paths( - files=None, - plus_files=None, - appname='wutta', - env_files_name=None, - env_plus_files_name=None, - env=None, - default_files=None, - winsvc=None): + files=None, + plus_files=None, + appname="wutta", + env_files_name=None, + env_plus_files_name=None, + env=None, + default_files=None, + winsvc=None, +): """ This function determines which files should ultimately be provided to the config constructor. It is normally called by @@ -856,7 +874,7 @@ def get_config_paths( # first identify any "primary" config files if files is None: if not env_files_name: - env_files_name = f'{appname.upper()}_CONFIG_FILES' + env_files_name = f"{appname.upper()}_CONFIG_FILES" files = env.get(env_files_name) if files is not None: @@ -869,8 +887,7 @@ def get_config_paths( files = [default_files] else: files = list(default_files) - files = [path for path in files - if os.path.exists(path)] + files = [path for path in files if os.path.exists(path)] else: files = [] @@ -886,7 +903,7 @@ def get_config_paths( # then identify any "plus" (config tweak) files if plus_files is None: if not env_plus_files_name: - env_plus_files_name = f'{appname.upper()}_CONFIG_PLUS_FILES' + env_plus_files_name = f"{appname.upper()}_CONFIG_PLUS_FILES" plus_files = env.get(env_plus_files_name) if plus_files is not None: @@ -911,9 +928,9 @@ def get_config_paths( if winsvc: config = configparser.ConfigParser() config.read(files) - section = f'{appname}.config' + section = f"{appname}.config" if config.has_section(section): - option = f'winsvc.{winsvc}' + 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)) @@ -922,20 +939,21 @@ def get_config_paths( def make_config( - files=None, - plus_files=None, - appname='wutta', - env_files_name=None, - env_plus_files_name=None, - env=None, - default_files=None, - winsvc=None, - usedb=None, - preferdb=None, - factory=None, - extend=True, - extension_entry_points=None, - **kwargs): + files=None, + plus_files=None, + appname="wutta", + env_files_name=None, + env_plus_files_name=None, + env=None, + default_files=None, + winsvc=None, + usedb=None, + preferdb=None, + factory=None, + extend=True, + extension_entry_points=None, + **kwargs, +): """ Make a new config (usually :class:`WuttaConfig`) object, initialized per the given parameters and (usually) further @@ -992,19 +1010,18 @@ def make_config( env_plus_files_name=env_plus_files_name, env=env, default_files=default_files, - winsvc=winsvc) + winsvc=winsvc, + ) # make config object if not factory: factory = WuttaConfig - config = factory(files, appname=appname, - usedb=usedb, preferdb=preferdb, - **kwargs) + config = factory(files, appname=appname, usedb=usedb, preferdb=preferdb, **kwargs) # maybe extend config object if extend: if not extension_entry_points: - extension_entry_points = f'{appname}.config.extensions' + extension_entry_points = f"{appname}.config.extensions" # apply all registered extensions # TODO: maybe let config disable some extensions? @@ -1104,4 +1121,4 @@ class WuttaConfigProfile: profile = TelemetryProfile("default") url = profile.get_str("submit_url") """ - return self.config.get(f'{self.section}.{self.key}.{option}', **kwargs) + return self.config.get(f"{self.section}.{self.key}.{option}", **kwargs) diff --git a/src/wuttjamaican/db/alembic/env.py b/src/wuttjamaican/db/alembic/env.py index 4a20bd5..84dba40 100644 --- a/src/wuttjamaican/db/alembic/env.py +++ b/src/wuttjamaican/db/alembic/env.py @@ -11,8 +11,7 @@ from wuttjamaican.conf import make_config alembic_config = context.config # this is the wutta-based config -wutta_config = make_config(alembic_config.config_file_name, - usedb=False) +wutta_config = make_config(alembic_config.config_file_name, usedb=False) # add your model's MetaData object here # for 'autogenerate' support @@ -54,9 +53,7 @@ def run_migrations_online() -> None: connectable = wutta_config.appdb_engine with connectable.connect() as connection: - context.configure( - connection=connection, target_metadata=target_metadata - ) + context.configure(connection=connection, target_metadata=target_metadata) with context.begin_transaction(): context.run_migrations() diff --git a/src/wuttjamaican/db/alembic/versions/3abcc44f7f91_add_people.py b/src/wuttjamaican/db/alembic/versions/3abcc44f7f91_add_people.py index 754cbfa..1b05c6f 100644 --- a/src/wuttjamaican/db/alembic/versions/3abcc44f7f91_add_people.py +++ b/src/wuttjamaican/db/alembic/versions/3abcc44f7f91_add_people.py @@ -5,6 +5,7 @@ Revises: d686f7abe3e0 Create Date: 2024-07-14 15:14:30.552682 """ + from typing import Sequence, Union from alembic import op @@ -13,8 +14,8 @@ import wuttjamaican.db.util # revision identifiers, used by Alembic. -revision: str = '3abcc44f7f91' -down_revision: Union[str, None] = 'd686f7abe3e0' +revision: str = "3abcc44f7f91" +down_revision: Union[str, None] = "d686f7abe3e0" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -22,25 +23,30 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # person - op.create_table('person', - sa.Column('uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('full_name', sa.String(length=100), nullable=False), - sa.Column('first_name', sa.String(length=50), nullable=True), - sa.Column('middle_name', sa.String(length=50), nullable=True), - sa.Column('last_name', sa.String(length=50), nullable=True), - sa.PrimaryKeyConstraint('uuid', name=op.f('pk_person')) - ) + op.create_table( + "person", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("full_name", sa.String(length=100), nullable=False), + sa.Column("first_name", sa.String(length=50), nullable=True), + sa.Column("middle_name", sa.String(length=50), nullable=True), + sa.Column("last_name", sa.String(length=50), nullable=True), + sa.PrimaryKeyConstraint("uuid", name=op.f("pk_person")), + ) # user - op.add_column('user', sa.Column('person_uuid', wuttjamaican.db.util.UUID(), nullable=True)) - op.create_foreign_key(op.f('fk_user_person_uuid_person'), 'user', 'person', ['person_uuid'], ['uuid']) + op.add_column( + "user", sa.Column("person_uuid", wuttjamaican.db.util.UUID(), nullable=True) + ) + op.create_foreign_key( + op.f("fk_user_person_uuid_person"), "user", "person", ["person_uuid"], ["uuid"] + ) def downgrade() -> None: # user - op.drop_constraint(op.f('fk_user_person_uuid_person'), 'user', type_='foreignkey') - op.drop_column('user', 'person_uuid') + op.drop_constraint(op.f("fk_user_person_uuid_person"), "user", type_="foreignkey") + op.drop_column("user", "person_uuid") # person - op.drop_table('person') + op.drop_table("person") diff --git a/src/wuttjamaican/db/alembic/versions/6bf900765500_add_user_prevent_edit.py b/src/wuttjamaican/db/alembic/versions/6bf900765500_add_user_prevent_edit.py index 4b23bce..cd6f4c5 100644 --- a/src/wuttjamaican/db/alembic/versions/6bf900765500_add_user_prevent_edit.py +++ b/src/wuttjamaican/db/alembic/versions/6bf900765500_add_user_prevent_edit.py @@ -5,6 +5,7 @@ Revises: ebd75b9feaa7 Create Date: 2024-11-24 16:52:36.773657 """ + from typing import Sequence, Union from alembic import op @@ -12,8 +13,8 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision: str = '6bf900765500' -down_revision: Union[str, None] = 'ebd75b9feaa7' +revision: str = "6bf900765500" +down_revision: Union[str, None] = "ebd75b9feaa7" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -21,10 +22,10 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # user - op.add_column('user', sa.Column('prevent_edit', sa.Boolean(), nullable=True)) + op.add_column("user", sa.Column("prevent_edit", sa.Boolean(), nullable=True)) def downgrade() -> None: # user - op.drop_column('user', 'prevent_edit') + op.drop_column("user", "prevent_edit") diff --git a/src/wuttjamaican/db/alembic/versions/d686f7abe3e0_add_users_roles.py b/src/wuttjamaican/db/alembic/versions/d686f7abe3e0_add_users_roles.py index b0a541f..73ee5a4 100644 --- a/src/wuttjamaican/db/alembic/versions/d686f7abe3e0_add_users_roles.py +++ b/src/wuttjamaican/db/alembic/versions/d686f7abe3e0_add_users_roles.py @@ -5,6 +5,7 @@ Revises: fc3a3bcaa069 Create Date: 2024-07-14 13:27:22.703093 """ + from typing import Sequence, Union from alembic import op @@ -13,8 +14,8 @@ import wuttjamaican.db.util # revision identifiers, used by Alembic. -revision: str = 'd686f7abe3e0' -down_revision: Union[str, None] = 'fc3a3bcaa069' +revision: str = "d686f7abe3e0" +down_revision: Union[str, None] = "fc3a3bcaa069" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -22,53 +23,63 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # role - op.create_table('role', - sa.Column('uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('name', sa.String(length=100), nullable=False), - sa.Column('notes', sa.Text(), nullable=True), - sa.PrimaryKeyConstraint('uuid'), - sa.UniqueConstraint('name', name=op.f('uq_role_name')) - ) + op.create_table( + "role", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("name", sa.String(length=100), nullable=False), + sa.Column("notes", sa.Text(), nullable=True), + sa.PrimaryKeyConstraint("uuid"), + sa.UniqueConstraint("name", name=op.f("uq_role_name")), + ) # user - op.create_table('user', - sa.Column('uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('username', sa.String(length=25), nullable=False), - sa.Column('password', sa.String(length=60), nullable=True), - sa.Column('active', sa.Boolean(), nullable=False), - sa.PrimaryKeyConstraint('uuid'), - sa.UniqueConstraint('username', name=op.f('uq_user_username')) - ) + op.create_table( + "user", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("username", sa.String(length=25), nullable=False), + sa.Column("password", sa.String(length=60), nullable=True), + sa.Column("active", sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint("uuid"), + sa.UniqueConstraint("username", name=op.f("uq_user_username")), + ) # permission - op.create_table('permission', - sa.Column('role_uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('permission', sa.String(length=254), nullable=False), - sa.ForeignKeyConstraint(['role_uuid'], ['role.uuid'], name=op.f('fk_permission_role_uuid_role')), - sa.PrimaryKeyConstraint('role_uuid', 'permission') - ) + op.create_table( + "permission", + sa.Column("role_uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("permission", sa.String(length=254), nullable=False), + sa.ForeignKeyConstraint( + ["role_uuid"], ["role.uuid"], name=op.f("fk_permission_role_uuid_role") + ), + sa.PrimaryKeyConstraint("role_uuid", "permission"), + ) # user_x_role - op.create_table('user_x_role', - sa.Column('uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('user_uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('role_uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.ForeignKeyConstraint(['role_uuid'], ['role.uuid'], name=op.f('fk_user_x_role_role_uuid_role')), - sa.ForeignKeyConstraint(['user_uuid'], ['user.uuid'], name=op.f('fk_user_x_role_user_uuid_user')), - sa.PrimaryKeyConstraint('uuid') - ) + op.create_table( + "user_x_role", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("user_uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("role_uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.ForeignKeyConstraint( + ["role_uuid"], ["role.uuid"], name=op.f("fk_user_x_role_role_uuid_role") + ), + sa.ForeignKeyConstraint( + ["user_uuid"], ["user.uuid"], name=op.f("fk_user_x_role_user_uuid_user") + ), + sa.PrimaryKeyConstraint("uuid"), + ) def downgrade() -> None: # user_x_role - op.drop_table('user_x_role') + op.drop_table("user_x_role") # permission - op.drop_table('permission') + op.drop_table("permission") # user - op.drop_table('user') + op.drop_table("user") # role - op.drop_table('role') + op.drop_table("role") diff --git a/src/wuttjamaican/db/alembic/versions/ebd75b9feaa7_add_upgrades.py b/src/wuttjamaican/db/alembic/versions/ebd75b9feaa7_add_upgrades.py index 0afd3b5..4f61163 100644 --- a/src/wuttjamaican/db/alembic/versions/ebd75b9feaa7_add_upgrades.py +++ b/src/wuttjamaican/db/alembic/versions/ebd75b9feaa7_add_upgrades.py @@ -5,6 +5,7 @@ Revises: 3abcc44f7f91 Create Date: 2024-08-24 09:42:21.199679 """ + from typing import Sequence, Union from alembic import op @@ -13,8 +14,8 @@ from sqlalchemy.dialects import postgresql import wuttjamaican.db.util # revision identifiers, used by Alembic. -revision: str = 'ebd75b9feaa7' -down_revision: Union[str, None] = '3abcc44f7f91' +revision: str = "ebd75b9feaa7" +down_revision: Union[str, None] = "3abcc44f7f91" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -22,26 +23,50 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # upgrade - sa.Enum('PENDING', 'EXECUTING', 'SUCCESS', 'FAILURE', name='upgradestatus').create(op.get_bind()) - op.create_table('upgrade', - sa.Column('uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('created', sa.DateTime(timezone=True), nullable=False), - sa.Column('created_by_uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('description', sa.String(length=255), nullable=False), - sa.Column('notes', sa.Text(), nullable=True), - sa.Column('executing', sa.Boolean(), nullable=False), - sa.Column('status', postgresql.ENUM('PENDING', 'EXECUTING', 'SUCCESS', 'FAILURE', name='upgradestatus', create_type=False), nullable=False), - sa.Column('executed', sa.DateTime(timezone=True), nullable=True), - sa.Column('executed_by_uuid', wuttjamaican.db.util.UUID(), nullable=True), - sa.Column('exit_code', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['created_by_uuid'], ['user.uuid'], name=op.f('fk_upgrade_created_by_uuid_user')), - sa.ForeignKeyConstraint(['executed_by_uuid'], ['user.uuid'], name=op.f('fk_upgrade_executed_by_uuid_user')), - sa.PrimaryKeyConstraint('uuid', name=op.f('pk_upgrade')) - ) + sa.Enum("PENDING", "EXECUTING", "SUCCESS", "FAILURE", name="upgradestatus").create( + op.get_bind() + ) + op.create_table( + "upgrade", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("created", sa.DateTime(timezone=True), nullable=False), + sa.Column("created_by_uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("description", sa.String(length=255), nullable=False), + sa.Column("notes", sa.Text(), nullable=True), + sa.Column("executing", sa.Boolean(), nullable=False), + sa.Column( + "status", + postgresql.ENUM( + "PENDING", + "EXECUTING", + "SUCCESS", + "FAILURE", + name="upgradestatus", + create_type=False, + ), + nullable=False, + ), + sa.Column("executed", sa.DateTime(timezone=True), nullable=True), + sa.Column("executed_by_uuid", wuttjamaican.db.util.UUID(), nullable=True), + sa.Column("exit_code", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["created_by_uuid"], + ["user.uuid"], + name=op.f("fk_upgrade_created_by_uuid_user"), + ), + sa.ForeignKeyConstraint( + ["executed_by_uuid"], + ["user.uuid"], + name=op.f("fk_upgrade_executed_by_uuid_user"), + ), + sa.PrimaryKeyConstraint("uuid", name=op.f("pk_upgrade")), + ) def downgrade() -> None: # upgrade - op.drop_table('upgrade') - sa.Enum('PENDING', 'EXECUTING', 'SUCCESS', 'FAILURE', name='upgradestatus').drop(op.get_bind()) + op.drop_table("upgrade") + sa.Enum("PENDING", "EXECUTING", "SUCCESS", "FAILURE", name="upgradestatus").drop( + op.get_bind() + ) diff --git a/src/wuttjamaican/db/alembic/versions/efdcb2c75034_add_user_api_token.py b/src/wuttjamaican/db/alembic/versions/efdcb2c75034_add_user_api_token.py index 61ed5fa..567f15c 100644 --- a/src/wuttjamaican/db/alembic/versions/efdcb2c75034_add_user_api_token.py +++ b/src/wuttjamaican/db/alembic/versions/efdcb2c75034_add_user_api_token.py @@ -5,6 +5,7 @@ Revises: 6bf900765500 Create Date: 2025-08-08 08:58:19.376105 """ + from typing import Sequence, Union from alembic import op @@ -13,8 +14,8 @@ import wuttjamaican.db.util # revision identifiers, used by Alembic. -revision: str = 'efdcb2c75034' -down_revision: Union[str, None] = '6bf900765500' +revision: str = "efdcb2c75034" +down_revision: Union[str, None] = "6bf900765500" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -22,18 +23,21 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # user_api_token - op.create_table('user_api_token', - sa.Column('uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('user_uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('description', sa.String(length=255), nullable=False), - sa.Column('token_string', sa.String(length=255), nullable=False), - sa.Column('created', sa.DateTime(timezone=True), nullable=False), - sa.ForeignKeyConstraint(['user_uuid'], ['user.uuid'], name=op.f('fk_user_api_token_user_uuid_user')), - sa.PrimaryKeyConstraint('uuid', name=op.f('pk_user_api_token')) - ) + op.create_table( + "user_api_token", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("user_uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("description", sa.String(length=255), nullable=False), + sa.Column("token_string", sa.String(length=255), nullable=False), + sa.Column("created", sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint( + ["user_uuid"], ["user.uuid"], name=op.f("fk_user_api_token_user_uuid_user") + ), + sa.PrimaryKeyConstraint("uuid", name=op.f("pk_user_api_token")), + ) def downgrade() -> None: # user_api_token - op.drop_table('user_api_token') + op.drop_table("user_api_token") diff --git a/src/wuttjamaican/db/alembic/versions/fc3a3bcaa069_init_with_settings_table.py b/src/wuttjamaican/db/alembic/versions/fc3a3bcaa069_init_with_settings_table.py index c7063fa..d3c56d7 100644 --- a/src/wuttjamaican/db/alembic/versions/fc3a3bcaa069_init_with_settings_table.py +++ b/src/wuttjamaican/db/alembic/versions/fc3a3bcaa069_init_with_settings_table.py @@ -1,10 +1,11 @@ """init with settings table Revision ID: fc3a3bcaa069 -Revises: +Revises: Create Date: 2024-07-10 20:33:41.273952 """ + from typing import Sequence, Union from alembic import op @@ -12,23 +13,24 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision: str = 'fc3a3bcaa069' +revision: str = "fc3a3bcaa069" down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = ('wutta',) +branch_labels: Union[str, Sequence[str], None] = ("wutta",) depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # setting - op.create_table('setting', - sa.Column('name', sa.String(length=255), nullable=False), - sa.Column('value', sa.Text(), nullable=True), - sa.PrimaryKeyConstraint('name') - ) + op.create_table( + "setting", + sa.Column("name", sa.String(length=255), nullable=False), + sa.Column("value", sa.Text(), nullable=True), + sa.PrimaryKeyConstraint("name"), + ) def downgrade() -> None: # setting - op.drop_table('setting') + op.drop_table("setting") diff --git a/src/wuttjamaican/db/conf.py b/src/wuttjamaican/db/conf.py index 769df3e..8753fa1 100644 --- a/src/wuttjamaican/db/conf.py +++ b/src/wuttjamaican/db/conf.py @@ -62,11 +62,11 @@ def get_engines(config, prefix): :returns: A dictionary of SQLAlchemy engines, with keys matching those found in config. """ - keys = config.get(f'{prefix}.keys', usedb=False) + keys = config.get(f"{prefix}.keys", usedb=False) if keys: keys = parse_list(keys) else: - keys = ['default'] + keys = ["default"] make_engine = config.get_engine_maker() @@ -75,11 +75,11 @@ def get_engines(config, prefix): for key in keys: key = key.strip() try: - engines[key] = make_engine(cfg, prefix=f'{key}.') + engines[key] = make_engine(cfg, prefix=f"{key}.") except KeyError: - if key == 'default': + if key == "default": try: - engines[key] = make_engine(cfg, prefix='sqlalchemy.') + engines[key] = make_engine(cfg, prefix="sqlalchemy.") except KeyError: pass return engines @@ -99,13 +99,10 @@ def get_setting(session, name): :returns: Setting value as string, or ``None``. """ sql = sa.text("select value from setting where name = :name") - return session.execute(sql, params={'name': name}).scalar() + return session.execute(sql, params={"name": name}).scalar() -def make_engine_from_config( - config_dict, - prefix='sqlalchemy.', - **kwargs): +def make_engine_from_config(config_dict, prefix="sqlalchemy.", **kwargs): """ Construct a new DB engine from configuration dict. @@ -141,14 +138,14 @@ def make_engine_from_config( config_dict = dict(config_dict) # convert 'poolclass' arg to actual class - key = f'{prefix}poolclass' - if key in config_dict and 'poolclass' not in kwargs: - kwargs['poolclass'] = load_object(config_dict.pop(key)) + key = f"{prefix}poolclass" + if key in config_dict and "poolclass" not in kwargs: + kwargs["poolclass"] = load_object(config_dict.pop(key)) # convert 'pool_pre_ping' arg to boolean - key = f'{prefix}pool_pre_ping' - if key in config_dict and 'pool_pre_ping' not in kwargs: - kwargs['pool_pre_ping'] = parse_bool(config_dict.pop(key)) + key = f"{prefix}pool_pre_ping" + if key in config_dict and "pool_pre_ping" not in kwargs: + kwargs["pool_pre_ping"] = parse_bool(config_dict.pop(key)) engine = sa.engine_from_config(config_dict, prefix, **kwargs) diff --git a/src/wuttjamaican/db/handler.py b/src/wuttjamaican/db/handler.py index 5d8d8af..3ca2b10 100644 --- a/src/wuttjamaican/db/handler.py +++ b/src/wuttjamaican/db/handler.py @@ -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): # pylint: disable=empty-docstring """ """ return bind.url.get_dialect().name @@ -59,7 +59,7 @@ class DatabaseHandler(GenericHandler): dialect = self.get_dialect(session.bind) # postgres uses "true" native sequence - if dialect == 'postgresql': + if dialect == "postgresql": sql = f"create sequence if not exists {key}_seq" session.execute(sa.text(sql)) sql = f"select nextval('{key}_seq')" @@ -69,8 +69,11 @@ class DatabaseHandler(GenericHandler): # otherwise use "magic" workaround engine = session.bind metadata = sa.MetaData() - table = sa.Table(f'_counter_{key}', metadata, - sa.Column('value', sa.Integer(), primary_key=True)) + table = sa.Table( + f"_counter_{key}", + metadata, + sa.Column("value", sa.Integer(), primary_key=True), + ) table.create(engine, checkfirst=True) with engine.begin() as cxn: result = cxn.execute(table.insert()) diff --git a/src/wuttjamaican/db/model/auth.py b/src/wuttjamaican/db/model/auth.py index 8ff9fc5..580e669 100644 --- a/src/wuttjamaican/db/model/auth.py +++ b/src/wuttjamaican/db/model/auth.py @@ -49,7 +49,7 @@ from wuttjamaican.db.util import uuid_column, uuid_fk_column from wuttjamaican.db.model.base import Base -class Role(Base): # pylint: disable=too-few-public-methods +class Role(Base): # pylint: disable=too-few-public-methods """ Represents an authentication role within the system; used for permission management. @@ -67,51 +67,65 @@ class Role(Base): # pylint: disable=too-few-public-methods See also :attr:`user_refs`. """ - __tablename__ = 'role' + + __tablename__ = "role" __versioned__ = {} uuid = uuid_column() - name = sa.Column(sa.String(length=100), nullable=False, unique=True, doc=""" + name = sa.Column( + sa.String(length=100), + nullable=False, + unique=True, + doc=""" Name for the role. Each role must have a name, which must be unique. - """) + """, + ) - notes = sa.Column(sa.Text(), nullable=True, doc=""" + notes = sa.Column( + sa.Text(), + nullable=True, + doc=""" Arbitrary notes for the role. - """) + """, + ) permission_refs = orm.relationship( - 'Permission', - back_populates='role', - cascade='all, delete-orphan', + "Permission", + back_populates="role", + cascade="all, delete-orphan", cascade_backrefs=False, doc=""" List of :class:`Permission` references for the role. See also :attr:`permissions`. - """) + """, + ) permissions = association_proxy( - 'permission_refs', 'permission', + "permission_refs", + "permission", creator=lambda p: Permission(permission=p), # TODO # getset_factory=getset_factory, ) user_refs = orm.relationship( - 'UserRole', - back_populates='role', - cascade='all, delete-orphan', + "UserRole", + back_populates="role", + cascade="all, delete-orphan", cascade_backrefs=False, doc=""" List of :class:`UserRole` instances belonging to the role. See also :attr:`users`. - """) + """, + ) users = association_proxy( - 'user_refs', 'user', + "user_refs", + "user", creator=lambda u: UserRole(user=u), # TODO # getset_factory=getset_factory, @@ -121,32 +135,38 @@ 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): # pylint: disable=too-few-public-methods """ Represents a permission granted to a role. """ - __tablename__ = 'permission' + + __tablename__ = "permission" __versioned__ = {} - role_uuid = uuid_fk_column('role.uuid', primary_key=True, nullable=False) + role_uuid = uuid_fk_column("role.uuid", primary_key=True, nullable=False) role = orm.relationship( Role, - back_populates='permission_refs', + back_populates="permission_refs", cascade_backrefs=False, doc=""" Reference to the :class:`Role` for which the permission is granted. - """) + """, + ) - permission = sa.Column(sa.String(length=254), primary_key=True, doc=""" + permission = sa.Column( + sa.String(length=254), + primary_key=True, + doc=""" Key (name) of the permission which is granted. - """) + """, + ) def __str__(self): return self.permission or "" -class User(Base): # pylint: disable=too-few-public-methods +class User(Base): # pylint: disable=too-few-public-methods """ Represents a user of the system. @@ -159,70 +179,93 @@ class User(Base): # pylint: disable=too-few-public-methods See also :attr:`role_refs`. """ - __tablename__ = 'user' + + __tablename__ = "user" __versioned__ = {} uuid = uuid_column() - username = sa.Column(sa.String(length=25), nullable=False, unique=True, doc=""" + username = sa.Column( + sa.String(length=25), + nullable=False, + unique=True, + doc=""" Account username. This is required and must be unique. - """) + """, + ) - password = sa.Column(sa.String(length=60), nullable=True, doc=""" + password = sa.Column( + sa.String(length=60), + nullable=True, + doc=""" Hashed password for login. (The raw password is not stored.) - """) + """, + ) - person_uuid = uuid_fk_column('person.uuid', nullable=True) + person_uuid = uuid_fk_column("person.uuid", nullable=True) person = orm.relationship( - 'Person', + "Person", # TODO: seems like this is not needed? # uselist=False, - back_populates='users', + back_populates="users", cascade_backrefs=False, doc=""" Reference to the :class:`~wuttjamaican.db.model.base.Person` whose user account this is. - """) + """, + ) - active = sa.Column(sa.Boolean(), nullable=False, default=True, doc=""" + active = sa.Column( + sa.Boolean(), + nullable=False, + default=True, + doc=""" Flag indicating whether the user account is "active" - it is ``True`` by default. The default auth logic will prevent login for "inactive" user accounts. - """) + """, + ) - prevent_edit = sa.Column(sa.Boolean(), nullable=True, doc=""" + prevent_edit = sa.Column( + sa.Boolean(), + nullable=True, + doc=""" If set, this user account can only be edited by root. User cannot change their own password. - """) + """, + ) role_refs = orm.relationship( - 'UserRole', - back_populates='user', - cascade='all, delete-orphan', + "UserRole", + back_populates="user", + cascade="all, delete-orphan", cascade_backrefs=False, doc=""" List of :class:`UserRole` instances belonging to the user. See also :attr:`roles`. - """) + """, + ) roles = association_proxy( - 'role_refs', 'role', + "role_refs", + "role", creator=lambda r: UserRole(role=r), # TODO # getset_factory=getset_factory, ) api_tokens = orm.relationship( - 'UserAPIToken', - back_populates='user', - order_by='UserAPIToken.created', - cascade='all, delete-orphan', + "UserAPIToken", + back_populates="user", + order_by="UserAPIToken.created", + cascade="all, delete-orphan", cascade_backrefs=False, doc=""" List of :class:`UserAPIToken` instances belonging to the user. - """) + """, + ) def __str__(self): if self.person: @@ -232,59 +275,72 @@ 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): # 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. """ - __tablename__ = 'user_x_role' + + __tablename__ = "user_x_role" __versioned__ = {} uuid = uuid_column() - user_uuid = uuid_fk_column('user.uuid', nullable=False) + user_uuid = uuid_fk_column("user.uuid", nullable=False) user = orm.relationship( User, - back_populates='role_refs', + back_populates="role_refs", cascade_backrefs=False, doc=""" Reference to the :class:`User` involved. - """) + """, + ) - role_uuid = uuid_fk_column('role.uuid', nullable=False) + role_uuid = uuid_fk_column("role.uuid", nullable=False) role = orm.relationship( Role, - back_populates='user_refs', + back_populates="user_refs", cascade_backrefs=False, doc=""" Reference to the :class:`Role` involved. - """) + """, + ) -class UserAPIToken(Base): # pylint: disable=too-few-public-methods +class UserAPIToken(Base): # pylint: disable=too-few-public-methods """ User authentication token for use with HTTP API """ - __tablename__ = 'user_api_token' + + __tablename__ = "user_api_token" uuid = uuid_column() - user_uuid = uuid_fk_column('user.uuid', nullable=False) + user_uuid = uuid_fk_column("user.uuid", nullable=False) user = orm.relationship( User, - back_populates='api_tokens', + back_populates="api_tokens", cascade_backrefs=False, doc=""" Reference to the :class:`User` whose token this is. - """) + """, + ) - description = sa.Column(sa.String(length=255), nullable=False, doc=""" + description = sa.Column( + sa.String(length=255), + nullable=False, + doc=""" Description of the token. - """) + """, + ) - token_string = sa.Column(sa.String(length=255), nullable=False, doc=""" + token_string = sa.Column( + sa.String(length=255), + nullable=False, + doc=""" Raw token string, to be used by API clients. - """) + """, + ) created = sa.Column( sa.DateTime(timezone=True), @@ -292,7 +348,8 @@ class UserAPIToken(Base): # pylint: disable=too-few-public-methods 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..db2a2a1 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): # pylint: disable=too-few-public-methods """ Base class for data models, from which :class:`Base` inherits. @@ -113,8 +113,8 @@ class WuttaModelBase(ModelBase): # pylint: disable=too-few-public-methods print(user.favorite_color) """ proxy = association_proxy( - extension, proxy_name or name, - creator=lambda value: cls(**{name: value})) + extension, proxy_name or name, creator=lambda value: cls(**{name: value}) + ) setattr(main_class, name, proxy) @@ -123,19 +123,29 @@ 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): # pylint: disable=too-few-public-methods """ Represents a :term:`config setting`. """ - __tablename__ = 'setting' - name = sa.Column(sa.String(length=255), primary_key=True, nullable=False, doc=""" + __tablename__ = "setting" + + name = sa.Column( + sa.String(length=255), + primary_key=True, + nullable=False, + doc=""" Unique name for the setting. - """) + """, + ) - value = sa.Column(sa.Text(), nullable=True, doc=""" + value = sa.Column( + sa.Text(), + nullable=True, + doc=""" String value for the setting. - """) + """, + ) def __str__(self): return self.name or "" @@ -153,36 +163,54 @@ class Person(Base): But this table could also be used as a basis for a Customer or Employee relationship etc. """ - __tablename__ = 'person' + + __tablename__ = "person" __versioned__ = {} uuid = uuid_column() - full_name = sa.Column(sa.String(length=100), nullable=False, doc=""" + full_name = sa.Column( + sa.String(length=100), + nullable=False, + doc=""" Full name for the person. Note that this is *required*. - """) + """, + ) - first_name = sa.Column(sa.String(length=50), nullable=True, doc=""" + first_name = sa.Column( + sa.String(length=50), + nullable=True, + doc=""" The person's first name. - """) + """, + ) - middle_name = sa.Column(sa.String(length=50), nullable=True, doc=""" + middle_name = sa.Column( + sa.String(length=50), + nullable=True, + doc=""" The person's middle name or initial. - """) + """, + ) - last_name = sa.Column(sa.String(length=50), nullable=True, doc=""" + last_name = sa.Column( + sa.String(length=50), + nullable=True, + doc=""" The person's last name. - """) + """, + ) users = orm.relationship( - 'User', - back_populates='person', + "User", + back_populates="person", cascade_backrefs=False, doc=""" List of :class:`~wuttjamaican.db.model.auth.User` accounts for the person. Typically there is only one user account per person, but technically multiple are supported. - """) + """, + ) def __str__(self): return self.full_name or "" diff --git a/src/wuttjamaican/db/model/batch.py b/src/wuttjamaican/db/model/batch.py index 0a24c8d..ad2ef3c 100644 --- a/src/wuttjamaican/db/model/batch.py +++ b/src/wuttjamaican/db/model/batch.py @@ -186,7 +186,7 @@ class BatchMixin: """ @declared_attr - def __table_args__(cls): # pylint: disable=no-self-argument + def __table_args__(cls): # pylint: disable=no-self-argument return cls.__default_table_args__() @classmethod @@ -196,12 +196,12 @@ class BatchMixin: @classmethod def __batch_table_args__(cls): return ( - sa.ForeignKeyConstraint(['created_by_uuid'], ['user.uuid']), - sa.ForeignKeyConstraint(['executed_by_uuid'], ['user.uuid']), + sa.ForeignKeyConstraint(["created_by_uuid"], ["user.uuid"]), + sa.ForeignKeyConstraint(["executed_by_uuid"], ["user.uuid"]), ) @declared_attr - def batch_type(cls): # pylint: disable=empty-docstring,no-self-argument + def batch_type(cls): # pylint: disable=empty-docstring,no-self-argument """ """ return cls.__tablename__ @@ -212,42 +212,44 @@ class BatchMixin: notes = sa.Column(sa.Text(), nullable=True) row_count = sa.Column(sa.Integer(), nullable=True, default=0) - STATUS_INCOMPLETE = 1 - STATUS_EXECUTABLE = 2 + STATUS_INCOMPLETE = 1 + STATUS_EXECUTABLE = 2 STATUS = { - STATUS_INCOMPLETE : "incomplete", - STATUS_EXECUTABLE : "executable", + STATUS_INCOMPLETE: "incomplete", + STATUS_EXECUTABLE: "executable", } status_code = sa.Column(sa.Integer(), nullable=True) status_text = sa.Column(sa.String(length=255), nullable=True) - created = sa.Column(sa.DateTime(timezone=True), nullable=False, - default=datetime.datetime.now) + created = sa.Column( + sa.DateTime(timezone=True), nullable=False, default=datetime.datetime.now + ) 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): # pylint: disable=empty-docstring,no-self-argument """ """ return orm.relationship( User, primaryjoin=lambda: User.uuid == cls.created_by_uuid, foreign_keys=lambda: [cls.created_by_uuid], - cascade_backrefs=False) - + cascade_backrefs=False, + ) executed = sa.Column(sa.DateTime(timezone=True), nullable=True) 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): # pylint: disable=empty-docstring,no-self-argument """ """ return orm.relationship( User, primaryjoin=lambda: User.uuid == cls.executed_by_uuid, foreign_keys=lambda: [cls.executed_by_uuid], - cascade_backrefs=False) + cascade_backrefs=False, + ) def __repr__(self): cls = self.__class__.__name__ @@ -266,11 +268,11 @@ class BatchMixin: print(batch.id_str) # => '00000042' """ if self.id: - return f'{self.id:08d}' + return f"{self.id:08d}" return None -class BatchRowMixin: # pylint: disable=too-few-public-methods +class BatchRowMixin: # pylint: disable=too-few-public-methods """ Mixin base class for :term:`data models ` which represent a :term:`batch row`. @@ -381,7 +383,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): # pylint: disable=no-self-argument return cls.__default_table_args__() @classmethod @@ -391,14 +393,12 @@ class BatchRowMixin: # pylint: disable=too-few-public-methods @classmethod def __batchrow_table_args__(cls): batch_table = cls.__batch_class__.__tablename__ - return ( - sa.ForeignKeyConstraint(['batch_uuid'], [f'{batch_table}.uuid']), - ) + return (sa.ForeignKeyConstraint(["batch_uuid"], [f"{batch_table}.uuid"]),) batch_uuid = sa.Column(UUID(), nullable=False) @declared_attr - def batch(cls): # pylint: disable=empty-docstring,no-self-argument + def batch(cls): # pylint: disable=empty-docstring,no-self-argument """ """ batch_class = cls.__batch_class__ row_class = cls @@ -409,16 +409,16 @@ class BatchRowMixin: # pylint: disable=too-few-public-methods batch_class.rows = orm.relationship( row_class, order_by=lambda: row_class.sequence, - collection_class=ordering_list('sequence', count_from=1), - cascade='all, delete-orphan', + collection_class=ordering_list("sequence", count_from=1), + cascade="all, delete-orphan", cascade_backrefs=False, - back_populates='batch') + back_populates="batch", + ) # now, here's the `BatchRow.batch` return orm.relationship( - batch_class, - back_populates='rows', - cascade_backrefs=False) + batch_class, back_populates="rows", cascade_backrefs=False + ) sequence = sa.Column(sa.Integer(), nullable=False) @@ -427,6 +427,9 @@ class BatchRowMixin: # pylint: disable=too-few-public-methods status_code = sa.Column(sa.Integer(), nullable=True) status_text = sa.Column(sa.String(length=255), nullable=True) - modified = sa.Column(sa.DateTime(timezone=True), nullable=True, - default=datetime.datetime.now, - onupdate=datetime.datetime.now) + modified = sa.Column( + sa.DateTime(timezone=True), + nullable=True, + default=datetime.datetime.now, + onupdate=datetime.datetime.now, + ) diff --git a/src/wuttjamaican/db/model/upgrades.py b/src/wuttjamaican/db/model/upgrades.py index 010e26e..50c865f 100644 --- a/src/wuttjamaican/db/model/upgrades.py +++ b/src/wuttjamaican/db/model/upgrades.py @@ -35,63 +35,95 @@ 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): # pylint: disable=too-few-public-methods """ Represents an app upgrade. """ - __tablename__ = 'upgrade' + + __tablename__ = "upgrade" uuid = uuid_column(UUID(), default=make_true_uuid) - created = sa.Column(sa.DateTime(timezone=True), nullable=False, - default=datetime.datetime.now, doc=""" + created = sa.Column( + sa.DateTime(timezone=True), + nullable=False, + default=datetime.datetime.now, + doc=""" When the upgrade record was created. - """) + """, + ) - created_by_uuid = uuid_fk_column('user.uuid', nullable=False) + created_by_uuid = uuid_fk_column("user.uuid", nullable=False) created_by = orm.relationship( - 'User', + "User", foreign_keys=[created_by_uuid], cascade_backrefs=False, doc=""" :class:`~wuttjamaican.db.model.auth.User` who created the upgrade record. - """) + """, + ) - description = sa.Column(sa.String(length=255), nullable=False, doc=""" + description = sa.Column( + sa.String(length=255), + nullable=False, + doc=""" Basic (identifying) description for the upgrade. - """) + """, + ) - notes = sa.Column(sa.Text(), nullable=True, doc=""" + notes = sa.Column( + sa.Text(), + nullable=True, + doc=""" Notes for the upgrade. - """) + """, + ) - executing = sa.Column(sa.Boolean(), nullable=False, default=False, doc=""" + executing = sa.Column( + sa.Boolean(), + nullable=False, + default=False, + doc=""" Whether or not the upgrade is currently being performed. - """) + """, + ) - status = sa.Column(sa.Enum(UpgradeStatus), nullable=False, doc=""" + status = sa.Column( + sa.Enum(UpgradeStatus), + nullable=False, + doc=""" Current status for the upgrade. This field uses an enum, :class:`~wuttjamaican.enum.UpgradeStatus`. - """) + """, + ) - executed = sa.Column(sa.DateTime(timezone=True), nullable=True, doc=""" + executed = sa.Column( + sa.DateTime(timezone=True), + nullable=True, + doc=""" When the upgrade was executed. - """) + """, + ) - executed_by_uuid = uuid_fk_column('user.uuid', nullable=True) + executed_by_uuid = uuid_fk_column("user.uuid", nullable=True) executed_by = orm.relationship( - 'User', + "User", foreign_keys=[executed_by_uuid], cascade_backrefs=False, doc=""" :class:`~wuttjamaican.db.model.auth.User` who executed the upgrade. - """) + """, + ) - exit_code = sa.Column(sa.Integer(), nullable=True, doc=""" + exit_code = sa.Column( + sa.Integer(), + nullable=True, + doc=""" Exit code for the upgrade execution process, if applicable. - """) + """, + ) def __str__(self): return str(self.description or "") diff --git a/src/wuttjamaican/db/sess.py b/src/wuttjamaican/db/sess.py index 9033832..be26b8f 100644 --- a/src/wuttjamaican/db/sess.py +++ b/src/wuttjamaican/db/sess.py @@ -38,7 +38,7 @@ from sqlalchemy import orm Session = orm.sessionmaker() -class short_session: # pylint: disable=invalid-name +class short_session: # pylint: disable=invalid-name """ Context manager for a short-lived database session. diff --git a/src/wuttjamaican/db/util.py b/src/wuttjamaican/db/util.py index 24e508f..16c6aa1 100644 --- a/src/wuttjamaican/db/util.py +++ b/src/wuttjamaican/db/util.py @@ -38,28 +38,27 @@ from wuttjamaican.util import make_true_uuid # nb. this convention comes from upstream docs # https://docs.sqlalchemy.org/en/14/core/constraints.html#constraint-naming-conventions naming_convention = { - 'ix': 'ix_%(column_0_label)s', - 'uq': 'uq_%(table_name)s_%(column_0_name)s', - 'ck': 'ck_%(table_name)s_%(constraint_name)s', - 'fk': 'fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s', - 'pk': 'pk_%(table_name)s', + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s", } SA2 = True -if Version(version('SQLAlchemy')) < Version('2'): # pragma: no cover +if Version(version("SQLAlchemy")) < Version("2"): # pragma: no cover SA2 = False -class ModelBase: # pylint: disable=empty-docstring +class ModelBase: # pylint: disable=empty-docstring """ """ def __iter__(self): # nb. we override this to allow for `dict(self)` state = sa.inspect(self) fields = [attr.key for attr in state.attrs] - return iter([(field, getattr(self, field)) - for field in fields]) + return iter([(field, getattr(self, field)) for field in fields]) def __getitem__(self, key): # nb. we override this to allow for `x = self['field']` @@ -69,7 +68,9 @@ 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 +): # pylint: disable=abstract-method,too-many-ancestors """ Platform-independent UUID type. @@ -80,17 +81,18 @@ class UUID(sa.types.TypeDecorator): # pylint: disable=abstract-method,too-many-a documentation `_. """ + 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): # pylint: disable=empty-docstring """ """ if dialect.name == "postgresql": return dialect.type_descriptor(PGUUID()) 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): # pylint: disable=empty-docstring """ """ if value is None: return value @@ -104,7 +106,9 @@ class UUID(sa.types.TypeDecorator): # pylint: disable=abstract-method,too-many-a # 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,empty-docstring """ """ if value is None: return value @@ -119,9 +123,9 @@ def uuid_column(*args, **kwargs): """ if not args: args = (UUID(),) - kwargs.setdefault('primary_key', True) - kwargs.setdefault('nullable', False) - kwargs.setdefault('default', make_true_uuid) + kwargs.setdefault("primary_key", True) + kwargs.setdefault("nullable", False) + kwargs.setdefault("default", make_true_uuid) return sa.Column(*args, **kwargs) @@ -149,8 +153,7 @@ 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 = {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 2b1d865..2966f3c 100644 --- a/src/wuttjamaican/email.py +++ b/src/wuttjamaican/email.py @@ -40,7 +40,7 @@ from wuttjamaican.util import resource_path log = logging.getLogger(__name__) -class EmailSetting: # pylint: disable=too-few-public-methods +class EmailSetting: # pylint: disable=too-few-public-methods """ Base class for all :term:`email settings `. @@ -93,6 +93,7 @@ class EmailSetting: # pylint: disable=too-few-public-methods rendered with the email context. But in most cases that feature can be ignored, and this will be a simple string. """ + default_subject = None def __init__(self, config): @@ -109,7 +110,7 @@ class EmailSetting: # pylint: disable=too-few-public-methods return {} -class Message: # pylint: disable=too-many-instance-attributes +class Message: # pylint: disable=too-many-instance-attributes """ Represents an email message to be sent. @@ -172,17 +173,17 @@ class Message: # pylint: disable=too-many-instance-attributes """ def __init__( - self, - key=None, - sender=None, - subject=None, - to=None, - cc=None, - bcc=None, - replyto=None, - txt_body=None, - html_body=None, - attachments=None, + self, + key=None, + sender=None, + subject=None, + to=None, + cc=None, + bcc=None, + replyto=None, + txt_body=None, + html_body=None, + attachments=None, ): self.key = key self.sender = sender @@ -195,7 +196,7 @@ class Message: # pylint: disable=too-many-instance-attributes self.html_body = html_body self.attachments = attachments or [] - def get_recips(self, value): # pylint: disable=empty-docstring + def get_recips(self, value): # pylint: disable=empty-docstring """ """ if value: if isinstance(value, str): @@ -216,15 +217,15 @@ class Message: # pylint: disable=too-many-instance-attributes msg = None if self.txt_body and self.html_body: - txt = MIMEText(self.txt_body, _charset='utf_8') - html = MIMEText(self.html_body, _subtype='html', _charset='utf_8') - msg = MIMEMultipart(_subtype='alternative', _subparts=[txt, html]) + txt = MIMEText(self.txt_body, _charset="utf_8") + html = MIMEText(self.html_body, _subtype="html", _charset="utf_8") + msg = MIMEMultipart(_subtype="alternative", _subparts=[txt, html]) elif self.txt_body: - msg = MIMEText(self.txt_body, _charset='utf_8') + msg = MIMEText(self.txt_body, _charset="utf_8") elif self.html_body: - msg = MIMEText(self.html_body, 'html', _charset='utf_8') + msg = MIMEText(self.html_body, "html", _charset="utf_8") if not msg: raise ValueError("message has no body parts") @@ -232,27 +233,29 @@ class Message: # pylint: disable=too-many-instance-attributes if self.attachments: for attachment in self.attachments: if isinstance(attachment, str): - raise ValueError("must specify valid MIME attachments; this class cannot " - "auto-create them from file path etc.") - msg = MIMEMultipart(_subtype='mixed', _subparts=[msg] + self.attachments) + raise ValueError( + "must specify valid MIME attachments; this class cannot " + "auto-create them from file path etc." + ) + msg = MIMEMultipart(_subtype="mixed", _subparts=[msg] + self.attachments) - msg['Subject'] = self.subject - msg['From'] = self.sender + msg["Subject"] = self.subject + msg["From"] = self.sender for addr in self.to: - msg['To'] = addr + msg["To"] = addr for addr in self.cc: - msg['Cc'] = addr + msg["Cc"] = addr for addr in self.bcc: - msg['Bcc'] = addr + msg["Bcc"] = addr if self.replyto: - msg.add_header('Reply-To', self.replyto) + msg.add_header("Reply-To", self.replyto) return msg.as_string() -class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods +class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods """ Base class and default implementation for the :term:`email handler`. @@ -272,13 +275,13 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods super().__init__(*args, **kwargs) # prefer configured list of template lookup paths, if set - templates = self.config.get_list(f'{self.config.appname}.email.templates') + 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(): - if hasattr(provider, 'email_templates'): + if hasattr(provider, "email_templates"): templates = provider.email_templates if isinstance(templates, str): templates = [templates] @@ -292,10 +295,12 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods # will use these lookups from now on self.txt_templates = TemplateLookup(directories=templates) - self.html_templates = TemplateLookup(directories=templates, - # nb. escape HTML special chars - # TODO: sounds great but i forget why? - default_filters=['h']) + self.html_templates = TemplateLookup( + directories=templates, + # nb. escape HTML special chars + # TODO: sounds great but i forget why? + default_filters=["h"], + ) def get_email_modules(self): """ @@ -309,7 +314,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods :meth:`~wuttjamaican.app.GenericHandler.get_provider_modules()` under the hood, for ``email`` module type. """ - return self.get_provider_modules('email') + return self.get_provider_modules("email") def get_email_settings(self): """ @@ -319,14 +324,16 @@ 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'): + if not hasattr(self, "_email_settings"): self._email_settings = {} for module in self.get_email_modules(): for name in dir(module): obj = getattr(module, name) - if (isinstance(obj, type) + if ( + isinstance(obj, type) and obj is not EmailSetting - and issubclass(obj, EmailSetting)): + and issubclass(obj, EmailSetting) + ): self._email_settings[obj.__name__] = obj return self._email_settings @@ -400,21 +407,23 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods * :meth:`get_auto_html_body()` """ context = context or {} - kwargs['key'] = key - if 'sender' not in kwargs: - kwargs['sender'] = self.get_auto_sender(key) - if 'subject' not in kwargs: - kwargs['subject'] = self.get_auto_subject(key, context, default=default_subject) - if 'to' not in kwargs: - kwargs['to'] = self.get_auto_to(key) - if 'cc' not in kwargs: - kwargs['cc'] = self.get_auto_cc(key) - if 'bcc' not in kwargs: - kwargs['bcc'] = self.get_auto_bcc(key) - if 'txt_body' not in kwargs: - kwargs['txt_body'] = self.get_auto_txt_body(key, context) - if 'html_body' not in kwargs: - kwargs['html_body'] = self.get_auto_html_body(key, context) + kwargs["key"] = key + if "sender" not in kwargs: + kwargs["sender"] = self.get_auto_sender(key) + if "subject" not in kwargs: + kwargs["subject"] = self.get_auto_subject( + key, context, default=default_subject + ) + if "to" not in kwargs: + kwargs["to"] = self.get_auto_to(key) + if "cc" not in kwargs: + kwargs["cc"] = self.get_auto_cc(key) + if "bcc" not in kwargs: + kwargs["bcc"] = self.get_auto_bcc(key) + if "txt_body" not in kwargs: + kwargs["txt_body"] = self.get_auto_txt_body(key, context) + if "html_body" not in kwargs: + kwargs["html_body"] = self.get_auto_html_body(key, context) return self.make_message(**kwargs) def get_auto_sender(self, key): @@ -424,13 +433,14 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods message, as determined by config. """ # prefer configured sender specific to key - sender = self.config.get(f'{self.config.appname}.email.{key}.sender') + sender = self.config.get(f"{self.config.appname}.email.{key}.sender") if sender: return sender # fall back to global default - return self.config.get(f'{self.config.appname}.email.default.sender', - default='root@localhost') + return self.config.get( + f"{self.config.appname}.email.default.sender", default="root@localhost" + ) def get_auto_replyto(self, key): """ @@ -438,14 +448,16 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods address for a message, as determined by config. """ # prefer configured replyto specific to key - replyto = self.config.get(f'{self.config.appname}.email.{key}.replyto') + replyto = self.config.get(f"{self.config.appname}.email.{key}.replyto") if replyto: return replyto # fall back to global default, if present - return self.config.get(f'{self.config.appname}.email.default.replyto') + return self.config.get(f"{self.config.appname}.email.default.replyto") - def get_auto_subject(self, key, context=None, rendered=True, setting=None, default=None): + def get_auto_subject( + self, key, context=None, rendered=True, setting=None, default=None + ): """ Returns automatic :attr:`~wuttjamaican.email.Message.subject` line for a message, as determined by config. @@ -501,7 +513,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods :returns: Final subject template, as raw text. """ # prefer configured subject specific to key - template = self.config.get(f'{self.config.appname}.email.{key}.subject') + template = self.config.get(f"{self.config.appname}.email.{key}.subject") if template: return template @@ -516,44 +528,47 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods return setting.default_subject # fall back to global default - return self.config.get(f'{self.config.appname}.email.default.subject', - default=self.universal_subject) + return self.config.get( + f"{self.config.appname}.email.default.subject", + default=self.universal_subject, + ) def get_auto_to(self, key): """ Returns automatic :attr:`~wuttjamaican.email.Message.to` recipient address(es) for a message, as determined by config. """ - return self.get_auto_recips(key, 'to') + return self.get_auto_recips(key, "to") def get_auto_cc(self, key): """ Returns automatic :attr:`~wuttjamaican.email.Message.cc` recipient address(es) for a message, as determined by config. """ - return self.get_auto_recips(key, 'cc') + return self.get_auto_recips(key, "cc") def get_auto_bcc(self, key): """ Returns automatic :attr:`~wuttjamaican.email.Message.bcc` recipient address(es) for a message, as determined by config. """ - return self.get_auto_recips(key, 'bcc') + 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): # pylint: disable=empty-docstring """ """ typ = typ.lower() - if typ not in ('to', 'cc', 'bcc'): + if typ not in ("to", "cc", "bcc"): raise ValueError("requested type not supported") # prefer configured recips specific to key - recips = self.config.get_list(f'{self.config.appname}.email.{key}.{typ}') + recips = self.config.get_list(f"{self.config.appname}.email.{key}.{typ}") if recips: return recips # fall back to global default - return self.config.get_list(f'{self.config.appname}.email.default.{typ}', - default=[]) + return self.config.get_list( + f"{self.config.appname}.email.default.{typ}", default=[] + ) def get_auto_txt_body(self, key, context=None): """ @@ -561,7 +576,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods content for a message, as determined by config. This renders a template with the given context. """ - template = self.get_auto_body_template(key, 'txt') + template = self.get_auto_body_template(key, "txt") if template: context = context or {} return template.render(**context) @@ -574,24 +589,24 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods message, as determined by config. This renders a template with the given context. """ - template = self.get_auto_body_template(key, 'html') + template = self.get_auto_body_template(key, "html") if template: context = context or {} 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): # pylint: disable=empty-docstring """ """ mode = mode.lower() - if mode == 'txt': + if mode == "txt": templates = self.txt_templates - elif mode == 'html': + elif mode == "html": templates = self.html_templates else: raise ValueError("requested mode not supported") try: - return templates.get_template(f'{key}.{mode}.mako') + return templates.get_template(f"{key}.{mode}.mako") except TopLevelLookupException: pass return None @@ -604,7 +619,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods :returns: Notes as string if found; otherwise ``None``. """ - return self.config.get(f'{self.config.appname}.email.{key}.notes') + return self.config.get(f"{self.config.appname}.email.{key}.notes") def is_enabled(self, key): """ @@ -646,8 +661,8 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods :returns: True if this email type is enabled, otherwise false. """ - for k in set([key, 'default']): - enabled = self.config.get_bool(f'{self.config.appname}.email.{k}.enabled') + for k in set([key, "default"]): + enabled = self.config.get_bool(f"{self.config.appname}.email.{k}.enabled") if enabled is not None: return enabled return True @@ -702,9 +717,11 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods message = message.as_string() # get smtp info - server = self.config.get(f'{self.config.appname}.mail.smtp.server', default='localhost') - username = self.config.get(f'{self.config.appname}.mail.smtp.username') - password = self.config.get(f'{self.config.appname}.mail.smtp.password') + server = self.config.get( + f"{self.config.appname}.mail.smtp.server", default="localhost" + ) + username = self.config.get(f"{self.config.appname}.mail.smtp.username") + password = self.config.get(f"{self.config.appname}.mail.smtp.password") # make sure sending is enabled log.debug("sending email from %s; to %s", sender, recips) @@ -735,10 +752,13 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods Note that it is OFF by default. """ - return self.config.get_bool(f'{self.config.appname}.mail.send_emails', - default=False) + return self.config.get_bool( + f"{self.config.appname}.mail.send_emails", default=False + ) - def send_email(self, key=None, context=None, message=None, sender=None, recips=None, **kwargs): + def send_email( + self, key=None, context=None, message=None, sender=None, recips=None, **kwargs + ): """ Send an email message. @@ -807,11 +827,13 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods # auto-create message from key + context if sender: - kwargs['sender'] = sender + kwargs["sender"] = sender message = self.make_auto_message(key, context or {}, **kwargs) if not (message.txt_body or message.html_body): - raise RuntimeError(f"message (type: {key}) has no body - " - "perhaps template file not found?") + raise RuntimeError( + f"message (type: {key}) has no body - " + "perhaps template file not found?" + ) if not (message.txt_body or message.html_body): if key: diff --git a/src/wuttjamaican/enum.py b/src/wuttjamaican/enum.py index 3265745..aa85bb8 100644 --- a/src/wuttjamaican/enum.py +++ b/src/wuttjamaican/enum.py @@ -32,7 +32,8 @@ class UpgradeStatus(Enum): Enum values for :attr:`wuttjamaican.db.model.upgrades.Upgrade.status`. """ - PENDING = 'pending' - EXECUTING = 'executing' - SUCCESS = 'success' - FAILURE = 'failure' + + PENDING = "pending" + EXECUTING = "executing" + SUCCESS = "success" + FAILURE = "failure" diff --git a/src/wuttjamaican/install.py b/src/wuttjamaican/install.py index 61be2d3..d0af8b1 100644 --- a/src/wuttjamaican/install.py +++ b/src/wuttjamaican/install.py @@ -80,7 +80,8 @@ class InstallHandler(GenericHandler): Egg name for the app. If not specified one will be guessed. """ - pkg_name = 'poser' + + pkg_name = "poser" app_title = None pypi_name = None egg_name = None @@ -97,7 +98,7 @@ class InstallHandler(GenericHandler): if not self.pypi_name: self.pypi_name = self.app_title if not self.egg_name: - self.egg_name = self.pypi_name.replace('-', '_') + self.egg_name = self.pypi_name.replace("-", "_") def run(self): """ @@ -118,11 +119,13 @@ class InstallHandler(GenericHandler): self.require_prompt_toolkit() paths = [ - self.app.resource_path('wuttjamaican:templates/install'), + self.app.resource_path("wuttjamaican:templates/install"), ] try: - paths.insert(0, self.app.resource_path(f'{self.pkg_name}:templates/install')) + paths.insert( + 0, self.app.resource_path(f"{self.pkg_name}:templates/install") + ) except (TypeError, ModuleNotFoundError): pass @@ -143,8 +146,10 @@ class InstallHandler(GenericHandler): """ 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): @@ -159,7 +164,7 @@ class InstallHandler(GenericHandler): This is normally called by :meth:`run()`. """ # appdir must not yet exist - appdir = os.path.join(sys.prefix, 'app') + appdir = os.path.join(sys.prefix, "app") if os.path.exists(appdir): self.rprint(f"\n\t[bold red]appdir already exists:[/bold red] {appdir}\n") sys.exit(2) @@ -185,7 +190,7 @@ class InstallHandler(GenericHandler): self.make_appdir(context) # install db schema if user likes - self.schema_installed = self.install_db_schema(dbinfo['dburl']) + self.schema_installed = self.install_db_schema(dbinfo["dburl"]) def get_dbinfo(self): """ @@ -199,27 +204,29 @@ class InstallHandler(GenericHandler): dbinfo = {} # get db info - dbinfo['dbtype'] = self.prompt_generic('db type', 'postgresql') - dbinfo['dbhost'] = self.prompt_generic('db host', 'localhost') - default_port = '3306' if dbinfo['dbtype'] == 'mysql' else '5432' - dbinfo['dbport'] = self.prompt_generic('db port', default_port) - dbinfo['dbname'] = self.prompt_generic('db name', self.pkg_name) - dbinfo['dbuser'] = self.prompt_generic('db user', self.pkg_name) + dbinfo["dbtype"] = self.prompt_generic("db type", "postgresql") + dbinfo["dbhost"] = self.prompt_generic("db host", "localhost") + default_port = "3306" if dbinfo["dbtype"] == "mysql" else "5432" + dbinfo["dbport"] = self.prompt_generic("db port", default_port) + dbinfo["dbname"] = self.prompt_generic("db name", self.pkg_name) + dbinfo["dbuser"] = self.prompt_generic("db user", self.pkg_name) # get db password - dbinfo['dbpass'] = None - while not dbinfo['dbpass']: - dbinfo['dbpass'] = self.prompt_generic('db pass', is_password=True) + dbinfo["dbpass"] = None + while not dbinfo["dbpass"]: + dbinfo["dbpass"] = self.prompt_generic("db pass", is_password=True) # test db connection - self.rprint("\n\ttesting db connection... ", end='') - dbinfo['dburl'] = self.make_db_url(dbinfo['dbtype'], - dbinfo['dbhost'], - dbinfo['dbport'], - dbinfo['dbname'], - dbinfo['dbuser'], - dbinfo['dbpass']) - error = self.test_db_connection(dbinfo['dburl']) + self.rprint("\n\ttesting db connection... ", end="") + dbinfo["dburl"] = self.make_db_url( + dbinfo["dbtype"], + dbinfo["dbhost"], + dbinfo["dbport"], + dbinfo["dbname"], + dbinfo["dbuser"], + dbinfo["dbpass"], + ) + error = self.test_db_connection(dbinfo["dburl"]) if error: self.rprint("[bold red]cannot connect![/bold red] ..error was:") self.rprint(f"\n{error}") @@ -229,23 +236,27 @@ class InstallHandler(GenericHandler): 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 + ): # pylint: disable=empty-docstring """ """ from sqlalchemy.engine import URL - if dbtype == 'mysql': - drivername = 'mysql+mysqlconnector' + if dbtype == "mysql": + drivername = "mysql+mysqlconnector" else: - drivername = 'postgresql+psycopg2' + drivername = "postgresql+psycopg2" - return URL.create(drivername=drivername, - username=dbuser, - password=dbpass, - host=dbhost, - port=dbport, - database=dbname) + return URL.create( + drivername=drivername, + username=dbuser, + password=dbpass, + host=dbhost, + port=dbport, + database=dbname, + ) - def test_db_connection(self, url): # pylint: disable=empty-docstring + def test_db_connection(self, url): # pylint: disable=empty-docstring """ """ import sqlalchemy as sa @@ -254,8 +265,8 @@ class InstallHandler(GenericHandler): # check for random table; does not matter if it exists, we # 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 + sa.inspect(engine).has_table("whatever") + except Exception as error: # pylint: disable=broad-exception-caught return str(error) return None @@ -287,16 +298,16 @@ class InstallHandler(GenericHandler): * ``db_url`` - value from ``dbinfo['dburl']`` """ envname = os.path.basename(sys.prefix) - appdir = os.path.join(sys.prefix, 'app') + appdir = os.path.join(sys.prefix, "app") context = { - 'envdir': sys.prefix, - 'envname': envname, - 'pkg_name': self.pkg_name, - 'app_title': self.app_title, - 'pypi_name': self.pypi_name, - 'appdir': appdir, - 'db_url': dbinfo['dburl'], - 'egg_name': self.egg_name, + "envdir": sys.prefix, + "envname": envname, + "pkg_name": self.pkg_name, + "app_title": self.app_title, + "pypi_name": self.pypi_name, + "appdir": appdir, + "db_url": dbinfo["dburl"], + "egg_name": self.egg_name, } context.update(kwargs) return context @@ -335,38 +346,36 @@ class InstallHandler(GenericHandler): # but then we also generate some files... # wutta.conf - self.make_config_file('wutta.conf.mako', - os.path.join(appdir, 'wutta.conf'), - **context) + self.make_config_file( + "wutta.conf.mako", os.path.join(appdir, "wutta.conf"), **context + ) # web.conf web_context = dict(context) - web_context.setdefault('beaker_key', context.get('pkg_name', 'poser')) - web_context.setdefault('beaker_secret', 'TODO_YOU_SHOULD_CHANGE_THIS') - web_context.setdefault('pyramid_host', '0.0.0.0') - web_context.setdefault('pyramid_port', '9080') - self.make_config_file('web.conf.mako', - os.path.join(appdir, 'web.conf'), - **web_context) + web_context.setdefault("beaker_key", context.get("pkg_name", "poser")) + web_context.setdefault("beaker_secret", "TODO_YOU_SHOULD_CHANGE_THIS") + web_context.setdefault("pyramid_host", "0.0.0.0") + web_context.setdefault("pyramid_port", "9080") + self.make_config_file( + "web.conf.mako", os.path.join(appdir, "web.conf"), **web_context + ) # upgrade.sh - template = self.templates.get_template('upgrade.sh.mako') - output_path = os.path.join(appdir, 'upgrade.sh') - self.render_mako_template(template, context, - output_path=output_path) - os.chmod(output_path, stat.S_IRWXU - | stat.S_IRGRP - | stat.S_IXGRP - | stat.S_IROTH - | stat.S_IXOTH) + template = self.templates.get_template("upgrade.sh.mako") + output_path = os.path.join(appdir, "upgrade.sh") + self.render_mako_template(template, context, output_path=output_path) + os.chmod( + output_path, + stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH, + ) self.rprint(f"\n\tappdir created at: [bold green]{appdir}[/bold green]") def render_mako_template( - self, - template, - context, - output_path=None, + self, + template, + context, + output_path=None, ): """ Convenience wrapper around @@ -384,8 +393,7 @@ class InstallHandler(GenericHandler): if isinstance(template, str): template = self.templates.get_template(template) - return self.app.render_mako_template(template, context, - output_path=output_path) + return self.app.render_mako_template(template, context, output_path=output_path) def make_config_file(self, template, output_path, **kwargs): """ @@ -413,14 +421,13 @@ class InstallHandler(GenericHandler): Once it does that it calls :meth:`render_mako_template()`. """ context = { - 'app_title': self.app.get_title(), - 'appdir': self.app.get_appdir(), - 'db_url': 'postresql://user:pass@localhost/poser', - 'os': os, + "app_title": self.app.get_title(), + "appdir": self.app.get_appdir(), + "db_url": "postresql://user:pass@localhost/poser", + "os": os, } context.update(kwargs) - self.render_mako_template(template, context, - output_path=output_path) + self.render_mako_template(template, context, output_path=output_path) return output_path def install_db_schema(self, db_url, appdir=None): @@ -444,13 +451,19 @@ class InstallHandler(GenericHandler): # install db schema appdir = appdir or self.app.get_appdir() - cmd = [os.path.join(sys.prefix, 'bin', 'alembic'), - '-c', os.path.join(appdir, 'wutta.conf'), - 'upgrade', 'heads'] + cmd = [ + os.path.join(sys.prefix, "bin", "alembic"), + "-c", + os.path.join(appdir, "wutta.conf"), + "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: " + f"[bold green]{obfuscate_url_pw(db_url)}[/bold green]" + ) return True def show_goodbye(self): @@ -472,19 +485,22 @@ class InstallHandler(GenericHandler): # console utility functions ############################## - def require_prompt_toolkit(self, answer=None): # pylint: disable=empty-docstring + 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 except ImportError: - value = answer or input("\nprompt_toolkit is not installed. shall i install it? [Yn] ") + value = answer or input( + "\nprompt_toolkit is not installed. shall i install it? [Yn] " + ) value = value.strip() if value and not self.config.parse_bool(value): sys.stderr.write("prompt_toolkit is required; aborting\n") sys.exit(1) - subprocess.check_call([sys.executable, '-m', 'pip', - 'install', 'prompt_toolkit']) + subprocess.check_call( + [sys.executable, "-m", "pip", "install", "prompt_toolkit"] + ) # nb. this should now succeed import prompt_toolkit @@ -495,23 +511,25 @@ class InstallHandler(GenericHandler): """ rich.print(*args, **kwargs) - def get_prompt_style(self): # pylint: disable=empty-docstring + def get_prompt_style(self): # pylint: disable=empty-docstring """ """ from prompt_toolkit.styles import Style # message formatting styles - return Style.from_dict({ - '': '', - 'bold': 'bold', - }) + return Style.from_dict( + { + "": "", + "bold": "bold", + } + ) def prompt_generic( - self, - info, - default=None, - is_password=False, - is_bool=False, - required=False, + self, + info, + default=None, + is_password=False, + is_bool=False, + required=False, ): """ Prompt the user to get their input. @@ -540,39 +558,42 @@ class InstallHandler(GenericHandler): # build prompt message message = [ - ('', '\n'), - ('class:bold', info), + ("", "\n"), + ("class:bold", info), ] if default is not None: if is_bool: - message.append(('', f' [{"Y" if default else "N"}]: ')) + message.append(("", f' [{"Y" if default else "N"}]: ')) else: - message.append(('', f' [{default}]: ')) + message.append(("", f" [{default}]: ")) else: - message.append(('', ': ')) + message.append(("", ": ")) # prompt user for input style = self.get_prompt_style() try: text = prompt(message, style=style, is_password=is_password) except (KeyboardInterrupt, EOFError): - self.rprint("\n\t[bold yellow]operation canceled by user[/bold yellow]\n", - file=sys.stderr) + self.rprint( + "\n\t[bold yellow]operation canceled by user[/bold yellow]\n", + file=sys.stderr, + ) sys.exit(1) if is_bool: - if text == '': + if text == "": return default - if text.upper() == 'Y': + if text.upper() == "Y": return True - if 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) if required and not text and not default: - return self.prompt_generic(info, default, is_password=is_password, - required=True) + return self.prompt_generic( + info, default, is_password=is_password, required=True + ) return text or default diff --git a/src/wuttjamaican/people.py b/src/wuttjamaican/people.py index 9513b1b..3474183 100644 --- a/src/wuttjamaican/people.py +++ b/src/wuttjamaican/people.py @@ -54,12 +54,14 @@ class PeopleHandler(GenericHandler): """ model = self.app.model - if 'full_name' not in kwargs: - full_name = self.app.make_full_name(kwargs.get('first_name'), - kwargs.get('middle_name'), - kwargs.get('last_name')) + if "full_name" not in kwargs: + full_name = self.app.make_full_name( + kwargs.get("first_name"), + kwargs.get("middle_name"), + kwargs.get("last_name"), + ) if full_name: - kwargs['full_name'] = full_name + kwargs["full_name"] = full_name return model.Person(**kwargs) diff --git a/src/wuttjamaican/problems.py b/src/wuttjamaican/problems.py index 3394c23..c312513 100644 --- a/src/wuttjamaican/problems.py +++ b/src/wuttjamaican/problems.py @@ -100,7 +100,7 @@ class ProblemCheck: """ return [] - def get_email_context(self, problems, **kwargs): # pylint: disable=unused-argument + def get_email_context(self, problems, **kwargs): # pylint: disable=unused-argument """ This can be used to add extra context for a specific check's report email template. @@ -149,15 +149,18 @@ class ProblemHandler(GenericHandler): :returns: List of :class:`ProblemCheck` classes. """ checks = [] - modules = self.config.get_list(f'{self.config.appname}.problems.modules', - default=['wuttjamaican.problems']) + modules = self.config.get_list( + f"{self.config.appname}.problems.modules", default=["wuttjamaican.problems"] + ) for module_path in modules: module = importlib.import_module(module_path) for name in dir(module): obj = getattr(module, name) - if (isinstance(obj, type) and - issubclass(obj, ProblemCheck) and - obj is not ProblemCheck): + if ( + isinstance(obj, type) + and issubclass(obj, ProblemCheck) + and obj is not ProblemCheck + ): checks.append(obj) return checks @@ -224,8 +227,8 @@ class ProblemHandler(GenericHandler): :returns: ``True`` if enabled; ``False`` if not. """ - key = f'{check.system_key}.{check.problem_key}' - enabled = self.config.get_bool(f'{self.config.appname}.problems.{key}.enabled') + key = f"{check.system_key}.{check.problem_key}" + enabled = self.config.get_bool(f"{self.config.appname}.problems.{key}.enabled") if enabled is not None: return enabled return True @@ -243,8 +246,10 @@ class ProblemHandler(GenericHandler): :returns: ``True`` if check should run; ``False`` if not. """ - key = f'{check.system_key}.{check.problem_key}' - enabled = self.config.get_bool(f'{self.config.appname}.problems.{key}.day{weekday}') + key = f"{check.system_key}.{check.problem_key}" + enabled = self.config.get_bool( + f"{self.config.appname}.problems.{key}.day{weekday}" + ) if enabled is not None: return enabled return True @@ -302,7 +307,7 @@ class ProblemHandler(GenericHandler): :param force: If true, run the check regardless of whether it is configured to run. """ - key = f'{check.system_key}.{check.problem_key}' + key = f"{check.system_key}.{check.problem_key}" log.info("running problem check: %s", key) if not self.is_enabled(check): @@ -312,8 +317,11 @@ class ProblemHandler(GenericHandler): weekday = datetime.date.today().weekday() if not self.should_run_for_weekday(check, weekday): - log.debug("problem check is not scheduled for %s: %s", - calendar.day_name[weekday], key) + log.debug( + "problem check is not scheduled for %s: %s", + calendar.day_name[weekday], + key, + ) if not force: return None @@ -355,7 +363,7 @@ class ProblemHandler(GenericHandler): :returns: Config key for problem report email message. """ - return f'{check.system_key}_problems_{check.problem_key}' + return f"{check.system_key}_problems_{check.problem_key}" def send_problem_report(self, check, problems): """ @@ -377,18 +385,20 @@ class ProblemHandler(GenericHandler): """ context = self.get_global_email_context() context = self.get_check_email_context(check, problems, **context) - context.update({ - 'config': self.config, - 'app': self.app, - 'check': check, - 'problems': problems, - }) + context.update( + { + "config": self.config, + "app": self.app, + "check": check, + "problems": problems, + } + ) email_key = self.get_email_key(check) attachments = check.make_email_attachments(context) - self.app.send_email(email_key, context, - default_subject=check.title, - attachments=attachments) + self.app.send_email( + email_key, context, default_subject=check.title, attachments=attachments + ) def get_global_email_context(self, **kwargs): """ @@ -413,6 +423,6 @@ class ProblemHandler(GenericHandler): :returns: Context dict for email template. """ - kwargs['system_title'] = self.get_system_title(check.system_key) + kwargs["system_title"] = self.get_system_title(check.system_key) kwargs = check.get_email_context(problems, **kwargs) return kwargs diff --git a/src/wuttjamaican/progress.py b/src/wuttjamaican/progress.py index 00d718a..d9ead5e 100644 --- a/src/wuttjamaican/progress.py +++ b/src/wuttjamaican/progress.py @@ -98,16 +98,20 @@ class ConsoleProgress(ProgressBase): def __init__(self, *args, **kwargs): super().__init__(*args) - self.stderr = kwargs.get('stderr', sys.stderr) + 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 - suffix='%(index)d/%(max)d %(percent)d%% ETA %(eta)ds') + self.bar = Bar( # pylint: disable=disallowed-name + 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): # pylint: disable=empty-docstring """ """ self.bar.next() - def finish(self): # pylint: disable=empty-docstring + def finish(self): # pylint: disable=empty-docstring """ """ self.bar.finish() diff --git a/src/wuttjamaican/reports.py b/src/wuttjamaican/reports.py index 0f84f87..136ffa0 100644 --- a/src/wuttjamaican/reports.py +++ b/src/wuttjamaican/reports.py @@ -39,6 +39,7 @@ class Report: This is the common display title for the report. """ + report_title = "Untitled Report" def __init__(self, config): @@ -146,7 +147,7 @@ class ReportHandler(GenericHandler): :meth:`~wuttjamaican.app.GenericHandler.get_provider_modules()` under the hood, for ``report`` module type. """ - return self.get_provider_modules('report') + return self.get_provider_modules("report") def get_reports(self): """ @@ -156,14 +157,16 @@ 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'): + if not hasattr(self, "_reports"): self._reports = {} for module in self.get_report_modules(): for name in dir(module): obj = getattr(module, name) - if (isinstance(obj, type) + if ( + isinstance(obj, type) and obj is not Report - and issubclass(obj, Report)): + and issubclass(obj, Report) + ): self._reports[obj.report_key] = obj return self._reports @@ -214,6 +217,6 @@ class ReportHandler(GenericHandler): """ data = report.make_data(params or {}, progress=progress, **kwargs) if not isinstance(data, dict): - data = {'data': data} - data.setdefault('output_title', report.report_title) + data = {"data": data} + data.setdefault("output_title", report.report_title) return data diff --git a/src/wuttjamaican/testing.py b/src/wuttjamaican/testing.py index dcdfe2e..8efda7b 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): # pylint: disable=empty-docstring """ """ self.setup_files() @@ -63,14 +63,17 @@ 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; pylint: disable=empty-docstring """ """ - warnings.warn("FileTestCase.setup_file_config() is deprecated; " - "please use setup_files() instead", - DeprecationWarning, stacklevel=2) + 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): # pylint: disable=empty-docstring """ """ self.teardown_files() @@ -80,11 +83,14 @@ 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; pylint: disable=empty-docstring """ """ - warnings.warn("FileTestCase.teardown_file_config() is deprecated; " - "please use teardown_files() instead", - DeprecationWarning, stacklevel=2) + warnings.warn( + "FileTestCase.teardown_file_config() is deprecated; " + "please use teardown_files() instead", + DeprecationWarning, + stacklevel=2, + ) self.teardown_files() def write_file(self, filename, content): @@ -95,15 +101,18 @@ class FileTestCase(TestCase): myconf = self.write_file('my.conf', '') """ path = os.path.join(self.tempdir, filename) - with open(path, 'wt', encoding='utf_8') as f: + with open(path, "wt", encoding="utf_8") as f: f.write(content) return path - def mkdir(self, dirname): # pylint: disable=unused-argument,empty-docstring + def mkdir(self, dirname): # pylint: disable=unused-argument,empty-docstring """ """ - warnings.warn("FileTestCase.mkdir() is deprecated; " - "please use FileTestCase.mkdtemp() instead", - DeprecationWarning, stacklevel=2) + warnings.warn( + "FileTestCase.mkdir() is deprecated; " + "please use FileTestCase.mkdtemp() instead", + DeprecationWarning, + stacklevel=2, + ) return self.mkdtemp() def mkdtemp(self): @@ -143,7 +152,7 @@ class ConfigTestCase(FileTestCase): methods for this class. """ - def setUp(self): # pylint: disable=empty-docstring + def setUp(self): # pylint: disable=empty-docstring """ """ self.setup_config() @@ -155,7 +164,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): # pylint: disable=empty-docstring """ """ self.teardown_config() @@ -165,7 +174,7 @@ class ConfigTestCase(FileTestCase): """ self.teardown_files() - def make_config(self, **kwargs): # pylint: disable=empty-docstring + def make_config(self, **kwargs): # pylint: disable=empty-docstring """ """ return WuttaConfig(**kwargs) @@ -203,7 +212,7 @@ class DataTestCase(FileTestCase): teardown methods, as this class handles that automatically. """ - def setUp(self): # pylint: disable=empty-docstring + def setUp(self): # pylint: disable=empty-docstring """ """ self.setup_db() @@ -212,9 +221,11 @@ class DataTestCase(FileTestCase): Perform config/app/db setup operations for the test. """ self.setup_files() - self.config = self.make_config(defaults={ - 'wutta.db.default.url': 'sqlite://', - }) + self.config = self.make_config( + defaults={ + "wutta.db.default.url": "sqlite://", + } + ) self.app = self.config.get_app() # init db @@ -222,7 +233,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): # pylint: disable=empty-docstring """ """ self.teardown_db() @@ -232,6 +243,6 @@ class DataTestCase(FileTestCase): """ self.teardown_files() - def make_config(self, **kwargs): # pylint: disable=empty-docstring + def make_config(self, **kwargs): # pylint: disable=empty-docstring """ """ return WuttaConfig(**kwargs) diff --git a/src/wuttjamaican/util.py b/src/wuttjamaican/util.py index 6897fe1..76c517b 100644 --- a/src/wuttjamaican/util.py +++ b/src/wuttjamaican/util.py @@ -106,7 +106,7 @@ def load_entry_points(group, ignore_errors=False): import importlib_metadata eps = importlib_metadata.entry_points() - if not hasattr(eps, 'select'): + if not hasattr(eps, "select"): # python < 3.10 eps = eps.get(group, []) else: @@ -115,11 +115,10 @@ 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 Exception: # pylint: disable=broad-exception-caught if not ignore_errors: raise - log.warning("failed to load entry point: %s", entry_point, - exc_info=True) + log.warning("failed to load entry point: %s", entry_point, exc_info=True) else: entry_points[entry_point.name] = ep @@ -151,7 +150,7 @@ def load_object(spec): if not spec: raise ValueError("no object spec provided") - module_path, name = spec.split(':') + module_path, name = spec.split(":") module = importlib.import_module(module_path) return getattr(module, name) @@ -165,10 +164,10 @@ def make_title(text): make_title('foo_bar') # => 'Foo Bar' """ - text = text.replace('_', ' ') - text = text.replace('-', ' ') + text = text.replace("_", " ") + text = text.replace("-", " ") words = text.split() - return ' '.join([x.capitalize() for x in words]) + return " ".join([x.capitalize() for x in words]) def make_full_name(*parts): @@ -185,10 +184,9 @@ def make_full_name(*parts): make_full_name('First', '', 'Last', 'Suffix') # => "First Last Suffix" """ - parts = [(part or '').strip() - for part in parts] + parts = [(part or "").strip() for part in parts] parts = [part for part in parts if part] - return ' '.join(parts) + return " ".join(parts) def make_true_uuid(): @@ -238,7 +236,7 @@ def parse_bool(value): return None if isinstance(value, bool): return value - if str(value).lower() in ('true', 'yes', 'y', 'on', '1'): + if str(value).lower() in ("true", "yes", "y", "on", "1"): return True return False @@ -253,7 +251,7 @@ def parse_list(value): if isinstance(value, list): return value parser = shlex.shlex(value) - parser.whitespace += ',' + parser.whitespace += "," parser.whitespace_split = True values = list(parser) for i, val in enumerate(values): @@ -344,15 +342,15 @@ def resource_path(path): :returns: Absolute file path to the resource. """ - if not os.path.isabs(path) and ':' in path: + 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 - except ImportError: # python < 3.9 + except ImportError: # python < 3.9 from importlib_resources import files, as_file - package, filename = path.split(':') + package, filename = path.split(":") ref = files(package) / filename with as_file(ref) as p: return str(p) diff --git a/tasks.py b/tasks.py index 7ce4250..43bb288 100644 --- a/tasks.py +++ b/tasks.py @@ -15,14 +15,14 @@ def release(c, skip_tests=False): Release a new version of WuttJamaican """ if not skip_tests: - c.run('pytest') + c.run("pytest") # rebuild local tar.gz file for distribution - if os.path.exists('dist'): - shutil.rmtree('dist') - if os.path.exists('WuttJamaican.egg-info'): - shutil.rmtree('WuttJamaican.egg-info') - c.run('python -m build --sdist') + if os.path.exists("dist"): + shutil.rmtree("dist") + if os.path.exists("WuttJamaican.egg-info"): + shutil.rmtree("WuttJamaican.egg-info") + c.run("python -m build --sdist") # upload to PyPI - c.run('twine upload dist/*') + c.run("twine upload dist/*") diff --git a/tests/cli/test_base.py b/tests/cli/test_base.py index 663cabf..2308788 100644 --- a/tests/cli/test_base.py +++ b/tests/cli/test_base.py @@ -11,13 +11,13 @@ from wuttjamaican.cli import base as mod here = os.path.dirname(__file__) -example_conf = os.path.join(here, 'example.conf') +example_conf = os.path.join(here, "example.conf") class TestMakeCliConfig(TestCase): def test_basic(self): - ctx = MagicMock(params={'config_paths': [example_conf]}) + ctx = MagicMock(params={"config_paths": [example_conf]}) config = mod.make_cli_config(ctx) self.assertIsInstance(config, WuttaConfig) self.assertEqual(config.files_read, [example_conf]) @@ -26,7 +26,7 @@ class TestMakeCliConfig(TestCase): class TestTyperCallback(TestCase): def test_basic(self): - ctx = MagicMock(params={'config_paths': [example_conf]}) + ctx = MagicMock(params={"config_paths": [example_conf]}) mod.typer_callback(ctx) self.assertIsInstance(ctx.wutta_config, WuttaConfig) self.assertEqual(ctx.wutta_config.files_read, [example_conf]) @@ -35,10 +35,10 @@ class TestTyperCallback(TestCase): class TestTyperEagerImports(TestCase): def test_basic(self): - typr = mod.make_typer(name='foobreezy') - with patch.object(mod, 'load_entry_points') as load_entry_points: + typr = mod.make_typer(name="foobreezy") + with patch.object(mod, "load_entry_points") as load_entry_points: mod.typer_eager_imports(typr) - load_entry_points.assert_called_once_with('foobreezy.typer_imports') + load_entry_points.assert_called_once_with("foobreezy.typer_imports") class TestMakeTyper(TestCase): diff --git a/tests/cli/test_make_appdir.py b/tests/cli/test_make_appdir.py index 0bd7d42..4ca6e2a 100644 --- a/tests/cli/test_make_appdir.py +++ b/tests/cli/test_make_appdir.py @@ -9,17 +9,16 @@ from wuttjamaican.app import AppHandler here = os.path.dirname(__file__) -example_conf = os.path.join(here, 'example.conf') +example_conf = os.path.join(here, "example.conf") class TestMakeAppdir(ConfigTestCase): def test_basic(self): - appdir = os.path.join(self.tempdir, 'app') - ctx = MagicMock(params={'config_paths': [example_conf], - 'appdir_path': appdir}) + appdir = os.path.join(self.tempdir, "app") + ctx = MagicMock(params={"config_paths": [example_conf], "appdir_path": appdir}) ctx.parent.wutta_config = self.config - with patch.object(AppHandler, 'make_appdir') as make_appdir: + with patch.object(AppHandler, "make_appdir") as make_appdir: mod.make_appdir(ctx) make_appdir.assert_called_once_with(appdir) diff --git a/tests/cli/test_make_uuid.py b/tests/cli/test_make_uuid.py index 05c5c8b..74dd4cf 100644 --- a/tests/cli/test_make_uuid.py +++ b/tests/cli/test_make_uuid.py @@ -8,13 +8,13 @@ from wuttjamaican.cli import make_uuid as mod here = os.path.dirname(__file__) -example_conf = os.path.join(here, 'example.conf') +example_conf = os.path.join(here, "example.conf") class TestMakeUuid(TestCase): def test_basic(self): - ctx = MagicMock(params={'config_paths': [example_conf]}) - with patch.object(mod, 'sys') as sys: + ctx = MagicMock(params={"config_paths": [example_conf]}) + with patch.object(mod, "sys") as sys: mod.make_uuid(ctx) sys.stdout.write.assert_called_once() diff --git a/tests/cli/test_problems.py b/tests/cli/test_problems.py index c6dd6f8..3405743 100644 --- a/tests/cli/test_problems.py +++ b/tests/cli/test_problems.py @@ -8,8 +8,8 @@ from wuttjamaican.problems import ProblemHandler, ProblemCheck class FakeCheck(ProblemCheck): - system_key = 'wuttatest' - problem_key = 'fake_check' + system_key = "wuttatest" + problem_key = "fake_check" title = "Fake problem check" @@ -20,18 +20,28 @@ class TestProblems(ConfigTestCase): ctx.parent.wutta_config = self.config # nb. avoid printing to console - with patch.object(mod.rich, 'print') as rich_print: + with patch.object(mod.rich, "print") as rich_print: # nb. use fake check - with patch.object(ProblemHandler, 'get_all_problem_checks', return_value=[FakeCheck]): + with patch.object( + ProblemHandler, "get_all_problem_checks", return_value=[FakeCheck] + ): - with patch.object(ProblemHandler, 'run_problem_checks') as run_problem_checks: + with patch.object( + ProblemHandler, "run_problem_checks" + ) as run_problem_checks: # list problem checks orig_organize = ProblemHandler.organize_problem_checks + def mock_organize(checks): return orig_organize(None, checks) - with patch.object(ProblemHandler, 'organize_problem_checks', side_effect=mock_organize) as organize_problem_checks: + + with patch.object( + ProblemHandler, + "organize_problem_checks", + side_effect=mock_organize, + ) as organize_problem_checks: mod.problems(ctx, list_checks=True) organize_problem_checks.assert_called_once_with([FakeCheck]) run_problem_checks.assert_not_called() @@ -41,10 +51,13 @@ class TestProblems(ConfigTestCase): # nb. just --list for convenience # note that since we also specify invalid --system, no checks will # match and hence nothing significant will be printed to stdout - mod.problems(ctx, list_checks=True, systems=['craziness']) + mod.problems(ctx, list_checks=True, systems=["craziness"]) rich_print.assert_called_once() self.assertEqual(len(rich_print.call_args.args), 1) - self.assertIn("No problem reports exist for system", rich_print.call_args.args[0]) + self.assertIn( + "No problem reports exist for system", + rich_print.call_args.args[0], + ) self.assertEqual(len(rich_print.call_args.kwargs), 0) run_problem_checks.assert_not_called() diff --git a/tests/db/model/test_auth.py b/tests/db/model/test_auth.py index bdee085..c737a62 100644 --- a/tests/db/model/test_auth.py +++ b/tests/db/model/test_auth.py @@ -18,22 +18,20 @@ else: role.name = "Managers" self.assertEqual(str(role), "Managers") - class TestPermission(TestCase): def test_basic(self): perm = model.Permission() self.assertEqual(str(perm), "") - perm.permission = 'users.create' + perm.permission = "users.create" self.assertEqual(str(perm), "users.create") - class TestUser(TestCase): def test_str(self): user = model.User() self.assertEqual(str(user), "") - user.username = 'barney' + user.username = "barney" self.assertEqual(str(user), "barney") def test_str_with_person(self): @@ -44,7 +42,6 @@ else: user.person = person self.assertEqual(str(user), "Barney Rubble") - class TestUserAPIToken(TestCase): def test_str(self): diff --git a/tests/db/model/test_base.py b/tests/db/model/test_base.py index d7e495a..6d459c1 100644 --- a/tests/db/model/test_base.py +++ b/tests/db/model/test_base.py @@ -11,35 +11,32 @@ except ImportError: pass else: - class MockUser(mod.Base): - __tablename__ = 'mock_user' - uuid = mod.uuid_column(sa.ForeignKey('user.uuid'), default=False) + __tablename__ = "mock_user" + uuid = mod.uuid_column(sa.ForeignKey("user.uuid"), default=False) user = orm.relationship( User, - backref=orm.backref('_mock', uselist=False, cascade='all, delete-orphan')) + backref=orm.backref("_mock", uselist=False, cascade="all, delete-orphan"), + ) favorite_color = sa.Column(sa.String(length=100), nullable=False) - class TestWuttaModelBase(TestCase): def test_make_proxy(self): - self.assertFalse(hasattr(User, 'favorite_color')) - MockUser.make_proxy(User, '_mock', 'favorite_color') - self.assertTrue(hasattr(User, 'favorite_color')) - user = User(favorite_color='green') - self.assertEqual(user.favorite_color, 'green') - + self.assertFalse(hasattr(User, "favorite_color")) + MockUser.make_proxy(User, "_mock", "favorite_color") + self.assertTrue(hasattr(User, "favorite_color")) + user = User(favorite_color="green") + self.assertEqual(user.favorite_color, "green") class TestSetting(TestCase): def test_basic(self): setting = mod.Setting() self.assertEqual(str(setting), "") - setting.name = 'foo' + setting.name = "foo" self.assertEqual(str(setting), "foo") - class TestPerson(TestCase): def test_basic(self): diff --git a/tests/db/model/test_batch.py b/tests/db/model/test_batch.py index 308cb6f..722f386 100644 --- a/tests/db/model/test_batch.py +++ b/tests/db/model/test_batch.py @@ -17,40 +17,46 @@ else: def test_basic(self): class MyBatch(mod.BatchMixin, model.Base): - __tablename__ = 'testing_mybatch' + __tablename__ = "testing_mybatch" model.Base.metadata.create_all(bind=self.session.bind) metadata = sa.MetaData() metadata.reflect(self.session.bind) - self.assertIn('testing_mybatch', metadata.tables) + self.assertIn("testing_mybatch", metadata.tables) - batch = MyBatch(id=42, uuid=_uuid.UUID('0675cdac-ffc9-7690-8000-6023de1c8cfd')) - self.assertEqual(repr(batch), "MyBatch(uuid=UUID('0675cdac-ffc9-7690-8000-6023de1c8cfd'))") + batch = MyBatch( + id=42, uuid=_uuid.UUID("0675cdac-ffc9-7690-8000-6023de1c8cfd") + ) + self.assertEqual( + repr(batch), + "MyBatch(uuid=UUID('0675cdac-ffc9-7690-8000-6023de1c8cfd'))", + ) self.assertEqual(str(batch), "00000042") self.assertEqual(batch.id_str, "00000042") batch2 = MyBatch() self.assertIsNone(batch2.id_str) - class TestBatchRowMixin(DataTestCase): def test_basic(self): class MyBatch2(mod.BatchMixin, model.Base): - __tablename__ = 'testing_mybatch2' + __tablename__ = "testing_mybatch2" class MyBatchRow2(mod.BatchRowMixin, model.Base): - __tablename__ = 'testing_mybatch_row2' + __tablename__ = "testing_mybatch_row2" __batch_class__ = MyBatch2 model.Base.metadata.create_all(bind=self.session.bind) metadata = sa.MetaData() metadata.reflect(self.session.bind) - self.assertIn('testing_mybatch2', metadata.tables) - self.assertIn('testing_mybatch_row2', metadata.tables) + self.assertIn("testing_mybatch2", metadata.tables) + self.assertIn("testing_mybatch_row2", metadata.tables) # nb. this gives coverage but doesn't really test much - batch = MyBatch2(id=42, uuid=_uuid.UUID('0675cdac-ffc9-7690-8000-6023de1c8cfd')) + batch = MyBatch2( + id=42, uuid=_uuid.UUID("0675cdac-ffc9-7690-8000-6023de1c8cfd") + ) row = MyBatchRow2() batch.rows.append(row) diff --git a/tests/db/test_conf.py b/tests/db/test_conf.py index 8c62a31..cb68c0e 100644 --- a/tests/db/test_conf.py +++ b/tests/db/test_conf.py @@ -27,109 +27,130 @@ else: def write_file(self, filename, content): path = os.path.join(self.tempdir, filename) - with open(path, 'wt') as f: + with open(path, "wt") as f: f.write(content) return path def test_no_default(self): - myfile = self.write_file('my.conf', '') + myfile = self.write_file("my.conf", "") config = WuttaConfig([myfile]) - self.assertEqual(conf.get_engines(config, 'wuttadb'), {}) + self.assertEqual(conf.get_engines(config, "wuttadb"), {}) def test_default(self): - myfile = self.write_file('my.conf', """\ + myfile = self.write_file( + "my.conf", + """\ [wuttadb] default.url = sqlite:// - """) + """, + ) config = WuttaConfig([myfile]) - result = conf.get_engines(config, 'wuttadb') + result = conf.get_engines(config, "wuttadb") self.assertEqual(len(result), 1) - self.assertIn('default', result) - engine = result['default'] - self.assertEqual(engine.dialect.name, 'sqlite') + self.assertIn("default", result) + engine = result["default"] + self.assertEqual(engine.dialect.name, "sqlite") def test_default_fallback(self): - myfile = self.write_file('my.conf', """\ + myfile = self.write_file( + "my.conf", + """\ [wuttadb] sqlalchemy.url = sqlite:// - """) + """, + ) config = WuttaConfig([myfile]) - result = conf.get_engines(config, 'wuttadb') + result = conf.get_engines(config, "wuttadb") self.assertEqual(len(result), 1) - self.assertIn('default', result) - engine = result['default'] - self.assertEqual(engine.dialect.name, 'sqlite') + self.assertIn("default", result) + engine = result["default"] + self.assertEqual(engine.dialect.name, "sqlite") def test_other(self): - myfile = self.write_file('my.conf', """\ + myfile = self.write_file( + "my.conf", + """\ [otherdb] keys = first, second first.url = sqlite:// second.url = sqlite:// - """) + """, + ) config = WuttaConfig([myfile]) - result = conf.get_engines(config, 'otherdb') + result = conf.get_engines(config, "otherdb") self.assertEqual(len(result), 2) - self.assertIn('first', result) - self.assertIn('second', result) - + self.assertIn("first", result) + self.assertIn("second", result) class TestGetSetting(TestCase): def setUp(self): Session = orm.sessionmaker() - engine = sa.create_engine('sqlite://') + engine = sa.create_engine("sqlite://") self.session = Session(bind=engine) - self.session.execute(sa.text(""" + self.session.execute( + sa.text( + """ create table setting ( name varchar(255) primary key, value text ); - """)) + """ + ) + ) def tearDown(self): self.session.close() def test_basic_value(self): self.session.execute(sa.text("insert into setting values ('foo', 'bar');")) - value = conf.get_setting(self.session, 'foo') - self.assertEqual(value, 'bar') + value = conf.get_setting(self.session, "foo") + self.assertEqual(value, "bar") def test_missing_value(self): - value = conf.get_setting(self.session, 'foo') + value = conf.get_setting(self.session, "foo") self.assertIsNone(value) - class TestMakeEngineFromConfig(TestCase): def test_basic(self): - engine = conf.make_engine_from_config({ - 'sqlalchemy.url': 'sqlite://', - }) + engine = conf.make_engine_from_config( + { + "sqlalchemy.url": "sqlite://", + } + ) self.assertIsInstance(engine, Engine) def test_poolclass(self): - engine = conf.make_engine_from_config({ - 'sqlalchemy.url': 'sqlite://', - }) + engine = conf.make_engine_from_config( + { + "sqlalchemy.url": "sqlite://", + } + ) self.assertNotIsInstance(engine.pool, NullPool) - engine = conf.make_engine_from_config({ - 'sqlalchemy.url': 'sqlite://', - 'sqlalchemy.poolclass': 'sqlalchemy.pool:NullPool', - }) + engine = conf.make_engine_from_config( + { + "sqlalchemy.url": "sqlite://", + "sqlalchemy.poolclass": "sqlalchemy.pool:NullPool", + } + ) self.assertIsInstance(engine.pool, NullPool) def test_pool_pre_ping(self): - engine = conf.make_engine_from_config({ - 'sqlalchemy.url': 'sqlite://', - }) + engine = conf.make_engine_from_config( + { + "sqlalchemy.url": "sqlite://", + } + ) self.assertFalse(engine.pool._pre_ping) - engine = conf.make_engine_from_config({ - 'sqlalchemy.url': 'sqlite://', - 'sqlalchemy.pool_pre_ping': 'true', - }) + engine = conf.make_engine_from_config( + { + "sqlalchemy.url": "sqlite://", + "sqlalchemy.pool_pre_ping": "true", + } + ) self.assertTrue(engine.pool._pre_ping) diff --git a/tests/db/test_handler.py b/tests/db/test_handler.py index bee05e5..5385ef1 100644 --- a/tests/db/test_handler.py +++ b/tests/db/test_handler.py @@ -22,20 +22,20 @@ else: # counter table should not exist yet metadata = sa.MetaData() metadata.reflect(self.session.bind) - self.assertNotIn('_counter_testing', metadata.tables) + self.assertNotIn("_counter_testing", metadata.tables) # using sqlite as backend, should make table for counter - value = handler.next_counter_value(self.session, 'testing') + value = handler.next_counter_value(self.session, "testing") self.assertEqual(value, 1) # counter table should exist now metadata.reflect(self.session.bind) - self.assertIn('_counter_testing', metadata.tables) + self.assertIn("_counter_testing", metadata.tables) # counter increments okay - value = handler.next_counter_value(self.session, 'testing') + value = handler.next_counter_value(self.session, "testing") self.assertEqual(value, 2) - value = handler.next_counter_value(self.session, 'testing') + value = handler.next_counter_value(self.session, "testing") self.assertEqual(value, 3) def test_next_counter_value_postgres(self): @@ -44,20 +44,20 @@ else: # counter table should not exist metadata = sa.MetaData() metadata.reflect(self.session.bind) - self.assertNotIn('_counter_testing', metadata.tables) + self.assertNotIn("_counter_testing", metadata.tables) # nb. we have to pretty much mock this out, can't really # test true sequence behavior for postgres since tests are # using sqlite backend. # using postgres as backend, should use "sequence" - with patch.object(handler, 'get_dialect', return_value='postgresql'): - with patch.object(self.session, 'execute') as execute: + with patch.object(handler, "get_dialect", return_value="postgresql"): + with patch.object(self.session, "execute") as execute: execute.return_value.scalar.return_value = 1 - value = handler.next_counter_value(self.session, 'testing') + value = handler.next_counter_value(self.session, "testing") self.assertEqual(value, 1) execute.return_value.scalar.assert_called_once_with() # counter table should still not exist metadata.reflect(self.session.bind) - self.assertNotIn('_counter_testing', metadata.tables) + self.assertNotIn("_counter_testing", metadata.tables) diff --git a/tests/db/test_util.py b/tests/db/test_util.py index f2002b8..9c358e1 100644 --- a/tests/db/test_util.py +++ b/tests/db/test_util.py @@ -16,18 +16,16 @@ except ImportError: pass else: - class TestModelBase(TestCase): def test_dict_behavior(self): setting = Setting() - self.assertEqual(list(iter(setting)), [('name', None), ('value', None)]) + self.assertEqual(list(iter(setting)), [("name", None), ("value", None)]) self.assertIsNone(setting.name) - self.assertIsNone(setting['name']) - setting.name = 'foo' - self.assertEqual(setting['name'], 'foo') - self.assertRaises(KeyError, lambda: setting['notfound']) - + self.assertIsNone(setting["name"]) + setting.name = "foo" + self.assertEqual(setting["name"], "foo") + self.assertRaises(KeyError, lambda: setting["notfound"]) class TestUUID(TestCase): @@ -39,14 +37,14 @@ else: # coverage at least.. # postgres - dialect.name = 'postgresql' + dialect.name = "postgresql" dialect.type_descriptor.return_value = 42 result = typ.load_dialect_impl(dialect) self.assertTrue(dialect.type_descriptor.called) self.assertEqual(result, 42) # other - dialect.name = 'mysql' + dialect.name = "mysql" dialect.type_descriptor.return_value = 43 dialect.type_descriptor.reset_mock() result = typ.load_dialect_impl(dialect) @@ -56,7 +54,7 @@ else: def test_process_bind_param_postgres(self): typ = mod.UUID() dialect = MagicMock() - dialect.name = 'postgresql' + dialect.name = "postgresql" # null result = typ.process_bind_param(None, dialect) @@ -75,7 +73,7 @@ else: def test_process_bind_param_other(self): typ = mod.UUID() dialect = MagicMock() - dialect.name = 'mysql' + dialect.name = "mysql" # null result = typ.process_bind_param(None, dialect) @@ -110,7 +108,6 @@ else: result = typ.process_result_value(uuid_true, dialect) self.assertIs(result, uuid_true) - class TestUUIDColumn(TestCase): def test_basic(self): @@ -118,24 +115,22 @@ else: self.assertIsInstance(column, sa.Column) self.assertIsInstance(column.type, mod.UUID) - class TestUUIDFKColumn(TestCase): def test_basic(self): - column = mod.uuid_fk_column('foo.bar') + column = mod.uuid_fk_column("foo.bar") self.assertIsInstance(column, sa.Column) self.assertIsInstance(column.type, mod.UUID) - class TestMakeTopoSortkey(DataTestCase): def test_basic(self): model = self.app.model sortkey = mod.make_topo_sortkey(model) - original = ['User', 'Person', 'UserRole', 'Role'] + original = ["User", "Person", "UserRole", "Role"] # models are sorted so dependants come later result = sorted(original, key=sortkey) - self.assertTrue(result.index('Role') < result.index('UserRole')) - self.assertTrue(result.index('User') < result.index('UserRole')) - self.assertTrue(result.index('Person') < result.index('User')) + self.assertTrue(result.index("Role") < result.index("UserRole")) + self.assertTrue(result.index("User") < result.index("UserRole")) + self.assertTrue(result.index("Person") < result.index("User")) diff --git a/tests/test_app.py b/tests/test_app.py index e75b527..1b0e19c 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -26,6 +26,7 @@ from wuttjamaican.batch import BatchHandler class MockBatchHandler(BatchHandler): pass + class AnotherBatchHandler(BatchHandler): pass @@ -34,14 +35,14 @@ class TestAppHandler(FileTestCase): def setUp(self): self.setup_files() - self.config = WuttaConfig(appname='wuttatest') + self.config = WuttaConfig(appname="wuttatest") self.app = mod.AppHandler(self.config) self.config.app = self.app def test_init(self): self.assertIs(self.app.config, self.config) self.assertEqual(self.app.handlers, {}) - self.assertEqual(self.app.appname, 'wuttatest') + self.assertEqual(self.app.appname, "wuttatest") def test_get_enum(self): self.assertIs(self.app.get_enum(), wuttjamaican.enum) @@ -50,48 +51,50 @@ class TestAppHandler(FileTestCase): # just confirm the method works on a basic level; the # underlying function is tested elsewhere - obj = self.app.load_object('wuttjamaican.util:UNSPECIFIED') + obj = self.app.load_object("wuttjamaican.util:UNSPECIFIED") self.assertIs(obj, UNSPECIFIED) def test_get_appdir(self): - mockdir = self.mkdir('mockdir') + mockdir = self.mkdir("mockdir") # default appdir - with patch.object(sys, 'prefix', new=mockdir): + with patch.object(sys, "prefix", new=mockdir): # default is returned by default appdir = self.app.get_appdir() - self.assertEqual(appdir, os.path.join(mockdir, 'app')) + self.assertEqual(appdir, os.path.join(mockdir, "app")) # but not if caller wants config only appdir = self.app.get_appdir(configured_only=True) self.assertIsNone(appdir) # also, cannot create if appdir path not known - self.assertRaises(ValueError, self.app.get_appdir, configured_only=True, create=True) + self.assertRaises( + ValueError, self.app.get_appdir, configured_only=True, create=True + ) # configured appdir - self.config.setdefault('wuttatest.appdir', mockdir) + self.config.setdefault("wuttatest.appdir", mockdir) appdir = self.app.get_appdir() self.assertEqual(appdir, mockdir) # appdir w/ subpath - appdir = self.app.get_appdir('foo', 'bar') - self.assertEqual(appdir, os.path.join(mockdir, 'foo', 'bar')) + appdir = self.app.get_appdir("foo", "bar") + self.assertEqual(appdir, os.path.join(mockdir, "foo", "bar")) # subpath is created self.assertEqual(len(os.listdir(mockdir)), 0) - appdir = self.app.get_appdir('foo', 'bar', create=True) - self.assertEqual(appdir, os.path.join(mockdir, 'foo', 'bar')) - self.assertEqual(os.listdir(mockdir), ['foo']) - self.assertEqual(os.listdir(os.path.join(mockdir, 'foo')), ['bar']) + appdir = self.app.get_appdir("foo", "bar", create=True) + self.assertEqual(appdir, os.path.join(mockdir, "foo", "bar")) + self.assertEqual(os.listdir(mockdir), ["foo"]) + self.assertEqual(os.listdir(os.path.join(mockdir, "foo")), ["bar"]) def test_make_appdir(self): # appdir is created, and 3 subfolders added by default tempdir = tempfile.mkdtemp() - appdir = os.path.join(tempdir, 'app') + appdir = os.path.join(tempdir, "app") self.assertFalse(os.path.exists(appdir)) self.app.make_appdir(appdir) self.assertTrue(os.path.exists(appdir)) @@ -107,23 +110,30 @@ class TestAppHandler(FileTestCase): shutil.rmtree(tempdir) def test_render_mako_template(self): - output_conf = self.write_file('output.conf', '') - template = Template("""\ + output_conf = self.write_file("output.conf", "") + template = Template( + """\ [wutta] app_title = WuttaTest -""") +""" + ) output = self.app.render_mako_template(template, {}, output_path=output_conf) - self.assertEqual(output, """\ + self.assertEqual( + output, + """\ [wutta] app_title = WuttaTest -""") +""", + ) - with open(output_conf, 'rt') as f: + with open(output_conf, "rt") as f: self.assertEqual(f.read(), output) def test_resource_path(self): - result = self.app.resource_path('wuttjamaican:templates') - self.assertEqual(result, os.path.join(os.path.dirname(mod.__file__), 'templates')) + result = self.app.resource_path("wuttjamaican:templates") + self.assertEqual( + result, os.path.join(os.path.dirname(mod.__file__), "templates") + ) def test_make_session(self): try: @@ -138,11 +148,12 @@ app_title = WuttaTest short_session = MagicMock() mockdb = MagicMock(short_session=short_session) - with patch.dict('sys.modules', **{'wuttjamaican.db': mockdb}): + with patch.dict("sys.modules", **{"wuttjamaican.db": mockdb}): - with self.app.short_session(foo='bar') as s: + with self.app.short_session(foo="bar") as s: short_session.assert_called_once_with( - foo='bar', factory=self.app.make_session) + foo="bar", factory=self.app.make_session + ) def test_get_setting(self): try: @@ -152,22 +163,26 @@ app_title = WuttaTest pytest.skip("test is not relevant without sqlalchemy") Session = orm.sessionmaker() - engine = sa.create_engine('sqlite://') + engine = sa.create_engine("sqlite://") session = Session(bind=engine) - session.execute(sa.text(""" + session.execute( + sa.text( + """ create table setting ( name varchar(255) primary key, value text ); - """)) + """ + ) + ) session.commit() - value = self.app.get_setting(session, 'foo') + value = self.app.get_setting(session, "foo") self.assertIsNone(value) session.execute(sa.text("insert into setting values ('foo', 'bar');")) - value = self.app.get_setting(session, 'foo') - self.assertEqual(value, 'bar') + value = self.app.get_setting(session, "foo") + self.assertEqual(value, "bar") def test_save_setting(self): try: @@ -177,25 +192,29 @@ app_title = WuttaTest pytest.skip("test is not relevant without sqlalchemy") Session = orm.sessionmaker() - engine = sa.create_engine('sqlite://') + engine = sa.create_engine("sqlite://") session = Session(bind=engine) - session.execute(sa.text(""" + session.execute( + sa.text( + """ create table setting ( name varchar(255) primary key, value text ); - """)) + """ + ) + ) session.commit() # value null by default - value = self.app.get_setting(session, 'foo') + value = self.app.get_setting(session, "foo") self.assertIsNone(value) # unless we save a value - self.app.save_setting(session, 'foo', '1') + self.app.save_setting(session, "foo", "1") session.commit() - value = self.app.get_setting(session, 'foo') - self.assertEqual(value, '1') + value = self.app.get_setting(session, "foo") + self.assertEqual(value, "1") def test_delete_setting(self): try: @@ -205,43 +224,48 @@ app_title = WuttaTest pytest.skip("test is not relevant without sqlalchemy") Session = orm.sessionmaker() - engine = sa.create_engine('sqlite://') + engine = sa.create_engine("sqlite://") session = Session(bind=engine) - session.execute(sa.text(""" + session.execute( + sa.text( + """ create table setting ( name varchar(255) primary key, value text ); - """)) + """ + ) + ) session.commit() # value null by default - value = self.app.get_setting(session, 'foo') + value = self.app.get_setting(session, "foo") self.assertIsNone(value) # unless we save a value - self.app.save_setting(session, 'foo', '1') + self.app.save_setting(session, "foo", "1") session.commit() - value = self.app.get_setting(session, 'foo') - self.assertEqual(value, '1') + value = self.app.get_setting(session, "foo") + self.assertEqual(value, "1") # but then if we delete it, should be null again - self.app.delete_setting(session, 'foo') + self.app.delete_setting(session, "foo") session.commit() - value = self.app.get_setting(session, 'foo') + value = self.app.get_setting(session, "foo") self.assertIsNone(value) def test_continuum_is_enabled(self): # false by default - with patch.object(self.app, 'providers', new={}): + with patch.object(self.app, "providers", new={}): self.assertFalse(self.app.continuum_is_enabled()) # but "any" provider technically could enable it... class MockProvider: def continuum_is_enabled(self): return True - with patch.object(self.app, 'providers', new={'mock': MockProvider()}): + + with patch.object(self.app, "providers", new={"mock": MockProvider()}): self.assertTrue(self.app.continuum_is_enabled()) def test_model(self): @@ -250,7 +274,7 @@ app_title = WuttaTest except ImportError: pytest.skip("test not relevant without sqlalchemy") - self.assertNotIn('model', self.app.__dict__) + self.assertNotIn("model", self.app.__dict__) self.assertIs(self.app.model, model) def test_get_model(self): @@ -262,20 +286,20 @@ app_title = WuttaTest self.assertIs(self.app.get_model(), model) def test_get_title(self): - self.assertEqual(self.app.get_title(), 'WuttJamaican') + self.assertEqual(self.app.get_title(), "WuttJamaican") def test_get_node_title(self): # default - self.assertEqual(self.app.get_node_title(), 'WuttJamaican') + self.assertEqual(self.app.get_node_title(), "WuttJamaican") # will fallback to app title - self.config.setdefault('wuttatest.app_title', "WuttaTest") - self.assertEqual(self.app.get_node_title(), 'WuttaTest') + self.config.setdefault("wuttatest.app_title", "WuttaTest") + self.assertEqual(self.app.get_node_title(), "WuttaTest") # will read from config - self.config.setdefault('wuttatest.node_title', "WuttaNode") - self.assertEqual(self.app.get_node_title(), 'WuttaNode') + self.config.setdefault("wuttatest.node_title", "WuttaNode") + self.assertEqual(self.app.get_node_title(), "WuttaNode") def test_get_node_type(self): @@ -283,8 +307,8 @@ app_title = WuttaTest self.assertIsNone(self.app.get_node_type()) # will read from config - self.config.setdefault('wuttatest.node_type', 'warehouse') - self.assertEqual(self.app.get_node_type(), 'warehouse') + self.config.setdefault("wuttatest.node_type", "warehouse") + self.assertEqual(self.app.get_node_type(), "warehouse") def test_get_distribution(self): @@ -296,16 +320,16 @@ app_title = WuttaTest # works with "non-native" objects query = Query({}) dist = self.app.get_distribution(query) - self.assertEqual(dist, 'SQLAlchemy') + self.assertEqual(dist, "SQLAlchemy") # can override dist via config - self.config.setdefault('wuttatest.app_dist', 'importlib_metadata') + self.config.setdefault("wuttatest.app_dist", "importlib_metadata") dist = self.app.get_distribution() - self.assertEqual(dist, 'importlib_metadata') + self.assertEqual(dist, "importlib_metadata") # but the provided object takes precedence dist = self.app.get_distribution(query) - self.assertEqual(dist, 'SQLAlchemy') + self.assertEqual(dist, "SQLAlchemy") def test_get_distribution_pre_python_3_10(self): @@ -318,30 +342,32 @@ app_title = WuttaTest importlib_metadata = MagicMock() importlib_metadata.packages_distributions = MagicMock( return_value={ - 'wuttjamaican': ['WuttJamaican'], - 'config': ['python-configuration'], - }) + "wuttjamaican": ["WuttJamaican"], + "config": ["python-configuration"], + } + ) orig_import = __import__ def mock_import(name, *args, **kwargs): - if name == 'importlib.metadata': + if name == "importlib.metadata": raise ImportError - if name == 'importlib_metadata': + if name == "importlib_metadata": return importlib_metadata return orig_import(name, *args, **kwargs) - with patch('builtins.__import__', side_effect=mock_import): + with patch("builtins.__import__", side_effect=mock_import): # default should always be WuttJamaican (right..?) dist = self.app.get_distribution() - self.assertEqual(dist, 'WuttJamaican') + self.assertEqual(dist, "WuttJamaican") # also works with "non-native" objects from config import Configuration + config = Configuration({}) dist = self.app.get_distribution(config) - self.assertEqual(dist, 'python-configuration') + self.assertEqual(dist, "python-configuration") # hacky sort of test, just in case we can't deduce the # package dist based on the obj - easy enough since we @@ -350,17 +376,17 @@ app_title = WuttaTest self.assertIsNone(dist) # can override dist via config - self.config.setdefault('wuttatest.app_dist', 'importlib_metadata') + self.config.setdefault("wuttatest.app_dist", "importlib_metadata") dist = self.app.get_distribution() - self.assertEqual(dist, 'importlib_metadata') + self.assertEqual(dist, "importlib_metadata") # but the provided object takes precedence dist = self.app.get_distribution(config) - self.assertEqual(dist, 'python-configuration') + self.assertEqual(dist, "python-configuration") # hacky test again, this time config override should win dist = self.app.get_distribution(42) - self.assertEqual(dist, 'importlib_metadata') + self.assertEqual(dist, "importlib_metadata") def test_get_version(self): from importlib.metadata import version @@ -373,31 +399,31 @@ app_title = WuttaTest # works with "non-native" objects query = Query({}) ver = self.app.get_version(obj=query) - self.assertEqual(ver, version('SQLAlchemy')) + self.assertEqual(ver, version("SQLAlchemy")) # random object will not yield a dist nor version ver = self.app.get_version(obj=42) self.assertIsNone(ver) # can override dist via config - self.config.setdefault('wuttatest.app_dist', 'python-configuration') + self.config.setdefault("wuttatest.app_dist", "python-configuration") ver = self.app.get_version() - self.assertEqual(ver, version('python-configuration')) + self.assertEqual(ver, version("python-configuration")) # but the provided object takes precedence ver = self.app.get_version(obj=query) - self.assertEqual(ver, version('SQLAlchemy')) + self.assertEqual(ver, version("SQLAlchemy")) # can also specify the dist - ver = self.app.get_version(dist='passlib') - self.assertEqual(ver, version('passlib')) + ver = self.app.get_version(dist="passlib") + self.assertEqual(ver, version("passlib")) def test_make_title(self): - text = self.app.make_title('foo_bar') + text = self.app.make_title("foo_bar") self.assertEqual(text, "Foo Bar") def test_make_full_name(self): - name = self.app.make_full_name('Fred', '', 'Flintstone', '') + name = self.app.make_full_name("Fred", "", "Flintstone", "") self.assertEqual(name, "Fred Flintstone") def test_make_uuid(self): @@ -414,12 +440,10 @@ app_title = WuttaTest pass # with progress - self.app.progress_loop(act, [1, 2, 3], ProgressBase, - message="whatever") + self.app.progress_loop(act, [1, 2, 3], ProgressBase, message="whatever") # without progress - self.app.progress_loop(act, [1, 2, 3], None, - message="whatever") + self.app.progress_loop(act, [1, 2, 3], None, message="whatever") def test_get_session(self): try: @@ -433,7 +457,7 @@ app_title = WuttaTest self.assertIsNone(self.app.get_session(user)) Session = orm.sessionmaker() - engine = sa.create_engine('sqlite://') + engine = sa.create_engine("sqlite://") mysession = Session(bind=engine) mysession.add(user) session = self.app.get_session(user) @@ -453,39 +477,39 @@ app_title = WuttaTest def test_render_currency(self): # null - self.assertEqual(self.app.render_currency(None), '') + self.assertEqual(self.app.render_currency(None), "") # basic decimal example - value = decimal.Decimal('42.00') - self.assertEqual(self.app.render_currency(value), '$42.00') + value = decimal.Decimal("42.00") + self.assertEqual(self.app.render_currency(value), "$42.00") # basic float example value = 42.00 - self.assertEqual(self.app.render_currency(value), '$42.00') + self.assertEqual(self.app.render_currency(value), "$42.00") # decimal places will be rounded - value = decimal.Decimal('42.12345') - self.assertEqual(self.app.render_currency(value), '$42.12') + value = decimal.Decimal("42.12345") + self.assertEqual(self.app.render_currency(value), "$42.12") # but we can declare the scale - value = decimal.Decimal('42.12345') - self.assertEqual(self.app.render_currency(value, scale=4), '$42.1234') + value = decimal.Decimal("42.12345") + self.assertEqual(self.app.render_currency(value, scale=4), "$42.1234") # negative numbers get parens - value = decimal.Decimal('-42.42') - self.assertEqual(self.app.render_currency(value), '($42.42)') + value = decimal.Decimal("-42.42") + self.assertEqual(self.app.render_currency(value), "($42.42)") def test_render_date(self): - self.assertEqual(self.app.render_date(None), '') + self.assertEqual(self.app.render_date(None), "") dt = datetime.date(2024, 12, 11) - self.assertEqual(self.app.render_date(dt), '2024-12-11') + self.assertEqual(self.app.render_date(dt), "2024-12-11") def test_render_datetime(self): - self.assertEqual(self.app.render_datetime(None), '') + self.assertEqual(self.app.render_datetime(None), "") dt = datetime.datetime(2024, 12, 11, 8, 30, tzinfo=datetime.timezone.utc) - self.assertEqual(self.app.render_datetime(dt), '2024-12-11 08:30+0000') + self.assertEqual(self.app.render_datetime(dt), "2024-12-11 08:30+0000") def test_render_error(self): @@ -509,15 +533,15 @@ app_title = WuttaTest self.assertEqual(self.app.render_percent(None), "") # typical - self.assertEqual(self.app.render_percent(12.3419), '12.34 %') + self.assertEqual(self.app.render_percent(12.3419), "12.34 %") # more decimal places - self.assertEqual(self.app.render_percent(12.3419, decimals=3), '12.342 %') - self.assertEqual(self.app.render_percent(12.3419, decimals=4), '12.3419 %') + self.assertEqual(self.app.render_percent(12.3419, decimals=3), "12.342 %") + self.assertEqual(self.app.render_percent(12.3419, decimals=4), "12.3419 %") # negative - self.assertEqual(self.app.render_percent(-12.3419), '(12.34 %)') - self.assertEqual(self.app.render_percent(-12.3419, decimals=3), '(12.342 %)') + self.assertEqual(self.app.render_percent(-12.3419), "(12.34 %)") + self.assertEqual(self.app.render_percent(-12.3419, decimals=3), "(12.342 %)") def test_render_quantity(self): @@ -525,11 +549,11 @@ app_title = WuttaTest self.assertEqual(self.app.render_quantity(None), "") # integer decimals become integers - value = decimal.Decimal('1.000') + value = decimal.Decimal("1.000") self.assertEqual(self.app.render_quantity(value), "1") # but decimal places are preserved - value = decimal.Decimal('1.234') + value = decimal.Decimal("1.234") self.assertEqual(self.app.render_quantity(value), "1.234") # zero can be empty string @@ -537,20 +561,20 @@ app_title = WuttaTest self.assertEqual(self.app.render_quantity(0, empty_zero=True), "") def test_render_time_ago(self): - with patch.object(mod, 'humanize') as humanize: - humanize.naturaltime.return_value = 'now' + with patch.object(mod, "humanize") as humanize: + humanize.naturaltime.return_value = "now" now = datetime.datetime.now() result = self.app.render_time_ago(now) - self.assertEqual(result, 'now') + self.assertEqual(result, "now") humanize.naturaltime.assert_called_once_with(now) def test_get_person(self): people = self.app.get_people_handler() - with patch.object(people, 'get_person') as get_person: - get_person.return_value = 'foo' - person = self.app.get_person('bar') - get_person.assert_called_once_with('bar') - self.assertEqual(person, 'foo') + with patch.object(people, "get_person") as get_person: + get_person.return_value = "foo" + person = self.app.get_person("bar") + get_person.assert_called_once_with("bar") + self.assertEqual(person, "foo") def test_get_auth_handler(self): from wuttjamaican.auth import AuthHandler @@ -561,55 +585,80 @@ app_title = WuttaTest def test_get_batch_handler(self): # error if handler not found - self.assertRaises(KeyError, self.app.get_batch_handler, 'CannotFindMe!') + self.assertRaises(KeyError, self.app.get_batch_handler, "CannotFindMe!") # caller can specify default - handler = self.app.get_batch_handler('foo', default='wuttjamaican.batch:BatchHandler') + handler = self.app.get_batch_handler( + "foo", default="wuttjamaican.batch:BatchHandler" + ) self.assertIsInstance(handler, BatchHandler) # default can be configured - self.config.setdefault('wuttatest.batch.foo.handler.default_spec', - 'wuttjamaican.batch:BatchHandler') - handler = self.app.get_batch_handler('foo') + self.config.setdefault( + "wuttatest.batch.foo.handler.default_spec", + "wuttjamaican.batch:BatchHandler", + ) + handler = self.app.get_batch_handler("foo") self.assertIsInstance(handler, BatchHandler) # preference can be configured - self.config.setdefault('wuttatest.batch.foo.handler.spec', - 'tests.test_app:MockBatchHandler') - handler = self.app.get_batch_handler('foo') + self.config.setdefault( + "wuttatest.batch.foo.handler.spec", "tests.test_app:MockBatchHandler" + ) + handler = self.app.get_batch_handler("foo") self.assertIsInstance(handler, MockBatchHandler) def test_get_batch_handler_specs(self): # empty by default - specs = self.app.get_batch_handler_specs('foo') + specs = self.app.get_batch_handler_specs("foo") self.assertEqual(specs, []) # caller can specify default as string - specs = self.app.get_batch_handler_specs('foo', default='wuttjamaican.batch:BatchHandler') - self.assertEqual(specs, ['wuttjamaican.batch:BatchHandler']) + specs = self.app.get_batch_handler_specs( + "foo", default="wuttjamaican.batch:BatchHandler" + ) + self.assertEqual(specs, ["wuttjamaican.batch:BatchHandler"]) # caller can specify default as list - specs = self.app.get_batch_handler_specs('foo', default=['wuttjamaican.batch:BatchHandler', - 'tests.test_app:MockBatchHandler']) - self.assertEqual(specs, ['wuttjamaican.batch:BatchHandler', - 'tests.test_app:MockBatchHandler']) + specs = self.app.get_batch_handler_specs( + "foo", + default=[ + "wuttjamaican.batch:BatchHandler", + "tests.test_app:MockBatchHandler", + ], + ) + self.assertEqual( + specs, + ["wuttjamaican.batch:BatchHandler", "tests.test_app:MockBatchHandler"], + ) # default can be configured - self.config.setdefault('wuttatest.batch.foo.handler.default_spec', - 'wuttjamaican.batch:BatchHandler') - specs = self.app.get_batch_handler_specs('foo') - self.assertEqual(specs, ['wuttjamaican.batch:BatchHandler']) + self.config.setdefault( + "wuttatest.batch.foo.handler.default_spec", + "wuttjamaican.batch:BatchHandler", + ) + specs = self.app.get_batch_handler_specs("foo") + self.assertEqual(specs, ["wuttjamaican.batch:BatchHandler"]) # the rest come from entry points - with patch.object(mod, 'load_entry_points', return_value={ - 'mock': MockBatchHandler, - 'another': AnotherBatchHandler, - }): - specs = self.app.get_batch_handler_specs('foo') - self.assertEqual(specs, ['wuttjamaican.batch:BatchHandler', - 'tests.test_app:AnotherBatchHandler', - 'tests.test_app:MockBatchHandler']) + with patch.object( + mod, + "load_entry_points", + return_value={ + "mock": MockBatchHandler, + "another": AnotherBatchHandler, + }, + ): + specs = self.app.get_batch_handler_specs("foo") + self.assertEqual( + specs, + [ + "wuttjamaican.batch:BatchHandler", + "tests.test_app:AnotherBatchHandler", + "tests.test_app:MockBatchHandler", + ], + ) def test_get_db_handler(self): try: @@ -653,15 +702,15 @@ app_title = WuttaTest def test_send_email(self): from wuttjamaican.email import EmailHandler - with patch.object(EmailHandler, 'send_email') as send_email: - self.app.send_email('foo') - send_email.assert_called_once_with('foo') + with patch.object(EmailHandler, "send_email") as send_email: + self.app.send_email("foo") + send_email.assert_called_once_with("foo") class TestAppProvider(TestCase): def setUp(self): - self.config = WuttaConfig(appname='wuttatest') + self.config = WuttaConfig(appname="wuttatest") self.app = mod.AppHandler(self.config) self.config._app = self.app @@ -671,11 +720,11 @@ class TestAppProvider(TestCase): provider = mod.AppProvider(self.config) self.assertIs(provider.config, self.config) self.assertIs(provider.app, self.app) - self.assertEqual(provider.appname, 'wuttatest') + self.assertEqual(provider.appname, "wuttatest") # but can pass app handler instead with warnings.catch_warnings(): - warnings.filterwarnings('ignore', category=DeprecationWarning) + warnings.filterwarnings("ignore", category=DeprecationWarning) provider = mod.AppProvider(self.app) self.assertIs(provider.config, self.config) self.assertIs(provider.app, self.app) @@ -686,17 +735,17 @@ class TestAppProvider(TestCase): pass # nb. we specify *classes* here - fake_providers = {'fake': FakeProvider} + fake_providers = {"fake": FakeProvider} - with patch('wuttjamaican.app.load_entry_points') as load_entry_points: + with patch("wuttjamaican.app.load_entry_points") as load_entry_points: load_entry_points.return_value = fake_providers # sanity check, we get *instances* back from this providers = self.app.get_all_providers() - load_entry_points.assert_called_once_with('wutta.app.providers') + load_entry_points.assert_called_once_with("wutta.app.providers") self.assertEqual(len(providers), 1) - self.assertIn('fake', providers) - self.assertIsInstance(providers['fake'], FakeProvider) + self.assertIn("fake", providers) + self.assertIsInstance(providers["fake"], FakeProvider) def test_hasattr(self): @@ -704,15 +753,15 @@ class TestAppProvider(TestCase): def fake_foo(self): pass - self.app.providers = {'fake': FakeProvider(self.config)} + self.app.providers = {"fake": FakeProvider(self.config)} - self.assertTrue(hasattr(self.app, 'fake_foo')) - self.assertFalse(hasattr(self.app, 'fake_method_does_not_exist')) + self.assertTrue(hasattr(self.app, "fake_foo")) + self.assertFalse(hasattr(self.app, "fake_method_does_not_exist")) def test_getattr(self): # enum - self.assertNotIn('enum', self.app.__dict__) + self.assertNotIn("enum", self.app.__dict__) self.assertIs(self.app.enum, wuttjamaican.enum) # now we test that providers are loaded... @@ -722,12 +771,12 @@ class TestAppProvider(TestCase): return 42 # nb. using instances here - fake_providers = {'fake': FakeProvider(self.config)} + fake_providers = {"fake": FakeProvider(self.config)} - with patch.object(self.app, 'get_all_providers') as get_all_providers: + with patch.object(self.app, "get_all_providers") as get_all_providers: get_all_providers.return_value = fake_providers - self.assertNotIn('providers', self.app.__dict__) + self.assertNotIn("providers", self.app.__dict__) self.assertIs(self.app.providers, fake_providers) get_all_providers.assert_called_once_with() @@ -738,27 +787,27 @@ class TestAppProvider(TestCase): pytest.skip("test not relevant without sqlalchemy") # model - self.assertNotIn('model', self.app.__dict__) + self.assertNotIn("model", self.app.__dict__) self.assertIs(self.app.model, wuttjamaican.db.model) def test_getattr_providers(self): # collection of providers is loaded on demand - self.assertNotIn('providers', self.app.__dict__) + self.assertNotIn("providers", self.app.__dict__) self.assertIsNotNone(self.app.providers) # custom attr does not exist yet - self.assertRaises(AttributeError, getattr, self.app, 'foo_value') + self.assertRaises(AttributeError, getattr, self.app, "foo_value") # but provider can supply the attr - self.app.providers['mytest'] = MagicMock(foo_value='bar') - self.assertEqual(self.app.foo_value, 'bar') + self.app.providers["mytest"] = MagicMock(foo_value="bar") + self.assertEqual(self.app.foo_value, "bar") class TestGenericHandler(ConfigTestCase): def make_config(self, **kw): - kw.setdefault('appname', 'wuttatest') + kw.setdefault("appname", "wuttatest") return super().make_config(**kw) def make_handler(self, **kwargs): @@ -768,34 +817,36 @@ class TestGenericHandler(ConfigTestCase): handler = mod.GenericHandler(self.config) self.assertIs(handler.config, self.config) self.assertIs(handler.app, self.app) - self.assertEqual(handler.appname, 'wuttatest') + self.assertEqual(handler.appname, "wuttatest") def test_get_spec(self): - self.assertEqual(mod.GenericHandler.get_spec(), 'wuttjamaican.app:GenericHandler') + 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={}): + with patch.object(self.app, "providers", new={}): handler = self.make_handler() - self.assertEqual(handler.get_provider_modules('email'), []) + self.assertEqual(handler.get_provider_modules("email"), []) # provider may specify modules as list providers = { - 'wuttatest': MagicMock(email_modules=['wuttjamaican.app']), + "wuttatest": MagicMock(email_modules=["wuttjamaican.app"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() - modules = handler.get_provider_modules('email') + 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'), + "wuttatest": MagicMock(email_modules="wuttjamaican.app"), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() - modules = handler.get_provider_modules('email') + modules = handler.get_provider_modules("email") self.assertEqual(len(modules), 1) self.assertIs(modules[0], mod) diff --git a/tests/test_auth.py b/tests/test_auth.py index c1b048b..f4c0f63 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -11,7 +11,6 @@ except ImportError: pass else: - class TestAuthHandler(TestCase): def setUp(self): @@ -19,7 +18,7 @@ else: self.app = self.config.get_app() self.handler = self.make_handler() - self.engine = sa.create_engine('sqlite://') + self.engine = sa.create_engine("sqlite://") self.app.model.Base.metadata.create_all(bind=self.engine) self.session = self.make_session() @@ -35,37 +34,37 @@ else: def test_authenticate_user(self): model = self.app.model - barney = model.User(username='barney') - self.handler.set_user_password(barney, 'goodpass') + barney = model.User(username="barney") + self.handler.set_user_password(barney, "goodpass") self.session.add(barney) self.session.commit() # login ok - user = self.handler.authenticate_user(self.session, 'barney', 'goodpass') + user = self.handler.authenticate_user(self.session, "barney", "goodpass") self.assertIs(user, barney) # can also pass user instead of username - user = self.handler.authenticate_user(self.session, barney, 'goodpass') + user = self.handler.authenticate_user(self.session, barney, "goodpass") self.assertIs(user, barney) # bad password - user = self.handler.authenticate_user(self.session, 'barney', 'BADPASS') + user = self.handler.authenticate_user(self.session, "barney", "BADPASS") self.assertIsNone(user) # bad username - user = self.handler.authenticate_user(self.session, 'NOBODY', 'goodpass') + user = self.handler.authenticate_user(self.session, "NOBODY", "goodpass") self.assertIsNone(user) # inactive user - user = self.handler.authenticate_user(self.session, 'barney', 'goodpass') + user = self.handler.authenticate_user(self.session, "barney", "goodpass") self.assertIs(user, barney) barney.active = False - user = self.handler.authenticate_user(self.session, 'barney', 'goodpass') + user = self.handler.authenticate_user(self.session, "barney", "goodpass") self.assertIsNone(user) def test_authenticate_user_token(self): model = self.app.model - barney = model.User(username='barney') + barney = model.User(username="barney") self.session.add(barney) token = self.handler.add_api_token(barney, "test token") self.session.commit() @@ -73,32 +72,38 @@ else: user = self.handler.authenticate_user_token(self.session, None) self.assertIsNone(user) - user = self.handler.authenticate_user_token(self.session, token.token_string) + user = self.handler.authenticate_user_token( + self.session, token.token_string + ) self.assertIs(user, barney) barney.active = False self.session.flush() - user = self.handler.authenticate_user_token(self.session, token.token_string) + user = self.handler.authenticate_user_token( + self.session, token.token_string + ) self.assertIsNone(user) barney.active = True self.session.flush() - user = self.handler.authenticate_user_token(self.session, token.token_string) + user = self.handler.authenticate_user_token( + self.session, token.token_string + ) self.assertIs(user, barney) - user = self.handler.authenticate_user_token(self.session, 'bad-token') + user = self.handler.authenticate_user_token(self.session, "bad-token") self.assertIsNone(user) def test_check_user_password(self): model = self.app.model - barney = model.User(username='barney') - self.handler.set_user_password(barney, 'goodpass') + barney = model.User(username="barney") + self.handler.set_user_password(barney, "goodpass") self.session.add(barney) self.session.commit() # basics - self.assertTrue(self.handler.check_user_password(barney, 'goodpass')) - self.assertFalse(self.handler.check_user_password(barney, 'BADPASS')) + self.assertTrue(self.handler.check_user_password(barney, "goodpass")) + self.assertFalse(self.handler.check_user_password(barney, "BADPASS")) def test_get_role(self): model = self.app.model @@ -120,17 +125,17 @@ else: # key may be represented within a setting self.config.usedb = True - role = self.handler.get_role(self.session, 'mykey') + role = self.handler.get_role(self.session, "mykey") self.assertIsNone(role) - setting = model.Setting(name='wutta.role.mykey', value=myrole.uuid.hex) + setting = model.Setting(name="wutta.role.mykey", value=myrole.uuid.hex) self.session.add(setting) self.session.commit() - role = self.handler.get_role(self.session, 'mykey') + role = self.handler.get_role(self.session, "mykey") self.assertIs(role, myrole) def test_get_user(self): model = self.app.model - myuser = model.User(username='myuser') + myuser = model.User(username="myuser") self.session.add(myuser) self.session.commit() @@ -155,7 +160,7 @@ else: self.assertIs(user, myuser) # find user from person - myperson = model.Person(full_name='My Name') + myperson = model.Person(full_name="My Name") self.session.add(myperson) user.person = myperson self.session.commit() @@ -173,11 +178,11 @@ else: self.assertIsNone(person.full_name) self.assertNotIn(person, self.session) - person = handler.make_person(first_name='Barney', last_name='Rubble') + person = handler.make_person(first_name="Barney", last_name="Rubble") self.assertIsInstance(person, model.Person) - self.assertEqual(person.first_name, 'Barney') - self.assertEqual(person.last_name, 'Rubble') - self.assertEqual(person.full_name, 'Barney Rubble') + self.assertEqual(person.first_name, "Barney") + self.assertEqual(person.last_name, "Rubble") + self.assertEqual(person.full_name, "Barney Rubble") self.assertNotIn(person, self.session) def test_make_user(self): @@ -197,13 +202,13 @@ else: # default username # nb. this behavior requires a session user = self.handler.make_user(session=self.session) - self.assertEqual(user.username, 'newuser') + self.assertEqual(user.username, "newuser") def test_delete_user(self): model = self.app.model # basics - myuser = model.User(username='myuser') + myuser = model.User(username="myuser") self.session.add(myuser) self.session.commit() user = self.session.query(model.User).one() @@ -217,67 +222,67 @@ else: # default name = self.handler.make_preferred_username(self.session) - self.assertEqual(name, 'newuser') + self.assertEqual(name, "newuser") # person/first+last - person = model.Person(first_name='Barney', last_name='Rubble') + person = model.Person(first_name="Barney", last_name="Rubble") name = self.handler.make_preferred_username(self.session, person=person) - self.assertEqual(name, 'barney.rubble') + self.assertEqual(name, "barney.rubble") # person/first - person = model.Person(first_name='Barney') + person = model.Person(first_name="Barney") name = self.handler.make_preferred_username(self.session, person=person) - self.assertEqual(name, 'barney') + self.assertEqual(name, "barney") # person/last - person = model.Person(last_name='Rubble') + person = model.Person(last_name="Rubble") name = self.handler.make_preferred_username(self.session, person=person) - self.assertEqual(name, 'rubble') + self.assertEqual(name, "rubble") def test_make_unique_username(self): model = self.app.model # default name = self.handler.make_unique_username(self.session) - self.assertEqual(name, 'newuser') + self.assertEqual(name, "newuser") user = model.User(username=name) self.session.add(user) self.session.commit() # counter invoked if name exists name = self.handler.make_unique_username(self.session) - self.assertEqual(name, 'newuser01') + self.assertEqual(name, "newuser01") user = model.User(username=name) self.session.add(user) self.session.commit() # starts by getting preferred name - person = model.Person(first_name='Barney', last_name='Rubble') + person = model.Person(first_name="Barney", last_name="Rubble") name = self.handler.make_unique_username(self.session, person=person) - self.assertEqual(name, 'barney.rubble') + self.assertEqual(name, "barney.rubble") user = model.User(username=name) self.session.add(user) self.session.commit() # counter invoked if name exists name = self.handler.make_unique_username(self.session, person=person) - self.assertEqual(name, 'barney.rubble01') + self.assertEqual(name, "barney.rubble01") def test_set_user_password(self): model = self.app.model - myuser = model.User(username='myuser') + myuser = model.User(username="myuser") self.session.add(myuser) # basics self.assertIsNone(myuser.password) - self.handler.set_user_password(myuser, 'goodpass') + self.handler.set_user_password(myuser, "goodpass") self.session.commit() self.assertIsNotNone(myuser.password) # nb. password is hashed - self.assertNotEqual(myuser.password, 'goodpass') + self.assertNotEqual(myuser.password, "goodpass") # confirm login works with new password - user = self.handler.authenticate_user(self.session, 'myuser', 'goodpass') + user = self.handler.authenticate_user(self.session, "myuser", "goodpass") self.assertIs(user, myuser) def test_get_role_administrator(self): @@ -337,15 +342,15 @@ else: self.assertEqual(len(perms), 0) # role perms - myrole = model.Role(name='My Role') + myrole = model.Role(name="My Role") self.session.add(myrole) - self.handler.grant_permission(myrole, 'foo') + self.handler.grant_permission(myrole, "foo") self.session.commit() perms = self.handler.get_permissions(self.session, myrole) - self.assertEqual(perms, {'foo'}) + self.assertEqual(perms, {"foo"}) # user perms - myuser = model.User(username='myuser') + myuser = model.User(username="myuser") self.session.add(myuser) self.session.commit() perms = self.handler.get_permissions(self.session, myuser) @@ -353,7 +358,7 @@ else: myuser.roles.append(myrole) self.session.commit() perms = self.handler.get_permissions(self.session, myuser) - self.assertEqual(perms, {'foo'}) + self.assertEqual(perms, {"foo"}) # invalid principal perms = self.handler.get_permissions(self.session, RuntimeError) @@ -368,39 +373,41 @@ else: # false default for role role = model.Role() - self.assertFalse(self.handler.has_permission(self.session, role, 'foo')) + self.assertFalse(self.handler.has_permission(self.session, role, "foo")) # empty default for user user = model.User() - self.assertFalse(self.handler.has_permission(self.session, user, 'foo')) + self.assertFalse(self.handler.has_permission(self.session, user, "foo")) # role perms - myrole = model.Role(name='My Role') + myrole = model.Role(name="My Role") self.session.add(myrole) self.session.commit() - self.assertFalse(self.handler.has_permission(self.session, myrole, 'foo')) - self.handler.grant_permission(myrole, 'foo') + self.assertFalse(self.handler.has_permission(self.session, myrole, "foo")) + self.handler.grant_permission(myrole, "foo") self.session.commit() - self.assertTrue(self.handler.has_permission(self.session, myrole, 'foo')) + self.assertTrue(self.handler.has_permission(self.session, myrole, "foo")) # user perms - myuser = model.User(username='myuser') + myuser = model.User(username="myuser") self.session.add(myuser) self.session.commit() - self.assertFalse(self.handler.has_permission(self.session, myuser, 'foo')) + self.assertFalse(self.handler.has_permission(self.session, myuser, "foo")) myuser.roles.append(myrole) self.session.commit() - self.assertTrue(self.handler.has_permission(self.session, myuser, 'foo')) + self.assertTrue(self.handler.has_permission(self.session, myuser, "foo")) # invalid principal - self.assertFalse(self.handler.has_permission(self.session, RuntimeError, 'foo')) + self.assertFalse( + self.handler.has_permission(self.session, RuntimeError, "foo") + ) # missing principal - self.assertFalse(self.handler.has_permission(self.session, None, 'foo')) + self.assertFalse(self.handler.has_permission(self.session, None, "foo")) def test_grant_permission(self): model = self.app.model - myrole = model.Role(name='My Role') + myrole = model.Role(name="My Role") self.session.add(myrole) self.session.commit() @@ -408,38 +415,38 @@ else: self.assertEqual(self.session.query(model.Permission).count(), 0) # grant one perm, and confirm - self.handler.grant_permission(myrole, 'foo') + self.handler.grant_permission(myrole, "foo") self.session.commit() self.assertEqual(self.session.query(model.Permission).count(), 1) perm = self.session.query(model.Permission).one() self.assertIs(perm.role, myrole) - self.assertEqual(perm.permission, 'foo') + self.assertEqual(perm.permission, "foo") # grant same perm again, confirm just one exists - self.handler.grant_permission(myrole, 'foo') + self.handler.grant_permission(myrole, "foo") self.session.commit() self.assertEqual(self.session.query(model.Permission).count(), 1) perm = self.session.query(model.Permission).one() self.assertIs(perm.role, myrole) - self.assertEqual(perm.permission, 'foo') + self.assertEqual(perm.permission, "foo") def test_revoke_permission(self): model = self.app.model - myrole = model.Role(name='My Role') + myrole = model.Role(name="My Role") self.session.add(myrole) - self.handler.grant_permission(myrole, 'foo') + self.handler.grant_permission(myrole, "foo") self.session.commit() # just the one perm self.assertEqual(self.session.query(model.Permission).count(), 1) # revoke it, then confirm - self.handler.revoke_permission(myrole, 'foo') + self.handler.revoke_permission(myrole, "foo") self.session.commit() self.assertEqual(self.session.query(model.Permission).count(), 0) # revoke again, confirm - self.handler.revoke_permission(myrole, 'foo') + self.handler.revoke_permission(myrole, "foo") self.session.commit() self.assertEqual(self.session.query(model.Permission).count(), 0) @@ -450,7 +457,7 @@ else: def test_add_api_token(self): model = self.app.model - barney = model.User(username='barney') + barney = model.User(username="barney") self.session.add(barney) token = self.handler.add_api_token(barney, "test token") @@ -461,7 +468,7 @@ else: def test_delete_api_token(self): model = self.app.model - barney = model.User(username='barney') + barney = model.User(username="barney") self.session.add(barney) token = self.handler.add_api_token(barney, "test token") self.session.commit() diff --git a/tests/test_batch.py b/tests/test_batch.py index 7a2e643..ce5470a 100644 --- a/tests/test_batch.py +++ b/tests/test_batch.py @@ -14,10 +14,10 @@ except ImportError: else: class MockBatch(model.BatchMixin, model.Base): - __tablename__ = 'testing_batch_mock' + __tablename__ = "testing_batch_mock" class MockBatchRow(model.BatchRowMixin, model.Base): - __tablename__ = 'testing_batch_mock_row' + __tablename__ = "testing_batch_mock_row" __batch_class__ = MockBatch class MockBatchHandler(mod.BatchHandler): @@ -30,12 +30,12 @@ else: def test_model_class(self): handler = mod.BatchHandler(self.config) - self.assertRaises(NotImplementedError, getattr, handler, 'model_class') + self.assertRaises(NotImplementedError, getattr, handler, "model_class") def test_batch_type(self): - with patch.object(mod.BatchHandler, 'model_class', new=MockBatch): + with patch.object(mod.BatchHandler, "model_class", new=MockBatch): handler = mod.BatchHandler(self.config) - self.assertEqual(handler.batch_type, 'testing_batch_mock') + self.assertEqual(handler.batch_type, "testing_batch_mock") def test_make_batch(self): handler = self.make_handler() @@ -50,25 +50,30 @@ else: self.assertEqual(second, first + 1) third = handler.consume_batch_id(self.session, as_str=True) - self.assertEqual(third, f'{first + 2:08d}') + self.assertEqual(third, f"{first + 2:08d}") def test_get_data_path(self): model = self.app.model - user = model.User(username='barney') + user = model.User(username="barney") self.session.add(user) - with patch.object(mod.BatchHandler, 'model_class', new=MockBatch): + with patch.object(mod.BatchHandler, "model_class", new=MockBatch): handler = self.make_handler() # root storage (default) - with patch.object(self.app, 'get_appdir', return_value=self.tempdir): + with patch.object(self.app, "get_appdir", return_value=self.tempdir): path = handler.get_data_path() - self.assertEqual(path, os.path.join(self.tempdir, 'data', 'batch', 'testing_batch_mock')) + self.assertEqual( + path, + os.path.join( + self.tempdir, "data", "batch", "testing_batch_mock" + ), + ) # root storage (configured) - self.config.setdefault('wutta.batch.storage_path', self.tempdir) + self.config.setdefault("wutta.batch.storage_path", self.tempdir) path = handler.get_data_path() - self.assertEqual(path, os.path.join(self.tempdir, 'testing_batch_mock')) + self.assertEqual(path, os.path.join(self.tempdir, "testing_batch_mock")) batch = handler.make_batch(self.session, created_by=user) self.session.add(batch) @@ -78,11 +83,18 @@ else: path = handler.get_data_path(batch) uuid = batch.uuid.hex final = os.path.join(uuid[-2:], uuid[:-2]) - self.assertEqual(path, os.path.join(self.tempdir, 'testing_batch_mock', final)) + self.assertEqual( + path, os.path.join(self.tempdir, "testing_batch_mock", final) + ) # with filename - path = handler.get_data_path(batch, 'input.csv') - self.assertEqual(path, os.path.join(self.tempdir, 'testing_batch_mock', final, 'input.csv')) + path = handler.get_data_path(batch, "input.csv") + self.assertEqual( + path, + os.path.join( + self.tempdir, "testing_batch_mock", final, "input.csv" + ), + ) # makedirs path = handler.get_data_path(batch) @@ -118,7 +130,7 @@ else: def test_remove_row(self): model = self.app.model handler = self.make_handler() - user = model.User(username='barney') + user = model.User(username="barney") self.session.add(user) batch = handler.make_batch(self.session, created_by=user) self.session.add(batch) @@ -134,7 +146,7 @@ else: model = self.app.model handler = self.make_handler() - user = model.User(username='barney') + user = model.User(username="barney") self.session.add(user) batch = handler.make_batch(self.session, created_by=user) self.session.add(batch) @@ -152,7 +164,7 @@ else: def test_do_execute(self): model = self.app.model - user = model.User(username='barney') + user = model.User(username="barney") self.session.add(user) handler = self.make_handler() @@ -161,7 +173,7 @@ else: self.session.flush() # error if execution not allowed - with patch.object(handler, 'why_not_execute', return_value="bad batch"): + with patch.object(handler, "why_not_execute", return_value="bad batch"): self.assertRaises(RuntimeError, handler.do_execute, batch, user) # nb. coverage only; tests nothing @@ -178,7 +190,7 @@ else: model = self.app.model handler = self.make_handler() - user = model.User(username='barney') + user = model.User(username="barney") self.session.add(user) # simple delete @@ -201,13 +213,13 @@ else: self.assertEqual(self.session.query(MockBatch).count(), 0) # delete w/ files - self.config.setdefault('wutta.batch.storage_path', self.tempdir) + self.config.setdefault("wutta.batch.storage_path", self.tempdir) batch = handler.make_batch(self.session, created_by=user) self.session.add(batch) self.session.flush() - path = handler.get_data_path(batch, 'data.txt', makedirs=True) - with open(path, 'wt') as f: - f.write('foo=bar') + path = handler.get_data_path(batch, "data.txt", makedirs=True) + with open(path, "wt") as f: + f.write("foo=bar") self.assertEqual(self.session.query(MockBatch).count(), 1) path = handler.get_data_path(batch) self.assertTrue(os.path.exists(path)) @@ -216,13 +228,13 @@ else: self.assertFalse(os.path.exists(path)) # delete w/ files (dry-run) - self.config.setdefault('wutta.batch.storage_path', self.tempdir) + self.config.setdefault("wutta.batch.storage_path", self.tempdir) batch = handler.make_batch(self.session, created_by=user) self.session.add(batch) self.session.flush() - path = handler.get_data_path(batch, 'data.txt', makedirs=True) - with open(path, 'wt') as f: - f.write('foo=bar') + path = handler.get_data_path(batch, "data.txt", makedirs=True) + with open(path, "wt") as f: + f.write("foo=bar") self.assertEqual(self.session.query(MockBatch).count(), 1) path = handler.get_data_path(batch) self.assertTrue(os.path.exists(path)) diff --git a/tests/test_conf.py b/tests/test_conf.py index 75271ca..4ec5d97 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -8,6 +8,7 @@ from unittest.mock import patch, MagicMock import pytest from wuttjamaican import conf as mod + # TODO: get rid of this eventually from wuttjamaican import conf from wuttjamaican.exc import ConfigurationError @@ -22,33 +23,39 @@ class TestWuttaConfig(FileTestCase): def test_contstructor_basic(self): config = conf.WuttaConfig() - self.assertEqual(config.appname, 'wutta') + self.assertEqual(config.appname, "wutta") self.assertEqual(config.files_read, []) def test_constructor_valid_files(self): - myfile = self.write_file('my.conf', '') + myfile = self.write_file("my.conf", "") config = conf.WuttaConfig(files=[myfile]) self.assertEqual(len(config.files_read), 1) self.assertEqual(config.files_read[0], myfile) def test_constructor_missing_files(self): - invalid = os.path.join(self.tempdir, 'invalid.conf') + invalid = os.path.join(self.tempdir, "invalid.conf") self.assertRaises(FileNotFoundError, conf.WuttaConfig, files=[invalid]) def test_constructor_required_files_are_present(self): - first = self.write_file('first.conf', """\ + first = self.write_file( + "first.conf", + """\ [foo] bar = 1 baz = A -""") +""", + ) - second = self.write_file('second.conf', """\ + second = self.write_file( + "second.conf", + """\ [wutta.config] require = %(here)s/first.conf [foo] baz = B -""") +""", + ) config = conf.WuttaConfig(files=[second]) self.assertEqual(len(config.files_read), 2) @@ -56,34 +63,43 @@ baz = B # same the as order in which files were initially read self.assertEqual(config.files_read[0], second) self.assertEqual(config.files_read[1], first) - self.assertEqual(config.get('foo.bar'), '1') - self.assertEqual(config.get('foo.baz'), 'B') + self.assertEqual(config.get("foo.bar"), "1") + self.assertEqual(config.get("foo.baz"), "B") def test_constructor_required_files_are_missing(self): - second = self.write_file('second.conf', """\ + second = self.write_file( + "second.conf", + """\ [wutta.config] require = %(here)s/first.conf [foo] baz = B -""") +""", + ) self.assertRaises(FileNotFoundError, conf.WuttaConfig, files=[second]) def test_constructor_included_files_are_present(self): - first = self.write_file('first.conf', """\ + first = self.write_file( + "first.conf", + """\ [foo] bar = 1 baz = A -""") +""", + ) - second = self.write_file('second.conf', """\ + second = self.write_file( + "second.conf", + """\ [wutta.config] include = %(here)s/first.conf [foo] baz = B -""") +""", + ) config = conf.WuttaConfig(files=[second]) self.assertEqual(len(config.files_read), 2) @@ -91,46 +107,58 @@ baz = B # same the as order in which files were initially read self.assertEqual(config.files_read[0], second) self.assertEqual(config.files_read[1], first) - self.assertEqual(config.get('foo.bar'), '1') - self.assertEqual(config.get('foo.baz'), 'B') + self.assertEqual(config.get("foo.bar"), "1") + self.assertEqual(config.get("foo.baz"), "B") def test_constructor_included_files_are_missing(self): - second = self.write_file('second.conf', """\ + second = self.write_file( + "second.conf", + """\ [wutta.config] include = %(here)s/first.conf [foo] baz = B -""") +""", + ) config = conf.WuttaConfig(files=[second]) self.assertEqual(len(config.files_read), 1) self.assertEqual(config.files_read[0], second) - self.assertIsNone(config.get('foo.bar')) - self.assertEqual(config.get('foo.baz'), 'B') + self.assertIsNone(config.get("foo.bar")) + self.assertEqual(config.get("foo.baz"), "B") def test_files_only_read_once(self): - base = self.write_file('base.conf', """ + base = self.write_file( + "base.conf", + """ [foo] bar = 1 baz = A -""") +""", + ) - middle = self.write_file('middle.conf', """ + middle = self.write_file( + "middle.conf", + """ [wutta.config] require = %(here)s/base.conf [foo] baz = B -""") +""", + ) - top = self.write_file('top.conf', """ + top = self.write_file( + "top.conf", + """ [wutta.config] require = %(here)s/middle.conf [foo] baz = C -""") +""", + ) config = conf.WuttaConfig(files=[top, middle, base]) self.assertEqual(len(config.files_read), 3) @@ -139,19 +167,25 @@ baz = C self.assertEqual(config.files_read[0], top) self.assertEqual(config.files_read[1], middle) self.assertEqual(config.files_read[2], base) - self.assertEqual(config.get('foo.bar'), '1') - self.assertEqual(config.get('foo.baz'), 'C') + self.assertEqual(config.get("foo.bar"), "1") + self.assertEqual(config.get("foo.baz"), "C") def test_prioritized_files(self): - first = self.write_file('first.conf', """\ + first = self.write_file( + "first.conf", + """\ [foo] bar = 1 -""") +""", + ) - second = self.write_file('second.conf', """\ + second = self.write_file( + "second.conf", + """\ [wutta.config] require = %(here)s/first.conf -""") +""", + ) config = conf.WuttaConfig(files=[second]) files = config.get_prioritized_files() @@ -160,24 +194,27 @@ require = %(here)s/first.conf self.assertEqual(files[1], first) def test_default_vars_interpolated(self): - myconf = self.write_file('my.conf', """ + myconf = self.write_file( + "my.conf", + """ [foo] bar = %(here)s/bar.txt baz = %(__file__)s -""") +""", + ) config = conf.WuttaConfig(files=[myconf]) - self.assertEqual(config.get('foo.bar'), f'{self.tempdir}/bar.txt') - self.assertEqual(config.get('foo.baz'), myconf) + self.assertEqual(config.get("foo.bar"), f"{self.tempdir}/bar.txt") + self.assertEqual(config.get("foo.baz"), myconf) def test_constructor_defaults(self): config = conf.WuttaConfig() self.assertEqual(config.defaults, {}) - self.assertIsNone(config.get('foo')) + self.assertIsNone(config.get("foo")) - config = conf.WuttaConfig(defaults={'foo': 'bar'}) - self.assertEqual(config.defaults, {'foo': 'bar'}) - self.assertEqual(config.get('foo'), 'bar') + config = conf.WuttaConfig(defaults={"foo": "bar"}) + self.assertEqual(config.defaults, {"foo": "bar"}) + self.assertEqual(config.get("foo"), "bar") def test_constructor_db_flags(self): try: @@ -187,11 +224,14 @@ baz = %(__file__)s except ImportError: pytest.skip("test is not relevant without sqlalchemy") - myfile = self.write_file('my.conf', """\ + myfile = self.write_file( + "my.conf", + """\ [wutta.config] usedb = true preferdb = true -""") +""", + ) # flags are off by default config = conf.WuttaConfig() @@ -232,22 +272,25 @@ preferdb = true orig_import = __import__ def mock_import(name, *args, **kwargs): - if name == 'wuttjamaican.db': + if name == "wuttjamaican.db": raise ImportError return orig_import(name, *args, **kwargs) - with patch('builtins.__import__', side_effect=mock_import): + with patch("builtins.__import__", side_effect=mock_import): config = conf.WuttaConfig(usedb=True, preferdb=True) self.assertFalse(config.usedb) self.assertFalse(config.preferdb) def test_constructor_may_configure_logging(self): - myfile = self.write_file('my.conf', """\ + myfile = self.write_file( + "my.conf", + """\ [wutta.config] configure_logging = true -""") +""", + ) - with patch.object(conf.WuttaConfig, '_configure_logging') as method: + with patch.object(conf.WuttaConfig, "_configure_logging") as method: # no logging config by default config = conf.WuttaConfig() @@ -264,15 +307,18 @@ configure_logging = true method.assert_called_once() def test_constructor_configures_logging(self): - myfile = self.write_file('my.conf', """\ + myfile = self.write_file( + "my.conf", + """\ [wutta] timezone.default = America/Chicago [wutta.config] configure_logging = true -""") +""", + ) - with patch('wuttjamaican.conf.logging') as logging: + with patch("wuttjamaican.conf.logging") as logging: # basic constructor attempts logging config config = conf.WuttaConfig(configure_logging=True) @@ -280,7 +326,9 @@ configure_logging = true # if logging config fails, error is *not* raised logging.config.fileConfig.reset_mock() - logging.config.fileConfig.side_effect = configparser.NoSectionError('logging') + logging.config.fileConfig.side_effect = configparser.NoSectionError( + "logging" + ) config = conf.WuttaConfig(configure_logging=True) logging.config.fileConfig.assert_called_once() @@ -294,7 +342,7 @@ configure_logging = true # 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.assertFalse(hasattr(config, "_app")) # but after that we can get an app okay app = config.get_app() @@ -304,29 +352,29 @@ configure_logging = true config = conf.WuttaConfig() # value is empty by default - self.assertIsNone(config.get('foo')) + self.assertIsNone(config.get("foo")) # but we can change that by setting default - config.setdefault('foo', 'bar') - self.assertEqual(config.get('foo'), 'bar') + config.setdefault("foo", "bar") + self.assertEqual(config.get("foo"), "bar") # also, value is returned when we set default - self.assertIsNone(config.get('baz')) - self.assertEqual(config.setdefault('baz', 'blarg'), 'blarg') + self.assertIsNone(config.get("baz")) + self.assertEqual(config.setdefault("baz", "blarg"), "blarg") def test_get_require_with_default(self): config = conf.WuttaConfig() - self.assertRaises(ValueError, config.get, 'foo', require=True, default='bar') + self.assertRaises(ValueError, config.get, "foo", require=True, default="bar") def test_get_require_missing(self): config = conf.WuttaConfig() - self.assertRaises(ConfigurationError, config.get, 'foo', require=True) + self.assertRaises(ConfigurationError, config.get, "foo", require=True) def test_get_with_default(self): config = conf.WuttaConfig() # nb. returns None if no default specified - self.assertIsNone(config.get('foo')) - self.assertEqual(config.get('foo', default='bar'), 'bar') + self.assertIsNone(config.get("foo")) + self.assertEqual(config.get("foo", default="bar"), "bar") def test_get_from_db(self): try: @@ -336,49 +384,53 @@ configure_logging = true pytest.skip("test is not relevant without sqlalchemy") # minimal config, but at least it needs db cxn info - config = conf.WuttaConfig(defaults={'wutta.db.default.url': 'sqlite://'}) + config = conf.WuttaConfig(defaults={"wutta.db.default.url": "sqlite://"}) session = Session() # setup table for testing - session.execute(sa.text(""" + session.execute( + sa.text( + """ create table setting ( name varchar(255) primary key, value text ); - """)) + """ + ) + ) session.commit() # setting not yet defined - self.assertIsNone(config.get_from_db('foo')) + self.assertIsNone(config.get_from_db("foo")) # insert setting value to db session.execute(sa.text("insert into setting values ('foo', 'bar')")) session.commit() # now setting returns a value - self.assertEqual(config.get_from_db('foo'), 'bar') + self.assertEqual(config.get_from_db("foo"), "bar") # also works if we provide the session - self.assertEqual(config.get_from_db('foo', session=session), 'bar') + self.assertEqual(config.get_from_db("foo", session=session), "bar") session.close() def test_get_default(self): config = conf.WuttaConfig() - self.assertIsNone(config.get('foo')) - self.assertEqual(config.get('foo', default='bar'), 'bar') + self.assertIsNone(config.get("foo")) + self.assertEqual(config.get("foo", default="bar"), "bar") def test_get_require(self): config = conf.WuttaConfig() - self.assertIsNone(config.get('foo')) - self.assertRaises(ConfigurationError, config.get, 'foo', require=True) + self.assertIsNone(config.get("foo")) + self.assertRaises(ConfigurationError, config.get, "foo", require=True) def test_get_require_message(self): config = conf.WuttaConfig() - self.assertIsNone(config.get('foo')) + self.assertIsNone(config.get("foo")) try: - config.get('foo', require=True, message="makin stuff up") + config.get("foo", require=True, message="makin stuff up") except ConfigurationError as error: self.assertIn("makin stuff up", str(error)) @@ -390,37 +442,42 @@ configure_logging = true pytest.skip("test is not relevant without sqlalchemy") # start out with a default value - config = conf.WuttaConfig(defaults={'wutta.db.default.url': 'sqlite://', - 'foo': 'bar'}) - self.assertEqual(config.get('foo'), 'bar') + config = conf.WuttaConfig( + defaults={"wutta.db.default.url": "sqlite://", "foo": "bar"} + ) + self.assertEqual(config.get("foo"), "bar") session = Session() # setup table for testing - session.execute(sa.text(""" + session.execute( + sa.text( + """ create table setting ( name varchar(255) primary key, value text ); - """)) + """ + ) + ) session.execute(sa.text("insert into setting values ('foo', 'baz')")) session.commit() # we did not specify usedb=True, so original default is still returned self.assertFalse(config.usedb) - self.assertEqual(config.get('foo'), 'bar') + self.assertEqual(config.get("foo"), "bar") # usedb but no preferdb means original default is still returned - self.assertEqual(config.get('foo', usedb=True), 'bar') + self.assertEqual(config.get("foo", usedb=True), "bar") # but preferdb should mean newer db value is returned - self.assertEqual(config.get('foo', usedb=True, preferdb=True), 'baz') + self.assertEqual(config.get("foo", usedb=True, preferdb=True), "baz") # try a different key to ensure db fallback works if no default present session.execute(sa.text("insert into setting values ('blarg', 'blitz')")) session.commit() - self.assertIsNone(config.get('blarg')) - self.assertEqual(config.get('blarg', usedb=True), 'blitz') + self.assertIsNone(config.get("blarg")) + self.assertEqual(config.get("blarg", usedb=True), "blitz") session.close() @@ -428,34 +485,34 @@ configure_logging = true config = conf.WuttaConfig() # value is returned if key is not ambiguous - config.setdefault('foo', 'bar') - self.assertEqual(config.get('foo'), 'bar') + config.setdefault("foo", "bar") + self.assertEqual(config.get("foo"), "bar") # but None is returned if key is ambiguous - config.setdefault('foo.bar', 'baz') - self.assertIsNone(config.get('foo')) + config.setdefault("foo.bar", "baz") + self.assertIsNone(config.get("foo")) def test_require(self): config = conf.WuttaConfig() - self.assertRaises(ConfigurationError, config.require, 'foo') + self.assertRaises(ConfigurationError, config.require, "foo") def test_get_bool(self): config = conf.WuttaConfig() - self.assertFalse(config.get_bool('foo.bar')) - config.setdefault('foo.bar', 'true') - self.assertTrue(config.get_bool('foo.bar')) + self.assertFalse(config.get_bool("foo.bar")) + config.setdefault("foo.bar", "true") + self.assertTrue(config.get_bool("foo.bar")) def test_get_int(self): config = conf.WuttaConfig() - self.assertIsNone(config.get_int('foo.bar')) - config.setdefault('foo.bar', '42') - self.assertEqual(config.get_int('foo.bar'), 42) + self.assertIsNone(config.get_int("foo.bar")) + config.setdefault("foo.bar", "42") + self.assertEqual(config.get_int("foo.bar"), 42) def test_get_list(self): config = conf.WuttaConfig() - self.assertIsNone(config.get_list('foo.bar')) - config.setdefault('foo.bar', 'hello world') - self.assertEqual(config.get_list('foo.bar'), ['hello', 'world']) + self.assertIsNone(config.get_list("foo.bar")) + config.setdefault("foo.bar", "hello world") + self.assertEqual(config.get_list("foo.bar"), ["hello", "world"]) def test_parse_bool_null(self): config = self.make_config() @@ -468,21 +525,21 @@ configure_logging = true def test_parse_bool_string_true(self): config = self.make_config() - self.assertTrue(config.parse_bool('true')) - self.assertTrue(config.parse_bool('yes')) - self.assertTrue(config.parse_bool('y')) - self.assertTrue(config.parse_bool('on')) - self.assertTrue(config.parse_bool('1')) + self.assertTrue(config.parse_bool("true")) + self.assertTrue(config.parse_bool("yes")) + self.assertTrue(config.parse_bool("y")) + self.assertTrue(config.parse_bool("on")) + self.assertTrue(config.parse_bool("1")) def test_parse_bool_string_false(self): config = self.make_config() - self.assertFalse(config.parse_bool('false')) - self.assertFalse(config.parse_bool('no')) - self.assertFalse(config.parse_bool('n')) - self.assertFalse(config.parse_bool('off')) - self.assertFalse(config.parse_bool('0')) + self.assertFalse(config.parse_bool("false")) + self.assertFalse(config.parse_bool("no")) + self.assertFalse(config.parse_bool("n")) + self.assertFalse(config.parse_bool("off")) + self.assertFalse(config.parse_bool("0")) # nb. assume false for unrecognized input - self.assertFalse(config.parse_bool('whatever-else')) + self.assertFalse(config.parse_bool("whatever-else")) def test_parse_list_null(self): config = self.make_config() @@ -498,75 +555,83 @@ configure_logging = true def test_parse_list_single_value(self): config = self.make_config() - value = config.parse_list('foo') + value = config.parse_list("foo") self.assertEqual(len(value), 1) - self.assertEqual(value[0], 'foo') + self.assertEqual(value[0], "foo") def test_parse_list_single_value_padded_by_spaces(self): config = self.make_config() - value = config.parse_list(' foo ') + value = config.parse_list(" foo ") self.assertEqual(len(value), 1) - self.assertEqual(value[0], 'foo') + self.assertEqual(value[0], "foo") def test_parse_list_slash_is_not_a_separator(self): config = self.make_config() - value = config.parse_list('/dev/null') + value = config.parse_list("/dev/null") self.assertEqual(len(value), 1) - self.assertEqual(value[0], '/dev/null') + self.assertEqual(value[0], "/dev/null") def test_parse_list_multiple_values_separated_by_whitespace(self): config = self.make_config() - value = config.parse_list('foo bar baz') + value = config.parse_list("foo bar baz") self.assertEqual(len(value), 3) - self.assertEqual(value[0], 'foo') - self.assertEqual(value[1], 'bar') - self.assertEqual(value[2], 'baz') + self.assertEqual(value[0], "foo") + self.assertEqual(value[1], "bar") + self.assertEqual(value[2], "baz") def test_parse_list_multiple_values_separated_by_commas(self): config = self.make_config() - value = config.parse_list('foo,bar,baz') + value = config.parse_list("foo,bar,baz") self.assertEqual(len(value), 3) - self.assertEqual(value[0], 'foo') - self.assertEqual(value[1], 'bar') - self.assertEqual(value[2], 'baz') + self.assertEqual(value[0], "foo") + self.assertEqual(value[1], "bar") + self.assertEqual(value[2], "baz") def test_parse_list_multiple_values_separated_by_whitespace_and_commas(self): config = self.make_config() - value = config.parse_list(' foo, bar baz') + value = config.parse_list(" foo, bar baz") self.assertEqual(len(value), 3) - self.assertEqual(value[0], 'foo') - self.assertEqual(value[1], 'bar') - self.assertEqual(value[2], 'baz') + self.assertEqual(value[0], "foo") + self.assertEqual(value[1], "bar") + self.assertEqual(value[2], "baz") - def test_parse_list_multiple_values_separated_by_whitespace_and_commas_with_some_quoting(self): + def test_parse_list_multiple_values_separated_by_whitespace_and_commas_with_some_quoting( + self, + ): config = self.make_config() - value = config.parse_list(""" + value = config.parse_list( + """ foo "C:\\some path\\with spaces\\and, a comma", baz - """) + """ + ) self.assertEqual(len(value), 3) - self.assertEqual(value[0], 'foo') - self.assertEqual(value[1], 'C:\\some path\\with spaces\\and, a comma') - self.assertEqual(value[2], 'baz') + self.assertEqual(value[0], "foo") + self.assertEqual(value[1], "C:\\some path\\with spaces\\and, a comma") + self.assertEqual(value[2], "baz") - def test_parse_list_multiple_values_separated_by_whitespace_and_commas_with_single_quotes(self): + def test_parse_list_multiple_values_separated_by_whitespace_and_commas_with_single_quotes( + self, + ): config = self.make_config() - value = config.parse_list(""" + value = config.parse_list( + """ foo 'C:\\some path\\with spaces\\and, a comma', baz - """) + """ + ) self.assertEqual(len(value), 3) - self.assertEqual(value[0], 'foo') - self.assertEqual(value[1], 'C:\\some path\\with spaces\\and, a comma') - self.assertEqual(value[2], 'baz') + self.assertEqual(value[0], "foo") + self.assertEqual(value[1], "C:\\some path\\with spaces\\and, a comma") + 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') + self.assertEqual(config.default_app_handler_spec, "wuttjamaican.app:AppHandler") app = config.get_app() self.assertIsInstance(app, AppHandler) # nb. make extra sure we didn't get a subclass @@ -574,7 +639,7 @@ configure_logging = true # custom default handler config = conf.WuttaConfig() - config.default_app_handler_spec = 'tests.test_conf:CustomAppHandler' + config.default_app_handler_spec = "tests.test_conf:CustomAppHandler" app = config.get_app() self.assertIsInstance(app, CustomAppHandler) @@ -586,13 +651,18 @@ configure_logging = true # default func config = conf.WuttaConfig() - self.assertEqual(config.default_engine_maker_spec, 'wuttjamaican.db.conf:make_engine_from_config') + self.assertEqual( + config.default_engine_maker_spec, + "wuttjamaican.db.conf:make_engine_from_config", + ) make_engine = config.get_engine_maker() self.assertIs(make_engine, make_engine_from_config) # custom default func config = conf.WuttaConfig() - config.default_engine_maker_spec = 'tests.test_conf:custom_make_engine_from_config' + config.default_engine_maker_spec = ( + "tests.test_conf:custom_make_engine_from_config" + ) make_engine = config.get_engine_maker() self.assertIs(make_engine, custom_make_engine_from_config) @@ -603,7 +673,7 @@ configure_logging = true self.assertFalse(config.production()) # but config may specify - config.setdefault('wutta.production', 'true') + config.setdefault("wutta.production", "true") self.assertTrue(config.production()) @@ -621,40 +691,40 @@ class TestWuttaConfigExtension(TestCase): config = conf.WuttaConfig() ext = conf.WuttaConfigExtension() self.assertIsNone(ext.key) - self.assertEqual(repr(ext), 'WuttaConfigExtension(key=None)') + self.assertEqual(repr(ext), "WuttaConfigExtension(key=None)") class TestGenericDefaultFiles(TestCase): def test_linux(self): - files = conf.generic_default_files('wuttatest') + files = conf.generic_default_files("wuttatest") self.assertIsInstance(files, list) self.assertTrue(len(files) > 1) - self.assertIn('/etc/wuttatest.conf', files) + self.assertIn("/etc/wuttatest.conf", files) def test_win32(self): win32com = MagicMock() - win32com.shell.SHGetSpecialFolderPath.return_value = r'C:' + os.sep - with patch.dict('sys.modules', **{'win32com.shell': win32com}): - with patch('wuttjamaican.conf.sys', platform='win32'): + 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') + files = conf.generic_default_files("wuttatest") self.assertIsInstance(files, list) self.assertTrue(len(files) > 1) - self.assertIn(os.path.join('C:', 'wuttatest.conf'), files) + self.assertIn(os.path.join("C:", "wuttatest.conf"), files) def test_win32_broken(self): orig_import = __import__ def mock_import(name, *args, **kwargs): - if name == 'win32com.shell': + if name == "win32com.shell": raise ImportError return orig_import(name, *args, **kwargs) - with patch('builtins.__import__', side_effect=mock_import): - with patch('wuttjamaican.conf.sys', platform='win32'): + with patch("builtins.__import__", side_effect=mock_import): + with patch("wuttjamaican.conf.sys", platform="win32"): - files = conf.generic_default_files('wuttatest') + files = conf.generic_default_files("wuttatest") self.assertIsInstance(files, list) self.assertEqual(len(files), 0) @@ -662,18 +732,23 @@ class TestGenericDefaultFiles(TestCase): class TestGetConfigPaths(FileTestCase): def test_winsvc(self): - myconf = self.write_file('my.conf', """ + myconf = self.write_file( + "my.conf", + """ [wutta.config] winsvc.RattailFileMonitor = /path/to/other/file -""") +""", + ) - files = conf.get_config_paths(files=[myconf], winsvc='RattailFileMonitor') - self.assertEqual(files, ['/path/to/other/file']) + files = conf.get_config_paths(files=[myconf], winsvc="RattailFileMonitor") + self.assertEqual(files, ["/path/to/other/file"]) def test_nonexistent_default_files(self): - files = conf.get_config_paths(files=None, - env_files_name='IGNORE_THIS', - default_files=['/this/does/not/exist']) + files = conf.get_config_paths( + files=None, + env_files_name="IGNORE_THIS", + default_files=["/this/does/not/exist"], + ) self.assertEqual(files, []) @@ -684,186 +759,208 @@ class TestMakeConfig(FileTestCase): # on the dev machine def test_generic_default_files(self): - generic = self.write_file('generic.conf', '') + generic = self.write_file("generic.conf", "") - with patch('wuttjamaican.conf.generic_default_files') as generic_default_files: - with patch('wuttjamaican.conf.WuttaConfig') as WuttaConfig: + 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') - generic_default_files.assert_called_once_with('wuttatest') - WuttaConfig.assert_called_once_with([generic], appname='wuttatest', - usedb=None, preferdb=None) + config = conf.make_config(appname="wuttatest") + generic_default_files.assert_called_once_with("wuttatest") + WuttaConfig.assert_called_once_with( + [generic], appname="wuttatest", usedb=None, preferdb=None + ) # make sure empty defaults works too generic_default_files.reset_mock() generic_default_files.return_value = [] WuttaConfig.reset_mock() - config = conf.make_config(appname='wuttatest') - generic_default_files.assert_called_once_with('wuttatest') - WuttaConfig.assert_called_once_with([], appname='wuttatest', - usedb=None, preferdb=None) + config = conf.make_config(appname="wuttatest") + generic_default_files.assert_called_once_with("wuttatest") + WuttaConfig.assert_called_once_with( + [], appname="wuttatest", usedb=None, preferdb=None + ) def test_specify_default_files(self): - generic = self.write_file('generic.conf', '') - myfile = self.write_file('my.conf', '') + generic = self.write_file("generic.conf", "") + myfile = self.write_file("my.conf", "") - with patch('wuttjamaican.conf.generic_default_files') as generic_default_files: - with patch('wuttjamaican.conf.WuttaConfig') as WuttaConfig: + 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') - generic_default_files.assert_called_once_with('wuttatest') - WuttaConfig.assert_called_once_with([generic], appname='wuttatest', - usedb=None, preferdb=None) + config = conf.make_config(appname="wuttatest") + generic_default_files.assert_called_once_with("wuttatest") + WuttaConfig.assert_called_once_with( + [generic], appname="wuttatest", usedb=None, preferdb=None + ) # can specify single default file generic_default_files.reset_mock() WuttaConfig.reset_mock() - config = conf.make_config(appname='wuttatest', default_files=myfile) + config = conf.make_config(appname="wuttatest", default_files=myfile) generic_default_files.assert_not_called() - WuttaConfig.assert_called_once_with([myfile], appname='wuttatest', - usedb=None, preferdb=None) + WuttaConfig.assert_called_once_with( + [myfile], appname="wuttatest", usedb=None, preferdb=None + ) # can specify default files as list generic_default_files.reset_mock() WuttaConfig.reset_mock() - config = conf.make_config(appname='wuttatest', default_files=[myfile]) + config = conf.make_config(appname="wuttatest", default_files=[myfile]) generic_default_files.assert_not_called() - WuttaConfig.assert_called_once_with([myfile], appname='wuttatest', - usedb=None, preferdb=None) + WuttaConfig.assert_called_once_with( + [myfile], appname="wuttatest", usedb=None, preferdb=None + ) # can specify default files as callable generic_default_files.reset_mock() WuttaConfig.reset_mock() - config = conf.make_config(appname='wuttatest', default_files=lambda appname: [myfile]) + config = conf.make_config( + appname="wuttatest", default_files=lambda appname: [myfile] + ) generic_default_files.assert_not_called() - WuttaConfig.assert_called_once_with([myfile], appname='wuttatest', - usedb=None, preferdb=None) + WuttaConfig.assert_called_once_with( + [myfile], appname="wuttatest", usedb=None, preferdb=None + ) def test_specify_plus_files(self): - generic = self.write_file('generic.conf', '') - myfile = self.write_file('my.conf', '') + generic = self.write_file("generic.conf", "") + myfile = self.write_file("my.conf", "") - with patch('wuttjamaican.conf.generic_default_files') as generic_default_files: - with patch('wuttjamaican.conf.WuttaConfig') as WuttaConfig: + 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 - config = conf.make_config(appname='wuttatest') - generic_default_files.assert_called_once_with('wuttatest') - WuttaConfig.assert_called_once_with([generic], appname='wuttatest', - usedb=None, preferdb=None) + config = conf.make_config(appname="wuttatest") + generic_default_files.assert_called_once_with("wuttatest") + WuttaConfig.assert_called_once_with( + [generic], appname="wuttatest", usedb=None, preferdb=None + ) # can specify single plus file generic_default_files.reset_mock() WuttaConfig.reset_mock() - config = conf.make_config(appname='wuttatest', plus_files=myfile) - generic_default_files.assert_called_once_with('wuttatest') - WuttaConfig.assert_called_once_with([generic, myfile], appname='wuttatest', - usedb=None, preferdb=None) + config = conf.make_config(appname="wuttatest", plus_files=myfile) + generic_default_files.assert_called_once_with("wuttatest") + WuttaConfig.assert_called_once_with( + [generic, myfile], appname="wuttatest", usedb=None, preferdb=None + ) # can specify plus files as list generic_default_files.reset_mock() WuttaConfig.reset_mock() - config = conf.make_config(appname='wuttatest', plus_files=[myfile]) - generic_default_files.assert_called_once_with('wuttatest') - WuttaConfig.assert_called_once_with([generic, myfile], appname='wuttatest', - usedb=None, preferdb=None) + config = conf.make_config(appname="wuttatest", plus_files=[myfile]) + generic_default_files.assert_called_once_with("wuttatest") + WuttaConfig.assert_called_once_with( + [generic, myfile], appname="wuttatest", usedb=None, preferdb=None + ) # can specify plus files via env generic_default_files.reset_mock() WuttaConfig.reset_mock() - config = conf.make_config(appname='wuttatest', - env={'WUTTATEST_CONFIG_PLUS_FILES': myfile}) - generic_default_files.assert_called_once_with('wuttatest') - WuttaConfig.assert_called_once_with([generic, myfile], appname='wuttatest', - usedb=None, preferdb=None) + config = conf.make_config( + appname="wuttatest", env={"WUTTATEST_CONFIG_PLUS_FILES": myfile} + ) + generic_default_files.assert_called_once_with("wuttatest") + WuttaConfig.assert_called_once_with( + [generic, myfile], appname="wuttatest", usedb=None, preferdb=None + ) def test_specify_primary_files(self): - generic = self.write_file('generic.conf', '') - myfile = self.write_file('my.conf', '') + generic = self.write_file("generic.conf", "") + myfile = self.write_file("my.conf", "") - with patch('wuttjamaican.conf.generic_default_files') as generic_default_files: - with patch('wuttjamaican.conf.WuttaConfig') as WuttaConfig: + 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 - config = conf.make_config(appname='wuttatest') - generic_default_files.assert_called_once_with('wuttatest') - WuttaConfig.assert_called_once_with([generic], appname='wuttatest', - usedb=None, preferdb=None) + config = conf.make_config(appname="wuttatest") + generic_default_files.assert_called_once_with("wuttatest") + WuttaConfig.assert_called_once_with( + [generic], appname="wuttatest", usedb=None, preferdb=None + ) # can specify single primary file (nb. no default files) generic_default_files.reset_mock() WuttaConfig.reset_mock() - config = conf.make_config(myfile, appname='wuttatest') + config = conf.make_config(myfile, appname="wuttatest") generic_default_files.assert_not_called() - WuttaConfig.assert_called_once_with([myfile], appname='wuttatest', - usedb=None, preferdb=None) + WuttaConfig.assert_called_once_with( + [myfile], appname="wuttatest", usedb=None, preferdb=None + ) # can specify primary files as list generic_default_files.reset_mock() WuttaConfig.reset_mock() - config = conf.make_config([myfile], appname='wuttatest') + config = conf.make_config([myfile], appname="wuttatest") generic_default_files.assert_not_called() - WuttaConfig.assert_called_once_with([myfile], appname='wuttatest', - usedb=None, preferdb=None) + WuttaConfig.assert_called_once_with( + [myfile], appname="wuttatest", usedb=None, preferdb=None + ) # can specify primary files via env generic_default_files.reset_mock() WuttaConfig.reset_mock() - config = conf.make_config(appname='wuttatest', - env={'WUTTATEST_CONFIG_FILES': myfile}) + config = conf.make_config( + appname="wuttatest", env={"WUTTATEST_CONFIG_FILES": myfile} + ) generic_default_files.assert_not_called() - WuttaConfig.assert_called_once_with([myfile], appname='wuttatest', - usedb=None, preferdb=None) + WuttaConfig.assert_called_once_with( + [myfile], appname="wuttatest", usedb=None, preferdb=None + ) def test_extensions(self): - generic = self.write_file('generic.conf', '') - myfile = self.write_file('my.conf', '') + generic = self.write_file("generic.conf", "") + myfile = self.write_file("my.conf", "") - with patch.object(mod, 'WuttaConfig') as WuttaConfig: - with patch.object(mod, 'load_entry_points') as load_entry_points: + 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([], appname='wuttatest', - usedb=None, preferdb=None) + config = mod.make_config(appname="wuttatest", extend=False) + WuttaConfig.assert_called_once_with( + [], appname="wuttatest", usedb=None, preferdb=None + ) load_entry_points.assert_not_called() # confirm entry points for default appname load_entry_points.reset_mock() WuttaConfig.reset_mock() - config = mod.make_config([], appname='wutta') - WuttaConfig.assert_called_once_with([], appname='wutta', - usedb=None, preferdb=None) - load_entry_points.assert_called_once_with('wutta.config.extensions') + config = mod.make_config([], appname="wutta") + WuttaConfig.assert_called_once_with( + [], appname="wutta", usedb=None, preferdb=None + ) + load_entry_points.assert_called_once_with("wutta.config.extensions") # confirm entry points for custom appname load_entry_points.reset_mock() WuttaConfig.reset_mock() - config = mod.make_config(appname='wuttatest') - WuttaConfig.assert_called_once_with([], appname='wuttatest', - usedb=None, preferdb=None) - load_entry_points.assert_called_once_with('wuttatest.config.extensions') + config = mod.make_config(appname="wuttatest") + WuttaConfig.assert_called_once_with( + [], appname="wuttatest", usedb=None, preferdb=None + ) + load_entry_points.assert_called_once_with("wuttatest.config.extensions") # confirm extensions are invoked load_entry_points.reset_mock() foo_obj = MagicMock() foo_cls = MagicMock(return_value=foo_obj) - load_entry_points.return_value = {'foo': foo_cls} + load_entry_points.return_value = {"foo": foo_cls} WuttaConfig.reset_mock() testconfig = MagicMock() WuttaConfig.return_value = testconfig - config = mod.make_config(appname='wuttatest') - WuttaConfig.assert_called_once_with([], appname='wuttatest', - usedb=None, preferdb=None) - load_entry_points.assert_called_once_with('wuttatest.config.extensions') + config = mod.make_config(appname="wuttatest") + WuttaConfig.assert_called_once_with( + [], appname="wuttatest", usedb=None, preferdb=None + ) + load_entry_points.assert_called_once_with("wuttatest.config.extensions") foo_cls.assert_called_once_with() foo_obj.configure.assert_called_once_with(testconfig) foo_obj.startup.assert_called_once_with(testconfig) @@ -875,13 +972,13 @@ class TestWuttaConfigProfile(ConfigTestCase): return mod.WuttaConfigProfile(self.config, key) def test_section(self): - profile = self.make_profile('default') - self.assertRaises(NotImplementedError, getattr, profile, 'section') + profile = self.make_profile("default") + self.assertRaises(NotImplementedError, getattr, profile, "section") def test_get_str(self): - self.config.setdefault('wutta.telemetry.default.submit_url', '/nodes/telemetry') - with patch.object(mod.WuttaConfigProfile, 'section', new='wutta.telemetry'): - profile = self.make_profile('default') - self.assertEqual(profile.section, 'wutta.telemetry') - url = profile.get_str('submit_url') - self.assertEqual(url, '/nodes/telemetry') + self.config.setdefault("wutta.telemetry.default.submit_url", "/nodes/telemetry") + with patch.object(mod.WuttaConfigProfile, "section", new="wutta.telemetry"): + profile = self.make_profile("default") + self.assertEqual(profile.section, "wutta.telemetry") + url = profile.get_str("submit_url") + self.assertEqual(url, "/nodes/telemetry") diff --git a/tests/test_email.py b/tests/test_email.py index 6e1a72d..e4b6ff9 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -18,7 +18,7 @@ class TestEmailSetting(ConfigTestCase): setting = mod.EmailSetting(self.config) self.assertIs(setting.config, self.config) self.assertIs(setting.app, self.app) - self.assertEqual(setting.key, 'EmailSetting') + self.assertEqual(setting.key, "EmailSetting") def test_sample_data(self): setting = mod.EmailSetting(self.config) @@ -34,23 +34,23 @@ class TestMessage(FileTestCase): msg = self.make_message() # set as list - recips = msg.get_recips(['sally@example.com']) - self.assertEqual(recips, ['sally@example.com']) + recips = msg.get_recips(["sally@example.com"]) + self.assertEqual(recips, ["sally@example.com"]) # set as tuple - recips = msg.get_recips(('barney@example.com',)) - self.assertEqual(recips, ['barney@example.com']) + recips = msg.get_recips(("barney@example.com",)) + self.assertEqual(recips, ["barney@example.com"]) # set as string - recips = msg.get_recips('wilma@example.com') - self.assertEqual(recips, ['wilma@example.com']) + recips = msg.get_recips("wilma@example.com") + self.assertEqual(recips, ["wilma@example.com"]) # set as null recips = msg.get_recips(None) self.assertEqual(recips, []) # otherwise error - self.assertRaises(ValueError, msg.get_recips, {'foo': 'foo@example.com'}) + self.assertRaises(ValueError, msg.get_recips, {"foo": "foo@example.com"}) def test_as_string(self): @@ -59,38 +59,44 @@ class TestMessage(FileTestCase): self.assertRaises(ValueError, msg.as_string) # txt body - msg = self.make_message(sender='bob@example.com', - txt_body="hello world") + msg = self.make_message(sender="bob@example.com", txt_body="hello world") complete = msg.as_string() - self.assertIn('From: bob@example.com', complete) + self.assertIn("From: bob@example.com", complete) # html body - msg = self.make_message(sender='bob@example.com', - html_body="

hello world

") + msg = self.make_message( + sender="bob@example.com", html_body="

hello world

" + ) complete = msg.as_string() - self.assertIn('From: bob@example.com', complete) + self.assertIn("From: bob@example.com", complete) # txt + html body - msg = self.make_message(sender='bob@example.com', - txt_body="hello world", - html_body="

hello world

") + msg = self.make_message( + sender="bob@example.com", + txt_body="hello world", + html_body="

hello world

", + ) complete = msg.as_string() - self.assertIn('From: bob@example.com', complete) + self.assertIn("From: bob@example.com", complete) # html + attachment - csv_part = MIMEText("foo,bar\n1,2", 'csv', 'utf_8') - msg = self.make_message(sender='bob@example.com', - html_body="

hello world

", - attachments=[csv_part]) + csv_part = MIMEText("foo,bar\n1,2", "csv", "utf_8") + msg = self.make_message( + sender="bob@example.com", + html_body="

hello world

", + attachments=[csv_part], + ) complete = msg.as_string() - self.assertIn('Content-Type: multipart/mixed; boundary=', complete) + self.assertIn("Content-Type: multipart/mixed; boundary=", complete) self.assertIn('Content-Type: text/csv; charset="utf_8"', complete) # error if improper attachment - csv_path = self.write_file('data.csv', "foo,bar\n1,2") - msg = self.make_message(sender='bob@example.com', - html_body="

hello world

", - attachments=[csv_path]) + csv_path = self.write_file("data.csv", "foo,bar\n1,2") + msg = self.make_message( + sender="bob@example.com", + html_body="

hello world

", + attachments=[csv_path], + ) self.assertRaises(ValueError, msg.as_string) try: msg.as_string() @@ -98,27 +104,30 @@ class TestMessage(FileTestCase): self.assertIn("must specify valid MIME attachments", str(err)) # everything - msg = self.make_message(sender='bob@example.com', - subject='meeting follow-up', - to='sally@example.com', - cc='marketing@example.com', - bcc='bob@example.com', - replyto='sales@example.com', - txt_body="hello world", - html_body="

hello world

") + msg = self.make_message( + sender="bob@example.com", + subject="meeting follow-up", + to="sally@example.com", + cc="marketing@example.com", + bcc="bob@example.com", + replyto="sales@example.com", + txt_body="hello world", + html_body="

hello world

", + ) complete = msg.as_string() - self.assertIn('From: bob@example.com', complete) - self.assertIn('Subject: meeting follow-up', complete) - self.assertIn('To: sally@example.com', complete) - self.assertIn('Cc: marketing@example.com', complete) - self.assertIn('Bcc: bob@example.com', complete) - self.assertIn('Reply-To: sales@example.com', complete) + self.assertIn("From: bob@example.com", complete) + self.assertIn("Subject: meeting follow-up", complete) + self.assertIn("To: sally@example.com", complete) + self.assertIn("Cc: marketing@example.com", complete) + self.assertIn("Bcc: bob@example.com", complete) + self.assertIn("Reply-To: sales@example.com", complete) class mock_foo(mod.EmailSetting): default_subject = "MOCK FOO!" + def sample_data(self): - return {'foo': 'mock'} + return {"foo": "mock"} class TestEmailHandler(ConfigTestCase): @@ -129,43 +138,43 @@ class TestEmailHandler(ConfigTestCase): def test_constructor_lookups(self): # empty lookup paths by default, if no providers - with patch.object(self.app, 'providers', new={}): + with patch.object(self.app, "providers", new={}): handler = self.make_handler() self.assertEqual(handler.txt_templates.directories, []) self.assertEqual(handler.html_templates.directories, []) # provider may specify paths as list providers = { - 'wuttatest': MagicMock(email_templates=['wuttjamaican:email-templates']), + "wuttatest": MagicMock(email_templates=["wuttjamaican:email-templates"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() - path = resource_path('wuttjamaican:email-templates') + path = resource_path("wuttjamaican:email-templates") self.assertEqual(handler.txt_templates.directories, [path]) self.assertEqual(handler.html_templates.directories, [path]) # provider may specify paths as string providers = { - 'wuttatest': MagicMock(email_templates='wuttjamaican:email-templates'), + "wuttatest": MagicMock(email_templates="wuttjamaican:email-templates"), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() - path = resource_path('wuttjamaican:email-templates') + path = resource_path("wuttjamaican:email-templates") self.assertEqual(handler.txt_templates.directories, [path]) self.assertEqual(handler.html_templates.directories, [path]) def test_get_email_modules(self): # no providers, no email modules - with patch.object(self.app, 'providers', new={}): + with patch.object(self.app, "providers", new={}): handler = self.make_handler() self.assertEqual(handler.get_email_modules(), []) # provider may specify modules as list providers = { - 'wuttatest': MagicMock(email_modules=['wuttjamaican.email']), + "wuttatest": MagicMock(email_modules=["wuttjamaican.email"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() modules = handler.get_email_modules() self.assertEqual(len(modules), 1) @@ -173,9 +182,9 @@ class TestEmailHandler(ConfigTestCase): # provider may specify modules as string providers = { - 'wuttatest': MagicMock(email_modules='wuttjamaican.email'), + "wuttatest": MagicMock(email_modules="wuttjamaican.email"), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() modules = handler.get_email_modules() self.assertEqual(len(modules), 1) @@ -184,36 +193,36 @@ class TestEmailHandler(ConfigTestCase): def test_get_email_settings(self): # no providers, no email settings - with patch.object(self.app, 'providers', new={}): + with patch.object(self.app, "providers", new={}): handler = self.make_handler() self.assertEqual(handler.get_email_settings(), {}) # provider may define email settings (via modules) providers = { - 'wuttatest': MagicMock(email_modules=['tests.test_email']), + "wuttatest": MagicMock(email_modules=["tests.test_email"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() settings = handler.get_email_settings() self.assertEqual(len(settings), 1) - self.assertIn('mock_foo', settings) + self.assertIn("mock_foo", settings) def test_get_email_setting(self): providers = { - 'wuttatest': MagicMock(email_modules=['tests.test_email']), + "wuttatest": MagicMock(email_modules=["tests.test_email"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() # as instance - setting = handler.get_email_setting('mock_foo') + setting = handler.get_email_setting("mock_foo") self.assertIsInstance(setting, mod.EmailSetting) self.assertIsInstance(setting, mock_foo) # as class - setting = handler.get_email_setting('mock_foo', instance=False) + setting = handler.get_email_setting("mock_foo", instance=False) self.assertTrue(issubclass(setting, mod.EmailSetting)) self.assertIs(setting, mock_foo) @@ -229,10 +238,10 @@ class TestEmailHandler(ConfigTestCase): # self.assertRaises(ConfigurationError, handler.make_auto_message, 'foo') # message is empty by default - msg = handler.make_auto_message('foo') + msg = handler.make_auto_message("foo") self.assertIsInstance(msg, mod.Message) - self.assertEqual(msg.key, 'foo') - self.assertEqual(msg.sender, 'root@localhost') + self.assertEqual(msg.key, "foo") + self.assertEqual(msg.sender, "root@localhost") self.assertEqual(msg.subject, "Automated message") self.assertEqual(msg.to, []) self.assertEqual(msg.cc, []) @@ -242,14 +251,14 @@ class TestEmailHandler(ConfigTestCase): self.assertIsNone(msg.html_body) # override defaults - self.config.setdefault('wutta.email.default.sender', 'bob@example.com') - self.config.setdefault('wutta.email.default.subject', 'Attention required') + self.config.setdefault("wutta.email.default.sender", "bob@example.com") + self.config.setdefault("wutta.email.default.subject", "Attention required") # message is empty by default - msg = handler.make_auto_message('foo') + msg = handler.make_auto_message("foo") self.assertIsInstance(msg, mod.Message) - self.assertEqual(msg.key, 'foo') - self.assertEqual(msg.sender, 'bob@example.com') + self.assertEqual(msg.key, "foo") + self.assertEqual(msg.sender, "bob@example.com") self.assertEqual(msg.subject, "Attention required") self.assertEqual(msg.to, []) self.assertEqual(msg.cc, []) @@ -260,15 +269,15 @@ class TestEmailHandler(ConfigTestCase): # but if there is a proper email profile configured for key, # then we should get back a more complete message - self.config.setdefault('wutta.email.test_foo.subject', "hello foo") - self.config.setdefault('wutta.email.test_foo.to', 'sally@example.com') - self.config.setdefault('wutta.email.templates', 'tests:email-templates') + self.config.setdefault("wutta.email.test_foo.subject", "hello foo") + self.config.setdefault("wutta.email.test_foo.to", "sally@example.com") + self.config.setdefault("wutta.email.templates", "tests:email-templates") handler = self.make_handler() - msg = handler.make_auto_message('test_foo') - self.assertEqual(msg.key, 'test_foo') - self.assertEqual(msg.sender, 'bob@example.com') + msg = handler.make_auto_message("test_foo") + self.assertEqual(msg.key, "test_foo") + self.assertEqual(msg.sender, "bob@example.com") self.assertEqual(msg.subject, "hello foo") - self.assertEqual(msg.to, ['sally@example.com']) + self.assertEqual(msg.to, ["sally@example.com"]) self.assertEqual(msg.cc, []) self.assertEqual(msg.bcc, []) self.assertIsNone(msg.replyto) @@ -279,160 +288,162 @@ class TestEmailHandler(ConfigTestCase): # kwarg at all; others get skipped if kwarg is empty # sender - with patch.object(handler, 'get_auto_sender') as get_auto_sender: - msg = handler.make_auto_message('foo', sender=None) + with patch.object(handler, "get_auto_sender") as get_auto_sender: + msg = handler.make_auto_message("foo", sender=None) get_auto_sender.assert_not_called() - msg = handler.make_auto_message('foo') - get_auto_sender.assert_called_once_with('foo') + msg = handler.make_auto_message("foo") + get_auto_sender.assert_called_once_with("foo") # subject - with patch.object(handler, 'get_auto_subject') as get_auto_subject: - msg = handler.make_auto_message('foo', subject=None) + with patch.object(handler, "get_auto_subject") as get_auto_subject: + msg = handler.make_auto_message("foo", subject=None) get_auto_subject.assert_not_called() - msg = handler.make_auto_message('foo') - get_auto_subject.assert_called_once_with('foo', {}, default=None) + msg = handler.make_auto_message("foo") + get_auto_subject.assert_called_once_with("foo", {}, default=None) # to - with patch.object(handler, 'get_auto_to') as get_auto_to: - msg = handler.make_auto_message('foo', to=None) + with patch.object(handler, "get_auto_to") as get_auto_to: + msg = handler.make_auto_message("foo", to=None) get_auto_to.assert_not_called() get_auto_to.return_value = None - msg = handler.make_auto_message('foo') - get_auto_to.assert_called_once_with('foo') + msg = handler.make_auto_message("foo") + get_auto_to.assert_called_once_with("foo") # cc - with patch.object(handler, 'get_auto_cc') as get_auto_cc: - msg = handler.make_auto_message('foo', cc=None) + with patch.object(handler, "get_auto_cc") as get_auto_cc: + msg = handler.make_auto_message("foo", cc=None) get_auto_cc.assert_not_called() get_auto_cc.return_value = None - msg = handler.make_auto_message('foo') - get_auto_cc.assert_called_once_with('foo') + msg = handler.make_auto_message("foo") + get_auto_cc.assert_called_once_with("foo") # bcc - with patch.object(handler, 'get_auto_bcc') as get_auto_bcc: - msg = handler.make_auto_message('foo', bcc=None) + with patch.object(handler, "get_auto_bcc") as get_auto_bcc: + msg = handler.make_auto_message("foo", bcc=None) get_auto_bcc.assert_not_called() get_auto_bcc.return_value = None - msg = handler.make_auto_message('foo') - get_auto_bcc.assert_called_once_with('foo') + msg = handler.make_auto_message("foo") + get_auto_bcc.assert_called_once_with("foo") # txt_body - with patch.object(handler, 'get_auto_txt_body') as get_auto_txt_body: - msg = handler.make_auto_message('foo', txt_body=None) + with patch.object(handler, "get_auto_txt_body") as get_auto_txt_body: + msg = handler.make_auto_message("foo", txt_body=None) get_auto_txt_body.assert_not_called() - msg = handler.make_auto_message('foo') - get_auto_txt_body.assert_called_once_with('foo', {}) + msg = handler.make_auto_message("foo") + get_auto_txt_body.assert_called_once_with("foo", {}) # html_body - with patch.object(handler, 'get_auto_html_body') as get_auto_html_body: - msg = handler.make_auto_message('foo', html_body=None) + with patch.object(handler, "get_auto_html_body") as get_auto_html_body: + msg = handler.make_auto_message("foo", html_body=None) get_auto_html_body.assert_not_called() - msg = handler.make_auto_message('foo') - get_auto_html_body.assert_called_once_with('foo', {}) + msg = handler.make_auto_message("foo") + get_auto_html_body.assert_called_once_with("foo", {}) def test_get_auto_sender(self): handler = self.make_handler() # basic global default - self.assertEqual(handler.get_auto_sender('foo'), 'root@localhost') + self.assertEqual(handler.get_auto_sender("foo"), "root@localhost") # can set global default - self.config.setdefault('wutta.email.default.sender', 'bob@example.com') - self.assertEqual(handler.get_auto_sender('foo'), 'bob@example.com') + self.config.setdefault("wutta.email.default.sender", "bob@example.com") + self.assertEqual(handler.get_auto_sender("foo"), "bob@example.com") # can set for key - self.config.setdefault('wutta.email.foo.sender', 'sally@example.com') - self.assertEqual(handler.get_auto_sender('foo'), 'sally@example.com') + self.config.setdefault("wutta.email.foo.sender", "sally@example.com") + self.assertEqual(handler.get_auto_sender("foo"), "sally@example.com") def test_get_auto_replyto(self): handler = self.make_handler() # null by default - self.assertIsNone(handler.get_auto_replyto('foo')) + self.assertIsNone(handler.get_auto_replyto("foo")) # can set global default - self.config.setdefault('wutta.email.default.replyto', 'george@example.com') - self.assertEqual(handler.get_auto_replyto('foo'), 'george@example.com') + self.config.setdefault("wutta.email.default.replyto", "george@example.com") + self.assertEqual(handler.get_auto_replyto("foo"), "george@example.com") # can set for key - self.config.setdefault('wutta.email.foo.replyto', 'kathy@example.com') - self.assertEqual(handler.get_auto_replyto('foo'), 'kathy@example.com') + self.config.setdefault("wutta.email.foo.replyto", "kathy@example.com") + self.assertEqual(handler.get_auto_replyto("foo"), "kathy@example.com") def test_get_auto_subject_template(self): handler = self.make_handler() # global default - template = handler.get_auto_subject_template('foo') + template = handler.get_auto_subject_template("foo") self.assertEqual(template, "Automated message") # can configure alternate global default - self.config.setdefault('wutta.email.default.subject', "Wutta Message") - template = handler.get_auto_subject_template('foo') + self.config.setdefault("wutta.email.default.subject", "Wutta Message") + template = handler.get_auto_subject_template("foo") self.assertEqual(template, "Wutta Message") # can configure just for key - self.config.setdefault('wutta.email.foo.subject', "Foo Message") - template = handler.get_auto_subject_template('foo') + self.config.setdefault("wutta.email.foo.subject", "Foo Message") + template = handler.get_auto_subject_template("foo") self.assertEqual(template, "Foo Message") # EmailSetting can provide default subject providers = { - 'wuttatest': MagicMock(email_modules=['tests.test_email']), + "wuttatest": MagicMock(email_modules=["tests.test_email"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() - template = handler.get_auto_subject_template('mock_foo') + template = handler.get_auto_subject_template("mock_foo") self.assertEqual(template, "MOCK FOO!") # caller can provide default subject - template = handler.get_auto_subject_template('mock_foo', default="whatever is clever") + template = handler.get_auto_subject_template( + "mock_foo", default="whatever is clever" + ) self.assertEqual(template, "whatever is clever") def test_get_auto_subject(self): handler = self.make_handler() # global default - subject = handler.get_auto_subject('foo') + subject = handler.get_auto_subject("foo") self.assertEqual(subject, "Automated message") # can configure alternate global default - self.config.setdefault('wutta.email.default.subject', "Wutta Message") - subject = handler.get_auto_subject('foo') + self.config.setdefault("wutta.email.default.subject", "Wutta Message") + subject = handler.get_auto_subject("foo") self.assertEqual(subject, "Wutta Message") # caller can provide default subject - subject = handler.get_auto_subject('foo', default="whatever is clever") + subject = handler.get_auto_subject("foo", default="whatever is clever") self.assertEqual(subject, "whatever is clever") # can configure just for key - self.config.setdefault('wutta.email.foo.subject', "Foo Message") - subject = handler.get_auto_subject('foo') + self.config.setdefault("wutta.email.foo.subject", "Foo Message") + subject = handler.get_auto_subject("foo") self.assertEqual(subject, "Foo Message") # proper template is rendered - self.config.setdefault('wutta.email.bar.subject', "${foo} Message") - subject = handler.get_auto_subject('bar', {'foo': "FOO"}) + self.config.setdefault("wutta.email.bar.subject", "${foo} Message") + subject = handler.get_auto_subject("bar", {"foo": "FOO"}) self.assertEqual(subject, "FOO Message") # unless we ask it not to - subject = handler.get_auto_subject('bar', {'foo': "FOO"}, rendered=False) + subject = handler.get_auto_subject("bar", {"foo": "FOO"}, rendered=False) self.assertEqual(subject, "${foo} Message") def test_get_auto_recips(self): handler = self.make_handler() # error if bad type requested - self.assertRaises(ValueError, handler.get_auto_recips, 'foo', 'doesnotexist') + self.assertRaises(ValueError, handler.get_auto_recips, "foo", "doesnotexist") # can configure global default - self.config.setdefault('wutta.email.default.to', 'admin@example.com') - recips = handler.get_auto_recips('foo', 'to') - self.assertEqual(recips, ['admin@example.com']) + self.config.setdefault("wutta.email.default.to", "admin@example.com") + recips = handler.get_auto_recips("foo", "to") + self.assertEqual(recips, ["admin@example.com"]) # can configure just for key - self.config.setdefault('wutta.email.foo.to', 'bob@example.com') - recips = handler.get_auto_recips('foo', 'to') - self.assertEqual(recips, ['bob@example.com']) + self.config.setdefault("wutta.email.foo.to", "bob@example.com") + recips = handler.get_auto_recips("foo", "to") + self.assertEqual(recips, ["bob@example.com"]) def test_get_auto_body_template(self): from mako.template import Template @@ -440,88 +451,88 @@ class TestEmailHandler(ConfigTestCase): handler = self.make_handler() # error if bad request - self.assertRaises(ValueError, handler.get_auto_body_template, 'foo', 'BADTYPE') + self.assertRaises(ValueError, handler.get_auto_body_template, "foo", "BADTYPE") # empty by default - template = handler.get_auto_body_template('foo', 'txt') + template = handler.get_auto_body_template("foo", "txt") self.assertIsNone(template) # but returns a template if it exists providers = { - 'wuttatest': MagicMock(email_templates=['tests:email-templates']), + "wuttatest": MagicMock(email_templates=["tests:email-templates"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() - template = handler.get_auto_body_template('test_foo', 'txt') + template = handler.get_auto_body_template("test_foo", "txt") self.assertIsInstance(template, Template) - self.assertEqual(template.uri, 'test_foo.txt.mako') + self.assertEqual(template.uri, "test_foo.txt.mako") def test_get_auto_txt_body(self): handler = self.make_handler() # empty by default - body = handler.get_auto_txt_body('some-random-email') + body = handler.get_auto_txt_body("some-random-email") self.assertIsNone(body) # but returns body if template exists providers = { - 'wuttatest': MagicMock(email_templates=['tests:email-templates']), + "wuttatest": MagicMock(email_templates=["tests:email-templates"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() - body = handler.get_auto_txt_body('test_foo') - self.assertEqual(body, 'hello from foo txt template\n') + body = handler.get_auto_txt_body("test_foo") + self.assertEqual(body, "hello from foo txt template\n") def test_get_auto_html_body(self): handler = self.make_handler() # empty by default - body = handler.get_auto_html_body('some-random-email') + body = handler.get_auto_html_body("some-random-email") self.assertIsNone(body) # but returns body if template exists providers = { - 'wuttatest': MagicMock(email_templates=['tests:email-templates']), + "wuttatest": MagicMock(email_templates=["tests:email-templates"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() - body = handler.get_auto_html_body('test_foo') - self.assertEqual(body, '

hello from foo html template

\n') + body = handler.get_auto_html_body("test_foo") + self.assertEqual(body, "

hello from foo html template

\n") def test_get_notes(self): handler = self.make_handler() # null by default - self.assertIsNone(handler.get_notes('foo')) + self.assertIsNone(handler.get_notes("foo")) # configured notes - self.config.setdefault('wutta.email.foo.notes', 'hello world') - self.assertEqual(handler.get_notes('foo'), 'hello world') + self.config.setdefault("wutta.email.foo.notes", "hello world") + self.assertEqual(handler.get_notes("foo"), "hello world") def test_is_enabled(self): handler = self.make_handler() # enabled by default - self.assertTrue(handler.is_enabled('default')) - self.assertTrue(handler.is_enabled('foo')) + self.assertTrue(handler.is_enabled("default")) + self.assertTrue(handler.is_enabled("foo")) # specific type disabled - self.config.setdefault('wutta.email.foo.enabled', 'false') - self.assertFalse(handler.is_enabled('foo')) + self.config.setdefault("wutta.email.foo.enabled", "false") + self.assertFalse(handler.is_enabled("foo")) # default is disabled - self.assertTrue(handler.is_enabled('bar')) - self.config.setdefault('wutta.email.default.enabled', 'false') - self.assertFalse(handler.is_enabled('bar')) + self.assertTrue(handler.is_enabled("bar")) + self.config.setdefault("wutta.email.default.enabled", "false") + self.assertFalse(handler.is_enabled("bar")) def test_deliver_message(self): handler = self.make_handler() - msg = handler.make_message(sender='bob@example.com', to='sally@example.com') - with patch.object(msg, 'as_string', return_value='msg-str'): + msg = handler.make_message(sender="bob@example.com", to="sally@example.com") + with patch.object(msg, "as_string", return_value="msg-str"): # no smtp session since sending email is disabled by default - with patch.object(mod, 'smtplib') as smtplib: + with patch.object(mod, "smtplib") as smtplib: session = MagicMock() smtplib.SMTP.return_value = session handler.deliver_message(msg) @@ -530,85 +541,99 @@ class TestEmailHandler(ConfigTestCase): session.sendmail.assert_not_called() # now let's enable sending - self.config.setdefault('wutta.mail.send_emails', 'true') + self.config.setdefault("wutta.mail.send_emails", "true") # smtp login not attempted by default - with patch.object(mod, 'smtplib') as smtplib: + with patch.object(mod, "smtplib") as smtplib: session = MagicMock() smtplib.SMTP.return_value = session handler.deliver_message(msg) - smtplib.SMTP.assert_called_once_with('localhost') + smtplib.SMTP.assert_called_once_with("localhost") session.login.assert_not_called() - session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str') + session.sendmail.assert_called_once_with( + "bob@example.com", {"sally@example.com"}, "msg-str" + ) # but login attempted if config has credentials - self.config.setdefault('wutta.mail.smtp.username', 'bob') - self.config.setdefault('wutta.mail.smtp.password', 'seekrit') - with patch.object(mod, 'smtplib') as smtplib: + self.config.setdefault("wutta.mail.smtp.username", "bob") + self.config.setdefault("wutta.mail.smtp.password", "seekrit") + with patch.object(mod, "smtplib") as smtplib: session = MagicMock() smtplib.SMTP.return_value = session handler.deliver_message(msg) - smtplib.SMTP.assert_called_once_with('localhost') - session.login.assert_called_once_with('bob', 'seekrit') - session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str') + smtplib.SMTP.assert_called_once_with("localhost") + session.login.assert_called_once_with("bob", "seekrit") + session.sendmail.assert_called_once_with( + "bob@example.com", {"sally@example.com"}, "msg-str" + ) # error if no sender - msg = handler.make_message(to='sally@example.com') + msg = handler.make_message(to="sally@example.com") self.assertRaises(ValueError, handler.deliver_message, msg) # error if no recips - msg = handler.make_message(sender='bob@example.com') + msg = handler.make_message(sender="bob@example.com") self.assertRaises(ValueError, handler.deliver_message, msg) # can set recips as list - msg = handler.make_message(sender='bob@example.com') - with patch.object(msg, 'as_string', return_value='msg-str'): - with patch.object(mod, 'smtplib') as smtplib: + msg = handler.make_message(sender="bob@example.com") + with patch.object(msg, "as_string", return_value="msg-str"): + with patch.object(mod, "smtplib") as smtplib: session = MagicMock() smtplib.SMTP.return_value = session - handler.deliver_message(msg, recips=['sally@example.com']) - smtplib.SMTP.assert_called_once_with('localhost') - session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str') + handler.deliver_message(msg, recips=["sally@example.com"]) + smtplib.SMTP.assert_called_once_with("localhost") + session.sendmail.assert_called_once_with( + "bob@example.com", {"sally@example.com"}, "msg-str" + ) # can set recips as string - msg = handler.make_message(sender='bob@example.com') - with patch.object(msg, 'as_string', return_value='msg-str'): - with patch.object(mod, 'smtplib') as smtplib: + msg = handler.make_message(sender="bob@example.com") + with patch.object(msg, "as_string", return_value="msg-str"): + with patch.object(mod, "smtplib") as smtplib: session = MagicMock() smtplib.SMTP.return_value = session - handler.deliver_message(msg, recips='sally@example.com') - smtplib.SMTP.assert_called_once_with('localhost') - session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str') + handler.deliver_message(msg, recips="sally@example.com") + smtplib.SMTP.assert_called_once_with("localhost") + session.sendmail.assert_called_once_with( + "bob@example.com", {"sally@example.com"}, "msg-str" + ) # can set recips via to - msg = handler.make_message(sender='bob@example.com', to='sally@example.com') - with patch.object(msg, 'as_string', return_value='msg-str'): - with patch.object(mod, 'smtplib') as smtplib: + msg = handler.make_message(sender="bob@example.com", to="sally@example.com") + with patch.object(msg, "as_string", return_value="msg-str"): + with patch.object(mod, "smtplib") as smtplib: session = MagicMock() smtplib.SMTP.return_value = session handler.deliver_message(msg) - smtplib.SMTP.assert_called_once_with('localhost') - session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str') + smtplib.SMTP.assert_called_once_with("localhost") + session.sendmail.assert_called_once_with( + "bob@example.com", {"sally@example.com"}, "msg-str" + ) # can set recips via cc - msg = handler.make_message(sender='bob@example.com', cc='sally@example.com') - with patch.object(msg, 'as_string', return_value='msg-str'): - with patch.object(mod, 'smtplib') as smtplib: + msg = handler.make_message(sender="bob@example.com", cc="sally@example.com") + with patch.object(msg, "as_string", return_value="msg-str"): + with patch.object(mod, "smtplib") as smtplib: session = MagicMock() smtplib.SMTP.return_value = session handler.deliver_message(msg) - smtplib.SMTP.assert_called_once_with('localhost') - session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str') + smtplib.SMTP.assert_called_once_with("localhost") + session.sendmail.assert_called_once_with( + "bob@example.com", {"sally@example.com"}, "msg-str" + ) # can set recips via bcc - msg = handler.make_message(sender='bob@example.com', bcc='sally@example.com') - with patch.object(msg, 'as_string', return_value='msg-str'): - with patch.object(mod, 'smtplib') as smtplib: + msg = handler.make_message(sender="bob@example.com", bcc="sally@example.com") + with patch.object(msg, "as_string", return_value="msg-str"): + with patch.object(mod, "smtplib") as smtplib: session = MagicMock() smtplib.SMTP.return_value = session handler.deliver_message(msg) - smtplib.SMTP.assert_called_once_with('localhost') - session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str') + smtplib.SMTP.assert_called_once_with("localhost") + session.sendmail.assert_called_once_with( + "bob@example.com", {"sally@example.com"}, "msg-str" + ) def test_sending_is_enabled(self): handler = self.make_handler() @@ -617,12 +642,12 @@ class TestEmailHandler(ConfigTestCase): self.assertFalse(handler.sending_is_enabled()) # but can be turned on - self.config.setdefault('wutta.mail.send_emails', 'true') + self.config.setdefault("wutta.mail.send_emails", "true") self.assertTrue(handler.sending_is_enabled()) def test_send_email(self): handler = self.make_handler() - with patch.object(handler, 'deliver_message') as deliver_message: + with patch.object(handler, "deliver_message") as deliver_message: # specify message w/ no body msg = handler.make_message() @@ -631,7 +656,7 @@ class TestEmailHandler(ConfigTestCase): # again, but also specify key msg = handler.make_message() - self.assertRaises(ValueError, handler.send_email, 'foo', message=msg) + self.assertRaises(ValueError, handler.send_email, "foo", message=msg) self.assertFalse(deliver_message.called) # specify complete message @@ -643,7 +668,7 @@ class TestEmailHandler(ConfigTestCase): # again, but also specify key deliver_message.reset_mock() msg = handler.make_message(txt_body="hello world") - handler.send_email('foo', message=msg) + handler.send_email("foo", message=msg) deliver_message.assert_called_once_with(msg, recips=None) # no key, no message @@ -652,25 +677,27 @@ class TestEmailHandler(ConfigTestCase): # auto-create message w/ no template deliver_message.reset_mock() - self.assertRaises(RuntimeError, handler.send_email, 'foo', sender='foo@example.com') + self.assertRaises( + RuntimeError, handler.send_email, "foo", sender="foo@example.com" + ) self.assertFalse(deliver_message.called) # auto create w/ body deliver_message.reset_mock() - handler.send_email('foo', sender='foo@example.com', txt_body="hello world") + handler.send_email("foo", sender="foo@example.com", txt_body="hello world") self.assertTrue(deliver_message.called) # type is disabled deliver_message.reset_mock() - self.config.setdefault('wutta.email.foo.enabled', False) - handler.send_email('foo', sender='foo@example.com', txt_body="hello world") + self.config.setdefault("wutta.email.foo.enabled", False) + handler.send_email("foo", sender="foo@example.com", txt_body="hello world") self.assertFalse(deliver_message.called) # default is disabled deliver_message.reset_mock() - handler.send_email('bar', sender='bar@example.com', txt_body="hello world") + handler.send_email("bar", sender="bar@example.com", txt_body="hello world") self.assertTrue(deliver_message.called) deliver_message.reset_mock() - self.config.setdefault('wutta.email.default.enabled', False) - handler.send_email('bar', sender='bar@example.com', txt_body="hello world") + self.config.setdefault("wutta.email.default.enabled", False) + handler.send_email("bar", sender="bar@example.com", txt_body="hello world") self.assertFalse(deliver_message.called) diff --git a/tests/test_install.py b/tests/test_install.py index 2bc2ddb..f05ffea 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -18,16 +18,16 @@ class TestInstallHandler(ConfigTestCase): def test_constructor(self): handler = self.make_handler() - self.assertEqual(handler.pkg_name, 'poser') - self.assertEqual(handler.app_title, 'poser') - self.assertEqual(handler.pypi_name, 'poser') - self.assertEqual(handler.egg_name, 'poser') + self.assertEqual(handler.pkg_name, "poser") + self.assertEqual(handler.app_title, "poser") + self.assertEqual(handler.pypi_name, "poser") + self.assertEqual(handler.egg_name, "poser") def test_run(self): handler = self.make_handler() - with patch.object(handler, 'show_welcome') as show_welcome: - with patch.object(handler, 'sanity_check') as sanity_check: - with patch.object(handler, 'do_install_steps') as do_install_steps: + with patch.object(handler, "show_welcome") as show_welcome: + with patch.object(handler, "sanity_check") as sanity_check: + with patch.object(handler, "do_install_steps") as do_install_steps: handler.run() show_welcome.assert_called_once_with() sanity_check.assert_called_once_with() @@ -35,9 +35,9 @@ class TestInstallHandler(ConfigTestCase): def test_show_welcome(self): handler = self.make_handler() - with patch.object(mod, 'sys') as sys: - with patch.object(handler, 'rprint') as rprint: - with patch.object(handler, 'prompt_bool') as prompt_bool: + 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 @@ -51,9 +51,9 @@ class TestInstallHandler(ConfigTestCase): def test_sanity_check(self): handler = self.make_handler() - with patch.object(mod, 'sys') as sys: - with patch.object(mod, 'os') as os: - with patch.object(handler, 'rprint') as rprint: + 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 @@ -67,24 +67,26 @@ class TestInstallHandler(ConfigTestCase): def test_do_install_steps(self): handler = self.make_handler() - handler.templates = TemplateLookup(directories=[ - self.app.resource_path('wuttjamaican:templates/install'), - ]) + handler.templates = TemplateLookup( + directories=[ + self.app.resource_path("wuttjamaican:templates/install"), + ] + ) dbinfo = { - 'dburl': f'sqlite:///{self.tempdir}/poser.sqlite', + "dburl": f"sqlite:///{self.tempdir}/poser.sqlite", } - 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: + 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(hasattr(handler, "schema_installed")) handler.do_install_steps() self.assertTrue(make_appdir.called) self.assertTrue(handler.schema_installed) - install_db_schema.assert_called_once_with(dbinfo['dburl']) + install_db_schema.assert_called_once_with(dbinfo["dburl"]) def test_get_dbinfo(self): try: @@ -97,16 +99,16 @@ class TestInstallHandler(ConfigTestCase): handler = self.make_handler() def prompt_generic(info, default=None, is_password=False): - if info in ('db name', 'db user'): - return 'poser' + if info in ("db name", "db user"): + return "poser" if is_password: - return 'seekrit' + return "seekrit" return default - with patch.object(mod, 'sys') as sys: - 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: + with patch.object(mod, "sys") as sys: + 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" @@ -114,7 +116,7 @@ class TestInstallHandler(ConfigTestCase): self.assertRaises(RuntimeError, handler.get_dbinfo) sys.exit.assert_called_once_with(1) - seekrit = '***' if SA2 else 'seekrit' + seekrit = "***" if SA2 else "seekrit" # good dbinfo sys.exit.reset_mock() @@ -122,8 +124,10 @@ class TestInstallHandler(ConfigTestCase): dbinfo = handler.get_dbinfo() self.assertFalse(sys.exit.called) rprint.assert_called_with("[bold green]good[/bold green]") - self.assertEqual(str(dbinfo['dburl']), - f'postgresql+psycopg2://poser:{seekrit}@localhost:5432/poser') + self.assertEqual( + str(dbinfo["dburl"]), + f"postgresql+psycopg2://poser:{seekrit}@localhost:5432/poser", + ) def test_make_db_url(self): try: @@ -134,13 +138,21 @@ class TestInstallHandler(ConfigTestCase): from wuttjamaican.db.util import SA2 handler = self.make_handler() - seekrit = '***' if SA2 else 'seekrit' + seekrit = "***" if SA2 else "seekrit" - url = handler.make_db_url('postgresql', 'localhost', '5432', 'poser', 'poser', 'seekrit') - self.assertEqual(str(url), f'postgresql+psycopg2://poser:{seekrit}@localhost:5432/poser') + url = handler.make_db_url( + "postgresql", "localhost", "5432", "poser", "poser", "seekrit" + ) + self.assertEqual( + str(url), f"postgresql+psycopg2://poser:{seekrit}@localhost:5432/poser" + ) - url = handler.make_db_url('mysql', 'localhost', '3306', 'poser', 'poser', 'seekrit') - self.assertEqual(str(url), f'mysql+mysqlconnector://poser:{seekrit}@localhost:3306/poser') + url = handler.make_db_url( + "mysql", "localhost", "3306", "poser", "poser", "seekrit" + ) + self.assertEqual( + str(url), f"mysql+mysqlconnector://poser:{seekrit}@localhost:3306/poser" + ) def test_test_db_connection(self): try: @@ -151,11 +163,11 @@ class TestInstallHandler(ConfigTestCase): handler = self.make_handler() # db does not exist - result = handler.test_db_connection('sqlite:///bad/url/should/not/exist') - self.assertIn('unable to open database file', result) + result = handler.test_db_connection("sqlite:///bad/url/should/not/exist") + self.assertIn("unable to open database file", result) # db is setup - url = f'sqlite:///{self.tempdir}/db.sqlite' + url = f"sqlite:///{self.tempdir}/db.sqlite" engine = sa.create_engine(url) with engine.begin() as cxn: cxn.execute(sa.text("create table whatever (id int primary key);")) @@ -163,27 +175,29 @@ class TestInstallHandler(ConfigTestCase): def test_make_template_context(self): handler = self.make_handler() - dbinfo = {'dburl': 'sqlite:///poser.sqlite'} + dbinfo = {"dburl": "sqlite:///poser.sqlite"} context = handler.make_template_context(dbinfo) - self.assertEqual(context['envdir'], sys.prefix) - self.assertEqual(context['pkg_name'], 'poser') - self.assertEqual(context['app_title'], 'poser') - self.assertEqual(context['pypi_name'], 'poser') - self.assertEqual(context['egg_name'], 'poser') - self.assertEqual(context['appdir'], os.path.join(sys.prefix, 'app')) - self.assertEqual(context['db_url'], 'sqlite:///poser.sqlite') + self.assertEqual(context["envdir"], sys.prefix) + self.assertEqual(context["pkg_name"], "poser") + self.assertEqual(context["app_title"], "poser") + self.assertEqual(context["pypi_name"], "poser") + self.assertEqual(context["egg_name"], "poser") + self.assertEqual(context["appdir"], os.path.join(sys.prefix, "app")) + self.assertEqual(context["db_url"], "sqlite:///poser.sqlite") def test_make_appdir(self): handler = self.make_handler() - handler.templates = TemplateLookup(directories=[ - self.app.resource_path('wuttjamaican:templates/install'), - ]) - dbinfo = {'dburl': 'sqlite:///poser.sqlite'} + handler.templates = TemplateLookup( + directories=[ + self.app.resource_path("wuttjamaican:templates/install"), + ] + ) + dbinfo = {"dburl": "sqlite:///poser.sqlite"} context = handler.make_template_context(dbinfo) handler.make_appdir(context, appdir=self.tempdir) - wutta_conf = os.path.join(self.tempdir, 'wutta.conf') - with open(wutta_conf, 'rt') as f: - self.assertIn('default.url = sqlite:///poser.sqlite', f.read()) + wutta_conf = os.path.join(self.tempdir, "wutta.conf") + with open(wutta_conf, "rt") as f: + self.assertIn("default.url = sqlite:///poser.sqlite", f.read()) def test_install_db_schema(self): try: @@ -192,89 +206,105 @@ class TestInstallHandler(ConfigTestCase): pytest.skip("test is not relevant without sqlalchemy") handler = self.make_handler() - db_url = f'sqlite:///{self.tempdir}/poser.sqlite' + db_url = f"sqlite:///{self.tempdir}/poser.sqlite" - wutta_conf = self.write_file('wutta.conf', f""" + wutta_conf = self.write_file( + "wutta.conf", + f""" [wutta.db] default.url = {db_url} -""") +""", + ) # convert to proper URL object db_url = sa.create_engine(db_url).url - with patch.object(mod, 'subprocess') as subprocess: + with patch.object(mod, "subprocess") as subprocess: # user declines offer to install schema - with patch.object(handler, 'prompt_bool', return_value=False): + with patch.object(handler, "prompt_bool", return_value=False): self.assertFalse(handler.install_db_schema(db_url, appdir=self.tempdir)) # user agrees to install schema - with patch.object(handler, 'prompt_bool', return_value=True): + with patch.object(handler, "prompt_bool", return_value=True): self.assertTrue(handler.install_db_schema(db_url, appdir=self.tempdir)) - subprocess.check_call.assert_called_once_with([ - os.path.join(sys.prefix, 'bin', 'alembic'), - '-c', wutta_conf, 'upgrade', 'heads']) + subprocess.check_call.assert_called_once_with( + [ + os.path.join(sys.prefix, "bin", "alembic"), + "-c", + wutta_conf, + "upgrade", + "heads", + ] + ) def test_show_goodbye(self): handler = self.make_handler() - with patch.object(handler, 'rprint') as rprint: + with patch.object(handler, "rprint") as rprint: handler.schema_installed = True handler.show_goodbye() - rprint.assert_any_call("\n\t[bold green]initial setup is complete![/bold green]") + rprint.assert_any_call( + "\n\t[bold green]initial setup is complete![/bold green]" + ) rprint.assert_any_call("\t[blue]bin/wutta -c app/web.conf webapp -r[/blue]") def test_require_prompt_toolkit_installed(self): # nb. this assumes we *do* have prompt_toolkit installed handler = self.make_handler() - with patch.object(mod, 'subprocess') as subprocess: - handler.require_prompt_toolkit(answer='Y') + with patch.object(mod, "subprocess") as subprocess: + handler.require_prompt_toolkit(answer="Y") self.assertFalse(subprocess.check_call.called) def test_require_prompt_toolkit_missing(self): handler = self.make_handler() orig_import = __import__ - stuff = {'attempts': 0} + stuff = {"attempts": 0} def mock_import(name, globals=None, locals=None, fromlist=(), level=0): - if name == 'prompt_toolkit': + if name == "prompt_toolkit": # nb. pretend this is not installed raise ImportError return orig_import(name, globals, locals, fromlist, level) # prompt_toolkit not installed, and user declines offer to install - with patch('builtins.__import__', side_effect=mock_import): - with patch.object(mod, 'subprocess') as subprocess: - with patch.object(mod, 'sys') as sys: + with patch("builtins.__import__", side_effect=mock_import): + with patch.object(mod, "subprocess") as subprocess: + with patch.object(mod, "sys") as sys: sys.exit.side_effect = RuntimeError - self.assertRaises(RuntimeError, handler.require_prompt_toolkit, answer='N') + self.assertRaises( + RuntimeError, handler.require_prompt_toolkit, answer="N" + ) self.assertFalse(subprocess.check_call.called) - sys.stderr.write.assert_called_once_with("prompt_toolkit is required; aborting\n") + sys.stderr.write.assert_called_once_with( + "prompt_toolkit is required; aborting\n" + ) sys.exit.assert_called_once_with(1) def test_require_prompt_toolkit_missing_then_installed(self): handler = self.make_handler() orig_import = __import__ - stuff = {'attempts': 0} + stuff = {"attempts": 0} def mock_import(name, globals=None, locals=None, fromlist=(), level=0): - if name == 'prompt_toolkit': - stuff['attempts'] += 1 - if stuff['attempts'] == 1: + if name == "prompt_toolkit": + stuff["attempts"] += 1 + if stuff["attempts"] == 1: # nb. pretend this is not installed raise ImportError - return orig_import('prompt_toolkit') + return orig_import("prompt_toolkit") return orig_import(name, globals, locals, fromlist, level) # prompt_toolkit not installed, and user declines offer to install - with patch('builtins.__import__', side_effect=mock_import): - with patch.object(mod, 'subprocess') as subprocess: - with patch.object(mod, 'sys') as sys: - sys.executable = 'python' - handler.require_prompt_toolkit(answer='Y') - subprocess.check_call.assert_called_once_with(['python', '-m', 'pip', - 'install', 'prompt_toolkit']) + with patch("builtins.__import__", side_effect=mock_import): + with patch.object(mod, "subprocess") as subprocess: + with patch.object(mod, "sys") as sys: + sys.executable = "python" + handler.require_prompt_toolkit(answer="Y") + subprocess.check_call.assert_called_once_with( + ["python", "-m", "pip", "install", "prompt_toolkit"] + ) self.assertFalse(sys.exit.called) - self.assertEqual(stuff['attempts'], 2) + self.assertEqual(stuff["attempts"], 2) def test_prompt_generic(self): handler = self.make_handler() @@ -283,86 +313,94 @@ default.url = {db_url} mock_prompt = MagicMock() def mock_import(name, globals=None, locals=None, fromlist=(), level=0): - if name == 'prompt_toolkit': - if fromlist == ('prompt',): + if name == "prompt_toolkit": + if fromlist == ("prompt",): return MagicMock(prompt=mock_prompt) return orig_import(name, globals, locals, fromlist, level) - 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: + 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') + mock_prompt.return_value = "" + result = handler.prompt_generic("foo") self.assertIsNone(result) - mock_prompt.assert_called_once_with([('', '\n'), - ('class:bold', 'foo'), - ('', ': ')], - style=style, is_password=False) + mock_prompt.assert_called_once_with( + [("", "\n"), ("class:bold", "foo"), ("", ": ")], + style=style, + is_password=False, + ) # fallback to default value mock_prompt.reset_mock() - mock_prompt.return_value = '' - result = handler.prompt_generic('foo', default='baz') - self.assertEqual(result, 'baz') - mock_prompt.assert_called_once_with([('', '\n'), - ('class:bold', 'foo'), - ('', ' [baz]: ')], - style=style, is_password=False) + mock_prompt.return_value = "" + result = handler.prompt_generic("foo", default="baz") + self.assertEqual(result, "baz") + mock_prompt.assert_called_once_with( + [("", "\n"), ("class:bold", "foo"), ("", " [baz]: ")], + style=style, + is_password=False, + ) # text input value mock_prompt.reset_mock() - mock_prompt.return_value = 'bar' - result = handler.prompt_generic('foo') - self.assertEqual(result, 'bar') - mock_prompt.assert_called_once_with([('', '\n'), - ('class:bold', 'foo'), - ('', ': ')], - style=style, is_password=False) + mock_prompt.return_value = "bar" + result = handler.prompt_generic("foo") + self.assertEqual(result, "bar") + mock_prompt.assert_called_once_with( + [("", "\n"), ("class:bold", "foo"), ("", ": ")], + style=style, + is_password=False, + ) # bool value (no default; true input) mock_prompt.reset_mock() - mock_prompt.return_value = 'Y' - result = handler.prompt_generic('foo', is_bool=True) + mock_prompt.return_value = "Y" + result = handler.prompt_generic("foo", is_bool=True) self.assertTrue(result) - mock_prompt.assert_called_once_with([('', '\n'), - ('class:bold', 'foo'), - ('', ': ')], - style=style, is_password=False) + mock_prompt.assert_called_once_with( + [("", "\n"), ("class:bold", "foo"), ("", ": ")], + style=style, + is_password=False, + ) # bool value (no default; false input) mock_prompt.reset_mock() - mock_prompt.return_value = 'N' - result = handler.prompt_generic('foo', is_bool=True) + mock_prompt.return_value = "N" + result = handler.prompt_generic("foo", is_bool=True) self.assertFalse(result) - mock_prompt.assert_called_once_with([('', '\n'), - ('class:bold', 'foo'), - ('', ': ')], - style=style, is_password=False) + mock_prompt.assert_called_once_with( + [("", "\n"), ("class:bold", "foo"), ("", ": ")], + style=style, + is_password=False, + ) # bool value (default; no input) mock_prompt.reset_mock() - mock_prompt.return_value = '' - result = handler.prompt_generic('foo', is_bool=True, default=True) + mock_prompt.return_value = "" + result = handler.prompt_generic("foo", is_bool=True, default=True) self.assertTrue(result) - mock_prompt.assert_called_once_with([('', '\n'), - ('class:bold', 'foo'), - ('', ' [Y]: ')], - style=style, is_password=False) + mock_prompt.assert_called_once_with( + [("", "\n"), ("class:bold", "foo"), ("", " [Y]: ")], + style=style, + is_password=False, + ) # bool value (bad input) mock_prompt.reset_mock() - counter = {'attempts': 0} + counter = {"attempts": 0} + def omg(*args, **kwargs): - counter['attempts'] += 1 - if counter['attempts'] == 1: + counter["attempts"] += 1 + if counter["attempts"] == 1: # nb. bad input first time we ask - return 'doesnotmakesense' + return "doesnotmakesense" # nb. but good input after that - return 'N' + return "N" + mock_prompt.side_effect = omg - result = handler.prompt_generic('foo', is_bool=True) + result = handler.prompt_generic("foo", is_bool=True) self.assertFalse(result) # nb. user was prompted twice self.assertEqual(mock_prompt.call_count, 2) @@ -370,32 +408,34 @@ default.url = {db_url} # Ctrl+C mock_prompt.reset_mock() mock_prompt.side_effect = KeyboardInterrupt - with patch.object(mod, 'sys') as sys: + with patch.object(mod, "sys") as sys: sys.exit.side_effect = RuntimeError - self.assertRaises(RuntimeError, handler.prompt_generic, 'foo') + self.assertRaises(RuntimeError, handler.prompt_generic, "foo") sys.exit.assert_called_once_with(1) # Ctrl+D mock_prompt.reset_mock() mock_prompt.side_effect = EOFError - with patch.object(mod, 'sys') as sys: + with patch.object(mod, "sys") as sys: sys.exit.side_effect = RuntimeError - self.assertRaises(RuntimeError, handler.prompt_generic, 'foo') + self.assertRaises(RuntimeError, handler.prompt_generic, "foo") sys.exit.assert_called_once_with(1) # missing required value mock_prompt.reset_mock() - counter = {'attempts': 0} + counter = {"attempts": 0} + def omg(*args, **kwargs): - counter['attempts'] += 1 - if counter['attempts'] == 1: + counter["attempts"] += 1 + if counter["attempts"] == 1: # nb. no input first time we ask - return '' + return "" # nb. but good input after that - return 'bar' + return "bar" + mock_prompt.side_effect = omg - result = handler.prompt_generic('foo', required=True) - self.assertEqual(result, 'bar') + result = handler.prompt_generic("foo", required=True) + self.assertEqual(result, "bar") # nb. user was prompted twice self.assertEqual(mock_prompt.call_count, 2) @@ -405,47 +445,49 @@ default.url = {db_url} mock_prompt = MagicMock() def mock_import(name, globals=None, locals=None, fromlist=(), level=0): - if name == 'prompt_toolkit': - if fromlist == ('prompt',): + if name == "prompt_toolkit": + if fromlist == ("prompt",): return MagicMock(prompt=mock_prompt) return orig_import(name, globals, locals, fromlist, level) - with patch('builtins.__import__', side_effect=mock_import): - with patch.object(handler, 'rprint') as rprint: + 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' - result = handler.prompt_bool('foo') + mock_prompt.return_value = "Y" + result = handler.prompt_bool("foo") self.assertTrue(result) mock_prompt.assert_called_once() # no default; false input mock_prompt.reset_mock() - mock_prompt.return_value = 'N' - result = handler.prompt_bool('foo') + mock_prompt.return_value = "N" + result = handler.prompt_bool("foo") self.assertFalse(result) mock_prompt.assert_called_once() # default; no input mock_prompt.reset_mock() - mock_prompt.return_value = '' - result = handler.prompt_bool('foo', default=True) + mock_prompt.return_value = "" + result = handler.prompt_bool("foo", default=True) self.assertTrue(result) mock_prompt.assert_called_once() # bad input mock_prompt.reset_mock() - counter = {'attempts': 0} + counter = {"attempts": 0} + def omg(*args, **kwargs): - counter['attempts'] += 1 - if counter['attempts'] == 1: + counter["attempts"] += 1 + if counter["attempts"] == 1: # nb. bad input first time we ask - return 'doesnotmakesense' + return "doesnotmakesense" # nb. but good input after that - return 'N' + return "N" + mock_prompt.side_effect = omg - result = handler.prompt_bool('foo') + result = handler.prompt_bool("foo") self.assertFalse(result) # nb. user was prompted twice self.assertEqual(mock_prompt.call_count, 2) diff --git a/tests/test_people.py b/tests/test_people.py index be36fc7..5dfd35e 100644 --- a/tests/test_people.py +++ b/tests/test_people.py @@ -9,7 +9,6 @@ except ImportError: pass else: - class TestPeopleHandler(DataTestCase): def make_handler(self): @@ -17,7 +16,7 @@ else: def test_get_person(self): model = self.app.model - myperson = model.Person(full_name='Barny Rubble') + myperson = model.Person(full_name="Barny Rubble") self.session.add(myperson) self.session.commit() handler = self.make_handler() @@ -31,7 +30,7 @@ else: self.assertIs(person, myperson) # find person from user - myuser = model.User(username='barney', person=myperson) + myuser = model.User(username="barney", person=myperson) self.session.add(myuser) self.session.commit() person = handler.get_person(myuser) @@ -48,9 +47,9 @@ else: self.assertIsNone(person.full_name) self.assertNotIn(person, self.session) - person = handler.make_person(first_name='Barney', last_name='Rubble') + person = handler.make_person(first_name="Barney", last_name="Rubble") self.assertIsInstance(person, model.Person) - self.assertEqual(person.first_name, 'Barney') - self.assertEqual(person.last_name, 'Rubble') - self.assertEqual(person.full_name, 'Barney Rubble') + self.assertEqual(person.first_name, "Barney") + self.assertEqual(person.last_name, "Rubble") + self.assertEqual(person.full_name, "Barney Rubble") self.assertNotIn(person, self.session) diff --git a/tests/test_problems.py b/tests/test_problems.py index a791bed..2310455 100644 --- a/tests/test_problems.py +++ b/tests/test_problems.py @@ -14,15 +14,15 @@ class TestProblemCheck(ConfigTestCase): def test_system_key(self): check = self.make_check() - self.assertRaises(AttributeError, getattr, check, 'system_key') + self.assertRaises(AttributeError, getattr, check, "system_key") def test_problem_key(self): check = self.make_check() - self.assertRaises(AttributeError, getattr, check, 'problem_key') + self.assertRaises(AttributeError, getattr, check, "problem_key") def test_title(self): check = self.make_check() - self.assertRaises(AttributeError, getattr, check, 'title') + self.assertRaises(AttributeError, getattr, check, "title") def test_find_problems(self): check = self.make_check() @@ -44,8 +44,8 @@ class TestProblemCheck(ConfigTestCase): class FakeProblemCheck(mod.ProblemCheck): - system_key = 'wuttatest' - problem_key = 'fake_check' + system_key = "wuttatest" + problem_key = "fake_check" title = "Fake problem check" # def find_problems(self): @@ -69,7 +69,7 @@ class TestProblemHandler(ConfigTestCase): self.assertEqual(len(checks), 0) # but let's configure our fake check - self.config.setdefault('wutta.problems.modules', 'tests.test_problems') + self.config.setdefault("wutta.problems.modules", "tests.test_problems") checks = self.handler.get_all_problem_checks() self.assertIsInstance(checks, list) self.assertEqual(len(checks), 1) @@ -82,27 +82,31 @@ class TestProblemHandler(ConfigTestCase): self.assertEqual(len(checks), 0) # but let's configure our fake check - self.config.setdefault('wutta.problems.modules', 'tests.test_problems') + self.config.setdefault("wutta.problems.modules", "tests.test_problems") checks = self.handler.filter_problem_checks() self.assertIsInstance(checks, list) self.assertEqual(len(checks), 1) # filter by system_key - checks = self.handler.filter_problem_checks(systems=['wuttatest']) + checks = self.handler.filter_problem_checks(systems=["wuttatest"]) self.assertEqual(len(checks), 1) - checks = self.handler.filter_problem_checks(systems=['something_else']) + checks = self.handler.filter_problem_checks(systems=["something_else"]) self.assertEqual(len(checks), 0) # filter by problem_key - checks = self.handler.filter_problem_checks(problems=['fake_check']) + checks = self.handler.filter_problem_checks(problems=["fake_check"]) self.assertEqual(len(checks), 1) - checks = self.handler.filter_problem_checks(problems=['something_else']) + checks = self.handler.filter_problem_checks(problems=["something_else"]) self.assertEqual(len(checks), 0) # filter by both - checks = self.handler.filter_problem_checks(systems=['wuttatest'], problems=['fake_check']) + checks = self.handler.filter_problem_checks( + systems=["wuttatest"], problems=["fake_check"] + ) self.assertEqual(len(checks), 1) - checks = self.handler.filter_problem_checks(systems=['wuttatest'], problems=['bad_check']) + checks = self.handler.filter_problem_checks( + systems=["wuttatest"], problems=["bad_check"] + ) self.assertEqual(len(checks), 0) def test_get_supported_systems(self): @@ -113,14 +117,14 @@ class TestProblemHandler(ConfigTestCase): self.assertEqual(len(systems), 0) # but let's configure our fake check - self.config.setdefault('wutta.problems.modules', 'tests.test_problems') + self.config.setdefault("wutta.problems.modules", "tests.test_problems") systems = self.handler.get_supported_systems() self.assertIsInstance(systems, list) - self.assertEqual(systems, ['wuttatest']) + self.assertEqual(systems, ["wuttatest"]) def test_get_system_title(self): - title = self.handler.get_system_title('wutta') - self.assertEqual(title, 'wutta') + title = self.handler.get_system_title("wutta") + self.assertEqual(title, "wutta") def test_is_enabled(self): check = FakeProblemCheck(self.config) @@ -129,7 +133,7 @@ class TestProblemHandler(ConfigTestCase): self.assertTrue(self.handler.is_enabled(check)) # config can disable - self.config.setdefault('wutta.problems.wuttatest.fake_check.enabled', 'false') + self.config.setdefault("wutta.problems.wuttatest.fake_check.enabled", "false") self.assertFalse(self.handler.is_enabled(check)) def test_should_run_for_weekday(self): @@ -140,8 +144,8 @@ class TestProblemHandler(ConfigTestCase): self.assertTrue(self.handler.should_run_for_weekday(check, weekday)) # config can disable, e.g. for weekends - self.config.setdefault('wutta.problems.wuttatest.fake_check.day5', 'false') - self.config.setdefault('wutta.problems.wuttatest.fake_check.day6', 'false') + self.config.setdefault("wutta.problems.wuttatest.fake_check.day5", "false") + self.config.setdefault("wutta.problems.wuttatest.fake_check.day6", "false") for weekday in range(5): self.assertTrue(self.handler.should_run_for_weekday(check, weekday)) for weekday in (5, 6): @@ -152,10 +156,10 @@ class TestProblemHandler(ConfigTestCase): organized = self.handler.organize_problem_checks(checks) self.assertIsInstance(organized, dict) - self.assertEqual(list(organized), ['wuttatest']) - self.assertIsInstance(organized['wuttatest'], dict) - self.assertEqual(list(organized['wuttatest']), ['fake_check']) - self.assertIs(organized['wuttatest']['fake_check'], FakeProblemCheck) + self.assertEqual(list(organized), ["wuttatest"]) + self.assertIsInstance(organized["wuttatest"], dict) + self.assertEqual(list(organized["wuttatest"]), ["fake_check"]) + self.assertIs(organized["wuttatest"]["fake_check"], FakeProblemCheck) def test_find_problems(self): check = FakeProblemCheck(self.config) @@ -165,7 +169,7 @@ class TestProblemHandler(ConfigTestCase): def test_get_email_key(self): check = FakeProblemCheck(self.config) key = self.handler.get_email_key(check) - self.assertEqual(key, 'wuttatest_problems_fake_check') + self.assertEqual(key, "wuttatest_problems_fake_check") def test_get_global_email_context(self): context = self.handler.get_global_email_context() @@ -175,44 +179,53 @@ class TestProblemHandler(ConfigTestCase): check = FakeProblemCheck(self.config) problems = [] context = self.handler.get_check_email_context(check, problems) - self.assertEqual(context, {'system_title': 'wuttatest'}) + self.assertEqual(context, {"system_title": "wuttatest"}) def test_send_problem_report(self): check = FakeProblemCheck(self.config) problems = [] - with patch.object(self.app, 'send_email') as send_email: + with patch.object(self.app, "send_email") as send_email: self.handler.send_problem_report(check, problems) - send_email.assert_called_once_with('wuttatest_problems_fake_check', { - 'system_title': 'wuttatest', - 'config': self.config, - 'app': self.app, - 'check': check, - 'problems': problems, - }, default_subject="Fake problem check", attachments=None) + send_email.assert_called_once_with( + "wuttatest_problems_fake_check", + { + "system_title": "wuttatest", + "config": self.config, + "app": self.app, + "check": check, + "problems": problems, + }, + default_subject="Fake problem check", + attachments=None, + ) def test_run_problem_check(self): - with patch.object(FakeProblemCheck, 'find_problems') as find_problems: - with patch.object(self.handler, 'send_problem_report') as send_problem_report: + with patch.object(FakeProblemCheck, "find_problems") as find_problems: + with patch.object( + self.handler, "send_problem_report" + ) as send_problem_report: # check runs by default - find_problems.return_value = [{'foo': 'bar'}] + find_problems.return_value = [{"foo": "bar"}] problems = self.handler.run_problem_check(FakeProblemCheck) - self.assertEqual(problems, [{'foo': 'bar'}]) + self.assertEqual(problems, [{"foo": "bar"}]) find_problems.assert_called_once_with() send_problem_report.assert_called_once() # does not run if generally disabled find_problems.reset_mock() send_problem_report.reset_mock() - with patch.object(self.handler, 'is_enabled', return_value=False): + with patch.object(self.handler, "is_enabled", return_value=False): problems = self.handler.run_problem_check(FakeProblemCheck) self.assertIsNone(problems) find_problems.assert_not_called() send_problem_report.assert_not_called() # unless caller gives force flag - problems = self.handler.run_problem_check(FakeProblemCheck, force=True) - self.assertEqual(problems, [{'foo': 'bar'}]) + problems = self.handler.run_problem_check( + FakeProblemCheck, force=True + ) + self.assertEqual(problems, [{"foo": "bar"}]) find_problems.assert_called_once_with() send_problem_report.assert_called_once() @@ -220,7 +233,9 @@ class TestProblemHandler(ConfigTestCase): find_problems.reset_mock() send_problem_report.reset_mock() weekday = datetime.date.today().weekday() - self.config.setdefault(f'wutta.problems.wuttatest.fake_check.day{weekday}', 'false') + self.config.setdefault( + f"wutta.problems.wuttatest.fake_check.day{weekday}", "false" + ) problems = self.handler.run_problem_check(FakeProblemCheck) self.assertIsNone(problems) find_problems.assert_not_called() @@ -228,16 +243,18 @@ class TestProblemHandler(ConfigTestCase): # unless caller gives force flag problems = self.handler.run_problem_check(FakeProblemCheck, force=True) - self.assertEqual(problems, [{'foo': 'bar'}]) + self.assertEqual(problems, [{"foo": "bar"}]) find_problems.assert_called_once_with() send_problem_report.assert_called_once() def test_run_problem_checks(self): - with patch.object(FakeProblemCheck, 'find_problems') as find_problems: - with patch.object(self.handler, 'send_problem_report') as send_problem_report: + with patch.object(FakeProblemCheck, "find_problems") as find_problems: + with patch.object( + self.handler, "send_problem_report" + ) as send_problem_report: # check runs by default - find_problems.return_value = [{'foo': 'bar'}] + find_problems.return_value = [{"foo": "bar"}] self.handler.run_problem_checks([FakeProblemCheck]) find_problems.assert_called_once_with() send_problem_report.assert_called_once() @@ -245,7 +262,7 @@ class TestProblemHandler(ConfigTestCase): # does not run if generally disabled find_problems.reset_mock() send_problem_report.reset_mock() - with patch.object(self.handler, 'is_enabled', return_value=False): + with patch.object(self.handler, "is_enabled", return_value=False): self.handler.run_problem_checks([FakeProblemCheck]) find_problems.assert_not_called() send_problem_report.assert_not_called() @@ -259,7 +276,9 @@ class TestProblemHandler(ConfigTestCase): find_problems.reset_mock() send_problem_report.reset_mock() weekday = datetime.date.today().weekday() - self.config.setdefault(f'wutta.problems.wuttatest.fake_check.day{weekday}', 'false') + self.config.setdefault( + f"wutta.problems.wuttatest.fake_check.day{weekday}", "false" + ) self.handler.run_problem_checks([FakeProblemCheck]) find_problems.assert_not_called() send_problem_report.assert_not_called() diff --git a/tests/test_progress.py b/tests/test_progress.py index 16a6787..0b00231 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -10,7 +10,7 @@ class TestProgressBase(TestCase): def test_basic(self): # sanity / coverage check - prog = mod.ProgressBase('testing', 2) + prog = mod.ProgressBase("testing", 2) prog.update(1) prog.update(2) prog.finish() @@ -21,7 +21,7 @@ class TestConsoleProgress(TestCase): def test_basic(self): # sanity / coverage check - prog = mod.ConsoleProgress('testing', 2) + prog = mod.ConsoleProgress("testing", 2) prog.update(1) prog.update(2) prog.finish() diff --git a/tests/test_reports.py b/tests/test_reports.py index 7d730e7..1e7a13a 100644 --- a/tests/test_reports.py +++ b/tests/test_reports.py @@ -7,12 +7,12 @@ from wuttjamaican.testing import ConfigTestCase class MockFooReport(mod.Report): - report_key = 'mock_foo' + report_key = "mock_foo" report_title = "MOCK Report" def make_data(self, params, **kwargs): return [ - {'foo': 'bar'}, + {"foo": "bar"}, ] @@ -35,15 +35,15 @@ class TestReportHandler(ConfigTestCase): def test_get_report_modules(self): # no providers, no report modules - with patch.object(self.app, 'providers', new={}): + with patch.object(self.app, "providers", new={}): handler = self.make_handler() self.assertEqual(handler.get_report_modules(), []) # provider may specify modules as list providers = { - 'wuttatest': MagicMock(report_modules=['wuttjamaican.reports']), + "wuttatest": MagicMock(report_modules=["wuttjamaican.reports"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() modules = handler.get_report_modules() self.assertEqual(len(modules), 1) @@ -51,9 +51,9 @@ class TestReportHandler(ConfigTestCase): # provider may specify modules as string providers = { - 'wuttatest': MagicMock(report_modules='wuttjamaican.reports'), + "wuttatest": MagicMock(report_modules="wuttjamaican.reports"), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() modules = handler.get_report_modules() self.assertEqual(len(modules), 1) @@ -62,54 +62,54 @@ class TestReportHandler(ConfigTestCase): def test_get_reports(self): # no providers, no reports - with patch.object(self.app, 'providers', new={}): + with patch.object(self.app, "providers", new={}): handler = self.make_handler() self.assertEqual(handler.get_reports(), {}) # provider may define reports (via modules) providers = { - 'wuttatest': MagicMock(report_modules=['tests.test_reports']), + "wuttatest": MagicMock(report_modules=["tests.test_reports"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() reports = handler.get_reports() self.assertEqual(len(reports), 1) - self.assertIn('mock_foo', reports) + self.assertIn("mock_foo", reports) def test_get_report(self): providers = { - 'wuttatest': MagicMock(report_modules=['tests.test_reports']), + "wuttatest": MagicMock(report_modules=["tests.test_reports"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() # as instance - report = handler.get_report('mock_foo') + report = handler.get_report("mock_foo") self.assertIsInstance(report, mod.Report) self.assertIsInstance(report, MockFooReport) # as class - report = handler.get_report('mock_foo', instance=False) + report = handler.get_report("mock_foo", instance=False) self.assertTrue(issubclass(report, mod.Report)) self.assertIs(report, MockFooReport) # not found - report = handler.get_report('unknown') + report = handler.get_report("unknown") self.assertIsNone(report) def test_make_report_data(self): providers = { - 'wuttatest': MagicMock(report_modules=['tests.test_reports']), + "wuttatest": MagicMock(report_modules=["tests.test_reports"]), } - with patch.object(self.app, 'providers', new=providers): + with patch.object(self.app, "providers", new=providers): handler = self.make_handler() - report = handler.get_report('mock_foo') + report = handler.get_report("mock_foo") data = handler.make_report_data(report) self.assertEqual(len(data), 2) - self.assertIn('output_title', data) - self.assertEqual(data['output_title'], "MOCK Report") - self.assertIn('data', data) - self.assertEqual(data['data'], [{'foo': 'bar'}]) + self.assertIn("output_title", data) + self.assertEqual(data["output_title"], "MOCK Report") + self.assertIn("data", data) + self.assertEqual(data["data"], [{"foo": "bar"}]) diff --git a/tests/test_util.py b/tests/test_util.py index 0f11918..a078100 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -10,9 +10,17 @@ from wuttjamaican import util as mod from wuttjamaican.progress import ProgressBase -class A: pass -class B(A): pass -class C(B): pass +class A: + pass + + +class B(A): + pass + + +class C(B): + pass + class TestGetClassHierarchy(TestCase): @@ -35,15 +43,15 @@ class TestLoadEntryPoints(TestCase): def test_empty(self): # empty set returned for unknown group - result = mod.load_entry_points('this_should_never_exist!!!!!!') + result = mod.load_entry_points("this_should_never_exist!!!!!!") self.assertEqual(result, {}) def test_basic(self): # load some entry points which should "always" be present, # even in a testing environment. basic sanity check - result = mod.load_entry_points('console_scripts', ignore_errors=True) + result = mod.load_entry_points("console_scripts", ignore_errors=True) self.assertTrue(len(result) >= 1) - self.assertIn('pip', result) + self.assertIn("pip", result) def test_basic_pre_python_3_10(self): @@ -54,6 +62,7 @@ class TestLoadEntryPoints(TestCase): pytest.skip("this test is not relevant before python 3.10") import importlib.metadata + real_entry_points = importlib.metadata.entry_points() class FakeEntryPoints(dict): @@ -63,13 +72,13 @@ class TestLoadEntryPoints(TestCase): importlib = MagicMock() importlib.metadata.entry_points.return_value = FakeEntryPoints() - with patch.dict('sys.modules', **{'importlib': importlib}): + with patch.dict("sys.modules", **{"importlib": importlib}): # load some entry points which should "always" be present, # even in a testing environment. basic sanity check - result = mod.load_entry_points('console_scripts', ignore_errors=True) + result = mod.load_entry_points("console_scripts", ignore_errors=True) self.assertTrue(len(result) >= 1) - self.assertIn('pytest', result) + self.assertIn("pytest", result) def test_basic_pre_python_3_8(self): @@ -80,11 +89,12 @@ class TestLoadEntryPoints(TestCase): pytest.skip("this test is not relevant before python 3.8") from importlib.metadata import entry_points + real_entry_points = entry_points() class FakeEntryPoints(dict): def get(self, group, default): - if hasattr(real_entry_points, 'select'): + if hasattr(real_entry_points, "select"): return real_entry_points.select(group=group) return real_entry_points.get(group, []) @@ -94,19 +104,19 @@ class TestLoadEntryPoints(TestCase): orig_import = __import__ def mock_import(name, *args, **kwargs): - if name == 'importlib.metadata': + if name == "importlib.metadata": raise ImportError - if name == 'importlib_metadata': + if name == "importlib_metadata": return importlib_metadata return orig_import(name, *args, **kwargs) - with patch('builtins.__import__', side_effect=mock_import): + with patch("builtins.__import__", side_effect=mock_import): # load some entry points which should "always" be present, # even in a testing environment. basic sanity check - result = mod.load_entry_points('console_scripts', ignore_errors=True) + result = mod.load_entry_points("console_scripts", ignore_errors=True) self.assertTrue(len(result) >= 1) - self.assertIn('pytest', result) + self.assertIn("pytest", result) def test_error(self): @@ -123,22 +133,24 @@ class TestLoadEntryPoints(TestCase): importlib = MagicMock() importlib.metadata.entry_points.return_value = entry_points - with patch.dict('sys.modules', **{'importlib': importlib}): + with patch.dict("sys.modules", **{"importlib": importlib}): # empty set returned if errors suppressed - result = mod.load_entry_points('wuttatest.thingers', ignore_errors=True) + result = mod.load_entry_points("wuttatest.thingers", ignore_errors=True) self.assertEqual(result, {}) importlib.metadata.entry_points.assert_called_once_with() - entry_points.select.assert_called_once_with(group='wuttatest.thingers') + entry_points.select.assert_called_once_with(group="wuttatest.thingers") entry_point.load.assert_called_once_with() # error is raised, if not suppressed importlib.metadata.entry_points.reset_mock() entry_points.select.reset_mock() entry_point.load.reset_mock() - self.assertRaises(NotImplementedError, mod.load_entry_points, 'wuttatest.thingers') + self.assertRaises( + NotImplementedError, mod.load_entry_points, "wuttatest.thingers" + ) importlib.metadata.entry_points.assert_called_once_with() - entry_points.select.assert_called_once_with(group='wuttatest.thingers') + entry_points.select.assert_called_once_with(group="wuttatest.thingers") entry_point.load.assert_called_once_with() @@ -148,7 +160,7 @@ class TestLoadObject(TestCase): self.assertRaises(ValueError, mod.load_object, None) def test_basic(self): - result = mod.load_object('unittest:TestCase') + result = mod.load_object("unittest:TestCase") self.assertIs(result, TestCase) @@ -169,20 +181,20 @@ class TestParseBool(TestCase): self.assertFalse(mod.parse_bool(False)) def test_string_true(self): - self.assertTrue(mod.parse_bool('true')) - self.assertTrue(mod.parse_bool('yes')) - self.assertTrue(mod.parse_bool('y')) - self.assertTrue(mod.parse_bool('on')) - self.assertTrue(mod.parse_bool('1')) + self.assertTrue(mod.parse_bool("true")) + self.assertTrue(mod.parse_bool("yes")) + self.assertTrue(mod.parse_bool("y")) + self.assertTrue(mod.parse_bool("on")) + self.assertTrue(mod.parse_bool("1")) def test_string_false(self): - self.assertFalse(mod.parse_bool('false')) - self.assertFalse(mod.parse_bool('no')) - self.assertFalse(mod.parse_bool('n')) - self.assertFalse(mod.parse_bool('off')) - self.assertFalse(mod.parse_bool('0')) + self.assertFalse(mod.parse_bool("false")) + self.assertFalse(mod.parse_bool("no")) + self.assertFalse(mod.parse_bool("n")) + self.assertFalse(mod.parse_bool("off")) + self.assertFalse(mod.parse_bool("0")) # nb. assume false for unrecognized input - self.assertFalse(mod.parse_bool('whatever-else')) + self.assertFalse(mod.parse_bool("whatever-else")) class TestParseList(TestCase): @@ -198,76 +210,82 @@ class TestParseList(TestCase): self.assertIs(value, mylist) def test_single_value(self): - value = mod.parse_list('foo') + value = mod.parse_list("foo") self.assertEqual(len(value), 1) - self.assertEqual(value[0], 'foo') + self.assertEqual(value[0], "foo") def test_single_value_padded_by_spaces(self): - value = mod.parse_list(' foo ') + value = mod.parse_list(" foo ") self.assertEqual(len(value), 1) - self.assertEqual(value[0], 'foo') + self.assertEqual(value[0], "foo") def test_slash_is_not_a_separator(self): - value = mod.parse_list('/dev/null') + value = mod.parse_list("/dev/null") self.assertEqual(len(value), 1) - self.assertEqual(value[0], '/dev/null') + self.assertEqual(value[0], "/dev/null") def test_multiple_values_separated_by_whitespace(self): - value = mod.parse_list('foo bar baz') + value = mod.parse_list("foo bar baz") self.assertEqual(len(value), 3) - self.assertEqual(value[0], 'foo') - self.assertEqual(value[1], 'bar') - self.assertEqual(value[2], 'baz') + self.assertEqual(value[0], "foo") + self.assertEqual(value[1], "bar") + self.assertEqual(value[2], "baz") def test_multiple_values_separated_by_commas(self): - value = mod.parse_list('foo,bar,baz') + value = mod.parse_list("foo,bar,baz") self.assertEqual(len(value), 3) - self.assertEqual(value[0], 'foo') - self.assertEqual(value[1], 'bar') - self.assertEqual(value[2], 'baz') + self.assertEqual(value[0], "foo") + self.assertEqual(value[1], "bar") + self.assertEqual(value[2], "baz") def test_multiple_values_separated_by_whitespace_and_commas(self): - value = mod.parse_list(' foo, bar baz') + value = mod.parse_list(" foo, bar baz") self.assertEqual(len(value), 3) - self.assertEqual(value[0], 'foo') - self.assertEqual(value[1], 'bar') - self.assertEqual(value[2], 'baz') + self.assertEqual(value[0], "foo") + self.assertEqual(value[1], "bar") + self.assertEqual(value[2], "baz") def test_multiple_values_separated_by_whitespace_and_commas_with_some_quoting(self): - value = mod.parse_list(""" + value = mod.parse_list( + """ foo "C:\\some path\\with spaces\\and, a comma", baz - """) + """ + ) self.assertEqual(len(value), 3) - self.assertEqual(value[0], 'foo') - self.assertEqual(value[1], 'C:\\some path\\with spaces\\and, a comma') - self.assertEqual(value[2], 'baz') + self.assertEqual(value[0], "foo") + self.assertEqual(value[1], "C:\\some path\\with spaces\\and, a comma") + self.assertEqual(value[2], "baz") - def test_multiple_values_separated_by_whitespace_and_commas_with_single_quotes(self): - value = mod.parse_list(""" + def test_multiple_values_separated_by_whitespace_and_commas_with_single_quotes( + self, + ): + value = mod.parse_list( + """ foo 'C:\\some path\\with spaces\\and, a comma', baz - """) + """ + ) self.assertEqual(len(value), 3) - self.assertEqual(value[0], 'foo') - self.assertEqual(value[1], 'C:\\some path\\with spaces\\and, a comma') - self.assertEqual(value[2], 'baz') + self.assertEqual(value[0], "foo") + self.assertEqual(value[1], "C:\\some path\\with spaces\\and, a comma") + self.assertEqual(value[2], "baz") class TestMakeTitle(TestCase): def test_basic(self): - text = mod.make_title('foo_bar') + text = mod.make_title("foo_bar") self.assertEqual(text, "Foo Bar") class TestMakeFullName(TestCase): def test_basic(self): - name = mod.make_full_name('Fred', '', 'Flintstone', '') - self.assertEqual(name, 'Fred Flintstone') + name = mod.make_full_name("Fred", "", "Flintstone", "") + self.assertEqual(name, "Fred Flintstone") class TestProgressLoop(TestCase): @@ -278,12 +296,10 @@ class TestProgressLoop(TestCase): pass # with progress - mod.progress_loop(act, [1, 2, 3], ProgressBase, - message="whatever") + mod.progress_loop(act, [1, 2, 3], ProgressBase, message="whatever") # without progress - mod.progress_loop(act, [1, 2, 3], None, - message="whatever") + mod.progress_loop(act, [1, 2, 3], None, message="whatever") class TestResourcePath(TestCase): @@ -291,11 +307,13 @@ class TestResourcePath(TestCase): def test_basic(self): # package spec is resolved to path - path = mod.resource_path('wuttjamaican:util.py') - self.assertTrue(path.endswith('wuttjamaican/util.py')) + path = mod.resource_path("wuttjamaican:util.py") + self.assertTrue(path.endswith("wuttjamaican/util.py")) # absolute path returned as-is - self.assertEqual(mod.resource_path('/tmp/doesnotexist.txt'), '/tmp/doesnotexist.txt') + self.assertEqual( + mod.resource_path("/tmp/doesnotexist.txt"), "/tmp/doesnotexist.txt" + ) def test_basic_pre_python_3_9(self): @@ -310,20 +328,22 @@ class TestResourcePath(TestCase): orig_import = __import__ def mock_import(name, globals=None, locals=None, fromlist=(), level=0): - if name == 'importlib.resources': + if name == "importlib.resources": raise ImportError - if name == 'importlib_resources': + if name == "importlib_resources": return MagicMock(files=files, as_file=as_file) return orig_import(name, globals, locals, fromlist, level) - with patch('builtins.__import__', side_effect=mock_import): + with patch("builtins.__import__", side_effect=mock_import): # package spec is resolved to path - path = mod.resource_path('wuttjamaican:util.py') - self.assertTrue(path.endswith('wuttjamaican/util.py')) + path = mod.resource_path("wuttjamaican:util.py") + self.assertTrue(path.endswith("wuttjamaican/util.py")) # absolute path returned as-is - self.assertEqual(mod.resource_path('/tmp/doesnotexist.txt'), '/tmp/doesnotexist.txt') + self.assertEqual( + mod.resource_path("/tmp/doesnotexist.txt"), "/tmp/doesnotexist.txt" + ) class TestSimpleError(TestCase): diff --git a/tox.ini b/tox.ini index f0f9be6..bb3058b 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,12 @@ commands = pytest {posargs} [testenv:nox] extras = tests +[testenv:black] +basepython = python3.11 +extras = tests +deps = +commands = black --check . + [testenv:pylint] basepython = python3.11 extras = db,tests