Skip to content

Commit 59b6d8d

Browse files
committed
Make it possible to use dot notation for setting context in reflex
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.
1 parent 23f5bf4 commit 59b6d8d

File tree

3 files changed

+75
-4
lines changed

3 files changed

+75
-4
lines changed

sockpuppet/consumer.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,14 @@ def render_page(self, reflex):
235235
reflex_context = {key: getattr(reflex, key) for key in instance_variables}
236236
reflex_context["stimulus_reflex"] = True
237237

238+
if not reflex.context._attr_data:
239+
msg = (
240+
"Setting context through instance variables is deprecated, "
241+
'please use reflex.context.context_variable = "my_data"'
242+
)
243+
logger.warning(msg)
244+
reflex_context.update(reflex.context)
245+
238246
original_context_data = view.view_class.get_context_data
239247
reflex.get_context_data(**reflex_context)
240248
# monkey patch context method

sockpuppet/reflex.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,75 @@
1+
from collections import UserDict
12
from django.urls import resolve
23
from urllib.parse import urlparse
34

45
from django.test import RequestFactory
56

67
PROTECTED_VARIABLES = [
78
"consumer",
9+
"context",
810
"element",
11+
"params",
912
"selectors",
1013
"session",
1114
"url",
1215
]
1316

1417

18+
class Context(UserDict):
19+
"""
20+
A dictionary that keeps track of whether it's been used as dictionary
21+
or if values has been set with dot notation. We expect things to be set
22+
in dot notation so a warning is issued until next major version (1.0)
23+
"""
24+
25+
def __init__(self, *args, **kwargs):
26+
super().__init__(*args, **kwargs)
27+
self._attr_data = {}
28+
29+
def __getitem__(self, key):
30+
if not self.data.get(key) and not self._attr_data.get(key):
31+
raise KeyError(key)
32+
return self.data.get(key) or self._attr_data.get(key)
33+
34+
def __getattr__(self, key):
35+
if not self.__dict__.get("data"):
36+
self.__dict__["data"] = {}
37+
if not self.__dict__.get("_attr_data"):
38+
self.__dict__["_attr_data"] = {}
39+
40+
if (
41+
self.__dict__["data"].get(key) is None
42+
and self.__dict__["_attr_data"].get(key) is None
43+
):
44+
raise AttributeError(key)
45+
return self.data.get(key) or self._attr_data.get(key)
46+
47+
def __setattr__(self, key, value):
48+
if not self.__dict__.get("_attr_data"):
49+
self.__dict__["_attr_data"] = {}
50+
self.__dict__["_attr_data"][key] = value
51+
52+
1553
class Reflex:
1654
def __init__(self, consumer, url, element, selectors, params):
1755
self.consumer = consumer
18-
self.url = url
56+
self.context = Context()
1957
self.element = element
58+
self.params = params
2059
self.selectors = selectors
2160
self.session = consumer.scope["session"]
22-
self.params = params
23-
self.context = {}
61+
self.url = url
62+
63+
self._init_run = True
2464

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

68+
def __setattr__(self, name, value):
69+
if name in PROTECTED_VARIABLES and getattr(self, "_init_run", None):
70+
raise ValueError("This instance variable is used by the reflex.")
71+
super().__setattr__(name, value)
72+
2873
def get_context_data(self, *args, **kwargs):
2974
if self.context:
3075
self.context.update(**kwargs)
@@ -45,7 +90,7 @@ def get_context_data(self, *args, **kwargs):
4590

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

48-
self.context = context
93+
self.context.update(context)
4994
self.context.update(**kwargs)
5095
return self.context
5196

tests/test_reflex.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.test import TestCase
22
from sockpuppet.test_utils import reflex_factory
3+
from sockpuppet.reflex import Context
34

45

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

1112
self.assertIn('count', context)
1213
self.assertIn('otherCount', context)
14+
15+
def test_context_api_works_correctly(self):
16+
'''Test that context correctly stores information'''
17+
context = Context()
18+
context.hello = 'hello'
19+
20+
self.assertEqual(context.hello, 'hello')
21+
self.assertEqual(context['hello'], 'hello')
22+
23+
self.assertEqual(context.data.get('hello'), None)
24+
self.assertEqual(context._attr_data.get('hello'), 'hello')
25+
26+
with self.assertRaises(AttributeError):
27+
context.not_an_attribute
28+
29+
with self.assertRaises(KeyError):
30+
context['not_in_dictionary']

0 commit comments

Comments
 (0)