feat: add warnings mode for import/export handlers, commands
can now specify `--warn` for import/export CLI, to get diff email when changes occur. this also adds `get_import_handler()` and friends, via app provider. also declare email settings for the 2 existing importers
This commit is contained in:
parent
1e7722de91
commit
19574ea4a0
18 changed files with 1150 additions and 26 deletions
|
|
@ -717,8 +717,9 @@ class TestFromSqlalchemy(DataTestCase):
|
|||
)
|
||||
query = imp.get_source_query()
|
||||
self.assertIsInstance(query, orm.Query)
|
||||
self.assertEqual(len(query.selectable.froms), 1)
|
||||
table = query.selectable.froms[0]
|
||||
froms = query.selectable.get_final_froms()
|
||||
self.assertEqual(len(froms), 1)
|
||||
table = froms[0]
|
||||
self.assertEqual(table.name, "upgrade")
|
||||
|
||||
def test_get_source_objects(self):
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@
|
|||
|
||||
from collections import OrderedDict
|
||||
from unittest.mock import patch
|
||||
from uuid import UUID
|
||||
|
||||
from wuttjamaican.testing import DataTestCase
|
||||
|
||||
from wuttasync.importing import handlers as mod, Importer, ToSqlalchemy
|
||||
|
||||
|
||||
class FromFooToBar(mod.ImportHandler):
|
||||
source_key = "foo"
|
||||
target_key = "bar"
|
||||
|
||||
|
||||
class TestImportHandler(DataTestCase):
|
||||
|
||||
def make_handler(self, **kwargs):
|
||||
|
|
@ -30,10 +36,10 @@ class TestImportHandler(DataTestCase):
|
|||
|
||||
def test_get_key(self):
|
||||
handler = self.make_handler()
|
||||
self.assertEqual(handler.get_key(), "to_None.from_None.import")
|
||||
self.assertEqual(handler.get_key(), "import.to_None.from_None")
|
||||
|
||||
with patch.multiple(mod.ImportHandler, source_key="csv", target_key="wutta"):
|
||||
self.assertEqual(handler.get_key(), "to_wutta.from_csv.import")
|
||||
self.assertEqual(handler.get_key(), "import.to_wutta.from_csv")
|
||||
|
||||
def test_get_spec(self):
|
||||
handler = self.make_handler()
|
||||
|
|
@ -149,15 +155,41 @@ class TestImportHandler(DataTestCase):
|
|||
kw = {}
|
||||
result = handler.consume_kwargs(kw)
|
||||
self.assertIs(result, kw)
|
||||
self.assertEqual(result, {})
|
||||
|
||||
# captures dry-run flag
|
||||
# dry_run (not consumed)
|
||||
self.assertFalse(handler.dry_run)
|
||||
kw["dry_run"] = True
|
||||
result = handler.consume_kwargs(kw)
|
||||
self.assertIs(result, kw)
|
||||
self.assertIn("dry_run", kw)
|
||||
self.assertTrue(kw["dry_run"])
|
||||
self.assertTrue(handler.dry_run)
|
||||
|
||||
# warnings (consumed)
|
||||
self.assertFalse(handler.warnings)
|
||||
kw["warnings"] = True
|
||||
result = handler.consume_kwargs(kw)
|
||||
self.assertIs(result, kw)
|
||||
self.assertNotIn("warnings", kw)
|
||||
self.assertTrue(handler.warnings)
|
||||
|
||||
# warnings_recipients (consumed)
|
||||
self.assertIsNone(handler.warnings_recipients)
|
||||
kw["warnings_recipients"] = "bob@example.com"
|
||||
result = handler.consume_kwargs(kw)
|
||||
self.assertIs(result, kw)
|
||||
self.assertNotIn("warnings_recipients", kw)
|
||||
self.assertEqual(handler.warnings_recipients, ["bob@example.com"])
|
||||
|
||||
# warnings_max_diffs (consumed)
|
||||
self.assertEqual(handler.warnings_max_diffs, 15)
|
||||
kw["warnings_max_diffs"] = 30
|
||||
result = handler.consume_kwargs(kw)
|
||||
self.assertIs(result, kw)
|
||||
self.assertNotIn("warnings_max_diffs", kw)
|
||||
self.assertEqual(handler.warnings_max_diffs, 30)
|
||||
|
||||
def test_define_importers(self):
|
||||
handler = self.make_handler()
|
||||
importers = handler.define_importers()
|
||||
|
|
@ -187,6 +219,94 @@ class TestImportHandler(DataTestCase):
|
|||
KeyError, handler.get_importer, "BunchOfNonsense", model_class=model.Setting
|
||||
)
|
||||
|
||||
def test_get_warnings_email_key(self):
|
||||
handler = FromFooToBar(self.config)
|
||||
|
||||
# default
|
||||
key = handler.get_warnings_email_key()
|
||||
self.assertEqual(key, "import_to_bar_from_foo_warning")
|
||||
|
||||
# override
|
||||
handler.warnings_email_key = "from_foo_to_bar"
|
||||
key = handler.get_warnings_email_key()
|
||||
self.assertEqual(key, "from_foo_to_bar")
|
||||
|
||||
def test_process_changes(self):
|
||||
model = self.app.model
|
||||
handler = self.make_handler()
|
||||
email_handler = self.app.get_email_handler()
|
||||
|
||||
handler.process_started = self.app.localtime()
|
||||
|
||||
alice = model.User(username="alice")
|
||||
bob = model.User(username="bob")
|
||||
charlie = model.User(username="charlie")
|
||||
|
||||
changes = {
|
||||
"User": (
|
||||
[
|
||||
(
|
||||
alice,
|
||||
{
|
||||
"uuid": UUID("06946d64-1ebf-79db-8000-ce40345044fe"),
|
||||
"username": "alice",
|
||||
},
|
||||
),
|
||||
],
|
||||
[
|
||||
(
|
||||
bob,
|
||||
{
|
||||
"uuid": UUID("06946d64-1ebf-7a8c-8000-05d78792b084"),
|
||||
"username": "bob",
|
||||
},
|
||||
{
|
||||
"uuid": UUID("06946d64-1ebf-7a8c-8000-05d78792b084"),
|
||||
"username": "bobbie",
|
||||
},
|
||||
),
|
||||
],
|
||||
[
|
||||
(
|
||||
charlie,
|
||||
{
|
||||
"uuid": UUID("06946d64-1ebf-7ad4-8000-1ba52f720c48"),
|
||||
"username": "charlie",
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
}
|
||||
|
||||
# no email if not in warnings mode
|
||||
self.assertFalse(handler.warnings)
|
||||
with patch.object(self.app, "send_email") as send_email:
|
||||
handler.process_changes(changes)
|
||||
send_email.assert_not_called()
|
||||
|
||||
# email sent (to default recip) if in warnings mode
|
||||
handler.warnings = True
|
||||
self.config.setdefault("wutta.email.default.to", "admin@example.com")
|
||||
with patch.object(email_handler, "deliver_message") as deliver_message:
|
||||
handler.process_changes(changes)
|
||||
deliver_message.assert_called_once()
|
||||
args, kwargs = deliver_message.call_args
|
||||
self.assertEqual(kwargs, {"recips": None})
|
||||
self.assertEqual(len(args), 1)
|
||||
msg = args[0]
|
||||
self.assertEqual(msg.to, ["admin@example.com"])
|
||||
|
||||
# can override email recip
|
||||
handler.warnings_recipients = ["bob@example.com"]
|
||||
with patch.object(email_handler, "deliver_message") as deliver_message:
|
||||
handler.process_changes(changes)
|
||||
deliver_message.assert_called_once()
|
||||
args, kwargs = deliver_message.call_args
|
||||
self.assertEqual(kwargs, {"recips": None})
|
||||
self.assertEqual(len(args), 1)
|
||||
msg = args[0]
|
||||
self.assertEqual(msg.to, ["bob@example.com"])
|
||||
|
||||
|
||||
class TestFromFileHandler(DataTestCase):
|
||||
|
||||
|
|
|
|||
|
|
@ -132,8 +132,8 @@ class TestFromWuttaToVersionBase(VersionTestCase):
|
|||
# version object should be embedded in data dict
|
||||
data = imp.normalize_target_object(version)
|
||||
self.assertIsInstance(data, dict)
|
||||
self.assertIn("_version", data)
|
||||
self.assertIs(data["_version"], version)
|
||||
self.assertIn("_objref", data)
|
||||
self.assertIs(data["_objref"], version)
|
||||
|
||||
# but normal object is not embedded
|
||||
data = imp.normalize_target_object(user)
|
||||
|
|
|
|||
126
tests/test_app.py
Normal file
126
tests/test_app.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from wuttjamaican.testing import ConfigTestCase
|
||||
|
||||
from wuttasync import app as mod
|
||||
from wuttasync.importing import ImportHandler
|
||||
from wuttasync.importing.csv import FromCsvToWutta
|
||||
|
||||
|
||||
class FromFooToBar(ImportHandler):
|
||||
source_key = "foo"
|
||||
target_key = "bar"
|
||||
|
||||
|
||||
class FromCsvToPoser(FromCsvToWutta):
|
||||
pass
|
||||
|
||||
|
||||
class TestWuttaSyncAppProvider(ConfigTestCase):
|
||||
|
||||
def test_get_all_import_handlers(self):
|
||||
|
||||
# by default our custom handler is not found
|
||||
handlers = self.app.get_all_import_handlers()
|
||||
self.assertIn(FromCsvToWutta, handlers)
|
||||
self.assertNotIn(FromFooToBar, handlers)
|
||||
|
||||
# make sure if we configure a custom handler, it is found
|
||||
self.config.setdefault(
|
||||
"wuttasync.importing.import.to_wutta.from_csv.handler",
|
||||
"tests.test_app:FromFooToBar",
|
||||
)
|
||||
handlers = self.app.get_all_import_handlers()
|
||||
self.assertIn(FromCsvToWutta, handlers)
|
||||
self.assertIn(FromFooToBar, handlers)
|
||||
|
||||
def test_get_designated_import_handler_spec(self):
|
||||
|
||||
# fetch of unknown key returns none
|
||||
spec = self.app.get_designated_import_handler_spec("test01")
|
||||
self.assertIsNone(spec)
|
||||
|
||||
# unless we require it, in which case, error
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
self.app.get_designated_import_handler_spec,
|
||||
"test01",
|
||||
require=True,
|
||||
)
|
||||
|
||||
# we configure one for whatever key we like
|
||||
self.config.setdefault(
|
||||
"wuttasync.importing.test02.handler", "tests.test_app:FromBarToFoo"
|
||||
)
|
||||
spec = self.app.get_designated_import_handler_spec("test02")
|
||||
self.assertEqual(spec, "tests.test_app:FromBarToFoo")
|
||||
|
||||
# we can also define a "default" designated handler
|
||||
self.config.setdefault(
|
||||
"wuttasync.importing.test03.default_handler",
|
||||
"tests.test_app:FromBarToFoo",
|
||||
)
|
||||
spec = self.app.get_designated_import_handler_spec("test03")
|
||||
self.assertEqual(spec, "tests.test_app:FromBarToFoo")
|
||||
|
||||
def test_get_designated_import_handlers(self):
|
||||
|
||||
# some designated handlers exist, but not our custom handler
|
||||
handlers = self.app.get_designated_import_handlers()
|
||||
csv_handlers = [
|
||||
h for h in handlers if h.get_key() == "import.to_wutta.from_csv"
|
||||
]
|
||||
self.assertEqual(len(csv_handlers), 1)
|
||||
csv_handler = csv_handlers[0]
|
||||
self.assertIsInstance(csv_handler, FromCsvToWutta)
|
||||
self.assertFalse(isinstance(csv_handler, FromCsvToPoser))
|
||||
self.assertFalse(
|
||||
any([h.get_key() == "import.to_bar.from_foo" for h in handlers])
|
||||
)
|
||||
self.assertFalse(any([isinstance(h, FromFooToBar) for h in handlers]))
|
||||
self.assertFalse(any([isinstance(h, FromCsvToPoser) for h in handlers]))
|
||||
self.assertTrue(
|
||||
any([h.get_key() == "import.to_versions.from_wutta" for h in handlers])
|
||||
)
|
||||
|
||||
# but we can make custom designated
|
||||
self.config.setdefault(
|
||||
"wuttasync.importing.import.to_wutta.from_csv.handler",
|
||||
"tests.test_app:FromCsvToPoser",
|
||||
)
|
||||
handlers = self.app.get_designated_import_handlers()
|
||||
csv_handlers = [
|
||||
h for h in handlers if h.get_key() == "import.to_wutta.from_csv"
|
||||
]
|
||||
self.assertEqual(len(csv_handlers), 1)
|
||||
csv_handler = csv_handlers[0]
|
||||
self.assertIsInstance(csv_handler, FromCsvToWutta)
|
||||
self.assertIsInstance(csv_handler, FromCsvToPoser)
|
||||
self.assertTrue(
|
||||
any([h.get_key() == "import.to_versions.from_wutta" for h in handlers])
|
||||
)
|
||||
|
||||
def test_get_import_handler(self):
|
||||
|
||||
# make sure a basic fetch works
|
||||
handler = self.app.get_import_handler("import.to_wutta.from_csv")
|
||||
self.assertIsInstance(handler, FromCsvToWutta)
|
||||
self.assertFalse(isinstance(handler, FromCsvToPoser))
|
||||
|
||||
# and make sure custom override works
|
||||
self.config.setdefault(
|
||||
"wuttasync.importing.import.to_wutta.from_csv.handler",
|
||||
"tests.test_app:FromCsvToPoser",
|
||||
)
|
||||
handler = self.app.get_import_handler("import.to_wutta.from_csv")
|
||||
self.assertIsInstance(handler, FromCsvToWutta)
|
||||
self.assertIsInstance(handler, FromCsvToPoser)
|
||||
|
||||
# unknown importer cannot be found
|
||||
handler = self.app.get_import_handler("bogus")
|
||||
self.assertIsNone(handler)
|
||||
|
||||
# and if we require it, error will raise
|
||||
self.assertRaises(
|
||||
ValueError, self.app.get_import_handler, "bogus", require=True
|
||||
)
|
||||
81
tests/test_emails.py
Normal file
81
tests/test_emails.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from wuttjamaican.testing import ConfigTestCase
|
||||
|
||||
from wuttasync import emails as mod
|
||||
from wuttasync.importing import ImportHandler
|
||||
from wuttasync.testing import ImportExportWarningTestCase
|
||||
|
||||
|
||||
class FromFooToWutta(ImportHandler):
|
||||
pass
|
||||
|
||||
|
||||
class TestImportExportWarning(ConfigTestCase):
|
||||
|
||||
def make_setting(self, factory=None):
|
||||
if not factory:
|
||||
factory = mod.ImportExportWarning
|
||||
setting = factory(self.config)
|
||||
return setting
|
||||
|
||||
def test_get_description(self):
|
||||
self.config.setdefault("wutta.app_title", "Wutta Poser")
|
||||
setting = self.make_setting()
|
||||
setting.import_handler_key = "import.to_wutta.from_csv"
|
||||
self.assertEqual(
|
||||
setting.get_description(),
|
||||
"Diff warning email for importing CSV → Wutta Poser",
|
||||
)
|
||||
|
||||
def test_get_default_subject(self):
|
||||
self.config.setdefault("wutta.app_title", "Wutta Poser")
|
||||
setting = self.make_setting()
|
||||
setting.import_handler_key = "import.to_wutta.from_csv"
|
||||
self.assertEqual(setting.get_default_subject(), "Changes for CSV → Wutta Poser")
|
||||
|
||||
def test_get_import_handler(self):
|
||||
|
||||
# nb. typical name pattern
|
||||
class import_to_wutta_from_foo_warning(mod.ImportExportWarning):
|
||||
pass
|
||||
|
||||
# nb. name does not match spec pattern
|
||||
class import_to_wutta_from_bar_blah(mod.ImportExportWarning):
|
||||
pass
|
||||
|
||||
# register our import handler
|
||||
self.config.setdefault(
|
||||
"wuttasync.importing.import.to_wutta.from_foo.handler",
|
||||
"tests.test_emails:FromFooToWutta",
|
||||
)
|
||||
|
||||
# error if spec/key not discoverable
|
||||
setting = self.make_setting(import_to_wutta_from_bar_blah)
|
||||
self.assertRaises(ValueError, setting.get_import_handler)
|
||||
|
||||
# can lookup by name (auto-spec)
|
||||
setting = self.make_setting(import_to_wutta_from_foo_warning)
|
||||
handler = setting.get_import_handler()
|
||||
self.assertIsInstance(handler, FromFooToWutta)
|
||||
|
||||
# can lookup by explicit spec
|
||||
setting = self.make_setting(import_to_wutta_from_bar_blah)
|
||||
setting.import_handler_spec = "tests.test_emails:FromFooToWutta"
|
||||
handler = setting.get_import_handler()
|
||||
self.assertIsInstance(handler, FromFooToWutta)
|
||||
|
||||
# can lookup by explicit key
|
||||
setting = self.make_setting(import_to_wutta_from_bar_blah)
|
||||
setting.import_handler_key = "import.to_wutta.from_foo"
|
||||
handler = setting.get_import_handler()
|
||||
self.assertIsInstance(handler, FromFooToWutta)
|
||||
|
||||
|
||||
class TestEmailSettings(ImportExportWarningTestCase):
|
||||
|
||||
def test_import_to_versions_from_wutta_warning(self):
|
||||
self.do_test_preview("import_to_versions_from_wutta_warning")
|
||||
|
||||
def test_import_to_wutta_from_csv_warning(self):
|
||||
self.do_test_preview("import_to_wutta_from_csv_warning")
|
||||
Loading…
Add table
Add a link
Reference in a new issue