I am using SQLALCHEMY and I can't update the table, i don't know why it doesn't work.
models.py
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Date
Base = declarative_base()
class Book(Base):
__tablename__='books'
id= Column(Integer, primary_key=True)
title=Column(String)
author = Column(String)
pages = Column(String)
published=Column(Date)
def __repr__(self):
return "<Book(title='{}', author='{}', pages={}, published={})>"\
.format(self.title, self.author, self.pages, self.published)
crud.py
from sqlalchemy import create_engine, update
from sqlalchemy.orm import sessionmaker
engine = create_engine(config.DATABASE_URI)
Session = sessionmaker(bind=engine)
s= Session()
def recreate_database():
models.Base.metadata.drop_all(engine)
models.Base.metadata.create_all(engine)
I insert the first row on the table 'books' :
book = models.Book(
title='Deep Learning',
author='Ian Goodfellow',
pages=775,
published=datetime(2016,11,18)
)
using:
recreate_database()
s.add(book)
Then I verify that row is added:
<Book(title='Deep Learning', author='Ian Goodfellow', pages=775, published=2016-11-18 00:00:00)>
And I try to update that valor changing the value of 'pages' to 900 using:
libro=models.Base.metadata.tables['books']
u=update(libro)
u=u.values({"pages": 900})
u=u.where(libro.c.title=="Deep Learning")
engine.execute(u)
s.commit()
s.close()
I don't get error but it does not update the value of pages.
I appreciate if you could tell me where is the error on my code to update the value.
Thanks !!
its s.execute(u) not engine.execute(u)
You also declared pages to be a string column when it actually is an integer (maybe you want to change that).
edit: if you already have the book object, you can also use
book.pages = 1200
s.commit()
to change the value in your database.
Related
I was trying to make my ORM code a bit more elegant by using classmethods in sqlalchemy and I wanted to make a method called get which would just retrieve a single existing instance of the ORM object with a few parameters. But since it seems I need a session to do it, the only way I could figure out how to do it was to pas the session as a parameter to the get method. It is working but I can't shake the feeling that I am building an antipattern.
Here is my ORM class (simplified):
class GeocellZone(Base):
__tablename__ = "geocell_zone"
__table_args__ = (UniqueConstraint("study_area_id", "version_id", "name"),)
id = Column(Integer, primary_key=True)
study_area_id = Column(Integer, ForeignKey("study_area.id"))
version_id = Column(Integer, ForeignKey("version.id"))
name = Column(Text)
geometry = Column(Geometry(srid=4326))
#classmethod
def get(cls, session, version, study_area, name):
return session.query(cls) \
.filter(cls.study_area_id == study_area.id) \
.filter(cls.version_id == version.id) \
.filter(cls.name == name) \
.first()
And here is what it looks like when I call it in my application:
import os
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from myapp.orm import *
engine = create_engine(
f"postgresql://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}#{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}",
echo=False
)
session = sessionmaker(bind=engine)()
GeocellZone.get(session, version, study_area, "Antwerpen")
This works, it returns the exact GeocellZone instance that I want. But I feel dirty passing the session to the ORM class like this. Am I overthinking this? Or is there a better way to do this?
In Hibernate it's possible to query using raw sql and get entities (objects) back. Something like: createSQLQuery(sql).addEntity(User.class).list().
Is it possible to do the same in sqlalchemy?
As #Ilja notes via link in a comment to the question, it is possible to do what you describe using .from_statement() as described in the documentation:
from sqlalchemy import Column, create_engine, Integer, select, String, text
from sqlalchemy.orm import declarative_base, Session
engine = create_engine("sqlite://")
Base = declarative_base()
class Person(Base):
__tablename__ = "person"
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
def __repr__(self):
return f"<Person(id={self.id}, name='{self.name}')>"
Base.metadata.create_all(engine)
# sample data
with Session(engine) as session, session.begin():
session.add_all(
[Person(name="Adam"), Person(name="Alicia"), Person(name="Brandon")]
)
# test
with Session(engine) as session, session.begin():
sql = "SELECT id FROM person WHERE name LIKE 'A%'"
results = session.scalars(select(Person).from_statement(text(sql))).all()
print(results)
# [<Person(id=1, name='Adam')>, <Person(id=2, name='Alicia')>]
When using the entityManager you can try:
entityManager.createNativeQuery("select some native query", User.class)
According to the API:
public Query createNativeQuery(String sqlString, Class resultClass);
Below is a form that populates an empty parent object and creates its children. It was necessary to manually invoke a ModelFormField, which was a minor annoyance. It works great. However, when I use the form to do an update, only the object is updated -- the children are created fresh.
What is the correct way to propogate an update to the children in this framework? Effectively, I'd like to the two print statements below to print the same thing. I'd be especially grateful if the form would create (delete) children if the formdata had extra (was missing) data for the children.
from multidict import MultiDict
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from wtforms_alchemy import ModelFieldList, ModelForm, ModelFormField
Base = declarative_base()
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
name = Column(String)
parent_id = Column(Integer, ForeignKey("parent.id"))
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
name = Column(String)
children = relationship(Child)
class ChildForm(ModelForm):
class Meta:
model = Child
class ParentForm(ModelForm):
class Meta:
model = Parent
children = ModelFieldList(ModelFormField(ChildForm)) # annoyed!
engine = create_engine("sqlite://")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
# Form data
formdata = MultiDict({"name": "Foo", "children-0-name": "hotdog"})
# Create initial object
question = Parent()
form = ParentForm(formdata, obj=question)
form.populate_obj(question)
if not form.validate():
raise RuntimeError(form.errors)
session.add(question)
session.commit()
# prints: "(1, 1)"
print((question.id, question.children[0].id))
# Retrieve object and update with same data
question_get = session.query(Parent).get(question.id)
form = ParentForm(formdata, obj=question_get)
form.populate_obj(question_get)
if not form.validate():
raise RuntimeError(form.errors)
session.add(question_get)
session.commit()
# prints: "(1, 2)", want it to print the same as above
print((question_get.id, question_get.children[0].id))
In digging through the code, it looks like object ids are expected for updates. Therefore, one must use a form on update which includes the id and pass around the ids.
--- orig.py 2018-05-01 20:51:09.000000000 -0700
+++ fixed.py 2018-05-01 20:56:16.000000000 -0700
## -4,6 +4,7 ##
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
+from wtforms import IntegerField
from wtforms_alchemy import ModelFieldList, ModelForm, ModelFormField
## -24,6 +25,7 ##
class ChildForm(ModelForm):
class Meta:
model = Child
+ id = IntegerField() # Optional field used in update operations.
class ParentForm(ModelForm):
class Meta:
## -53,7 +55,11 ##
# Retrieve object and update with same data
question_get = session.query(Parent).get(question.id)
-form = ParentForm(formdata, obj=question_get)
+child_id = question_get.children[0].id
+formdata_update = MultiDict({"name": "Foo",
+ "children-0-id": child_id,
+ "children-0-name": "pizza"})
+form = ParentForm(formdata_update, obj=question_get)
form.populate_obj(question_get)
if not form.validate():
raise RuntimeError(form.errors)
Nice sleuthing. Still this seems rather cumbersome. Have you subsequently found a easier way to do an update on a form with a one to many relationship? WTForms are supposed to save developer time. But this seems like more overhead than doing it without WTForms.
When I try to get property from mapper by name I can't do it when hybrid_property name specified.
import datetime
from sqlalchemy import create_engine, MetaData
from sqlalchemy import Column, String, DateTime, Integer
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import sessionmaker
metadata = MetaData()
Base = declarative_base(metadata=metadata)
class Duration(Base):
__tablename__ = "duration"
pk = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
#hybrid_property
def duration(obj):
return obj.name
engine = create_engine('sqlite:///:memory:', echo=True)
metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
duration = Duration(name="Test")
session.add(duration)
session.commit()
print duration.__mapper__.has_property('duration') # Returns False, needs True
As you can see the has_property('duration') does not see the hybrid_property duration and returns False instead True.
What solution could be suggested?
while there's some poor naming quality going on here, a hybrid is not a MapperProperty, which is what the mapper considers to be "properties". To suit the use case where users want to view all class members that are at-all ORM specific, not just MapperProperty, we have all_orm_descriptors :
print "duration" in duration.__mapper__.all_orm_descriptors
Say I have a class in SQLAlchemy like
class Note(Base):
__tablename__ = 'notes'
slug = Column('short_title', String, primary_key=True)
date = Column('day', Date, primary_key=True)
contents = Column(Text, nullable=True)
Given only a reference to the Note class, how could a function build the list ['Note.slug', 'Note.date'] predicated on their constituting the corresponding table's primary key?
Furthermore, is there a way to filter class attributes based on column properties? (such as nullable, type, uniqueness, membership in an index, etc)
Try this to find the primary fields.
from sqlalchemy import create_engine
engine = create_engine('sqlite:///:memory:', echo=True)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Date, Text
from sqlalchemy.orm.attributes import InstrumentedAttribute
Base = declarative_base()
class Note(Base):
__tablename__ = 'notes'
slug = Column('short_title', String, primary_key=True)
date = Column('day', Date, primary_key=True)
contents = Column(Text, nullable=True)
if __name__ == '__main__':
import inspect
predicate = lambda x: isinstance(x, InstrumentedAttribute)
fields = inspect.getmembers(Note, predicate=predicate)
fields_vars = [vars(y.property.columns[0]) for x,y in fields]
primary_fields = [x['name'] for x in fields_vars if x['primary_key']]
print "All Fields :", fields_vars
print "Primary Fields :", primary_fields
This way you can find any type of fields from your model.
this will give you a list of column names which are primary_keys:
from sqlalchemy.orm import class_mapper
primary_keys = [key.name for key in class_mapper(Note).primary_key]
Not exactly what's needed, but a bit shorter variant that's only using inside information:
from sqlalchemy import create_engine
engine = create_engine('sqlite:///:memory:', echo=True)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Date, Text
Base = declarative_base()
class Note(Base):
__tablename__ = 'notes'
slug = Column('short_title', String, primary_key=True)
date = Column('day', Date, primary_key=True)
contents = Column(Text, nullable=True)
if __name__ == '__main__':
print [(i.name, i.primary_key) for i in Note.__table__.columns]
This gives us following result:
[('short_title', True), ('day', True), ('contents', False)]