Updating an entity with sqlalchemy ORM - sqlalchemy

I can't seem to find a way to update an entity in the database using sqlalchemy ORM
Here's what I'm doing:
query = Table(self.table_name, self.sql_controller.metadata, autoload=True).select(ItemEntity)
database_items: List[ItemEntity] = session.execute(query).all()
database_items[0].Sell_price = 50000
But that raises an exception "AttributeError: can't set attribute"
I see that the same manipulation is being done in the official documentation of sqlalchemy https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html#updating-orm-objects
Can someone point me in the right direction? It's really irritating to fail at basic CRUD operations.

Table objects are not part of SQLAlchemy ORM, they are part of SQLAlchemy Core. In order to use ORM you'll want to do something like this:
from sqlalchemy import create_engine
from sqlalchemy import select
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
engine = create_engine("sqlite://", echo=True)
# create test environment
with engine.begin() as conn:
conn.exec_driver_sql("CREATE TABLE my_thing (id int primary key, sell_price int)")
conn.exec_driver_sql("INSERT INTO my_thing (id, sell_price) VALUES (1, 123)")
Base = automap_base()
class MyThing(Base):
__tablename__ = "my_thing"
Base.prepare(autoload_with=engine)
# test
with Session(engine) as sess:
thing_1 = sess.scalar(select(MyThing).where(MyThing.id == 1))
thing_1.sell_price = 456
sess.commit()
""" SQL emitted:
UPDATE my_thing SET sell_price=? WHERE my_thing.id = ?
[generated in 0.00032s] (456, 1)
"""

Related

SQLAlchemy Table classes and imports

I started a project using PostgreSQL and SQLAlchemy. Since i'm not a experienced programmer(just started using classes) and also quite new to databases i noticed some workflows i don't really understand.
What i understand up till now from classes is the following workflow:
# filename.py
class ClassName():
def __init__(self):
# do something
def some_funcion(self, var1, var2):
# do something with parameters
---------------------------------------
# main.py
from filename import ClassName
par1 = ...
par2 = ...
a = ClassName()
b = a.some_function(par1, par2)
Now i am creating tables from classes:
# base.py
from sqlalchemy.orm import declarative_base
Base = declarative_base()
# tables.py
from base import Base
from sqlalchemy import Column
from sqlalchemy import Integer, String
class A(Base):
__tablename__ = "a"
a_id = Column(Integer, primary_key=True)
a_column = Column(String(30))
class B(Base):
__tablename__ = "b"
b_id = Column(Integer, primary_key=True)
b_column = Column(String(30))
and
import typing
from base import Base
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import Table
from sqlalchemy.orm import sessionmaker
from tables import A, B
metadata_obj = MetaData()
def create_tables(engine):
session = sessionmaker()
session.configure(bind=engine)
Base.metadata.create_all(bind=engine)
a = Table("a", metadata_obj, autoload_with=engine)
b = Table("b", metadata_obj, autoload_with=engine)
return(a, b) # not sure return is needed
if __name__ == "__main__":
username = "username"
password = "AtPasswordHere!"
dbname = "dbname"
url = "postgresql://" + username + ":" + password + "#localhost/" + dbname
engine = create_engine(url, echo=True, future=True)
a, b = create_tables(engine)
Everything works fine in that it creates Table A and Table B in the database. The point i don't understand is the following:
Both my IDE(pyflake) and LGTM complain 'Tables. ... imported but not used'. (EDIT i understand why it complains in the way it is not the normal Class flow. It is mor about Why it is not the normal class workflow)
Is this normal behavior for this usecase? I only see examples that make use of the above workflow
Are there better methods to create the same results (but without the warnings)
If this is the normal behavior: Is there an explanation for this? I didn't read it anywhere.

What is the SQLAlchemy (ORM) command to return an object (which represents a row in the database) based on a query filter?

When I issue the command sess.query(TestClass).all()
SQLAlchemy returns the two objects I instantiated (as expected).
Code:
query_result_1 = sess.query(TestClass).all()
print(query_result_1)
output:
[<main.TestClass object at 0x10e9df7c0>, <main.TestClass object at 0x10e9df9a0>]
When I issue the command that I expected to return an object based on a filter, I instead got SQL commands...
code:
query_result_2 = sess.query(TestClass).filter(TestClass.name=='name_1')
output:
SELECT test_table.id AS test_table_id, test_table.name AS test_table_name
FROM test_table
WHERE test_table.name = ?
Here is the entire script:
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import Session
from sqlalchemy.orm import Query
print('\nsqlalchemy version: ' + sqlalchemy.__version__)
Base = declarative_base()
class TestClass(Base):
__tablename__ = 'test_table'
id = Column(Integer, primary_key=True)
name = Column(String(20))
def __init__(self, name):
self.name = name
engine = create_engine('sqlite:///', echo=False)
Base.metadata.create_all(engine)
sess = Session(engine)
obj_1 = TestClass('name_1')
sess.add(obj_1)
obj_2 = TestClass('name_2')
sess.add(obj_2)
print('\ntest instance creation (before commit):')
print('obj_1.name = %s' % (obj_1.name))
print('obj_2.name = %s' % (obj_2.name))
query_result_1 = sess.query(TestClass).all()
print('\nquery_result_1 (query all before commit):')
print(query_result_1)
# QUESTION: What's wrong with this statement...'
# or my expectation of what it should produce?
query_result_2 = sess.query(TestClass).filter(TestClass.name=='name_1')
print('\nquery_result_2 (query (filter by name) before commit):')
print('I expected this query to return the TestClass object with name = name_1')
print('instead, I got this:')
print(query_result_2)
sess.commit()
query_result_3 = sess.query(TestClass).all()
print('\nquery_result_3 (query all after commit):')
print(query_result_3)
# QUESTION: What's wrong with this statement...'
# or my expectation of what it should produce?
query_result_4 = sess.query(TestClass).filter(TestClass.name=='name_1')
print('\nquery_result_4 (query (filter by name) after commit):')
print('I expected this query to return the TestClass object with name = name_1')
print('instead, I got this:')
print(query_result_4)
Here is the terminal output:
sqlalchemy version: 1.3.17
test instance creation (before commit):
obj_1.name = name_1
obj_2.name = name_2
query_result_1 (query all before commit):
[<main.TestClass object at 0x10e9df7c0>, <main.TestClass object at 0x10e9df9a0>]
query_result_2 (query (filter by name) before commit):
I expected this query to return the TestClass object with name = name_1
instead, I got this:
SELECT test_table.id AS test_table_id, test_table.name AS test_table_name
FROM test_table
WHERE test_table.name = ?
query_result_3 (query all after commit):
[<main.TestClass object at 0x10e9df7c0>, <main.TestClass object at 0x10e9df9a0>]
query_result_4 (query (filter by name) after commit):
I expected this query to return the TestClass object with name = name_1
instead, I got this:
SELECT test_table.id AS test_table_id, test_table.name AS test_table_name
FROM test_table
WHERE test_table.name = ?
UPDATE...
I have figured out that SQLAlchemy is, in fact, returning a list of objects (in this case the list only contains one object) after I issue the query statement with the filter: query(class).filter(class.attrbt==___).
The good news is that SQLAlchemy is (mostly) behaving the way I expected, and there is nothing wrong with my query statement. But now I have a different question:
Why does the output from SQLAlchemy show SQL commands in response to the query().filter() statement? In response to the query().all() statement it returns a list of objects - this is what it "should" do (and, in fact, it is what is happening) in response to the query().filter() statement.
Your first example returns a list of objects because you invoked .all() to actually execute the query and return the results.
Your second example prints the SQL because you have created the query but you haven't executed it yet. .filter modifies the query but does not execute it. Compare ...
thing = session.query(Account).filter(Account.id == 1)
print(type(thing)) # <class 'sqlalchemy.orm.query.Query'>
print(thing) # SELECT so62234199.id AS so62234199_id, ...
... with ...
thing = session.query(Account).filter(Account.id == 1).one()
print(type(thing)) # <class '__main__.Account'>
print(thing) # <Account(id=1, created='2020-01-01 00:00:00')>
... and ...
thing = session.query(Account).filter(Account.id == 1).all()
print(type(thing)) # <class 'list'>
print(thing) # [<Account(id=1, created='2020-01-01 00:00:00')>]
For more information, see Returning Lists and Scalars in the ORM tutorial.

SQLalchemy query column by name [duplicate]

This question already has answers here:
How to give column name dynamically from string variable in sql alchemy filter?
(4 answers)
Closed 7 years ago.
I need to select multiple users by various field values like, where field is a string of the column name like 'name' or 'email':
users = meta.Session.query(User) \
.filter(User[field].in_(values)) \
.all()
How do I programmatically access a column by its name?
If I understood well, you could use literal_column or standard getattr, see this example:
from sqlalchemy import create_engine, Column, types
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.sql.expression import literal_column
engine = create_engine('sqlite:///:memory:', echo=False)
Base = declarative_base()
session = scoped_session(sessionmaker(bind=engine))
class User(Base):
__tablename__ = "user"
login = Column(types.String(50), primary_key=True)
name = Column(types.String(255))
def __repr__(self):
return "User(login=%r, name=%r)" % (self.login, self.name)
Base.metadata.create_all(engine)
if __name__ == '__main__':
# create two users
u1 = User(login='someone', name="Some one")
u2 = User(login='someuser', name="Some User")
u3 = User(login='user', name="User")
u4 = User(login='anotheruser', name="Another User")
session.add(u1)
session.add(u2)
session.add(u3)
session.add(u4)
session.commit()
print "using literal_column"
print session.query(User).filter(literal_column("login").in_(["someuser", "someone"])).all()
print "using getattr"
print session.query(User).filter(getattr(User, "login").in_(["someuser", "someone"])).all()
output:
using literal_column
[User(login=u'someone', name=u'Some one'), User(login=u'someuser', name=u'Some User')]
using getattr
[User(login=u'someone', name=u'Some one'), User(login=u'someuser', name=u'Some User')]
If you will use literal_column, beware that the argument is inserted into the query without any transformation. This may expose you to a SQL Injection vulnerability if you accept values for the text parameter from outside your application.

SQLAlchemy: how exchange unique values in one transaction?

My versions:
$ python
Python 3.4.0 (default, Apr 11 2014, 13:05:11)
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sqlalchemy
>>> sqlalchemy.__version__
'1.0.0'
Let's look to the example for sqlite (in production I use mysql, but it does not matter here):
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import Integer, String
Base=declarative_base()
engine = create_engine('sqlite:///:memory:', echo=True)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(16))
tech_id = Column(Integer, unique=True, nullable=True)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
s = Session()
Now we add two records:
u1=User(name="User1", tech_id=None)
u2=User(name="User2", tech_id=10)
s.add(u1)
s.add(u2)
s.commit()
Next try to modify them:
u1=s.query(User).filter(User.name=="User1").first()
u2=s.query(User).filter(User.name=="User2").first()
u2.tech_id=None
u1.tech_id=10
s.commit()
After commit I've got the exception:
<-- cut -->
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: user.tech_id [SQL: 'UPDATE user SET tech_id=? WHERE user.id = ?'] [parameters: ((10, 1), (None, 2))]
If I do like this:
u2.tech_id=None
s.commit()
u1.tech_id=10
s.commit()
It's all right.
Is it possible to do requests by only one commit (by only one transaction)?
You can do it in this way:
#u1=s.query(User).filter(User.name=="User1").first()
#u2=s.query(User).filter(User.name=="User2").first()
#u2.tech_id=None
#u1.tech_id=10
#s.commit()
s.query(User).filter(User.name=="User2").update(
{User.tech_id: None}
)
s.query(User).filter(User.name=="User1").update(
{User.tech_id: 10}
)
s.commit()
The essence of that changes is to perform right sequence of actions. You can't create duplicate values for unique key (at least in mysql) even before committing. But you can create multiple NULL values for unique column.

SQLAlchemy: replacing object with a new one, following defaults

I want to create a new instance of an SQLAlchemy object, so that fields are filled with default values, but I want to commit that to the database generating an UPDATE to a row that already exists with the same primary key, effectively resetting it to the default values. Is there any simple way to do that?
I have tried to do that and failed, because SQLAlchemy session tracks state of objects. So there is no easy way to make session to track new object as persistent one.
But you want to reset object to default, do you? There is a simple way to do that:
from sqlalchemy.ext.declarative import declarative_base
class Base(object):
def reset(self):
for name, column in self.__class__.__table__.columns.items():
if column.default is not None:
setattr(self, name, column.default.execute())
Base = declarative_base(bind=engine, cls=Base)
This adds reset method to all your model classes.
Here is the complete working example to fiddle with:
import os
from datetime import datetime
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import functions
here = os.path.abspath(os.path.dirname(__file__))
engine = create_engine('sqlite:///%s/db.sqlite' % here, echo=True)
Session = sessionmaker(bind=engine)
class Base(object):
def reset(self):
for name, column in self.__class__.__table__.columns.items():
if column.default is not None:
setattr(self, name, column.default.execute())
Base = declarative_base(bind=engine, cls=Base)
class Thing(Base):
__tablename__ = 'things'
id = Column(Integer, primary_key=True)
value = Column(String(255), default='default')
ts1 = Column(DateTime, default=datetime.now)
ts2 = Column(DateTime, default=functions.now())
def __repr__(self):
return '<Thing(id={0.id!r}, value={0.value!r}, ' \
'ts1={0.ts1!r}, ts2={0.ts2!r})>'.format(self)
if __name__ == '__main__':
Base.metadata.drop_all()
Base.metadata.create_all()
print("---------------------------------------")
print("Create a new thing")
print("---------------------------------------")
session = Session()
thing = Thing(
value='some value',
ts1=datetime(2014, 1, 1),
ts2=datetime(2014, 2, 2),
)
session.add(thing)
session.commit()
session.close()
print("---------------------------------------")
print("Quering it from DB")
print("---------------------------------------")
session = Session()
thing = session.query(Thing).filter(Thing.id == 1).one()
print(thing)
session.close()
print("---------------------------------------")
print("Reset it to default")
print("---------------------------------------")
session = Session()
thing = session.query(Thing).filter(Thing.id == 1).one()
thing.reset()
session.commit()
session.close()
print("---------------------------------------")
print("Quering it from DB")
print("---------------------------------------")
session = Session()
thing = session.query(Thing).filter(Thing.id == 1).one()
print(thing)
session.close()
Is there any simple way to do that?
Upon further consideration, not really. The cleanest way will be to define your defaults in __init__. The constructor is never called when fetching objects from the DB, so it's perfectly safe. You can also use backend functions such as current_timestamp().
class MyObject(Base):
id = Column(sa.Integer, primary_key=True)
column1 = Column(sa.String)
column2 = Column(sa.Integer)
columnN = Column(sa.String)
updated = Column(sa.DateTime)
def __init__(self, **kwargs):
kwargs.setdefault('column1', 'default value')
kwargs.setdefault('column2', 123)
kwargs.setdefault('columnN', None)
kwargs.setdefault('updated', sa.func.current_timestamp())
super(MyObject, self).__init__(**kwargs)
default_obj = MyObject()
default_obj.id = old_id
session.merge(default_obj)
session.commit()