How to ignore some models to migrate? [duplicate] - python

I am using Flask-SQLAlchemy to define my models, and then using Flask-Migrate to auto-generate migration scripts for deployment onto a PostgreSQL database. I have defined a number of SQL Views on the database that I use in my application like below.
However, Flask-Migrate now generates a migration file for the view as it thinks it's a table. How do I correctly get Flask-Migrate / Alembic to ignore the view during autogenerate?
SQL View name: vw_SampleView with two columns: id and rowcount.
class ViewSampleView(db.Model):
__tablename__ = 'vw_report_high_level_count'
info = dict(is_view=True)
id = db.Column(db.String(), primary_key=True)
rowcount = db.Column(db.Integer(), nullable=False)
Which means I can now do queries like so:
ViewSampleView.query.all()
I tried following instructions on http://alembic.zzzcomputing.com/en/latest/cookbook.html and added the info = dict(is_view=True) portion to my model and the following bits to my env.py file, but don't know where to go from here.
def include_object(object, name, type_, reflected, compare_to):
"""
Exclude views from Alembic's consideration.
"""
return not object.info.get('is_view', False)
...
context.configure(url=url,include_object = include_object)

I think (though haven't tested) that you can mark your Table as a view with the __table_args__ attribute:
class ViewSampleView(db.Model):
__tablename__ = 'vw_report_high_level_count'
__table_args__ = {'info': dict(is_view=True)}
id = db.Column(db.String(), primary_key=True)
rowcount = db.Column(db.Integer(), nullable=False)

Related

Nameerror in flask db migrate - Table not defined

I have run flask db upgrade for creating Todos table. Now I added a new table and established a relationship with the existing table and also added a new field in the existing table.
I would expect flask db migrate to record the differences (adding new table Todoslist and adding a new field in Todo) however it says name error - table not defined.
class TodoList(db.Model):
__tablename__ = 'todolists'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(), nullable=False)
todos.db.relationship('Todo', backref='list', lazy = True)
class Todo(db.Model):
__tablename__ = 'todos'
id = db.Column(db.Integer, primary_key=True)
description = db.Column(db.String(), nullable=False)
completed = db.Column(db.Boolean, nullable=False, default=False)
list_id = db.Column(db.Integer, db.ForeignKey(todolists.id), nullable=True)
- Error details:
File "C:\Users\rg\anaconda3\lib\site-packages\flask\_compat.py", line 39, in reraise
raise value
File "C:\Users\rg\class_demos\Todoapp\app.py", line 26, in <module>
class Todo(db.Model):
File "C:\Users\rg\class_demos\Todoapp\app.py", line 32, in Todo
list_id = db.Column(db.Integer, db.ForeignKey(todolists.id), nullable=True)
NameError: name 'todolists' is not defined
Solutions tried
Flask app is set to the current python module
Tried swapping the create table (one before the other)
Searched extensively and this error seems to be common but not able to find a matching case like the one mentioned above.
Any help is much appreciated.
The error stems from how you are creating a relationship between Todo and TodoList models.
list_id needs to be properly initialized as a foreign key to TodoList.id, which means that it references an id value from the TodoList table. You have incorrectly used todolists.id, which is a table that does not exist. Rather, you should define the list_id as:
list_id = db.Column(db.Integer, db.ForeignKey("todoList.id"))
To create the relationship in TodoList table, you will need to initialize the field todos with db.relationship. This is not an actual database field, but a high-level view of the relationship between TodoList and Todo, and for that reason it isn't in the database diagram. So, you can properly create the field as:
todos = db.relationship('Todo', backref='list', lazy='True')
My bad. Missed the quotes for todolists.id
list_id = db.Column(db.Integer, db.ForeignKey("todolists.id"), nullable=True)
This fixed the issue :)

Is there an ORM structure which allows me to have tables which result in Catalog.Schema.Table?

I am currently working on a project with a pre-existing database. The Server is a clustered server with multiple Catalogs (database), and in each Catalog there are multiple Schemas with Tables. The table name format for the traditional SQL query would be [Catalog].[Schema].[Table]. This structure works for the traditional SQL.
The problem comes in when I try to flask db migrate to an sqlite database for testing. I get a number of errors depending on what I try.
I am using
Python 3.7
Flask 1.0.2
Flask-SQLAlchemy 2.4.0
Flask-Migrate 2.4.0
Windows 10 (not ideal, but its what I have)
I have tried the following with different results:
Schema only method:
class User(db.Model):
__tablename__ = 'user'
__table_args__ = (
db.PrimaryKeyConstraint('userid')
, db.ForeignKeyConstraint(('manageruserid',), ['CatalogA.SchemaA.userid'])
, {'schema': 'CatalogA.SchemaA'}
)
manager_user_id = db.Column('manageruserid', db.Integer())
user_id = db.Column('userid', db.Integer(), nullable=False)
class Tool(db.Model):
__tablename__ = 'tool'
__table_args__ = (
db.PrimaryKeyConstraint('toolid')
, db.ForeignKeyConstraint(('ownerid',), ['CatalogA.SchemaA.user.userid'])
, {'schema': 'CatalogB.SchemaB'}
)
tool_id = db.Column('toolid', db.Integer())
owner_id = db.Column('ownerid', db.Integer(), nullable=False)
when trying to upgrade it creates an error:
"sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) unknown database "CatalogA.SchemaA" [SQL: CREATE TABLE "CatalogA.SchemaA".user (
manageruserid INTEGER,
userid INTEGER NOT NULL,
PRIMARY KEY (userid),
FOREIGN KEY(manageruserid) REFERENCES user (userid) )"
Bind With Schema (binds are setup correctly)
class User(db.Model):
__bind_key__ = 'CatalogA'
__tablename__ = 'user'
__table_args__ = (
db.PrimaryKeyConstraint('userid')
, db.ForeignKeyConstraint(('manageruserid',), ['CatalogA.SchemaA.user.userid'])
, {'schema': 'SchemaA'}
)
manager_user_id = db.Column('manageruserid', db.Integer())
user_id = db.Column('userid', db.Integer(), nullable=False)
class Tool(db.Model):
__bind_key__ = 'CatalogB'
__tablename__ = 'tool'
__table_args__ = (
db.PrimaryKeyConstraint('toolid')
, db.ForeignKeyConstraint(('ownerid',), ['CatalogA.SchemaA.user.userid'])
, {'schema': 'SchemaB'}
)
tool_id = db.Column('toolid', db.Integer())
owner_id = db.Column('ownerid', db.Integer(), nullable=False)
when trying to migrate it creates an error:
"sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'user.manageruserid' could not find table 'CatalogA.SchemaA.user' with which to generate a foreign key to target column 'userid'"
If I do it the Schema only method way then I can run queries on the database, but it doesn't correctly setup my test-db.
I looked for multiple hours trying to find a solution, and would love someone to help me find the way forward (if you find a link to another solution, please tell me what you searched as well to increase my google-fu).
Main questions are:
What is the right way to have a model for this situation?
Was/Is there something in the documentation which I missed for this scenario?

Create SQLAlchemy Model for pg_timezone_names (and add in relationship)

UPDATE:
It just works.
Even though you can't see the pg_* tables in reflection, you can build models on them.
So the answer is the question!
There's just too much going on here to connect the dots.
I have a Postgres database (schema? catalog?) with a set of tables. I have created models using Base = declarative_base().
E.g:
class MyModel(Base):
__tablename__ = 'mymodel'
id = Column(UUIDType(binary=False), primary_key=True, default=uuid.uuid4)
timezone = Column(Text)
first_name = Column(Text)
There's this awesome table in pg_catalog: pg_timezone_names which I would like to join to the timezone column here.
pg_timezone_names is a view (not that is relevant necessarily) and in SQLA would look something like this (though I believe I SHOULD be able to build this automatically with reflect):
class TimezoneNames(Base):
__tablename__ = 'pg_timezone_names'
name = Column(Text, primary_key=True)
abbrev = Column(Text)
utc_offset = Column(Interval)
is_dst = Column(Boolean)
Let's say I connect with:
engine = create_engine("postgres://localhost:5432/mydb")
I would like to be able to grab a record from MyModel and access the utc_offset by creating a relationship with the pg_timezone_names table.
When I try to grab Base.classes, I only see mymodel but not any of the pg_catalog tables. Yet when I'm in mydb I can perform:
SELECT * from pg_timezone_names WHERE name LIKE "%America%";
In other words, it's totally easy in psql to perform the query. But how do I specify the model connects to that table?
I believe I could change my model to:
class MyModel(Base):
__tablename__ = 'mymodel'
id = Column(UUIDType(binary=False), primary_key=True, default=uuid.uuid4)
timezone_name = Column(Text ForeignKey('pg_timezone_names.name'))
first_name = Column(Text)
timezone = relationship("TimezoneNames")
So that I can grab the record's utc_offset with something like this:
myOffset = session.query(MyModel).first().timezone.utc_offset
The question is:
How do I tell sqlalchemy to find pg_timezone_names so I can create the relationships I want?
Thanks for your consideration!

Flask-Sqlalchemy multiple databases (binds) with the same model (class DB.Model)

I'm a beginner with python/Flask/SQLAlchemy so sorry if my questions are dumb.
I want to create an API with Flask using Flask-SQLAlchemy as following:
one sqlite database for users/passwords
SQLALCHEMY_DATABASE_URI = 'sqlite:////path/to/users.db'
class User(DB.Model):
__tablename__ = 'users'
id = DB.Column(DB.Integer, primary_key=True)
username = DB.Column(DB.String(64), index=True)
password = DB.Column(DB.String(128))
Lets say I have multiple "customers" witch a user can create using
$ http POST http://localhost:5000/api/customers/ name=customer1
class Customer(DB.Model):
__tablename__ = 'customer'
customer_id = DB.Column(DB.Integer, primary_key=True)
customer_name = DB.Column(DB.String, unique=True, index=True)
I need to create a separate sqlite file for each "customers" :
SQLALCHEMY_BINDS = {
'customer1' = 'sqlite:////path/customer1.db',
'customer2' = 'sqlite:////path/customer2.db',
...
}
My questions are:
I do not have fixed number of "customers" so I cannot create a model class for each and specify the "bind_key" for each. Is it possible to do this with Flask-SQLAlchemy or I need to use plain SQLAlchemy?
I have 3 "customers" in data/ as customer1.db, customer2.db and customer3.db.
I would start the application and create SQLALCHEMY_BINDS dictionary listing the files in data/ and then DB.create_all() on a request for a specific "customer" .
how can I bind to the right .db file using the Flask-SQLAlchemy
DB.session?
I've read Using different binds in the same class in Flask-SQLAlchemy
Why exactly do you want entirely separate DB files for each customer?
In any case this is easier with straight SQLAlchemy. You can create a getter function which returns a session pointing to your db file.
def get_session(customer_id):
sqlite_url = 'sqlite:////path/customer%s.db' % customer_id
engine = create_engine(sqlite_url)
# initialize the db if it hasn't yet been initialized
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
return session
You can then use and close that session.
But without knowing your specific use case, it is difficult to understand why you would want to do this instead of just using a single SQLite database.

MySQL DB migration: change of string length

I'm using SQLAlchemy + alembic to manage my database. I had a string field which was 10 characters long and later on found out that it has to be 20. So I updated the model definition.
class Foo(db.Model):
__tablename__ = 'foos'
id = db.Column(db.Integer, primary_key=True)
foo_id = db.Column(db.Integer, db.ForeignKey('users.id'))
name = db.Column(db.String(80))
When I run alembic revision --autogenerate, this was not detected. Now I did read the documentation and suspected that this might not be supported. How do I managed such changes in DB gracefully?
You need to enable optional column type checking.
See this for notes on what is checked by default
context.configure(
# ...
compare_type = True
)

Categories

Resources