data_accessor.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. # Copyright 2021-2025 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 inspect
  12. import typing as t
  13. from abc import ABC, abstractmethod
  14. from .._warnings import _warn
  15. from ..utils import _TaipyData
  16. from .data_format import _DataFormat
  17. if t.TYPE_CHECKING:
  18. from ..gui import Gui
  19. class _DataAccessor(ABC):
  20. _WS_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
  21. def __init__(self, gui: "Gui") -> None:
  22. self._gui = gui
  23. @staticmethod
  24. @abstractmethod
  25. def get_supported_classes() -> t.List[t.Type]:
  26. pass
  27. @abstractmethod
  28. def get_data(
  29. self, var_name: str, value: t.Any, payload: t.Dict[str, t.Any], data_format: _DataFormat
  30. ) -> t.Dict[str, t.Any]:
  31. pass
  32. @abstractmethod
  33. def get_cols_description(self, var_name: str, value: t.Any) -> t.Dict[str, t.Dict[str, str]]:
  34. pass
  35. @abstractmethod
  36. def to_pandas(self, value: t.Any) -> t.Union[t.List[t.Any], t.Any]:
  37. pass
  38. @abstractmethod
  39. def on_edit(self, value: t.Any, payload: t.Dict[str, t.Any]) -> t.Optional[t.Any]:
  40. pass
  41. @abstractmethod
  42. def on_delete(self, value: t.Any, payload: t.Dict[str, t.Any]) -> t.Optional[t.Any]:
  43. pass
  44. @abstractmethod
  45. def on_add(
  46. self, value: t.Any, payload: t.Dict[str, t.Any], new_row: t.Optional[t.List[t.Any]] = None
  47. ) -> t.Optional[t.Any]:
  48. pass
  49. @abstractmethod
  50. def to_csv(self, var_name: str, value: t.Any) -> t.Optional[str]:
  51. pass
  52. class _InvalidDataAccessor(_DataAccessor):
  53. @staticmethod
  54. def get_supported_classes() -> t.List[t.Type]:
  55. return []
  56. def get_data(
  57. self, var_name: str, value: t.Any, payload: t.Dict[str, t.Any], data_format: _DataFormat
  58. ) -> t.Dict[str, t.Any]:
  59. return {}
  60. def get_cols_description(self, var_name: str, value: t.Any) -> t.Dict[str, t.Dict[str, str]]:
  61. return {}
  62. def to_pandas(self, value: t.Any) -> t.Union[t.List[t.Any], t.Any]:
  63. return None
  64. def on_edit(self, value: t.Any, payload: t.Dict[str, t.Any]):
  65. return None
  66. def on_delete(self, value: t.Any, payload: t.Dict[str, t.Any]):
  67. return None
  68. def on_add(self, value: t.Any, payload: t.Dict[str, t.Any], new_row: t.Optional[t.List[t.Any]] = None):
  69. return None
  70. def to_csv(self, var_name: str, value: t.Any):
  71. return None
  72. class _DataAccessors(object):
  73. def __init__(self, gui: "Gui") -> None:
  74. self.__access_4_type: t.Dict[t.Type, _DataAccessor] = {}
  75. self.__invalid_data_accessor = _InvalidDataAccessor(gui)
  76. self.__data_format = _DataFormat.JSON
  77. self.__gui = gui
  78. from .array_dict_data_accessor import _ArrayDictDataAccessor
  79. from .numpy_data_accessor import _NumpyDataAccessor
  80. from .pandas_data_accessor import _PandasDataAccessor
  81. self._register(_PandasDataAccessor)
  82. self._register(_ArrayDictDataAccessor)
  83. self._register(_NumpyDataAccessor)
  84. def _register(self, cls: t.Type[_DataAccessor], force: t.Optional[bool] = False) -> None:
  85. """Register a new DataAccessor type."""
  86. if not inspect.isclass(cls):
  87. raise AttributeError("The argument of 'DataAccessors.register()' should be a class")
  88. if not issubclass(cls, _DataAccessor):
  89. raise TypeError(f"Class {cls.__name__} is not a subclass of DataAccessor")
  90. classes = cls.get_supported_classes()
  91. if not classes:
  92. raise TypeError(f"{cls.__name__}.get_supported_classes() returned an invalid value")
  93. # check existence
  94. inst: t.Optional[_DataAccessor] = None
  95. if force:
  96. for cl in classes:
  97. inst = self.__access_4_type.get(cl)
  98. if inst:
  99. break
  100. if inst is None:
  101. try:
  102. inst = cls(self.__gui)
  103. except Exception as e:
  104. raise TypeError(f"Class {cls.__name__} cannot be instantiated") from e
  105. if inst:
  106. for cl in classes:
  107. self.__access_4_type[cl] = inst
  108. def _unregister(self, cls: t.Type[_DataAccessor]) -> None:
  109. """Unregister a DataAccessor type."""
  110. if cls in self.__access_4_type:
  111. del self.__access_4_type[cls]
  112. def _get_instance(self, value: _TaipyData) -> _DataAccessor:
  113. value = value.get() if isinstance(value, _TaipyData) else value
  114. access = self.__access_4_type.get(type(value))
  115. if access is None:
  116. if value is not None:
  117. converted_value = type(self.__gui)._convert_unsupported_data(value) # type: ignore[attr-defined]
  118. if converted_value is not None:
  119. access = self.__access_4_type.get(type(converted_value))
  120. if access is not None:
  121. return access
  122. _warn(f"Can't find Data Accessor for type {str(type(value))}.")
  123. return self.__invalid_data_accessor
  124. return access
  125. def get_data(self, var_name: str, value: _TaipyData, payload: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
  126. return self._get_instance(value).get_data(var_name, value.get(), payload, self.__data_format)
  127. def get_cols_description(self, var_name: str, value: _TaipyData) -> t.Dict[str, t.Dict[str, str]]:
  128. return self._get_instance(value).get_cols_description(var_name, value.get())
  129. def set_data_format(self, data_format: _DataFormat):
  130. self.__data_format = data_format
  131. def get_dataframe(self, value: t.Any):
  132. return self._get_instance(value).to_pandas(value)
  133. def on_edit(self, value: t.Any, payload: t.Dict[str, t.Any]):
  134. return self._get_instance(value).on_edit(value, payload)
  135. def on_delete(self, value: t.Any, payload: t.Dict[str, t.Any]):
  136. return self._get_instance(value).on_delete(value, payload)
  137. def on_add(self, value: t.Any, payload: t.Dict[str, t.Any], new_row: t.Optional[t.List[t.Any]] = None):
  138. return self._get_instance(value).on_add(value, payload, new_row)
  139. def to_csv(self, var_name: str, value: t.Any):
  140. return self._get_instance(value).to_csv(var_name, value.get())
  141. def to_pandas(self, value: t.Any):
  142. return self._get_instance(value).to_pandas(value.get())