I have to create an Flask application which need to read data from multiple almost identical schemas placed on this same Oracle instance. I am able to connect to this database only by 'read_only' user.
By do this I am using blueprints.
project
|-warehouse01
| -__init__.py
| - view.py
|-warehouse02
| -__init__.py
| - view.py
|-warehouse999
| -__init__.py
| - view.py
-model.py
run.py
config.py
Of course model for all schemas is identical
class Product(db.Model):
#__table_args__ = {'schema': 'warehouse01'}
__tablename__ = 'PRODUCTS'
prod_no = db.Column(db.Integer, primary_key=True, nullable=False)
prod_name = db.Column(db.String(35), nullable=False)
quantity = db.Column(db.Integer, nullable=False)
...
steps = db.relationship('Shipment', backref='shipments', lazy='dynamic')
class Shipment(db.Model):
__tablename__ = 'SHIPMENTS'
#__table_args__ = {'schema': 'warehouse01'}
shipm_no = db.Column(db.Integer, , primary_key=True, nullable=False)
prod_no = db.Column(db.Integer, db.ForeignKey('PRODUCTS.prod_no'), nullable=False)
customer_id= db.Column(db.Integer, db.ForeignKey('CUSTOMERS.customer_id') nullable=False)
...
Problem is that connecting by different schema (READ_ONLY) I have to declare a schema in table_args
I would like to use this same model to all schemas. Obviously I do not want to create separate models for all blueprints.
Is it possible to use this same model for all schemas or redefine in blueprint a schema name?
I think that you can use a factory function to create your models on the fly:
def create_product_model(schema):
class Product(db.Model):
__table_args__ = {'schema': schema}
__tablename__ = 'PRODUCTS'
prod_no = db.Column(db.Integer, primary_key=True, nullable=False)
prod_name = db.Column(db.String(35), nullable=False)
quantity = db.Column(db.Integer, nullable=False)
...
steps = db.relationship('Shipment', backref='shipments', lazy='dynamic')
return Product
Finally I have found a way to do this. Unfortunately a approach proposed by #Laurent LAPORTE was not proper. It works only when I had just one class in model, and only after adding 'extend_existing': True. Please correct me if I am wrong but in Flask a database model is declared at application start. As schema name is a part of a class declared in __table_args__ = {'schema': schema}, while you would like to change the schema, SqlAlchemy treats it as try to defining new class with existing name in model registry.
My solution is to remove schema from table_args and setup a schema name by sql command like below:
def estimate_schema(schema):
db.engine.execute(f"alter session set current_schema={schema}")
This I use before each route and it works
def index():
estimate_schema('warehouse01')
...
Related
I'm having an issue with circular imports, and I'm not sure how to restructure my code to overcome it.
Say I have the structure
├── app
│ ├──__init__.py
│ ├── models_one.py
│ ├── models_two.py
With models_one.py being the following:
from . import db
from .models_two import Post
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
posts = db.relationship("Post")
def get_user_posts_with_content(self, passed_content):
return Post.query.filter(Post.user_id==self.id, Post.content==passed_content).all()
And models_two.py being the following:
from . import db
from .models_one import User
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(64))
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
def get_user(self):
return User.query.filter(User.id == self.user_id).first()
This creates a circular import, is there a way to do this without having both classes in the same file? If I had both in a models.py this would work, but for a larger project with 30 models I need a better way to segment them all.
I know that you can get all "posts" in this example by calling User.posts, but in the User model you can see how I use a passed variable as part of the query, my project uses quite a bit of this sort of thing.
I'm also aware that it's because within both classes I have a function that calls a query on the other object, but this simplifies some of the template rendering rather than having to load the data within the view first (unless there's something I'm missing with web development, which is VERY possible).
Thanks for any advice you can give, and sorry if this is a simple question, I've read through the O'Reilly book on Flask Web Development and tackled some simpler projects, but I don't understand this well enough even with a day of googling.
I found this artical. Maybe this helps you. But what the page basically says is, to merge all into one file, so I guess there is no way to do this how you want to do it. But maybe you can delete the methods from the class and insert them into a third module. So basically:
modules_one:
from . import db
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
posts = db.relationship("Post")
modules_two:
from . import db
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(64))
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
moduels_three:
from .models_one import User
from .models_two import Post
def get_user_posts_with_content(self, passed_content):
return Post.query.filter(Post.user_id==self.id, Post.content==passed_content).all()
def get_user(self):
return User.query.filter(User.id == self.user_id).first()
I have two model classes in separate files, created a One-To-Many relationship between them.
user.py:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), index=True, unique=True, nullable=False)
password = db.Column(db.String(128), nullable=False)
projects = db.relationship('Project', backref='owner', lazy='dynamic')
project.py:
class Project(db.Model):
__tablename__ = 'projects'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), index=True, unique=True, nullable=False)
owner_id = db.Column(db.Integer, db.ForeignKey('User.id'))
This is how I create my app:
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
db = SQLAlchemy(app)
#app.before_first_request
def create_tables():
db.create_all()
app.run(debug=True)
However when I create a request I get the following error:
sqlalchemy.exc.NoReferencedTableError:
Foreign key associated with column 'projects.owner_id' could not find
table 'User' with which to generate a foreign key to target column 'id'
I understand this is a duplicate of this question asked before, however I tried that and did not work:
#app.before_first_request
def create_tables():
from projectx.models.auth.user import User
from projectx.models.private_data.project import Project
db.create_all()
I also tried giving the same __table_args__ = {"schema": "testdb"} args to both models (I manually created the db with this name) and refer to db.ForeignKey('testdb.User.id) to no avail, the error is the same.
What am I missing here?
It seems I misunderstood some concepts about how this relationship abstraction works.
The foreign key reference is to the tablename and not the class name naturally.
I should have written db.ForeignKey('users.id').
In a flask application, I have a user table in multiple schemas. I can list the users inside a schema by specifying the schema in the model using __table_args__
class User(db.Model):
__table_args__ = {"schema": "schema1"} #fetch the user table in "schema1" schema
id = db.Column(db.Integer, primary_key=True)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
name = db.Column(db.String)
But I have no idea to change the schema name dynamically. How to change the schema name by passing a string from the controller?
So I'm working on an webapp using Flask. I followed a naming convention in my data models but it seemed that this convention does not properly integrate well with Flask-extensions for specific field naming, quoting for instance, from Flask-Security extension
Models
Flask-Security assumes you’ll be using libraries such as SQLAlchemy,
MongoEngine, Peewee or PonyORM to define a data model that includes a
User and Role model. The fields on your models must follow a
particular convention depending on the functionality your app
requires. Aside from this, you’re free to add any additional fields to
your model(s) if you want. At the bare minimum your User and Role
model should include the following fields:
User
id
email
password
active
...
Now assume my user model is something like:
class User(UserMixin, db.Model):
'''This model represents all types of Users registered'''
__tablename__ = 'users'
user_id = db.Column(db.Integer, primary_key=True)
user_email = db.Column(db.String(64), unique=True, index=True)
user_password_hash = db.Column(db.String(128))
If I have to change my model's field to what Flask-extension requires, that requires me to change in a lot of files, which is a tedious task to do.
What I thought of is something like this:
class User(UserMixin, db.Model):
'''This model represents all types of Users registered'''
__tablename__ = 'users'
user_id = db.Column(db.Integer, primary_key=True)
id = self.user_id #For Flask-Extensions
user_email = db.Column(db.String(64), unique=True, index=True)
email = self.user_email #For Flask-Extensions
user_password_hash = db.Column(db.String(128))
password = self.user_password_hash #For Flask-Extensions
How bad is this solution and what alternatives I have?
I think you can use Synonyms.
I didn't check but I think this should works.
from sqlalchemy.orm import synonym
class User(UserMixin, db.Model):
__tablename__ = 'users'
user_id = db.Column(db.Integer, primary_key=True)
id = synonym('user_id')
user_email = db.Column(db.String(64), unique=True, index=True)
email = synonym('user_email')
user_password_hash = db.Column(db.String(128))
password = synonym('user_password_hash')
that's more or less workable and something I've done. I'd recommend implementing it using a property:
#property
def id(self):
return self.user_id
If Flask-Security needs the property accessible at the class level as well, you can use SQLAlchemy's hybrid_property instead.
I've defined some models with some association tables for m2m relationships:
from itsdangerous import TimedJSONWebSignatureSerializer
from passlib.hash import bcrypt
from sqlalchemy.ext.declarative import declarative_base
import app
from app import db
Base = declarative_base()
class UserGroupRelationship(Base):
__tablename__ = 'users_groups'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey('groups.id'), primary_key=True)
class FriendRelationship(Base):
__tablename__ = u'users_friends'
id = db.Column(db.Integer, primary_key=True)
user_left = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
user_right = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
class User(db.Model):
__tablename__ = u'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
email = db.Column(db.String(120), unique=True)
password = db.Column(db.String(120))
# ...
last_login = db.Column(db.DateTime, default=db.func.now())
friends = db.relationship(FriendRelationship,
primaryjoin=id==FriendRelationship.user_left,
backref='friends', lazy='dynamic')
friends_with = db.relationship(FriendRelationship,
primaryjoin=id==FriendRelationship.user_right,
backref='friends_with', lazy='dynamic')
class Group(db.Model):
__tablename__ = u'groups'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
users = db.relationship(UserGroupRelationship,
primaryjoin=id==UserGroupRelationship.group_id,
backref='groups', lazy='dynamic')
class Device(db.Model):
''' devices linked to users '''
__tablename__ = u'devices'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
uuid = db.Column(db.String(50))
date_added = db.Column(db.DateTime)
user_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
running alembic revision --autogenerate does generate table for classes inheriting from db.Model but not for the tables used for my m2m relationships.
INFO [alembic.migration] Context impl PostgresqlImpl.
INFO [alembic.migration] Will assume transactional DDL.
INFO [alembic.autogenerate] Detected added table u'groups'
INFO [alembic.autogenerate] Detected added table u'users'
INFO [alembic.autogenerate] Detected added table u'devices'
INFO [alembic.autogenerate] Detected added table u'question'
Generating /Users/rxdazn/w/xxx/xxx-
yyy/migrations/versions/4e47aa7f3050_.py...done
My alembic.ini and env.py files are the default ones. I simply import my models init my project's __init__.py
any idea of what could cause this behaviour?
(Miguel answered this in the comments. I'll delete this answer if he posts it and someone pokes me in a comment. I only posted it so it could be marked as an answer, as I had the same problem and almost left the page before reading the comments.)
Don't inherit the association tables from Base. All models should inherit from db.Model, like this:
class FriendRelationship(db.Model):
This is why:
There are two patterns for many-to-many relationships. The basic one
uses a Table object for the association table. The advanced one uses
models, and has the advantage that you can store additional columns in
the association table. You seem to be using the advanced one, but if
you are using Flask-SQLAlchemy then all your models should inherit
from db.Model. You should not go directly to SQLAlchemy.