I’m trying to make a gui flet that would update when data changes in multiprocessing processes.
The problem is that the data is not updated in the gui. As you can see from the log below, all the necessary functions are called, but the exception AssertionError: Control must be added to the page first.
I think this is because the BaseCallback
class contains a copy of TestItem
in self.render
, not a reference. Don’t proxy objects refer to parent objects that have the desired reference?
Is the self._callmethod('update')
function call passed from the proxy to a parent object that has a reference?
import asyncio
import multiprocessing
from multiprocessing.managers import BaseManager, BaseProxy, NamespaceProxy
from time import sleep
import flet as ft
class BaseItem:
def __init__(self, value = 0, callback_provider = None) -> None:
self._value = value
self.callback_provider = callback_provider
@property
def value(self):
return self._value
@value.setter
def value(self, value):
self._value = value
if self.callback_provider:
self.callback_provider.update()
class Lib:
def __init__(self) -> None:
self.items: list[BaseItem] = [BaseItem(i) for i in range(0, 10)]
def _work_pipeline(self, item: BaseItem):
print('Lib._work_pipeline')
item.value = 1
return item
def do_work(self):
print('Lib.do_work')
_pool = multiprocessing.Manager().Pool(processes=1)
for item in _pool.imap_unordered(self._work_pipeline, self.items):
print(f'Lib.do_work result: {item.value}')
_pool.close()
class TestItem(ft.CupertinoListTile):
def __init__(self, item=None, callback_proxy = None):
super().__init__(title=ft.Text(item.value))
self.item: BaseItem = item
if callback_proxy:
self.item.callback_provider = callback_proxy(self)
def value_update(self):
print('TestItem.update')
self.title = ft.Text(self.item.value)
self.update()
class BaseCallback:
def __init__(self, render) -> None:
self.render = render
def update(self):
print('BaseCallback.update')
self.render.value_update()
class CallbackManager(BaseManager):
pass
class BaseCallbackProxy(BaseProxy):
_exposed_ = ('__getattribute__', '__setattr__', '__delattr__', 'update')
def __init__(self, *args, **kwargs):
super().__init__(*args,**kwargs)
def update(self):
print('BaseCallbackProxy.update')
return self._callmethod('update')
CallbackManager.register('BaseCallbackProxyRegistred', BaseCallback, BaseCallbackProxy)
class FletUI(ft.Column):
def __init__(self):
super().__init__()
self.page = None
self.item_list_view = ft.ListView()
self.lib = Lib()
self._callback_mngr = CallbackManager()
self._callback_mngr.start()
self._callback_proxy = self._callback_mngr.BaseCallbackProxyRegistred
async def startup(self, page: ft.Page) -> None:
self.page = page
self.page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
self.page.vertical_alignment = ft.MainAxisAlignment.CENTER
for item in self.lib.items:
self.item_list_view.controls.append(TestItem(item, self._callback_proxy))
self.page.add(self.item_list_view)
self.page.update()
await self.run_work()
async def run_work(self):
asyncio.get_event_loop().run_in_executor(None, self.lib.do_work).add_done_callback(lambda e: self.page.update())
def run(self):
try:
ft.app(target=self.startup)
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == '__main__':
FletUI().run()
Lib.do_work
Lib._work_pipeline
BaseCallbackProxy.update
BaseCallback.update
TestItem.update
Future exception was never retrieved
future: <Future finished exception=AssertionError('Control must be added to the page first.')>
Traceback (most recent call last):
File "C:UsersWindows.pyenvpyenv-winversions3.12.3Libconcurrentfuturesthread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:UsersDocumentsProjectsfletmain.py", line 34, in do_work
for item in _pool.imap_unordered(self._work_pipeline, self.items):
File "C:UsersWindows.pyenvpyenv-winversions3.12.3Libmultiprocessingmanagers.py", line 1038, in __next__
return self._callmethod('__next__', args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:UsersWindows.pyenvpyenv-winversions3.12.3Libmultiprocessingmanagers.py", line 836, in _callmethod
raise convert_to_error(kind, result)
AssertionError: Control must be added to the page first.