Is it possible to query using raw sql and get object back? - sqlalchemy

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);

Related

Classmethod for retrieving a specific instance with sqlalchemy

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?

Dialect-specific table and column names in sqlalchemy while reusing an existing ORM mapping

I use sqlalchemy's ORM library to interface with one of our application's databases. On one host, the database backend of an application instance is PostgreSQL, while on another, we use MySQL for historical reasons.
The schemas (and relationships) are identical on both backends, but the table and column names in PostgreSQL are lowercase, while on MySQL they are uppercase. Is there any way I can adapt my schema in sqlalchemy such that I don't need to duplicate code?
The following is an example of my schema. Hint, the application is a Confluence wiki ;)
from sqlalchemy import (
BigInteger,
Column,
DateTime,
String,
)
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Space(Base):
__tablename__ = 'spaces'
spaceid = Column(BigInteger, primary_key=True)
spacename = Column(String(255))
spacekey = Column(String(255), nullable=False, unique=True)
lowerspacekey = Column(String(255), nullable=False, index=True)
spacedescid = Column(BigInteger, index=True)
homepage = Column(BigInteger, index=True)
creator = Column(String(255), index=True)
creationdate = Column(DateTime, index=True)
lastmodifier = Column(String(255), index=True)
lastmoddate = Column(DateTime)
spacetype = Column(String(255))
spacestatus = Column(String(255), index=True)
Thanks to #rfkortekaas' suggestion, I managed to solve the issue by automapping the schema and subsequently overriding the naming scheme for table and column names.
The documentation of sqlalchemy has two very useful pages:
Overriding Naming Schemes
Intercepting Column Definitions
For posterity's sake:
from sqlalchemy import event
from sqlalchemy.ext.automap import automap_base
from .utilities import to_pascal_case, to_snake_case
AutomapBase = automap_base()
#event.listens_for(AutomapBase.metadata, "column_reflect")
def column_reflect(inspector, table, column_info):
column_info['key'] = to_snake_case(column_info['name'])
def classname_for_table(base, table_name, table):
return to_pascal_case(table_name)
if __name__ == '__main__':
dbcs = 'postgresql://username:password#127.0.0.1:3306/database'
automap_engine = create_engine(dbcs, future=True, echo=verbose > 2)
AutomapBase.prepare(
autoload_with=automap_engine,
classname_for_table=classname_for_table,
)
# Access the automapped classes with AutomapBase.classes.<class_name>

Can't create MySQL Boolean Field Using flask-sqlalchemy & flask-migrate

I have a working flask app with few models. The User model is as follow...
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(128), index=True, unique=True)
password_hash = db.Column(db.String(128))
first_name = db.Column(db.String(32))
last_name = db.Column(db.String(32))
bio = db.Column(db.String(255))
patterns = db.relationship('Pattern', backref='user', lazy='dynamic')
Now I want to add a new boolean column to the model. I'm utilizing MySQL as a database. I have tried the following...
invited = db.Column(db.Boolean, default=0)
but when I runt flask db migrate I get the following...
INFO [alembic.runtime.migration] Context impl MySQLImpl.
I also tried
from sqlalchemy import BOOLEAN # also with Boolean
...
invited = db.Column(BOOLEAN, default=0) # also with Boolean
but get the same error. Reading MySQL documentation found out that MySQL doesn't have boolean type rather TINYINT. But reading this Github thread I understand that the Boolean class will turn into TINYINT based on the dialect. So I did the following...
from sqlalchemy.dialect.mysql import BOOLEAN
and still I get the same error when flask run migrate. Seems like Alembic can't see the changes in the model.
Is there a way to create a boolean field in mysql utilizing flask-migrate and flask-sqlalchemy?
Try below, I'm assuming Flask migrate is not recognizing db.Column(BOOLEAN, default=0).
a_boolean_field = db.Column(db.Boolean(), default=False)
I've just tested it and above works and Flask-Migrate was able to detect it.
You can do it like so
from sqlalchemy import Boolean, Column
invited = Column(Boolean, nullable=False, default=False)
this should work with MySQL.

SQLAlchemy: how mapper has_property method to do return True when hybrid_property

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

sqlalchemy multiple databases with same table names not working

I have two databases that I'm working with in with Python using SQLAlchemy, the databases share table names and therefore I'm getting an error message when running the code.
The error message is :
sqlalchemy.exc.InvalidRequestError: Table 'wo' is already defined for this MetaData instance. Specify 'extend_existing=True' to redefine options and columns on an existing Table object.
The simplified code is below:
from sqlalchemy import create_engine, Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, backref
from mysql.connector.connection import MySQLConnection
Base = declarative_base()
def get_characterset_info(self):
return self.get_charset()
MySQLConnection.get_characterset_info = MySQLConnection.get_charset
mysqlengine = create_engine('mysql+mysqlconnector://......../mp2', echo=True)
MYSQLSession = sessionmaker(bind=mysqlengine)
mysqlsession= MYSQLSession()
MP2engine = create_engine('mssql+pyodbc://......../mp2', echo=True)
MP2Session = sessionmaker(bind=MP2engine)
mp2session= MP2Session()
class MYSQLWo(Base):
__tablename__= 'wo'
wonum = Column(String, primary_key=True)
taskdesc = Column(String)
comments = relationship("MYSQLWocom", order_by="MYSQLWocom.wonum", backref='wo')
class MYSQLWocom (Base):
__tablename__='wocom'
wonum = Column(String, ForeignKey('wo.wonum'), primary_key=True)
comments = Column(String, primary_key=True)
class MP2Wo(Base):
__tablename__= 'wo'
wonum = Column(String, primary_key=True)
taskdesc = Column(String)
comments = relationship("MP2Wocom", order_by="MP2Wocom.wonum", backref='wo')
class MP2Wocom (Base):
__tablename__='woc'
wonum = Column(String, ForeignKey('wo.wonum'), primary_key=True)
location = Column(String)
sublocation1 = Column(String)
texts = Column(String, primary_key=True)
How do I deal with databases having the same table structure? I'm guessing it has something to do with the MetaData instance, but the SQLAlchemy documentation gets a little confusing when talking about the difference in the class declarative and classical usage..
Since in reality the tables had different structures, the solution was to simply create a separate declarative base. If the tables indeed had the same structure, I would have only needed one class for both tables.
Base = declarative_base()
Base2 = declarative_base() #this is all I needed
class MYSQLWo(Base):
....
class MYSQLWocom(Base):
....
class MP2Wo(Base2):
....
class MP2Wocom(Base2)
http://groups.google.com/group/sqlalchemy/browse_thread/thread/afe09d6387a4dc69?hl=en
You can use one db instance with two Model to bypass this problem.
And this can also be used to implement a master/slave use case in Flask-SQLAlchemy.
Just like this:
app = Flask(__name__)
app.config['SQLALCHEMY_BINDS'] = {'rw': 'rw', 'r': 'r'}
db = SQLAlchemy(app)
db.Model_RW = db.make_declarative_base()
class A(db.Model):
__tablename__ = 'common'
class B(db.Model_RW):
__tablename__ = 'common'