SQLAlchemy Oracle - InvalidRequestError: could not retrieve isolation level - python

I am having problems accessing tables in an Oracle database over a SQLAlchemy connection. Specifically, I am using Kedro catalog.load('table_name') and getting the error message Table table_name not found. So I decided to test my connection using the method listed in this answer: How to verify SqlAlchemy engine object.
from sqlalchemy import create_engine
engine = create_engine('oracle+cx_oracle://USER:PASSWORD#HOST:PORT/?service_name=SERVICE_NAME')
engine.connect()
Error: InvalidRequestError: could not retrieve isolation level
I have tried explicitly adding an isolation level as explained in the documentation like this:
engine = create_engine('oracle+cx_oracle://USER:PASSWORD#HOST:PORT/?service_name=SERVICE_NAME', execution_options={'isolation_level': 'AUTOCOMMIT'})
and this:
engine.connect().execution_options(isolation_level='AUTOCOMMIT')
and this:
connection = engine.connect()
connection = connection.execution_options(
isolation_level="AUTOCOMMIT"
)
but I get the same error in all cases.

Upgrading from SqlAlchemy 1.3.21 to 1.3.22 solved the problem.

Related

pandas to_sql() gives a SADeprecationWarning

The to_sql() function in pandas is now producing a SADeprecationWarning.
df.to_sql(name=tablename, con=c, if_exists='append', index=False )
[..]/lib/python3.8/site-packages/pandas/io/sql.py:1430: SADeprecationWarning:The Connection.run_callable() method is deprecated and will be removed in a future release. Use a context manager instead. (deprecated since: 1.4)
I was getting this even with df.read_sql() command, when running sql select statements. Changing it to the original df.read_sql_query() that it wraps around, got rid of it. I'm suspecting there would be some linkage there.
So, question is, how to do I write a dataframe table to SQL without it getting deprecated in a future release? What does "use a context manager" mean, how can I implement that?
Versions:
pandas: 1.1.5 | SQLAlchemy: 1.4.0 | pyodbc: 4.0.30 | Python: 3.8.0
Working with a mssql database.
OS: Linux Mint Xfce, 18.04. Using a python virtual environment.
If it matters, connection created like so:
conn_str = r'mssql+pyodbc:///?odbc_connect={}'.format(dbString).strip()
sqlEngine = sqlalchemy.create_engine(conn_str,echo=False, pool_recycle=3600)
c = sqlEngine.connect()
And after the db operation,
c.close()
Doing so keeps the main connection sqlEngine "alive" between api calls and lets me use a pooled connection rather than having to connect anew.
Update: according to the pandas team, this will be fixed in Pandas 1.2.4 which as of the time of writing has not been released yet.
Adding this as an answer since Google led here but the accepted answer is not applicable.
Our surrounding code that uses Pandas does use a context manager:
with get_engine(dbname).connect() as conn:
df = pd.read_sql(stmt, conn, **kwargs)
return df
In my case, this error is being thrown from within pandas itself, not in the surrounding code that uses pandas:
/Users/tommycarpenter/Development/python-indexapi/.tox/py37/lib/python3.7/site-packages/pandas/io/sql.py:1430: SADeprecationWarning: The Engine.run_callable() method is deprecated and will be removed in a future release. Use the Engine.connect() context manager instead. (deprecated since: 1.4)
The snippet from pandas itself is:
def has_table(self, name, schema=None):
return self.connectable.run_callable(
self.connectable.dialect.has_table, name, schema or self.meta.schema
)
I raised an issue: https://github.com/pandas-dev/pandas/issues/40825
You could try...
connection_string = r'mssql+pyodbc:///?odbc_connect={}'.format(dbString).strip()
engine = sqlalchemy.create_engine(connection_string, echo=False, pool_recycle=3600)
with engine.connect() as connection:
df.to_sql(name=tablename, con=connection, if_exists='append', index=False)
This approach uses a ContextManager. The ContextManager of the engine returns a connection and automatically invokes connection.close() on it, see. Read more about ContextManager here. Another useful thing to know is, that a connection is a ContextManager as well and handles transactions for you. This means it begins and ends a transaction and in case of an error it automatically invokes a rollback.

Executing Multiple SQL statements using execute()

I am trying to do an SQL injection in a server of mine.
I am using the command :
cursor.execute("select * from some_table")
to execute the SQL commands in my server.
But is there a way to execute multiple commands using the same execute() function.
I tried :
cursor.execute("select * from some_table ; INSERT INTO ...")
DBMS is mariadb
Here is an overview of SQL injection strategies. The one you are trying to do is called stacking queries. It seems that at least this strategy is prevented by most database APIs.
You mention MariaDB which is basically more or less the same as MySQL.
And although python is not listed explicitly, I would also assume that the python database API prevents query stacking.
Update: When you check the API of execute() you can see there is a parameter multi which defaults to False. As long as you don't set it to True, you should be safe.
MySQL (and MariaDB) allows you to run several SQL statements in one go by setting capability flag CLIENT_MULTI_STATEMENTS (0x10000) on connecting to the database server. Check out the documentation of the python database driver used in your implementation, there should be method to set the flag and you need to do so in advance before creating cursor and execute the SQL statements.
Here is the code example of mariadb python driver, for other drivers (like pymysql) they may work the same way
import mariadb
from mariadb.constants.CLIENT import MULTI_STATEMENTS
conn_params= {
"user" : "YOUR_USERNAME",
"password" : "YOUR_PASSWORD",
"host" : "NETWORK_DOMAIN_NAME",
"database" : "DB_NAME",
"client_flag": MULTI_STATEMENTS,
}
db_conn = mariadb.connect(**conn_params)
rawsqls = [
'SELECT * FROM table2',
'INSERT INTO table3 ....',
'SELECT * FROM table4',
]
with db_conn.cursor() as cursor:
cursor.execute(';'.join(rawsqls))
rows1 = cursor.fetchall()
cursor.nextset()
rows2 = cursor.fetchall()
cursor.nextset()
rows3 = cursor.fetchall()
CAUTION
To avoid SQL injection, You should be careful and use the flag CLIENT_MULTI_STATEMENTS ONLY when you are pretty sure that all the inputs to your SQL statements come from trusted source.

Concurrent db table indexing through alembic script

Is it possible to create concurrent indexes for DB table through alembic script?
I'm using postgres DB, and able to create concurrent table indexes through sql command on postgres prompt.(create index concurrently on ();)
But couldn't find way to create same through Db migration(alembic) script. If we create normal index(not concurrent) , it'll lock DB table so can't perform any query in parallel. So just want to know how to create concurrent index through alembic(DB migration) script
Alembic supports PostgreSQL concurrently indexes creation
def upgrade():
op.execute('COMMIT')
op.create_index('ix_1', 't1', ['col1'], postgresql_concurrently=True)
I'm not using Postgres and I am not able to test it, but it should be possible.
According to:
http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html
Concurrent indexes are allowed in the Postgres dialect from version 0.9.9.
However, a migration script like this should work with older versions (direct SQL creation):
from alembic import op, context
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import text
# ---------- COMMONS
# Base objects for SQL operations are:
# - use op = INSERT, UPDATE, DELETE
# - use connection = SELECT (and also INSERT, UPDATE, DELETE but this object has lot of logics)
metadata = MetaData()
connection = context.get_bind()
tbl = Table('test', metadata, Column('data', Integer), Column("unique_key", String))
# If you want to define a index on the current loaded schema:
# idx1 = Index('test_idx1', tbl.c.data, postgresql_concurrently=True)
def upgrade():
...
queryc = \
"""
CREATE INDEX CONCURRENTLY test_idx1 ON test (data, unique_key);
"""
# it should be possible to create an index here (direct SQL):
connection.execute(text(queryc))
...
Whereas concurrent indexes are allowed in Postgresql, Alembic does not support concurrent operations, only one process should be running at a time.

mysql sqlalchemy mysql isolation error

How to set isolation level in sqlalchemy pool configuration. I get the following error when i try to set it as paramaeter in create_engine(...)
File "build/bdist.linux-i686/egg/sqlalchemy/engine/__init__.py", line 321, in create_engine
File "build/bdist.linux-i686/egg/sqlalchemy/engine/strategies.py", line 141, in create
TypeError: Invalid argument(s) 'isolation_level' sent to create_engine(), using configuration
MySQLDialect_mysqldb/QueuePool/Engine
Which version of sqlalchemy do you have because this functionality for mysql is added in 0.7.6.
Like explained in this answer, you can achieve the same result by directly issuing the SQL command to set the isolation level. In case of SQL Server, you could do:
query = 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED'
session.connection().connection.execute(query)
'isolation_level' parameter is passed as one of the argument to create_engine method
Michael Bayer replied on the SQLAlchemy forum : it's the development version. for the moment you can get it like this: hg.sqlalchemy.org/sqlalchemy/archive/default.tar.gz
This solves the problem.

Sqlite / SQLAlchemy: how to enforce Foreign Keys?

The new version of SQLite has the ability to enforce Foreign Key constraints, but for the sake of backwards-compatibility, you have to turn it on for each database connection separately!
sqlite> PRAGMA foreign_keys = ON;
I am using SQLAlchemy -- how can I make sure this always gets turned on?
What I have tried is this:
engine = sqlalchemy.create_engine('sqlite:///:memory:', echo=True)
engine.execute('pragma foreign_keys=on')
...but it is not working!...What am I missing?
EDIT:
I think my real problem is that I have more than one version of SQLite installed, and Python is not using the latest one!
>>> import sqlite3
>>> print sqlite3.sqlite_version
3.3.4
But I just downloaded 3.6.23 and put the exe in my project directory!
How can I figure out which .exe it's using, and change it?
For recent versions (SQLAlchemy ~0.7) the SQLAlchemy homepage says:
PoolListener is deprecated. Please refer to PoolEvents.
Then the example by CarlS becomes:
engine = create_engine(database_url)
def _fk_pragma_on_connect(dbapi_con, con_record):
dbapi_con.execute('pragma foreign_keys=ON')
from sqlalchemy import event
event.listen(engine, 'connect', _fk_pragma_on_connect)
Building on the answers from conny and shadowmatter, here's code that will check if you are using SQLite3 before emitting the PRAGMA statement:
from sqlalchemy import event
from sqlalchemy.engine import Engine
from sqlite3 import Connection as SQLite3Connection
#event.listens_for(Engine, "connect")
def _set_sqlite_pragma(dbapi_connection, connection_record):
if isinstance(dbapi_connection, SQLite3Connection):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON;")
cursor.close()
I now have this working:
Download the latest sqlite and pysqlite2 builds as described above: make sure correct versions are being used at runtime by python.
import sqlite3
import pysqlite2
print sqlite3.sqlite_version # should be 3.6.23.1
print pysqlite2.__path__ # eg C:\\Python26\\lib\\site-packages\\pysqlite2
Next add a PoolListener:
from sqlalchemy.interfaces import PoolListener
class ForeignKeysListener(PoolListener):
def connect(self, dbapi_con, con_record):
db_cursor = dbapi_con.execute('pragma foreign_keys=ON')
engine = create_engine(database_url, listeners=[ForeignKeysListener()])
Then be careful how you test if foreign keys are working: I had some confusion here. When using sqlalchemy ORM to add() things my import code was implicitly handling the relation hookups so could never fail. Adding nullable=False to some ForeignKey() statements helped me here.
The way I test sqlalchemy sqlite foreign key support is enabled is to do a manual insert from a declarative ORM class:
# example
ins = Coverage.__table__.insert().values(id = 99,
description = 'Wrong',
area = 42.0,
wall_id = 99, # invalid fkey id
type_id = 99) # invalid fkey_id
session.execute(ins)
Here wall_id and type_id are both ForeignKey()'s and sqlite throws an exception correctly now if trying to hookup invalid fkeys. So it works! If you remove the listener then sqlalchemy will happily add invalid entries.
I believe the main problem may be multiple sqlite3.dll's (or .so) lying around.
As a simpler approach if your session creation is centralised behind a Python helper function (rather than exposing the SQLA engine directly), you can just issue session.execute('pragma foreign_keys=on') before returning the freshly created session.
You only need the pool listener approach if arbitrary parts of your application may create SQLA sessions against the database.
From the SQLite dialect page:
SQLite supports FOREIGN KEY syntax when emitting CREATE statements for tables, however by default these constraints have no effect on the operation of the table.
Constraint checking on SQLite has three prerequisites:
At least version 3.6.19 of SQLite must be in use
The SQLite libary must be compiled without the SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER symbols enabled.
The PRAGMA foreign_keys = ON statement must be emitted on all connections before use.
SQLAlchemy allows for the PRAGMA statement to be emitted automatically for new connections through the usage of events:
from sqlalchemy.engine import Engine
from sqlalchemy import event
#event.listens_for(Engine, "connect")
def set_sqlite_pragma(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
One-liner version of conny's answer:
from sqlalchemy import event
event.listen(engine, 'connect', lambda c, _: c.execute('pragma foreign_keys=on'))
I had the same problem before (scripts with foreign keys constraints were going through but actuall constraints were not enforced by the sqlite engine); got it solved by:
downloading, building and installing the latest version of sqlite from here: sqlite-sqlite-amalgamation; before this I had sqlite 3.6.16 on my ubuntu machine; which didn't support foreign keys yet; it should be 3.6.19 or higher to have them working.
installing the latest version of pysqlite from here: pysqlite-2.6.0
after that I started getting exceptions whenever foreign key constraint failed
hope this helps, regards
If you need to execute something for setup on every connection, use a PoolListener.
Enforce Foreign Key constraints for sqlite when using Flask + SQLAlchemy.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
def create_app(config: str=None):
app = Flask(__name__, instance_relative_config=True)
if config is None:
app.config.from_pyfile('dev.py')
else:
logger.debug('Using %s as configuration', config)
app.config.from_pyfile(config)
db.init_app(app)
# Ensure FOREIGN KEY for sqlite3
if 'sqlite' in app.config['SQLALCHEMY_DATABASE_URI']:
def _fk_pragma_on_connect(dbapi_con, con_record): # noqa
dbapi_con.execute('pragma foreign_keys=ON')
with app.app_context():
from sqlalchemy import event
event.listen(db.engine, 'connect', _fk_pragma_on_connect)
Source:
https://gist.github.com/asyd/a7aadcf07a66035ac15d284aef10d458

Categories