I am trying to use the pandas function pd.read_sql to read records that have been created, added, and flushed in a SQLAlchemy session, but not committed. So I want to create an object in a SQLAlchemy session and query it with pandas before calling commit. Using pandas 0.22.0 and SQLAlchemy 1.1.10.
I have tried setting the isolation_level on create_engine, and various other ways of setting the isolation level to 'READ UNCOMMITTED', but this does not seem to work. Minimal example below:
# Import packages
import pandas as pd
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker
# Set up an example ORM
Base = declarative_base()
class Record(Base):
__tablename__ = 'records'
id = Column(Integer, primary_key=True)
foo = Column(String(255))
# Create a session and engine:
database='foobar'
user=''
password = ''
host = 'localhost'
port = '5432'
connection_string = f"postgresql+psycopg2://{user}:{password}#{host}:{port}/{database}"
engine = create_engine(connection_string, encoding = 'utf8', convert_unicode = True,
isolation_level='READ_UNCOMMITTED'
)
session = sessionmaker()
session.configure(bind=engine)
db = session()
# Set up the example record:
Record.__table__.create(bind=engine)
record = Record(foo='bar')
db.add(record)
db.flush()
# Attempt to query:
records = pd.read_sql('select * from records', db.get_bind())
assert records.empty
I am looking for a solution that will cause the above code to throw an AssertionError on the last line. records.empty currently evaluates to true.
And of course I figure it out as soon as I post here. For posterity: use db.connection() instead of db.get_bind().
Related
Making an API using FastAPI and SQLAlchemy I'm experiencing strange behaviour when database (SQLite) is in-memory which doesn't occur when stored as file.
Model:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
Base = declarative_base()
class Thing(Base):
__tablename__ = "thing"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String)
I create two global engine objects. One with database as file, the other as in-memory database:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
args = dict(echo=True, connect_args={"check_same_thread": False})
engine1 = create_engine("sqlite:///db.sqlite", **args)
engine2 = create_engine("sqlite:///:memory:", **args)
Session1 = sessionmaker(bind=engine1)
Session2 = sessionmaker(bind=engine2)
I create my FastAPI app and a path to add an object to database:
from fastapi import FastAPI
app = FastAPI()
#app.get("/")
def foo(x: int):
with {1: Session1, 2: Session2}[x]() as session:
session.add(Thing(name="foo"))
session.commit()
My main to simulate requests and check everything is working:
from fastapi.testclient import TestClient
if __name__ == "__main__":
Base.metadata.create_all(engine1)
Base.metadata.create_all(engine2)
client = TestClient(app)
assert client.get("/1").status_code == 200
assert client.get("/2").status_code == 200
thing table is created in engine1 and committed, same with engine2. On first request "foo" was successfully inserted into engine1's database (stored as file) but second request raises "sqlite3.OperationalError" claiming "no such table: thing".
Why is there different behaviour between the two? Why does in-memory database claim the table doesn't exist even though SQLAlchemy logs show create table statement ran successfully and was committed?
The docs explain this in the following https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#using-a-memory-database-in-multiple-threads
To use a :memory: database in a multithreaded scenario, the same connection object must be shared among threads, since the database exists only within the scope of that connection. The StaticPool implementation will maintain a single connection globally, and the check_same_thread flag can be passed to Pysqlite as False
It also shows how to get the intended behavior, so in your case
from sqlalchemy.pool import StaticPool
args = dict(echo=True, connect_args={"check_same_thread": False}, poolclass=StaticPool)
I am using SQLAlchemy as ORM for a python project. I have created few models/schema and it is working fine. Now I need to query a existing MySQL database, no insert/update just the select statement.
How can I create a wrapper around the tables of this existing database? I have briefly gone through the sqlalchemy docs and SO but couldn't find anything relevant. All suggest execute method, where I need to write the raw sql queries, while I want to use the SQLAlchemy query method in same way as I am using with the SA models.
For example if the existing db has table name User then I want to query it using the dbsession ( only the select operation, probably with join)
You seem to have an impression that SQLAlchemy can only work with a database structure created by SQLAlchemy (probably using MetaData.create_all()) - this is not correct. SQLAlchemy can work perfectly with a pre-existing database, you just need to define your models to match database tables. One way to do that is to use reflection, as Ilja Everilä suggests:
from sqlalchemy import Table
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class MyClass(Base):
__table__ = Table('mytable', Base.metadata,
autoload=True, autoload_with=some_engine)
(which, in my opinion, would be totally fine for one-off scripts but may lead to incredibly frustrating bugs in a "real" application if there's a potential that the database structure may change over time)
Another way is to simply define your models as usual taking care to define your models to match the database tables, which is not that difficult. The benefit of this approach is that you can map only a subset of database tables to you models and even only a subset of table columns to your model's fields. Suppose you have 10 tables in the database but only interested in users table from where you only need id, name and email fields:
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
email = sa.Column(sa.String)
(note how we didn't need to define some details which are only needed to emit correct DDL, such as the length of the String fields or the fact that the email field has an index)
SQLAlchemy will not emit INSERT/UPDATE queries unless you create or modify models in your code. If you want to ensure that your queries are read-only you may create a special user in the database and grant that user SELECT privileges only. Alternatively/in addition, you may also experiment with rolling back the transaction in your application code.
You can access an existing table using the automap extension:
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
Base = automap_base()
Base.prepare(engine, reflect=True)
Users = Base.classes.users
session = Session(engine)
res = session.query(Users).first()
Create a table with autoload enabled that will inspect it. Some example code:
from sqlalchemy.sql import select
from sqlalchemy import create_engine, MetaData, Table
CONN_STR = '…'
engine = create_engine(CONN_STR, echo=True)
metadata = MetaData()
cookies = Table('cookies', metadata, autoload=True,
autoload_with=engine)
cols = cookies.c
with engine.connect() as conn:
query = (
select([cols.created_at, cols.name])
.order_by(cols.created_at)
.limit(1)
)
for row in conn.execute(query):
print(row)
Other answers don't mention what to do if you have a table with no primary key, so I thought I would address this. Assuming a table called Customers that has columns for CustomerId, CustomerName, CustomerLocation you could do;
from sqlalchemy.ext.automap import automap_base
from sqlalchemy import create_engine, MetaData, Column, String, Table
from sqlalchemy.orm import Session
Base = automap_base()
conn_str = '...'
engine = create_engine(conn_str)
metadata = MetaData()
# you only need to define which column is the primary key. It can automap the rest of the columns.
customers = Table('Customers',metadata, Column('CustomerId', String, primary_key=true), autoload=True, autoload_with=engine)
Base.prepare()
Customers= Base.classes.Customers
session = Session(engine)
customer1 = session.query(Customers).first()
print(customer1.CustomerName)
Assume we have a Postgresql database named accounts. And we already have a table named users.
import sqlalchemy as sa
psw = "verysecret"
db = "accounts"
# create an engine
pengine = sa.create_engine('postgresql+psycopg2://postgres:' + psw +'#localhost/' + db)
from sqlalchemy.ext.declarative import declarative_base
# define declarative base
Base = declarative_base()
# reflect current database engine to metadata
metadata = sa.MetaData(pengine)
metadata.reflect()
# build your User class on existing `users` table
class User(Base):
__table__ = sa.Table("users", metadata)
# call the session maker factory
Session = sa.orm.sessionmaker(pengine)
session = Session()
# filter a record
session.query(User).filter(User.id==1).first()
Warning: Your table should have a Primary Key defined. Otherwise, Sqlalchemy won't like it.
I'm newer in SQLAlchemy I use some examples to create table and insert information to it and it's working 100% .
But what I didn't find is some example for how can I update & delete some information from the database.
What I'm doing is :
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
Base = declarative_base()
## create
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
name = Column(String(250), nullable=False)
engine = create_engine('sqlite:///database.db')
Base.metadata.create_all(engine)
## insert
Base.metadata.bind = engine
DBSession = sessionmaker(bind=engine)
session = DBSession()
new_person = Person(name='new person')
session.add(new_person)
session.commit()
## fetch
getperson = session.query(Person).first()
print getperson.name
# this will print : new person
# I need some example to how can I update and delete this : new person
So in this code it'll print "new person" my question is how can I update or delete it ?
Here's some example on each CRUD operation in sqlalchemy (ommiting Create, Read as you already know how to perform those):
First, necessary imports and configs for any operation:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Category, Item, User are my tables
from database_setup import Base, Category, Item, User
# Generating session to connect to the db's ORM
engine = create_engine('sqlite:///catalogwithusers.db') # my db
Base.metadata.bind = engine
DBSession = sessionmaker(bind = engine)
session = DBSession()
Then peforming an update:
# Get the item filtering by it's id using a one() query on Item table
# If query is not empty, update the attributes, add query to session and commit
q = session.query(Item).filter_by(id=item_id).one()
if q != []:
q.name = edited_name
q.description = edited_description
session.add(q)
session.commit()
Finally, performing a deletion:
# Again get the item similarly to the example above
# Then if query returned results, use the delete method and commit
q = session.query(Item).filter_by(id=item_id).one()
if q != []:
session.delete(q)
session.commit()
These examples are taken from here. I suggest you have a look. ORM Creation is inside database_setup.py and CRUD ops are performed inside project.py and populatecatalog.py.
As the title says.
Here are the codes.
from sqlalchemy import Column, ForeignKey, Integer, String, DateTime, func, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
from sqlalchemy import create_engine
import sqlalchemy.exc
from sqlalchemy import event
from settings import DB_HOST
def return_a_scoped_session():
engine = create_engine(DB_HOST)
session_factory = sessionmaker(bind=engine)
db_session = scoped_session(session_factory)
return db_session()
Base = declarative_base()
class MyClass(Base):
"""Doc string for MyClass"""
__tablename__ = 'my_table'
file_name = Column(String(512), nullable=True)
class Aria2Jobs(Base):
__tablename__ = 'nh_downloading_jobs'
id = Column(Integer, primary_key = True)
file_name = Column(String(512), nullable=True)
is_verified = Column(Boolean, default=False, nullable=True)
def check_if_verified(self):
if self.is_verified:
# create an instance
a_job= MyClass(file_name=self.file_name)
_session = return_a_scoped_session()
_session.add(a_job)
_session.commit()
_session.close()
# event
#event.listens_for(Aria2Jobs.is_verified, 'set')
def send_to_jsonpyes_jobs(target, value, oldvalue, initiator):
target.check_if_verified()
# error is when I set a property of an object (this property will trigger an event 'set' and the event will try to commit a session.
session = return_a_scoped_session()
row = session.query(Aria2Jobs).first()
row.is_verified = True
session.add(row)
# the error came out
#
# sqlalchemy.exc.invalidrequesterror object is already attached to session
session.commit()
# How to commit another object in a property of an object?
How to commit another object in a property of an object in sqlalchemy?
As you can see, when I tried session.add(row),
error:
sqlalchemy.exc.invalidrequesterror object is already attached to session
I don't know which session the row is attached to.
I want to run the function check_if_verified
-- latest error --
sqlalchemy.exc.InvalidRequestError: Object '<Aria2Jobs at 0x7fad3a635050>' is already attached to session '1' (this is
Just remove session.add(row). Becouse you used session.query the row object is already in your session.
To save the data is enough to run session.commit function
You're using scoped_session incorrectly. What's happening here is each time you call return_a_scoped_session() it's returning a new session, with a completely new engine. row is being added to a different session somewhere else. (In fact, the code you posted doesn't even show the other places it's being added; I can't reproduce your error with the code you posted.) The solution is to fix your scoped_session:
engine = create_engine(DB_HOST)
Session = scoped_session(sessionmaker(bind=engine))
def return_a_scoped_session():
return Session()
I access a a postgres table using SQLAlchemy. I want a query to have eagerloading.
from sqlalchemy.orm import sessionmaker, scoped_session, eagerload
from settings import DATABASE_USER, DATABASE_PASSWORD, DATABASE_HOST, DATABASE_PORT, DATABASE_NAME
from sqlalchemy import create_engine
from sqlalchemy import Table, Column, Integer, String, Boolean, MetaData, ForeignKey
from sqlalchemy.orm import mapper
from sqlalchemy.ext.declarative import declarative_base
def create_session():
engine = create_engine('postgres://%s:%s#%s:%s/%s' % (DATABASE_USER, DATABASE_PASSWORD, DATABASE_HOST, DATABASE_PORT, DATABASE_NAME), echo=True)
Session = scoped_session(sessionmaker(bind=engine))
return Session()
Base = declarative_base()
class Zipcode(Base):
__tablename__ = 'zipcode'
zipcode = Column(String(6), primary_key = True, nullable=False)
city = Column(String(30), nullable=False)
state = Column(String(30), nullable=False)
session = create_session()
query = session.query(Zipcode).options(eagerload('zipcode')).filter(Zipcode.state.in_(['NH', 'ME']))
#query = session.query(Zipcode.zipcode).filter(Zipcode.state.in_(['NH', 'ME']))
print query.count()
This fails with
AttributeError: 'ColumnProperty' object has no attribute 'mapper'
One without eagerloading returns the records correctly.
I am new to SQLAlchemy. I am not sure what the problem is. Any pointers?
You can only eager load on a relation property. Not on the table itself. Eager loading is meant for loading objects from other tables at the same time as getting a particular object. The way you load all the objects for a query will be simply adding all().
query = session.query(Zipcode).options(eagerload('zipcode')).filter(Zipcode.state.in_(['NH', 'ME'])).all()
The query will now be a list of all objects (rows) in the table and len(query) will give you the count.