diff --git a/CHANGELOG.md b/CHANGELOG.md index 9482ef2..983cc3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,17 +5,6 @@ All notable changes to WuttJamaican will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## v0.27.0 (2025-12-20) - -### Feat - -- add simple Diff class, to render common table - -### Fix - -- add `fallback_key` support for email settings -- include thousands separator for `app.render_quantity()` - ## v0.26.0 (2025-12-17) ### Feat diff --git a/docs/api/wuttjamaican.diffs.rst b/docs/api/wuttjamaican.diffs.rst deleted file mode 100644 index 716b0c1..0000000 --- a/docs/api/wuttjamaican.diffs.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wuttjamaican.diffs`` -====================== - -.. automodule:: wuttjamaican.diffs - :members: diff --git a/docs/glossary.rst b/docs/glossary.rst index 0218540..6550ea0 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -203,11 +203,10 @@ Glossary setting` definitions. email setting - This refers to the :term:`config settings ` for a - particular :term:`email type`, i.e. its sender and recipients, - subject etc. So each email type has a "collection" of settings, - and that collection is referred to simply as an "email setting" - in the singular. + This refers to the settings for a particular :term:`email type`, + i.e. its sender and recipients, subject etc. So each email type + has a "collection" of settings, and that collection is referred + to simply as an "email setting" in the singular. email template Usually this refers to the HTML or TXT template file, used to diff --git a/docs/index.rst b/docs/index.rst index f61d77d..e2ccb8a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -90,7 +90,6 @@ Contents api/wuttjamaican.db.model.upgrades api/wuttjamaican.db.sess api/wuttjamaican.db.util - api/wuttjamaican.diffs api/wuttjamaican.email api/wuttjamaican.enum api/wuttjamaican.exc diff --git a/docs/narr/email/sending.rst b/docs/narr/email/sending.rst index fb09fd4..31b76db 100644 --- a/docs/narr/email/sending.rst +++ b/docs/narr/email/sending.rst @@ -10,8 +10,8 @@ Basics To send an email you (usually) need 3 things: -* key - unique key identifying the :term:`email type` -* template - :term:`email template` file to render message body +* key - unique key identifying the type of email +* template - template file to render message body * context - context dict for template file rendering And actually the template just needs to exist somewhere it can be diff --git a/pyproject.toml b/pyproject.toml index 3bd01e4..2e48e86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "WuttJamaican" -version = "0.27.0" +version = "0.26.0" description = "Base package for Wutta Framework" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}] @@ -37,7 +37,6 @@ dependencies = [ "python-configuration", "typer", "uuid7", - "WebHelpers2", ] diff --git a/src/wuttjamaican/app.py b/src/wuttjamaican/app.py index 3b7ef86..044331f 100644 --- a/src/wuttjamaican/app.py +++ b/src/wuttjamaican/app.py @@ -962,8 +962,8 @@ class AppHandler: # pylint: disable=too-many-public-methods value = int(value) if empty_zero and value == 0: return "" - return f"{value:,}" - return f"{value:,}".rstrip("0") + return str(value) + return str(value).rstrip("0") def render_time_ago(self, value): """ diff --git a/src/wuttjamaican/diffs.py b/src/wuttjamaican/diffs.py deleted file mode 100644 index 38e4214..0000000 --- a/src/wuttjamaican/diffs.py +++ /dev/null @@ -1,186 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# WuttJamaican -- Base package for Wutta Framework -# Copyright © 2023-2025 Lance Edgar -# -# This file is part of Wutta Framework. -# -# Wutta Framework is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# Wutta Framework is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# Wutta Framework. If not, see . -# -################################################################################ -""" -Tools for displaying simple data diffs -""" - -from mako.template import Template -from webhelpers2.html import HTML - - -class Diff: # pylint: disable=too-many-instance-attributes - """ - Represent / display a basic "diff" between two data records. - - You must provide both the "old" and "new" data records, when - constructing an instance of this class. Then call - :meth:`render_html()` to display the diff table. - - :param config: The app :term:`config object`. - - :param old_data: Dict of "old" data record. - - :param new_data: Dict of "new" data record. - - :param fields: Optional list of field names. If not specified, - will be derived from the data records. - - :param nature: What sort of diff is being represented; must be one - of: ``("create", "update", "delete")`` - - :param old_color: Background color to display for "old/deleted" - field data, when applicable. - - :param new_color: Background color to display for "new/created" - field data, when applicable. - - :param cell_padding: Optional override for cell padding style. - """ - - cell_padding = "0.25rem" - - def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - config, - old_data: dict, - new_data: dict, - fields: list = None, - nature="update", - old_color="#ffebe9", - new_color="#dafbe1", - cell_padding=None, - ): - self.config = config - self.app = self.config.get_app() - self.old_data = old_data - self.new_data = new_data - self.columns = ["field name", "old value", "new value"] - self.fields = fields or self.make_fields() - self.nature = nature - self.old_color = old_color - self.new_color = new_color - if cell_padding: - self.cell_padding = cell_padding - - def make_fields(self): # pylint: disable=missing-function-docstring - return sorted(set(self.old_data) | set(self.new_data), key=lambda x: x.lower()) - - def render_html(self, template=None, **kwargs): - """ - Render the diff as HTML table. - - :param template: Name of template to render, if you need to - override the default. - - :param \\**kwargs: Remaining kwargs are passed as context to - the template renderer. - - :returns: HTML literal string - """ - context = kwargs - context["diff"] = self - - if not isinstance(template, Template): - path = self.app.resource_path( - template or "wuttjamaican:templates/diff.mako" - ) - template = Template(filename=path) - - return HTML.literal(template.render(**context)) - - def render_field_row(self, field): # pylint: disable=missing-function-docstring - is_diff = self.values_differ(field) - - kw = {} - if self.cell_padding: - kw["style"] = f"padding: {self.cell_padding}" - td_field = HTML.tag("td", class_="field", c=field, **kw) - - td_old_value = HTML.tag( - "td", - c=self.render_old_value(field), - **self.get_old_value_attrs(is_diff), - ) - - td_new_value = HTML.tag( - "td", - c=self.render_new_value(field), - **self.get_new_value_attrs(is_diff), - ) - - return HTML.tag("tr", c=[td_field, td_old_value, td_new_value]) - - def render_cell_value(self, value): # pylint: disable=missing-function-docstring - return HTML.tag("span", c=[value], style="font-family: monospace;") - - def render_old_value(self, field): # pylint: disable=missing-function-docstring - value = "" if self.nature == "create" else repr(self.old_value(field)) - return self.render_cell_value(value) - - def render_new_value(self, field): # pylint: disable=missing-function-docstring - value = "" if self.nature == "delete" else repr(self.new_value(field)) - return self.render_cell_value(value) - - def get_cell_attrs( # pylint: disable=missing-function-docstring - self, style=None, **attrs - ): - style = dict(style or {}) - - if self.cell_padding and "padding" not in style: - style["padding"] = self.cell_padding - - if style: - attrs["style"] = "; ".join([f"{k}: {v}" for k, v in style.items()]) - - return attrs - - def get_old_value_attrs( # pylint: disable=missing-function-docstring - self, is_diff - ): - style = {} - if self.nature == "update" and is_diff: - style["background-color"] = self.old_color - elif self.nature == "delete": - style["background-color"] = self.old_color - - return self.get_cell_attrs(style) - - def get_new_value_attrs( # pylint: disable=missing-function-docstring - self, is_diff - ): - style = {} - if self.nature == "create": - style["background-color"] = self.new_color - elif self.nature == "update" and is_diff: - style["background-color"] = self.new_color - - return self.get_cell_attrs(style) - - def old_value(self, field): # pylint: disable=missing-function-docstring - return self.old_data.get(field) - - def new_value(self, field): # pylint: disable=missing-function-docstring - return self.new_data.get(field) - - def values_differ(self, field): # pylint: disable=missing-function-docstring - return self.new_value(field) != self.old_value(field) diff --git a/src/wuttjamaican/email.py b/src/wuttjamaican/email.py index bbe1be7..f752dc7 100644 --- a/src/wuttjamaican/email.py +++ b/src/wuttjamaican/email.py @@ -23,10 +23,8 @@ """ Email Handler """ -# pylint: disable=too-many-lines import logging -import re import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText @@ -62,7 +60,6 @@ class EmailSetting: # pylint: disable=too-few-public-methods default_subject = "Something happened!" - # nb. this is not used for sending; only preview def sample_data(self): return { 'foo': 1234, @@ -87,80 +84,23 @@ class EmailSetting: # pylint: disable=too-few-public-methods * allows for hard-coded sample context which can be used to render templates for preview - .. attribute:: key - - Unique identifier for this :term:`email type`. - - This is the :term:`email key` used for config/template lookup, - e.g. when sending an email. - - This is automatically set based on the *class name* so there is - no need (or point) to set it. But the attribute is here for - read access, for convenience / code readability:: - - class poser_alert_foo(EmailSetting): - default_subject = "Something happened!" - - handler = app.get_email_handler() - setting = handler.get_email_setting("poser_alert_foo") - assert setting.key == "poser_alert_foo" - - See also :attr:`fallback_key`. - .. attribute:: default_subject - Default subject for sending emails of this type. - - Usually, if config does not override, this will become - :attr:`Message.subject`. + Default :attr:`Message.subject` for the email, if none is + configured. This is technically a Mako template string, so it will be rendered with the email context. But in most cases that feature can be ignored, and this will be a simple string. - - Calling code should not access this directly, but instead use - :meth:`get_default_subject()` . """ default_subject = None - fallback_key = None - """ - Optional fallback key to use for config/template lookup, if - nothing is found for :attr:`key`. - """ - def __init__(self, config): self.config = config self.app = config.get_app() self.key = self.__class__.__name__ - def get_description(self): - """ - This must return the full description for the :term:`email - type`. It is not used for the sending of email; only for - settings administration. - - Default logic will use the class docstring. - - :returns: String description for the email type - """ - return self.__class__.__doc__.strip() - - def get_default_subject(self): - """ - This must return the default subject, for sending emails of - this type. - - If config does not override, this will become - :attr:`Message.subject`. - - Default logic here returns :attr:`default_subject` as-is. - - :returns: Default subject as string - """ - return self.default_subject - def sample_data(self): """ Should return a dict with sample context needed to render the @@ -385,18 +325,13 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods """ if "email_settings" not in self.classes: self.classes["email_settings"] = {} - - # nb. we only want lower_case_names - all UpperCaseNames - # are assumed to be base classes - pattern = re.compile(r"^[a-z]") - for module in self.get_email_modules(): for name in dir(module): obj = getattr(module, name) if ( isinstance(obj, type) + and obj is not EmailSetting and issubclass(obj, EmailSetting) - and pattern.match(obj.__name__) ): self.classes["email_settings"][obj.__name__] = obj @@ -433,9 +368,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods """ return Message(**kwargs) - def make_auto_message( - self, key, context=None, default_subject=None, fallback_key=None, **kwargs - ): + def make_auto_message(self, key, context=None, default_subject=None, **kwargs): """ Make a new email message using config to determine its properties, and auto-generating body from a template. @@ -446,8 +379,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods :param key: Unique key for this particular "type" of message. This key is used as a prefix for all config settings and - template names pertinent to the message. See also the - ``fallback_key`` param, below. + template names pertinent to the message. :param context: Context dict used to render template(s) for the message. @@ -455,10 +387,6 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods :param default_subject: Optional :attr:`~Message.subject` template/string to use, if config does not specify one. - :param fallback_key: Optional fallback :term:`email key` to - use for config/template lookup, if nothing is found for - ``key``. - :param \\**kwargs: Any remaining kwargs are passed as-is to :meth:`make_message()`. More on this below. @@ -483,7 +411,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods kwargs["sender"] = self.get_auto_sender(key) if "subject" not in kwargs: kwargs["subject"] = self.get_auto_subject( - key, context, default=default_subject, fallback_key=fallback_key + key, context, default=default_subject ) if "to" not in kwargs: kwargs["to"] = self.get_auto_to(key) @@ -492,47 +420,11 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods 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, fallback_key=fallback_key - ) + 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, fallback_key=fallback_key - ) + kwargs["html_body"] = self.get_auto_html_body(key, context) return self.make_message(**kwargs) - def get_email_context(self, key, context=None): # pylint: disable=unused-argument - """ - This must return the "full" context for rendering the email - subject and/or body templates. - - Normally the input ``context`` is coming from the - :meth:`send_email()` param of the same name. - - By default, this method modifies the input context to add the - following: - - * ``config`` - reference to the :term:`config object` - * ``app`` - reference to the :term:`app handler` - - Subclass may further modify as needed. - - :param key: The :term:`email key` for which to get context. - - :param context: Input context dict. - - :returns: Final context dict - """ - if context is None: - context = {} - context.update( - { - "config": self.config, - "app": self.app, - } - ) - return context - def get_auto_sender(self, key): """ Returns automatic @@ -563,13 +455,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods return self.config.get(f"{self.config.appname}.email.default.replyto") def get_auto_subject( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - key, - context=None, - rendered=True, - setting=None, - default=None, - fallback_key=None, + self, key, context=None, rendered=True, setting=None, default=None ): """ Returns automatic :attr:`~wuttjamaican.email.Message.subject` @@ -578,8 +464,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods This calls :meth:`get_auto_subject_template()` and then (usually) renders the result using the given context. - :param key: Key for the :term:`email type`. See also the - ``fallback_key`` param, below. + :param key: Key for the :term:`email type`. :param context: Dict of context for rendering the subject template, if applicable. @@ -594,23 +479,16 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods :param default: Default subject to use if none is configured. - :param fallback_key: Optional fallback :term:`email key` to - use for config lookup, if nothing is found for ``key``. - :returns: Final subject text, either "raw" or rendered. """ - template = self.get_auto_subject_template( - key, setting=setting, default=default, fallback_key=fallback_key - ) + template = self.get_auto_subject_template(key, setting=setting, default=default) if not rendered: return template - context = self.get_email_context(key, context) + context = context or {} return Template(template).render(**context) - def get_auto_subject_template( - self, key, setting=None, default=None, fallback_key=None - ): + def get_auto_subject_template(self, key, setting=None, default=None): """ Returns the template string to use for automatic subject line of a message, as determined by config. @@ -631,32 +509,22 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods :param default: Default subject to use if none is configured. - :param fallback_key: Optional fallback :term:`email key` to - use for config lookup, if nothing is found for ``key``. - :returns: Final subject template, as raw text. """ # prefer configured subject specific to key - if 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 # or use caller-specified default, if applicable if default: return default - # or use fallback key, if provided - if fallback_key: - if template := self.config.get( - f"{self.config.appname}.email.{fallback_key}.subject" - ): - return template - # or subject from email setting, if defined if not setting: setting = self.get_email_setting(key) - if setting: - if subject := setting.get_default_subject(): - return subject + if setting and setting.default_subject: + return setting.default_subject # fall back to global default return self.config.get( @@ -701,34 +569,32 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods f"{self.config.appname}.email.default.{typ}", default=[] ) - def get_auto_txt_body(self, key, context=None, fallback_key=None): + def get_auto_txt_body(self, key, context=None): """ Returns automatic :attr:`~wuttjamaican.email.Message.txt_body` content for a message, as determined by config. This renders a template with the given context. """ - template = self.get_auto_body_template(key, "txt", fallback_key=fallback_key) + template = self.get_auto_body_template(key, "txt") if template: - context = self.get_email_context(key, context) + context = context or {} return template.render(**context) return None - def get_auto_html_body(self, key, context=None, fallback_key=None): + def get_auto_html_body(self, key, context=None): """ Returns automatic :attr:`~wuttjamaican.email.Message.html_body` content for a message, as determined by config. This renders a template with the given context. """ - template = self.get_auto_body_template(key, "html", fallback_key=fallback_key) + template = self.get_auto_body_template(key, "html") if template: - context = self.get_email_context(key, context) + context = context or {} return template.render(**context) return None - def get_auto_body_template( # pylint: disable=empty-docstring - self, key, mode, fallback_key=None - ): + def get_auto_body_template(self, key, mode): # pylint: disable=empty-docstring """ """ mode = mode.lower() if mode == "txt": @@ -739,19 +605,9 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods raise ValueError("requested mode not supported") try: - - # prefer specific template for key return templates.get_template(f"{key}.{mode}.mako") - except TopLevelLookupException: - - # but can use fallback if applicable - if fallback_key: - try: - return templates.get_template(f"{fallback_key}.{mode}.mako") - except TopLevelLookupException: - pass - + pass return None def get_notes(self, key): @@ -900,14 +756,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods ) def send_email( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - key=None, - context=None, - message=None, - sender=None, - recips=None, - fallback_key=None, - **kwargs, + self, key=None, context=None, message=None, sender=None, recips=None, **kwargs ): """ Send an email message. @@ -924,7 +773,6 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods :param key: When auto-creating a message, this is the :term:`email key` identifying the type of email to send. Used to lookup config settings and template files. - See also the ``fallback_key`` param, below. :param context: Context dict for rendering automatic email template(s). @@ -964,10 +812,6 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods context = {'data': [1, 2, 3]} app.send_email('foo', context, to='me@example.com', cc='bobby@example.com') - :param fallback_key: Optional fallback :term:`email key` to - use for config/template lookup, if nothing is found for - ``key``. - :param \\**kwargs: Any remaining kwargs are passed along to :meth:`make_auto_message()`. So, not used if you provide the ``message``. @@ -983,9 +827,7 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods # auto-create message from key + context if sender: kwargs["sender"] = sender - message = self.make_auto_message( - key, context or {}, fallback_key=fallback_key, **kwargs - ) + 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 - " diff --git a/src/wuttjamaican/templates/diff.mako b/src/wuttjamaican/templates/diff.mako deleted file mode 100644 index 977c1ef..0000000 --- a/src/wuttjamaican/templates/diff.mako +++ /dev/null @@ -1,15 +0,0 @@ -## -*- coding: utf-8; -*- - - - - % for column in diff.columns: - - % endfor - - - - % for field in diff.fields: - ${diff.render_field_row(field)} - % endfor - -
${column}
diff --git a/tests/test_app.py b/tests/test_app.py index 7bdebf9..cd2236b 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -672,14 +672,6 @@ app_title = WuttaTest self.assertEqual(self.app.render_quantity(0), "0") self.assertEqual(self.app.render_quantity(0, empty_zero=True), "") - # has thousands separator - value = 1234 - self.assertEqual(self.app.render_quantity(value), "1,234") - value = decimal.Decimal("1234.567") - self.assertEqual(self.app.render_quantity(value), "1,234.567") - value = decimal.Decimal("1234.567000") - self.assertEqual(self.app.render_quantity(value), "1,234.567") - def test_render_time_ago(self): with patch.object(mod, "humanize") as humanize: humanize.naturaltime.return_value = "now" diff --git a/tests/test_diffs.py b/tests/test_diffs.py deleted file mode 100644 index 32dedeb..0000000 --- a/tests/test_diffs.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- coding: utf-8; -*- - -from wuttjamaican import diffs as mod -from wuttjamaican.testing import ConfigTestCase - - -class TestDiff(ConfigTestCase): - - def make_diff(self, *args, **kwargs): - return mod.Diff(self.config, *args, **kwargs) - - def test_constructor(self): - old_data = {"foo": "bar"} - new_data = {"foo": "baz"} - diff = self.make_diff(old_data, new_data, fields=["foo"]) - self.assertEqual(diff.fields, ["foo"]) - self.assertEqual(diff.cell_padding, "0.25rem") - diff = self.make_diff(old_data, new_data, cell_padding="0.5rem") - self.assertEqual(diff.cell_padding, "0.5rem") - - def test_make_fields(self): - old_data = {"foo": "bar"} - new_data = {"foo": "bar", "baz": "zer"} - # nb. this calls make_fields() - diff = self.make_diff(old_data, new_data) - # TODO: should the fields be cumulative? or just use new_data? - self.assertEqual(diff.fields, ["baz", "foo"]) - - def test_values(self): - old_data = {"foo": "bar"} - new_data = {"foo": "baz"} - diff = self.make_diff(old_data, new_data) - self.assertEqual(diff.old_value("foo"), "bar") - self.assertEqual(diff.new_value("foo"), "baz") - - def test_values_differ(self): - old_data = {"foo": "bar"} - new_data = {"foo": "baz"} - diff = self.make_diff(old_data, new_data) - self.assertTrue(diff.values_differ("foo")) - - old_data = {"foo": "bar"} - new_data = {"foo": "bar"} - diff = self.make_diff(old_data, new_data) - self.assertFalse(diff.values_differ("foo")) - - def test_render_values(self): - old_data = {"foo": "bar"} - new_data = {"foo": "baz"} - diff = self.make_diff(old_data, new_data) - self.assertEqual( - diff.render_old_value("foo"), - ''bar'', - ) - self.assertEqual( - diff.render_new_value("foo"), - ''baz'', - ) - - def test_get_old_value_attrs(self): - - # no change - old_data = {"foo": "bar"} - new_data = {"foo": "bar"} - diff = self.make_diff(old_data, new_data, nature="update") - self.assertEqual(diff.get_old_value_attrs(False), {"style": "padding: 0.25rem"}) - - # update - old_data = {"foo": "bar"} - new_data = {"foo": "baz"} - diff = self.make_diff(old_data, new_data, nature="update") - self.assertEqual( - diff.get_old_value_attrs(True), - {"style": f"background-color: {diff.old_color}; padding: 0.25rem"}, - ) - - # delete - old_data = {"foo": "bar"} - new_data = {} - diff = self.make_diff(old_data, new_data, nature="delete") - self.assertEqual( - diff.get_old_value_attrs(True), - {"style": f"background-color: {diff.old_color}; padding: 0.25rem"}, - ) - - def test_get_new_value_attrs(self): - - # no change - old_data = {"foo": "bar"} - new_data = {"foo": "bar"} - diff = self.make_diff(old_data, new_data, nature="update") - self.assertEqual(diff.get_new_value_attrs(False), {"style": "padding: 0.25rem"}) - - # update - old_data = {"foo": "bar"} - new_data = {"foo": "baz"} - diff = self.make_diff(old_data, new_data, nature="update") - self.assertEqual( - diff.get_new_value_attrs(True), - {"style": f"background-color: {diff.new_color}; padding: 0.25rem"}, - ) - - # create - old_data = {} - new_data = {"foo": "bar"} - diff = self.make_diff(old_data, new_data, nature="create") - self.assertEqual( - diff.get_new_value_attrs(True), - {"style": f"background-color: {diff.new_color}; padding: 0.25rem"}, - ) - - def test_render_field_row(self): - old_data = {"foo": "bar"} - new_data = {"foo": "baz"} - diff = self.make_diff(old_data, new_data) - row = diff.render_field_row("foo") - self.assertIn("", row) - self.assertIn("'bar'", row) - self.assertIn( - f'style="background-color: {diff.old_color}; padding: 0.25rem"', row - ) - self.assertIn("'baz'", row) - self.assertIn( - f'style="background-color: {diff.new_color}; padding: 0.25rem"', row - ) - self.assertIn("", row) - - def test_render_html(self): - old_data = {"foo": "bar"} - new_data = {"foo": "baz"} - diff = self.make_diff(old_data, new_data) - html = diff.render_html() - self.assertIn("", html) - self.assertIn("'bar'", html) - self.assertIn( - f'style="background-color: {diff.old_color}; padding: 0.25rem"', html - ) - self.assertIn("'baz'", html) - self.assertIn( - f'style="background-color: {diff.new_color}; padding: 0.25rem"', html - ) - self.assertIn("", html) - self.assertIn("", html) diff --git a/tests/test_email.py b/tests/test_email.py index c9dca71..e4b6ff9 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -5,7 +5,6 @@ from unittest import TestCase from unittest.mock import patch, MagicMock import pytest -from mako.template import Template from wuttjamaican import email as mod from wuttjamaican.util import resource_path @@ -21,16 +20,6 @@ class TestEmailSetting(ConfigTestCase): self.assertIs(setting.app, self.app) self.assertEqual(setting.key, "EmailSetting") - def test_get_description(self): - - class MockSetting(mod.EmailSetting): - """ - this should be a good test - """ - - setting = MockSetting(self.config) - self.assertEqual(setting.get_description(), "this should be a good test") - def test_sample_data(self): setting = mod.EmailSetting(self.config) self.assertEqual(setting.sample_data(), {}) @@ -310,9 +299,7 @@ class TestEmailHandler(ConfigTestCase): 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, fallback_key=None - ) + get_auto_subject.assert_called_once_with("foo", {}, default=None) # to with patch.object(handler, "get_auto_to") as get_auto_to: @@ -343,18 +330,14 @@ class TestEmailHandler(ConfigTestCase): 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", {"config": self.config, "app": self.app}, fallback_key=None - ) + 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) get_auto_html_body.assert_not_called() msg = handler.make_auto_message("foo") - get_auto_html_body.assert_called_once_with( - "foo", {"config": self.config, "app": self.app}, fallback_key=None - ) + get_auto_html_body.assert_called_once_with("foo", {}) def test_get_auto_sender(self): handler = self.make_handler() @@ -401,11 +384,6 @@ class TestEmailHandler(ConfigTestCase): template = handler.get_auto_subject_template("foo") self.assertEqual(template, "Foo Message") - # can configure via fallback_key - self.config.setdefault("wutta.email.bar.subject", "Bar Message") - template = handler.get_auto_subject_template("baz", fallback_key="bar") - self.assertEqual(template, "Bar Message") - # EmailSetting can provide default subject providers = { "wuttatest": MagicMock(email_modules=["tests.test_email"]), @@ -468,48 +446,26 @@ class TestEmailHandler(ConfigTestCase): self.assertEqual(recips, ["bob@example.com"]) def test_get_auto_body_template(self): + from mako.template import Template + handler = self.make_handler() - # error if invalid mode (must be 'html' or 'txt') - self.assertRaises(ValueError, handler.get_auto_body_template, "foo", "BAD_MODE") + # error if bad request + self.assertRaises(ValueError, handler.get_auto_body_template, "foo", "BADTYPE") - # no template by default - self.assertIsNone(handler.get_auto_body_template("foo", "html")) - self.assertIsNone(handler.get_auto_body_template("foo", "txt")) + # empty by default + template = handler.get_auto_body_template("foo", "txt") + self.assertIsNone(template) - # mock template lookup + # but returns a template if it exists providers = { "wuttatest": MagicMock(email_templates=["tests:email-templates"]), } with patch.object(self.app, "providers", new=providers): handler = self.make_handler() - - # template exists (txt) - template = handler.get_auto_body_template("test_foo", "txt") - self.assertIsInstance(template, Template) - self.assertEqual(template.uri, "test_foo.txt.mako") - - # template exists (html) - template = handler.get_auto_body_template("test_foo", "html") - self.assertIsInstance(template, Template) - self.assertEqual(template.uri, "test_foo.html.mako") - - # no such template - template = handler.get_auto_body_template("no_such_template", "html") - self.assertIsNone(template) - - # but can use fallback - template = handler.get_auto_body_template( - "no_such_template", "html", fallback_key="test_foo" - ) - self.assertIsInstance(template, Template) - self.assertEqual(template.uri, "test_foo.html.mako") - - # what if fallback is also not found - template = handler.get_auto_body_template( - "no_such_template", "html", fallback_key="this_neither" - ) - self.assertIsNone(template) + template = handler.get_auto_body_template("test_foo", "txt") + self.assertIsInstance(template, Template) + self.assertEqual(template.uri, "test_foo.txt.mako") def test_get_auto_txt_body(self): handler = self.make_handler()