Skip to content

Commit 59b012c

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 59b012c

File tree

3 files changed

+72
-4
lines changed

3 files changed

+72
-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: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,72 @@
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 self.__dict__['data'].get(key) is None and self.__dict__['_attr_data'].get(key) is None:
41+
raise AttributeError(key)
42+
return self.data.get(key) or self._attr_data.get(key)
43+
44+
def __setattr__(self, key, value):
45+
if not self.__dict__.get('_attr_data'):
46+
self.__dict__['_attr_data'] = {}
47+
self.__dict__['_attr_data'][key] = value
48+
49+
1550
class Reflex:
1651
def __init__(self, consumer, url, element, selectors, params):
1752
self.consumer = consumer
18-
self.url = url
53+
self.context = Context()
1954
self.element = element
55+
self.params = params
2056
self.selectors = selectors
2157
self.session = consumer.scope["session"]
22-
self.params = params
23-
self.context = {}
58+
self.url = url
59+
60+
self._init_run = True
2461

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

65+
def __setattr__(self, name, value):
66+
if name in PROTECTED_VARIABLES and getattr(self, "_init_run", None):
67+
raise ValueError("This instance variable is used by the reflex.")
68+
super().__setattr__(name, value)
69+
2870
def get_context_data(self, *args, **kwargs):
2971
if self.context:
3072
self.context.update(**kwargs)
@@ -45,7 +87,7 @@ def get_context_data(self, *args, **kwargs):
4587

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

48-
self.context = context
90+
self.context.update(context)
4991
self.context.update(**kwargs)
5092
return self.context
5193

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)