I have the following flask factory directory setup:
server/
__init__.py
.env
wsgi.py
app/
__init__.py
config/
__init__.py
config.py
test_config.py
models/
__init__.py
sku.py
views/
__init__.py
sku_bp.py
tests/
__init__.py
test_sku_view.py
The create_app method is defined in server/app/__init__.py:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(config):
app = Flask(__name__, instance_relative_config=False)
app.config.from_object(config)
db.init_app(app)
with app.app_context()
from app.views.sku_bp import sku_bp
app.register_blueprint(sku_bp, url_prefix='/api/sku/')
return app
And the model (server/app/models/sku.py) and blueprint (server/app/views/sku_bp.py) are given by:
from app import db
class SKU(db.Model):
__tablename__ = 'sku'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
def to_dict(self):
return {'id': self.id, 'name': self.name}
and
from flask import Blueprint, jsonify
from app.models.sku import SKU
sku_bp = Blueprint('sku_bp', __name__)
#sku_bp.route('/get/')
#sku_bp.route('/get/<string:sku_name>')
def get(sku_name: str):
try:
sku = SKU.query.filter_by(name=sku_name).first()
except Exception as e:
return jsonify({'Error': f'{e}'}), 404
else:
return jsonify(sku.to_dict()), 200
respectively.
I am trying to the compute unit tests in server/tests/test_sku_view.py as follows:
import unittest
import sys
sys.path.append('./')
from flask_testing import TestCase
import pandas as pd
from app.models.sku import SKU
from app.config.test_config import TestConfig
from app import create_app, db
class TestSKUView(TestConfig, TestCase):
def create_app(self):
return create_app(TestConfig)
def setUp(self):
db.create_all()
df_sku = pd.read_csv('tests/io/test.sku.csv')
for index, row in df_sku.iterrows():
sku = SKU(**row)
db.session.commit()
def tearDown(self):
db.session.remove()
db.drop_all()
def test_sku_view(self):
skus = SKU.query.all()
print(f'skus = {skus}')
def main():
unittest.main()
if __name__ == '__main__':
main()
where the server/tests/io/test.sku.csv file looks like this:
id,name
1,'A'
2,'B'
and the server/app/config/test_config.py file looks like this:
class TestConfig():
DEBUG = True
TESTING = True
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True
SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/test.db'
I call this script from within the server/ directory using python3.7 tests/test_sku_view.py. However, when I print the skus list which queries the database, nothing is returned, i.e. skus = []. Where am I going wrong here, please? I know the sku is being generated properly but it seems not to save in the database on commit ...
You need to add a db.session.add(sku) before the commit, obviously in the for loop :-)
Related
I was trying to package my code as it was getting kind of complex for me to keep in one file and i encountered an import error when i tried to run the file that says circular import error, how do i solve this error? I have been analyzing the code and i cannot seem to be able to figure out what might be wrong.
run.py
from market import app
if __name__ == "__main__":
app.run(debug=True)
init.py
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from market import routes
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///market.db"
db = SQLAlchemy(app)
routes.py
from market import app
from flask import render_template
from market.models import Item
#app.route("/")
#app.route("/home")
def home():
return render_template("index.html")
#app.route("/market")
def market():
items = Item.query.all()
return render_template("market.html", items=items)
models.py
from market import db
class Item(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(length=30), nullable=False, unique=True)
price = db.Column(db.Integer(), nullable=False)
barcode = db.Column(db.String(length=12), nullable=False, unique=True)
description = db.Column(db.String(length=1024), nullable=False, unique=True)
def __repr__(self):
return f"Item {self.name}"
project structure
error
Moving your routes import to the bottom of the file should help.
Just as you would do for example with blueprints in application factory. You import blueprints/views after you create app instance with app = Flask(__name__):
def create_app(config_filename):
app = Flask(__name__)
app.config.from_pyfile(config_filename)
from yourapplication.model import db
db.init_app(app)
from yourapplication.views.admin import admin
from yourapplication.views.frontend import frontend
app.register_blueprint(admin)
app.register_blueprint(frontend)
return app
Also check:
Is a Python module import at the bottom ok?
in your __init__.py you import routes
in routes.py you import app (defined in __init__.py)
I'm have flask init with create app function. I create test file for my unitests. In unitest class add setUp function were i create new flask app and add context, push context to it. Next i create test db with create_all() function and, where i start test file, i have next error:
in _execute_for_all_tables op(bind=self.get_engine(app, bind), **extra)
TypeError: create_all() got an unexpected keyword argument 'forms'
I haven't 'forms' files or variable on models or anywere.
Before this error place in flask_sqlalchemy/init.py MetaData(bind=None), may be it some help.
from flask import Flask
from config import Config
from blocker_application.database import db
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_moment import Moment
migrate = Migrate()
login = LoginManager()
login.login_view = 'user.login'
moment = Moment()
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
db.init_app(app)
migrate.init_app(app, db)
login.init_app(app)
moment.init_app(app)
from blocker_application.main.routes import bp_main
from blocker_application.errors.handlers import bp_error
from blocker_application.reports.routes import bp_reports
from blocker_application.user.routes import bp_user
from blocker_application.applications.routes import bp_applications
app.register_blueprint(bp_main)
app.register_blueprint(bp_error)
app.register_blueprint(bp_reports, url_prefix='/reports')
app.register_blueprint(bp_user, url_prefix='/user')
app.register_blueprint(bp_applications, url_prefix='/applications')
return app
from blocker_application import models
________________________________________________________________________
/config.py
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'not realy finish secret key configuration'
SQLALCHEMY_DATABASE_URI = 'mysql://some_database'
SQLALCHEMY_TRACK_MODIFICATIONS = False
class TestConfig(Config):
TESTING = True
SQLALCHEMY_BINDS = {'test': 'mysql://some_database_test'}
______________________________________________________________________
/tests.py
import unittest
from blocker_application import create_app, db
from blocker_application import models
from config import TestConfig
class UserModelCase(unittest.TestCase):
def setUp(self):
self.app = create_app(TestConfig)
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all(bind='test')
def tearDown(self):
db.session.remove()
db.drop_all(bind='test')
self.app_context.pop()
def test_password(self):
u = models.User(username='Mark')
u.set_password('Mark')
self.assertTrue(u.check_password('Mark'))
self.assertFalse(u.check_password('Tony'))
if __name__ == '__main__':
unittest.main(verbosity=2)
I found decision. Unittest work ok, after recreate the virtual enveroment.
I am using Flask Blueprints to create an application. I am testing it with pytest. Generating test_client in different states with pytest.fixture is causing a blueprint name collision.
This is my Flask __init__ code:
from flask import Flask
from flask_dropzone import Dropzone
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_user import UserManager
from flask_googlemaps import GoogleMaps
import os
from app.settings import app, db
# get extensions
dropzone = Dropzone()
google_maps = GoogleMaps()
migrate = Migrate()
def create_app(config_class=Config):
# initialise extensions
google_maps.init_app(app)
dropzone.init_app(app)
db.init_app(app)
migrate.init_app(app, db)
# FLASK-User Stuff
from app.models import User
user_manager = UserManager(app, db, User)
from app.errors import bp as errors_bp
app.register_blueprint(errors_bp)
from app.main import bp as main_bp
app.register_blueprint(main_bp)
return app
I am then trying to use multiple pytests to test different aspects of the application. For each I am creating a pytest.fixture to generate a test_client. For example I'm creating two fixtures for testing the app with the db in different states.
For example.
#pytest.fixture(scope='module')
def client_state1():
app = create_app()
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' + PATH_TO_DB_STATE_1
app.config['TESTING'] = True
client = app.test_client()
ctx = app.app_context()
ctx.push()
yield client
ctx.pop()
#pytest.fixture(scope='module')
def client_state2():
app = create_app()
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' + PATH_TO_DB_STATE_2
app.config['TESTING'] = True
client = app.test_client()
ctx = app.app_context()
ctx.push()
yield client
ctx.pop()
def test_with_state1(client_state1):
"""Test should pass"""
assert 1 == 1
def test_with_state2(client_state2):
"""Test should pass"""
assert 1 == 1
When I run the tests I get the following error.
AssertionError: A blueprint's name collision occurred between <flask.blueprints.Blueprint object at 0x1a16b5d4e0> and <flask.blueprints.Blueprint object at 0x1a1633f6a0>. Both share the same name "googlemaps". Blueprints that are created on the fly need unique names.
I have tried splitting out tests into separate files and setting the scope of the fixture to function. But to no avail. In all cases the first test passes and the second causes the collision. What am I doing wrong?
I am trying this "Application Factory" stuff in Flask.
When I run the application via flask, everything works fine (as expected).
#__init__.py
from flask import Flask
from .config import config_by_name
from flask import Flask
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config_by_name[config_name])
return app
#config.py
class Config:
SECRET_KEY = os.getenv('SECRET_KEY', 'my_precious_secret_key')
SQLALCHEMY_TRACK_MODIFICATIONS = False
DEBUG = False
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = "postgresql://postgres#10.11.12.13:1234/DB"
SECRET_KEY = 'dev!'
class TestingConfig(Config):
DEBUG = True
TESTING = True
PRESERVE_CONTEXT_ON_EXCEPTION = False
SQLALCHEMY_DATABASE_URI = 'sqlite:///testing.db'
SECRET_KEY = 'test!'
config_by_name = dict(
dev=DevelopmentConfig,
test=TestingConfig
)
key = Config.SECRET_KEY
#app.py
from flask import Flask
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from application import create_app
app = create_app('dev')
engine = create_engine(app.config['SQLALCHEMY_DATABASE_URI'])
DBSession = sessionmaker(bind=engine)
session = DBSession()
#app.route('/item/<int:id>', methods=['GET', 'POST'])
def do_something(id):
return '%s' % session.bind
if __name__ == '__main__':
app.run()
When I call http://localhost:5000/item/1 I get the expected result:
Engine(postgresql://postgres#10.11.12.13:1234/DB)
Now I want to write some tests. How can I switch to the "testing config"?
# test_app.py
import unittest
from application.app import app
from flask_testing import TestCase
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
class BaseTestCase(TestCase):
def create_app(self):
app.config.from_object('application.config.TestingConfig')
return app
def setUp(self):
engine = create_engine(app.config['SQLALCHEMY_DATABASE_URI'])
DBSession = sessionmaker(bind=engine)
self.session = DBSession()
def tearDown(self):
pass
def test_edit(self):
response = self.client.get('/item/9999')
print(response.data)
if __name__ == '__main__':
unittest.main()
When I run the unittests I get the also the "dev" DB-Connection:
python -m unittest application/test_app.py
b'Engine(postgresql://postgres#10.11.12.13:1234/DB)'
How do I get the "sqlite:///testing.db" connection. What am I doing wrong?!
The main problem is that you are trying to use 'created app' in tests. Look at test.py:
from application.app import app
You imported configured app. What doesn't mean? The following code was executed:
app = create_app('dev')
After that you try to update configured app in tests but it doesn't work:
def create_app(self):
app.config.from_object('application.config.TestingConfig')
The easiest way is just use ENV variable for configuration:
def create_app(config_name):
app = Flask(__name__)
# get config name from env variable
config_name = os.environ.get('FLASK_CONFIG', config_name)
app.config.from_object(config_by_name[config_name])
return app
From terminal:
export FLASK_CONFIG=test
# you can run app with 'test' config python application/app.py
# or run tests etc...
nosetests ./tests/ --exe --logging-level=INFO
Hope this helps.
I'm new in bluprint, and have problem with importing db into mydatabase.py file which is models file.
I've faced with this error:
ImportError: cannot import name 'db'
The tree of my project
nikoofar/
run.py
bookshelf/
__init__.py
mydatabase.py
main/
controllers.py
__init__.py
run.py
from bookshelf import app
if __name__ == '__main__':
app.run(debug=True, port=8000)
bookshelf / intit.py
from flask import Flask
from bookshelf.main.controllers import main
from flask_sqlalchemy import SQLAlchemy
from mydatabase import pmenu
app = Flask(__name__, instance_relative_config=True)
db = SQLAlchemy(app)
db.init_app(app)
application.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://username:password#localhost/databasename'
app.config.from_object('config')
app.register_blueprint(main, url_prefix='/')
bookshelf / main / controllers.py
from flask import Blueprint
from bookshelf.mydatabase import *
from flask_sqlalchemy import SQLAlchemy
main = Blueprint('main', __name__)
#main.route('/')
def index():
g = pmenu.query.all()
print (g)
return "ok"
The problem backs to from bookshelf import db, and if I delete that, the error will be changed to:
ImportError: cannot import name 'db'
bookshelf / mydatabase.py
from bookshelf import db
class pmenu(db.Model):
__tablename__ = 'p_menu'
id = db.Column(db.Integer, primary_key=True)
txt = db.Column(db.String(80), unique=True)
link = db.Column(db.String(1024))
def __init__(self, txt, link):
self.txt = txt
self.link = link
def __repr__(self):
return "{'txt': " + self.txt + ", 'link':" + self.link + "}"
Any solution?
This is actually a simple, yet frustrating issue. The problem is you are importing main BEFORE you are creating the instance of db in your __init__.py
If move the import to after your db = SQLAlchemy(app), it will work:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://uername:password#localhost/test'
db = SQLAlchemy(app)
from bookshelf.main.controllers import main #<--move this here
app.register_blueprint(main, url_prefix='/')