Flask-admin не может импортировать имя _class_resolver под uWSGI и Nginx

Я размещал проект Python Flask в AWS с использованием uWSGI и Nginx, изначально все работало нормально, но затем я решил внедрить модуль Flask-admin. Теперь страница показывает мне сообщение об ошибке внутреннего сервера.

Я проверил журнал UWSGI и говорит:

  Traceback (most recent call last):
  File "/var/www/project/run.py", line 2, in <module>
    from app import app
  File "/var/www/project/app/__init__.py", line 22, in <module>
    from app import views
  File "/var/www/project/app/views.py", line 15, in <module>
    from flask_admin.contrib.sqla import ModelView
  File "/var/www/project/venv/local/lib/python2.7/site-packages/flask_admin/contrib/sqla/__init__.py", line 2, in <module>
    from .view import ModelView
  File "/var/www/project/venv/local/lib/python2.7/site-packages/flask_admin/contrib/sqla/view.py", line 17, in <module>
    from flask_admin.contrib.sqla.tools import is_relationship
  File "/var/www/project/venv/local/lib/python2.7/site-packages/flask_admin/contrib/sqla/tools.py", line 4, in <module>
    from sqlalchemy.ext.declarative.clsregistry import _class_resolver
ImportError: cannot import name _class_resolver
Fri Nov 10 07:41:08 2017 - unable to load app 0 (mountpoint='project.domain.com|') (callable not found or import error)
Fri Nov 10 07:41:08 2017 - --- no python application found, check your startup logs for errors ---

Мой файл app/views.py:

# -*- coding: utf-8 -*-
import os, time
import flask
from flask import render_template, request, jsonify, send_from_directory, send_file, session, redirect, g, url_for, make_response, session, request
from flask_sqlalchemy import SQLAlchemy
from models import RequestsLog
import requests
import json
from app import app, db, appname #Line edited
import flask_admin as admin
#from flask_admin import Admin, helpers, expose  ------Line Erased
from flask_admin.contrib.sqla import ModelView
#from flask_admin.contrib import sqla   ----------- Line Erased

class MyAdminIndexView(admin.AdminIndexView):
        @admin.expose('/')
        def index(self):
                count = 56
                return self.render('admin/index.html', count=count)

class CustomView(ModelView):
       can_create = False
       can_edit = False
       can_delete = False  # disable model deletion
       can_export = True
       export_columns = ['phone_number', 'doc_number', 'date', 'doc_type']
       can_view_details = True       
       column_export_exclude_list = ['job_id', 'status']
       list_template = 'list.html'
       details_modal = True
       page_size = 50
       can_set_page_size = True

admin = admin.Admin(app, appname, template_mode='bootstrap3', index_view=MyAdminIndexView())
admin.add_view(CustomView(RequestsLog, db.session, menu_icon_type ='fa', menu_icon_value='fa-calendar'))

РЕДАКТИРОВАТЬ: Если я прокомментирую эту строку, все работает как надо:

from flask_admin.contrib.sqla import ModelView

Поиск в Google Я заметил, что ImportError может быть вызван из-за избыточного импорта, но я думаю, что необходим весь импорт flask-admin.

Я думаю, что проблема не вызвана uWSGI, но вот файл конфигурации:

[uwsgi]
uid = www-data
gid = www-data

plugins=python
vhost=true
socket=/tmp/project.sock
chmod-socket = 666
chown-socket = www-data:www-data

enable-threads = true
procname-prefix = project_
chdir = /var/www/project

Я снова создал виртуальную среду, но все установлено идеально.

РЕДАКТИРОВАТЬ: Следует отметить, что когда я запускаю файл run.py напрямую, все работает, как ожидалось, даже с импортом из-под flashk-admin, проблема только под uWSGI.

Наконец, мой проект размещен в зависимой среде Ubuntu 14.04 Trusty AWS.

РЕДАКТИРОВАТЬ:

Добавление файла run.py:

#!venv/bin/python
from app import app

if __name__ == '__main__':

    app.run(host='0.0.0.0', threaded=True, debug=True, port=5010)

РЕДАКТИРОВАТЬ 2:

Добавление файла __init__.py:

# -*- coding: utf-8 -*-
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_socketio import SocketIO
#import flask_admin as admin
from flask_babel import Babel

#I've defined the next three lines to avoid encoding conflicts
import sys
reload(sys)
sys.setdefaultencoding("utf-8")

app = Flask(__name__)
app.config.from_pyfile('../config.py')
appname = 'Project'

db = SQLAlchemy(app, session_options={'autoflush': False})
babel = Babel(app)

async_mode = None
socketio = SocketIO(app, async_mode=async_mode, ping_timeout=1800)

from app import views

venv/bin/pip freeze выход:

Babel==2.5.1
Flask==0.12.2
Flask-Admin==1.5.0
Flask-Babel==0.11.2
Flask-SQLAlchemy==2.3.2
Flask-SocketIO==2.9.2
Jinja2==2.9.6
MarkupSafe==1.0
SQLAlchemy==1.1.14
WTForms==2.1
Werkzeug==0.12.2
argparse==1.2.1
asn1crypto==0.22.0
certifi==2017.7.27.1
chardet==3.0.4
click==6.7
click-completion==0.2.1
colorama==0.3.9
crayons==0.1.2
elibom==1.2
gunicorn==19.7.1
idna==2.6
itsdangerous==0.24
psycopg2==2.7.3.1
py==1.4.34
pycycle==0.0.8
pytest==3.2.3
python-engineio==1.7.0
python-socketio==1.8.1
pytz==2017.3
requests==2.18.4
six==1.11.0
uWSGI==2.0.15
urllib3==1.22
wsgiref==0.1.2

Конфигурационный файл Nginx:

server {
        listen  80 ssl;
    server_name project.domain.com; 
    listen 443 ssl;

        ssl_certificate /etc/nginx/project.domain.com/fullchain.pem;
        ssl_certificate_key /etc/nginx/project.domain.com/privkey.pem;
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50m;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !MEDIUM !RC4";
        ssl_prefer_server_ciphers on;

        ssl_stapling on;
        ssl_stapling_verify on;

    location /static/ {
                autoindex               on;
                alias                   /var/www/project/app/static/;
        }

        location / {
                include                 uwsgi_params;
                uwsgi_pass              unix:/tmp/project.sock;
                uwsgi_read_timeout      300;
                uwsgi_param             UWSGI_PYHOME    /var/www/project/venv;
                uwsgi_param             UWSGI_CHDIR     /var/www/project;
                uwsgi_param             UWSGI_MODULE    run;
                uwsgi_param             UWSGI_CALLABLE  app;
        }

        error_page 404 /404.html;

        location /socket.io {
            include proxy_params;
            proxy_http_version 1.1;
            proxy_buffering off;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
            proxy_pass http://127.0.0.1:5010/socket.io;
    }
}

Большое спасибо за вашу помощь.

1 ответ

Я начну с немедленной ошибки, а затем рассмотрю причины, по которым она может отличаться от uWSGI.

Немедленная ошибка:

Здесь происходит небольшое ускорение импорта, что является обычным явлением, когда вы что-то создаете или используете функции. Хорошее место для начала - очистить его, чтобы уменьшить избыточность и очистить его. Даже если вы не решите проблему в пути, каждое удаление будет легче продумывать оставшийся импорт.

В дополнение к изменениям, предложенным Луисом в комментариях, вы можете объединить следующие две строки, которые используют разные соглашения для импорта из одного места:

Импортировать flask_admin как администратор из flask_admin. Импортировать администратора, помощников, выставить

Вы можете либо просто импортировать flask_admin:

import flask_admin
...
class MyAdminIndexView(flask_admin.AdminIndexView):
    @flask_admin.expose('/')
...
admin = flask_admin.Admin...

Или вы могли бы добавить AdminIndexView к списку явных импортов:

from flask_admin import Admin, AdminIndexView, helpers, expose

Большинство ошибок импорта Flask, с которыми я столкнулся, связано с попыткой импортировать другой объект из вашего приложения, который еще не был должным образом инициализирован (часто из-за циклического импорта), поэтому я предполагаю, что он скрывается за одной из этих 3 строк:

from app import app
...
from models import RequestsLog
...
from app import db, appname

Причина может выпасть на вас, но иногда цикл состоит из нескольких модулей. Если этого не произойдет, следующим шагом будет запуск вашего проекта через линтер, такой как Pylint. Если вы уже используете Pylint, вы можете дважды проверить ваши настройки и убедиться, что правила, связанные с импортом, включены! Если вы еще не используете его, вы можете запустить его вручную из командной строки или использовать его через плагин редактора, который может запускать ваш код во время работы (вероятно, вы столкнетесь со многими проблемами, если вы еще этого не сделали). linting - но я бы просто сосредоточился на кодах импорта / модуля, чтобы начать).

Вы также можете найти это быстрее с помощью более сфокусированного инструмента, такого как pycycle, хотя я еще сам не попробовал.

Иногда вы сможете избежать проблем циклического импорта во Flask с помощью тщательного проектирования или небольшого рефакторинга, но по мере роста сложности вашего приложения новые функции с большей вероятностью приведут вас к циклическому импорту. Если вы выявили проблему, но не нашли очевидного пути ее решения, следующим шагом, как правило, является принятие шаблона фабрики приложений Flask. Этот паттерн не волшебен - вы все равно можете нарисовать себя в углах, связанных с импортом, - но он, как правило, облегчает их рефакторинг.

Запуск от uWSGI:

На данный момент, я думаю, что это две наиболее вероятные причины, по которым вы будете видеть другое поведение при запуске через python из командной строки и из-за uWSGI. Это просто общий список, поскольку более конкретный ответ зависит от нескольких вещей, помимо файлов, которые вы включили. У uWSGI и nginx есть довольно сложный набор параметров конфигурации, и я вылетаю из памяти, поэтому я могу быть не совсем точен...:)

  1. Приложение настроено условно, возможно, с использованием переменных среды или что-то вроде if __name__ == '__main__': Таким образом, вы не запускаете точно один и тот же код в обоих случаях.

  2. uWSGI не использует тот же вызываемый / модуль. Между файлом конфигурации и командной строкой, я помню, было несколько способов сообщить uWSGI, какой файл python загружать и как называется объект python приложения, но я не помню, чтобы это можно было сделать из nginx., Я не вижу ни одного из них, указанных в вашем файле конфигурации, но включенный вами фрагмент журнала предполагает, что исключение импорта происходит, когда uWSGI пытается найти вызываемый объект (обычно app или же application) в run.py, Вы используете тот же код из CLI?

Другие вопросы по тегам