server.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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 os
  12. import pathlib
  13. import re
  14. import typing as t
  15. from abc import ABC, abstractmethod
  16. from contextlib import contextmanager
  17. from random import choices, randint
  18. from gitignore_parser import parse_gitignore
  19. import __main__
  20. from ..utils import _is_port_open, _RuntimeManager
  21. from ..utils._css import get_style
  22. from ..utils.singleton import _Singleton
  23. class _Server(ABC):
  24. _RE_OPENING_CURLY = re.compile(r"([^\"])(\{)")
  25. _RE_CLOSING_CURLY = re.compile(r"(\})([^\"])")
  26. _OPENING_CURLY = r"\1{"
  27. _CLOSING_CURLY = r"}\2"
  28. _RESOURCE_HANDLER_ARG = "tprh"
  29. def _is_ignored(self, file_path: str) -> bool:
  30. if not hasattr(self, "_ignore_matches"):
  31. __IGNORE_FILE = ".taipyignore"
  32. ignore_file = (
  33. (pathlib.Path(__main__.__file__).parent / __IGNORE_FILE) if hasattr(__main__, "__file__") else None
  34. )
  35. if not ignore_file or not ignore_file.is_file():
  36. ignore_file = pathlib.Path(self._gui._root_dir) / __IGNORE_FILE # type: ignore[attr-defined]
  37. self._ignore_matches = (
  38. parse_gitignore(ignore_file) if ignore_file.is_file() and os.access(ignore_file, os.R_OK) else None
  39. )
  40. if callable(self._ignore_matches):
  41. return self._ignore_matches(file_path)
  42. return False
  43. def _get_random_port(
  44. self, port_auto_ranges: t.Optional[t.List[t.Union[int, t.Tuple[int, int]]]] = None
  45. ): # pragma: no cover
  46. if not hasattr(self, "_host"):
  47. raise RuntimeError("Server host is not set")
  48. port_auto_ranges = port_auto_ranges or [(49152, 65535)]
  49. random_weights = [1 if isinstance(r, int) else abs(r[1] - r[0]) + 1 for r in port_auto_ranges]
  50. while True:
  51. random_choices = [
  52. r if isinstance(r, int) else randint(min(r[0], r[1]), max(r[0], r[1])) for r in port_auto_ranges
  53. ]
  54. port = choices(random_choices, weights=random_weights)[0]
  55. if port not in _RuntimeManager().get_used_port() and not _is_port_open(self._host, port): # type: ignore
  56. return port
  57. @abstractmethod
  58. def get_server_instance(self):
  59. raise NotImplementedError
  60. @abstractmethod
  61. def get_port(self) -> int:
  62. raise NotImplementedError
  63. @abstractmethod
  64. def send_ws_message(self, *args, **kwargs):
  65. raise NotImplementedError
  66. @abstractmethod
  67. def direct_render_json(self, data):
  68. raise NotImplementedError
  69. @abstractmethod
  70. def save_uploaded_file(self, file, path):
  71. raise NotImplementedError
  72. def render(self, html_fragment, script_paths, style, head, context):
  73. template_str = _Server._RE_OPENING_CURLY.sub(_Server._OPENING_CURLY, html_fragment)
  74. template_str = _Server._RE_CLOSING_CURLY.sub(_Server._CLOSING_CURLY, template_str)
  75. template_str = template_str.replace('"{!', "{")
  76. template_str = template_str.replace('!}"', "}")
  77. style = get_style(style)
  78. return self.direct_render_json(
  79. {
  80. "jsx": template_str,
  81. "style": (style + os.linesep) if style else "",
  82. "head": head or [],
  83. "context": context or self._gui._get_default_module_name(), # type: ignore[attr-defined]
  84. "scriptPaths": script_paths,
  85. }
  86. )
  87. @abstractmethod
  88. def _get_default_handler(
  89. self,
  90. static_folder: str,
  91. template_folder: str,
  92. title: str,
  93. favicon: str,
  94. root_margin: str,
  95. scripts: t.List[str],
  96. styles: t.List[str],
  97. version: str,
  98. client_config: t.Dict[str, t.Any],
  99. watermark: t.Optional[str],
  100. css_vars: str,
  101. base_url: str,
  102. ):
  103. raise NotImplementedError
  104. @abstractmethod
  105. def test_client(self):
  106. raise NotImplementedError
  107. @abstractmethod
  108. @contextmanager
  109. def test_request_context(self, path, data):
  110. raise NotImplementedError
  111. @abstractmethod
  112. def run(
  113. self,
  114. host,
  115. port,
  116. client_url,
  117. debug,
  118. use_reloader,
  119. server_log,
  120. run_in_thread,
  121. allow_unsafe_werkzeug,
  122. notebook_proxy,
  123. port_auto_ranges,
  124. ):
  125. raise NotImplementedError
  126. @abstractmethod
  127. def is_running(self):
  128. raise NotImplementedError
  129. @abstractmethod
  130. def stop_thread(self):
  131. raise NotImplementedError
  132. ServerFrameworks = t.Literal["flask", "fastapi"]
  133. class ServerManager(object, metaclass=_Singleton):
  134. def __init__(self):
  135. self._server: t.Optional[_Server] = None
  136. self._framework: t.Optional[ServerFrameworks] = None
  137. def set_server(self, server: _Server):
  138. self._server = server
  139. def get_server(self) -> _Server:
  140. if self._server is None:
  141. raise RuntimeError("Server is not set")
  142. return self._server
  143. def set_server_type(self, framework: ServerFrameworks):
  144. self._framework = framework
  145. def get_server_type(self) -> ServerFrameworks:
  146. if self._framework is None:
  147. raise RuntimeError("Server type is not set")
  148. return self._framework