From e75f4c5ad91962e272d65daa770e00f70f931ecb Mon Sep 17 00:00:00 2001 From: Ben Nemec Date: Tue, 10 Oct 2017 20:40:34 +0000 Subject: [PATCH] Make from_dict extensible Some of our consumers define additional members on the context class that they want included in to_dict and from_dict. While it is possible to do this today via overrides of those functions, the from_dict implementation in particular is a little non-obvious. This has led to bugs when the base class's to_dict behavior changes. This change moves the logic around extending the keys recognized by from_dict into from_dict itself and allows consumers to simply provide a list of those keys by overriding a class member. Change-Id: Ib143f8a5c129dbf6711800c4d87c8830a8aa3365 Related-Bug: 1721432 --- oslo_context/context.py | 5 +++++ oslo_context/tests/test_context.py | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/oslo_context/context.py b/oslo_context/context.py index 06f981e..24ded43 100644 --- a/oslo_context/context.py +++ b/oslo_context/context.py @@ -180,6 +180,9 @@ class RequestContext(object): """ user_idt_format = u'{user} {tenant} {domain} {user_domain} {p_domain}' + # Can be overridden in subclasses to specify extra keys that should be + # read when constructing a context using from_dict. + FROM_DICT_EXTRA_KEYS = [] @_renamed_kwarg('user', 'user_id') @_renamed_kwarg('tenant', 'project_id') @@ -391,6 +394,8 @@ class RequestContext(object): values.get('project_domain_name')) kwargs.setdefault('is_admin_project', values.get('is_admin_project', True)) + for key in cls.FROM_DICT_EXTRA_KEYS: + kwargs.setdefault(key, values.get(key)) return cls(**kwargs) @classmethod diff --git a/oslo_context/tests/test_context.py b/oslo_context/tests/test_context.py index 8595975..f81f4f0 100644 --- a/oslo_context/tests/test_context.py +++ b/oslo_context/tests/test_context.py @@ -54,6 +54,24 @@ class Object(object): pass +class TestContext(context.RequestContext): + """A test context with additional members + + This is representative of how at least some of our consumers use the + RequestContext class in their projects. + """ + FROM_DICT_EXTRA_KEYS = ['foo'] + + def __init__(self, foo=None, **kwargs): + super(TestContext, self).__init__(**kwargs) + self.foo = foo + + def to_dict(self): + d = super(TestContext, self).to_dict() + d['foo'] = self.foo + return d + + class ContextTest(test_base.BaseTestCase): def setUp(self): @@ -182,6 +200,13 @@ class ContextTest(test_base.BaseTestCase): self.assertFalse(ctx.is_admin) self.assertTrue(ctx.read_only) + def test_from_dict_extended(self): + initial = TestContext(foo='bar') + dct = initial.to_dict() + final = TestContext.from_dict(dct) + self.assertEqual('bar', final.foo) + self.assertEqual(dct, final.to_dict()) + def test_is_user_context(self): self.assertFalse(context.is_user_context(None)) ctx = context.RequestContext(is_admin=True)