match.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. """rx.match."""
  2. import textwrap
  3. from typing import Any, cast
  4. from typing_extensions import Unpack
  5. from reflex.components.base import Fragment
  6. from reflex.components.component import BaseComponent, Component
  7. from reflex.utils import types
  8. from reflex.utils.exceptions import MatchTypeError
  9. from reflex.vars.base import VAR_TYPE, Var
  10. from reflex.vars.number import MatchOperation
  11. CASE_TYPE = tuple[Unpack[tuple[Any, ...]], Var[VAR_TYPE] | VAR_TYPE]
  12. def _process_match_cases(cases: tuple[CASE_TYPE[VAR_TYPE], ...]):
  13. """Process the individual match cases.
  14. Args:
  15. cases: The match cases.
  16. Raises:
  17. ValueError: If the default case is not the last case or the tuple elements are less than 2.
  18. """
  19. for case in cases:
  20. if not isinstance(case, tuple):
  21. raise ValueError(
  22. "rx.match should have tuples of cases and a default case as the last argument."
  23. )
  24. # There should be at least two elements in a case tuple(a condition and return value)
  25. if len(case) < 2:
  26. raise ValueError(
  27. "A case tuple should have at least a match case element and a return value."
  28. )
  29. def _validate_return_types(match_cases: tuple[CASE_TYPE[VAR_TYPE], ...]) -> None:
  30. """Validate that match cases have the same return types.
  31. Args:
  32. match_cases: The match cases.
  33. Raises:
  34. MatchTypeError: If the return types of cases are different.
  35. """
  36. first_case_return = match_cases[0][-1]
  37. return_type = type(first_case_return)
  38. if types._isinstance(first_case_return, BaseComponent):
  39. return_type = BaseComponent
  40. elif types._isinstance(first_case_return, Var):
  41. return_type = Var
  42. for index, case in enumerate(match_cases):
  43. if not (
  44. types._issubclass(type(case[-1]), return_type)
  45. or (
  46. isinstance(case[-1], Var)
  47. and types.typehint_issubclass(case[-1]._var_type, return_type)
  48. )
  49. ):
  50. raise MatchTypeError(
  51. f"Match cases should have the same return types. Case {index} with return "
  52. f"value `{case[-1]._js_expr if isinstance(case[-1], Var) else textwrap.shorten(str(case[-1]), width=250)}`"
  53. f" of type {(type(case[-1]) if not isinstance(case[-1], Var) else case[-1]._var_type)!r} is not {return_type}"
  54. )
  55. def _create_match_var(
  56. match_cond_var: Var,
  57. match_cases: tuple[CASE_TYPE[VAR_TYPE], ...],
  58. default: VAR_TYPE | Var[VAR_TYPE],
  59. ) -> Var[VAR_TYPE]:
  60. """Create the match var.
  61. Args:
  62. match_cond_var: The match condition var.
  63. match_cases: The match cases.
  64. default: The default case.
  65. Returns:
  66. The match var.
  67. """
  68. return MatchOperation.create(match_cond_var, match_cases, default)
  69. def match(
  70. cond: Any,
  71. *cases: Unpack[
  72. tuple[Unpack[tuple[CASE_TYPE[VAR_TYPE], ...]], Var[VAR_TYPE] | VAR_TYPE]
  73. ],
  74. ) -> Var[VAR_TYPE]:
  75. """Create a match var.
  76. Args:
  77. cond: The condition to match.
  78. cases: The match cases. Each case should be a tuple with the first elements as the match case and the last element as the return value. The last argument should be the default case.
  79. Returns:
  80. The match var.
  81. Raises:
  82. ValueError: If the default case is not the last case or the tuple elements are less than 2.
  83. """
  84. default = None
  85. if len([case for case in cases if not isinstance(case, tuple)]) > 1:
  86. raise ValueError("rx.match can only have one default case.")
  87. if not cases:
  88. raise ValueError("rx.match should have at least one case.")
  89. # Get the default case which should be the last non-tuple arg
  90. if not isinstance(cases[-1], tuple):
  91. default = cases[-1]
  92. actual_cases = cases[:-1]
  93. else:
  94. actual_cases = cast(tuple[CASE_TYPE[VAR_TYPE], ...], cases)
  95. _process_match_cases(actual_cases)
  96. _validate_return_types(actual_cases)
  97. if default is None and any(
  98. not (
  99. isinstance((return_type := case[-1]), Component)
  100. or (
  101. isinstance(return_type, Var)
  102. and types.typehint_issubclass(return_type._var_type, Component)
  103. )
  104. )
  105. for case in actual_cases
  106. ):
  107. raise ValueError(
  108. "For cases with return types as Vars, a default case must be provided"
  109. )
  110. elif default is None:
  111. default = Fragment.create()
  112. default = cast(Var[VAR_TYPE] | VAR_TYPE, default)
  113. return _create_match_var(
  114. cond,
  115. actual_cases,
  116. default,
  117. )