From d7d0768a9cea42cea33437e4c2784ef7b76382b6 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 17 Mar 2026 18:24:42 -0500 Subject: [PATCH] feat: add concept of (non-)default importers for handler i.e. when telling handler to "import all" which should be included vs. excluded by default? --- src/wuttasync/cli/base.py | 30 ++++++++++++---- src/wuttasync/emails.py | 8 +++++ src/wuttasync/importing/handlers.py | 32 ++++++++++++++++- src/wuttasync/importing/wutta.py | 16 +++++++++ tests/cli/test_base.py | 54 ++++++++++++++++++++++++----- tests/importing/test_handlers.py | 20 +++++++++++ tests/test_emails.py | 3 ++ 7 files changed, 148 insertions(+), 15 deletions(-) diff --git a/src/wuttasync/cli/base.py b/src/wuttasync/cli/base.py index 09db3ea..188555e 100644 --- a/src/wuttasync/cli/base.py +++ b/src/wuttasync/cli/base.py @@ -177,7 +177,7 @@ class ImportCommandHandler(GenericHandler): # sort out which models to process models = kw.pop("models", None) if not models: - models = list(self.import_handler.importers) + models = self.import_handler.get_default_importer_keys() log.debug( "%s %s for models: %s", self.import_handler.actioning, @@ -196,13 +196,31 @@ class ImportCommandHandler(GenericHandler): This is what happens when command line has ``--list-models``. """ - sys.stdout.write("\nALL MODELS:\n") + all_keys = list(self.import_handler.importers) + default_keys = [k for k in all_keys if self.import_handler.is_default(k)] + extra_keys = [k for k in all_keys if k not in default_keys] + + sys.stdout.write("\n") sys.stdout.write("==============================\n") - for key in self.import_handler.importers: - sys.stdout.write(key) - sys.stdout.write("\n") + sys.stdout.write(" DEFAULT MODELS:\n") sys.stdout.write("==============================\n") - sys.stdout.write(f"for {self.import_handler.get_title()}\n\n") + if default_keys: + for key in default_keys: + sys.stdout.write(f"{key}\n") + else: + sys.stdout.write("(none)\n") + + sys.stdout.write("==============================\n") + sys.stdout.write(" EXTRA MODELS:\n") + sys.stdout.write("==============================\n") + if extra_keys: + for key in extra_keys: + sys.stdout.write(f"{key}\n") + else: + sys.stdout.write("(none)\n") + + sys.stdout.write("==============================\n") + sys.stdout.write(f" for {self.import_handler.get_title()}\n\n") def import_command_template( # pylint: disable=unused-argument,too-many-arguments,too-many-positional-arguments,too-many-locals diff --git a/src/wuttasync/emails.py b/src/wuttasync/emails.py index a23fd74..0c96f4f 100644 --- a/src/wuttasync/emails.py +++ b/src/wuttasync/emails.py @@ -150,6 +150,14 @@ class ImportExportWarning(EmailSetting): } +class export_to_wutta_from_wutta_warning( # pylint: disable=invalid-name + ImportExportWarning +): + """ + Diff warning for Wutta → Wutta export. + """ + + class import_to_versions_from_wutta_warning( # pylint: disable=invalid-name ImportExportWarning ): diff --git a/src/wuttasync/importing/handlers.py b/src/wuttasync/importing/handlers.py index 47b2fbc..2aa8ffe 100644 --- a/src/wuttasync/importing/handlers.py +++ b/src/wuttasync/importing/handlers.py @@ -371,7 +371,7 @@ class ImportHandler( # pylint: disable=too-many-public-methods,too-many-instanc changes = OrderedDict() if not keys: - keys = list(self.importers) + keys = self.get_default_importer_keys() success = False try: @@ -655,6 +655,36 @@ class ImportHandler( # pylint: disable=too-many-public-methods,too-many-instanc """ return kwargs + def is_default(self, key): + """ + Return a boolean indicating whether the importer corresponding + to ``key`` should be considered "default" - i.e. included as + part of a typical "import all" job. + + The default logic here returns ``True`` in all cases; subclass can + override as needed. + + :param key: Key indicating the importer. + + :rtype: bool + """ + return True + + def get_default_importer_keys(self): + """ + Return the list of importer keys which should be considered + "default" - i.e. which should be included as part of a typical + "import all" job. + + This inspects :attr:`importers` and calls :meth:`is_default()` + for each, to determine the result. + + :returns: List of importer keys (strings). + """ + keys = list(self.importers) + keys = [k for k in keys if self.is_default(k)] + return keys + def process_changes(self, changes): """ Run post-processing operations on the given changes, if diff --git a/src/wuttasync/importing/wutta.py b/src/wuttasync/importing/wutta.py index cb1be5d..10e6e6c 100644 --- a/src/wuttasync/importing/wutta.py +++ b/src/wuttasync/importing/wutta.py @@ -126,6 +126,22 @@ class FromWuttaToWuttaBase(FromWuttaHandler, ToWuttaHandler): }, ) + def is_default(self, key): + """ """ + special = [ + "Setting", + "Role", + "Permission", + "User", + "UserRole", + "UserAPIToken", + "Upgrade", + ] + if key in special: + return False + + return True + class FromWuttaToWuttaImport(FromWuttaToWuttaBase): """ diff --git a/tests/cli/test_base.py b/tests/cli/test_base.py index 52f7124..d2b98e6 100644 --- a/tests/cli/test_base.py +++ b/tests/cli/test_base.py @@ -3,7 +3,7 @@ import inspect import sys from unittest import TestCase -from unittest.mock import patch, Mock +from unittest.mock import patch, Mock, call from wuttasync.cli import base as mod from wuttjamaican.testing import DataTestCase @@ -169,22 +169,60 @@ class TestImportCommandHandler(DataTestCase): params={}, ), ) - # self.assertRaises(FileNotFoundError, handler.run, ctx) handler.run(ctx) exit_.assert_not_called() def test_list_models(self): + + # CSV -> Wutta (all importers are default) handler = self.make_handler( import_handler="wuttasync.importing.csv:FromCsvToWutta" ) - with patch.object(mod, "sys") as sys: handler.list_models({}) - # just test a few random things we expect to see - self.assertTrue(sys.stdout.write.has_call("ALL MODELS:\n")) - self.assertTrue(sys.stdout.write.has_call("Person")) - self.assertTrue(sys.stdout.write.has_call("User")) - self.assertTrue(sys.stdout.write.has_call("Upgrade")) + sys.stdout.write.assert_has_calls( + [ + call("==============================\n"), + call(" EXTRA MODELS:\n"), + call("==============================\n"), + call("(none)\n"), + call("==============================\n"), + ] + ) + + # Wutta -> Wutta (only Person importer is default) + handler = self.make_handler( + import_handler="wuttasync.importing.wutta:FromWuttaToWuttaImport" + ) + with patch.object(mod, "sys") as sys: + handler.list_models({}) + sys.stdout.write.assert_has_calls( + [ + call("==============================\n"), + call(" DEFAULT MODELS:\n"), + call("==============================\n"), + call("Person\n"), + call("==============================\n"), + call(" EXTRA MODELS:\n"), + call("==============================\n"), + ] + ) + + # again, but pretend there are no default importers + with patch.object(handler.import_handler, "is_default", return_value=False): + with patch.object(mod, "sys") as sys: + handler.list_models({}) + sys.stdout.write.assert_has_calls( + [ + call("==============================\n"), + call(" DEFAULT MODELS:\n"), + call("==============================\n"), + call("(none)\n"), + call("==============================\n"), + call(" EXTRA MODELS:\n"), + call("==============================\n"), + ] + ) class TestImporterCommand(TestCase): diff --git a/tests/importing/test_handlers.py b/tests/importing/test_handlers.py index a81a933..ad8a9a3 100644 --- a/tests/importing/test_handlers.py +++ b/tests/importing/test_handlers.py @@ -7,6 +7,7 @@ from uuid import UUID from wuttjamaican.testing import DataTestCase from wuttasync.importing import handlers as mod, Importer, ToSqlalchemy +from wuttasync.importing.wutta import FromWuttaToWuttaImport class FromFooToBar(mod.ImportHandler): @@ -253,6 +254,25 @@ class TestImportHandler(DataTestCase): KeyError, handler.get_importer, "BunchOfNonsense", model_class=model.Setting ) + def test_is_default(self): + handler = self.make_handler() + # nb. anything is considered default, by default + self.assertTrue(handler.is_default("there_is_no_way_this_is_valid")) + + def test_get_default_importer_keys(self): + + # use handler which already has some non/default keys + handler = FromWuttaToWuttaImport(self.config) + + # it supports many importers + self.assertIn("Person", handler.importers) + self.assertIn("User", handler.importers) + self.assertIn("Setting", handler.importers) + + # but only Person is default + keys = handler.get_default_importer_keys() + self.assertEqual(keys, ["Person"]) + def test_get_warnings_email_key(self): handler = FromFooToBar(self.config) diff --git a/tests/test_emails.py b/tests/test_emails.py index fc927bf..bdaebed 100644 --- a/tests/test_emails.py +++ b/tests/test_emails.py @@ -85,6 +85,9 @@ class TestEmailSettings(ImportExportWarningTestCase): return config + def test_export_to_wutta_from_wutta_warning(self): + self.do_test_preview("export_to_wutta_from_wutta_warning") + def test_import_to_versions_from_wutta_warning(self): self.do_test_preview("import_to_versions_from_wutta_warning")