cengal.parallel_execution.coroutines.coro_standard_services.tkinter.versions.v_0.tkinter
Module Docstring Docstrings: http://www.python.org/dev/peps/pep-0257/
1#!/usr/bin/env python 2# coding=utf-8 3 4# Copyright © 2012-2024 ButenkoMS. All rights reserved. Contacts: <gtalk@butenkoms.space> 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17 18 19""" 20Module Docstring 21Docstrings: http://www.python.org/dev/peps/pep-0257/ 22""" 23 24 25__author__ = "ButenkoMS <gtalk@butenkoms.space>" 26__copyright__ = "Copyright © 2012-2024 ButenkoMS. All rights reserved. Contacts: <gtalk@butenkoms.space>" 27__credits__ = ["ButenkoMS <gtalk@butenkoms.space>", ] 28__license__ = "Apache License, Version 2.0" 29__version__ = "4.4.1" 30__maintainer__ = "ButenkoMS <gtalk@butenkoms.space>" 31__email__ = "gtalk@butenkoms.space" 32# __status__ = "Prototype" 33__status__ = "Development" 34# __status__ = "Production" 35 36 37from time import perf_counter 38from cengal.introspection.inspect.versions.v_0.inspect import get_exception 39from cengal.parallel_execution.coroutines.coro_scheduler import * 40from cengal.parallel_execution.coroutines.coro_standard_services.loop_yield.versions.v_0.loop_yield import CoroPriority 41from cengal.parallel_execution.coroutines.coro_standard_services.simple_yield import Yield 42from cengal.parallel_execution.coroutines.coro_tools.await_coro import * 43from enum import Enum 44from typing import Callable, Dict, Hashable, Tuple, Union, Type, Optional, Any, List, Set 45from cengal.time_management.repeat_for_a_time import Tracer 46from cengal.code_flow_control.smart_values.versions.v_1 import ValueExistence 47from async_generator import asynccontextmanager, async_generator, yield_ 48import asyncio 49from tkinter import Tk, TclError 50import inspect 51from functools import partial 52from inspect import isfunction, ismethod 53from copy import copy 54from cengal.time_management.sleep_tools import get_usable_min_sleep_interval 55 56 57TkObjId = int 58 59 60class TkinterServiceRequest(ServiceRequest): 61 def create(self, tk_class: Type[Tk], *args, **kwargs) -> TkObjId: 62 return self._save(0, tk_class, *args, **kwargs) 63 64 def put(self, tk_obj: Tk) -> TkObjId: 65 return self._save(1, tk_obj) 66 67 def get(self, tk_obj_id: TkObjId) -> Tk: 68 return self._save(2, tk_obj_id) 69 70 def destroy(self, tk_obj_id: TkObjId, just_mark_as_destroyed: bool = False) -> None: 71 return self._save(3, tk_obj_id, just_mark_as_destroyed) 72 73 def destroy_and_wait_for_destroyed(self, tk_obj_id: TkObjId, just_mark_as_destroyed: bool = False) -> None: 74 return self._save(4, tk_obj_id, just_mark_as_destroyed) 75 76 def wait_for_destroyed(self, tk_obj_id: TkObjId) -> None: 77 return self._save(5, tk_obj_id) 78 79 def put_coro(self, tk_obj_id: TkObjId, coro_worker: AnyWorker, *args, **kwargs) -> CoroID: 80 return self._save(6, tk_obj_id, coro_worker, *args, **kwargs) 81 82 def register_coro(self, tk_obj_id: TkObjId, coro_id: CoroID) -> None: 83 return self._save(7, tk_obj_id, coro_id) 84 85 def set_update_period(self, tk_obj_id: TkObjId, period: float) -> None: 86 return self._save(8, tk_obj_id, period) 87 88 89class TkObjDestroyedError(Exception): 90 pass 91 92 93class TkObjWrapper: 94 def __init__(self, interface: Interface, tk_obj_id, tk_obj, destroy_on_finish: bool = False) -> None: 95 self._interface = interface 96 self._tk_obj_id = tk_obj_id 97 self._tk_obj = tk_obj 98 self._destroyed: bool = False 99 self._just_mark_as_destroyed: bool = not destroy_on_finish 100 self._service: TkinterService = self._interface._loop.get_service_instance(TkinterService) 101 102 @property 103 def tk(self): 104 if self._destroyed: 105 raise TkObjDestroyedError 106 else: 107 return self._tk_obj 108 109 @property 110 def tk_id(self): 111 if self._destroyed: 112 raise TkObjDestroyedError 113 else: 114 return self._tk_obj_id 115 116 def destroy(self, just_mark_as_destroyed: bool = False): 117 if not self._destroyed: 118 self._destroyed = True 119 self._interface(TkinterService, TkinterServiceRequest().destroy(self._tk_obj_id, just_mark_as_destroyed)) 120 121 async def adestroy(self, just_mark_as_destroyed: bool = False): 122 if not self._destroyed: 123 self._destroyed = True 124 await self._interface(TkinterService, TkinterServiceRequest().destroy(self._tk_obj_id, just_mark_as_destroyed)) 125 126 def put_coro(self, coro_worker: AnyWorker, *args, **kwargs): 127 return self._interface(TkinterService, TkinterServiceRequest().put_coro(self.tk_id, coro_worker, *args, **kwargs)) 128 129 async def aput_coro(self, coro_worker: AnyWorker, *args, **kwargs): 130 return await self._interface(TkinterService, TkinterServiceRequest().put_coro(self.tk_id, coro_worker, *args, **kwargs)) 131 132 def register_coro(self, coro_id: CoroID): 133 return self._interface(TkinterService, TkinterServiceRequest().register_coro(self.tk_id, coro_id)) 134 135 async def aregister_coro(self, coro_id: CoroID): 136 return await self._interface(TkinterService, TkinterServiceRequest().register_coro(self.tk_id, coro_id)) 137 138 @property 139 def destroyed(self): 140 return self._destroyed or (not self._service._get_inline(self._tk_obj_id)) 141 142 def __call__(self) -> Any: 143 return self.tk 144 145 def __enter__(self): 146 return self._tk_obj 147 148 def __exit__(self, type, value, traceback): 149 self.destroy() 150 151 async def __aenter__(self): 152 return self._tk_obj 153 154 async def __aexit__(self, type, value, traceback): 155 await self.adestroy() 156 157 def set_update_period(self, period: float): 158 return self._interface(TkinterService, TkinterServiceRequest().set_update_period(self.tk_id, period)) 159 160 async def aset_update_period(self, period: float): 161 return await self._interface(TkinterService, TkinterServiceRequest().set_update_period(self.tk_id, period)) 162 163 164class TkinterContextManager: 165 def __init__(self, interface: Optional[Interface] = None, tk_obj_or_id: Optional[Union[Tk, TkObjId, Type[Tk]]] = None, *args, **kwargs): 166 self._interface: Interface = interface or current_interface() 167 self._tk_obj_or_id: Optional[Union[Tk, TkObjId]] = tk_obj_or_id 168 self._args = args 169 self._kwargs = kwargs 170 self._tk_obj: Tk = None 171 self._tk_obj_id: Optional[TkObjId] = None 172 self._tk_wrapper: TkObjWrapper = None 173 self._destroy_on_finish: bool = False 174 175 def __enter__(self): 176 if self._tk_obj_or_id is None: 177 self._destroy_on_finish = True 178 self._tk_obj_or_id = self._interface(TkinterService, TkinterServiceRequest().create(Tk, *self._args, **self._kwargs)) 179 180 if isinstance(self._tk_obj_or_id, Tk): 181 self._tk_obj = self._tk_obj_or_id 182 self._tk_obj_id = self._interface(TkinterService, TkinterServiceRequest().put(self._tk_obj)) 183 elif inspect.isclass(self._tk_obj_or_id): 184 self._destroy_on_finish = True 185 if not issubclass(self._tk_obj_or_id, Tk): 186 self._interface._loop.logger.warning('Given class is not a subclass of Tk') 187 188 self._tk_obj_id = self._interface(TkinterService, TkinterServiceRequest().create(self._tk_obj_or_id, *self._args, **self._kwargs)) 189 self._tk_obj = self._interface(TkinterService, TkinterServiceRequest().get(self._tk_obj_id)) 190 else: 191 self._tk_obj_id = self._tk_obj_or_id 192 self._tk_obj = self._interface(TkinterService, TkinterServiceRequest().get(self._tk_obj_id)) 193 194 self._tk_wrapper = TkObjWrapper(self._interface, self._tk_obj_id, self._tk_obj, self._destroy_on_finish) 195 return self._tk_wrapper 196 197 def __exit__(self, type, value: Exception, traceback): 198 if not self._tk_wrapper.destroyed: 199 self._interface(TkinterService, TkinterServiceRequest().wait_for_destroyed(self._tk_wrapper._tk_obj_id)) 200 201 async def __aenter__(self): 202 if self._tk_obj_or_id is None: 203 self._destroy_on_finish = True 204 self._tk_obj_or_id = await self._interface(TkinterService, TkinterServiceRequest().create(Tk, *self._args, **self._kwargs)) 205 206 if isinstance(self._tk_obj_or_id, Tk): 207 self._tk_obj = self._tk_obj_or_id 208 self._tk_obj_id = await self._interface(TkinterService, TkinterServiceRequest().put(self._tk_obj)) 209 elif inspect.isclass(self._tk_obj_or_id): 210 self._destroy_on_finish = True 211 if not issubclass(self._tk_obj_or_id, Tk): 212 self._interface._loop.logger.warning('Given class is not a subclass of Tk') 213 214 self._tk_obj_id = await self._interface(TkinterService, TkinterServiceRequest().create(self._tk_obj_or_id, *self._args, **self._kwargs)) 215 self._tk_obj = await self._interface(TkinterService, TkinterServiceRequest().get(self._tk_obj_id)) 216 else: 217 self._tk_obj_id = self._tk_obj_or_id 218 self._tk_obj = await self._interface(TkinterService, TkinterServiceRequest().get(self._tk_obj_id)) 219 220 self._tk_wrapper = TkObjWrapper(self._interface, self._tk_obj_id, self._tk_obj, self._destroy_on_finish) 221 return self._tk_wrapper 222 223 async def __aexit__(self, type, value, traceback): 224 if not self._tk_wrapper.destroyed: 225 await self._interface(TkinterService, TkinterServiceRequest().wait_for_destroyed(self._tk_wrapper._tk_obj_id)) 226 227 228tcm = TkinterContextManager 229 230 231class TkObjNotFoundError(Exception): 232 pass 233 234 235class TkinterService(Service, EntityStatsMixin): 236 def __init__(self, loop: CoroSchedulerType): 237 super(TkinterService, self).__init__(loop) 238 239 self._request_workers = { 240 0: self._on_create, 241 1: self._on_put, 242 2: self._on_get, 243 3: self._on_destroy, 244 4: self._on_destroy_and_wait_for_destroyed, 245 5: self._on_wait_for_destroyed, 246 6: self._on_put_coro, 247 7: self._on_register_coro, 248 8: self._on_set_update_period, 249 } 250 251 self.standard_ui_update_interval: float = 1 / 60 252 self.default_update_period: float = max(get_usable_min_sleep_interval(), self.standard_ui_update_interval) 253 self.update_period: float = self.default_update_period 254 self.update_periods: Dict[TkObjId, float] = dict() 255 self.tk_counter = Counter() 256 self.tk_by_id: Dict[TkObjId, Tk] = dict() 257 self.coroutines: Dict[CoroID, Set[TkObjId]] = dict() 258 self.waiting_for_destroyed: Dict[TkObjId, Set[CoroID]] = dict() 259 self.new_destroyed: Dict[TkObjId, bool] = dict() 260 self.new_closed: Dict[TkObjId, bool] = dict() 261 self.destroyed: Set[TkObjId] = set() 262 self.coro_by_tk: Dict[TkObjId, CoroID] = dict() 263 self.tk_users: Dict[TkObjId, Set[CoroID]] = dict() 264 self.tk_after_ids: Dict[TkObjId, Set[str]] = dict() 265 266 self.updater_running: bool = False 267 268 def start_tk_updater(self): 269 if not self.updater_running: 270 self.updater_running = True 271 tk_updater_coro = self._loop.put_coro(tk_updater, self) 272 tk_updater_coro.is_background_coro = True 273 274 def get_entity_stats(self, stats_level: 'EntityStatsMixin.StatsLevel' = EntityStatsMixin.StatsLevel.debug) -> Tuple[str, Dict[str, Any]]: 275 return type(self).__name__, { 276 'tk_counter': self.tk_counter._index + 1, 277 } 278 279 def single_task_registration_or_immediate_processing(self, request: Optional[TkinterServiceRequest]=None 280 ) -> ServiceProcessingResponse: 281 if request is not None: 282 return self.resolve_request(request) 283 return True, None, None 284 285 def full_processing_iteration(self): 286 # closed 287 new_closed_bak = self.new_closed 288 self.new_closed = type(new_closed_bak)() 289 for tk_obj_id, just_mark_as_destroyed in new_closed_bak.items(): 290 self.new_destroyed[tk_obj_id] = just_mark_as_destroyed 291 292 # waiting_for_destroyed 293 new_destroyed_bak = self.new_destroyed 294 self.new_destroyed = type(self.new_destroyed)() 295 for tk_obj_id, just_mark_as_destroyed in new_destroyed_bak.items(): 296 if tk_obj_id in self.destroyed: 297 continue 298 299 self.destroyed.add(tk_obj_id) 300 301 tk_obj = self.tk_by_id[tk_obj_id] 302 303 # if tk_obj_id in self.tk_after_ids: 304 # after_ids = self.tk_after_ids[tk_obj_id] 305 # for after_id in after_ids: 306 # tk_obj.after_cancel(after_id) 307 308 # del self.tk_after_ids[tk_obj_id] 309 310 # cancel_all_after(tk_obj) 311 312 if not just_mark_as_destroyed: 313 tk_obj.destroy() 314 315 if tk_obj_id in self.tk_by_id: 316 del self.tk_by_id[tk_obj_id] 317 318 if tk_obj_id in self.waiting_for_destroyed: 319 for waiter_coro_id in self.waiting_for_destroyed[tk_obj_id]: 320 self.register_response(waiter_coro_id, None, None) 321 322 del self.waiting_for_destroyed[tk_obj_id] 323 324 if tk_obj_id in self.coro_by_tk: 325 coros_tks = self.coroutines[self.coro_by_tk[tk_obj_id]] 326 del self.coro_by_tk[tk_obj_id] 327 if tk_obj_id in coros_tks: 328 coros_tks.remove(tk_obj_id) 329 330 if tk_obj_id in self.tk_users: 331 tk_users = self.tk_users[tk_obj_id] 332 for tk_user_coro_id in tk_users: 333 # TODO: switch to an appropriate service 334 self._loop.kill_coro_by_id(tk_user_coro_id) 335 336 if tk_obj_id in self.update_periods: 337 del self.update_periods[tk_obj_id] 338 339 # compute min update period 340 if self.update_periods: 341 self.update_period = min(self.update_periods.values()) 342 else: 343 self.update_period = self.default_update_period 344 345 # general 346 if (not self.new_closed) and (not self.new_destroyed): 347 self.make_dead() 348 349 def in_work(self) -> bool: 350 result: bool = bool(self.new_closed) or bool(self.new_destroyed) or bool(self.update_periods) 351 return self.thrifty_in_work(result) 352 353 def _on_create(self, tk_class: Type[Tk], *args, **kwargs) -> ServiceProcessingResponse: 354 tk = tk_class(*args, **kwargs) 355 tk_id: TkObjId = self.tk_counter.get() 356 on_destroy = partial(self._on_create_on_destroyed, tk_id, tk) 357 tk.bind("<Destroy>", on_destroy, '+') 358 old_on_close = tk.protocol("WM_DELETE_WINDOW", None) 359 if (not isfunction(old_on_close)) and (not ismethod(old_on_close)): 360 old_on_close = None 361 362 on_create_on_closed = partial(self._on_create_on_closed, tk_id, tk, old_on_close) 363 tk.protocol("WM_DELETE_WINDOW", on_create_on_closed) 364 self.tk_by_id[tk_id] = tk 365 coro = self.current_caller_coro_info.coro 366 coro_id = self.current_caller_coro_info.coro_id 367 if coro_id not in self.coroutines: 368 self.coroutines[coro_id] = set() 369 coro.add_on_coro_del_handler(self._on_created_coro_del_handler) 370 371 self.coroutines[coro_id].add(tk_id) 372 self.coro_by_tk[tk_id] = coro_id 373 # after_setup(tk, 1) 374 after_idle_setup(tk) 375 self.start_tk_updater() 376 return True, tk_id, None 377 378 def _on_put(self, tk_obj: Tk) -> ServiceProcessingResponse: 379 tk: Tk = tk_obj 380 tk_id: TkObjId = self.tk_counter.get() 381 on_destroy = partial(self._on_put_on_destroyed, tk_id, tk) 382 tk.bind("<Destroy>", on_destroy, '+') 383 old_on_close = tk.protocol("WM_DELETE_WINDOW", None) 384 if (not isfunction(old_on_close)) and (not ismethod(old_on_close)): 385 old_on_close = None 386 387 on_put_on_closed = partial(self._on_put_on_closed, tk_id, tk, old_on_close) 388 tk.protocol("WM_DELETE_WINDOW", on_put_on_closed) 389 self.tk_by_id[tk_id] = tk 390 coro = self.current_caller_coro_info.coro 391 coro_id = self.current_caller_coro_info.coro_id 392 if coro_id not in self.coroutines: 393 self.coroutines[coro_id] = set() 394 coro.add_on_coro_del_handler(self._on_put_coro_del_handler) 395 396 self.coroutines[coro_id].add(tk_id) 397 self.coro_by_tk[tk_id] = coro_id 398 # after_setup(tk, 1) 399 after_idle_setup(tk) 400 self.start_tk_updater() 401 return True, tk_id, None 402 403 def _on_get(self, tk_obj_id: TkObjId) -> ServiceProcessingResponse: 404 if tk_obj_id in self.tk_by_id: 405 return True, self.tk_by_id[tk_obj_id], None 406 else: 407 return True, None, TkObjNotFoundError() 408 409 def _get_inline(self, tk_obj_id: TkObjId) -> Optional[Tk]: 410 return self.tk_by_id.get(tk_obj_id, None) 411 412 def _on_destroy(self, tk_obj_id: TkObjId, just_mark_as_destroyed: bool = False) -> ServiceProcessingResponse: 413 self.new_destroyed[tk_obj_id] = just_mark_as_destroyed 414 self.make_live() 415 return True, None, None 416 417 def _on_destroy_and_wait_for_destroyed(self, tk_obj_id: TkObjId, just_mark_as_destroyed: bool = False) -> ServiceProcessingResponse: 418 self.new_destroyed[tk_obj_id] = just_mark_as_destroyed 419 return self._on_wait_for_destroyed(tk_obj_id) 420 421 def _on_wait_for_destroyed(self, tk_obj_id: TkObjId) -> ServiceProcessingResponse: 422 if tk_obj_id in self.destroyed: 423 return True, None, None 424 425 if tk_obj_id not in self.waiting_for_destroyed: 426 self.waiting_for_destroyed[tk_obj_id] = set() 427 428 self.waiting_for_destroyed[tk_obj_id].add(self.current_caller_coro_info.coro_id) 429 self.make_live() 430 return False, None, None 431 432 def _on_put_coro(self, tk_obj_id: TkObjId, coro_worker: AnyWorker, *args, **kwargs): 433 exception = None 434 coro_id: CoroID = None 435 try: 436 # TODO: switch to an appropriate service 437 coro_id = self._loop.put_coro(coro_worker, *args, **kwargs).coro_id 438 except: 439 exception = get_exception() 440 441 self._register_coro_impl(tk_obj_id, coro_id) 442 return True, coro_id, exception 443 444 def _on_register_coro(self, tk_obj_id: TkObjId, coro_id: CoroID): 445 exception = None 446 try: 447 self._register_coro_impl(tk_obj_id, coro_id) 448 except: 449 exception = get_exception() 450 451 return True, None, exception 452 453 def _register_coro_impl(self, tk_obj_id: TkObjId, coro_id: CoroID): 454 if tk_obj_id not in self.tk_users: 455 self.tk_users[tk_obj_id] = set() 456 457 self.tk_users[tk_obj_id].add(coro_id) 458 459 def _on_set_update_period(self, tk_obj_id: TkObjId, period: float): 460 self.update_periods[tk_obj_id] = period 461 462 self.make_live() 463 return True, None, None 464 465 def _on_created_coro_del_handler(self, coro: CoroWrapperBase) -> bool: 466 for tk_obj_id in self.coroutines[coro.coro_id]: 467 self.new_destroyed[tk_obj_id] = False 468 469 self.make_live() 470 return True 471 472 def _on_put_coro_del_handler(self, coro: CoroWrapperBase) -> bool: 473 for tk_obj_id in self.coroutines[coro.coro_id]: 474 self.new_destroyed[tk_obj_id] = True 475 476 self.make_live() 477 return True 478 479 def _on_create_on_destroyed(self, tk_obj_id: TkObjId, tk_obj: Tk, event): 480 if event.widget is tk_obj: 481 if tk_obj_id not in self.destroyed: 482 self.new_destroyed[tk_obj_id] = True 483 484 self.make_live() 485 486 def _on_put_on_destroyed(self, tk_obj_id: TkObjId, tk_obj: Tk, event): 487 if event.widget is tk_obj: 488 if tk_obj_id not in self.destroyed: 489 self.new_destroyed[tk_obj_id] = True 490 491 self.make_live() 492 493 def _on_create_on_closed(self, tk_obj_id: TkObjId, tk: Tk, previous_on_close: Optional[Callable]): 494 if previous_on_close is not None: 495 previous_on_close() 496 497 if tk_obj_id not in self.destroyed: 498 self.new_closed[tk_obj_id] = False 499 500 self.make_live() 501 502 def _on_put_on_closed(self, tk_obj_id: TkObjId, tk: Tk, previous_on_close: Optional[Callable]): 503 if previous_on_close is not None: 504 previous_on_close() 505 506 if tk_obj_id not in self.destroyed: 507 self.new_closed[tk_obj_id] = False 508 509 self.make_live() 510 511 512# def tk_updater(i: Interface, tkinter_service: TkinterService): 513# from cengal.parallel_execution.coroutines.coro_standard_services.sleep import Sleep 514# while tkinter_service.tk_by_id: 515# tk_by_id_bak = copy(tkinter_service.tk_by_id) 516# for tk_id, tk_obj in tk_by_id_bak.items(): 517# if tk_id not in tkinter_service.destroyed: 518# tk_obj.update() 519 520# i(Sleep, tkinter_service.update_period) 521 522# tkinter_service.updater_running = False 523 524 525# def tk_updater(i: Interface, tkinter_service: TkinterService): 526# """ 527# https://stackoverflow.com/questions/4083796/how-do-i-run-unittest-on-a-tkinter-app 528# https://github.com/ipython/ipython/blob/master/IPython/terminal/pt_inputhooks/tk.py 529 530# Args: 531# i (Interface): [description] 532# tkinter_service (TkinterService): [description] 533# """ 534# from _tkinter import ALL_EVENTS as _tkinter__ALL_EVENTS, DONT_WAIT as _tkinter__DONT_WAIT 535# from cengal.parallel_execution.coroutines.coro_standard_services.sleep import Sleep 536# from cengal.parallel_execution.coroutines.coro_standard_services.loop_yield import get_loop_yield 537# ly = get_loop_yield(CoroPriority.low) 538# while tkinter_service.tk_by_id: 539# tk_by_id_bak = copy(tkinter_service.tk_by_id) 540# for tk_id, tk_obj in tk_by_id_bak.items(): 541# ly() 542# if tk_id not in tkinter_service.destroyed: 543# while tk_obj.dooneevent(_tkinter__ALL_EVENTS | _tkinter__DONT_WAIT): 544# ly() 545 546# i(Sleep, tkinter_service.update_period) 547 548# tkinter_service.updater_running = False 549 550 551# def tk_updater(i: Interface, tkinter_service: TkinterService): 552# """ 553# https://stackoverflow.com/questions/4083796/how-do-i-run-unittest-on-a-tkinter-app 554# https://github.com/ipython/ipython/blob/master/IPython/terminal/pt_inputhooks/tk.py 555 556# Args: 557# i (Interface): [description] 558# tkinter_service (TkinterService): [description] 559# """ 560# from _tkinter import ALL_EVENTS as _tkinter__ALL_EVENTS, DONT_WAIT as _tkinter__DONT_WAIT 561# from cengal.parallel_execution.coroutines.coro_standard_services.sleep import Sleep 562# from cengal.parallel_execution.coroutines.coro_standard_services.loop_yield import get_loop_yield 563# ly = get_loop_yield(CoroPriority.low) 564# while tkinter_service.tk_by_id: 565# ly() 566# tk_by_id_bak = copy(tkinter_service.tk_by_id) 567# tk_objects_with_events = set() 568# for tk_id, tk_obj in tk_by_id_bak.items(): 569# if tk_id not in tkinter_service.destroyed: 570# tk_objects_with_events.add(tk_obj) 571 572# while tk_objects_with_events: 573# new_tk_objects_with_events = set() 574# for tk_obj in tk_objects_with_events: 575# ly() 576# if tk_obj.dooneevent(_tkinter__ALL_EVENTS | _tkinter__DONT_WAIT): 577# new_tk_objects_with_events.add(tk_obj) 578 579# tk_objects_with_events = new_tk_objects_with_events 580 581# i(Sleep, tkinter_service.update_period) 582 583# tkinter_service.updater_running = False 584 585 586TkinterServiceRequest.default_service_type = TkinterService 587 588 589# TODO: currently it uses Yield if delay is bigger that a magic number `0.001` seconds. Maybe make it some how use TkinterService.update_periods or somethin like it instead? 590def tk_updater(i: Interface, tkinter_service: TkinterService): 591 """ 592 https://stackoverflow.com/questions/4083796/how-do-i-run-unittest-on-a-tkinter-app 593 https://github.com/ipython/ipython/blob/master/IPython/terminal/pt_inputhooks/tk.py 594 595 Args: 596 i (Interface): [description] 597 tkinter_service (TkinterService): [description] 598 """ 599 from _tkinter import ALL_EVENTS as _tkinter__ALL_EVENTS, WINDOW_EVENTS as _tkinter__WINDOW_EVENTS, FILE_EVENTS as _tkinter__FILE_EVENTS, TIMER_EVENTS as _tkinter__TIMER_EVENTS, IDLE_EVENTS as _tkinter__IDLE_EVENTS, DONT_WAIT as _tkinter__DONT_WAIT 600 from cengal.parallel_execution.coroutines.coro_standard_services.sleep import Sleep 601 from cengal.parallel_execution.coroutines.coro_standard_services.loop_yield import get_loop_yield 602 ly = get_loop_yield(CoroPriority.low) 603 while tkinter_service.tk_by_id: 604 ly() 605 tk_by_id_bak = copy(tkinter_service.tk_by_id) 606 desired_length = len(tk_by_id_bak.keys()) 607 tk_ids_without_events = set() 608 609 while len(tk_ids_without_events) < desired_length: 610 for tk_id, tk_obj in tk_by_id_bak.items(): 611 if tk_id in tk_ids_without_events: 612 continue 613 614 if tk_id in tkinter_service.destroyed: 615 tk_ids_without_events.add(tk_id) 616 continue 617 618 start = perf_counter() 619 has_window_events = tk_obj.dooneevent(_tkinter__WINDOW_EVENTS | _tkinter__DONT_WAIT) 620 stop = perf_counter() 621 if (stop - start) > 0.001: 622 i(Yield) 623 # ly() 624 625 start = perf_counter() 626 has_file_events = tk_obj.dooneevent(_tkinter__FILE_EVENTS | _tkinter__DONT_WAIT) 627 stop = perf_counter() 628 if (stop - start) > 0.001: 629 i(Yield) 630 # ly() 631 632 start = perf_counter() 633 has_timer_events = tk_obj.dooneevent(_tkinter__TIMER_EVENTS | _tkinter__DONT_WAIT) 634 stop = perf_counter() 635 if (stop - start) > 0.001: 636 i(Yield) 637 # ly() 638 639 has_events = has_window_events or has_file_events or has_timer_events 640 641 if not has_events: 642 start = perf_counter() 643 tk_obj.dooneevent(_tkinter__IDLE_EVENTS | _tkinter__DONT_WAIT) 644 stop = perf_counter() 645 if (stop - start) > 0.001: 646 i(Yield) 647 # ly() 648 tk_ids_without_events.add(tk_id) 649 650 i(Sleep, tkinter_service.update_period) 651 652 tkinter_service.updater_running = False 653 654 655def after_func(root): 656 from cengal.parallel_execution.coroutines.coro_standard_services.simple_yield import Yield 657 from cengal.parallel_execution.coroutines.coro_scheduler import current_interface, OutsideCoroSchedulerContext 658 try: 659 i = current_interface() 660 if i is not None: 661 i(Yield) 662 except OutsideCoroSchedulerContext: 663 pass 664 665 # after_setup(root, 1) 666 after_idle_setup(root) 667 668 669def after_setup(root, update_period) -> bool: 670 try: 671 handler = root.after(update_period, after_func, root) 672 except TclError: 673 return False 674 675 return True 676 677 678def after_idle_setup(root) -> bool: 679 try: 680 handler = root.after_idle(after_setup, root, 0) 681 except TclError: 682 return False 683 684 return True
TkObjId =
<class 'int'>
class
TkinterServiceRequest(cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.ServiceRequest):
61class TkinterServiceRequest(ServiceRequest): 62 def create(self, tk_class: Type[Tk], *args, **kwargs) -> TkObjId: 63 return self._save(0, tk_class, *args, **kwargs) 64 65 def put(self, tk_obj: Tk) -> TkObjId: 66 return self._save(1, tk_obj) 67 68 def get(self, tk_obj_id: TkObjId) -> Tk: 69 return self._save(2, tk_obj_id) 70 71 def destroy(self, tk_obj_id: TkObjId, just_mark_as_destroyed: bool = False) -> None: 72 return self._save(3, tk_obj_id, just_mark_as_destroyed) 73 74 def destroy_and_wait_for_destroyed(self, tk_obj_id: TkObjId, just_mark_as_destroyed: bool = False) -> None: 75 return self._save(4, tk_obj_id, just_mark_as_destroyed) 76 77 def wait_for_destroyed(self, tk_obj_id: TkObjId) -> None: 78 return self._save(5, tk_obj_id) 79 80 def put_coro(self, tk_obj_id: TkObjId, coro_worker: AnyWorker, *args, **kwargs) -> CoroID: 81 return self._save(6, tk_obj_id, coro_worker, *args, **kwargs) 82 83 def register_coro(self, tk_obj_id: TkObjId, coro_id: CoroID) -> None: 84 return self._save(7, tk_obj_id, coro_id) 85 86 def set_update_period(self, tk_obj_id: TkObjId, period: float) -> None: 87 return self._save(8, tk_obj_id, period)
def
destroy_and_wait_for_destroyed(self, tk_obj_id: int, just_mark_as_destroyed: bool = False) -> None:
def
put_coro( self, tk_obj_id: int, coro_worker: typing.Union[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.ExplicitWorker, collections.abc.Callable[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Interface, typing.Any], collections.abc.Callable[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Interface, typing.Awaitable[typing.Any]]], *args, **kwargs) -> int:
default_service_type: Union[type[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Service], NoneType] =
<class 'TkinterService'>
Inherited Members
- cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.ServiceRequest
- default__request__type__
- request_type
- args
- kwargs
- provide_to_request_handler
- interface
- i
- async_interface
- ai
class
TkObjDestroyedError(builtins.Exception):
Common base class for all non-exit exceptions.
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- args
class
TkObjWrapper:
94class TkObjWrapper: 95 def __init__(self, interface: Interface, tk_obj_id, tk_obj, destroy_on_finish: bool = False) -> None: 96 self._interface = interface 97 self._tk_obj_id = tk_obj_id 98 self._tk_obj = tk_obj 99 self._destroyed: bool = False 100 self._just_mark_as_destroyed: bool = not destroy_on_finish 101 self._service: TkinterService = self._interface._loop.get_service_instance(TkinterService) 102 103 @property 104 def tk(self): 105 if self._destroyed: 106 raise TkObjDestroyedError 107 else: 108 return self._tk_obj 109 110 @property 111 def tk_id(self): 112 if self._destroyed: 113 raise TkObjDestroyedError 114 else: 115 return self._tk_obj_id 116 117 def destroy(self, just_mark_as_destroyed: bool = False): 118 if not self._destroyed: 119 self._destroyed = True 120 self._interface(TkinterService, TkinterServiceRequest().destroy(self._tk_obj_id, just_mark_as_destroyed)) 121 122 async def adestroy(self, just_mark_as_destroyed: bool = False): 123 if not self._destroyed: 124 self._destroyed = True 125 await self._interface(TkinterService, TkinterServiceRequest().destroy(self._tk_obj_id, just_mark_as_destroyed)) 126 127 def put_coro(self, coro_worker: AnyWorker, *args, **kwargs): 128 return self._interface(TkinterService, TkinterServiceRequest().put_coro(self.tk_id, coro_worker, *args, **kwargs)) 129 130 async def aput_coro(self, coro_worker: AnyWorker, *args, **kwargs): 131 return await self._interface(TkinterService, TkinterServiceRequest().put_coro(self.tk_id, coro_worker, *args, **kwargs)) 132 133 def register_coro(self, coro_id: CoroID): 134 return self._interface(TkinterService, TkinterServiceRequest().register_coro(self.tk_id, coro_id)) 135 136 async def aregister_coro(self, coro_id: CoroID): 137 return await self._interface(TkinterService, TkinterServiceRequest().register_coro(self.tk_id, coro_id)) 138 139 @property 140 def destroyed(self): 141 return self._destroyed or (not self._service._get_inline(self._tk_obj_id)) 142 143 def __call__(self) -> Any: 144 return self.tk 145 146 def __enter__(self): 147 return self._tk_obj 148 149 def __exit__(self, type, value, traceback): 150 self.destroy() 151 152 async def __aenter__(self): 153 return self._tk_obj 154 155 async def __aexit__(self, type, value, traceback): 156 await self.adestroy() 157 158 def set_update_period(self, period: float): 159 return self._interface(TkinterService, TkinterServiceRequest().set_update_period(self.tk_id, period)) 160 161 async def aset_update_period(self, period: float): 162 return await self._interface(TkinterService, TkinterServiceRequest().set_update_period(self.tk_id, period))
TkObjWrapper( interface: cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Interface, tk_obj_id, tk_obj, destroy_on_finish: bool = False)
95 def __init__(self, interface: Interface, tk_obj_id, tk_obj, destroy_on_finish: bool = False) -> None: 96 self._interface = interface 97 self._tk_obj_id = tk_obj_id 98 self._tk_obj = tk_obj 99 self._destroyed: bool = False 100 self._just_mark_as_destroyed: bool = not destroy_on_finish 101 self._service: TkinterService = self._interface._loop.get_service_instance(TkinterService)
def
put_coro( self, coro_worker: typing.Union[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.ExplicitWorker, collections.abc.Callable[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Interface, typing.Any], collections.abc.Callable[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Interface, typing.Awaitable[typing.Any]]], *args, **kwargs):
async def
aput_coro( self, coro_worker: typing.Union[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.ExplicitWorker, collections.abc.Callable[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Interface, typing.Any], collections.abc.Callable[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Interface, typing.Awaitable[typing.Any]]], *args, **kwargs):
class
TkinterContextManager:
165class TkinterContextManager: 166 def __init__(self, interface: Optional[Interface] = None, tk_obj_or_id: Optional[Union[Tk, TkObjId, Type[Tk]]] = None, *args, **kwargs): 167 self._interface: Interface = interface or current_interface() 168 self._tk_obj_or_id: Optional[Union[Tk, TkObjId]] = tk_obj_or_id 169 self._args = args 170 self._kwargs = kwargs 171 self._tk_obj: Tk = None 172 self._tk_obj_id: Optional[TkObjId] = None 173 self._tk_wrapper: TkObjWrapper = None 174 self._destroy_on_finish: bool = False 175 176 def __enter__(self): 177 if self._tk_obj_or_id is None: 178 self._destroy_on_finish = True 179 self._tk_obj_or_id = self._interface(TkinterService, TkinterServiceRequest().create(Tk, *self._args, **self._kwargs)) 180 181 if isinstance(self._tk_obj_or_id, Tk): 182 self._tk_obj = self._tk_obj_or_id 183 self._tk_obj_id = self._interface(TkinterService, TkinterServiceRequest().put(self._tk_obj)) 184 elif inspect.isclass(self._tk_obj_or_id): 185 self._destroy_on_finish = True 186 if not issubclass(self._tk_obj_or_id, Tk): 187 self._interface._loop.logger.warning('Given class is not a subclass of Tk') 188 189 self._tk_obj_id = self._interface(TkinterService, TkinterServiceRequest().create(self._tk_obj_or_id, *self._args, **self._kwargs)) 190 self._tk_obj = self._interface(TkinterService, TkinterServiceRequest().get(self._tk_obj_id)) 191 else: 192 self._tk_obj_id = self._tk_obj_or_id 193 self._tk_obj = self._interface(TkinterService, TkinterServiceRequest().get(self._tk_obj_id)) 194 195 self._tk_wrapper = TkObjWrapper(self._interface, self._tk_obj_id, self._tk_obj, self._destroy_on_finish) 196 return self._tk_wrapper 197 198 def __exit__(self, type, value: Exception, traceback): 199 if not self._tk_wrapper.destroyed: 200 self._interface(TkinterService, TkinterServiceRequest().wait_for_destroyed(self._tk_wrapper._tk_obj_id)) 201 202 async def __aenter__(self): 203 if self._tk_obj_or_id is None: 204 self._destroy_on_finish = True 205 self._tk_obj_or_id = await self._interface(TkinterService, TkinterServiceRequest().create(Tk, *self._args, **self._kwargs)) 206 207 if isinstance(self._tk_obj_or_id, Tk): 208 self._tk_obj = self._tk_obj_or_id 209 self._tk_obj_id = await self._interface(TkinterService, TkinterServiceRequest().put(self._tk_obj)) 210 elif inspect.isclass(self._tk_obj_or_id): 211 self._destroy_on_finish = True 212 if not issubclass(self._tk_obj_or_id, Tk): 213 self._interface._loop.logger.warning('Given class is not a subclass of Tk') 214 215 self._tk_obj_id = await self._interface(TkinterService, TkinterServiceRequest().create(self._tk_obj_or_id, *self._args, **self._kwargs)) 216 self._tk_obj = await self._interface(TkinterService, TkinterServiceRequest().get(self._tk_obj_id)) 217 else: 218 self._tk_obj_id = self._tk_obj_or_id 219 self._tk_obj = await self._interface(TkinterService, TkinterServiceRequest().get(self._tk_obj_id)) 220 221 self._tk_wrapper = TkObjWrapper(self._interface, self._tk_obj_id, self._tk_obj, self._destroy_on_finish) 222 return self._tk_wrapper 223 224 async def __aexit__(self, type, value, traceback): 225 if not self._tk_wrapper.destroyed: 226 await self._interface(TkinterService, TkinterServiceRequest().wait_for_destroyed(self._tk_wrapper._tk_obj_id))
TkinterContextManager( interface: typing.Union[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Interface, NoneType] = None, tk_obj_or_id: typing.Union[tkinter.Tk, int, typing.Type[tkinter.Tk], NoneType] = None, *args, **kwargs)
166 def __init__(self, interface: Optional[Interface] = None, tk_obj_or_id: Optional[Union[Tk, TkObjId, Type[Tk]]] = None, *args, **kwargs): 167 self._interface: Interface = interface or current_interface() 168 self._tk_obj_or_id: Optional[Union[Tk, TkObjId]] = tk_obj_or_id 169 self._args = args 170 self._kwargs = kwargs 171 self._tk_obj: Tk = None 172 self._tk_obj_id: Optional[TkObjId] = None 173 self._tk_wrapper: TkObjWrapper = None 174 self._destroy_on_finish: bool = False
tcm =
<class 'TkinterContextManager'>
class
TkObjNotFoundError(builtins.Exception):
Common base class for all non-exit exceptions.
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- args
class
TkinterService(cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Service, cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.EntityStatsMixin):
236class TkinterService(Service, EntityStatsMixin): 237 def __init__(self, loop: CoroSchedulerType): 238 super(TkinterService, self).__init__(loop) 239 240 self._request_workers = { 241 0: self._on_create, 242 1: self._on_put, 243 2: self._on_get, 244 3: self._on_destroy, 245 4: self._on_destroy_and_wait_for_destroyed, 246 5: self._on_wait_for_destroyed, 247 6: self._on_put_coro, 248 7: self._on_register_coro, 249 8: self._on_set_update_period, 250 } 251 252 self.standard_ui_update_interval: float = 1 / 60 253 self.default_update_period: float = max(get_usable_min_sleep_interval(), self.standard_ui_update_interval) 254 self.update_period: float = self.default_update_period 255 self.update_periods: Dict[TkObjId, float] = dict() 256 self.tk_counter = Counter() 257 self.tk_by_id: Dict[TkObjId, Tk] = dict() 258 self.coroutines: Dict[CoroID, Set[TkObjId]] = dict() 259 self.waiting_for_destroyed: Dict[TkObjId, Set[CoroID]] = dict() 260 self.new_destroyed: Dict[TkObjId, bool] = dict() 261 self.new_closed: Dict[TkObjId, bool] = dict() 262 self.destroyed: Set[TkObjId] = set() 263 self.coro_by_tk: Dict[TkObjId, CoroID] = dict() 264 self.tk_users: Dict[TkObjId, Set[CoroID]] = dict() 265 self.tk_after_ids: Dict[TkObjId, Set[str]] = dict() 266 267 self.updater_running: bool = False 268 269 def start_tk_updater(self): 270 if not self.updater_running: 271 self.updater_running = True 272 tk_updater_coro = self._loop.put_coro(tk_updater, self) 273 tk_updater_coro.is_background_coro = True 274 275 def get_entity_stats(self, stats_level: 'EntityStatsMixin.StatsLevel' = EntityStatsMixin.StatsLevel.debug) -> Tuple[str, Dict[str, Any]]: 276 return type(self).__name__, { 277 'tk_counter': self.tk_counter._index + 1, 278 } 279 280 def single_task_registration_or_immediate_processing(self, request: Optional[TkinterServiceRequest]=None 281 ) -> ServiceProcessingResponse: 282 if request is not None: 283 return self.resolve_request(request) 284 return True, None, None 285 286 def full_processing_iteration(self): 287 # closed 288 new_closed_bak = self.new_closed 289 self.new_closed = type(new_closed_bak)() 290 for tk_obj_id, just_mark_as_destroyed in new_closed_bak.items(): 291 self.new_destroyed[tk_obj_id] = just_mark_as_destroyed 292 293 # waiting_for_destroyed 294 new_destroyed_bak = self.new_destroyed 295 self.new_destroyed = type(self.new_destroyed)() 296 for tk_obj_id, just_mark_as_destroyed in new_destroyed_bak.items(): 297 if tk_obj_id in self.destroyed: 298 continue 299 300 self.destroyed.add(tk_obj_id) 301 302 tk_obj = self.tk_by_id[tk_obj_id] 303 304 # if tk_obj_id in self.tk_after_ids: 305 # after_ids = self.tk_after_ids[tk_obj_id] 306 # for after_id in after_ids: 307 # tk_obj.after_cancel(after_id) 308 309 # del self.tk_after_ids[tk_obj_id] 310 311 # cancel_all_after(tk_obj) 312 313 if not just_mark_as_destroyed: 314 tk_obj.destroy() 315 316 if tk_obj_id in self.tk_by_id: 317 del self.tk_by_id[tk_obj_id] 318 319 if tk_obj_id in self.waiting_for_destroyed: 320 for waiter_coro_id in self.waiting_for_destroyed[tk_obj_id]: 321 self.register_response(waiter_coro_id, None, None) 322 323 del self.waiting_for_destroyed[tk_obj_id] 324 325 if tk_obj_id in self.coro_by_tk: 326 coros_tks = self.coroutines[self.coro_by_tk[tk_obj_id]] 327 del self.coro_by_tk[tk_obj_id] 328 if tk_obj_id in coros_tks: 329 coros_tks.remove(tk_obj_id) 330 331 if tk_obj_id in self.tk_users: 332 tk_users = self.tk_users[tk_obj_id] 333 for tk_user_coro_id in tk_users: 334 # TODO: switch to an appropriate service 335 self._loop.kill_coro_by_id(tk_user_coro_id) 336 337 if tk_obj_id in self.update_periods: 338 del self.update_periods[tk_obj_id] 339 340 # compute min update period 341 if self.update_periods: 342 self.update_period = min(self.update_periods.values()) 343 else: 344 self.update_period = self.default_update_period 345 346 # general 347 if (not self.new_closed) and (not self.new_destroyed): 348 self.make_dead() 349 350 def in_work(self) -> bool: 351 result: bool = bool(self.new_closed) or bool(self.new_destroyed) or bool(self.update_periods) 352 return self.thrifty_in_work(result) 353 354 def _on_create(self, tk_class: Type[Tk], *args, **kwargs) -> ServiceProcessingResponse: 355 tk = tk_class(*args, **kwargs) 356 tk_id: TkObjId = self.tk_counter.get() 357 on_destroy = partial(self._on_create_on_destroyed, tk_id, tk) 358 tk.bind("<Destroy>", on_destroy, '+') 359 old_on_close = tk.protocol("WM_DELETE_WINDOW", None) 360 if (not isfunction(old_on_close)) and (not ismethod(old_on_close)): 361 old_on_close = None 362 363 on_create_on_closed = partial(self._on_create_on_closed, tk_id, tk, old_on_close) 364 tk.protocol("WM_DELETE_WINDOW", on_create_on_closed) 365 self.tk_by_id[tk_id] = tk 366 coro = self.current_caller_coro_info.coro 367 coro_id = self.current_caller_coro_info.coro_id 368 if coro_id not in self.coroutines: 369 self.coroutines[coro_id] = set() 370 coro.add_on_coro_del_handler(self._on_created_coro_del_handler) 371 372 self.coroutines[coro_id].add(tk_id) 373 self.coro_by_tk[tk_id] = coro_id 374 # after_setup(tk, 1) 375 after_idle_setup(tk) 376 self.start_tk_updater() 377 return True, tk_id, None 378 379 def _on_put(self, tk_obj: Tk) -> ServiceProcessingResponse: 380 tk: Tk = tk_obj 381 tk_id: TkObjId = self.tk_counter.get() 382 on_destroy = partial(self._on_put_on_destroyed, tk_id, tk) 383 tk.bind("<Destroy>", on_destroy, '+') 384 old_on_close = tk.protocol("WM_DELETE_WINDOW", None) 385 if (not isfunction(old_on_close)) and (not ismethod(old_on_close)): 386 old_on_close = None 387 388 on_put_on_closed = partial(self._on_put_on_closed, tk_id, tk, old_on_close) 389 tk.protocol("WM_DELETE_WINDOW", on_put_on_closed) 390 self.tk_by_id[tk_id] = tk 391 coro = self.current_caller_coro_info.coro 392 coro_id = self.current_caller_coro_info.coro_id 393 if coro_id not in self.coroutines: 394 self.coroutines[coro_id] = set() 395 coro.add_on_coro_del_handler(self._on_put_coro_del_handler) 396 397 self.coroutines[coro_id].add(tk_id) 398 self.coro_by_tk[tk_id] = coro_id 399 # after_setup(tk, 1) 400 after_idle_setup(tk) 401 self.start_tk_updater() 402 return True, tk_id, None 403 404 def _on_get(self, tk_obj_id: TkObjId) -> ServiceProcessingResponse: 405 if tk_obj_id in self.tk_by_id: 406 return True, self.tk_by_id[tk_obj_id], None 407 else: 408 return True, None, TkObjNotFoundError() 409 410 def _get_inline(self, tk_obj_id: TkObjId) -> Optional[Tk]: 411 return self.tk_by_id.get(tk_obj_id, None) 412 413 def _on_destroy(self, tk_obj_id: TkObjId, just_mark_as_destroyed: bool = False) -> ServiceProcessingResponse: 414 self.new_destroyed[tk_obj_id] = just_mark_as_destroyed 415 self.make_live() 416 return True, None, None 417 418 def _on_destroy_and_wait_for_destroyed(self, tk_obj_id: TkObjId, just_mark_as_destroyed: bool = False) -> ServiceProcessingResponse: 419 self.new_destroyed[tk_obj_id] = just_mark_as_destroyed 420 return self._on_wait_for_destroyed(tk_obj_id) 421 422 def _on_wait_for_destroyed(self, tk_obj_id: TkObjId) -> ServiceProcessingResponse: 423 if tk_obj_id in self.destroyed: 424 return True, None, None 425 426 if tk_obj_id not in self.waiting_for_destroyed: 427 self.waiting_for_destroyed[tk_obj_id] = set() 428 429 self.waiting_for_destroyed[tk_obj_id].add(self.current_caller_coro_info.coro_id) 430 self.make_live() 431 return False, None, None 432 433 def _on_put_coro(self, tk_obj_id: TkObjId, coro_worker: AnyWorker, *args, **kwargs): 434 exception = None 435 coro_id: CoroID = None 436 try: 437 # TODO: switch to an appropriate service 438 coro_id = self._loop.put_coro(coro_worker, *args, **kwargs).coro_id 439 except: 440 exception = get_exception() 441 442 self._register_coro_impl(tk_obj_id, coro_id) 443 return True, coro_id, exception 444 445 def _on_register_coro(self, tk_obj_id: TkObjId, coro_id: CoroID): 446 exception = None 447 try: 448 self._register_coro_impl(tk_obj_id, coro_id) 449 except: 450 exception = get_exception() 451 452 return True, None, exception 453 454 def _register_coro_impl(self, tk_obj_id: TkObjId, coro_id: CoroID): 455 if tk_obj_id not in self.tk_users: 456 self.tk_users[tk_obj_id] = set() 457 458 self.tk_users[tk_obj_id].add(coro_id) 459 460 def _on_set_update_period(self, tk_obj_id: TkObjId, period: float): 461 self.update_periods[tk_obj_id] = period 462 463 self.make_live() 464 return True, None, None 465 466 def _on_created_coro_del_handler(self, coro: CoroWrapperBase) -> bool: 467 for tk_obj_id in self.coroutines[coro.coro_id]: 468 self.new_destroyed[tk_obj_id] = False 469 470 self.make_live() 471 return True 472 473 def _on_put_coro_del_handler(self, coro: CoroWrapperBase) -> bool: 474 for tk_obj_id in self.coroutines[coro.coro_id]: 475 self.new_destroyed[tk_obj_id] = True 476 477 self.make_live() 478 return True 479 480 def _on_create_on_destroyed(self, tk_obj_id: TkObjId, tk_obj: Tk, event): 481 if event.widget is tk_obj: 482 if tk_obj_id not in self.destroyed: 483 self.new_destroyed[tk_obj_id] = True 484 485 self.make_live() 486 487 def _on_put_on_destroyed(self, tk_obj_id: TkObjId, tk_obj: Tk, event): 488 if event.widget is tk_obj: 489 if tk_obj_id not in self.destroyed: 490 self.new_destroyed[tk_obj_id] = True 491 492 self.make_live() 493 494 def _on_create_on_closed(self, tk_obj_id: TkObjId, tk: Tk, previous_on_close: Optional[Callable]): 495 if previous_on_close is not None: 496 previous_on_close() 497 498 if tk_obj_id not in self.destroyed: 499 self.new_closed[tk_obj_id] = False 500 501 self.make_live() 502 503 def _on_put_on_closed(self, tk_obj_id: TkObjId, tk: Tk, previous_on_close: Optional[Callable]): 504 if previous_on_close is not None: 505 previous_on_close() 506 507 if tk_obj_id not in self.destroyed: 508 self.new_closed[tk_obj_id] = False 509 510 self.make_live()
TkinterService( loop: typing.Union[cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.CoroSchedulerGreenlet, cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.CoroSchedulerAwaitable])
237 def __init__(self, loop: CoroSchedulerType): 238 super(TkinterService, self).__init__(loop) 239 240 self._request_workers = { 241 0: self._on_create, 242 1: self._on_put, 243 2: self._on_get, 244 3: self._on_destroy, 245 4: self._on_destroy_and_wait_for_destroyed, 246 5: self._on_wait_for_destroyed, 247 6: self._on_put_coro, 248 7: self._on_register_coro, 249 8: self._on_set_update_period, 250 } 251 252 self.standard_ui_update_interval: float = 1 / 60 253 self.default_update_period: float = max(get_usable_min_sleep_interval(), self.standard_ui_update_interval) 254 self.update_period: float = self.default_update_period 255 self.update_periods: Dict[TkObjId, float] = dict() 256 self.tk_counter = Counter() 257 self.tk_by_id: Dict[TkObjId, Tk] = dict() 258 self.coroutines: Dict[CoroID, Set[TkObjId]] = dict() 259 self.waiting_for_destroyed: Dict[TkObjId, Set[CoroID]] = dict() 260 self.new_destroyed: Dict[TkObjId, bool] = dict() 261 self.new_closed: Dict[TkObjId, bool] = dict() 262 self.destroyed: Set[TkObjId] = set() 263 self.coro_by_tk: Dict[TkObjId, CoroID] = dict() 264 self.tk_users: Dict[TkObjId, Set[CoroID]] = dict() 265 self.tk_after_ids: Dict[TkObjId, Set[str]] = dict() 266 267 self.updater_running: bool = False
def
get_entity_stats( self, stats_level: cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.EntityStatsMixin.StatsLevel = <StatsLevel.debug: 1>) -> Tuple[str, Dict[str, Any]]:
def
single_task_registration_or_immediate_processing( self, request: typing.Union[TkinterServiceRequest, NoneType] = None) -> Tuple[bool, Any, Union[BaseException, NoneType]]:
def
full_processing_iteration(self):
286 def full_processing_iteration(self): 287 # closed 288 new_closed_bak = self.new_closed 289 self.new_closed = type(new_closed_bak)() 290 for tk_obj_id, just_mark_as_destroyed in new_closed_bak.items(): 291 self.new_destroyed[tk_obj_id] = just_mark_as_destroyed 292 293 # waiting_for_destroyed 294 new_destroyed_bak = self.new_destroyed 295 self.new_destroyed = type(self.new_destroyed)() 296 for tk_obj_id, just_mark_as_destroyed in new_destroyed_bak.items(): 297 if tk_obj_id in self.destroyed: 298 continue 299 300 self.destroyed.add(tk_obj_id) 301 302 tk_obj = self.tk_by_id[tk_obj_id] 303 304 # if tk_obj_id in self.tk_after_ids: 305 # after_ids = self.tk_after_ids[tk_obj_id] 306 # for after_id in after_ids: 307 # tk_obj.after_cancel(after_id) 308 309 # del self.tk_after_ids[tk_obj_id] 310 311 # cancel_all_after(tk_obj) 312 313 if not just_mark_as_destroyed: 314 tk_obj.destroy() 315 316 if tk_obj_id in self.tk_by_id: 317 del self.tk_by_id[tk_obj_id] 318 319 if tk_obj_id in self.waiting_for_destroyed: 320 for waiter_coro_id in self.waiting_for_destroyed[tk_obj_id]: 321 self.register_response(waiter_coro_id, None, None) 322 323 del self.waiting_for_destroyed[tk_obj_id] 324 325 if tk_obj_id in self.coro_by_tk: 326 coros_tks = self.coroutines[self.coro_by_tk[tk_obj_id]] 327 del self.coro_by_tk[tk_obj_id] 328 if tk_obj_id in coros_tks: 329 coros_tks.remove(tk_obj_id) 330 331 if tk_obj_id in self.tk_users: 332 tk_users = self.tk_users[tk_obj_id] 333 for tk_user_coro_id in tk_users: 334 # TODO: switch to an appropriate service 335 self._loop.kill_coro_by_id(tk_user_coro_id) 336 337 if tk_obj_id in self.update_periods: 338 del self.update_periods[tk_obj_id] 339 340 # compute min update period 341 if self.update_periods: 342 self.update_period = min(self.update_periods.values()) 343 else: 344 self.update_period = self.default_update_period 345 346 # general 347 if (not self.new_closed) and (not self.new_destroyed): 348 self.make_dead()
def
in_work(self) -> bool:
350 def in_work(self) -> bool: 351 result: bool = bool(self.new_closed) or bool(self.new_destroyed) or bool(self.update_periods) 352 return self.thrifty_in_work(result)
Will be executed twice per iteration: once before and once after the full_processing_iteration() execution
Raises: NotImplementedError: _description_
Returns: bool: _description_
Inherited Members
- cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Service
- current_caller_coro_info
- iteration
- make_response
- register_response
- put_task
- resolve_request
- try_resolve_request
- in_forground_work
- thrifty_in_work
- time_left_before_next_event
- is_low_latency
- make_live
- make_dead
- service_id_impl
- service_id
- destroy
- cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.EntityStatsMixin
- StatsLevel
def
tk_updater( i: cengal.parallel_execution.coroutines.coro_scheduler.versions.v_0.coro_scheduler.Interface, tkinter_service: TkinterService):
591def tk_updater(i: Interface, tkinter_service: TkinterService): 592 """ 593 https://stackoverflow.com/questions/4083796/how-do-i-run-unittest-on-a-tkinter-app 594 https://github.com/ipython/ipython/blob/master/IPython/terminal/pt_inputhooks/tk.py 595 596 Args: 597 i (Interface): [description] 598 tkinter_service (TkinterService): [description] 599 """ 600 from _tkinter import ALL_EVENTS as _tkinter__ALL_EVENTS, WINDOW_EVENTS as _tkinter__WINDOW_EVENTS, FILE_EVENTS as _tkinter__FILE_EVENTS, TIMER_EVENTS as _tkinter__TIMER_EVENTS, IDLE_EVENTS as _tkinter__IDLE_EVENTS, DONT_WAIT as _tkinter__DONT_WAIT 601 from cengal.parallel_execution.coroutines.coro_standard_services.sleep import Sleep 602 from cengal.parallel_execution.coroutines.coro_standard_services.loop_yield import get_loop_yield 603 ly = get_loop_yield(CoroPriority.low) 604 while tkinter_service.tk_by_id: 605 ly() 606 tk_by_id_bak = copy(tkinter_service.tk_by_id) 607 desired_length = len(tk_by_id_bak.keys()) 608 tk_ids_without_events = set() 609 610 while len(tk_ids_without_events) < desired_length: 611 for tk_id, tk_obj in tk_by_id_bak.items(): 612 if tk_id in tk_ids_without_events: 613 continue 614 615 if tk_id in tkinter_service.destroyed: 616 tk_ids_without_events.add(tk_id) 617 continue 618 619 start = perf_counter() 620 has_window_events = tk_obj.dooneevent(_tkinter__WINDOW_EVENTS | _tkinter__DONT_WAIT) 621 stop = perf_counter() 622 if (stop - start) > 0.001: 623 i(Yield) 624 # ly() 625 626 start = perf_counter() 627 has_file_events = tk_obj.dooneevent(_tkinter__FILE_EVENTS | _tkinter__DONT_WAIT) 628 stop = perf_counter() 629 if (stop - start) > 0.001: 630 i(Yield) 631 # ly() 632 633 start = perf_counter() 634 has_timer_events = tk_obj.dooneevent(_tkinter__TIMER_EVENTS | _tkinter__DONT_WAIT) 635 stop = perf_counter() 636 if (stop - start) > 0.001: 637 i(Yield) 638 # ly() 639 640 has_events = has_window_events or has_file_events or has_timer_events 641 642 if not has_events: 643 start = perf_counter() 644 tk_obj.dooneevent(_tkinter__IDLE_EVENTS | _tkinter__DONT_WAIT) 645 stop = perf_counter() 646 if (stop - start) > 0.001: 647 i(Yield) 648 # ly() 649 tk_ids_without_events.add(tk_id) 650 651 i(Sleep, tkinter_service.update_period) 652 653 tkinter_service.updater_running = False
https://stackoverflow.com/questions/4083796/how-do-i-run-unittest-on-a-tkinter-app https://github.com/ipython/ipython/blob/master/IPython/terminal/pt_inputhooks/tk.py
Args: i (Interface): [description] tkinter_service (TkinterService): [description]
def
after_func(root):
656def after_func(root): 657 from cengal.parallel_execution.coroutines.coro_standard_services.simple_yield import Yield 658 from cengal.parallel_execution.coroutines.coro_scheduler import current_interface, OutsideCoroSchedulerContext 659 try: 660 i = current_interface() 661 if i is not None: 662 i(Yield) 663 except OutsideCoroSchedulerContext: 664 pass 665 666 # after_setup(root, 1) 667 after_idle_setup(root)
def
after_setup(root, update_period) -> bool:
def
after_idle_setup(root) -> bool: