Treat relation as a list of strings - sqlalchemy

class User:
id = Column(String(), primary_key=True)
favorite_names = relationship("FavoriteName")
class FavoriteName:
user_id = Column(
String(),
ForeignKey("user.id", onupdate="CASCADE", ondelete="CASCADE"),
primary_key=True
)
name = Column(String(), primary_key=True)
In an example like above, is it possible to treat User.favorite_names as a list of str values (representing the FavoriteName.name property) rather than a list of FavoriteName instances?
I'm hoping I would be able to do something like:
myUser = session.query(User).one()
myUser.favorite_names.append("Samantha")
session.commit()

As #SuperShoot mentioned in a comment on my question, I'm describing an association_proxy.

Related

Generated column (row_number) in Flask SQL-Alchemy

I'm trying to have a additional property on my object-relationship-mapped object.
I need to have a property that is calculated at the database using the ROW_NUMBER() function.
My Class looks something like this:
class Example(db.Model):
id = db.Column(db.Integer, primary_key=True)
group = db.Column(db.Integer)
I think I need to use the hybrid_property for this. I don't know to define the property but I think I have an idea about the expression:
class Example(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer)
#hybrid_property
def group_specific_id(self):
# no idea what to put here
pass
#group_specific_id.expression
def project_specific_id(cls):
return db.session.query(func.row_number().over(partition_by=cls.group_id))
Am I on the right path? Can anyone help me here?
This definition should work for you:
class Example(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer)
#hybrid_property
def group_specific_id(self):
return db.session.query(func.row_number().over(partition_by=self.group_id))
#group_specific_id.expression
def group_specific_id(cls):
return db.session.query(func.row_number().over(partition_by=cls.group_id))
And now you can use hybrid property like that:
example = Example(group_id=2)
example.group_specific_id
or like an expression
examples = session.query(Example).filter(Example.group_specific_id == <smth>)

Specifying a key for SQLAlchemy's `EncryptedType` at runtime

The SQLAlchemy-Utils documentation for the EncryptedType column type has an example that looks something like this:
secret_key = 'secretkey1234'
# setup
engine = create_engine('sqlite:///:memory:')
connection = engine.connect()
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = sa.Column(sa.Integer, primary_key=True)
username = sa.Column(EncryptedType(sa.Unicode,
secret_key,
AesEngine,
'pkcs5'))
But what if I don't know what the secret key is before I define the User class? For example, what if I want to prompt the user to enter the secret key?
This is the last example in the docs that you linked to:
The key parameter accepts a callable to allow for the key to change
per-row instead of being fixed for the whole table.
def get_key():
return 'dynamic-key'
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True)
username = sa.Column(EncryptedType(
sa.Unicode, get_key))

List of VLAN IDs on ports and circuits, how to model relationships?

I am trying to save a list of VLAN IDs per network port and also per network circuit. The list itself is something like this:
class ListOfVlanIds(Base):
__tablename__ = 'listofvlanids'
id = Column(Integer, primary_key=True)
listofvlanids_name = Column('listofvlanids_name', String, nullable = True)
And I then have a Port
class Port(Base):
__tablename__ = 'ports'
id = Column(Integer, primary_key=True)
listofvlanids_id = Column('listofvlanids_id', ForeignKey('ListOfVlanIds.id'), nullable = True)
and a Circuit:
class Circuit(Base):
__tablename__ = 'circuits'
id = Column(Integer, primary_key=True)
listofvlanids_id = Column('listofvlanids_id', ForeignKey('ListOfVlanIds.id'), nullable = True)
Running code like this results (for me) in a sqlalchemy.exc.NoReferencedTableError error on the ForeignKey.
Looking for the error I read I should add a relationship back from the list. I haven't found a way (or an example) where I can build this from both Port and Circuit. What am I missing?
Creating a list table for Ports and Circuits just moves the problem downstream, since a VLAN ID is it's own table... I'd love to be able to use ORM, instead of having to write (a lot of) SQL by hand.
ForeignKey expects a table and column name, not model and attribute name, so it should be ForeignKey('listofvlanids.id').

How to build backref with both associatition object and secondaryjoin?

I need some models for instance following:
Work - e.g. works of literature.
Worker - e.g. composer, translator or something similar has contribution to work.
Thus, a 'type' field is required to distinguish workers by division of work. As SQLAlchemy's documentation, this case can benifit from association object like following:
class Work(base):
id = Column(Integer, primary_key=True)
name = Column(String(50))
description = Column(Text)
class Worker(base):
id = Column(Integer, primary_key=True)
name = Column(String(50))
description = Column(Text)
class Assignment(base):
work_id = Column(Integer, Foreignkey('work.id'), primary_key=True)
worker_id = Column(Integer, Foreignkey('worker.id'), primary_key=True)
type = Column(SmallInteger, nullable=True)
Nonetheless, how to take advantage of backref and alternatvie join condition for building relation immediately to implement that each Work object can retrieve and modify corresponding Worker(s) via different attributions for distinction. For example:
work = session.query(Work).get(1)
work.name
>>> 'A Dream of The Red Mansions'
work.composers
>>> [<Worker('Xueqin Cao')>]
work.translators
>>> [<Worker('Xianyi Yang')>, <Worker('Naidie Dai')>]
Vice versa:
worker = session.query(Worker).get(1)
worker.name
>>> 'Xueqin Cao'
worker.composed
>>> [<Work('A Dream of The Red Mansions')>]
worker.translated
>>> []
Adding secondaryjoin directly without secondary specified seems not feasible, besides, SQLAlchemy's docs notes that:
When using the association object pattern, it is advisable that the association-mapped table not be used as the secondary argument on a relationship() elsewhere, unless that relationship() contains the option viewonly=True. SQLAlchemy otherwise may attempt to emit redundant INSERT and DELETE statements on the same table, if similar state is detected on the related attribute as well as the associated object.
Then, is there some way to build these relations elegantly and readily ?
There's three general ways to go here.
One is, do a "vanilla" setup where you have "work"/"workers" set up without distinguishing on "type" - then, use relationship() for "composer", "composed", "translator", "translated" by using "secondary" to Assignment.__table__ along with custom join conditions, as well as viewonly=True. So you'd do writes via the vanilla properties only. A disadvantage here is that there's no immediate synchronization between the "vanilla" and "specific" collections.
Another is, same with the "vanilla" setup, but just use plain Python descriptors to give "composer", "composed", "translator", "translated" views in memory, that is, [obj.worker for obj in self.workers if obj.type == 'composer']. This is the simplest way to go. Whatever you put in the "vanilla" collections shows right up in the "filtered" collection, the SQL is simple, and there's fewer SELECT statements in play (one per Worker/Work instead of N per Worker/Work).
Finally, the approach that's closest to what you're asking, with primary joins and backrefs, but note with the association object, the backrefs are between Work/Assignment and Assignment/Worker, but not between Work/Worker directly. This approach probably winds up using more SQL to get at the results but is the most complete, and also has the nifty feature that the "type" is written automatically. We're also using a "one way backref", as Assignment doesn't have a simple way of relating back outwards (there's ways to do it but it would be tedious). Using a Python function to automate creation of the relationships reduces the boilerplate, and note here I'm using a string for "type", this can be an integer if you add more arguments to the system:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
Base = declarative_base()
def _work_assignment(name):
assign_ = relationship("Assignment",
primaryjoin="and_(Assignment.work_id==Work.id, "
"Assignment.type=='%s')" % name,
back_populates="work", cascade="all, delete-orphan")
assoc = association_proxy("%s_assign" % name, "worker",
creator=lambda worker: Assignment(worker=worker, type=name))
return assoc, assign_
def _worker_assignment(name):
assign_ = relationship("Assignment",
primaryjoin="and_(Assignment.worker_id==Worker.id, "
"Assignment.type=='%s')" % name,
back_populates="worker", cascade="all, delete-orphan")
assoc = association_proxy("%s_assign" % name, "work",
creator=lambda work: Assignment(work=work, type=name))
return assoc, assign_
class Work(Base):
__tablename__ = 'work'
id = Column(Integer, primary_key=True)
name = Column(String(50))
description = Column(Text)
composers, composer_assign = _work_assignment("composer")
translators, translator_assign = _work_assignment("translator")
class Worker(Base):
__tablename__ = 'worker'
id = Column(Integer, primary_key=True)
name = Column(String(50))
description = Column(Text)
composed, composer_assign = _worker_assignment("composer")
translated, translator_assign = _worker_assignment("translator")
class Assignment(Base):
__tablename__ = 'assignment'
work_id = Column(Integer, ForeignKey('work.id'), primary_key=True)
worker_id = Column(Integer, ForeignKey('worker.id'), primary_key=True)
type = Column(String, nullable=False)
worker = relationship("Worker")
work = relationship("Work")
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
session = Session(e)
ww1, ww2, ww3 = Worker(name='Xueqin Cao'), Worker(name='Xianyi Yang'), Worker(name='Naidie Dai')
w1 = Work(name='A Dream of The Red Mansions')
w1.composers.append(ww1)
w1.translators.extend([ww2, ww3])
session.add(w1)
session.commit()
work = session.query(Work).get(1)
assert work.name == 'A Dream of The Red Mansions'
assert work.composers == [ww1]
assert work.translators == [ww2, ww3]
worker = session.query(Worker).get(ww1.id)
assert worker.name == 'Xueqin Cao'
assert worker.composed == [work]
assert worker.translated == []
worker.composed[:] = []
# either do this...
session.expire(work, ['composer_assign'])
# or this....basically need composer_assign to reload
# session.commit()
assert work.composers == []

In SQLAlchemy, is it possible to declare cascade relations while using declarative style?

In SQLAlchemy, we can declare tables and their relations like this:
user = Table(
'users', metadata,
Column('id', Integer, primary_key=True))
address = Table(
'adresses', metadata,
Column('id', Integer, primary_key=True),
Column('user_id', Integer, ForeignKey('user.id')))
class User(object): pass
class Address(object): pass
session.mapper(User, user, properties=dict(
'address' = relation(Address, backref='user', cascade="all")))
(Please notice the cascade relation in the line above.)
However, we can also use an alternate shorthand style, called declarative style, in which we can express the same things in fewer lines of code, omitting the mapper() relationships:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
class Adress(Base):
__tablename__ = 'adresses'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id')))
But if we use this declarative style, is there an alternative way to define cascade relationships?
Actually the use of "declarative" does not mean you omit the relationships. You still specify them, roughly the same way, but directly on the class as attributes rather than in the mapper:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
address = relation('Address', backref='user', cascade='all')
class Address(Base):
__tablename__ = 'addresses'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
(Note that I've fixed the spelling of "address", in case you cut-and-paste this.)
The declarative documentation covers this. Note that in the simple cases, it figures out what foreign keys to use automatically, but you can be more explicit if required.
Also note the use of the string 'Address' since at the time I use it in the User class the related class has not yet been defined.