Skip to content

Commit

Permalink
purge Lenient.__setattr__ from api
Browse files Browse the repository at this point in the history
  • Loading branch information
bjlittle committed Jun 15, 2020
1 parent 35a603d commit c6bce3d
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 123 deletions.
52 changes: 33 additions & 19 deletions lib/iris/common/lenient.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

__all__ = [
"LENIENT",
"LENIENT_ENABLE",
"LENIENT_PROTECTED",
"Lenient",
"lenient_client",
"lenient_service",
Expand All @@ -23,9 +21,9 @@


#: Default Lenient services global activation state.
LENIENT_ENABLE = True
LENIENT_ENABLE_DEFAULT = True

#: Protected Lenient internal non-client, non-service controlled keys.
#: Protected Lenient internal non-client, non-service keys.
LENIENT_PROTECTED = ("active", "enable")


Expand Down Expand Up @@ -259,7 +257,7 @@ def __init__(self, *args, **kwargs):
# The executing lenient client at runtime.
self.__dict__["active"] = None
# The global lenient services state activation switch.
self.__dict__["enable"] = LENIENT_ENABLE
self.__dict__["enable"] = LENIENT_ENABLE_DEFAULT

for service in args:
self.register_service(service)
Expand Down Expand Up @@ -323,37 +321,28 @@ def __repr__(self):
joiner = ",\n{}".format(" " * width)
return "{}({})".format(cls, joiner.join(kwargs))

def __setattr__(self, name, value):
self._common_setter(name, value, AttributeError)

def __setitem__(self, name, value):
name = qualname(name)
self._common_setter(name, value, KeyError)

def _common_setter(self, name, value, exception):
cls = self.__class__.__name__

if name not in self.__dict__:
emsg = f"Invalid {cls!r} option, got {name!r}."
raise exception(emsg)
raise KeyError(emsg)

if name == "active":
value = qualname(value)
if not isinstance(value, str) and value is not None:
emsg = f"Invalid {cls!r} option {name!r}, got {value!r}."
raise ValueError(emsg)
self.__dict__[name] = value
elif name == "enable":
if not isinstance(value, bool):
emsg = f"Invalid {cls!r} option {name!r}, got {value!r}."
raise ValueError(emsg)
self.enable = value
else:
if isinstance(value, str) or callable(value):
value = (value,)

if isinstance(value, Iterable):
value = tuple([qualname(item) for item in value])

self.__dict__[name] = value
self.__dict__[name] = value

@contextmanager
def context(self, *args, **kwargs):
Expand All @@ -378,7 +367,7 @@ def context(self, *args, **kwargs):
original_state = self.__dict__.copy()
# Temporarily update the state with the kwargs first.
for name, value in kwargs.items():
setattr(self, name, value)
self[name] = value
# Temporarily update the client/services, if provided.
if args:
active = self.__dict__["active"]
Expand All @@ -393,6 +382,31 @@ def context(self, *args, **kwargs):
self.__dict__.clear()
self.__dict__.update(original_state)

@property
def enable(self):
"""Return the activation state of the lenient services."""
return self.__dict__["enable"]

@enable.setter
def enable(self, state):
"""
Set the activate state of the lenient services.
Setting the state to `False` disables all lenient services, and
setting the state to `True` enables all lenient services.
Args:
* state (bool):
Activate state for lenient services.
"""
if not isinstance(state, bool):
cls = self.__class__.__name__
emsg = f"Invalid {cls!r} option 'enable', got {state!r}."
raise ValueError(emsg)
self.__dict__["enable"] = state

def register_client(self, func, services):
"""
Add the provided mapping of lenient client function/method to
Expand Down
23 changes: 13 additions & 10 deletions lib/iris/common/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from functools import wraps
import re

from .lenient import LENIENT, lenient_service
from .lenient import LENIENT, lenient_service, qualname


__all__ = [
Expand Down Expand Up @@ -39,12 +39,11 @@ class _NamedTupleMeta(ABCMeta):
"""

def __new__(mcs, name, bases, namespace):
token = "_members"
names = []

for base in bases:
if hasattr(base, token):
base_names = getattr(base, token)
if hasattr(base, "_members"):
base_names = getattr(base, "_members")
is_abstract = getattr(
base_names, "__isabstractmethod__", False
)
Expand All @@ -55,10 +54,10 @@ def __new__(mcs, name, bases, namespace):
base_names = (base_names,)
names.extend(base_names)

if token in namespace and not getattr(
namespace[token], "__isabstractmethod__", False
if "_members" in namespace and not getattr(
namespace["_members"], "__isabstractmethod__", False
):
namespace_names = namespace[token]
namespace_names = namespace["_members"]

if (not isinstance(namespace_names, Iterable)) or isinstance(
namespace_names, str
Expand Down Expand Up @@ -194,6 +193,7 @@ def func(field):
result = left
return result

# Note that, we use "_members" not "_fields".
return [func(field) for field in BaseMetadata._members]

@staticmethod
Expand Down Expand Up @@ -382,7 +382,8 @@ def combine(self, other, lenient=None):
if lenient:
args, kwargs = (self.combine,), dict()
else:
args, kwargs = (), {self.combine: False}
# Require to qualname the method to make it a hashable key.
args, kwargs = (), {qualname(self.combine): False}

with LENIENT.context(*args, **kwargs):
values = self._combine(other)
Expand Down Expand Up @@ -427,7 +428,8 @@ def difference(self, other, lenient=None):
if lenient:
args, kwargs = (self.difference,), dict()
else:
args, kwargs = (), {self.difference: False}
# Require to qualname the method to make it a hashable key.
args, kwargs = (), {qualname(self.difference): False}

with LENIENT.context(*args, **kwargs):
values = self._difference(other)
Expand Down Expand Up @@ -460,7 +462,8 @@ def equal(self, other, lenient=None):
if lenient:
args, kwargs = (self.equal,), dict()
else:
args, kwargs = (), {self.equal: False}
# Require to qualname the method to make it a hashable key.
args, kwargs = (), {qualname(self.equal): False}

with LENIENT.context(*args, **kwargs):
result = self.__eq__(other)
Expand Down
118 changes: 24 additions & 94 deletions lib/iris/tests/unit/common/lenient/test_Lenient.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from collections import Iterable

from iris.common.lenient import (
LENIENT_ENABLE,
LENIENT_ENABLE_DEFAULT,
LENIENT_PROTECTED,
Lenient,
qualname,
Expand All @@ -24,7 +24,7 @@

class Test___init__(tests.IrisTest):
def setUp(self):
self.expected = dict(active=None, enable=LENIENT_ENABLE)
self.expected = dict(active=None, enable=LENIENT_ENABLE_DEFAULT)

def test_default(self):
lenient = Lenient()
Expand Down Expand Up @@ -323,96 +323,6 @@ def service():
_ = self.lenient[service]


class Test___setattr__(tests.IrisTest):
def setUp(self):
self.lenient = Lenient()

def test_not_in(self):
emsg = "Invalid .* option, got 'wibble'."
with self.assertRaisesRegex(AttributeError, emsg):
self.lenient.wibble = None

def test_in_value_str(self):
client = "client"
service = "service"
self.lenient.__dict__[client] = None
self.lenient.client = service
self.assertEqual(self.lenient.__dict__[client], (service,))

def test_in_value_callable(self):
def service():
pass

client = "client"
qualname_service = qualname(service)
self.lenient.__dict__[client] = None
self.lenient.client = service
self.assertEqual(self.lenient.__dict__[client], (qualname_service,))

def test_in_value_bool(self):
client = "client"
self.lenient.__dict__[client] = None
self.lenient.client = True
self.assertTrue(self.lenient.__dict__[client])
self.assertFalse(isinstance(self.lenient.__dict__[client], Iterable))

def test_in_value_iterable(self):
client = "client"
services = ("service1", "service2")
self.lenient.__dict__[client] = None
self.lenient.client = services
self.assertEqual(self.lenient.__dict__[client], services)

def test_in_value_iterable_callable(self):
def service1():
pass

def service2():
pass

client = "client"
self.lenient.__dict__[client] = None
qualname_services = (qualname(service1), qualname(service2))
self.lenient.client = (service1, service2)
self.assertEqual(self.lenient.__dict__[client], qualname_services)

def test_active_iterable(self):
self.assertIsNone(self.lenient.__dict__["active"])
emsg = "Invalid .* option 'active'"
with self.assertRaisesRegex(ValueError, emsg):
self.lenient.active = (None,)

def test_active_str(self):
active = "active"
client = "client1"
self.assertIsNone(self.lenient.__dict__[active])
self.lenient.active = client
self.assertEqual(self.lenient.__dict__[active], client)

def test_active_callable(self):
def client():
pass

active = "active"
qualname_client = qualname(client)
self.assertIsNone(self.lenient.__dict__[active])
self.lenient.active = client
self.assertEqual(self.lenient.__dict__[active], qualname_client)

def test_enable(self):
enable = "enable"
self.assertEqual(self.lenient.__dict__[enable], LENIENT_ENABLE)
self.lenient.enable = True
self.assertTrue(self.lenient.__dict__[enable])
self.lenient.enable = False
self.assertFalse(self.lenient.__dict__[enable])

def test_enable_invalid(self):
emsg = "Invalid .* option 'enable'"
with self.assertRaisesRegex(ValueError, emsg):
self.lenient.enable = None


class Test___setitem__(tests.IrisTest):
def setUp(self):
self.lenient = Lenient()
Expand Down Expand Up @@ -557,7 +467,9 @@ def client():

def test_enable(self):
enable = "enable"
self.assertEqual(self.lenient.__dict__[enable], LENIENT_ENABLE)
self.assertEqual(
self.lenient.__dict__[enable], LENIENT_ENABLE_DEAFAULT
)
self.lenient[enable] = True
self.assertTrue(self.lenient.__dict__[enable])
self.lenient[enable] = False
Expand All @@ -572,7 +484,7 @@ def test_enable_invalid(self):
class Test_context(tests.IrisTest):
def setUp(self):
self.lenient = Lenient()
self.default = dict(active=None, enable=LENIENT_ENABLE)
self.default = dict(active=None, enable=LENIENT_ENABLE_DEFAULT)

def copy(self):
return self.lenient.__dict__.copy()
Expand Down Expand Up @@ -674,6 +586,24 @@ def test_context_runtime(self):
self.assertEqual(post, self.default)


class Test_enable(tests.IrisTest):
def setUp(self):
self.lenient = Lenient()

def test_getter(self):
self.assertEqual(self.lenient.enable, LENIENT_ENABLE_DEFAULT)

def test_setter_invalid(self):
emsg = "Invalid .* option 'enable'"
with self.assertRaisesRegex(ValueError, emsg):
self.lenient.enable = 0

def test_setter(self):
self.assertEqual(self.lenient.enable, LENIENT_ENABLE_DEFAULT)
self.lenient.enable = False
self.assertFalse(self.lenient.enable)


class Test_register_client(tests.IrisTest):
def setUp(self):
self.lenient = Lenient()
Expand Down

0 comments on commit c6bce3d

Please sign in to comment.