99
1010import orjson
1111
12- from .call_method_parser import parse_args , parse_call_method_name
12+ from .call_method_parser import (
13+ InvalidKwarg ,
14+ parse_args ,
15+ parse_call_method_name ,
16+ parse_kwarg ,
17+ )
1318from .components import UnicornField , UnicornView
1419from .errors import UnicornViewError
1520from .utils import generate_checksum
@@ -58,7 +63,7 @@ def _set_property_from_data(
5863
5964
6065def _set_property_from_payload (
61- component : UnicornView , payload : Dict , data : Dict
66+ component : UnicornView , payload : Dict , data : Dict = {}
6267) -> None :
6368 """
6469 Sets properties on the component based on the payload.
@@ -67,68 +72,97 @@ def _set_property_from_payload(
6772 Args:
6873 param component: Component to set attributes on.
6974 param payload: Dictionary that comes with request.
70- param data: Dictionary that gets sent back with the response.
75+ param data: Dictionary that gets sent back with the response. Defaults to {}.
7176 """
7277
7378 property_name = payload .get ("name" )
79+ assert property_name is not None , "Payload name is required"
7480 property_value = payload .get ("value" )
81+ assert property_value is not None , "Payload value is required"
82+
7583 component .updating (property_name , property_value )
7684
77- if property_name is not None and property_value is not None :
78- """
79- Handles nested properties. For example, for the following component:
85+ """
86+ Handles nested properties. For example, for the following component:
8087
81- class Author(UnicornField):
82- name = "Neil"
88+ class Author(UnicornField):
89+ name = "Neil"
8390
84- class TestView(UnicornView):
85- author = Author()
86-
87- `payload` would be `{'name': 'author.name', 'value': 'Neil Gaiman'}`
91+ class TestView(UnicornView):
92+ author = Author()
93+
94+ `payload` would be `{'name': 'author.name', 'value': 'Neil Gaiman'}`
8895
89- The following code updates UnicornView.author.name based the payload's `author.name`.
90- """
91- property_name_parts = property_name .split ("." )
92- component_or_field = component
93- data_or_dict = data # Could be an internal portion of data that gets set
94-
95- for (idx , property_name_part ) in enumerate (property_name_parts ):
96- if hasattr (component_or_field , property_name_part ):
97- if idx == len (property_name_parts ) - 1 :
98- if hasattr (component_or_field , "_set_property" ):
99- # Can assume that `component_or_field` is a component
100- component_or_field ._set_property (
101- property_name_part , property_value
102- )
103- else :
104- # Handle calling the updating/updated method for nested properties
105- property_name_snake_case = property_name .replace ("." , "_" )
106- updating_function_name = f"updating_{ property_name_snake_case } "
107- updated_function_name = f"updated_{ property_name_snake_case } "
108-
109- if hasattr (component , updating_function_name ):
110- getattr (component , updating_function_name )(property_value )
111-
112- setattr (component_or_field , property_name_part , property_value )
113-
114- if hasattr (component , updated_function_name ):
115- getattr (component , updated_function_name )(property_value )
116-
117- data_or_dict [property_name_part ] = property_value
118- else :
119- component_or_field = getattr (component_or_field , property_name_part )
120- data_or_dict = data_or_dict .get (property_name_part , {})
121- elif isinstance (component_or_field , dict ):
122- if idx == len (property_name_parts ) - 1 :
123- component_or_field [property_name_part ] = property_value
124- data_or_dict [property_name_part ] = property_value
96+ The following code updates UnicornView.author.name based the payload's `author.name`.
97+ """
98+ property_name_parts = property_name .split ("." )
99+ component_or_field = component
100+ data_or_dict = data # Could be an internal portion of data that gets set
101+
102+ for (idx , property_name_part ) in enumerate (property_name_parts ):
103+ if hasattr (component_or_field , property_name_part ):
104+ if idx == len (property_name_parts ) - 1 :
105+ if hasattr (component_or_field , "_set_property" ):
106+ # Can assume that `component_or_field` is a component
107+ component_or_field ._set_property (property_name_part , property_value )
125108 else :
126- component_or_field = component_or_field [property_name_part ]
127- data_or_dict = data_or_dict .get (property_name_part , {})
109+ # Handle calling the updating/updated method for nested properties
110+ property_name_snake_case = property_name .replace ("." , "_" )
111+ updating_function_name = f"updating_{ property_name_snake_case } "
112+ updated_function_name = f"updated_{ property_name_snake_case } "
113+
114+ if hasattr (component , updating_function_name ):
115+ getattr (component , updating_function_name )(property_value )
116+
117+ setattr (component_or_field , property_name_part , property_value )
118+
119+ if hasattr (component , updated_function_name ):
120+ getattr (component , updated_function_name )(property_value )
121+
122+ data_or_dict [property_name_part ] = property_value
123+ else :
124+ component_or_field = getattr (component_or_field , property_name_part )
125+ data_or_dict = data_or_dict .get (property_name_part , {})
126+ elif isinstance (component_or_field , dict ):
127+ if idx == len (property_name_parts ) - 1 :
128+ component_or_field [property_name_part ] = property_value
129+ data_or_dict [property_name_part ] = property_value
130+ else :
131+ component_or_field = component_or_field [property_name_part ]
132+ data_or_dict = data_or_dict .get (property_name_part , {})
128133
129134 component .updated (property_name , property_value )
130135
131136
137+ def _get_property_value (component : UnicornView , property_name : str ) -> Any :
138+ """
139+ Gets property value from the component based on the property name.
140+ Handles nested property names.
141+
142+ Args:
143+ param component: Component to get property values from.
144+ param property_name: Property name. Can be "dot-notation" to get nested properties.
145+ """
146+
147+ assert property_name is not None , "property_name name is required"
148+
149+ # Handles nested properties
150+ property_name_parts = property_name .split ("." )
151+ component_or_field = component
152+
153+ for (idx , property_name_part ) in enumerate (property_name_parts ):
154+ if hasattr (component_or_field , property_name_part ):
155+ if idx == len (property_name_parts ) - 1 :
156+ return getattr (component_or_field , property_name_part )
157+ else :
158+ component_or_field = getattr (component_or_field , property_name_part )
159+ elif isinstance (component_or_field , dict ):
160+ if idx == len (property_name_parts ) - 1 :
161+ return component_or_field [property_name_part ]
162+ else :
163+ component_or_field = component_or_field [property_name_part ]
164+
165+
132166def _call_method_name (
133167 component : UnicornView , method_name : str , params : List [Any ]
134168) -> None :
@@ -291,19 +325,24 @@ def message(request: HttpRequest, component_name: str = None) -> JsonResponse:
291325 call_method_name = payload .get ("name" , "" )
292326 assert call_method_name , "Missing 'name' key for callMethod"
293327
328+ (method_name , params ) = parse_call_method_name (call_method_name )
329+ setter_method = {}
330+
294331 if "=" in call_method_name :
295- # TODO: Parse this in a sane way so that an equal sign
296- # in the middle of a string doesn't trigger the set shortcut
297- equal_sign_idx = call_method_name .index ("=" )
298- property_name = call_method_name [:equal_sign_idx ]
299- parsed_args = parse_args (call_method_name [equal_sign_idx + 1 :])
300- property_value = parsed_args [0 ] if parsed_args else None
301-
302- if hasattr (component , property_name ) and property_value is not None :
303- component .calling (f"set_{ property_name } " , property_value )
304- setattr (component , property_name , property_value )
305- component .called (f"set_{ property_name } " , property_value )
306- component_request .data [property_name ] = property_value
332+ try :
333+ setter_method = parse_kwarg (
334+ call_method_name , raise_if_unparseable = True
335+ )
336+ except InvalidKwarg :
337+ pass
338+
339+ if setter_method :
340+ # Create a fake "payload" so that nested properties will get set as expected
341+ property_name = list (setter_method .keys ())[0 ]
342+ property_value = setter_method [property_name ]
343+ payload = {"name" : property_name , "value" : property_value }
344+
345+ _set_property_from_payload (component , payload )
307346 else :
308347 (method_name , params ) = parse_call_method_name (call_method_name )
309348
@@ -329,12 +368,12 @@ def message(request: HttpRequest, component_name: str = None) -> JsonResponse:
329368 is_reset_called = True
330369 elif method_name == "toggle" :
331370 for property_name in params :
332- if hasattr (component , property_name ):
333- property_value = getattr (component , property_name )
371+ # Create a fake "payload" so that nested properties will get set as expected
372+ property_value = _get_property_value (component , property_name )
373+ property_value = not property_value
374+ payload = {"name" : property_name , "value" : property_value }
334375
335- if isinstance (property_value , bool ):
336- property_value = not property_value
337- setattr (component , property_name , property_value )
376+ _set_property_from_payload (component , payload )
338377 elif method_name == "validate" :
339378 # Handle the validate special action
340379 validate_all_fields = True
0 commit comments