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 create(self, tk_class: typing.Type[tkinter.Tk], *args, **kwargs) -> int:
62    def create(self, tk_class: Type[Tk], *args, **kwargs) -> TkObjId:
63        return self._save(0, tk_class, *args, **kwargs)
def put(self, tk_obj: tkinter.Tk) -> int:
65    def put(self, tk_obj: Tk) -> TkObjId:
66        return self._save(1, tk_obj)
def get(self, tk_obj_id: int) -> tkinter.Tk:
68    def get(self, tk_obj_id: TkObjId) -> Tk:
69        return self._save(2, tk_obj_id)
def destroy(self, tk_obj_id: int, just_mark_as_destroyed: bool = False) -> None:
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)
def destroy_and_wait_for_destroyed(self, tk_obj_id: int, just_mark_as_destroyed: bool = False) -> None:
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)
def wait_for_destroyed(self, tk_obj_id: int) -> None:
77    def wait_for_destroyed(self, tk_obj_id: TkObjId) -> None:
78        return self._save(5, tk_obj_id)
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:
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)
def register_coro(self, tk_obj_id: int, coro_id: int) -> None:
83    def register_coro(self, tk_obj_id: TkObjId, coro_id: CoroID) -> None:
84        return self._save(7, tk_obj_id, coro_id)
def set_update_period(self, tk_obj_id: int, period: float) -> None:
86    def set_update_period(self, tk_obj_id: TkObjId, period: float) -> None:
87        return self._save(8, tk_obj_id, period)
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):
90class TkObjDestroyedError(Exception):
91    pass

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)
tk
103    @property
104    def tk(self):
105        if self._destroyed:
106            raise TkObjDestroyedError
107        else:
108            return self._tk_obj
tk_id
110    @property
111    def tk_id(self):
112        if self._destroyed:
113            raise TkObjDestroyedError
114        else:
115            return self._tk_obj_id
def destroy(self, just_mark_as_destroyed: bool = False):
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))
async def adestroy(self, just_mark_as_destroyed: bool = False):
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))
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):
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))
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):
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))
def register_coro(self, coro_id: int):
133    def register_coro(self, coro_id: CoroID):
134        return self._interface(TkinterService, TkinterServiceRequest().register_coro(self.tk_id, coro_id))
async def aregister_coro(self, coro_id: int):
136    async def aregister_coro(self, coro_id: CoroID):
137        return await self._interface(TkinterService, TkinterServiceRequest().register_coro(self.tk_id, coro_id))
destroyed
139    @property
140    def destroyed(self):
141        return self._destroyed or (not self._service._get_inline(self._tk_obj_id))
def set_update_period(self, period: float):
158    def set_update_period(self, period: float):
159        return self._interface(TkinterService, TkinterServiceRequest().set_update_period(self.tk_id, period))
async def aset_update_period(self, period: float):
161    async def aset_update_period(self, period: float):
162        return await self._interface(TkinterService, TkinterServiceRequest().set_update_period(self.tk_id, period))
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):
232class TkObjNotFoundError(Exception):
233    pass

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
standard_ui_update_interval: float
default_update_period: float
update_period: float
update_periods: Dict[int, float]
tk_counter
tk_by_id: Dict[int, tkinter.Tk]
coroutines: Dict[int, Set[int]]
waiting_for_destroyed: Dict[int, Set[int]]
new_destroyed: Dict[int, bool]
new_closed: Dict[int, bool]
destroyed: Set[int]
coro_by_tk: Dict[int, int]
tk_users: Dict[int, Set[int]]
tk_after_ids: Dict[int, Set[str]]
updater_running: bool
def start_tk_updater(self):
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
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]]:
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        }
def single_task_registration_or_immediate_processing( self, request: typing.Union[TkinterServiceRequest, NoneType] = None) -> Tuple[bool, Any, Union[BaseException, NoneType]]:
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
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
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:
670def after_setup(root, update_period) -> bool:
671    try:
672        handler = root.after(update_period, after_func, root)
673    except TclError:
674        return False
675
676    return True
def after_idle_setup(root) -> bool:
679def after_idle_setup(root) -> bool:
680    try:
681        handler = root.after_idle(after_setup, root, 0)
682    except TclError:
683        return False
684
685    return True