python-3.xbuttonpython.netdisablenicegui

cannot pickle 'Decimal' object when using pythonnet and nicegui


In nicegui, I have a button to start a function that creates a class. I want the button to be disabled during the creation of the class, which takes a few seconds in my real code (not the example I am posting here). I am using the nicegui example: disable button with a context manager as a starting point. The class I create has an attribute that requires pythonnet and the use of Decimal module (from System). I am not so familiar with pythonnet, but the class works when I use it without the cpu_bound function of nicegui. Otherwise, I get a TypeError: cannot pickle 'Decimal' object. As I understand, I need this cpu_bound to disable my button during the class creation. How can I avoid the TypeError above?

I have simplified my code as much as possible so that it still shows the error:

from contextlib import contextmanager
from nicegui import run, ui
import clr
from System import Decimal

@contextmanager
def disable(button: ui.button):
    button.disable()
    try:
        yield
    finally:
        button.enable()

class my_class:
    def __init__(self, value):
        self.value = Decimal(value)

def some_function():
    return my_class(1)

async def get_slow_response(button: ui.button) -> None:
    with disable(button):
        return_value = await run.cpu_bound(some_function)

ui.button('Get slow response', on_click=lambda e: get_slow_response(e.sender))

ui.run(port=80)

I'm not sure if it is related, but I noticed that to import Decimal, I also need to have the import clr and in VS Code, System carries a "could not be resolved" warning. Although, as I said, the class works fine in its own. Simply removing this Decimal in the class fixes the issue, but then my class needs this attribute to be Decimal so it can pass it to a DLL.

I am using pythonnet 3.0.3, nicegui 1.4.22 on Python 3.10.14 (I am on Windows if that matters).


Solution

  • I somehow managed to get the intended behavior using asyncio.to_thread instead of run.cpu_bound. The part with the contextmanager didn't work anymore, but the code does what I want. I am not qualified to explain why it works but perhaps someone else can comment on this.

    import asyncio
    import clr
    import time
    from nicegui import ui
    from System import Decimal
    
    class my_class:
        def __init__(self, value):
            self.value = Decimal(value)
    
    def some_function():
        time.sleep(1)
        return my_class(1)
    
    async def get_slow_response(button: ui.button) -> None:
        button.disable()
        return_value = await asyncio.to_thread(some_function)
        button.enable()
    
    ui.button('Get slow response', on_click=lambda e: get_slow_response(e.sender))
    
    ui.run(port=80)
    

    I also documented this on the nicegui repo in discussion #2985.