Правильное структурирование вашего Flask проекта (без Blueprint) - PullRequest
0 голосов
/ 09 апреля 2020

Intro

Я прочитал около тысячи постов на SO и других сайтах, пытаясь выяснить, что не так с моей структурой Flask и почему я не могу что-то выяснить. В качестве последнего средства я решил наконец задать вопрос здесь.

Мой проект действительно прост:

  1. Мне нужно получить некоторые данные с некоторых сетевых устройств через API, обработать данные и сохраните их в Postgresql БД (большая часть кода находится в lib/).
  2. Этот проект будет развернут в нескольких средах (test, dev, staging и prod).

Для этого я использую следующее:


Детали проекта

Структура моего проекта выглядит следующим образом:

my_project/
├── api/  
├── app.py             
├── config.py
├── __init__.py
├── lib/
│   ├── exceptions.py
│   └── f5_bigip.py
├── log.py
├── logs/
├── manage.py
├── migrations/
├── models/
│   ├── __init__.py
│   ├── model1.py
│   └── model2.py
└── run.py

My app.py выглядит так:

import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from dotenv import load_dotenv
from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy


db = SQLAlchemy()
migrate = Migrate()


def create_app():
    load_dotenv()

    app = Flask(__name__)

    environment = app.config['ENV']

    if environment == 'production':
        app.config.from_object('config.ProductionConfig')
    elif environment == 'testing':
        app.config.from_object('config.TestingConfig')
    else:
        app.config.from_object('config.DevelopmentConfig')

    db.init_app(app)
    migrate.init_app(app, db)

    return app

Мой config.py выглядит так:

import os

from sqlalchemy.engine.url import URL

PROJECT_ROOT = os.path.dirname(os.path.realpath(__file__))


class BaseConfig:
    DEBUG = False
    TESTING = False

    DB_DRIVERNAME = os.getenv('DB_DRIVERNAME')
    DB_HOST = os.getenv('DB_HOST')
    DB_PORT = os.getenv('DB_PORT')
    DB_NAME = os.getenv('DB_NAME')
    DB_USERNAME = os.getenv('DB_USERNAME')
    DB_PASSWORD = os.getenv('DB_PASSWORD')

    DB = {
        'drivername': DB_DRIVERNAME,
        'host': DB_HOST,
        'port': DB_PORT,
        'database': DB_NAME,
        'username': DB_USERNAME,
        'password': DB_PASSWORD,
    }

    SQLALCHEMY_DATABASE_URI = URL(**DB)
    SQLALCHEMY_TRACK_MODIFICATIONS = False


class DevelopmentConfig(BaseConfig):
    DEVELOPMENT = True
    DEBUG = True


class TestingConfig(BaseConfig):
    TESTING = True


class StagingConfig(BaseConfig):
    DEVELOPMENT = True
    DEBUG = True


class ProductionConfig(BaseConfig):
    pass

Мой __ init__.py выглядит так:

from contextlib import contextmanager

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# here, create_engine needs the SQLALCHEMY_DATABASE_URI
# how do I get it from the proper config?
engine = create_engine()
Session = sessionmaker(bind=engine)


@contextmanager
def session_scope():
    """
    Provide a transactional scope around a series of operations.
    """
    session = Session()
    try:
        yield session
        session.commit()
    except Exception as e:
        print(f'Something went wrong here: {str(e)}. rolling back.')
        session.rollback()
        raise
    finally:
        session.close()

My manage.py выглядит так:

from flask_script import Manager
from flask_migrate import MigrateCommand

from app import create_app


from models import *


manager = Manager(create_app)
manager.add_command('db', MigrateCommand)

if __name__ == '__main__':
    manager.run()

My models / model1.py выглядит так:

from sqlalchemy.dialects.postgresql import INET
from sqlalchemy.sql import func

from app import db


class Model1(db.Model):
    __tablename__ = 'model1'

    id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
    ip_address = db.Column(INET, unique=True, nullable=False)
    last_update = db.Column(db.DateTime(), server_default=func.now())

    def __repr__(self):
        return f'<Model1: {self.ip_address}>'

    def __init__(self, ip_address):
        self.ip_address = ip_address

Вопросы

Теперь у меня есть три основных вопроса:

  1. В моем основном __init__.py Как я могу импорт SQLALCHEMY_DATABASE_URI из конфигурации приложения?
  2. Наличие объекта Session() в __init__.py не кажется слишком интуитивным. Должен ли он быть размещен в других местах? Для большего контекста session используется в lib/f5_bigip.py и, вероятно, будет использоваться также в api/.
  3. В целом структура проекта в порядке?

1 Ответ

1 голос
/ 09 апреля 2020

Ваши вопросы 1 и 2 напрямую связаны с частью вашего проекта, которая мне показалась странной, поэтому вместо ответа на эти вопросы я просто дам вам гораздо более простой и лучший способ.

Кажется, в __init__.py вы реализуете свои собственные сеансы базы данных, просто для того, чтобы вы могли создать контекстный менеджер сессий с областью действия. Может быть, вы получили этот код из другого проекта? Он не очень хорошо интегрирован с остальной частью вашего проекта, который использует расширение Flask -SQLAlchemy, вы просто игнорируете Flask -SQLAlchemy, который управляет вашими соединениями с базой данных и сессиями, и в основном создает другое соединение с базой данных и новые сеансы.

Вместо этого вам следует использовать соединения и сеансы, которые предоставляет Flask -SQLAlchemy. Я бы переписал __init__.py следующим образом (делая это по памяти, поэтому извините за незначительные ошибки):

from contextlib import contextmanager

from app import db


@contextmanager
def session_scope():
    """
    Provide a transactional scope around a series of operations.
    """
    try:
        yield db.session
        session.commit()
    except Exception as e:
        print(f'Something went wrong here: {str(e)}. rolling back.')
        db.session.rollback()
        raise
    finally:
        db.session.close()

При этом вы повторно используете соединение / сеансы из Flask -SQLAlchemy. Ваш вопрос 1 больше не является проблемой. Для вопроса 2 вы должны использовать db.session в любом месте вашего приложения, где вам нужны сеансы работы с базой данных.

Что касается вопроса 3, я думаю, вы в основном в порядке. Я бы посоветовал вам не использовать Flask -Script, который является довольно старым и не поддерживаемым расширением. Вместо этого вы можете переместить свой CLI в собственную поддержку CLI Flask.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...