Search code examples
pythonmultithreadingpython-multithreadingpython-watchdog

Flexx for Python: how to get PyComponent instance outside of the app context (watchdog EventHandler)


I'm currently diving into Flexx for making a UI to my Python program. I'm starting to get the hang of it but I'm running into a problem.

I need to be able to call a PyComponent's method from outside the scope of the App. I understand that app.cls gives the class that was used to instanciate the App, but it's not the actual instance, so I can't do something like app.cls.my_method(...).

Is there a way to get the instance of the component inside the app wrapper, only knowing the App instance?

Thanks!

EDIT: After looking at the source, I found the mostly undocumented AppManager and Session definitions and could get a reference to the component's instance with:

from flexx.app import manager
app_instance = manager.get_connections('MyAppName')[0].app

But if I call one of its methods from a watchdog.events.FileSystemEventHandler instantiated by the watchdog library, I get the following traceback:

  File "myfile.py", line 37, in on_created
    manager.get_connections('MyAppName')[0].app.update_verbose(False)
  File "lib\site-packages\flexx\event\_action.py", line 150, in __call__
    if loop.can_mutate(ob):
  File "lib\site-packages\flexx\event\_loop.py", line 85, in can_mutate
    active = self.get_active_component()
  File "lib\site-packages\flexx\event\_loop.py", line 103, in get_active_component
    if len(self._local._active_components) > 0:
AttributeError: '_thread._local' object has no attribute '_active_components'

Note that update_verbose is a Flexx action that mutates a property. I suspect an issue where the watchdog handler is running on a different thread but I can't see what to change...


Solution

  • As suspected, the problem probably comes from the fact that the FileSystemEventHandler is running on a different thread than the Flexx loop. Thanks to the author almarklein for his help on solving this.

    The solution was to get a reference to the root object, not with the complicated method described above in my question, but rather with the return value from the launch call:

    root = app.launch('app')
    

    Then inside the event handler, tell the main loop to call my method (decorated with @flx.action):

    flx.loop.call_soon(root.my_action, 'argument`)
    

    Hope this helps someone some day!