create_or_get entry in a table - python

I have two related classes as below:
class IP(Base):
__tablename__ = 'ip'
id = Column(Integer, primary_key=True)
value = Column(String, unique=True)
topics = relationship('Topic')
class Topic(Base):
__tablename__ = 'topic'
id = Column(Integer, primary_key=True)
value = Column(String)
ip_id = Column(Integer, ForeignKey('ip.id'))
ip = relationship('IP')
if __name__ == '__main__':
Base.metadata.create_all(engine)
topics = [
Topic(value='t1', ip=IP(value='239.255.48.1')),
Topic(value='t2', ip=IP(value='239.255.48.1')),
Topic(value='t3', ip=IP(value='239.255.48.1'))
]
session.add_all(topics)
The above doesnt work as it tries to add different ip entries with same value. Is it possible to create or get the existing one so that I can use like below?
topics = [
Topic(value='t1', ip=create_or_get(value='239.255.48.1')),
Topic(value='t2', ip=create_or_get(value='239.255.48.1')),
Topic(value='t3', ip=create_or_get(value='239.255.48.1'))
]

Sure, just create the function:
def create_or_get(value):
obj = session.query(IP).filter(IP.value==value).first()
if not obj:
obj = IP(value=value)
session.add(obj)
return obj
Of course, it needs a session, but if you use scoped_session factory, it is straightforward. Alternatively, you might look into events, but it gets too complicated for the problem to solve.

Related

How to store data via relationship in constructor of SQLAlchemy model?

How to add objects in the constructor with relationship? The id is not yet ready when constructor is evaluated. In simpler cases it is possible to just provide a list, calculated beforehand. In the example below I tried to say there is a complex_cls_method, in a way it is more like black box.
from sqlalchemy import create_engine, MetaData, Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker
DB_URL = "mysql://user:password#localhost/exampledb?charset=utf8"
engine = create_engine(DB_URL, encoding='utf-8', convert_unicode=True, pool_recycle=3600, pool_size=10)
session = sessionmaker(autocommit=False, autoflush=False, bind=engine)()
Model = declarative_base()
class User(Model):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
simple = Column(String(255))
main_address = Column(String(255))
addresses = relationship("Address",
cascade="all, delete-orphan")
def __init__(self, addresses, simple):
self.simple = simple
self.main_address = addresses[0]
return # because the following does not work
self.addresses = Address.complex_cls_method(
user_id_=self.id, # <-- this does not work of course
key_="address",
value_=addresses
)
class Address(Model):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
keyword = Column(String(255))
value = Column(String(255))
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
parent_id = Column(Integer, ForeignKey('address.id'), nullable=True)
#classmethod
def complex_cls_method(cls, user_id_, key_, value_):
main = Address(keyword=key_, value="", user_id=user_id_, parent_id=None)
session.add_all([main])
session.flush()
addrs = [Address(keyword=key_, value=item, user_id=user_id_, parent_id=main.id) for item in value_]
session.add_all(addrs)
return [main] + addrs
if __name__ == "__main__":
# Model.metadata.create_all(engine)
user = User([u"address1", u"address2"], "simple")
session.add(user)
session.flush()
# as it can't be done in constructor, these additional statements needed
user.addresses = Address.complex_cls_method(
user_id_=user.id,
key_="address",
value_=[u"address1", u"address2"]
)
session.commit()
The question is, is there syntactically elegant (and technically sound) way to do this with User's constructor, or is it safer to just call a separate method of User class after session.flush to add desired objects to relationships (as in the example code)?
Giving up on constructor altogether is still possible, but less desirable option as resulting signature change would require significant refactorings.
Instead of manually flushing and setting ids etc. you could let SQLAlchemy handle persisting your object graph. You'll just need one more adjacency list relationship in Address and you're all set:
class User(Model):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
simple = Column(String(255))
main_address = Column(String(255))
addresses = relationship("Address",
cascade="all, delete-orphan")
def __init__(self, addresses, simple):
self.simple = simple
self.main_address = addresses[0]
self.addresses = Address.complex_cls_method(
key="address",
values=addresses
)
class Address(Model):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
keyword = Column(String(255))
value = Column(String(255))
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
parent_id = Column(Integer, ForeignKey('address.id'), nullable=True)
# For handling parent/child relationships in factory method
parent = relationship("Address", remote_side=[id])
#classmethod
def complex_cls_method(cls, key, values):
main = cls(keyword=key, value="")
addrs = [cls(keyword=key, value=item, parent=main) for item in values]
return [main] + addrs
if __name__ == "__main__":
user = User([u"address1", u"address2"], "simple")
session.add(user)
session.commit()
print(user.addresses)
Note the absence of manual flushes etc. SQLAlchemy automatically figures out the required order of insertions based on the object relationships, so that dependencies between rows can be honoured. This is a part of the Unit of Work pattern.

How to combine two model class in SQLAlchemy

I am new to ORM with SQLAlchemy and I only used to work with raw SQL. I have database tables, Label, Position, and DataSetlike following:
And the corresponding python classes following:
class Label(Base):
__tablename__ = 'Label'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=true)
class Position(Base):
__tablename__ = 'Position'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=true)
class DataSet(Base):
__tablename__ = 'DataSet'
id = Column(Integer, primary_key=True)
label_id = Column(Integer, ForeignKey('Label.id'))
position_id = Column(Integer, ForeignKey('Position.id'))
timestamp = Column(Integer, nullable=False)
But in my servie, I don't expose those label_id and position_id. So I made a new class Data to hold label and position as string.
# Not a full class to only show my concept
class Data:
# data dictionary will have data
def __init__(self, **kwargs):
# So it doesn't have ids. Label and Position as string
keys = {'label', 'position', 'timestamp'}
self.data = {k: kwargs[k] for k in keys}
# An example of inserting data.
# skipped detail and error handling to clarify
def insert(self):
session = Session()
# get id of label and position
# remember that it returns a tuple, not a single value
self.data['label_id'] = session.query(Label.id).\
filter(Label.name == self.data['label']).one_or_none()
self.data['position_id'] = session.query(Position.id).\
filter(Position.name == self.data['position']).one_or_none()
# add new dataset
self.data.pop('label')
self.data.pop('position')
new_data = DataSet(**self.data)
session.add(new_data)
session.commit()
But it looks somewhat ugly and I think there should be a simpler way to do it. Are there any way to combine these table classes using SQLAlchemy APIs?
You can use relationships and association proxies to make links from DataSet to Label and Position objects:
from sqlalchemy.orm import relationship
from sqlalchemy.ext.associationproxy import association_proxy
class DataSet(Base):
__tablename__ = 'DataSet'
id = Column(Integer, primary_key=True)
label_id = Column(Integer, ForeignKey('Label.id'))
label = relationship('Label')
label_name = association_proxy('label', 'name')
position_id = Column(Integer, ForeignKey('Position.id'))
position = relationship('Position')
position_name = association_proxy('position', 'name')
timestamp = Column(Integer, nullable=False)
After this you can access Label and Position objects linked to DataSet (and their names) through new attributes:
>>> d = session.query(DataSet).first()
>>> d.position
<Position object at 0x7f3021a9ed30>
>>> d.position_name
'position1'
Inserting DataSet objects is not so beautiful unfortunately. You can specify creator function for association_proxy which can get a name and create or retrieve a corresponding object (found in this answer):
def _label_creator(name):
session = Session()
label = session.query(Label).filter_by(name=name).first()
if not label:
label = Label(name=name)
session.add(label)
session.commit()
session.close()
return label
label_name = association_proxy('label', 'name', creator=_label_creator)
After specifying creator functions for both proxies you can create new DataSet objects this way:
dataset = DataSet(
label_name='label1',
position_name='position2',
timestamp=datetime.datetime.now()
)

SQLAlchemy Inserting Data in a Many-to-Many Relationship with Association Table

I've seen a few questions similar to this but none quite hit the nail on the head. Essentially I have three table models Center(), Business(), and CenterBusiness() in a Flask Application using SQLAlchemy. Currently I'm adding to said relationship in this manner:
biz = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
db.session.add(biz)
db.session.commit()
assoc = CenterBusiness(bizId=biz.id, cenId=session['center'])
db.session.add(assoc)
db.session.commit()
As you can see that's a bit ugly and I know there is a way to do it in one hit with the relationship as they are defined. I see on SQLAlchemy's docs they have a explanation of working with such a table but I can't seem to get it to work.
#Directly from SQLAlchemy Docs
p = Parent()
a = Association(extra_data="some data")
a.child = Child()
p.children.append(a)
#My Version Using my Tables
center = Center.query.get(session['center']
assoc = CenterBusiness()
assoc.business = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
center.businesses.append(assoc)
db.session.commit()
Unfortunately, that doesn't seem to be doing the trick... Any help would be greatly appreciated and below I've posted the models involved.
class Center(db.Model):
id = db.Column(MEDIUMINT(8, unsigned=True), primary_key=True,
autoincrement=False)
phone = db.Column(VARCHAR(10), nullable=False)
location = db.Column(VARCHAR(255), nullable=False)
businesses = db.relationship('CenterBusiness', lazy='dynamic')
employees = db.relationship('CenterEmployee', lazy='dynamic')
class Business(db.Model):
id = db.Column(MEDIUMINT(8, unsigned=True), primary_key=True,
autoincrement=True)
typId = db.Column(TINYINT(2, unsigned=True),
db.ForeignKey('biz_type.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
nullable=False)
type = db.relationship('BizType', backref='businesses',
lazy='subquery')
name = db.Column(VARCHAR(255), nullable=False)
contact = db.Column(VARCHAR(255), nullable=False)
phone = db.Column(VARCHAR(10), nullable=False)
documents = db.relationship('Document', backref='business',
lazy='dynamic')
class CenterBusiness(db.Model):
cenId = db.Column(MEDIUMINT(8, unsigned=True),
db.ForeignKey('center.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
primary_key=True)
bizId = db.Column(MEDIUMINT(8, unsigned=True),
db.ForeignKey('business.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
primary_key=True)
info = db.relationship('Business', backref='centers',
lazy='joined')
archived = db.Column(TINYINT(1, unsigned=True), nullable=False,
server_default='0')
I was able to get this working, my problem lied in the following bit of code (error in bold):
#My Version Using my Tables
center = Center.query.get(session['center']
assoc = CenterBusiness()
**assoc.info** = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
center.businesses.append(assoc)
db.session.commit()
As explained in my comment in the question:
Alright my issue was that I was not using the relationship key "info"
I have in my CenterBusiness model to define the appended association.
I was saying center.business thinking that the term business in that
case was arbitrary. However, I needed to actually reference that
relationship. As such, the appropriate key I had setup already in
CenterBusiness was info.
I will still accept any updates and/or better ways to handle this situation, though I think this is the best route at the time.
below example can help u
more details http://docs.sqlalchemy.org/en/latest/orm/extensions/associationproxy.html
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# association proxy of "user_keywords" collection
# to "keyword" attribute
keywords = association_proxy('user_keywords', 'keyword')
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String(50))
# bidirectional attribute/collection of "user"/"user_keywords"
user = relationship(User,
backref=backref("user_keywords",
cascade="all, delete-orphan")
)
# reference to the "Keyword" object
keyword = relationship("Keyword")
def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
self.special_key = special_key
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)

Select item having maximum from sqlalchemy relationship

Given this pair of classes:
class Thing(Base):
id = Column(Integer, primary_key=True)
class ThingInfo(Base):
id = Column(Integer, primary_key=True)
thing_id = Column(Integer, ForeignKey(Thing))
recorded_at = Column(DateTime)
thing = relationship(Thing, backref='all_info')
How can I define a property Thing.newest_info to achieve:
t = s.query(Thing).first()
newest_info = max(t.all_info, key=lambda i: i.recorded_at)
print newest_info
#equivalent to:
t = s.query(Thing).first()
print t.newest_info
I'd like to do this with a column_property or relationship, not a normal property. So far what I have is:
select([ThingInfo])
.group_by(ThingInfo.thing)
.having(func.max(ThingInfo.recorded_at))
But I can't figure out how to attach this as a propery of a single Thing object.
Add an order_by clause to the relationship and this becomes trivial:
class ThingInfo(Base):
id = Column(Integer, primary_key=True)
thing_id = Column(Integer, ForeignKey(Thing))
recorded_at = Column(DateTime)
thing = relationship(Thing, backref=backref('all_info', order_by='ThingInfo.recorded_at')
thing = session.query(Thing).get(id)
newest_info = thing.all_info[-1]
or alternatively backref=backref('all_info', order_by='desc(ThingInfo.recorded_at)') and newest_info=thing.all_info[0].
Ok, here's a working attempt:
t = aliased(ThingInfo)
ThingInfo.is_newest = column_property(
select([
ThingInfo.recorded_at == func.max(t.recorded_at)
])
.select_from(r)
.where(t.thing_id == ThingInfo.thing_id)
)
Thing.newest_info = relationship(
ThingInfo,
viewonly=True,
uselist=False,
primaryjoin=(ThingInfo.thing_id == Thing.id) & ThingInfo.is_newest
)
Things I dislike about this:
I'm having to specify how to join Things to ThingInfos in a second place
I'm trying to work out how to write this to use a groubby

Sqlalchemy - how to get an object, and filter which rows in the child class

I’m new to Sqlalchemy, and in need of some help.
i have a model, with a one to many relation:
class Metnadev(DeclarativeBase):
__tablename__ = 'metnadev'
#personal info
id = Column(Integer, autoincrement=True, primary_key=True)
first_name = Column(Unicode(255))
last_name = Column(Unicode(255))
birth_day = Column(Date)
activitys = relationship("Activity", backref="metnadev")
class Activity(DeclarativeBase):
__tablename__ = 'activity'
id = Column(Integer, autoincrement=True, primary_key=True)
metnadev_id = Column(Integer, ForeignKey('metnadev.id'))
location = Column(Unicode(255))
visible = Column(Boolean,default=True)
When I do
metnadev = DBSession.query(Metnadev).filter_by(id=kw['id']).one()
I get the object, with the child, great.
I want to get the object, but only get the rows from the child class, where visible == True
I searched but I’m not sure how to do it,
Thanks for the help
This section of the documentation has your answer: http://docs.sqlalchemy.org/en/rel_0_6/orm/relationships.html#building-query-enabled-properties
class Metnadev(DeclarativeBase):
#...
#property
def activities(self):
return object_session(self).query(Activity).filter_by(visible=True).with_parent(self).all()
There's a couple ways you can do this.
For a one-off, you can just run a second query:
from sqlalchemy import and_
activities = Activity.query.filter(and_(Activity.metnadev_id == kw['id'], Activity.visible==True)).all()
You can change the relationship so only visible items are returned:
activities = relationship("Activity",
primaryjoin="and_(Activity.metnadev_id==Metnadev.id, "
"Activity.visible==True)")
If you need more control you can join the tables, but it sounds like the relationship configuration would work for you. Let me know if that's not the case.
Hope that helps!

Categories

Resources