Hello! I'm trying to seed my database using my first migration script, and
I have trouble understanding what `op.get_bind()` returns, and mainly the
difference between `Connection` and `engine.connection()`. Say I have a
migration script that creates a table called `cities` and then it performs
a `COPY FROM CSV` using `engine.raw_connection`.
The method `insert_default_csv` is failing in (note that line might not be
the exact one)
File "/src/app/migrations/versions/d054d8692328_initial_migration.py",
line 91, in insert_default_csv
cursor.copy_expert(cmd, f)
psycopg2.ProgrammingError: relation "cities" does not exist
This is probably due to the fact that the `bind` object the class `City`
was created with and `bind.engine` used in that method are pointing to
different places. This can be tested with the bit
logger.info(bind.engine.connect() == bind) # INFO [alembic] False
logger.info(bind.engine.dialect.has_table(bind.engine.connect(), "cities"))
# INFO [alembic] False
logger.info(bind.engine.dialect.has_table(bind, "cities")) # INFO
[alembic] True
In short what are the differences between these two objects and how can I
access a working engine in Alembic from the context or the bind?
I'm using
Flask-SQLAlchemy==2.4.0
marshmallow-sqlalchemy==0.16.2
SQLAlchemy==1.3.3
alembic==1.0.10
Here is my migration script
"""Initial migration
Revision ID: d054d8692328
Revises:
Create Date: 2019-03-08 09:34:02.836061
"""
import time
import csv
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
from pathlib import Path
import logging
from alembic import op, context
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "d054d8692328"
down_revision = None
branch_labels = None
depends_on = None
DATA_FOLDER = Path.cwd().joinpath(f"migrations/data/{revision}/")
logger = logging.getLogger("alembic")
# ─── NON TEMPLATED IMPORTS
──────────────────────────────────────────────────────────────
# ─── LOCAL MODEL DEFINITIONS
────────────────────────────────────────────────────
db = SQLAlchemy()
class TerritoryMixin(object):
"""A Mixin to keep definitions DRY"""
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.Integer, nullable=False, unique=True)
code_alias = db.Column(db.String(64), nullable=False, unique=True)
name = db.Column(db.String(64), unique=True)
region_code = db.Column(db.Integer, nullable=True)
last_updated = db.Column(db.DateTime(), default=datetime.utcnow)
class InsertionMixin(object):
@classmethod
def insert_default_elements(cls, session):
"""A local implementation of
`my_app.models.mixins.BaseModelMixin.insert_defaults`
This version does NOT check state, and is meant to be handled by
alembic only.
"""
for element in cls._default_elements:
# constructor call
instance = cls(**element)
session.add(instance)
session.commit()
@classmethod
def insert_default_csv(cls, csv_path, engine):
"""A local implementation of
`my_app.models.mixins.BaseModelMixin.insert_from_csv_psql_raw`
This version does NOT check state and is meant to be handled by alembic
only.
"""
SEP = ';'
HEADER = True
logger.debug('using engine: "%s"', engine)
with open(csv_path, "rt") as f:
columns = next(csv.reader(f))[0].split(SEP)
logger.debug('columns: "%s"', columns)
f.seek(0)
coltxt = " (%s)" % ", ".join(columns) if columns else ""
logger.debug('coltxt: "%s"', coltxt)
conn = engine.raw_connection()
cursor = conn.cursor()
cursor.execute("SET datestyle = 'ISO,DMY'")
cmd = "COPY %s%s FROM STDIN DELIMITER '%s' CSV %s" % (
cls.__tablename__,
coltxt,
SEP,
"HEADER" if HEADER else "", ) # \COPY instead of COPY is
preferred
logger.debug('SQL query: "%s"', cmd)
cursor.copy_expert(cmd, f)
# commit the query
t0 = time.time()
conn.commit()
# take the file pointer back to the beginning so we can read it
again
f.seek(0)
logger.info(
"Table '%s': %i row(s) inserted in %.1f seconds.",
cls.__tablename__,
sum(1 for row in csv.reader(f)), time.time() - t0, )
class City(InsertionMixin, TerritoryMixin, db.Model):
__tablename__ = "cities"
# users = backref from many to many relationship with User model
_default_csv_filename = DATA_FOLDER.joinpath('cities.csv')
# ─── END OF LOCAL MODEL DEFINITIONS
─────────────────────────────────────────────
# ─── BEGIN OF ALEMBIC COMMANDS
──────────────────────────────────────────────────
def upgrade():
schema_upgrades()
data_upgrades()
def downgrade():
schema_downgrades()
data_downgrades()
def schema_upgrades():
bind = op.get_bind()
session = sa.orm.Session(bind=bind)
City.__table__.create(bind)
def schema_downgrades():
op.drop_table("cities")
def data_upgrades():
bind = op.get_bind()
session = sa.orm.Session(bind=bind)
# ─── INSERT DATA DEFINED LOCALLY
────────────────────────────────────────────────
logger.info(bind) # INFO [alembic] <sqlalchemy.engine.base.Connection
object at 0x7fb78b1cc518>
logger.info(session) # INFO [alembic] <sqlalchemy.orm.session.Session
object at 0x7fb77b0b5eb8>
logger.info(bind.engine) # INFO [alembic]
Engine(postgresql+psycopg2://lap:***@postgres:5432/lap)
# https://groups.google.com/forum/#!topic/sqlalchemy/PsBfyWiBgBY
logger.info(bind.engine.connect() == bind) # INFO [alembic] False
logger.info(bind.engine.dialect.has_table(bind.engine.connect(), "cities"))
# INFO [alembic] False
logger.info(bind.engine.dialect.has_table(bind, "cities")) # INFO
[alembic] True
City.insert_default_csv(City._default_csv_filename, engine=bind.engine) #
fails with "cities" table does not exist, WHY?
def data_downgrades():
bind = op.get_bind()
session = sa.orm.Session(bind=bind)
session.drop_all()
# ─── END OF ALEMBIC COMMANDS
────────────────────────────────────────────────────
Thanks!
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
http://www.sqlalchemy.org/
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable
Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
To view this discussion on the web visit
https://groups.google.com/d/msgid/sqlalchemy/cc4d5a88-a8b6-496b-9b18-9d96722d2ed1%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.