feat: add support for --runas CLI param, to set versioning authorship
only relevant if Wutta-Continuum is enabled
This commit is contained in:
parent
c6d1822f3b
commit
6ee008e169
8 changed files with 103 additions and 21 deletions
|
|
@ -102,7 +102,7 @@ class ImportCommandHandler(GenericHandler):
|
|||
elif key:
|
||||
self.import_handler = self.app.get_import_handler(key, require=True)
|
||||
|
||||
def run(self, params, progress=None): # pylint: disable=unused-argument
|
||||
def run(self, ctx, progress=None): # pylint: disable=unused-argument
|
||||
"""
|
||||
Run the import/export job(s) based on command line params.
|
||||
|
||||
|
|
@ -113,20 +113,27 @@ class ImportCommandHandler(GenericHandler):
|
|||
Unless ``--list-models`` was specified on the command line in
|
||||
which case we do :meth:`list_models()` instead.
|
||||
|
||||
:param params: Dict of params from command line. This must
|
||||
include a ``'models'`` key, the rest are optional.
|
||||
:param ctx: :class:`typer.Context` instance.
|
||||
|
||||
:param progress: Optional progress indicator factory.
|
||||
"""
|
||||
|
||||
# maybe just list models and bail
|
||||
if params.get("list_models"):
|
||||
self.list_models(params)
|
||||
if ctx.params.get("list_models"):
|
||||
self.list_models(ctx.params)
|
||||
return
|
||||
|
||||
# otherwise process some data
|
||||
# otherwise we'll process some data
|
||||
log.debug("using handler: %s", self.import_handler.get_spec())
|
||||
kw = dict(params)
|
||||
|
||||
# all params from caller will be passed along
|
||||
kw = dict(ctx.params)
|
||||
|
||||
# runas user also, but it comes from root/parent command
|
||||
if username := ctx.parent.params.get("runas_username"):
|
||||
kw["runas_username"] = username
|
||||
|
||||
# sort out which models to process
|
||||
models = kw.pop("models")
|
||||
if not models:
|
||||
models = list(self.import_handler.importers)
|
||||
|
|
@ -136,6 +143,8 @@ class ImportCommandHandler(GenericHandler):
|
|||
self.import_handler.get_title(),
|
||||
", ".join(models),
|
||||
)
|
||||
|
||||
# process data
|
||||
log.debug("params are: %s", kw)
|
||||
self.import_handler.process_data(*models, **kw)
|
||||
|
||||
|
|
|
|||
|
|
@ -39,4 +39,4 @@ def import_csv(ctx: typer.Context, **kwargs): # pylint: disable=unused-argument
|
|||
"""
|
||||
config = ctx.parent.wutta_config
|
||||
handler = ImportCommandHandler(config, key="import.to_wutta.from_csv")
|
||||
handler.run(ctx.params)
|
||||
handler.run(ctx)
|
||||
|
|
|
|||
|
|
@ -70,4 +70,4 @@ def import_versions( # pylint: disable=unused-argument
|
|||
sys.exit(1)
|
||||
|
||||
handler = ImportCommandHandler(config, key="import.to_versions.from_wutta")
|
||||
handler.run(ctx.params)
|
||||
handler.run(ctx)
|
||||
|
|
|
|||
|
|
@ -166,6 +166,12 @@ class ImportHandler(GenericHandler): # pylint: disable=too-many-public-methods
|
|||
See also :attr:`warnings`.
|
||||
"""
|
||||
|
||||
runas_username = None
|
||||
"""
|
||||
Username responsible for running the import/export job. This is
|
||||
mostly used for Continuum versioning.
|
||||
"""
|
||||
|
||||
importers = None
|
||||
"""
|
||||
This should be a dict of all importer/exporter classes available
|
||||
|
|
@ -416,6 +422,9 @@ class ImportHandler(GenericHandler): # pylint: disable=too-many-public-methods
|
|||
if "warnings_max_diffs" in kwargs:
|
||||
self.warnings_max_diffs = kwargs.pop("warnings_max_diffs")
|
||||
|
||||
if "runas_username" in kwargs:
|
||||
self.runas_username = kwargs.pop("runas_username")
|
||||
|
||||
return kwargs
|
||||
|
||||
def begin_transaction(self):
|
||||
|
|
@ -946,11 +955,29 @@ class ToWuttaHandler(ToSqlalchemyHandler):
|
|||
|
||||
def make_target_session(self):
|
||||
"""
|
||||
Call
|
||||
:meth:`~wuttjamaican:wuttjamaican.app.AppHandler.make_session()`
|
||||
and return the result.
|
||||
This creates a typical :term:`db session` for the app by
|
||||
calling
|
||||
:meth:`~wuttjamaican:wuttjamaican.app.AppHandler.make_session()`.
|
||||
|
||||
If :attr:`~ImportHandler.runas_username` is set, the
|
||||
responsible user (``continuum_user_id``) will be set for the
|
||||
new session as well. This info is only used if the
|
||||
Wutta-Continuum versioning feature is enabled.
|
||||
|
||||
:returns: :class:`~wuttjamaican:wuttjamaican.db.sess.Session`
|
||||
instance.
|
||||
"""
|
||||
return self.app.make_session()
|
||||
session = self.app.make_session()
|
||||
|
||||
if self.runas_username:
|
||||
model = self.app.model
|
||||
if user := (
|
||||
session.query(model.User)
|
||||
.filter_by(username=self.runas_username)
|
||||
.first()
|
||||
):
|
||||
session.info["continuum_user_id"] = user.uuid
|
||||
else:
|
||||
log.warning("runas username not found: %s", self.runas_username)
|
||||
|
||||
return session
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import inspect
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
from wuttasync.cli import base as mod
|
||||
from wuttjamaican.testing import DataTestCase
|
||||
|
|
@ -44,12 +44,20 @@ class TestImportCommandHandler(DataTestCase):
|
|||
)
|
||||
|
||||
with patch.object(handler, "list_models") as list_models:
|
||||
handler.run({"list_models": True})
|
||||
list_models.assert_called_once_with({"list_models": True})
|
||||
ctx = Mock(params={"list_models": True})
|
||||
handler.run(ctx)
|
||||
list_models.assert_called_once_with(ctx.params)
|
||||
|
||||
class Object:
|
||||
def __init__(self, **kw):
|
||||
self.__dict__.update(kw)
|
||||
|
||||
with patch.object(handler, "import_handler") as import_handler:
|
||||
handler.run({"models": []})
|
||||
import_handler.process_data.assert_called_once_with()
|
||||
parent = Mock(params={"runas_username": "fred"})
|
||||
# TODO: why can't we just use Mock here? the parent attr is problematic
|
||||
ctx = Object(params={"models": []}, parent=parent)
|
||||
handler.run(ctx)
|
||||
import_handler.process_data.assert_called_once_with(runas_username="fred")
|
||||
|
||||
def test_list_models(self):
|
||||
handler = self.make_handler(
|
||||
|
|
|
|||
|
|
@ -19,4 +19,4 @@ class TestImportCsv(TestCase):
|
|||
ctx = MagicMock(params=params)
|
||||
with patch.object(ImportCommandHandler, "run") as run:
|
||||
mod.import_csv(ctx)
|
||||
run.assert_called_once_with(params)
|
||||
run.assert_called_once_with(ctx)
|
||||
|
|
|
|||
|
|
@ -19,4 +19,4 @@ class TestImportCsv(TestCase):
|
|||
ctx = MagicMock(params=params)
|
||||
with patch.object(ImportCommandHandler, "run") as run:
|
||||
mod.import_versions(ctx)
|
||||
run.assert_called_once_with(params)
|
||||
run.assert_called_once_with(ctx)
|
||||
|
|
|
|||
|
|
@ -190,6 +190,14 @@ class TestImportHandler(DataTestCase):
|
|||
self.assertNotIn("warnings_max_diffs", kw)
|
||||
self.assertEqual(handler.warnings_max_diffs, 30)
|
||||
|
||||
# runas_username (consumed)
|
||||
self.assertIsNone(handler.runas_username)
|
||||
kw["runas_username"] = "fred"
|
||||
result = handler.consume_kwargs(kw)
|
||||
self.assertIs(result, kw)
|
||||
self.assertNotIn("runas_username", kw)
|
||||
self.assertEqual(handler.runas_username, "fred")
|
||||
|
||||
def test_define_importers(self):
|
||||
handler = self.make_handler()
|
||||
importers = handler.define_importers()
|
||||
|
|
@ -490,11 +498,41 @@ class TestToWuttaHandler(DataTestCase):
|
|||
self.assertEqual(handler.get_target_title(), "what_about_this")
|
||||
|
||||
def test_make_target_session(self):
|
||||
model = self.app.model
|
||||
handler = self.make_handler()
|
||||
|
||||
# makes "new" (mocked in our case) app session
|
||||
fred = model.User(username="fred")
|
||||
self.session.add(fred)
|
||||
self.session.commit()
|
||||
|
||||
# makes "new" (mocked in our case) app session, with no runas
|
||||
# username set by default
|
||||
with patch.object(self.app, "make_session") as make_session:
|
||||
make_session.return_value = self.session
|
||||
session = handler.make_target_session()
|
||||
make_session.assert_called_once_with()
|
||||
self.assertIs(session, self.session)
|
||||
self.assertNotIn("continuum_user_id", session.info)
|
||||
self.assertNotIn("continuum_user_id", self.session.info)
|
||||
|
||||
# runas user also should not be set, if username is not valid
|
||||
handler.runas_username = "freddie"
|
||||
with patch.object(self.app, "make_session") as make_session:
|
||||
make_session.return_value = self.session
|
||||
session = handler.make_target_session()
|
||||
make_session.assert_called_once_with()
|
||||
self.assertIs(session, self.session)
|
||||
self.assertNotIn("continuum_user_id", session.info)
|
||||
self.assertNotIn("continuum_user_id", self.session.info)
|
||||
|
||||
# this time we should have runas user properly set
|
||||
handler.runas_username = "fred"
|
||||
with patch.object(self.app, "make_session") as make_session:
|
||||
make_session.return_value = self.session
|
||||
session = handler.make_target_session()
|
||||
make_session.assert_called_once_with()
|
||||
self.assertIs(session, self.session)
|
||||
self.assertIn("continuum_user_id", session.info)
|
||||
self.assertEqual(session.info["continuum_user_id"], fred.uuid)
|
||||
self.assertIn("continuum_user_id", self.session.info)
|
||||
self.assertEqual(self.session.info["continuum_user_id"], fred.uuid)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue