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
This commit is contained in:
Ben Nemec 2017-10-10 20:40:34 +00:00
parent 936ce1aa54
commit e75f4c5ad9
2 changed files with 30 additions and 0 deletions

View File

@ -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

View File

@ -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)