Avoiding circular imports Python - python

I know this question have already been answered, but for my application, I can't resolve it. What I'm trying to do is to setup a database using MongoAlchemy instead of using MongoClient. I want to have diffrent scripts for every operation This is my main file, app.py:
import os
from flask import Flask, jsonify
from blueprints.db import insert_db
from blueprints.delete_blueprint import delete_bp
from blueprints.insert_blueprint import insert_bp
from blueprints.read_blueprint import read_bp
from blueprints.login_update_blueprint import log_update
import traceback
app = Flask(__name__)
app.register_blueprint(log_update)
app.register_blueprint(read_bp)
app.register_blueprint(insert_bp)
app.register_blueprint(delete_bp)
# database configuration
app.config['MONGOALCHEMY_DATABASE'] = 'rest_api'
# populate table with initial values
insert_db.populateTables()
# mail configuration
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = 'chis.simion12#gmail.com'
app.config['MAIL_PASSWORD'] = ''
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
#app.errorhandler(404)
def page_not_found(c):
return 'The required page does not exist'
#app.errorhandler(400)
def bad_req(c):
return 'Bad request. Please review the request'
#app.errorhandler(Exception)
def handle_Exception(error):
print("\n ______ ERROR ______\n")
print(traceback.format_exc())
print("_____________________\n")
response = dict()
response['message'] = "A PROBLEM HAS OCCURED, PLEASE TRY AGAIN LATER"
response['status_code'] = 500
response = jsonify(response)
response.status_code = 500
return response
if __name__ == '__main__':
app.secret_key = os.urandom(12)
app.run(debug=True, threaded=True)
Note that I'm modyfing on the old version, so the imports are still there. The problem with the circular imports appear when I try do import app from app.py into the script which initializes the database, described below:
from flask import current_app
from flask_mongoalchemy import MongoAlchemy
# from app import app
db = MongoAlchemy(current_app)
class People(db.Document):
Name = db.StringField()
Age = db.IntField()
Password = db.StringField()
Vms = db.AnythingField()
If I try to use current_app I get the error: RuntimeError: Working outside of application context. which means that the app is not currently initialized, and if I import the app from app.py the circular dependency appears. I'm sorry if I wasn't pretty clear, so feel free to ask for more details.

I like to place all extensions on a extensions.py file and create a function to initialize and configurate the app instance
extensions.py
from flask_mongoalchemy import MongoAlchemy
db = MongoAlchemy()
models.py
from extensions import db
class Person(db.Model):
pass
flaskr.py
from flask import Flask
from extensions import db
def create_app():
app = Flask(__name__)
db.init_app(app)
return app
app = create_app()
app.run()

Related

Flask-Pymongo DB is returning as None

I am trying create a webapp with Flask-Pymongo, but it is saying that my database does not exist.
This is my __init__.py:
import os
from flask import Flask
from flask_pymongo import PyMongo
mongo = PyMongo()
def init_app():
app = Flask(__name__)
app.config.from_pyfile('config.py')
mongo.init_app(app)
with app.app_context():
from temp.routes.stage_routes import stage_route
app.register_blueprint(stage_route)
return app
This is my db.py (temp is the top level directory)
from temp.__init__ import mongo
db = mongo.db
and this is one of my blueprints with routes to query the database
from flask import Blueprint
from temp.db import db
stage_route = Blueprint('stage_route', __name__, url_prefix='/stages')
#stage_route.route('/')
def home():
return 'This is the home page for the stage blueprint'
#stage_route.route('/all')
def all():
stage = db.stages.find() # This is where the error is
print(stage)
For some reason I get the error saying that "NoneType does not have attribute 'stages'" because it is saying that the db variable is none. I can't figure out why this is is happening since the database and the collection does exist and the MONGO_URI string is loaded from the config file. I can see that it is connecting on the mongodb side, but i'm assuming it has something to do with my create_app() function in the init.py file. Do you see something that I am missing? Any help would be appreciated
The code is missing the connection URI string, as mentioned in the documentation -
from flask import Flask
from flask_pymongo import PyMongo
app = Flask(__name__)
# The missing URI
app.config["MONGO_URI"] = "mongodb://localhost:27017/myDatabase"
mongo = PyMongo(app)

Pytest using development sqlite db during testing - NoneType object has no attribute drivername

I was trying to access a sqlite db I use on development server, but I got following error:
'NoneType' object has no attribute 'drivername'. It happend both during calling business logic from a view or during calling db.create_all() inside test client definition (currently removed from code).
Test file code:
import os
import json
import pytest
from flask import Flask
import run
from src.utils.config import TestingConfig
from src.models.user_model import User
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI')
SQLALCHEMY_ECHO = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
#pytest.fixture
def client():
app = run.create_app(TestingConfig)
context = app.app_context()
context.push()
with context:
with app.test_client() as client:
yield client
context.pop()
def test_user_register(client):
user_data = {
'login': 'login',
'password': 'password',
'email': 'email#example.com',
'first_name': 'first_name',
'last_name': 'last_name',
'index': '000000'
}
rv = client.post('/api/v1/users/register', data=json.dumps(user_data), content_type='application/json')
user_data['user_role'] = 2
assert '' == rv.data
Create app code:
from flask import Flask, current_app
from flask_cors import CORS
from flask_swagger_ui import get_swaggerui_blueprint
from src.utils.config import Config, DevelopmentConfig, ProductionConfig
from src.utils.extensions import db, ma, migrate, cors
from src.views import user_view, task_view, task_submission_view
from src.exceptions import exceptions_view
def create_app(config):
app = Flask(__name__)
app.config.from_object(config)
app.register_blueprint(exceptions_view.handler)
app.register_blueprint(user_view.blueprint, url_prefix='/api/v1/users')
app.register_blueprint(task_view.blueprint, url_prefix='/api/v1/tasks')
app.register_blueprint(task_submission_view.blueprint, url_prefix='/api/v1/taskSubmissions')
swagger_blueprint = get_swaggerui_blueprint('/swagger', '/static/swagger.json')
app.register_blueprint(swagger_blueprint)
db.init_app(app)
ma.init_app(app)
migrate.init_app(app, db)
cors.init_app(app)
return app
.env fragment :
SQLALCHEMY_DATABASE_URI = "sqlite:///sqlite.db"
Answer:
When I created the app from an external script run.create_app() even though blueprints were registered correctly the configuration was wiped out after the script execution.
After some trial-and-error runs I found out that configurating app by function app.config.from_object(), more precisely during os.getenv(), was an issue - even after moving it to the test file it wasn't able to configurate the app.
Solution was to set the configuration manually (inside or outside the test file):
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sqlite.db'

Converting a flask app to an application factory pattern screws up all imports

So i've been building a flask app just using an app.py file and running it.
It has quite a big app now and i'm now just trying to convert it into an application factory because I need to use SQLAlchemy in my Celery tasks.
here is my init.py in my app folder
def create_app():
load_dotenv(".env")
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///data.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["BROKER_URL"] = os.getenv("REDIS_BACKEND_BROKER")
app.config["CELERY_BROKER_URL"] = os.getenv("REDIS_BACKEND_BROKER")
app.config["CELERY_IMPORTS"] = "celery_tasks"
app.secret_key = os.getenv("SECRET_KEY")
CORS(app)
api = Api(app)
jwt = JWTManager(app)
db.init_app(app)
ma.init_app(app)
celery.init_app(app)
#app.before_first_request
def create_tables():
db.create_all()
#jwt.invalid_token_loader
def invalid_token_callback(self):
return {"message": "invalid"}, 401
with app.app_context():
from .resources.auth import Auth, CheckUser
from .resources.period import Period
from .resources.project import Project
from .resources.session import Session
api.add_resource(Auth, "/auth")
api.add_resource(CheckUser, "/check")
api.add_resource(Project, "/createproject")
api.add_resource(Period, "/createperiod")
api.add_resource(Session, "/createsession")
return app
The problem is that all the resources that being imported breaks because they can no longer import based on modules either.
For example resources.period also imports SQLAlchemy models and Masrhmallow schemas
resources/period.py
#THESE ARE NO LONGER IMPORTED SUCCESSFULLY
from models.project import ProjectModel
from schemas.task import TaskSchema
from schemas.period import PeriodSchema
Here is my file structure
This is an awesome tutorial by Miguel Grinberg where he refactores a complete application like you want it, too:
https://www.youtube.com/watch?v=NH-8oLHUyDc&t=2934s
Did you try to make an "absolute" import like:
from app.models.project import ProjectModel
Since you're importing from resources/period.py using relative imports, you need to go up a level:
from ..models.project import ProjectModel
from ..schemas.task import TaskSchema
from ..schemas.period import PeriodSchema

Access to Flask Global Variables in Blueprint Apps

my source code has this structure:
main.py:
from flask import Flask, g
app = Flask(__name__)
with app.app_context():
g.my_db = PostgreSQL()
app.register_blueprint(my_app, url_prefix="/my_app")
my_app.py:
from flask import Blueprint, g
my_app = Blueprint("my_app", __name__)
#my_app.route("/")
def index():
return g.my_db.fetch_all() <<< ERROR
but it shows this error:
AttributeError: '_AppCtxGlobals' object has no attribute 'my_db'
Even when I try to use g outside of app context, it shows this error:
RuntimeError: Working outside of application context.
So how to set and access to global variables in Flask?
This happens because the data are lost when the context (with app.app_context()) ends (doc).
Inside the context, everything is ok :
from flask import Flask, g
app = Flask(__name__)
with app.app_context():
g.my_db = 'database ok'
print(g.my_db)
# >>> this prints 'database ok'
But outside, you cannot access the attribute :
from flask import Flask, g
app = Flask(__name__)
with app.app_context():
g.my_db = 'database ok'
print(g.my_db)
# >>> this throws RuntimeError: Working outside of application context
even if you create a new context:
from flask import Flask, g
app = Flask(__name__)
with app.app_context():
g.my_db = 'database ok'
with app.app_context():
print(g.my_db)
>>> this throws AttributeError: '_AppCtxGlobals' object has no attribute 'my_db'
Your best call should be to declare the database object before the context, and then import it. Or maybe you can create it directly inside my_app.py where you need it ?
g isn't persistent in the way you're trying to use it. Write a function to create a connection each time you need it. Preferably use a database extension like Flask-SQLAlchemy to manage connections for you.
db.py:
import <postgresql dependencies>
def get_db():
db = PostgreSQL()
# config here
return db
main.py:
from flask import Flask
app = Flask(__name__)
app.register_blueprint(my_app, url_prefix="/my_app")
my_app.py:
from flask import Blueprint, g
from db import get_db
my_app = Blueprint("my_app", __name__)
#my_app.route("/")
def index():
db = get_db()
data = db.fetch_all()
db.close()
return data

Flask - working outside of application context

I am testing a Flask application and am receiving a "working outside of application context" error. My file directory is as follows:
api
app.py
__init__.py
models
__init__.py
user.py
resources
__init__.py
deals.py
stores.py
common
__init__.py
calculations.py
decorators.py
My app.py file looks like the following:
import os
from flask import Flask, jsonify, url_for, redirect, request, g, current_app
from flask_pymongo import PyMongo
from flask_restful import Api, Resource
from flask_httpauth import HTTPTokenAuth
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.httpauth import HTTPBasicAuth
from resources.deals import Deals
from resources.stores import Stores
from models.user import User
USERDBFILE=os.path.join(os.path.join(os.path.dirname(os.path.realpath(__file__)),'database'),'db.sqlite')
#Deals database
app = Flask(__name__)
app.config["MONGO_DBNAME"] = "database"
mongo = PyMongo(app,config_prefix='MONGO')
app.db = mongo
#User database
app.config['SECRET_KEY'] = 'SECRET KEY'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.dbuser = SQLAlchemy(app)
#App url
app.APP_URL = "http://127.0.0.1:5000"
#Setup authorization
auth = HTTPTokenAuth(scheme='Token')
#Setup the app
api = Api(app)
api.add_resource(Deals, '/deals', '/Deals/<string:type>/<string:id>',endpoint="dealType")
api.add_resource(Stores, '/stores', '/Stores/<string:type>/<string:id>',endpoint="type")
if __name__ == "__main__":
if not os.path.exists(USERDBFILE):
app.dbuser.create_all()
app.run(debug=True)
My users.py file is as follows:
from flask import current_app
import os
from flask import Flask, abort, request, jsonify, g, url_for
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.httpauth import HTTPBasicAuth
from passlib.apps import custom_app_context as pwd_context
from itsdangerous import (TimedJSONWebSignatureSerializer
as Serializer, BadSignature, SignatureExpired)
class User(current_app.dbuser.Model):
__tablename__ = 'user_api'
id = current_app.dbuser.Column(current_app.dbuser.Integer,primary_key=True)
date_created = current_app.dbuser.Column(current_app.dbuser.DateTime,default=current_app.dbuser.func.current_timestamp())
date_modified = current_app.dbuser.Column(current_app.dbuser.DateTime,default=current_app.dbuser.func.current_timestamp(),
onupdate=current_app.dbuser.func.current_timestamp())
# User Name
name = current_app.dbuser.Column(current_app.dbuser.String(128),nullable=False)
# Identification Data: email & password
email = current_app.dbuser.Column(current_app.dbuser.String(128),nullable=False,unique=True)
password = current_app.dbuser.Column(current_app.dbuser.String(192),nullable=False)
company = current_app.dbuser.Column(current_app.dbuser.String(128),nullable=False,unique=True)
# Authorization Data: role & status
role = current_app.dbuser.Column(current_app.dbuser.String(32),nullable=False,default='user')
status = current_app.dbuser.Column(current_app.dbuser.Boolean,nullable=False,default=True)
hourly_limit = current_app.dbuser.Column(current_app.dbuser.Integer,nullable=False,default=100)
daily_limit = current_app.dbuser.Column(current_app.dbuser.Integer,nullable=False,default=2400)
monthly_limit = current_app.dbuser.Column(current_app.dbuser.Integer,nullable=False,default=2400)
admin = current_app.dbuser.Column(current_app.dbuser.Boolean,nullable=False,default=True)
def hash_password(self, password):
self.password_hash = pwd_context.encrypt(password)
def verify_password(self, password):
return pwd_context.verify(password, self.password_hash)
def generate_auth_token(self, expiration=600):
s = Serializer(current_app.config['SECRET_KEY'], expires_in=expiration)
return s.dumps({'id': self.id})
#staticmethod
def verify_auth_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(token)
except SignatureExpired:
return None # valid token, but expired
except BadSignature:
return None # invalid token
user = User.query.get(data['id'])
return user
I run the file in the same directory as app.py using
python app.py
But it returns the following error:
File "app.py", line 13, in <module>
from models.user import User
File "/Users/toby/api/api/models/user.py", line 10, in <module>
class User(current_app.dbuser.Model):
File "/Users/toby/api/venv/lib/python3.4/site-packages/werkzeug/local.py", line 343, in __getattr__
return getattr(self._get_current_object(), name)
File "/Users/toby/api/venv/lib/python3.4/site-packages/werkzeug/local.py", line 302, in _get_current_object
return self.__local()
File "/Users/toby/api/venv/lib/python3.4/site-packages/flask/globals.py", line 34, in _find_app
raise RuntimeError('working outside of application context')
RuntimeError: working outside of application context
If I move the contents of the user.py file into the app.py file and change the inheritance from current_app.dbuser.Model to app.dbuser.Model it seems to work fine. Does anyone know what I am doing wrong?
Flask-Sqlalchemy binds some sqlalchemy concepts like the session, the engine, and the declaritive base to the flask app. This is convenient because you only have one thing to instantiate at your uwsgi entry point (the app object) but a pain when testing- because you have to instantiate the app object.
EDIT- I am leaving the part about testing below, but I reread your question and realized you're not actually trying to test anything.
You don't have access to the 'current_app' object at import time (when you are trying to initialize your sqlalchemy models). Instead, you have to actually import the app object from your app file. This of course means you have to worry about circular dependencies...
I have a method called 'register_routes' that gets called after I initialize the app object that imports models and views files that require access to the app object at import time.
#at the bottom of app.py
def register_models(app):
from models import User
register_models(app)
# in models.py
from app import app
class User(app.dbuser.Model):
...
EDIT- the below discusses this issue with respect to unit testing
Flask-Testing is a project that attempts to solve these problems, and is almost certainly appropriate for a beginner in this area- it provides a test class to inherit from that will set up your flask app before test cases and tear it down after. (As you come to understand the various globals and what they do you may want to move away from this... but it is very helpful for getting started!)
If you don't want to do that, you need to create an app and initialize an app context before doing anything with your flask-sqlalchemy models. This may just be
app = myapp.create()
with app.test_request_context():
# do some testing...
You will probably want to refresh this in between methods, otherwise global state will leak between test cases.
Basically, flask uses quite a lot of global variables like current_app, request etc. which only exist when a flask app is instantiated and running and in various states.
You have used current_app in the definition of the User object which is evaluated as soon as the file is imported by Python. You need to ensure you only use values like this when an app is already running.
You could move the instantiation of the User class until after the app exists, but I think the root problem is why are you using current_app.dbuser.Boolean rather than say sqlalchemy.types.Boolean?
I'm not a great expert on flask.ext.sqlalchemy, but my guess is you don't need to load those definitions of things like Column and Boolean from the particular instance of the app you have. Using the static definitions from sqlalchemy would prevent you from having a dependency from the User class to the app.

Categories