Skip to content

Commit

Permalink
Deprecate old-style Waffle switches
Browse files Browse the repository at this point in the history
Namespaced waffle switches are now deprecated, just like waffle flags.
Namespaced classes are still available as legacy classes.
  • Loading branch information
regisb committed Nov 11, 2020
1 parent 8aa5559 commit 80f00dd
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 99 deletions.
35 changes: 26 additions & 9 deletions edx_toggles/tests/test_legacy_waffle.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
WaffleFlag,
WaffleFlagNamespace,
WaffleSwitch,
WaffleSwitchNamespace,
WaffleSwitchNamespace
)


Expand All @@ -16,18 +16,35 @@ class TestWaffleSwitch(TestCase):
Tests the WaffleSwitch.
"""

NAMESPACE_NAME = "test_namespace"
WAFFLE_SWITCH_NAME = "test_switch_name"
TEST_NAMESPACE = WaffleSwitchNamespace(NAMESPACE_NAME)
WAFFLE_SWITCH = WaffleSwitch(TEST_NAMESPACE, WAFFLE_SWITCH_NAME, __name__)

def test_namespaced_switch_name(self):
"""
Verify namespaced_switch_name returns the correct namespace switch name
"""
expected = self.NAMESPACE_NAME + "." + self.WAFFLE_SWITCH_NAME
actual = self.WAFFLE_SWITCH.namespaced_switch_name
self.assertEqual(actual, expected)
namespace = WaffleSwitchNamespace("test_namespace")
switch = WaffleSwitch(namespace, "test_switch_name", __name__)
self.assertEqual(
"test_namespace.test_switch_name", switch.namespaced_switch_name
)

def test_default_value(self):
namespace = WaffleSwitchNamespace("test_namespace")
switch = WaffleSwitch(namespace, "test_switch_name", module_name="module1")
self.assertFalse(switch.is_enabled())
self.assertFalse(namespace.is_enabled("test_switch_name"))

def test_get_set_cache_for_request(self):
namespace = WaffleSwitchNamespace("test_namespace")
switch = WaffleSwitch(namespace, "test_switch_name", module_name="module1")
self.assertFalse(
namespace.get_request_cache_with_short_name("test_switch_name")
)
namespace.set_request_cache_with_short_name("test_switch_name", True)
self.assertTrue(namespace.get_request_cache_with_short_name("test_switch_name"))
self.assertTrue(switch.is_enabled())
namespace.set_request_cache_with_short_name("test_switch_name", False)
self.assertFalse(
namespace.get_request_cache_with_short_name("test_switch_name")
)


class TestWaffleFlag(TestCase):
Expand Down
5 changes: 2 additions & 3 deletions edx_toggles/tests/test_testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.test.client import RequestFactory
from edx_django_utils.cache import RequestCache

from edx_toggles.toggles import WaffleFlag, WaffleSwitch, WaffleSwitchNamespace
from edx_toggles.toggles import WaffleFlag, WaffleSwitch
from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch


Expand Down Expand Up @@ -99,8 +99,7 @@ class OverrideWaffleSwitchTests(TestCase):
"""

def test_override(self):
namespace = WaffleSwitchNamespace("test_namespace")
switch = WaffleSwitch(namespace, "test_switch", module_name="testmodule")
switch = WaffleSwitch("test_namespace.test_switch", module_name="testmodule")

self.assertFalse(switch.is_enabled())
with override_waffle_switch(switch, active=True):
Expand Down
22 changes: 21 additions & 1 deletion edx_toggles/tests/test_waffle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,32 @@

from django.test import TestCase

from edx_toggles.toggles import WaffleFlag
from edx_toggles.toggles import WaffleFlag, WaffleSwitch


class WaffleFlagTests(TestCase):
"""
WaffleFlag tests.
"""

def test_name_validation(self):
WaffleFlag("namespaced.name", module_name="module1")
self.assertRaises(
ValueError, WaffleFlag, "non_namespaced", module_name="module1"
)


class WaffleSwitchTest(TestCase):
"""
WaffleSwitch tests.
"""

def test_name_validation(self):
WaffleSwitch("namespaced.name", module_name="module1")
self.assertRaises(
ValueError, WaffleSwitch, "non_namespaced", module_name="module1"
)

def test_constructor(self):
switch = WaffleSwitch("namespaced.name", module_name="module1")
self.assertEqual("module1", switch.module_name)
4 changes: 3 additions & 1 deletion edx_toggles/toggles/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
from .internal.waffle.flag import WaffleFlag
from .internal.waffle.legacy import WaffleFlag as LegacyWaffleFlag
from .internal.waffle.legacy import WaffleFlagNamespace as LegacyWaffleFlagNamespace
from .internal.waffle.legacy import WaffleSwitch, WaffleSwitchNamespace
from .internal.waffle.legacy import WaffleSwitch as LegacyWaffleSwitch
from .internal.waffle.legacy import WaffleSwitchNamespace as LegacyWaffleSwitchNamespace
from .internal.waffle.switch import WaffleSwitch
29 changes: 29 additions & 0 deletions edx_toggles/toggles/internal/waffle/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
Base waffle toggle classes.
"""

from ..base import BaseToggle


# pylint: disable=abstract-method
class BaseWaffle(BaseToggle):
"""
Base waffle toggle class, which performs waffle name validation.
"""

def __init__(self, name, module_name=None):
"""
Base waffle constructor
Arguments:
name (String): The name of the switch. This name must include a dot (".") to indicate namespacing.
module_name (String): The name of the module where the flag is created. This should be ``__name__`` in most
cases.
"""
if "." not in name:
raise ValueError(
"Cannot create non-namespaced '{}' {} instance".format(
name, self.__class__.__name__
)
)
super().__init__(name, default=False, module_name=module_name)
16 changes: 3 additions & 13 deletions edx_toggles/toggles/internal/waffle/flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from edx_django_utils.monitoring import set_custom_attribute
from waffle import flag_is_active

from ..base import BaseToggle
from .base import BaseWaffle
from .cache import _get_waffle_request_cache

log = logging.getLogger(__name__)


class WaffleFlag(BaseToggle):
class WaffleFlag(BaseWaffle):
"""
Represents a single waffle flag, using both a global and a request cache.
"""
Expand All @@ -25,19 +25,9 @@ class WaffleFlag(BaseToggle):
def __init__(self, name, module_name=None, log_prefix=""):
"""
Waffle flag constructor
Arguments:
name (String): The name of the flag. This name must include a dot (".") to indicate namespacing.
module_name (String): The name of the module where the flag is created. This should be ``__name__`` in most
cases.
log_prefix (str): Optional string to be appended to log messages (e.g. "Grades: ").
"""
if "." not in name:
raise ValueError(
"Cannot create non-namespaced '{}' WaffleFlag".format(name)
)
self.log_prefix = log_prefix
super().__init__(name, default=False, module_name=module_name)
super().__init__(name, module_name=module_name)

def is_enabled(self):
"""
Expand Down
85 changes: 23 additions & 62 deletions edx_toggles/toggles/internal/waffle/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,11 @@
"""
import warnings
from abc import ABC
from weakref import WeakSet

from edx_django_utils.monitoring import set_custom_attribute
from waffle import switch_is_active

from ..base import BaseToggle
from .cache import _get_waffle_request_cache as _get_waffle_namespace_request_cache
from .flag import WaffleFlag as NewWaffleFlag
from .switch import WaffleSwitch as NewWaffleSwitch


class BaseNamespace(ABC):
Expand Down Expand Up @@ -90,94 +87,58 @@ def _namespaced_name(self, setting_name):

class WaffleSwitchNamespace(BaseNamespace):
"""
Provides a single namespace for a set of waffle switches.
All namespaced switch values are stored in a single request cache containing
all switches for all namespaces.
Legacy waffle switch namespace class.
"""

def is_enabled(self, switch_name):
"""
Returns and caches whether the given waffle switch is enabled.
Legacy method preserved for backward compatibility.
"""
namespaced_switch_name = self._namespaced_name(switch_name)
value = self.get_request_cache(namespaced_switch_name)
if value is None:
value = switch_is_active(namespaced_switch_name)
self.set_request_cache(namespaced_switch_name, value)
return value
return NewWaffleSwitch(self._namespaced_name(switch_name)).is_enabled()

def get_request_cache(self, namespaced_switch_name, default=None):
"""
API for accessing the request cache. In general, users should avoid accessing the namespace cache.
"""
return self._cached_switches.get(namespaced_switch_name, default)
return NewWaffleSwitch(namespaced_switch_name).get_request_cache(
default=default
)

def get_request_cache_with_short_name(self, switch_name, default=None):
"""
Compatibility method. This will be removed soon in favor of the namespaced `get_request_cache` method.
"""
return self.get_request_cache(
self._namespaced_name(switch_name), default=default
)

def set_request_cache(self, namespaced_switch_name, value):
"""
Manually set the request cache value. Beware! There be dragons.
"""
self._cached_switches[namespaced_switch_name] = value
NewWaffleSwitch(namespaced_switch_name).set_request_cache(value)

def set_request_cache_with_short_name(self, switch_name, value):
"""
Compatibility method. This will be removed soon in favor of the namespaced `set_request_cache` method.
"""
self.set_request_cache(self._namespaced_name(switch_name), value)

@property
def _cached_switches(self):
"""
Return a dictionary of all namespaced switches in the request cache.
"""
return _get_waffle_namespace_request_cache().setdefault("switches", {})


class WaffleSwitch(BaseToggle):
class WaffleSwitch(NewWaffleSwitch):
"""
Represents a single waffle switch, using a cached namespace.
Legacy namespaced waffle switch class.
"""

NAMESPACE_CLASS = WaffleSwitchNamespace
_class_instances = WeakSet()

def __init__(self, waffle_namespace, switch_name, module_name=None):
"""
Arguments:
waffle_namespace (Namespace | String): Namespace for this switch.
switch_name (String): The name of the switch (without namespacing).
module_name (String): The name of the module where the flag is created. This should be ``__name__`` in most
cases.
"""
if isinstance(waffle_namespace, str):
waffle_namespace = self.NAMESPACE_CLASS(name=waffle_namespace)
warnings.warn(
(
"{} is deprecated. Please use non-namespaced edx_toggles.toggles.WaffleSwitch instead."
).format(self.__class__.__name__),
DeprecationWarning,
stacklevel=2,
)
set_custom_attribute("deprecated_edx_toggles_waffle", "WaffleSwitch")
if not isinstance(waffle_namespace, str):
waffle_namespace = waffle_namespace.name

self.waffle_namespace = waffle_namespace
# Non-namespaced flag_name attribute preserved for backward compatibility
self.switch_name = switch_name

# Note that the waffle constructor does not provide a default
name = self.waffle_namespace._namespaced_name(self.switch_name)
super().__init__(name, default=False, module_name=module_name)
name = "{}.{}".format(waffle_namespace, switch_name)
super().__init__(name, module_name=module_name)

@property
def namespaced_switch_name(self):
"""
For backward compatibility, we still provide the `namespaced_switch_name`, property, even though users should
now use the `name` attribute.
"""
return self.name

def is_enabled(self):
return self.waffle_namespace.is_enabled(self.switch_name)


class WaffleFlagNamespace(BaseNamespace):
"""
Expand Down
46 changes: 46 additions & 0 deletions edx_toggles/toggles/internal/waffle/switch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
New-style switch classes: these classes no longer depend on namespaces to be created.
"""
from weakref import WeakSet

from waffle import switch_is_active

from .base import BaseWaffle
from .cache import _get_waffle_request_cache


class WaffleSwitch(BaseWaffle):
"""
Represents a single waffle switch, using both a global and a request cache.
"""

_class_instances = WeakSet()

def is_enabled(self):
"""
Legacy method preserved for backward compatibility.
"""
value = self.get_request_cache()
if value is None:
value = switch_is_active(self.name)
self.set_request_cache(value)
return value

def get_request_cache(self, default=None):
"""
API for accessing the request cache. In general, users should avoid accessing the namespace cache.
"""
return self._cached_switches.get(self.name, default)

def set_request_cache(self, value):
"""
Manually set the request cache value. Beware! There be dragons.
"""
self._cached_switches[self.name] = value

@property
def _cached_switches(self):
"""
Return a dictionary of all namespaced switches in the request cache.
"""
return _get_waffle_request_cache().setdefault("switches", {})
14 changes: 4 additions & 10 deletions edx_toggles/toggles/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,13 @@ def __init__(self, switch, active):
self.switch = switch
self._cached_value = None
self._previous_active = None
super().__init__(switch.namespaced_switch_name, active)
super().__init__(switch.name, active)

def __enter__(self):
self._previous_active = self.switch.waffle_namespace.is_enabled(
self.switch.switch_name
)
self.switch.waffle_namespace.set_request_cache(
self.switch.namespaced_switch_name, self.active
)
self._previous_active = self.switch.is_enabled()
self.switch.set_request_cache(self.active)
super().__enter__()

def __exit__(self, exc_type, exc_val, exc_tb):
super().__exit__(exc_type, exc_val, exc_tb)
self.switch.waffle_namespace.set_request_cache(
self.switch.namespaced_switch_name, self._previous_active
)
self.switch.set_request_cache(self._previous_active)

0 comments on commit 80f00dd

Please sign in to comment.