[BUG] Dash 2.1.0 incompatible with Apache wsgi
In summary I am using Apache as the web-server, but the importing of app causes the error:
'Read-only: can only be set in the Dash constructor or during init_app()'
This seems to be similar to https://github.com/plotly/dash/issues/1907. All works fine with dash 2.0.0, but breaks with 2.1.0.
Detailed explanation:
Following https://dash.plotly.com/urls we have:
- app.py
- pages
|-- __init__.py
|-- page1.py
|-- page2.py
We also have the modwsgi python file, /var/www/path_to_site/site_name.wsgi, which Apache is set-up to use, this is what is causing the error:
import sys
sys.path.insert(0, "/var/www/path_to_site/")
from site_name.app import server as application
and the Apache settings /etc/httpd/sites-enabled/site_name.conf
<VirtualHost *:80>
ServerName server_name
WSGIDaemonProcess site_name home=/var/www/path_to_site processes=4 threads=12 python-home=/var/www/path_to_site/venv
WSGIScriptAlias /st40_phys_viewer /var/www/path_to_site/site_name.wsgi
<Directory /var/www/path_to_site>
WSGIProcessGroup site_name
WSGIApplicationGroup %{GLOBAL}
Order allow,deny
Allow from all
</Directory>
<Directory /var/www/path_to_site/assets>
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
I have found a workaround... but it's not ideal.
Modify the following file: /PATH_TO_YOUR_PYTHON_ENVIROMENT/site-packages/dash/_utils.py lines 157-158
Original:
def __setitem__(self, key, val):
if key in self.__dict__.get("_read_only", {}):
raise AttributeError(self._read_only[key], key)
final_msg = self.__dict__.get("_final")
if final_msg and key not in self:
raise AttributeError(final_msg, key)
return super().__setitem__(key, val)
Modified:
def __setitem__(self, key, val):
#if key in self.__dict__.get("_read_only", {}):
# raise AttributeError(self._read_only[key], key)
final_msg = self.__dict__.get("_final")
if final_msg and key not in self:
raise AttributeError(final_msg, key)
return super().__setitem__(key, val)
other suggestions https://github.com/plotly/jupyter-dash/issues/75 are to do this:
del app.config._read_only["requests_pathname_prefix"]
however, for me this did not work.
@pfbuxton are you able to share the full traceback you're seeing? These attributes are read-only for a reason: if they're modified late without being extremely careful about it the app can break in some very confusing ways.
Obviously jupyter-dash needed to do this anyway - and https://github.com/plotly/jupyter-dash/pull/76 fixes it to work with Dash 2.1 - but before we go about extending that pattern elsewhere it would be nice to understand what's going on in your case.
Hi @alexcjohnson,
Sorry for the delay. I needed to create a virtual OS to do some of the testing. We are using CentOS 7.5.
Attached are the relevant files (both settings and errors): FILES.zip
To start the Apache example you can do:
sudo /sbin/service httpd restart
Here is the error log, /etc/httpd/logs/error_log:
[Mon Jul 11 14:44:33.473341 2022] [wsgi:error] [pid 20737] hello1
[Mon Jul 11 14:44:33.473416 2022] [wsgi:error] [pid 20737] /var/www/example/venv/bin/python
[Mon Jul 11 14:44:33.722478 2022] [wsgi:error] [pid 20737] hello2
[Mon Jul 11 14:44:34.996686 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] mod_wsgi (pid=20737): Failed to exec Python script file '/var/www/example/example_wsgi.wsgi'.
[Mon Jul 11 14:44:34.996737 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] mod_wsgi (pid=20737): Exception occurred processing WSGI script '/var/www/example/example_wsgi.wsgi'.
[Mon Jul 11 14:44:34.997070 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] Traceback (most recent call last):
[Mon Jul 11 14:44:34.997097 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] File "/var/www/example/example_wsgi.wsgi", line 9, in <module>
[Mon Jul 11 14:44:34.997100 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] from minimal_example import app
[Mon Jul 11 14:44:34.997113 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] File "/var/www/example/minimal_example.py", line 12, in <module>
[Mon Jul 11 14:44:34.997116 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] requests_pathname_prefix = '/st40_phys_viewer/'
[Mon Jul 11 14:44:34.997120 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] File "/var/www/example/venv/lib64/python3.6/site-packages/dash/_utils.py", line 124, in update
[Mon Jul 11 14:44:34.997123 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] self[k] = v
[Mon Jul 11 14:44:34.997127 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] File "/var/www/example/venv/lib64/python3.6/site-packages/dash/_utils.py", line 113, in __setitem__
[Mon Jul 11 14:44:34.997129 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] raise AttributeError(self._read_only[key], key)
[Mon Jul 11 14:44:34.997143 2022] [wsgi:error] [pid 20737] [remote 10.99.1.36:51432] AttributeError: ('Read-only: can only be set in the Dash constructor or during init_app()', 'routes_pathname_prefix')
Thanks, Peter
I see, thanks. Is there a reason this can't all be moved into the constructor though? Also if you're setting routes_pathname_prefix and requests_pathname_prefix to the same thing ('/example/'), I don't believe there's any purpose setting url_base_pathname to something else. And while we're at it, a few of the other settings you're using here are now the defaults as of Dash 2.0: compress=False and serve_locally=True. And app.enable_dev_tools(debug=False) I believe is a noop. So I'd think you can rewrite this block in minimal_example.py:
app = Dash(__name__, url_base_pathname='/', compress=False)
app.config.update(dict(
routes_pathname_prefix = '/example/',
requests_pathname_prefix = '/example/'
))
app.enable_dev_tools(debug=False)
# Only use local css and javascripts
app.css.config.serve_locally = True
app.scripts.config.serve_locally = True
app.config.suppress_callback_exceptions = True
app.title = 'example'
as:
app = Dash(
__name__,
url_base_pathname='/example/',
suppress_callback_exceptions=True,
title='example'
)
Does that work?
Hi @alexcjohnson,
Your suggestion partially fixed this issue.
With my apache settings (i.e. the /etc/httpd/sites-enabled/site_name.conf file) I needed to have routes_pathname_prefix, and requests_pathname_prefix set using app.config.update. If they were all set during the initial construction Dash complained that they weren't the same as url_base_pathname!
Anyway the solution is to change the scope in the wsgi file form:
WSGIScriptAlias /st40_phys_viewer /var/www/path_to_site/site_name.wsgi
to:
WSGIScriptAlias / /var/www/path_to_site/site_name.wsgi