1
0

io_ctrl.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. """
  2. 输入输出的底层实现函数
  3. """
  4. import json
  5. import logging
  6. from .session import chose_impl, next_client_event, get_current_task_id, get_current_session
  7. logger = logging.getLogger(__name__)
  8. class OutputReturn:
  9. """ ``output`` 消息的处理类 """
  10. def __init__(self, spec, on_embed=None):
  11. self.spec = spec
  12. self.processed = False
  13. self.on_embed = on_embed or (lambda d: d)
  14. def embed_data(self):
  15. """返回供嵌入到布局中的数据,可以设置一些默认值"""
  16. self.processed = True
  17. return self.on_embed(self.spec)
  18. def __del__(self):
  19. """未嵌入时的操作:直接输出消息"""
  20. if not self.processed:
  21. send_msg('output', self.spec)
  22. class OutputEncoder(json.JSONEncoder):
  23. def default(self, obj):
  24. if isinstance(obj, OutputReturn):
  25. return obj.embed_data()
  26. # Let the base class default method raise the TypeError
  27. return json.JSONEncoder.default(self, obj)
  28. def send_msg(cmd, spec=None):
  29. msg = dict(command=cmd, spec=spec, task_id=get_current_task_id())
  30. get_current_session().send_task_command(msg)
  31. @chose_impl
  32. def single_input(item_spec, valid_func, preprocess_func):
  33. """
  34. Note: 鲁棒性在上层完成
  35. 将单个input构造成input_group,并获取返回值
  36. :param item_spec: 单个输入项的参数 'name' must in item_spec, 参数一定已经验证通过
  37. :param valid_func: Not None
  38. :param preprocess_func: Not None
  39. """
  40. if item_spec.get('name') is None: # single input
  41. item_spec['name'] = 'data'
  42. else: # as input_group item
  43. return dict(item_spec=item_spec, valid_func=valid_func, preprocess_func=preprocess_func)
  44. label = item_spec['label']
  45. name = item_spec['name']
  46. # todo 是否可以原地修改spec
  47. item_spec['label'] = ''
  48. item_spec.setdefault('auto_focus', True) # 如果没有设置autofocus参数,则开启参数 todo CHECKBOX, RADIO 特殊处理
  49. spec = dict(label=label, inputs=[item_spec])
  50. data = yield input_control(spec, {name: preprocess_func}, {name: valid_func})
  51. return data[name]
  52. @chose_impl
  53. def input_control(spec, preprocess_funcs, item_valid_funcs, form_valid_funcs=None):
  54. """
  55. 发送input命令,监听事件,验证输入项,返回结果
  56. :param spec:
  57. :param preprocess_funcs: keys 严格等于 spec中的name集合
  58. :param item_valid_funcs: keys 严格等于 spec中的name集合
  59. :param form_valid_funcs:
  60. :return:
  61. """
  62. send_msg('input_group', spec)
  63. data = yield input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_funcs)
  64. send_msg('destroy_form')
  65. return data
  66. def check_item(name, data, valid_func, preprocess_func):
  67. try:
  68. data = preprocess_func(data)
  69. error_msg = valid_func(data)
  70. except Exception as e:
  71. logger.warning('Get %r in valid_func for name:"%s"', e, name)
  72. error_msg = '字段内容不合法'
  73. if error_msg is not None:
  74. send_msg('update_input', dict(target_name=name, attributes={
  75. 'valid_status': False,
  76. 'invalid_feedback': error_msg
  77. }))
  78. return False
  79. return True
  80. @chose_impl
  81. def input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_funcs):
  82. """
  83. 根据提供的校验函数处理表单事件
  84. :param item_valid_funcs: map(name -> valid_func) valid_func 为 None 时,不进行验证
  85. valid_func: callback(data) -> error_msg or None
  86. :param form_valid_funcs: callback(data) -> (name, error_msg) or None
  87. :param preprocess_funcs:
  88. :return:
  89. """
  90. while True:
  91. event = yield next_client_event()
  92. event_name, event_data = event['event'], event['data']
  93. if event_name == 'input_event':
  94. input_event = event_data['event_name']
  95. if input_event == 'blur':
  96. onblur_name = event_data['name']
  97. check_item(onblur_name, event_data['value'], item_valid_funcs[onblur_name],
  98. preprocess_funcs[onblur_name])
  99. elif event_name == 'from_submit':
  100. all_valid = True
  101. # 调用输入项验证函数进行校验
  102. for name, valid_func in item_valid_funcs.items():
  103. if not check_item(name, event_data[name], valid_func, preprocess_funcs[name]):
  104. all_valid = False
  105. if all_valid: # todo 减少preprocess_funcs[name]调用次数
  106. data = {name: preprocess_funcs[name](val) for name, val in event_data.items()}
  107. # 调用表单验证函数进行校验
  108. if form_valid_funcs:
  109. v_res = form_valid_funcs(data)
  110. if v_res is not None:
  111. all_valid = False
  112. onblur_name, error_msg = v_res
  113. send_msg('update_input', dict(target_name=onblur_name, attributes={
  114. 'valid_status': False,
  115. 'invalid_feedback': error_msg
  116. }))
  117. if all_valid:
  118. break
  119. elif event_name == 'from_cancel':
  120. data = None
  121. break
  122. else:
  123. logger.warning("Unhandled Event: %s", event)
  124. return data
  125. def output_register_callback(callback, **options):
  126. task_id = get_current_session().register_callback(callback, **options)
  127. return task_id