diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b5b295..7126fd7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,16 +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.13.0 (2024-08-26)
-
-### Feat
-
-- add basic email handler support
-- add `util.resource_path()` function
-- add app handler method, `get_appdir()`
-- add basic support for progress indicators
-- add table/model for app upgrades
-
## v0.12.1 (2024-08-22)
### Fix
diff --git a/docs/api/wuttjamaican/db.model.upgrades.rst b/docs/api/wuttjamaican/db.model.upgrades.rst
deleted file mode 100644
index f89fcf2..0000000
--- a/docs/api/wuttjamaican/db.model.upgrades.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-
-``wuttjamaican.db.model.upgrades``
-==================================
-
-.. automodule:: wuttjamaican.db.model.upgrades
- :members:
diff --git a/docs/api/wuttjamaican/email.handler.rst b/docs/api/wuttjamaican/email.handler.rst
deleted file mode 100644
index 4e4900f..0000000
--- a/docs/api/wuttjamaican/email.handler.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-
-``wuttjamaican.email.handler``
-==============================
-
-.. automodule:: wuttjamaican.email.handler
- :members:
diff --git a/docs/api/wuttjamaican/email.message.rst b/docs/api/wuttjamaican/email.message.rst
deleted file mode 100644
index 1656196..0000000
--- a/docs/api/wuttjamaican/email.message.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-
-``wuttjamaican.email.message``
-==============================
-
-.. automodule:: wuttjamaican.email.message
- :members:
diff --git a/docs/api/wuttjamaican/email.rst b/docs/api/wuttjamaican/email.rst
deleted file mode 100644
index d187d98..0000000
--- a/docs/api/wuttjamaican/email.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-
-``wuttjamaican.email``
-======================
-
-.. automodule:: wuttjamaican.email
- :members:
diff --git a/docs/api/wuttjamaican/enum.rst b/docs/api/wuttjamaican/enum.rst
deleted file mode 100644
index 12b0081..0000000
--- a/docs/api/wuttjamaican/enum.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-
-``wuttjamaican.enum``
-=====================
-
-.. automodule:: wuttjamaican.enum
- :members:
diff --git a/docs/api/wuttjamaican/index.rst b/docs/api/wuttjamaican/index.rst
index 69a754e..43b1642 100644
--- a/docs/api/wuttjamaican/index.rst
+++ b/docs/api/wuttjamaican/index.rst
@@ -15,14 +15,8 @@
db.model
db.model.auth
db.model.base
- db.model.upgrades
db.sess
- email
- email.handler
- email.message
- enum
exc
people
- progress
testing
util
diff --git a/docs/api/wuttjamaican/progress.rst b/docs/api/wuttjamaican/progress.rst
deleted file mode 100644
index 7a14cb3..0000000
--- a/docs/api/wuttjamaican/progress.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-
-``wuttjamaican.progress``
-=========================
-
-.. automodule:: wuttjamaican.progress
- :members:
diff --git a/docs/conf.py b/docs/conf.py
index 23fc2cf..baf9505 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -22,7 +22,6 @@ extensions = [
'sphinxcontrib.programoutput',
'sphinx.ext.viewcode',
'sphinx.ext.todo',
- 'enum_tools.autoenum',
]
templates_path = ['_templates']
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 3b87762..c9b2f94 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -25,14 +25,6 @@ Glossary
Usually this is named ``app`` and is located at the root of the
virtual environment.
- Can be retrieved via
- :meth:`~wuttjamaican.app.AppHandler.get_appdir()`.
-
- app enum
- Python module whose namespace contains all the "enum" values
- used by the :term:`app`. Available on the :term:`app handler`
- as :attr:`~wuttjamaican.app.AppHandler.enum`.
-
app handler
Python object representing the core :term:`handler` for the
:term:`app`. There is normally just one "global" app handler;
@@ -124,12 +116,6 @@ Glossary
In practice this generally refers to a
:class:`~wuttjamaican.db.sess.Session` instance.
- email handler
- The :term:`handler` responsible for sending email on behalf of
- the :term:`app`.
-
- Default is :class:`~wuttjamaican.email.handler.EmailHandler`.
-
entry point
This refers to a "setuptools-style" entry point specifically,
which is a mechanism used to register "plugins" and the like.
diff --git a/pyproject.toml b/pyproject.toml
index af34925..dab36b5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
[project]
name = "WuttJamaican"
-version = "0.13.0"
+version = "0.12.1"
description = "Base package for Wutta Framework"
readme = "README.md"
authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
@@ -27,16 +27,13 @@ classifiers = [
requires-python = ">= 3.8"
dependencies = [
'importlib-metadata; python_version < "3.10"',
- "importlib_resources ; python_version < '3.9'",
- "progress",
"python-configuration",
]
[project.optional-dependencies]
-db = ["SQLAlchemy<2", "alembic", "alembic-postgresql-enum", "passlib"]
-email = ["Mako"]
-docs = ["Sphinx", "sphinxcontrib-programoutput", "enum-tools[sphinx]", "furo"]
+db = ["SQLAlchemy<2", "alembic", "passlib"]
+docs = ["Sphinx", "sphinxcontrib-programoutput", "furo"]
tests = ["pytest-cov", "tox"]
diff --git a/src/wuttjamaican/app.py b/src/wuttjamaican/app.py
index 5d67df2..0d1eb77 100644
--- a/src/wuttjamaican/app.py
+++ b/src/wuttjamaican/app.py
@@ -26,12 +26,9 @@ WuttJamaican - app handler
import importlib
import os
-import sys
import warnings
-from wuttjamaican.util import (load_entry_points, load_object,
- make_title, make_uuid, parse_bool,
- progress_loop)
+from wuttjamaican.util import load_entry_points, load_object, make_title, make_uuid, parse_bool
class AppHandler:
@@ -62,16 +59,6 @@ class AppHandler:
need to call :meth:`get_model()` yourself - that part will
happen automatically.
- .. attribute:: enum
-
- Reference to the :term:`app enum` module.
-
- Note that :meth:`get_enum()` is responsible for determining
- which module this will point to. However you can always get
- the model using this attribute (e.g. ``app.enum``) and do not
- need to call :meth:`get_enum()` yourself - that part will
- happen automatically.
-
.. attribute:: providers
Dictionary of :class:`AppProvider` instances, as returned by
@@ -79,9 +66,7 @@ class AppHandler:
"""
default_app_title = "WuttJamaican"
default_model_spec = 'wuttjamaican.db.model'
- default_enum_spec = 'wuttjamaican.enum'
default_auth_handler_spec = 'wuttjamaican.auth:AuthHandler'
- default_email_handler_spec = 'wuttjamaican.email:EmailHandler'
default_people_handler_spec = 'wuttjamaican.people:PeopleHandler'
def __init__(self, config):
@@ -118,9 +103,6 @@ class AppHandler:
if name == 'model':
return self.get_model()
- if name == 'enum':
- return self.get_enum()
-
if name == 'providers':
self.providers = self.get_all_providers()
return self.providers
@@ -316,30 +298,6 @@ class AppHandler:
self.model = importlib.import_module(spec)
return self.model
- def get_enum(self):
- """
- Returns the :term:`app enum` module.
-
- Note that you don't actually need to call this method; you can
- get the module by simply accessing :attr:`enum`
- (e.g. ``app.enum``) instead.
-
- By default this will return :mod:`wuttjamaican.enum` unless
- the config class or some :term:`config extension` has provided
- another default.
-
- A custom app can override the default like so (within a config
- extension)::
-
- 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)
- self.enum = importlib.import_module(spec)
- return self.enum
-
def load_object(self, spec):
"""
Import and/or load and return the object designated by the
@@ -355,54 +313,6 @@ class AppHandler:
"""
return load_object(spec)
- def get_appdir(self, *args, **kwargs):
- """
- Returns path to the :term:`app dir`.
-
- This does not check for existence of the path, it only reads
- it from config or (optionally) provides a default path.
-
- :param configured_only: Pass ``True`` here if you only want
- the configured path and ignore the default path.
-
- :param create: Pass ``True`` here if you want to ensure the
- returned path exists, creating it if necessary.
-
- :param \*args: Any additional args will be added as child
- paths for the final value.
-
- For instance, assuming ``/srv/envs/poser`` is the virtual
- environment root::
-
- app.get_appdir() # => /srv/envs/poser/app
-
- app.get_appdir('data') # => /srv/envs/poser/app/data
- """
- 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)
-
- # get configured path
- kwargs.setdefault('usedb', False)
- path = self.config.get(f'{self.appname}.appdir', **kwargs)
-
- # add any subpath info
- if path and args:
- path = os.path.join(path, *args)
-
- # create path if requested/needed
- if create:
- if not path:
- raise ValueError("appdir path unknown! so cannot create it.")
- if not os.path.exists(path):
- os.makedirs(path)
-
- return path
-
def make_appdir(self, path, subfolders=None, **kwargs):
"""
Establish an :term:`app dir` at the given path.
@@ -469,18 +379,6 @@ class AppHandler:
"""
return make_uuid()
- def progress_loop(self, *args, **kwargs):
- """
- Convenience method to iterate over a set of items, invoking
- logic for each, and updating a progress indicator along the
- way.
-
- This is a wrapper around
- :func:`wuttjamaican.util.progress_loop()`; see those docs for
- param details.
- """
- return progress_loop(*args, **kwargs)
-
def get_session(self, obj):
"""
Returns the SQLAlchemy session with which the given object is
@@ -607,21 +505,6 @@ class AppHandler:
self.handlers['auth'] = factory(self.config, **kwargs)
return self.handlers['auth']
- def get_email_handler(self, **kwargs):
- """
- Get the configured :term:`email handler`.
-
- See also :meth:`send_email()`.
-
- :rtype: :class:`~wuttjamaican.email.handler.EmailHandler`
- """
- 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']
-
def get_people_handler(self, **kwargs):
"""
Get the configured "people" :term:`handler`.
@@ -650,15 +533,6 @@ class AppHandler:
"""
return self.get_people_handler().get_person(obj, **kwargs)
- def send_email(self, *args, **kwargs):
- """
- Send an email message.
-
- This is a convenience wrapper around
- :meth:`~wuttjamaican.email.handler.EmailHandler.send_email()`.
- """
- self.get_email_handler().send_email(*args, **kwargs)
-
class AppProvider:
"""
diff --git a/src/wuttjamaican/conf.py b/src/wuttjamaican/conf.py
index c04b603..6c3adf5 100644
--- a/src/wuttjamaican/conf.py
+++ b/src/wuttjamaican/conf.py
@@ -440,8 +440,8 @@ class WuttaConfig:
# raise error if required value not found
if require:
- message = message or "missing config"
- raise ConfigurationError(f"{message}; set value for: {key}")
+ message = message or "missing or invalid config"
+ raise ConfigurationError(f"{message}; please set config value for: {key}")
# give the default value if specified
if default is not UNSPECIFIED:
diff --git a/src/wuttjamaican/db/alembic/env.py b/src/wuttjamaican/db/alembic/env.py
index 4a20bd5..2bc674c 100644
--- a/src/wuttjamaican/db/alembic/env.py
+++ b/src/wuttjamaican/db/alembic/env.py
@@ -1,6 +1,5 @@
# -*- coding: utf-8; -*-
-import alembic_postgresql_enum
from alembic import context
from wuttjamaican.conf import make_config
diff --git a/src/wuttjamaican/db/alembic/versions/ebd75b9feaa7_add_upgrades.py b/src/wuttjamaican/db/alembic/versions/ebd75b9feaa7_add_upgrades.py
deleted file mode 100644
index 1ccbd66..0000000
--- a/src/wuttjamaican/db/alembic/versions/ebd75b9feaa7_add_upgrades.py
+++ /dev/null
@@ -1,46 +0,0 @@
-"""add upgrades
-
-Revision ID: ebd75b9feaa7
-Revises: 3abcc44f7f91
-Create Date: 2024-08-24 09:42:21.199679
-
-"""
-from typing import Sequence, Union
-
-from alembic import op
-import sqlalchemy as sa
-from sqlalchemy.dialects import postgresql
-
-# revision identifiers, used by Alembic.
-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
-
-
-def upgrade() -> None:
-
- # upgrade
- sa.Enum('PENDING', 'EXECUTING', 'SUCCESS', 'FAILURE', name='upgradestatus').create(op.get_bind())
- op.create_table('upgrade',
- sa.Column('uuid', sa.String(length=32), nullable=False),
- sa.Column('created', sa.DateTime(timezone=True), nullable=False),
- sa.Column('created_by_uuid', sa.String(length=32), 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', sa.String(length=32), 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())
diff --git a/src/wuttjamaican/db/model/__init__.py b/src/wuttjamaican/db/model/__init__.py
index 267738c..760e3a6 100644
--- a/src/wuttjamaican/db/model/__init__.py
+++ b/src/wuttjamaican/db/model/__init__.py
@@ -36,9 +36,7 @@ The ``wuttjamaican.db.model`` namespace contains the following:
* :class:`~wuttjamaican.db.model.auth.Permission`
* :class:`~wuttjamaican.db.model.auth.User`
* :class:`~wuttjamaican.db.model.auth.UserRole`
-* :class:`~wuttjamaican.db.model.upgrades.Upgrade`
"""
from .base import uuid_column, uuid_fk_column, Base, Setting, Person
from .auth import Role, Permission, User, UserRole
-from .upgrades import Upgrade
diff --git a/src/wuttjamaican/db/model/upgrades.py b/src/wuttjamaican/db/model/upgrades.py
deleted file mode 100644
index c8f3666..0000000
--- a/src/wuttjamaican/db/model/upgrades.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# -*- coding: utf-8; -*-
-################################################################################
-#
-# WuttJamaican -- Base package for Wutta Framework
-# Copyright © 2023-2024 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
hello from foo html template
diff --git a/tests/email/templates/test_foo.txt.mako b/tests/email/templates/test_foo.txt.mako deleted file mode 100644 index dcbc4c6..0000000 --- a/tests/email/templates/test_foo.txt.mako +++ /dev/null @@ -1 +0,0 @@ -hello from foo txt template diff --git a/tests/email/test_handler.py b/tests/email/test_handler.py deleted file mode 100644 index 63c4874..0000000 --- a/tests/email/test_handler.py +++ /dev/null @@ -1,403 +0,0 @@ -# -*- coding: utf-8; -*- - -from unittest import TestCase -from unittest.mock import patch, MagicMock - -from wuttjamaican.email import handler as mod -from wuttjamaican.email import Message -from wuttjamaican.conf import WuttaConfig -from wuttjamaican.util import resource_path -from wuttjamaican.exc import ConfigurationError - - -class TestEmailHandler(TestCase): - - def setUp(self): - self.config = WuttaConfig() - self.app = self.config.get_app() - - def make_handler(self, **kwargs): - return mod.EmailHandler(self.config, **kwargs) - - def test_constructor_lookups(self): - - # empty lookup paths by default, if no providers - 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']), - } - with patch.object(self.app, 'providers', new=providers): - handler = self.make_handler() - 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'), - } - with patch.object(self.app, 'providers', new=providers): - handler = self.make_handler() - path = resource_path('wuttjamaican.email:templates') - self.assertEqual(handler.txt_templates.directories, [path]) - self.assertEqual(handler.html_templates.directories, [path]) - - def test_make_message(self): - handler = self.make_handler() - msg = handler.make_message() - self.assertIsInstance(msg, Message) - - def test_make_auto_message(self): - handler = self.make_handler() - - # error if default sender not defined - self.assertRaises(ConfigurationError, handler.make_auto_message, 'foo') - - # so let's define that - self.config.setdefault('wutta.email.default.sender', 'bob@example.com') - - # message is empty by default - msg = handler.make_auto_message('foo') - self.assertIsInstance(msg, Message) - self.assertEqual(msg.key, 'foo') - self.assertEqual(msg.sender, 'bob@example.com') - self.assertEqual(msg.subject, "Automated message") - self.assertEqual(msg.to, []) - self.assertEqual(msg.cc, []) - self.assertEqual(msg.bcc, []) - self.assertIsNone(msg.replyto) - self.assertIsNone(msg.txt_body) - self.assertIsNone(msg.html_body) - - # 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') - handler = self.make_handler() - 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.cc, []) - self.assertEqual(msg.bcc, []) - self.assertIsNone(msg.replyto) - self.assertEqual(msg.txt_body, "hello from foo txt template\n") - self.assertEqual(msg.html_body, "hello from foo html template
\n") - - # *some* auto methods get skipped if caller specifies the - # 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) - get_auto_sender.assert_not_called() - 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) - get_auto_subject.assert_not_called() - msg = handler.make_auto_message('foo') - get_auto_subject.assert_called_once_with('foo', {}) - - # to - 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') - - # cc - 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') - - # bcc - 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') - - # txt_body - 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', {}) - - # 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', {}) - - def test_get_auto_sender(self): - handler = self.make_handler() - - # error if none configured - self.assertRaises(ConfigurationError, handler.get_auto_sender, 'foo') - - # can set global default - 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') - - def test_get_auto_subject_template(self): - handler = self.make_handler() - - # global default - 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.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.assertEqual(template, "Foo Message") - - def test_get_auto_subject(self): - handler = self.make_handler() - - # global default - 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.assertEqual(subject, "Wutta Message") - - # can configure just for key - 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.assertEqual(subject, "FOO Message") - - # unless we ask it not to - 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') - - # 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']) - - # 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']) - - def test_get_auto_body_template(self): - from mako.template import Template - - handler = self.make_handler() - - # error if bad request - self.assertRaises(ValueError, handler.get_auto_body_template, 'foo', 'BADTYPE') - - # empty by default - 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']), - } - with patch.object(self.app, 'providers', new=providers): - handler = self.make_handler() - 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() - - # empty by default - 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']), - } - 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') - - def test_get_auto_html_body(self): - handler = self.make_handler() - - # empty by default - 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']), - } - 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') - - 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'): - - # no smtp session since sending email is disabled by default - with patch.object(mod, 'smtplib') as smtplib: - session = MagicMock() - smtplib.SMTP.return_value = session - handler.deliver_message(msg) - smtplib.SMTP.assert_not_called() - session.login.assert_not_called() - session.sendmail.assert_not_called() - - # now let's enable sending - self.config.setdefault('wutta.mail.send_emails', 'true') - - # smtp login not attempted by default - 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_not_called() - 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: - 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') - - # error if no sender - 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') - 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: - 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') - - # 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: - 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') - - # 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: - 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') - - # 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: - 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') - - # 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: - 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') - - def test_sending_is_enabled(self): - handler = self.make_handler() - - # off by default - self.assertFalse(handler.sending_is_enabled()) - - # but can be turned on - self.config.setdefault('wutta.mail.send_emails', 'true') - self.assertTrue(handler.sending_is_enabled()) - - def test_send_email(self): - with patch.object(mod.EmailHandler, 'deliver_message') as deliver_message: - handler = self.make_handler() - - # deliver_message() is called - handler.send_email('foo', sender='bob@example.com', to='sally@example.com', - txt_body='hello world') - deliver_message.assert_called_once() - - # make_auto_message() called only if needed - with patch.object(handler, 'make_auto_message') as make_auto_message: - - msg = handler.make_message() - handler.send_email(message=msg) - make_auto_message.assert_not_called() - - handler.send_email('foo', sender='bob@example.com', to='sally@example.com', - txt_body='hello world') - make_auto_message.assert_called_once_with('foo', {}, - sender='bob@example.com', - to='sally@example.com', - txt_body='hello world') diff --git a/tests/email/test_message.py b/tests/email/test_message.py deleted file mode 100644 index f8ff67a..0000000 --- a/tests/email/test_message.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8; -*- - -from unittest import TestCase - -from wuttjamaican.email import message as mod - - -class TestMessage(TestCase): - - def make_message(self, **kwargs): - return mod.Message(**kwargs) - - def test_set_recips(self): - msg = self.make_message() - self.assertEqual(msg.to, []) - - # set as list - msg.set_recips('to', ['sally@example.com']) - self.assertEqual(msg.to, ['sally@example.com']) - - # set as tuple - msg.set_recips('to', ('barney@example.com',)) - self.assertEqual(msg.to, ['barney@example.com']) - - # set as string - msg.set_recips('to', 'wilma@example.com') - self.assertEqual(msg.to, ['wilma@example.com']) - - # set as null - msg.set_recips('to', None) - self.assertEqual(msg.to, []) - - # otherwise error - self.assertRaises(ValueError, msg.set_recips, 'to', {'foo': 'foo@example.com'}) - - def test_as_string(self): - - # error if no body - msg = self.make_message() - self.assertRaises(ValueError, msg.as_string) - - # txt body - msg = self.make_message(sender='bob@example.com', - txt_body="hello world") - complete = msg.as_string() - self.assertIn('From: bob@example.com', complete) - - # html body - msg = self.make_message(sender='bob@example.com', - html_body="hello world
") - complete = msg.as_string() - 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
") - complete = msg.as_string() - self.assertIn('From: bob@example.com', complete) - - # 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
") - 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) diff --git a/tests/test_app.py b/tests/test_app.py index ef4f254..35ec466 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -10,18 +10,14 @@ from unittest.mock import patch, MagicMock import pytest -import wuttjamaican.enum from wuttjamaican import app -from wuttjamaican.progress import ProgressBase from wuttjamaican.conf import WuttaConfig from wuttjamaican.util import UNSPECIFIED -from wuttjamaican.testing import FileConfigTestCase -class TestAppHandler(FileConfigTestCase): +class TestAppHandler(TestCase): def setUp(self): - self.setup_files() self.config = WuttaConfig(appname='wuttatest') self.app = app.AppHandler(self.config) self.config.app = self.app @@ -31,9 +27,6 @@ class TestAppHandler(FileConfigTestCase): self.assertEqual(self.app.handlers, {}) self.assertEqual(self.app.appname, 'wuttatest') - def test_get_enum(self): - self.assertIs(self.app.get_enum(), wuttjamaican.enum) - def test_load_object(self): # just confirm the method works on a basic level; the @@ -41,40 +34,6 @@ class TestAppHandler(FileConfigTestCase): obj = self.app.load_object('wuttjamaican.util:UNSPECIFIED') self.assertIs(obj, UNSPECIFIED) - def test_get_appdir(self): - - mockdir = self.mkdir('mockdir') - - # default appdir - 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')) - - # 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) - - # configured appdir - 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')) - - # 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']) - def test_make_appdir(self): # appdir is created, and 3 subfolders added by default @@ -352,19 +311,6 @@ class TestAppHandler(FileConfigTestCase): uuid = self.app.make_uuid() self.assertEqual(len(uuid), 32) - def test_progress_loop(self): - - def act(obj, i): - pass - - # with progress - self.app.progress_loop(act, [1, 2, 3], ProgressBase, - message="whatever") - - # without progress - self.app.progress_loop(act, [1, 2, 3], None, - message="whatever") - def test_get_session(self): try: import sqlalchemy as sa @@ -397,25 +343,12 @@ class TestAppHandler(FileConfigTestCase): auth = self.app.get_auth_handler() self.assertIsInstance(auth, AuthHandler) - def test_get_email_handler(self): - from wuttjamaican.email import EmailHandler - - mail = self.app.get_email_handler() - self.assertIsInstance(mail, EmailHandler) - def test_get_people_handler(self): from wuttjamaican.people import PeopleHandler people = self.app.get_people_handler() self.assertIsInstance(people, PeopleHandler) - def test_get_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') - class TestAppProvider(TestCase): @@ -470,12 +403,6 @@ class TestAppProvider(TestCase): def test_getattr(self): - # enum - self.assertNotIn('enum', self.app.__dict__) - self.assertIs(self.app.enum, wuttjamaican.enum) - - # now we test that providers are loaded... - class FakeProvider(app.AppProvider): def fake_foo(self): return 42 @@ -490,16 +417,6 @@ class TestAppProvider(TestCase): self.assertIs(self.app.providers, fake_providers) get_all_providers.assert_called_once_with() - def test_getattr_model(self): - try: - import wuttjamaican.db.model - except ImportError: - pytest.skip("test not relevant without sqlalchemy") - - # model - 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 diff --git a/tests/test_progress.py b/tests/test_progress.py deleted file mode 100644 index 16a6787..0000000 --- a/tests/test_progress.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8; -*- - -from unittest import TestCase - -from wuttjamaican import progress as mod - - -class TestProgressBase(TestCase): - - def test_basic(self): - - # sanity / coverage check - prog = mod.ProgressBase('testing', 2) - prog.update(1) - prog.update(2) - prog.finish() - - -class TestConsoleProgress(TestCase): - - def test_basic(self): - - # sanity / coverage check - prog = mod.ConsoleProgress('testing', 2) - prog.update(1) - prog.update(2) - prog.finish() diff --git a/tests/test_util.py b/tests/test_util.py index 3d350cd..0f2baf4 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -7,7 +7,6 @@ from unittest.mock import patch, MagicMock import pytest from wuttjamaican import util as mod -from wuttjamaican.progress import ProgressBase class A: pass @@ -261,59 +260,3 @@ class TestMakeTitle(TestCase): def test_basic(self): text = mod.make_title('foo_bar') self.assertEqual(text, "Foo Bar") - - -class TestProgressLoop(TestCase): - - def test_basic(self): - - def act(obj, i): - pass - - # with progress - mod.progress_loop(act, [1, 2, 3], ProgressBase, - message="whatever") - - # without progress - mod.progress_loop(act, [1, 2, 3], None, - message="whatever") - - -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')) - - # absolute path returned as-is - self.assertEqual(mod.resource_path('/tmp/doesnotexist.txt'), '/tmp/doesnotexist.txt') - - def test_basic_pre_python_3_9(self): - - # the goal here is to get coverage for code which would only - # run on python 3.8 and older, but we only need that coverage - # if we are currently testing python 3.9+ - if sys.version_info.major == 3 and sys.version_info.minor < 9: - pytest.skip("this test is not relevant before python 3.9") - - from importlib.resources import files, as_file - - orig_import = __import__ - - def mock_import(name, globals=None, locals=None, fromlist=(), level=0): - if name == 'importlib.resources': - raise ImportError - 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): - - # package spec is resolved to path - 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')