3

I am trying create a view with SQLAlchemy with Postgresql as the underlying DB. The separate select query to create the view works well and returns results but when I use it in the create view, I get the error sqlalchemy.exc.NoSuchTableError: popular which means the view is not being selected. I am getting the error when I try to select from the view. Creating the view does not throw any error but it does not create the view. Here is my code:

from sqlalchemy import *
import sqlalchemy as db
from sqlalchemy import func
from sqlalchemy import desc
from sqlalchemy import Table
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import Executable, ClauseElement

try:
    engine = db.create_engine('postgresql://user:pass@localhost:5432/db_name')
    connection = engine.connect()

except:
    print('Error establishing DB connection')

# Import metadata
metadata = db.MetaData()

# Import articles, authors and log tables
art = db.Table('articles', metadata, autoload=True, autoload_with=engine)
aut = db.Table('authors', metadata, autoload=True, autoload_with=engine)
log = db.Table('log', metadata, autoload=True, autoload_with=engine)


class CreateView(Executable, ClauseElement):
    def __init__(self, name, select):
        self.name = name
        self.select = select


@compiles(CreateView)
def visit_create_view(element, compiler, **kw):
    return "CREATE VIEW %s AS %s" % (
         element.name,
         compiler.process(element.select, literal_binds=True)
         )



# Method to create view with top three articles
def view_top_three():
    top_three_view = CreateView('popular', db.select([art.columns.title, func.count(log.columns.path)]) \
        .where(func.concat('/article/', art.columns.slug) == log.columns.path) \
        .where(log.columns.path != "/") \
        .group_by(log.columns.path, art.columns.title) \
        .order_by(desc(func.count(log.columns.path))) \
        .limit(3))

    engine.execute(top_three_view)
    v = Table('popular', metadata, autoload=True, autoload_with=engine)
    for r in engine.execute(v.select()):
        print(r)


# Call the method which creates view and selects from view
view_top_three()

Any help will be appreciated.

3
  • Loosely related: stackoverflow.com/questions/45347565/… Commented Nov 27, 2018 at 11:46
  • Thank you so much @IljaEverilä for pointing me to that post. The issue was auto commit. Adding .execution_options(autocommit=True) when creating the engine solved my issue Commented Nov 27, 2018 at 11:55
  • That might not be the best solution though, since now every statement run without an explicit transaction implicitly commits. If you'd take that route, it'd be better to issue the required options on the statement to execute itself: engine.execute(top_three_view.execution_options(...)), but since CREATE VIEW is DDL, your class should be inheriting from DDLElement anyway. Commented Nov 27, 2018 at 11:58

2 Answers 2

1

Since your CreateView inherits from Executable, and ClauseElement, it is not considered a data changing operation. In other words

engine.execute(top_three_view)

executes the CREATE VIEW statement and then implicitly rollbacks, when the connection is returned to the pool.

Instead it should be a subclass of DDLElement, as shown in the usage recipes wiki. Simply changing the baseclass will allow SQLAlchemy autocommit to work properly.

Sign up to request clarification or add additional context in comments.

Comments

0

I found the solution. The issue was to do with autocommit. Setting autocommit to true when creating the engine solved the issue as follows:

engine = db.create_engine('postgresql://user:pass@localhost:5432/db_name').execution_options(autocommit=True)

Special mention to @ilja-everilä

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.