builder.py 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142
  1. # Copyright 2021-2024 Avaiga Private Limited
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  4. # the License. You may obtain a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  9. # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  10. # specific language governing permissions and limitations under the License.
  11. import contextlib
  12. import json
  13. import numbers
  14. import time as _time
  15. import typing as t
  16. import xml.etree.ElementTree as etree
  17. from datetime import date, datetime, time
  18. from enum import Enum
  19. from inspect import isclass
  20. from types import LambdaType
  21. from urllib.parse import quote
  22. from .._warnings import _warn
  23. from ..partial import Partial
  24. from ..types import PropertyType, _get_taipy_type
  25. from ..utils import (
  26. _date_to_string,
  27. _get_broadcast_var_name,
  28. _get_client_var_name,
  29. _get_data_type,
  30. _get_expr_var_name,
  31. _get_lambda_id,
  32. _getscopeattr,
  33. _getscopeattr_drill,
  34. _is_boolean,
  35. _is_function,
  36. _is_true,
  37. _is_unnamed_function,
  38. _MapDict,
  39. _to_camel_case,
  40. )
  41. from ..utils.chart_config_builder import _CHART_NAMES, _build_chart_config
  42. from ..utils.table_col_builder import _enhance_columns, _get_name_indexed_property
  43. from ..utils.types import _TaipyBase, _TaipyData, _TaipyToJson
  44. from .json import _TaipyJsonEncoder
  45. from .utils import _add_to_dict_and_get, _get_columns_dict, _get_tuple_val
  46. if t.TYPE_CHECKING:
  47. from ..gui import Gui
  48. class _Builder:
  49. """
  50. Constructs an XML node that can be rendered as a React node.
  51. This class can only be instantiated internally by Taipy.
  52. """
  53. __keys: t.Dict[str, int] = {}
  54. __BLOCK_CONTROLS = ["dialog", "expandable", "pane", "part"]
  55. __TABLE_COLUMNS_DEPS = {
  56. "data",
  57. "columns",
  58. "date_format",
  59. "number_format",
  60. "nan_value",
  61. "width",
  62. "filter",
  63. "editable",
  64. "group_by",
  65. "apply",
  66. "style",
  67. "tooltip",
  68. "lov",
  69. "row_class_name",
  70. "cell_class_name",
  71. "format_fn"
  72. }
  73. def __init__(
  74. self,
  75. gui: "Gui",
  76. control_type: str,
  77. element_name: str,
  78. attributes: t.Optional[t.Dict[str, t.Any]],
  79. hash_names: t.Optional[t.Dict[str, str]] = None,
  80. default_value: t.Optional[t.Any] = "<Empty>",
  81. lib_name: str = "taipy",
  82. ):
  83. from ..gui import Gui
  84. from .factory import _Factory
  85. if hash_names is None:
  86. hash_names = {}
  87. self.el = etree.Element(element_name)
  88. self.__control_type = control_type
  89. self.__element_name = element_name
  90. self.__lib_name = lib_name
  91. self.__attributes = attributes or {}
  92. self.__hashes = hash_names.copy()
  93. self.__update_vars: t.List[str] = []
  94. self.__gui: Gui = gui
  95. self.__default_property_name = _Factory.get_default_property_name(control_type) or ""
  96. default_property_value = self.__attributes.get(self.__default_property_name, None)
  97. if default_property_value is None and default_value is not None:
  98. self.__attributes[self.__default_property_name] = default_value
  99. # Bind properties dictionary to attributes if condition is matched (will
  100. # leave the binding for function at the builder )
  101. if "properties" in self.__attributes:
  102. (prop_dict, prop_hash) = _Builder.__parse_attribute_value(gui, self.__attributes["properties"])
  103. if prop_hash is None:
  104. prop_hash = prop_dict
  105. prop_hash = self.__gui._bind_var(prop_hash)
  106. if hasattr(self.__gui._bindings(), prop_hash):
  107. prop_dict = _getscopeattr(self.__gui, prop_hash)
  108. if isinstance(prop_dict, (dict, _MapDict)):
  109. # Iterate through prop_dict and append to self.attributes
  110. var_name, _ = gui._get_real_var_name(prop_hash)
  111. for k, v in prop_dict.items():
  112. (val, key_hash) = _Builder.__parse_attribute_value(gui, v)
  113. self.__attributes[k] = (
  114. f"{{None if ({var_name}) is None else ({var_name}).get('{k}')}}" if key_hash is None else v
  115. )
  116. else:
  117. _warn(f"{self.__control_type}.properties ({prop_hash}) must be a dict.")
  118. # Bind potential function and expressions in self.attributes
  119. self.__hashes.update(_Builder._get_variable_hash_names(gui, self.__attributes, hash_names))
  120. # set classname
  121. self.__set_class_names()
  122. # define a unique key
  123. self.set_attribute("key", _Builder._get_key(self.__element_name))
  124. @staticmethod
  125. def __parse_attribute_value(gui: "Gui", value) -> t.Tuple:
  126. if isinstance(value, str) and gui._is_expression(value):
  127. hash_value = gui._evaluate_expr(value)
  128. try:
  129. func = gui._get_user_function(hash_value)
  130. if _is_function(func):
  131. return (func, hash_value)
  132. return (_getscopeattr_drill(gui, hash_value), hash_value)
  133. except AttributeError:
  134. _warn(f"Expression '{value}' cannot be evaluated.")
  135. return (value, None)
  136. @staticmethod
  137. def _get_variable_hash_names(
  138. gui: "Gui", attributes: t.Dict[str, t.Any], hash_names: t.Optional[t.Dict[str, str]] = None
  139. ) -> t.Dict[str, str]:
  140. if hash_names is None:
  141. hash_names = {}
  142. hashes = {}
  143. # Bind potential function and expressions in self.attributes
  144. for k, v in attributes.items():
  145. hash_name = hash_names.get(k)
  146. if hash_name is None:
  147. if isinstance(v, str):
  148. # need to unescape the double quotes that were escaped during preprocessing
  149. (val, hash_name) = _Builder.__parse_attribute_value(gui, v.replace('\\"', '"'))
  150. else:
  151. val = v
  152. if _is_function(val) and not hash_name:
  153. # if it's not a callable (and not a string), forget it
  154. if _is_unnamed_function(val):
  155. # lambda or callable instance
  156. hash_name = _get_lambda_id(t.cast(LambdaType, val))
  157. gui._bind_var_val(hash_name, val) # type: ignore[arg-type]
  158. else:
  159. hash_name = _get_expr_var_name(val.__name__)
  160. if val is not None or hash_name:
  161. attributes[k] = val
  162. if hash_name:
  163. hashes[k] = hash_name
  164. return hashes
  165. @staticmethod
  166. def __to_string(x: t.Any) -> str:
  167. return str(x)
  168. @staticmethod
  169. def _get_key(name: str) -> str:
  170. key_index = _Builder.__keys.get(name, 0)
  171. _Builder.__keys[name] = key_index + 1
  172. return f"{name}.{key_index}"
  173. @staticmethod
  174. def _reset_key() -> None:
  175. _Builder.__keys = {}
  176. def get_name_indexed_property(self, name: str) -> t.Dict[str, t.Any]:
  177. """
  178. TODO-undocumented
  179. Returns all properties defined as <property name>[<named index>] as a dict.
  180. Arguments:
  181. name (str): The property name.
  182. """
  183. return _get_name_indexed_property(self.__attributes, name)
  184. def __get_boolean_attribute(self, name: str, default_value=False):
  185. bool_attr = self.__attributes.get(name, default_value)
  186. return _is_true(bool_attr) if isinstance(bool_attr, str) else bool(bool_attr)
  187. def set_boolean_attribute(self, name: str, value: bool):
  188. """
  189. TODO-undocumented
  190. Defines a React Boolean attribute (attr={true|false}).
  191. Arguments:
  192. name (str): The property name.
  193. value (bool): the boolean value.
  194. """
  195. return self.__set_react_attribute(_to_camel_case(name), value)
  196. def set_dict_attribute(self, name: str, default_value: t.Optional[t.Dict[str, t.Any]] = None):
  197. """
  198. TODO-undocumented
  199. Defines a React attribute as a stringified json dict.
  200. The original property can be a dict or a string formed as <key 1>:<value 1>;<key 2>:<value 2>.
  201. Arguments:
  202. name (str): The property name.
  203. default value (dict): used if no value is specified.
  204. """
  205. dict_attr = self.__attributes.get(name)
  206. if dict_attr is None:
  207. dict_attr = default_value
  208. if dict_attr is not None:
  209. if isinstance(dict_attr, str):
  210. vals = [x.strip().split(":") for x in dict_attr.split(";")]
  211. dict_attr = {val[0].strip(): val[1].strip() for val in vals if len(val) > 1}
  212. if isinstance(dict_attr, (dict, _MapDict)):
  213. self.__set_json_attribute(_to_camel_case(name), dict_attr)
  214. else:
  215. _warn(f"{self.__element_name}: {name} should be a dict: '{str(dict_attr)}'.")
  216. return self
  217. def set_dynamic_dict_attribute(self, name: str, default_value: t.Optional[t.Dict[str, t.Any]] = None):
  218. """
  219. TODO-undocumented
  220. Defines a React attribute as a stringified json dict.
  221. The original property can be a dict or a string formed as <key 1>:<value 1>;<key 2>:<value 2>.
  222. Arguments:
  223. name (str): The property name.
  224. default value (dict): used if no value is specified.
  225. """
  226. dict_attr = self.__attributes.get(name)
  227. if dict_attr is None:
  228. dict_attr = default_value
  229. if dict_attr is not None:
  230. if isinstance(dict_attr, str):
  231. vals = [x.strip().split(":") for x in dict_attr.split(";")]
  232. dict_attr = {val[0].strip(): val[1].strip() for val in vals if len(val) > 1}
  233. if isinstance(dict_attr, (dict, _MapDict)):
  234. self.__set_json_attribute(_to_camel_case("default_" + name), dict_attr)
  235. else:
  236. _warn(f"{self.__element_name}: {name} should be a dict: '{str(dict_attr)}'.")
  237. if dict_hash := self.__hashes.get(name):
  238. dict_hash = self.__get_typed_hash_name(dict_hash, PropertyType.dynamic_dict)
  239. prop_name = _to_camel_case(name)
  240. self.__update_vars.append(f"{prop_name}={dict_hash}")
  241. self.__set_react_attribute(prop_name, dict_hash)
  242. return self
  243. def __set_json_attribute(self, name, value):
  244. return self.set_attribute(name, json.dumps(value, cls=_TaipyJsonEncoder))
  245. def set_number_attribute(self, name: str, default_value: t.Optional[str] = None, optional: t.Optional[bool] = True):
  246. """
  247. TODO-undocumented
  248. Defines a React number attribute (attr={<number>}).
  249. Arguments:
  250. name (str): The property name.
  251. default_value (optional(str)): the default value as a string.
  252. optional (bool): Default to True, the property is required if False.
  253. """
  254. value = self.__attributes.get(name, default_value)
  255. if value is None:
  256. if not optional:
  257. _warn(f"Property {name} is required for control {self.__control_type}.")
  258. return self
  259. if isinstance(value, str):
  260. try:
  261. val = float(value)
  262. except ValueError:
  263. raise ValueError(f"Property {name} expects a number for control {self.__control_type}") from None
  264. elif isinstance(value, numbers.Number):
  265. val = value # type: ignore[assignment]
  266. else:
  267. raise ValueError(
  268. f"Property {name} expects a number for control {self.__control_type}, received {type(value)}"
  269. )
  270. return self.__set_react_attribute(_to_camel_case(name), val)
  271. def __set_string_attribute(
  272. self, name: str, default_value: t.Optional[str] = None, optional: t.Optional[bool] = True
  273. ):
  274. str_attr = self.__attributes.get(name, default_value)
  275. if str_attr is None:
  276. if not optional:
  277. _warn(f"Property {name} is required for control {self.__control_type}.")
  278. return self
  279. return self.set_attribute(_to_camel_case(name), str(str_attr))
  280. def __set_dynamic_date_attribute(self, var_name: str, default_value: t.Optional[str] = None):
  281. date_attr = self.__attributes.get(var_name, default_value)
  282. if date_attr is None:
  283. date_attr = default_value
  284. if isinstance(date_attr, (datetime, date, time)):
  285. value = _date_to_string(date_attr)
  286. self.set_attribute(_to_camel_case(var_name), value)
  287. return self
  288. def __set_dynamic_string_attribute(
  289. self,
  290. name: str,
  291. default_value: t.Optional[str] = None,
  292. with_update: t.Optional[bool] = False,
  293. dynamic_property_name: t.Optional[str] = None,
  294. ):
  295. str_val = self.__attributes.get(name, default_value)
  296. if str_val is not None:
  297. self.set_attribute(
  298. _to_camel_case(f"default_{name}" if dynamic_property_name is None else name), str(str_val)
  299. )
  300. if hash_name := self.__hashes.get(name):
  301. prop_name = _to_camel_case(name if dynamic_property_name is None else dynamic_property_name)
  302. if with_update:
  303. self.__update_vars.append(f"{prop_name}={hash_name}")
  304. self.__set_react_attribute(prop_name, hash_name)
  305. return self
  306. def __set_function_attribute(
  307. self, name: str, default_value: t.Optional[str] = None, optional: t.Optional[bool] = True
  308. ):
  309. str_attr = self.__attributes.get(name, default_value)
  310. if str_attr is None:
  311. if not optional:
  312. _warn(f"Property {name} is required for control {self.__control_type}.")
  313. return self
  314. elif _is_function(str_attr):
  315. str_attr = self.__hashes.get(name)
  316. if str_attr is None:
  317. return self
  318. elif _is_boolean(str_attr) and not _is_true(t.cast(str, str_attr)):
  319. return self.__set_react_attribute(_to_camel_case(name), False)
  320. elif str_attr:
  321. str_attr = str(str_attr)
  322. func = self.__gui._get_user_function(str_attr)
  323. if func == str_attr:
  324. _warn(f"{self.__control_type}.{name}: {str_attr} is not a function.")
  325. return self.set_attribute(_to_camel_case(name), str_attr) if str_attr else self
  326. def __set_string_or_number_attribute(self, name: str, default_value: t.Optional[t.Any] = None):
  327. attr = self.__attributes.get(name, default_value)
  328. if attr is None:
  329. return self
  330. if isinstance(attr, numbers.Number):
  331. return self.__set_react_attribute(_to_camel_case(name), attr)
  332. else:
  333. return self.set_attribute(_to_camel_case(name), attr)
  334. def __set_react_attribute(self, name: str, value: t.Any):
  335. return self.set_attribute(name, "{!" + (str(value).lower() if isinstance(value, bool) else str(value)) + "!}")
  336. @staticmethod
  337. def enum_adapter(e: t.Union[Enum, str]):
  338. return (e.value, e.name) if isinstance(e, Enum) else e
  339. def _get_lov_adapter( # noqa: C901
  340. self, var_name: str, property_name: t.Optional[str] = None, multi_selection=True, with_default=True
  341. ):
  342. property_name = var_name if property_name is None else property_name
  343. lov_name = self.__hashes.get(var_name)
  344. real_var_name = self.__gui._get_real_var_name(lov_name)[0] if lov_name else None
  345. lov = self.__attributes.get(var_name)
  346. adapter: t.Any = None
  347. var_type: t.Optional[str] = None
  348. if isinstance(lov, str):
  349. lov = list(lov.split(";"))
  350. if isclass(lov) and issubclass(lov, Enum):
  351. adapter = _Builder.enum_adapter
  352. var_type = "Enum"
  353. lov = list(lov)
  354. if not isinstance(lov, list) and hasattr(lov, "tolist"):
  355. try:
  356. lov = lov.tolist() # type: ignore[union-attr]
  357. except Exception as e:
  358. _warn(f"Error accessing List of values for '{real_var_name or property_name}'", e)
  359. lov = None
  360. default_lov: t.Optional[t.List[t.Any]] = [] if with_default or not lov_name else None
  361. adapter = self.__attributes.get("adapter", adapter)
  362. if adapter and isinstance(adapter, str):
  363. adapter = self.__gui._get_user_function(adapter)
  364. if adapter and not _is_function(adapter):
  365. _warn(f"{self.__element_name}: adapter property value is invalid.")
  366. adapter = None
  367. var_type = self.__attributes.get("type", var_type)
  368. if isclass(var_type):
  369. var_type = var_type.__name__
  370. if isinstance(lov, list):
  371. if not isinstance(var_type, str):
  372. elt = None
  373. if lov:
  374. elt = lov[0]
  375. else:
  376. value = self.__attributes.get("value")
  377. if isinstance(value, list):
  378. if len(value) > 0:
  379. elt = value[0]
  380. else:
  381. elt = value
  382. var_type = self.__gui._get_unique_type_adapter(type(elt).__name__)
  383. if adapter is None:
  384. adapter = self.__gui._get_adapter_for_type(var_type)
  385. elif var_type == str.__name__ and _is_function(adapter):
  386. var_type += (
  387. _get_lambda_id(t.cast(LambdaType, adapter))
  388. if _is_unnamed_function(adapter)
  389. else _get_expr_var_name(adapter.__name__)
  390. )
  391. if lov_name:
  392. if adapter is None:
  393. adapter = self.__gui._get_adapter_for_type(lov_name)
  394. else:
  395. self.__gui._add_type_for_var(lov_name, t.cast(str, var_type))
  396. if value_name := self.__hashes.get("value"):
  397. if adapter is None:
  398. adapter = self.__gui._get_adapter_for_type(value_name)
  399. else:
  400. self.__gui._add_type_for_var(value_name, t.cast(str, var_type))
  401. if adapter is not None:
  402. self.__gui._add_adapter_for_type(var_type, adapter) # type: ignore[arg-type]
  403. if default_lov is not None and lov:
  404. for elt in lov:
  405. ret = self.__gui._run_adapter(
  406. t.cast(t.Callable, adapter),
  407. elt,
  408. adapter.__name__ if hasattr(adapter, "__name__") else "adapter",
  409. )
  410. if ret is not None:
  411. default_lov.append(ret)
  412. ret_list = []
  413. value = self.__attributes.get("value")
  414. val_list = value if isinstance(value, list) else [value]
  415. for val in val_list:
  416. ret = self.__gui._run_adapter(
  417. t.cast(t.Callable, adapter),
  418. val,
  419. adapter.__name__ if hasattr(adapter, "__name__") else "adapter",
  420. id_only=True,
  421. )
  422. if ret is not None:
  423. ret_list.append(ret)
  424. if multi_selection:
  425. self.__set_default_value("value", ret_list)
  426. else:
  427. ret_val = ret_list[0] if len(ret_list) else ""
  428. if ret_val == "-1" and self.__attributes.get("unselected_value") is not None:
  429. ret_val = str(self.__attributes.get("unselected_value", ""))
  430. self.__set_default_value("value", ret_val)
  431. # LoV default value
  432. if default_lov is not None:
  433. self.__set_json_attribute(_to_camel_case(f"default_{property_name}"), default_lov)
  434. # LoV expression binding
  435. if lov_name and real_var_name:
  436. typed_lov_hash = (
  437. self.__gui._evaluate_expr(
  438. f"{{{self.__gui._get_call_method_name('_get_adapted_lov')}({real_var_name},'{var_type}')}}"
  439. )
  440. if var_type
  441. else lov_name
  442. )
  443. hash_name = self.__get_typed_hash_name(typed_lov_hash, PropertyType.lov)
  444. camel_prop = _to_camel_case(property_name)
  445. self.__update_vars.append(f"{camel_prop}={hash_name}")
  446. self.__set_react_attribute(camel_prop, hash_name)
  447. return self
  448. def __filter_attribute_names(self, names: t.Iterable[str]):
  449. return [k for k in self.__attributes if k in names or any(k.startswith(n + "[") for n in names)]
  450. def __get_held_name(self, key: str):
  451. name = self.__hashes.get(key)
  452. if name:
  453. v = self.__attributes.get(key)
  454. if isinstance(v, _TaipyBase):
  455. return name[: len(v.get_hash()) + 1]
  456. return name
  457. def __filter_attributes_hashes(self, keys: t.List[str]):
  458. hash_names = [k for k in self.__hashes if k in keys]
  459. attr_names = [k for k in keys if k not in hash_names]
  460. return (
  461. {k: v for k, v in self.__attributes.items() if k in attr_names},
  462. {k: self.__get_held_name(k) for k in self.__hashes if k in hash_names},
  463. )
  464. def __build_rebuild_fn(self, fn_name: str, attribute_names: t.Iterable[str]):
  465. rebuild = self.__attributes.get("rebuild", False)
  466. rebuild_hash = self.__hashes.get("rebuild")
  467. if rebuild_hash or _is_true(rebuild):
  468. attributes, hashes = self.__filter_attributes_hashes(self.__filter_attribute_names(attribute_names))
  469. rebuild_name = f"bool({self.__gui._get_real_var_name(rebuild_hash)[0]})" if rebuild_hash else "None"
  470. try:
  471. self.__gui._set_building(True)
  472. return self.__gui._evaluate_expr(
  473. "{"
  474. + f'{fn_name}({rebuild}, {rebuild_name}, "{quote(json.dumps(attributes))}", "{quote(json.dumps(hashes))}", {", ".join([f"{k}={v2}" for k, v2 in {v: self.__gui._get_real_var_name(t.cast(str, v))[0] for v in hashes.values()}.items()])})' # noqa: E501
  475. + "}"
  476. )
  477. finally:
  478. self.__gui._set_building(False)
  479. return None
  480. def _get_dataframe_attributes(self) -> "_Builder":
  481. date_format = _add_to_dict_and_get(self.__attributes, "date_format", "MM/dd/yyyy")
  482. data = self.__attributes.get("data")
  483. data_hash = self.__hashes.get("data", "")
  484. cmp_hash = ""
  485. if data_hash:
  486. cmp_idx = 1
  487. cmp_datas = []
  488. cmp_datas_hash = []
  489. while cmp_data := self.__hashes.get(f"data[{cmp_idx}]"):
  490. cmp_idx += 1
  491. cmp_datas.append(self.__gui._get_real_var_name(cmp_data)[0])
  492. cmp_datas_hash.append(cmp_data)
  493. if cmp_datas:
  494. cmp_hash = self.__gui._evaluate_expr(
  495. "{"
  496. + f"{self.__gui._get_call_method_name('_compare_data')}"
  497. + f'({self.__gui._get_real_var_name(data_hash)[0]},{",".join(cmp_datas)})'
  498. + "}"
  499. )
  500. self.__update_vars.append(f"comparedatas={','.join(cmp_datas_hash)}")
  501. col_types = self.__gui._get_accessor().get_col_types(data_hash, _TaipyData(data, data_hash))
  502. col_dict = _get_columns_dict(
  503. data, self.__attributes.get("columns", {}), col_types, date_format, self.__attributes.get("number_format")
  504. )
  505. rebuild_fn_hash = self.__build_rebuild_fn(
  506. self.__gui._get_call_method_name("_tbl_cols"), _Builder.__TABLE_COLUMNS_DEPS
  507. )
  508. if rebuild_fn_hash:
  509. self.__set_react_attribute("columns", rebuild_fn_hash)
  510. if col_dict is not None:
  511. _enhance_columns(self.__attributes, self.__hashes, col_dict, self.__element_name)
  512. self.__set_json_attribute("defaultColumns", col_dict)
  513. if cmp_hash:
  514. hash_name = self.__get_typed_hash_name(cmp_hash, PropertyType.data)
  515. self.__set_react_attribute(
  516. _to_camel_case("data"),
  517. _get_client_var_name(hash_name),
  518. )
  519. self.__set_update_var_name(hash_name)
  520. self.set_boolean_attribute("compare", True)
  521. self.__set_string_attribute("on_compare")
  522. if not isinstance(self.__attributes.get("style"), (type(None), dict, _MapDict)):
  523. _warn("Table: property 'style' has been renamed to 'row_class_name'.")
  524. if row_class_name := self.__attributes.get("row_class_name"):
  525. if _is_function(row_class_name):
  526. value = self.__hashes.get("row_class_name")
  527. elif isinstance(row_class_name, str):
  528. value = row_class_name.strip()
  529. else:
  530. value = None
  531. if value in col_types.keys():
  532. _warn(f"{self.__element_name}: row_class_name={value} must not be a column name.")
  533. elif value:
  534. self.set_attribute("rowClassName", value)
  535. if tooltip := self.__attributes.get("tooltip"):
  536. if _is_function(tooltip):
  537. value = self.__hashes.get("tooltip")
  538. elif isinstance(tooltip, str):
  539. value = tooltip.strip()
  540. else:
  541. value = None
  542. if value in col_types.keys():
  543. _warn(f"{self.__element_name}: tooltip={value} must not be a column name.")
  544. elif value:
  545. self.set_attribute("tooltip", value)
  546. return self
  547. def _get_chart_config(self, default_type: str, default_mode: str):
  548. self.__attributes["_default_type"] = default_type
  549. self.__attributes["_default_mode"] = default_mode
  550. rebuild_fn_hash = self.__build_rebuild_fn(
  551. self.__gui._get_call_method_name("_chart_conf"),
  552. _CHART_NAMES + ("_default_type", "_default_mode", "data"),
  553. )
  554. if rebuild_fn_hash:
  555. self.__set_react_attribute("config", rebuild_fn_hash)
  556. # read column definitions
  557. data = self.__attributes.get("data")
  558. data_hash = self.__hashes.get("data", "")
  559. col_types = self.__gui._get_accessor().get_col_types(data_hash, _TaipyData(data, data_hash))
  560. config = _build_chart_config(self.__gui, self.__attributes, col_types)
  561. self.__set_json_attribute("defaultConfig", config)
  562. self._set_chart_selected(max=len(config.get("traces", [])))
  563. self.__set_refresh_on_update()
  564. return self
  565. def _set_string_with_check(self, var_name: str, values: t.List[str], default_value: t.Optional[str] = None):
  566. value = self.__attributes.get(var_name, default_value)
  567. if value is not None:
  568. value = str(value).lower()
  569. self.__attributes[var_name] = value
  570. if value not in values:
  571. _warn(f"{self.__element_name}: {var_name}={value} should be in {values}.")
  572. else:
  573. self.__set_string_attribute(var_name, default_value)
  574. return self
  575. def __set_list_attribute(
  576. self,
  577. name: str,
  578. hash_name: t.Optional[str],
  579. val: t.Any,
  580. elt_type: t.Type,
  581. dynamic=True,
  582. default_val: t.Optional[t.Any] = None,
  583. ) -> t.List[str]:
  584. val = default_val if val is None else val
  585. if not hash_name and isinstance(val, str):
  586. val = [elt_type(t.strip()) for t in val.split(";")]
  587. if isinstance(val, list):
  588. if hash_name and dynamic:
  589. self.__set_react_attribute(name, hash_name)
  590. return [f"{name}={hash_name}"]
  591. else:
  592. self.__set_json_attribute(name, val)
  593. elif val is not None:
  594. _warn(f"{self.__element_name}: {name} should be a list of {elt_type}.")
  595. return []
  596. def _set_chart_selected(self, max=0):
  597. name = "selected"
  598. default_sel = self.__attributes.get(name)
  599. if not isinstance(default_sel, list) and name in self.__attributes:
  600. default_sel = []
  601. if max == 0:
  602. self.__update_vars.extend(
  603. self.__set_list_attribute(
  604. name,
  605. self.__hashes.get(name),
  606. default_sel,
  607. int,
  608. )
  609. )
  610. return
  611. idx = 1
  612. name_idx = f"{name}[{idx}]"
  613. sel = self.__attributes.get(name_idx)
  614. if not isinstance(sel, list) and name_idx in self.__attributes:
  615. sel = []
  616. while idx <= max or name_idx in self.__attributes:
  617. if sel is not None or default_sel is not None:
  618. self.__update_vars.extend(
  619. self.__set_list_attribute(
  620. f"{name}{idx - 1}",
  621. self.__hashes.get(name_idx if sel is not None else name),
  622. sel if sel is not None else default_sel,
  623. int,
  624. )
  625. )
  626. idx += 1
  627. name_idx = f"{name}[{idx}]"
  628. sel = self.__attributes.get(name_idx)
  629. if not isinstance(sel, list) and name_idx in self.__attributes:
  630. sel = []
  631. def _get_list_attribute(self, name: str, list_type: PropertyType):
  632. hash_name = self.__hashes.get(name)
  633. if hash_name is None:
  634. list_val = self.__attributes.get(name)
  635. if isinstance(list_val, str):
  636. list_val = list(list_val.split(";"))
  637. if isinstance(list_val, list):
  638. # TODO catch the cast exception
  639. if list_type.value == PropertyType.number.value:
  640. list_val = [int(v) for v in list_val]
  641. else:
  642. list_val = [int(v) for v in list_val]
  643. else:
  644. if list_val is not None:
  645. _warn(f"{self.__element_name}: {name} should be a list.")
  646. list_val = []
  647. self.__set_react_attribute(_to_camel_case(name), list_val)
  648. else:
  649. self.__set_react_attribute(_to_camel_case(name), hash_name)
  650. self.__update_vars.append(f"{_to_camel_case(name)}={hash_name}")
  651. return self
  652. def __set_class_names(self):
  653. self.set_attribute("libClassName", self.__lib_name + "-" + self.__control_type.replace("_", "-"))
  654. if (private_css := self.__attributes.get("style")) and isinstance(private_css, (dict, _MapDict)):
  655. taipy_style = etree.Element("TaipyStyle")
  656. taipy_style.set("className", f"tpcss-{id(private_css)}")
  657. taipy_style.set(
  658. "content",
  659. json.dumps(private_css if isinstance(private_css, dict) else private_css._dict),
  660. )
  661. taipy_style.text = "style"
  662. self.el.append(taipy_style)
  663. return self.__set_dynamic_string_attribute("class_name", dynamic_property_name="dynamic_class_name")
  664. def _set_dataType(self):
  665. value = self.__attributes.get("value")
  666. return self.set_attribute("dataType", _get_data_type(value))
  667. def _set_file_content(self, var_name: str = "content"):
  668. if hash_name := self.__hashes.get(var_name):
  669. self.__set_update_var_name(hash_name)
  670. else:
  671. _warn(f"file_selector: {var_name} should be bound.")
  672. return self
  673. def _set_content(self, var_name: str = "content", image=True):
  674. content = self.__attributes.get(var_name)
  675. hash_name = self.__hashes.get(var_name)
  676. if content is None and hash_name is None:
  677. return self
  678. value = self.__gui._get_content(hash_name or var_name, content, image)
  679. if hash_name:
  680. hash_name = self.__get_typed_hash_name(hash_name, PropertyType.image if image else PropertyType.content)
  681. if hash_name:
  682. self.__set_react_attribute(
  683. var_name,
  684. _get_client_var_name(hash_name),
  685. )
  686. return self.set_attribute(_to_camel_case(f"default_{var_name}"), value)
  687. def __set_dynamic_string_list(self, var_name: str, default_value: t.Any):
  688. hash_name = self.__hashes.get(var_name)
  689. loi = self.__attributes.get(var_name)
  690. if loi is None:
  691. loi = default_value
  692. if isinstance(loi, str):
  693. loi = [s.strip() for s in loi.split(";") if s.strip()]
  694. if isinstance(loi, list):
  695. self.__set_json_attribute(_to_camel_case(f"default_{var_name}"), loi)
  696. if hash_name:
  697. self.__update_vars.append(f"{var_name}={hash_name}")
  698. self.__set_react_attribute(var_name, hash_name)
  699. return self
  700. def __set_dynamic_number_attribute(self, var_name: str, default_value: t.Any):
  701. hash_name = self.__hashes.get(var_name)
  702. numVal = self.__attributes.get(var_name)
  703. if numVal is None:
  704. numVal = default_value
  705. if isinstance(numVal, str):
  706. try:
  707. numVal = float(numVal)
  708. except Exception as e:
  709. _warn(f"{self.__element_name}: {var_name} cannot be transformed into a number", e)
  710. numVal = 0
  711. if isinstance(numVal, numbers.Number):
  712. self.__set_react_attribute(_to_camel_case(f"default_{var_name}"), numVal)
  713. elif numVal is not None:
  714. _warn(f"{self.__element_name}: {var_name} value is not valid ({numVal}).")
  715. if hash_name:
  716. hash_name = self.__get_typed_hash_name(hash_name, PropertyType.number)
  717. self.__update_vars.append(f"{var_name}={hash_name}")
  718. self.__set_react_attribute(var_name, hash_name)
  719. return self
  720. def __set_default_value(
  721. self,
  722. var_name: str,
  723. value: t.Optional[t.Any] = None,
  724. native_type: bool = False,
  725. var_type: t.Optional[t.Union[PropertyType, t.Type[_TaipyBase]]] = None,
  726. ):
  727. if value is None:
  728. value = self.__attributes.get(var_name)
  729. default_var_name = _to_camel_case(f"default_{var_name}")
  730. if isinstance(value, (datetime, date, time)):
  731. return self.set_attribute(default_var_name, _date_to_string(value))
  732. elif isinstance(value, str):
  733. return self.set_attribute(default_var_name, value)
  734. elif native_type and isinstance(value, numbers.Number):
  735. return self.__set_react_attribute(default_var_name, value)
  736. elif value is None:
  737. return self.__set_react_attribute(default_var_name, "null")
  738. elif var_type == PropertyType.lov_value:
  739. # Done by _get_adapter
  740. return self
  741. elif isclass(var_type) and issubclass(var_type, _TaipyBase):
  742. return self.__set_default_value(var_name, t.cast(t.Callable, var_type)(value, "").get())
  743. else:
  744. return self.__set_json_attribute(default_var_name, value)
  745. def __set_update_var_name(self, hash_name: str):
  746. return self.set_attribute("updateVarName", hash_name)
  747. def set_value_and_default(
  748. self,
  749. var_name: t.Optional[str] = None,
  750. with_update=True,
  751. with_default=True,
  752. native_type=False,
  753. var_type: t.Optional[t.Union[PropertyType, t.Type[_TaipyBase]]] = None,
  754. default_val: t.Any = None,
  755. ):
  756. """
  757. TODO-undocumented
  758. Sets the value associated with the default property.
  759. Arguments:
  760. var_name (str): The property name (default to default property name).
  761. with_update (optional(bool)): Should the attribute be dynamic (default True).
  762. with_default (optional(bool)): Should a default attribute be set (default True).
  763. native_type (optional(bool)): If var_type == dynamic_number, parse the value to number.
  764. var_type (optional(PropertyType)): the property type (default to string).
  765. default_val (optional(Any)): the default value.
  766. """
  767. var_name = self.__default_property_name if var_name is None else var_name
  768. if var_type == PropertyType.slider_value or var_type == PropertyType.toggle_value:
  769. if self.__attributes.get("lov"):
  770. var_type = PropertyType.lov_value
  771. native_type = False
  772. elif var_type == PropertyType.toggle_value:
  773. self.__set_react_attribute(_to_camel_case("is_switch"), True)
  774. var_type = PropertyType.dynamic_boolean
  775. native_type = True
  776. else:
  777. var_type = (
  778. PropertyType.dynamic_lo_numbers
  779. if isinstance(self.__attributes.get("value"), list)
  780. else PropertyType.dynamic_number
  781. )
  782. native_type = True
  783. if var_type == PropertyType.dynamic_boolean:
  784. return self.set_attributes([(var_name, var_type, bool(default_val), with_update)])
  785. if hash_name := self.__hashes.get(var_name):
  786. hash_name = self.__get_typed_hash_name(hash_name, var_type)
  787. self.__set_react_attribute(
  788. _to_camel_case(var_name),
  789. _get_client_var_name(hash_name),
  790. )
  791. if with_update:
  792. self.__set_update_var_name(hash_name)
  793. if with_default:
  794. if native_type:
  795. val = self.__attributes.get(var_name)
  796. if native_type and isinstance(val, str):
  797. with contextlib.suppress(Exception):
  798. val = float(val)
  799. self.__set_default_value(var_name, val, native_type=native_type)
  800. else:
  801. self.__set_default_value(var_name, var_type=var_type)
  802. else:
  803. if var_type == PropertyType.data and (self.__control_type != "chart" or "figure" not in self.__attributes):
  804. _warn(f"{self.__control_type}.{var_name} property should be bound.")
  805. value = self.__attributes.get(var_name)
  806. if value is not None:
  807. if native_type:
  808. if isinstance(value, str):
  809. with contextlib.suppress(Exception):
  810. value = float(value)
  811. if isinstance(value, (int, float)):
  812. return self.__set_react_attribute(_to_camel_case(var_name), value)
  813. if isinstance(value, (datetime, date, time)):
  814. value = _date_to_string(value)
  815. self.set_attribute(_to_camel_case(var_name), value)
  816. return self
  817. def _set_labels(self, var_name: str = "labels"):
  818. if value := self.__attributes.get(var_name):
  819. if _is_true(value):
  820. return self.__set_react_attribute(_to_camel_case(var_name), True)
  821. elif isinstance(value, (dict, _MapDict)):
  822. return self.set_dict_attribute(var_name)
  823. return self
  824. def _set_partial(self):
  825. if self.__control_type not in _Builder.__BLOCK_CONTROLS:
  826. return self
  827. if partial := self.__attributes.get("partial"):
  828. if self.__attributes.get("page"):
  829. _warn(f"{self.__element_name} control: page and partial should not be both defined.")
  830. if isinstance(partial, Partial):
  831. self.__attributes["page"] = partial._route
  832. self.__set_react_attribute("partial", partial._route)
  833. self.__set_react_attribute("defaultPartial", True)
  834. return self
  835. def _set_propagate(self):
  836. val = self.__get_boolean_attribute("propagate", t.cast(bool, self.__gui._config.config.get("propagate")))
  837. return self if val else self.set_boolean_attribute("propagate", False)
  838. def __set_refresh_on_update(self):
  839. if self.__update_vars:
  840. self.set_attribute("updateVars", ";".join(self.__update_vars))
  841. return self
  842. def _set_table_pagesize_options(self, default_size=None):
  843. if default_size is None:
  844. default_size = [50, 100, 500]
  845. page_size_options = self.__attributes.get("page_size_options", default_size)
  846. if isinstance(page_size_options, str):
  847. try:
  848. page_size_options = [int(s.strip()) for s in page_size_options.split(";")]
  849. except Exception as e:
  850. _warn(f"{self.__element_name}: page_size_options value is invalid ({page_size_options})", e)
  851. if isinstance(page_size_options, list):
  852. self.__set_json_attribute("pageSizeOptions", page_size_options)
  853. else:
  854. _warn(f"{self.__element_name}: page_size_options should be a list.")
  855. return self
  856. def _set_input_type(self, type_name: str, allow_password=False):
  857. if allow_password and self.__get_boolean_attribute("password", False):
  858. return self.set_attribute("type", "password")
  859. return self.set_attribute("type", self.__attributes.get("type", type_name))
  860. def _set_kind(self):
  861. if self.__attributes.get("theme", False):
  862. self.set_attribute("mode", "theme")
  863. return self
  864. def __get_typed_hash_name(
  865. self, hash_name: str, var_type: t.Optional[t.Union[PropertyType, t.Type[_TaipyBase]]]
  866. ) -> str:
  867. if taipy_type := _get_taipy_type(var_type):
  868. expr = self.__gui._get_expr_from_hash(hash_name)
  869. hash_name = self.__gui._evaluate_bind_holder(t.cast(t.Type[_TaipyBase], taipy_type), expr)
  870. return hash_name
  871. def __set_dynamic_bool_attribute(self, name: str, def_val: t.Any, with_update: bool, update_main=True):
  872. hash_name = self.__hashes.get(name)
  873. val = self.__get_boolean_attribute(name, def_val)
  874. default_name = f"default_{name}" if hash_name is not None else name
  875. if val != def_val:
  876. self.set_boolean_attribute(default_name, val)
  877. if hash_name is not None:
  878. hash_name = self.__get_typed_hash_name(hash_name, PropertyType.dynamic_boolean)
  879. self.__set_react_attribute(_to_camel_case(name), _get_client_var_name(hash_name))
  880. if with_update:
  881. if update_main:
  882. self.__set_update_var_name(hash_name)
  883. else:
  884. self.__update_vars.append(f"{_to_camel_case(name)}={hash_name}")
  885. def __set_dynamic_property_without_default(
  886. self, name: str, property_type: PropertyType, optional: t.Optional[bool] = False
  887. ):
  888. hash_name = self.__hashes.get(name)
  889. if hash_name is None:
  890. if not optional:
  891. _warn(f"{self.__element_name}.{name} should be bound.")
  892. else:
  893. hash_name = self.__get_typed_hash_name(hash_name, property_type)
  894. self.__update_vars.append(f"{_to_camel_case(name)}={hash_name}")
  895. self.__set_react_attribute(_to_camel_case(name), _get_client_var_name(hash_name))
  896. return self
  897. def __set_html_content(self, name: str, property_name: str, property_type: PropertyType):
  898. hash_name = self.__hashes.get(name)
  899. if not hash_name:
  900. return self
  901. front_var = self.__get_typed_hash_name(hash_name, property_type)
  902. self.set_attribute(
  903. _to_camel_case(f"default_{property_name}"),
  904. self.__gui._get_user_content_url(
  905. None,
  906. {
  907. "variable_name": front_var,
  908. self.__gui._HTML_CONTENT_KEY: str(_time.time()),
  909. },
  910. ),
  911. )
  912. return self.__set_react_attribute(_to_camel_case(property_name), _get_client_var_name(front_var))
  913. def set_attributes(self, attributes: t.List[tuple]): # noqa: C901
  914. """
  915. TODO-undocumented
  916. Sets the attributes from the property with type and default value.
  917. Arguments:
  918. attributes (list(tuple)): The list of attributes as (property name, property type, default value).
  919. """
  920. attributes.append(("id",)) # Every element should have an id attribute
  921. for attr in attributes:
  922. if not isinstance(attr, tuple):
  923. attr = (attr,)
  924. var_type = _get_tuple_val(attr, 1, PropertyType.string)
  925. if var_type == PropertyType.to_json:
  926. var_type = _TaipyToJson
  927. if var_type == PropertyType.boolean:
  928. def_val = _get_tuple_val(attr, 2, False)
  929. if isinstance(def_val, bool) or self.__attributes.get(attr[0], None) is not None:
  930. val = self.__get_boolean_attribute(attr[0], def_val)
  931. if val != def_val:
  932. self.set_boolean_attribute(attr[0], val)
  933. elif var_type == PropertyType.dynamic_boolean:
  934. self.__set_dynamic_bool_attribute(
  935. attr[0],
  936. _get_tuple_val(attr, 2, False),
  937. _get_tuple_val(attr, 3, False),
  938. _get_tuple_val(attr, 4, True),
  939. )
  940. elif var_type == PropertyType.number:
  941. self.set_number_attribute(attr[0], _get_tuple_val(attr, 2, None))
  942. elif var_type == PropertyType.dynamic_number:
  943. self.__set_dynamic_number_attribute(attr[0], _get_tuple_val(attr, 2, None))
  944. elif var_type == PropertyType.string:
  945. self.__set_string_attribute(attr[0], _get_tuple_val(attr, 2, None), _get_tuple_val(attr, 3, True))
  946. elif var_type == PropertyType.dynamic_string:
  947. self.__set_dynamic_string_attribute(
  948. attr[0], _get_tuple_val(attr, 2, None), _get_tuple_val(attr, 3, False)
  949. )
  950. elif var_type == PropertyType.string_list:
  951. self.__update_vars.extend(
  952. self.__set_list_attribute(
  953. attr[0],
  954. self.__hashes.get(attr[0]),
  955. self.__attributes.get(attr[0]),
  956. str,
  957. False,
  958. _get_tuple_val(attr, 2, None),
  959. )
  960. )
  961. elif var_type == PropertyType.function:
  962. self.__set_function_attribute(attr[0], _get_tuple_val(attr, 2, None), _get_tuple_val(attr, 3, True))
  963. elif var_type == PropertyType.react:
  964. prop_name = _to_camel_case(attr[0])
  965. if hash_name := self.__hashes.get(attr[0]):
  966. self.__update_vars.append(f"{prop_name}={hash_name}")
  967. self.__set_react_attribute(prop_name, hash_name)
  968. else:
  969. self.__set_react_attribute(prop_name, self.__attributes.get(attr[0], _get_tuple_val(attr, 2, None)))
  970. elif var_type == PropertyType.broadcast:
  971. self.__set_react_attribute(
  972. _to_camel_case(attr[0]), _get_broadcast_var_name(_get_tuple_val(attr, 2, None))
  973. )
  974. elif var_type == PropertyType.string_or_number:
  975. self.__set_string_or_number_attribute(attr[0], _get_tuple_val(attr, 2, None))
  976. elif var_type == PropertyType.dict:
  977. self.set_dict_attribute(attr[0], _get_tuple_val(attr, 2, None))
  978. elif var_type == PropertyType.dynamic_dict:
  979. self.set_dynamic_dict_attribute(attr[0], _get_tuple_val(attr, 2, None))
  980. elif var_type == PropertyType.dynamic_list:
  981. self.__set_dynamic_string_list(attr[0], _get_tuple_val(attr, 2, None))
  982. elif var_type == PropertyType.boolean_or_list:
  983. if _is_boolean(self.__attributes.get(attr[0])):
  984. self.__set_dynamic_bool_attribute(attr[0], _get_tuple_val(attr, 2, False), True, update_main=False)
  985. else:
  986. self.__set_dynamic_string_list(attr[0], _get_tuple_val(attr, 2, None))
  987. elif var_type == PropertyType.dynamic_date:
  988. self.__set_dynamic_date_attribute(attr[0], _get_tuple_val(attr, 2, None))
  989. elif var_type == PropertyType.data:
  990. self.__set_dynamic_property_without_default(attr[0], t.cast(PropertyType, var_type))
  991. elif (
  992. var_type == PropertyType.lov
  993. or var_type == PropertyType.single_lov
  994. or var_type == PropertyType.lov_no_default
  995. ):
  996. self._get_lov_adapter(
  997. attr[0],
  998. multi_selection=var_type != PropertyType.single_lov,
  999. with_default=var_type != PropertyType.lov_no_default,
  1000. )
  1001. elif var_type == PropertyType.lov_value:
  1002. self.__set_dynamic_property_without_default(
  1003. attr[0], t.cast(PropertyType, var_type), _get_tuple_val(attr, 2, None) == "optional"
  1004. )
  1005. elif var_type == PropertyType.toHtmlContent:
  1006. self.__set_html_content(attr[0], "page", t.cast(PropertyType, var_type))
  1007. elif isclass(var_type) and issubclass(var_type, _TaipyBase):
  1008. prop_name = _to_camel_case(attr[0])
  1009. if hash_name := self.__hashes.get(attr[0]):
  1010. expr = self.__gui._get_expr_from_hash(hash_name)
  1011. hash_name = self.__gui._evaluate_bind_holder(var_type, expr)
  1012. self.__update_vars.append(f"{prop_name}={hash_name}")
  1013. self.__set_react_attribute(prop_name, hash_name)
  1014. else:
  1015. val = self.__attributes.get(attr[0])
  1016. self.set_attribute(
  1017. prop_name, var_type(_get_tuple_val(attr, 2, None) if val is None else val, "").get()
  1018. )
  1019. self.__set_refresh_on_update()
  1020. return self
  1021. def set_attribute(self, name: str, value: t.Any):
  1022. """
  1023. TODO-undocumented
  1024. Sets an attribute.
  1025. Arguments:
  1026. name (str): The name of the attribute.
  1027. value (Any): The value of the attribute (must be json serializable).
  1028. """
  1029. if value is not None:
  1030. self.el.set(name, value)
  1031. return self
  1032. def get_element(self):
  1033. """
  1034. TODO-undocumented
  1035. Returns the xml.etree.ElementTree.Element
  1036. """
  1037. return self.el
  1038. def _build_to_string(self):
  1039. el_str = str(etree.tostring(self.el, encoding="utf8").decode("utf8"))
  1040. el_str = el_str.replace("<?xml version='1.0' encoding='utf8'?>\n", "")
  1041. el_str = el_str.replace("/>", ">")
  1042. el_str = el_str.replace(">style</TaipyStyle>", "/>")
  1043. return el_str, self.__element_name