__init__.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. r"""
  2. .. autofunction:: get_info
  3. .. autofunction:: run_async
  4. .. autofunction:: run_asyncio_coroutine
  5. .. autofunction:: register_thread
  6. .. autofunction:: defer_call
  7. .. autofunction:: hold
  8. .. autoclass:: pywebio.session.coroutinebased.TaskHandle
  9. :members:
  10. """
  11. import threading
  12. from functools import wraps
  13. from .base import AbstractSession
  14. from .coroutinebased import CoroutineBasedSession
  15. from .threadbased import ThreadBasedSession, ScriptModeSession
  16. from ..exceptions import SessionNotFoundException
  17. from ..utils import iscoroutinefunction, isgeneratorfunction, run_as_function, to_coroutine
  18. # 当前进程中正在使用的会话实现的列表
  19. _active_session_cls = []
  20. __all__ = ['run_async', 'run_asyncio_coroutine', 'register_thread', 'hold', 'defer_call', 'get_info']
  21. def register_session_implement_for_target(target_func):
  22. """根据target_func函数类型注册会话实现,并返回会话实现"""
  23. if iscoroutinefunction(target_func) or isgeneratorfunction(target_func):
  24. cls = CoroutineBasedSession
  25. else:
  26. cls = ThreadBasedSession
  27. if ScriptModeSession in _active_session_cls:
  28. raise RuntimeError("Already in script mode, can't start server")
  29. if cls not in _active_session_cls:
  30. _active_session_cls.append(cls)
  31. return cls
  32. def get_session_implement():
  33. """获取当前会话实现。仅供内部实现使用。应在会话上下文中调用"""
  34. if not _active_session_cls:
  35. _active_session_cls.append(ScriptModeSession)
  36. _start_script_mode_server()
  37. # 当前正在使用的会话实现只有一个
  38. if len(_active_session_cls) == 1:
  39. return _active_session_cls[0]
  40. # 当前有多个正在使用的会话实现
  41. for cls in _active_session_cls:
  42. try:
  43. cls.get_current_session()
  44. return cls
  45. except SessionNotFoundException:
  46. pass
  47. raise SessionNotFoundException
  48. def _start_script_mode_server():
  49. from ..platform.tornado import start_server_in_current_thread_session
  50. start_server_in_current_thread_session()
  51. def get_current_session() -> "AbstractSession":
  52. return get_session_implement().get_current_session()
  53. def get_current_task_id():
  54. return get_session_implement().get_current_task_id()
  55. def check_session_impl(session_type):
  56. def decorator(func):
  57. """装饰器:在函数调用前检查当前会话实现是否满足要求"""
  58. @wraps(func)
  59. def inner(*args, **kwargs):
  60. curr_impl = get_session_implement()
  61. # Check if 'now_impl' is a derived from session_type or is the same class
  62. if not issubclass(curr_impl, session_type):
  63. func_name = getattr(func, '__name__', str(func))
  64. require = getattr(session_type, '__name__', str(session_type))
  65. curr = getattr(curr_impl, '__name__', str(curr_impl))
  66. raise RuntimeError("Only can invoke `{func_name:s}` in {require:s} context."
  67. " You are now in {curr:s} context".format(func_name=func_name, require=require,
  68. curr=curr))
  69. return func(*args, **kwargs)
  70. return inner
  71. return decorator
  72. def chose_impl(gen_func):
  73. """根据当前会话实现来将 gen_func 转化为协程对象或直接以函数运行"""
  74. @wraps(gen_func)
  75. def inner(*args, **kwargs):
  76. gen = gen_func(*args, **kwargs)
  77. if get_session_implement() == CoroutineBasedSession:
  78. return to_coroutine(gen)
  79. else:
  80. return run_as_function(gen)
  81. return inner
  82. @chose_impl
  83. def next_client_event():
  84. res = yield get_current_session().next_client_event()
  85. return res
  86. @chose_impl
  87. def hold():
  88. """保持会话,直到用户关闭浏览器,
  89. 此时函数抛出 `SessionClosedException <pywebio.exceptions.SessionClosedException>` 异常。
  90. 注意⚠️:在 :ref:`基于协程 <coroutine_based_session>` 的会话上下文中,需要使用 ``await hold()`` 语法来进行调用。
  91. """
  92. while True:
  93. yield next_client_event()
  94. @check_session_impl(CoroutineBasedSession)
  95. def run_async(coro_obj):
  96. """异步运行协程对象。协程中依然可以调用 PyWebIO 交互函数。 仅能在 :ref:`基于协程 <coroutine_based_session>` 的会话上下文中调用
  97. :param coro_obj: 协程对象
  98. :return: An instance of `TaskHandle <pywebio.session.coroutinebased.TaskHandle>` is returned, which can be used later to close the task.
  99. """
  100. return get_current_session().run_async(coro_obj)
  101. @check_session_impl(CoroutineBasedSession)
  102. async def run_asyncio_coroutine(coro_obj):
  103. """若会话线程和运行事件的线程不是同一个线程,需要用 run_asyncio_coroutine 来运行asyncio中的协程。 仅能在 :ref:`基于协程 <coroutine_based_session>` 的会话上下文中调用。
  104. :param coro_obj: 协程对象
  105. """
  106. return await get_current_session().run_asyncio_coroutine(coro_obj)
  107. @check_session_impl(ThreadBasedSession)
  108. def register_thread(thread: threading.Thread):
  109. """注册线程,以便在线程内调用 PyWebIO 交互函数。仅能在默认的基于线程的会话上下文中调用。
  110. :param threading.Thread thread: 线程对象
  111. """
  112. return get_current_session().register_thread(thread)
  113. def defer_call(func):
  114. """设置会话结束时调用的函数。无论是用户主动关闭会话还是任务结束会话关闭,设置的函数都会被运行。
  115. 可以用于资源清理等工作。
  116. 在会话中可以多次调用 `defer_call()` ,会话结束后将会顺序执行设置的函数。
  117. `defer_call` 同样支持以装饰器的方式使用::
  118. @defer_call
  119. def cleanup():
  120. pass
  121. :param func: 话结束时调用的函数
  122. .. attention:: 通过 `defer_call()` 设置的函数被调用时会话已经关闭,所以在函数体内不可以调用 PyWebIO 的交互函数
  123. """
  124. get_current_session().defer_call(func)
  125. return func
  126. def get_info():
  127. """ 获取当前会话的相关信息
  128. :return: 表示会话信息的对象,属性有:
  129. * ``user_agent`` : 表示用户浏览器信息的对象,属性有
  130. * ``is_mobile`` (bool): 用户使用的设备是否为手机 (比如 iPhone, Android phones, Blackberry, Windows Phone 等设备)
  131. * ``is_tablet`` (bool): 用户使用的设备是否为平板 (比如 iPad, Kindle Fire, Nexus 7 等设备)
  132. * ``is_pc`` (bool): 用户使用的设备是否为桌面电脑 (比如运行 Windows, OS X, Linux 的设备)
  133. * ``is_touch_capable`` (bool): 用户使用的设备是否支持触控
  134. * ``browser.family`` (str): 浏览器家族. 比如 'Mobile Safari'
  135. * ``browser.version`` (tuple): 浏览器版本元组. 比如 (5, 1)
  136. * ``browser.version_string`` (str): 浏览器版本字符串. 比如 '5.1'
  137. * ``os.family`` (str): 操作系统家族. 比如 'iOS'
  138. * ``os.version`` (tuple): 操作系统版本元组. 比如 (5, 1)
  139. * ``os.version_string`` (str): 操作系统版本字符串. 比如 '5.1'
  140. * ``device.family`` (str): 设备家族. 比如 'iPhone'
  141. * ``device.brand`` (str): 设备品牌. 比如 'Apple'
  142. * ``device.model`` (str): 设备幸好. 比如 'iPhone'
  143. * ``user_language`` (str): 用户操作系统使用的语言. 比如 ``'zh-CN'``
  144. * ``server_host`` (str): 当前会话的服务器host,包含域名和端口,端口为80时可以被省略
  145. * ``origin`` : 当前用户的页面地址. 包含 协议、主机、端口 部分. 比如 ``'http://localhost:8080'`` .
  146. 只在当用户的页面地址不在当前服务器下(即 主机、端口部分和 ``server_host`` 不一致)时有值.
  147. 返回值的 ``user_agent`` 属性是通过user_agents库进行解析生成的。参见 https://github.com/selwin/python-user-agents#usage
  148. """
  149. return get_current_session().info