SQL-Alchemy: having problems creating relationships without ForeignKeys - python

Im trying to create relations but without foreign key constraints in db
quite similar to this post:
sqlalchemy: create relations but without foreign key constraint in db?
However im trying to do it with classical mapping
and I cant figure out what Im doing wrong with it
from sqlalchemy import (
Table,
MetaData,
Column,
String,
)
from sqlalchemy.orm import mapper, relationship
from uuid import uuid4
class InspectionRecord:
def __init__(self, equipment):
self.equipment = equipment
class InspectedItem:
def __init__(self, item):
self.item = item
metadata = MetaData()
inspected_items = Table(
'inspected_items',
metadata,
Column('inspection_id', String(50)),
Column('inspected_item_id', String(50), primary_key=True),
Column('item', String(50))
)
inspection_records = Table(
'inspection_records',
metadata,
Column('inspection_id', String(50), primary_key=True, default=uuid4),
Column('equipment', String(50))
)
def start_mappers():
inspected_items_mapper = mapper(InspectedItem, inspected_items)
inspection_records_mapper = mapper(InspectionRecord, inspection_records, properties={
"inspected_items": relationship(inspected_items_mapper,
primaryjoin='foreign(inspected_items.inspection_id) == inspection_records.inspection_id',
uselist=False)}
) # this is the part where I'm having difficulties with
if __name__ == '__main__':
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///foo.db')
metadata.drop_all(bind=engine)
metadata.create_all(engine)
start_mappers()
Session = sessionmaker(bind=engine)
session = Session()
inspection_record = InspectionRecord(equipment='equipment_01')
session.add(inspection_record)
after so many attempts i even with additional tinkering i only get
this error
sqlalchemy.exc.InvalidRequestError: When initializing mapper mapped class InspectionRecord->inspection_records, expression '[InspectedItem.inspection_id]' failed to locate a name ("name 'InspectedItem' is not defined")
Any help would be really really really appreciated :)

got this working:
change to mapper to mapper_registry.map_imperatively
def start_mappers():
inspected_items_mapper = mapper_registry.map_imperatively(InspectedItem, inspected_items)
inspection_records_mapper = mapper_registry.map_imperatively(InspectionRecord, inspection_records, properties={
"inspected_items": relationship(InspectedItem,
primaryjoin='foreign(InspectedItem.inspection_id) == InspectionRecord.inspection_id',
uselist=False)}

Related

how make Select with SQLAlachemy?

I am using SQAlchemy and python, without flask, well when I do an infinite loop and invoke the select method that sqlalchemy offers, it returns the value of my table, but when changing the value of a specific column from phpmyadmin, in the python script is not reflected the change made, someone could give me some advice or help, please thank you in advance.
PD: I leave the code for you to analyze:
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey, create_engine, Float, DateTime, update, Date, Time
from sqlalchemy.sql import select
import time
import os
ahora = Table('ahora', metadata,
Column('id', Integer, primary_key=True),
Column('temperatura', Float()),
Column('humedad', Float()),
Column('canal1', Float()),
Column('canal2', Float()),
Column('canal3', Float()),
Column('canal4', Float()),
Column('hora', Time()),
)
engine = create_engine('mysql+pymysql://javi:javiersolis12#10.0.0.20/Tuti')
connection = engine.connect()
while True:
#Seleccionara la unica entrada en la tabla Configuracion
query = select([configuracion])
confi_actual = connection.execute(query).fetchone()
query_aux = select([ahora])
datos_actuales = connection.execute(query_aux).fetchone()
print(datos_actuales)
time.sleep(8)
You can specify the components you need to select into the Query in your Session. And then use, for example all() to get all the enterance. Please look for the next example:
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
...
class Ahora(Base):
__tablename__ = 'ahora'
id = sa.Column(sa.Integer, primary_key=True)
temperatura: int = sa.Column(sa.Float)
...
...
engine = create_engine('mysql+pymysql://javi:javiersolis12#10.0.0.20/Tuti')
session = Session(engine)
query = session.query(Ahora)
# some actions with query, for example filter(), limit(), etc
ahora = query.all()

SQLAlchemy Inheritance - Foreign keys do not resolve when using the same base table

I'd like for some of my tables to be able to access models via foreign keys, using SqlAlchemy's relationship function. Unfortunately, I'm running into an issue where the foreign keys do not appear to resolve.
What I'm doing differently from the standard SqlAlchemy docs is using an inheritance structure where most tables inherit from the same base.
Tables:
Noun:
nounId INT
Person:
nounId INT PRIMARY KEY References(Noun.nounId)
name STRING
Place:
nounId INT PRIMARY KEY References(Noun.nounId)
location STRING
Plan:
nounId INT PRIMARY KEY References(Noun.nounId)
personId INT References(Person.nounId)
plan STRING
Trip:
nounId INT PRIMARY KEY References(Noun.nounId)
planId INT References(Plan.nounId)
placeId INT References(Place.nounId)
plan STRING
Currently, I'm unable to get SqlAlchemy to resolve the plan.person model and I'm unsure as to why. (I'd also like to be able to resolve person.places via SqlAlchemy's secondary, but I think the two issues might be related).
The following code will raise an error at the "assert" line:
from sqlalchemy import *
from sqlalchemy.orm import *
def test():
metadata = MetaData()
# Base Table
nounTable = Table(
'Nouns', metadata,
Column('nounId', Integer, primary_key=True)
)
personsTable = Table(
'Persons', metadata,
Column('nounId', Integer, ForeignKey('Nouns.nounId'), primary_key=True),
Column('name', String)
)
placesTable = Table(
'Places', metadata,
Column('nounId', Integer, ForeignKey('Nouns.nounId'), primary_key=True),
Column('location', String)
)
plansTable = Table(
'Plans', metadata,
Column('nounId', Integer, ForeignKey('Nouns.nounId'), primary_key=True),
Column('personId', Integer, ForeignKey('Persons.nounId')),
Column('plan', String)
)
tripsTable = Table(
'Trips', metadata,
Column('nounId', Integer, ForeignKey('Nouns.nounId'), primary_key=True),
Column('planId', Integer, ForeignKey('Plans.nounId')),
Column('placeId', Integer, ForeignKey('Places.nounId')),
Column('plan', String)
)
class Noun(object): pass
class Person(Noun): pass
class Place(Noun): pass
class Plan(Noun): pass
class Trip(Noun): pass
mapper(Noun, nounTable)
mapper(Trip, tripsTable, inherits=Noun)
mapper(Place, placesTable, inherits=Noun)
mapper(Plan, plansTable, inherits=Noun, properties={
# SqlAlchemy will raise an exception if `foreign_keys` is not explicitly defined
'person': relationship(Person, foreign_keys=[personsTable.c.nounId], backref='plans')
})
mapper(Person, personsTable, inherits=Noun, properties={
# This is not resolved either
'places': relationship(Place,
secondary = join(Plan, Trip, Plan.nounId==Trip.planId),
secondaryjoin = lambda: Trip.placeId==Place.nounId,
primaryjoin = lambda: Person.nounId==Plan.personId
)
})
engine = create_engine('sqlite://')
metadata.create_all(engine)
session = sessionmaker(bind=engine)()
alice = Person()
alice.name = "alice"
session.add(alice)
session.commit()
planA = Plan()
planA.personId = alice.nounId
planA.plan = "This is a plan"
session.add(planA)
session.commit()
# The reference isn't resolved
assert planA.person, "No person found"
print "Plan: {}".format([planA.nounId, planA.person])
test()
Ideally, I'd like to be able to retrieve all Persons at a place and vice versa, but currently, it is not able to resolve the simple Plan->Person relationship.

Updating row in SqlAlchemy ORM

I am trying to obtain a row from DB, modify that row and save it again.
Everything by using SqlAlchemy
My code
from sqlalchemy import Column, DateTime, Integer, String, Table, MetaData
from sqlalchemy.orm import mapper
from sqlalchemy import create_engine, orm
metadata = MetaData()
product = Table('product', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(1024), nullable=False, unique=True),
)
class Product(object):
def __init__(self, id, name):
self.id = id
self.name = name
mapper(Product, product)
db = create_engine('sqlite:////' + db_path)
sm = orm.sessionmaker(bind=db, autoflush=True, autocommit=True, expire_on_commit=True)
session = orm.scoped_session(sm)
result = session.execute("select * from product where id = :id", {'id': 1}, mapper=Product)
prod = result.fetchone() #there are many products in db so query is ok
prod.name = 'test' #<- here I got AttributeError: 'RowProxy' object has no attribute 'name'
session .add(prod)
session .flush()
Unfortunately it does not work, because I am trying to modify RowProxy object. How can I do what I want (load, change and save(update) row) in SqlAlchemy ORM way?
I assume that your intention is to use Object-Relational API.
So to update row in db you'll need to do this by loading mapped object from the table record and updating object's property.
Please see code example below.
Please note I've added example code for creating new mapped object and creating first record in table also there is commented out code at the end for deleting the record.
from sqlalchemy import Column, DateTime, Integer, String, Table, MetaData
from sqlalchemy.orm import mapper
from sqlalchemy import create_engine, orm
metadata = MetaData()
product = Table('product', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(1024), nullable=False, unique=True),
)
class Product(object):
def __init__(self, id, name):
self.id = id
self.name = name
def __repr__(self):
return "%s(%r,%r)" % (self.__class__.name,self.id,self.name)
mapper(Product, product)
db = create_engine('sqlite:////temp/test123.db')
metadata.create_all(db)
sm = orm.sessionmaker(bind=db, autoflush=True, autocommit=True, expire_on_commit=True)
session = orm.scoped_session(sm)
#create new Product record:
if session.query(Product).filter(Product.id==1).count()==0:
new_prod = Product("1","Product1")
print "Creating new product: %r" % new_prod
session.add(new_prod)
session.flush()
else:
print "product with id 1 already exists: %r" % session.query(Product).filter(Product.id==1).one()
print "loading Product with id=1"
prod = session.query(Product).filter(Product.id==1).one()
print "current name: %s" % prod.name
prod.name = "new name"
print prod
prod.name = 'test'
session.add(prod)
session.flush()
print prod
#session.delete(prod)
#session.flush()
PS SQLAlchemy also provides SQL Expression API that allows to work with table records directly without creating mapped objects. In my practice we are using Object-Relation API in most of the applications, sometimes we use SQL Expressions API when we need to perform low level db operations efficiently such as inserting or updating thousands of records with one query.
Direct links to SQLAlchemy documentation:
Object Relational Tutorial
SQL Expression Language Tutorial

Delete children after parent is deleted in SQLAlchemy

My problem is the following:
I have the two models Entry and Tag linked by a many-to-many relationship in SQLAlchemy. Now I want to delete every Tag that doesn't have any corresponding Entry after an Entry is deleted.
Example to illustrate what I want:
Entry 1 with tags python, java
Entry 2 with tags python, c++
With these two entries the database contains the tags python, java, and c++. If I now delete Entry 2 I want SQLAlchemy to automatically delete the c++ tag from the database. Is it possible to define this behavior in the Entry model itself or is there an even more elegant way?
Thanks.
this question was asked awhile back here: Setting delete-orphan on SQLAlchemy relationship causes AssertionError: This AttributeImpl is not configured to track parents
This is the "many-to-many orphan" problem. jadkik94 is close in that you should use events to catch this, but I try to recommend against using the Session inside of mapper events, though it works in this case.
Below, I take the answer verbatim from the other SO question, and replace the word "Role" with "Entry":
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import event
from sqlalchemy.orm import attributes
Base= declarative_base()
tagging = Table('tagging',Base.metadata,
Column('tag_id', Integer, ForeignKey('tag.id', ondelete='cascade'), primary_key=True),
Column('entry_id', Integer, ForeignKey('entry.id', ondelete='cascade'), primary_key=True)
)
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True)
name = Column(String(100), unique=True, nullable=False)
def __init__(self, name=None):
self.name = name
class Entry(Base):
__tablename__ = 'entry'
id = Column(Integer, primary_key=True)
tag_names = association_proxy('tags', 'name')
tags = relationship('Tag',
secondary=tagging,
backref='entries')
#event.listens_for(Session, 'after_flush')
def delete_tag_orphans(session, ctx):
# optional: look through Session state to see if we want
# to emit a DELETE for orphan Tags
flag = False
for instance in session.dirty:
if isinstance(instance, Entry) and \
attributes.get_history(instance, 'tags').deleted:
flag = True
break
for instance in session.deleted:
if isinstance(instance, Entry):
flag = True
break
# emit a DELETE for all orphan Tags. This is safe to emit
# regardless of "flag", if a less verbose approach is
# desired.
if flag:
session.query(Tag).\
filter(~Tag.entries.any()).\
delete(synchronize_session=False)
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
r1 = Entry()
r2 = Entry()
r3 = Entry()
t1, t2, t3, t4 = Tag("t1"), Tag("t2"), Tag("t3"), Tag("t4")
r1.tags.extend([t1, t2])
r2.tags.extend([t2, t3])
r3.tags.extend([t4])
s.add_all([r1, r2, r3])
assert s.query(Tag).count() == 4
r2.tags.remove(t2)
assert s.query(Tag).count() == 4
r1.tags.remove(t2)
assert s.query(Tag).count() == 3
r1.tags.remove(t1)
assert s.query(Tag).count() == 2
two almost identical SO questions qualifies this as something to have on hand so I've added it to the wiki at http://www.sqlalchemy.org/trac/wiki/UsageRecipes/ManyToManyOrphan.
I will let code speak for me:
from sqlalchemy import create_engine, exc, event
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import func, Table, Column, Integer, String, Float, Boolean, MetaData, ForeignKey
from sqlalchemy.orm import relationship, backref
# Connection
engine = create_engine('sqlite:///', echo=True)
Base = declarative_base(bind=engine)
Session = sessionmaker(bind=engine)
# Models
entry_tag_link = Table('entry_tag', Base.metadata,
Column('entry_id', Integer, ForeignKey('entries.id')),
Column('tag_id', Integer, ForeignKey('tags.id'))
)
class Entry(Base):
__tablename__ = 'entries'
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False, default='')
tags = relationship("Tag", secondary=entry_tag_link, backref="entries")
def __repr__(self):
return '<Entry %s>' % (self.name,)
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
def __repr__(self):
return '<Tag %s>' % (self.name,)
# Delete listener
def delete_listener(mapper, connection, target):
print "---- DELETING %s ----" % (target,)
print '-' * 20
for t in target.tags:
if len(t.entries) == 0:
print ' ' * 5, t, 'is to be deleted'
session.delete(t)
print '-' * 20
event.listen(Entry, 'before_delete', delete_listener)
# Utility functions
def dump(session):
entries = session.query(Entry).all()
tags = session.query(Tag).all()
print '*' * 20
print 'Entries', entries
print 'Tags', tags
print '*' * 20
Base.metadata.create_all()
session = Session()
t1, t2, t3 = Tag(name='python'), Tag(name='java'), Tag(name='c++')
e1, e2 = Entry(name='Entry 1', tags=[t1, t2]), Entry(name='Entry 2', tags=[t1, t3])
session.add_all([e1,e2])
session.commit()
dump(session)
raw_input("---- Press return to delete the second entry and see the result ----")
session.delete(e2)
session.commit()
dump(session)
This code above uses the after_delete event of the SQLAlchemy ORM events. This line does the magic:
event.listen(Entry, 'before_delete', delete_listener)
This says to listen to all deletes to an Entry item, and call our listener which will do what we want. However, the docs do not recommend changing the session inside the events (see the warning in the link I added). But as far as I can see, it works, so it's up to you to see if this works for you.

How to join the same table in sqlalchemy

I'm trying to join the same table in sqlalchemy. This is a minimial version of what I tried:
#!/usr/bin/env python
import sqlalchemy as sa
from sqlalchemy import create_engine
from sqlalchemy.orm import mapper, sessionmaker, aliased
engine = create_engine('sqlite:///:memory:', echo=True)
metadata = sa.MetaData()
device_table = sa.Table("device", metadata,
sa.Column("device_id", sa.Integer, primary_key=True),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("parent_device_id", sa.Integer, sa.ForeignKey('device.device_id')),
)
class Device(object):
device_id = None
def __init__(self, name, parent_device_id=None):
self.name = name
self.parent_device_id = parent_device_id
def __repr__(self):
return "<Device(%s, '%s', %s)>" % (self.device_id,
self.name,
self.parent_device_id )
mapper(Device, device_table)
metadata.create_all(engine)
db_session = sessionmaker(bind=engine)()
parent = Device('parent')
db_session.add(parent)
db_session.commit()
child = Device('child', parent.device_id)
db_session.add(child)
db_session.commit()
ParentDevice = aliased(Device, name='parent_device')
q = db_session.query(Device, ParentDevice)\
.outerjoin(ParentDevice,
Device.parent_device_id==ParentDevice.device_id)
print list(q)
This gives me this error:
ArgumentError: Can't determine join between 'device' and 'parent_device'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
But I am specifying a onclause for the join. How should I be doing this?
For query.[outer]join, you specify as list of joins (which is different to expression.[outer]join.) So I needed to put the 2 elements of the join, the table and the onclause in a tuple, like this:
q = db_session.query(Device, ParentDevice)\
.outerjoin(
(ParentDevice, Device.parent_device_id==ParentDevice.device_id)
)
Your mapper should specificy the connection between the two items, here's an example: adjacency list relationships.

Categories