From 23e16822608c96d191db3a1058dbc9f4f02fbbfe Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Tue, 14 Apr 2020 09:39:39 +0100 Subject: [PATCH] Make dynamic attributes of model introspectable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `dir(object)` is a useful Python function that tells you what attributes and methods an object has. It’s also used by tools like iPython and IDEs for code completion. Some of the attributes of a `JSONModel` are dynamic, based on what fields we expect in the underlying JSON. Therefore they don’t automatically end up in the result of calling `dir`. To get around this we can implement our own `__dir__` method, which also returns the names of the fields we’re expecting the the JSON. Inspired by this Raymond Hettinger tweet: > #python tip: If you add attributes to an API with __getattr__() or > __getattribute__(), remember to update __dir__() to make the extension > introspectable. — https://twitter.com/raymondh/status/1249860863525146624 --- app/models/__init__.py | 3 +++ tests/app/models/test_base_model.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/app/models/__init__.py b/app/models/__init__.py index d63f21bbe..c7770b1e1 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -18,6 +18,9 @@ class JSONModel(): def __hash__(self): return hash(self.id) + def __dir__(self): + return super().__dir__() + list(sorted(self.ALLOWED_PROPERTIES)) + def __eq__(self, other): return self.id == other.id diff --git a/tests/app/models/test_base_model.py b/tests/app/models/test_base_model.py index 0c3ccf2f7..7f5c748ee 100644 --- a/tests/app/models/test_base_model.py +++ b/tests/app/models/test_base_model.py @@ -68,3 +68,11 @@ def test_model_doesnt_swallow_attribute_errors(json_response): Custom(json_response).foo assert str(e.value) == 'Something has gone wrong' + + +def test_dynamic_properties_are_introspectable(): + + class Custom(JSONModel): + ALLOWED_PROPERTIES = {'foo', 'bar', 'baz'} + + assert dir(Custom({}))[-3:] == ['bar', 'baz', 'foo']