flet icon indicating copy to clipboard operation
flet copied to clipboard

bug: Control with ID xxx is not registered for flet_permission_handler when using Python 3.14.

Open Creeper19472 opened this issue 1 month ago • 1 comments

Duplicate Check

  • [x] I have searched the opened issues and there are no duplicates

Describe the bug

This exception will occur only in Python 3.14.0 if PermissionHandler is not explicitly initialized using page.services.append(p). In fact, when I tried to reproduce the problem, the results were slightly different, but the problem was basically the same.

The problem appears to be with the page.push_route function. Without this function, the will_unmount() event will not be triggered for ph.

Code sample

Code
import flet as ft
import flet_permission_handler as fph
import logging

logging.basicConfig(level=logging.DEBUG)

data = {}

async def process():
    p: fph.PermissionHandler = data["p"]
    print("Requesting permission for MANAGE_EXTERNAL_STORAGE")
    await p.request(
        fph.Permission.MANAGE_EXTERNAL_STORAGE
    )
    print("Done requesting permission")
    

async def handler():
    await process()

async def main(page: ft.Page):
    p = fph.PermissionHandler()
    # page.services.append(p)
    data["p"] = p
    await page.push_route("/issues/ph")
    page.run_task(handler)

if __name__ == "__main__":
    ft.run(main)

To reproduce

  1. Run the repro code
  2. It fails

Expected behavior

No exceptions occurred

Screenshots / Videos

Captures

[Upload media here]

Operating System

Windows

Operating system details

Windows 25H2

Flet version

0.70.0.dev6999

Regression

I'm not sure / I don't know

Suggestions

No response

Logs

Logs
DEBUG:asyncio:Using proactor: IocpProactor
INFO:flet:Assets path configured: D:\projects\cfms_client_next\src\include\ui\issues\assets
DEBUG:flet:Creating new PubSubHub instance
INFO:flet:Starting up TCP server on localhost:65094
INFO:flet:Flet app has started...
INFO:flet:App URL: tcp://localhost:65094
INFO:flet_desktop:Starting Flet View app...
INFO:flet_desktop:Looking for Flet executable at: d:\projects\cfms_client_next\.venv\Lib\site-packages\flet_desktop\app\flet\flet.exe
INFO:flet_desktop:Flet View found in: d:\projects\cfms_client_next\.venv\Lib\site-packages\flet_desktop\app\flet\flet.exe
DEBUG:flet:Connected new TCP client
DEBUG:flet_transport:_on_message: ClientAction.REGISTER_CLIENT {'session_id': None, 'page_name': '', 'page': {'route': '/', 'pwa': False, 'web': False, 'debug': False, 'wasm': False, 'test': False, 'multi_view': False, 'pyodide': False, 'platform_brightness': 'dark', 'width': 1265.3333333333333, 'height': 682.6666666666666, 'platform': 'windows', 'window': {'maximized': False, 'minimized': False, 'full_screen': False, 'always_on_top': False, 'focused': True, 'visible': True, 'minimizable': True, 'maximizable': True, 'resizable': True, 'prevent_close': False, 'skip_task_bar': True, 'width': 1280.0, 'height': 720.0, 'top': 10.0, 'left': 10.0, 'opacity': 1.0}, 'media': {'padding': {'top': 0.0, 'right': 0.0, 'bottom': 0.0, 'left': 0.0}, 'view_padding': {'top': 0.0, 'right': 0.0, 'bottom': 0.0, 'left': 0.0}, 'view_insets': {'top': 0.0, 'right': 0.0, 'bottom': 0.0, 'left': 0.0}, 'device_pixel_ratio': 1.5, 'orientation': 'landscape', 'always_use_24_hour_format': True}}}
DEBUG:flet_controls:Page(1 - 2808760225472).did_mount()
DEBUG:flet_controls:View(3 - 2808760225808).did_mount()
DEBUG:flet_controls:Overlay(4 - 2808760226816).did_mount()
DEBUG:flet_controls:Dialogs(5 - 2808760227152).did_mount()
DEBUG:flet_controls:Window(2 - 2808760227488).did_mount()
DEBUG:flet_controls:ServiceRegistry(7 - 2808760227824).did_mount()
DEBUG:flet_transport:send_message: ClientMessage(action=<ClientAction.REGISTER_CLIENT: 1>, body=RegisterClientResponseBody(session_id='EueUsNbuiRkxcflS', page_patch=Page(_i=1, _c='Page', data=None, key=None, expand=None, expand_loose=False, col=12, opacity=1.0, tooltip=None, badge=None, visible=True, disabled=False, rtl=False, adaptive=None, views=[View(_i=3, _c='View', data=None, key=None, expand=None, expand_loose=False, col=12, opacity=1.0, tooltip=None, badge=None, visible=True, disabled=False, rtl=False, width=None, height=None, left=None, top=None, right=None, bottom=None, align=None, margin=None, rotate=None, scale=None, offset=None, aspect_ratio=None, animate_opacity=None, animate_size=None, animate_position=None, animate_align=None, animate_margin=None, animate_rotation=None, animate_scale=None, animate_offset=None, on_animation_end=None, scroll=None, auto_scroll=False, scroll_interval=10, on_scroll=None, controls=[], route='/', appbar=None, bottom_appbar=None, floating_action_button=None, floating_action_button_location=None, navigation_bar=None, drawer=None, end_drawer=None, vertical_alignment=<MainAxisAlignment.START: 'start'>, horizontal_alignment=<CrossAxisAlignment.START: 'start'>, spacing=10, padding=Padding(left=10, top=10, right=10, bottom=10), bgcolor=None, decoration=None, foreground_decoration=None, fullscreen_dialog=False, services=[], can_pop=True, on_confirm_pop=None)], theme_mode=<ThemeMode.SYSTEM: 'system'>, theme=None, dark_theme=None, locale_configuration=None, show_semantics_debugger=None, title=None, enable_screenshots=False, on_resize=None, on_media_change=None, media=PageMediaData(padding=Padding(left=0.0, top=0.0, right=0.0, bottom=0.0), view_padding=Padding(left=0.0, top=0.0, right=0.0, bottom=0.0), view_insets=Padding(left=0.0, top=0.0, right=0.0, bottom=0.0), device_pixel_ratio=1.5, orientation=<Orientation.LANDSCAPE: 'landscape'>, always_use_24_hour_format=True), width=1265.3333333333333, height=682.6666666666666, _overlay=Overlay(_i=4, _c='Overlay', data=None, key=None, controls=[]), _dialogs=Dialogs(_i=5, _c='Dialogs', data=None, key=None, controls=[]), multi_views=[], window=Window(_i=2, _c='Window', data=None, key=None, bgcolor=None, width=1280.0, height=720.0, top=10.0, left=10.0, max_width=None, max_height=None, min_width=None, min_height=None, opacity=1.0, aspect_ratio=None, brightness=None, maximized=False, minimized=False, minimizable=True, maximizable=True, resizable=True, movable=True, full_screen=False, always_on_top=False, always_on_bottom=False, prevent_close=False, skip_task_bar=True, title_bar_hidden=False, title_bar_buttons_hidden=False, frameless=False, progress_bar=None, focused=True, visible=True, shadow=True, alignment=None, badge_label=None, icon=None, ignore_mouse_events=False, on_event=None), route='/', web=False, pwa=False, debug=False, wasm=False, test=False, multi_view=False, pyodide=False, platform_brightness=<Brightness.DARK: 'dark'>, client_ip=None, client_user_agent=None, platform=<PagePlatform.WINDOWS: 'windows'>, fonts=None, on_platform_brightness_change=None, on_app_lifecycle_state_change=None, on_route_change=None, on_view_pop=None, on_keyboard_event=None, on_connect=None, on_disconnect=None, on_close=None, on_login=None, on_logout=None, on_error=None, on_multi_view_add=None, on_multi_view_remove=None, _services=ServiceRegistry(_i=7, _c='ServiceRegistry', data=None, key=None, _services=[])), error=''))
INFO:flet:App session started
DEBUG:flet:Registering service PermissionHandler(9) to registry 7
DEBUG:flet_transport:send_message: ClientMessage(action=<ClientAction.PATCH_CONTROL: 2>, body=PatchControlBody(id=7, patch=[[0], [<Operation.Replace: 0>, 0, '_services', [PermissionHandler(_i=9, _c='PermissionHandler', data=None, key=None)]]]))
DEBUG:flet_controls:PermissionHandler(9 - 2808760229504).did_mount()
DEBUG:flet_transport:send_message: ClientMessage(action=<ClientAction.INVOKE_METHOD: 5>, body=InvokeMethodRequestBody(control_id=1, call_id='m2kHIBrryQ', name='push_route', args={'route': '/issues/ph'}))
DEBUG:flet_transport:_on_message: ClientAction.UPDATE_CONTROL_PROPS {'id': 1, 'props': {'platform_brightness': 'dark'}}
DEBUG:flet_transport:_on_message: ClientAction.CONTROL_EVENT {'target': 1, 'name': 'platform_brightness_change', 'data': 'dark'}
DEBUG:flet_transport:_on_message: ClientAction.UPDATE_CONTROL_PROPS {'id': 1, 'props': {'media': {'padding': {'top': 0.0, 'right': 0.0, 'bottom': 0.0, 'left': 0.0}, 'view_padding': {'top': 0.0, 'right': 0.0, 'bottom': 0.0, 'left': 0.0}, 'view_insets': {'top': 0.0, 'right': 0.0, 'bottom': 0.0, 'left': 0.0}, 'device_pixel_ratio': 1.5, 'orientation': 'landscape', 'always_use_24_hour_format': True}}}
DEBUG:flet_transport:_on_message: ClientAction.UPDATE_CONTROL_PROPS {'id': 1, 'props': {'width': 1265.3333333333333, 'height': 682.6666666666666}}
DEBUG:flet_transport:_on_message: ClientAction.UPDATE_CONTROL_PROPS {'id': 2, 'props': {'maximized': False, 'minimized': False, 'full_screen': False, 'always_on_top': False, 'focused': True, 'visible': True, 'minimizable': True, 'maximizable': True, 'resizable': True, 'prevent_close': False, 'skip_task_bar': True, 'width': 1280.0, 'height': 720.0, 'top': 10.0, 'left': 10.0, 'opacity': 1.0}}
DEBUG:flet_controls:Trigger event Page(1 - 2808760225472).on_platform_brightness_change Event(name='platform_brightness_change', data='dark')
DEBUG:flet_transport:_on_message: ClientAction.UPDATE_CONTROL_PROPS {'id': 1, 'props': {'route': '/issues/ph'}}
DEBUG:flet_transport:_on_message: ClientAction.CONTROL_EVENT {'target': 1, 'name': 'route_change', 'data': {'route': '/issues/ph'}}
DEBUG:flet_transport:_on_message: ClientAction.INVOKE_METHOD {'control_id': 1, 'call_id': 'm2kHIBrryQ', 'result': None, 'error': None}
DEBUG:flet_controls:Trigger event Page(1 - 2808760225472).on_route_change RouteChangeEvent(name='route_change', data=None, route='/issues/ph')
DEBUG:flet:Removed 1 services from the registry
DEBUG:flet_controls:PermissionHandler(9 - 2808760229504).will_unmount()
DEBUG:flet_transport:send_message: ClientMessage(action=<ClientAction.PATCH_CONTROL: 2>, body=PatchControlBody(id=7, patch=[[0], [<Operation.Replace: 0>, 0, '_services', []]]))    
DEBUG:flet_transport:send_message: ClientMessage(action=<ClientAction.INVOKE_METHOD: 5>, body=InvokeMethodRequestBody(control_id=9, call_id='y1iRlVu0yv', name='request', args={'permission': <Permission.MANAGE_EXTERNAL_STORAGE: 'manageExternalStorage'>}))
DEBUG:flet:Receive loop exiting.
DEBUG:flet:Send loop cancelled.
DEBUG:flet:Send loop exiting.
DEBUG:flet:Connection writer closed.
DEBUG:flet_desktop:Flet View process 8596
DEBUG:flet:Closing connection...
DEBUG:flet:Shutting down TCP server...
DEBUG:flet:Shutting down thread pool...
DEBUG:flet:Cancelling pending tasks...
DEBUG:flet:Connection closed.
DEBUG:flet:Session was garbage collected: EueUsNbuiRkxcflS

Additional details

No response

Creeper19472 avatar Dec 11 '25 17:12 Creeper19472

Update: when running the distribution built by flet build windows, this issue will occur too, even with page.services.add(p).

Creeper19472 avatar Dec 12 '25 12:12 Creeper19472

Update: The problem has not been improved in version 0.80.0.

Creeper19472 avatar Dec 26 '25 12:12 Creeper19472

Why do you need to keep a reference to permission handler service instance? It's stateless. You can just use it as:

import flet as ft
import flet_permission_handler as fph
import logging

logging.basicConfig(level=logging.DEBUG)

async def process():
    p = fph.PermissionHandler()
    print("Requesting permission for MANAGE_EXTERNAL_STORAGE")
    await p.request(
        fph.Permission.MANAGE_EXTERNAL_STORAGE
    )
    print("Done requesting permission")
    

async def handler():
    await process()

async def main(page: ft.Page):
    await page.push_route("/issues/ph")
    page.run_task(handler)

if __name__ == "__main__":
    ft.run(main)

FeodorFitsner avatar Jan 13 '26 03:01 FeodorFitsner

I don't seem to have found any statement anywhere indicating that it is stateless, and the documentation about Service can be described as ambiguous. However, a blog states: 'When you create a new instance of a service, it is automatically registered within the page and disposed of when it is no longer referenced - for example, at the end of a method call.' This seems to imply that maintaining a reference to keep the service alive is expected behavior.

You know, it's really frustrating when the documentation is missing instructions on how to register the service.

Creeper19472 avatar Jan 13 '26 03:01 Creeper19472

I understand frustration - the documentation is the work in progress with No 1 priority.

Did it work?

FeodorFitsner avatar Jan 13 '26 03:01 FeodorFitsner

Obviously, this approach within a function scope can of course work, and I am currently using it to circumvent the issue. However, I expect consistency between the current documentation and actual behavior; logically, push_route should not directly unmount a Service that still has references.

Creeper19472 avatar Jan 13 '26 03:01 Creeper19472

I can confirm in Python 3.14 the behavior is different from Python <=3.13. Let me investigate.

FeodorFitsner avatar Jan 13 '26 19:01 FeodorFitsner