diff --git a/src/wuttjamaican/app.py b/src/wuttjamaican/app.py index d8483b4..6314f9d 100644 --- a/src/wuttjamaican/app.py +++ b/src/wuttjamaican/app.py @@ -38,6 +38,7 @@ from webhelpers2.html import HTML from wuttjamaican.util import ( get_timezone_by_name, + get_value, localtime, load_entry_points, load_object, @@ -374,6 +375,22 @@ class AppHandler: # pylint: disable=too-many-public-methods self.__dict__["enum"] = importlib.import_module(spec) return self.enum + def get_value(self, obj, key): + """ + Convenience wrapper around + :func:`wuttjamaican.util.get_value()`. + + :param obj: Arbitrary dict or object of any kind which would + have named attributes. + + :param key: Key/name of the field to get. + + :returns: Whatever value is found. Or maybe an + ``AttributeError`` is raised if the object does not have + the key/attr set. + """ + return get_value(obj, key) + def load_object(self, spec): """ Import and/or load and return the object designated by the diff --git a/src/wuttjamaican/db/util.py b/src/wuttjamaican/db/util.py index 27aef29..d01917d 100644 --- a/src/wuttjamaican/db/util.py +++ b/src/wuttjamaican/db/util.py @@ -56,7 +56,8 @@ class ModelBase: # pylint: disable=empty-docstring def __iter__(self): # nb. we override this to allow for `dict(self)` - # nb. this does *not* include association proxy values + # nb. this does *not* include association proxy values; + # see also wuttjamaican.util.get_value() state = sa.inspect(self) fields = [attr.key for attr in state.attrs] return iter([(field, getattr(self, field)) for field in fields]) diff --git a/src/wuttjamaican/util.py b/src/wuttjamaican/util.py index a129887..b1ef966 100644 --- a/src/wuttjamaican/util.py +++ b/src/wuttjamaican/util.py @@ -82,6 +82,34 @@ def get_class_hierarchy(klass, topfirst=True): return hierarchy +def get_value(obj, key): + """ + Convenience function to retrive a value by name from the given + object. This will first try to assume the object is a dict but + will fallback to using ``getattr()`` on it. + + :param obj: Arbitrary dict or object of any kind which would have + named attributes. + + :param key: Key/name of the field to get. + + :returns: Whatever value is found. Or maybe an ``AttributeError`` + is raised if the object does not have the key/attr set. + """ + # nb. we try dict access first, since wutta data model objects + # should all support that anyway, so it's 2 birds 1 stone. + try: + return obj[key] + + except (KeyError, TypeError): + # nb. key error means the object supports key lookup (i.e. is + # dict-like) but did not have that key set. which is actually + # an expected scenario for association proxy fields, but for + # those a getattr() should still work; see also + # wuttjamaican.db.util.ModelBase + return getattr(obj, key) + + def load_entry_points(group, ignore_errors=False): """ Load a set of ``setuptools``-style entry points. diff --git a/tests/test_app.py b/tests/test_app.py index 1070e28..78a8862 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -48,6 +48,32 @@ class TestAppHandler(FileTestCase): def test_get_enum(self): self.assertIs(self.app.get_enum(), wuttjamaican.enum) + def test_get_value(self): + + class Object: + def __init__(self, **kw): + self.__dict__.update(kw) + + class Dict(dict): + pass + + # dict object + obj = {"foo": "bar"} + self.assertEqual(self.app.get_value(obj, "foo"), "bar") + + # object w/ attrs + obj = Object(foo="bar") + self.assertEqual(self.app.get_value(obj, "foo"), "bar") + + # dict-like w/ attrs + obj = Dict({"foo": "bar"}) + obj.baz = "yyy" + self.assertEqual(self.app.get_value(obj, "baz"), "yyy") + + # missing attr + obj = Object(foo="bar") + self.assertRaises(AttributeError, self.app.get_value, obj, "baz") + def test_load_object(self): # just confirm the method works on a basic level; the diff --git a/tests/test_util.py b/tests/test_util.py index 31b0527..ddf220e 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -41,6 +41,35 @@ class TestGetClassHierarchy(TestCase): self.assertEqual(classes, [C, B, A]) +class TestGetValue(TestCase): + + def test_basic(self): + + class Object: + def __init__(self, **kw): + self.__dict__.update(kw) + + class Dict(dict): + pass + + # dict object + obj = {"foo": "bar"} + self.assertEqual(mod.get_value(obj, "foo"), "bar") + + # object w/ attrs + obj = Object(foo="bar") + self.assertEqual(mod.get_value(obj, "foo"), "bar") + + # dict-like w/ attrs + obj = Dict({"foo": "bar"}) + obj.baz = "yyy" + self.assertEqual(mod.get_value(obj, "baz"), "yyy") + + # missing attr + obj = Object(foo="bar") + self.assertRaises(AttributeError, mod.get_value, obj, "baz") + + class TestLoadEntryPoints(TestCase): def test_empty(self):