Files
notifications-admin/app/models/__init__.py
Chris Hill-Scott 23e1682260 Make dynamic attributes of model introspectable
`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
2020-04-14 09:44:38 +01:00

83 lines
2.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from abc import ABC, abstractmethod
from collections.abc import Sequence
from flask import abort
class JSONModel():
ALLOWED_PROPERTIES = set()
def __init__(self, _dict):
# in the case of a bad request _dict may be `None`
self._dict = _dict or {}
def __bool__(self):
return self._dict != {}
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
def __getattribute__(self, attr):
try:
return super().__getattribute__(attr)
except AttributeError as e:
# Re-raise any `AttributeError`s that are not directly on
# this object because they indicate an underlying exception
# that we dont want to swallow
if str(e) != "'{}' object has no attribute '{}'".format(
self.__class__.__name__, attr
):
raise e
if attr in super().__getattribute__('ALLOWED_PROPERTIES'):
return super().__getattribute__('_dict')[attr]
raise AttributeError((
"'{}' object has no attribute '{}' and '{}' is not a field "
"in the underlying JSON"
).format(
self.__class__.__name__, attr, attr
))
def _get_by_id(self, things, id):
try:
return next(thing for thing in things if thing['id'] == str(id))
except StopIteration:
abort(404)
class ModelList(ABC, Sequence):
@property
@abstractmethod
def client_method(self):
pass
@property
@abstractmethod
def model(self):
pass
def __init__(self, *args):
self.items = self.client_method(*args)
def __getitem__(self, index):
return self.model(self.items[index])
def __len__(self):
return len(self.items)
def __add__(self, other):
return list(self) + list(other)
def __radd__(self, other):
return list(other) + list(self)