Skip to content

Commit

Permalink
Make it possible to use dot notation for setting context in reflex
Browse files Browse the repository at this point in the history
In an effort to improve the api for setting the context that is
used in the final context in the reflex we introduce a dot notation
for setting the context. Ie `reflex.context.my_context = 'value'`.

For the old way of setting the context in instance variables you
now also prevented to set an instance variable that is already used
by the reflex.
  • Loading branch information
jonathan-s committed Jul 28, 2021
1 parent 23f5bf4 commit 59b6d8d
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 4 deletions.
8 changes: 8 additions & 0 deletions sockpuppet/consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,14 @@ def render_page(self, reflex):
reflex_context = {key: getattr(reflex, key) for key in instance_variables}
reflex_context["stimulus_reflex"] = True

if not reflex.context._attr_data:
msg = (
"Setting context through instance variables is deprecated, "
'please use reflex.context.context_variable = "my_data"'
)
logger.warning(msg)
reflex_context.update(reflex.context)

original_context_data = view.view_class.get_context_data
reflex.get_context_data(**reflex_context)
# monkey patch context method
Expand Down
53 changes: 49 additions & 4 deletions sockpuppet/reflex.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,75 @@
from collections import UserDict
from django.urls import resolve
from urllib.parse import urlparse

from django.test import RequestFactory

PROTECTED_VARIABLES = [
"consumer",
"context",
"element",
"params",
"selectors",
"session",
"url",
]


class Context(UserDict):
"""
A dictionary that keeps track of whether it's been used as dictionary
or if values has been set with dot notation. We expect things to be set
in dot notation so a warning is issued until next major version (1.0)
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._attr_data = {}

def __getitem__(self, key):
if not self.data.get(key) and not self._attr_data.get(key):
raise KeyError(key)
return self.data.get(key) or self._attr_data.get(key)

def __getattr__(self, key):
if not self.__dict__.get("data"):
self.__dict__["data"] = {}
if not self.__dict__.get("_attr_data"):
self.__dict__["_attr_data"] = {}

if (
self.__dict__["data"].get(key) is None
and self.__dict__["_attr_data"].get(key) is None
):
raise AttributeError(key)
return self.data.get(key) or self._attr_data.get(key)

def __setattr__(self, key, value):
if not self.__dict__.get("_attr_data"):
self.__dict__["_attr_data"] = {}
self.__dict__["_attr_data"][key] = value


class Reflex:
def __init__(self, consumer, url, element, selectors, params):
self.consumer = consumer
self.url = url
self.context = Context()
self.element = element
self.params = params
self.selectors = selectors
self.session = consumer.scope["session"]
self.params = params
self.context = {}
self.url = url

self._init_run = True

def __repr__(self):
return f"<Reflex url: {self.url}, session: {self.get_channel_id()}>"

def __setattr__(self, name, value):
if name in PROTECTED_VARIABLES and getattr(self, "_init_run", None):
raise ValueError("This instance variable is used by the reflex.")
super().__setattr__(name, value)

def get_context_data(self, *args, **kwargs):
if self.context:
self.context.update(**kwargs)
Expand All @@ -45,7 +90,7 @@ def get_context_data(self, *args, **kwargs):

context = view.get_context_data(**{"stimulus_reflex": True})

self.context = context
self.context.update(context)
self.context.update(**kwargs)
return self.context

Expand Down
18 changes: 18 additions & 0 deletions tests/test_reflex.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.test import TestCase
from sockpuppet.test_utils import reflex_factory
from sockpuppet.reflex import Context


class ReflexTests(TestCase):
Expand All @@ -10,3 +11,20 @@ def test_reflex_can_access_context(self):

self.assertIn('count', context)
self.assertIn('otherCount', context)

def test_context_api_works_correctly(self):
'''Test that context correctly stores information'''
context = Context()
context.hello = 'hello'

self.assertEqual(context.hello, 'hello')
self.assertEqual(context['hello'], 'hello')

self.assertEqual(context.data.get('hello'), None)
self.assertEqual(context._attr_data.get('hello'), 'hello')

with self.assertRaises(AttributeError):
context.not_an_attribute

with self.assertRaises(KeyError):
context['not_in_dictionary']

0 comments on commit 59b6d8d

Please sign in to comment.