Структура проекта Flask-RESTful

Вот с чем я работаю:

/myproject
    README.md
    runserver.py
    /myproject
        __init__.py
        api.py
        /resources
            __init__.py
            foo.py
            bar.py
        /common
            __init__.py
            db.py
    /tests
        test_myproject.py

Здесь нет ничего особенного. Большую часть этого можно найти на странице Промежуточное использование в Руководство пользователя Flask-RESTful.

Меня беспокоит круговой импорт...

api.py

from flask import Flask
from flask_restful import Api

app = Flask(__name__)

from myproject.resources.foo import Foo
from myproject.resources.bar import Bar

api = Api(app)

api.add_resource(Foo, '/Foo', '/Foo/<str:id>')
api.add_resource(Bar, '/Bar', '/Bar/<str:id>')

foo.py

from flask_restful import Resource
from myproject.common.db import query_db


class Foo(Resource):
    def get(self):
        pass
    def post(self):
        pass

db.py

from flask import g
import sqlite3
from myproject.api import app


def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = sqlite3.connect(app.config['DATABASE'])
        db.row_factory = make_dicts
    return db


def query_db(query, args=(), one=False):
    cur = get_db().execute(query, args)
    rv = cur.fetchall()
    cur.close()
    return (rv[0] if rv else None) if one else rv


@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.commit()
        db.close()

Понятно, что я ввел циклический импорт в свой проект:

api.py -> foo.py -> db.py -> api.py

Это не является большой проблемой, если я создаю экземпляр объекта приложения Flask перед импортом своих ресурсов (что я и делаю). Аналогичный шаблон обсуждается здесь (см. раздел Циркулярный импорт внизу страницы).

Мой вопрос...

Это хороший способ структурировать проект Flask-RESTful?

Это самый близкий вопрос SO по этому тему, которую я смог найти. Меня не удовлетворил предоставленный ответ, потому что я не хочу хранить свои функции базы данных в файле __init__.py верхнего уровня (или в api.py, где находятся маршруты).

Вот несколько других подобных вопросов SO, но они имеют дело с ошибками импорта (которыми я не являюсь):

Structure Flask-Restful API для использования SQLAlchemy

Ошибка импорта моделей при попытке запустить приложение


person jamesdarabi    schedule 11.06.2015    source источник


Ответы (1)


Поскольку ваш вопрос в значительной степени основан на мнении, я собираюсь предложить то, что, по моему мнению, было бы лучшим решением :)

Вместо того, чтобы импортировать myproject.api.app в db.py, я бы создал свою собственную глобальную переменную уровня модуля в db.py для хранения конфигурации базы данных:

db.py

from flask import g
import sqlite3

_db_config = None # Holds database config

def init(app):
    """ Function must be called to initalize this module """
    global _db_config
    global close_connection
    _db_config = app.config['DATABASE']
    # Manually apply @app.teardown_appcontext decorator
    close_connection = app.teardown_appcontext(close_connection)


def _db_connect():
    if _db_config is None:
        raise Exception('Call init first') # or whatever error you want
    return sqlite3.connect(_db_config)


def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = _db_connect()
        db.row_factory = make_dicts
    return db

....

def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.commit()
        db.close()

Затем в api.py инициализируйте БД, вызвав myproject.common.db.init()

api.py

from flask import Flask
from flask_restful import Api
from myproject.common import db

app = Flask(__name__)
db.init(app)

....

Таким образом, db.py больше не зависит от api.py.

person junnytony    schedule 11.06.2015
comment
Спасибо за ваш ответ. Мне нравится ваше решение, однако db.py по-прежнему будет зависеть от api.py. См. функцию close_connection с декоратором @app.teardown_appcontext. - person jamesdarabi; 12.06.2015
comment
Это можно легко исправить, если вы не возражаете против применения декоратора вручную :). См. мое редактирование выше. - person junnytony; 12.06.2015
comment
Во-первых, я думаю, вам нужно объявить close_connection глобальным в вашей функции init, иначе Python будет рассматривать его как локальную переменную. Во-вторых, когда я пробую этот подход, я получаю следующую ошибку: ProgrammingError: Cannot operate on a closed database. - person jamesdarabi; 22.06.2015
comment
Верно. Исправил это. Вы можете отследить местонахождение ProgrammingError? - person junnytony; 24.06.2015
comment
Привет, а как насчет runserver.py? - person Saravanan Nandhan; 27.11.2018