-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BUG] long_callback fails when bundling python code #1885
Comments
I run into the same problem under Windows 10. |
@Simon-12 a workaround with pyinstaller is to write the long callbacks in a separate file and include it using 'datas'. |
@JonThom thanks for the fast response. I will test during the next week and will write a feedback here. |
Here is a sketch of the suggested approach: def register_long_callbacks(app):
@app.long_callback(
Output("id1","prop1"),
Input("id2","prop2"),
)
def lc1(input_1):
...
@app.long_callback() .. Then in the main app file, from .long_callbacks import register_long_callbacks
my_app = Dash()
register_long_callbacks(my_app)
... Now, in the pyinstaller .spec file, include the
|
@JonThom I missed this issue when you first raised it, apologies and thanks for bringing this case to our attention. The problematic line you link to is in building a cache key for this function invocation, I believe the reason we're using the function source for that is so the cache will invalidate if you modify the callback code itself. Clearly not a concern if you've wrapped the app up and shipped it, it's really only for apps being actively developed. @T4rk1n I bet we can fall back on |
@JonThom It was possible for me to implement the workaround and the error message disapered. |
Just a warning, Pyinstaller doesn't obfuscate any part of the code, I can take the executable and get all the original source quite easily. The only way to keep the code secure is keeping it private and deploying the server. |
I agree the callback_id should be included in the cache key for callback factories that may share the same function but have different outputs/inputs. |
Thanks @T4rk1n, I should have made clear that including the source as plain text just makes it even more trivial to access it. |
No, I don't think obfuscation is a reliable way to protect code. If there is intellectual property that should be protected by licensing. Otherwise private code or secrets used to access databases belongs on servers. |
I dont want to secure my code, i just want to distribute the app to my friends who are absolutely not familiar with programming. I wrote a small example, just a simple dash app with a long callback: github.com/Simon-12/simple-dash When i start the long callback function nothing happens, the app magically restarts and opens a new tab. Cheers |
Hi @Simon-12 , have you solve this problem so far? Would be nice of you to share with me the workaround if you figured it out. I'm deploying a desktop dashboard using |
Hi @DeKhaos |
@JonThom , I tried as you suggested, but I got the same result as @Simon-12, the error message doesn't show up but the background callback doesn't work either. Every time I triggered the Run button, the server restarts and nothing happens. I used example 4 from Background callbacks and separate the callback to another file as you suggested and included to
|
I have taken your code and made a working app using pyinstaller |
Thank you for your time and the clear instruction @JonThom Although I copied your whole repository and install poetry and followed every step, running My environment is Window 10 64-bit and |
Sorry to hear the pyinstaller repo isn't working for you, either. Edit: I looked again at your debug info, have you checked out that |
Hi @DeKhaos, sorry for my late response but there was a lot of stuff to do. I just tried all kind of python bundle tools like My final workaround is. I just copied my whole python environment folder to the target device. For example, my python environment is under:
And that’s it. I tested it on three different devices. In detail the python bundle tools do the same thing, They take your whole python environment and pack it up into an executable file (exe). When you start the exe, everything gets unpacked into a temp folder (this is actually very time consuming). I also created a python script to automate the copy process, maybe I can provide you next week. Finally, it’s not the perfect solution but it works fine me😊 Cheers |
@JonThom Yeah, maybe it's because of the differences in OS. Things that work on your environment doesn't seem to work for me. With @Simon-12 , really nice approach. I think that might work for me, it would be nice to see your code snippet soon. |
Hi, here is my build script: import os
import sys
import shutil
target_path = 'build/python-dash'
target_code = target_path + '/code'
target_assets = target_code + '/assets'
data = ['config.ini']
start_file = 'start_app.bat'
copy_interpreter = True
create_zip = False
def main():
print('Creates build folder ...')
if not os.path.exists('build'):
os.mkdir('build')
# Python interpreter
if copy_interpreter:
print('Copy python interpreter ...')
if os.path.exists(target_path):
shutil.rmtree(target_path)
path_env = sys.executable
path_env = path_env.replace('python.exe', '')
shutil.copytree(path_env, target_path)
# Copy code
if os.path.exists(target_code):
shutil.rmtree(target_code)
os.mkdir(target_code)
files = os.listdir('.')
for f in files:
if '.py' not in f or '.pytest' in f:
continue # skip
shutil.copyfile(f, f'{target_code}/{f}')
# Needed files
for d in data:
shutil.copyfile(d, f'{target_code}/{d}')
# assets folder
if os.path.exists(target_assets):
shutil.rmtree(target_assets)
shutil.copytree('assets', target_assets)
# Start up file
if not os.path.isfile(f'build/{start_file}'):
shutil.copyfile(start_file, f'build/{start_file}')
if create_zip:
print('Create zip folder ...')
if os.path.isfile('python-dash.zip'):
os.remove('python-dash.zip')
shutil.make_archive('python-dash', 'zip', 'build')
print('Finished!')
if __name__ == '__main__':
main() I also update my repository with a working example: github.com/Simon-12/simple-dash Cheers |
@DeKhaos I had forgotten that the pyinstaller workflow and .spec files are OS-specific. Can you try the updated example https://github.com/JonThom/dash-background-callbacks-pyinstaller? For simplicity, I have updated the project to use PS: At least on macOS, pyinstaller builds both a single executable, as well as an app bundle. This is why the |
Adding the Python-source to the import multiprocessing
multiprocessing.freeze_support() as described by @rokm in pyinstaller/pyinstaller-hooks-contrib#493 did the trick for me! |
Describe your context
Please provide us your environment, so we can easily reproduce the issue.
pip list | grep dash
belowDescribe the bug
When using Pyinstaller to bundle a Dash app using long_callback as a single MacOS .app file, the app fails to register the long_callback.
The error occurs in
dash/dash/long_callback/managers/__init__.py
Line 38 in 78ca3ec
with the call to inspect.getsource, which throws an OSError, stating that the source code cannot be retrieved.
In this case, the long_callback calls external libraries used to query a database.
The error only occurs when packaging with Pyinstaller.
Although the Pyinstaller packaging is probably a fairly rare use case, I was wondering why access to source is needed, and of course whether there might be an obvious workaround.
Expected behavior
Expect the long_callback to register correctly.
The text was updated successfully, but these errors were encountered: