Python delete attribute method is deleting the value not the attribute - python

In my flask admin app, I have a sqlalchemy orm model, old_model. I want to delete an attribute, action_id from the object. I am doing
del old_model.action_id
I expect the attribute action_id to be removed from the object old_model. Instead of getting deleted, its value is updated to None and the attribute still exists. How to delete the attribute completely along with the value?

So this was expected, as SQLAlchemy ORM mapped objects don't support this particular state for an attribute, that is, attribute doesn't exist and would raise AttributeError. for an ORM mapped class, a mapped attribute always defaults to None and/or empty collection. there's a little bit of an introduction to this here: https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html#instances-of-classes-represent-rows
For this particular problem, you can define your column as
id = Column('id', String, FetchedValue())
FetchedValue is used when the database is configured to provide some automatic default for a column. So, in this case you only want to ignore the column this would work like a charm. Updated model would look something like:
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
pid = Column('id', Integer, primary_key=True)
id = Column('id', String, FetchedValue())

Related

Delete attribute from SQLAlchemy model object

I'm trying to delete an attribute from model object that is created on the go, but there seems to be some problem doing so. So, far I've searched through every place including SQLAlchemy documentation and some of its code to find a potential fix but unable to find one.
Below code works fine on python classes but not with a class inherited from declarative_base
for row in data:
model_obj = DBEngine.models.User()
[setattr(model_obj, key, value) for key, value in row.items()]
# below line doesn't work as expected, instead of deleting the
# attribute it just sets the value of attribute to None
delattr(model_obj, 'localedit')
session.add(model_obj)
User model
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column('id', Integer, primary_key=True)
localedit = Column('localedit', String, default="0000-00-00 00:00:00")
I've also tried deleting the attribute using del but it's doing the same job as delattr, I guess both del and delattr calling the same code under the hood.
I'm totally stumped on this problem and couldn't come up with anything.
Any help would be appreciated, Thanks.
So this was expected, as SQLAlchemy ORM mapped objects don't support this particular state for an attribute, that is, attribute doesn't exist and would raise AttributeError. for an ORM mapped class, a mapped attribute always defaults to None and/or empty collection. there's a little bit of an introduction to this here: https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html#instances-of-classes-represent-rows
For this particular problem, you can define your column as
localedit = Column('localedit', String, FetchedValue())
FetchedValue is used when the database is configured to provide some automatic default for a column. So, in this case you only want to ignore the column this would work like a charm. Updated model would look something like:
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column('id', Integer, primary_key=True)
localedit = Column('localedit', String, FetchedValue())

Why I need both relationship and foreign key for Many to One relationship?

In the docs for SQLAlchemy for Many to One relationships it shows the following example:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
child_id = Column(Integer, ForeignKey('child.id'))
child = relationship("Child")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
Many parents for a single child. Then, when if we create a Parent, we need to populate child_id and child, which seems kind of redundant? Is this mandatory, or what's the purpose of each thing?
child = Child()
Parent(child_id=child, child=child)
Also, in Flask-SQLAlchemy, there is this example for a simple relationship in which it creates a post like this:
Post(title='Hello Python!', body='Python is pretty cool', category=py)
without providing a category_id. If I replicate that scenario, category_id value is None.
For the purpose of creating new objects like Parent(child=child), would it be enough to add foreign_keys=[child_id] or does it have further implications?
It is not mandatory; you do not need to populate both. Setting the foreign key to the related instance can be an error waiting to manifest itself. The only thing you need to do is
child = Child()
parent = Parent(child=child)
After this parent.child_id is None, but they represent the object part of ORM just fine. parent.child is a reference to the created child. They have not been persisted to the database and have no identity, other than their Python object ID. Only when you add them to a Session and flush the changes to the database do they receive an identity, due to them using generated surrogate keys. Here is where the mapping from the object world to the relational world happens. SQLAlchemy automatically fills in parent.child_id, so that their relationship is recorded in the database as well (note that this is not what "relational" in relational model means).
Returning to the example, adding some printing helps keep track of what happens and when:
child = Child()
parent = Parent(child=child)
print(parent.child_id) # None
session.add(parent)
session.flush() # Send changes held in session to DB
print(parent.child_id) # The ID assigned to child
You can also reverse the situation: you might have the ID of an existing Child, but not the actual object. In that case you can simply assign child_id yourself.
So, to answer the title: you do not need the ORM relationship in order to have a DB foreign key relationship, but you can use it to map the DB relationship to the object world.

How do you specify the foreign key column value in SQLAlchemy?

One of my models has the following relationship:
class User(Base):
account = relationship("Account")
I would like to set the account id manually.
My first attempt was this:
class User(Base):
account = relationship("Account")
accounts_id = Column(Integer, ForeignKey("accounts.id"), nullable=True)
#classmethod
def from_json(cls, json):
appointment = Appointment()
appointment.account_id = json["account_id"]
return appointment
The above dosen't work. We can't refer to this column because SQLAlchemy throws a fit. This is the exception:
sqlalchemy.exc.InvalidRequestError: Implicitly combining column users.accounts_id with column users.accounts_id under attribute 'accounts_id'. Please configure one or more attributes for these same-named columns explicitly.
I've tried hunting through the docs and expermiented with getting to the attribute numerous ways but I haven't been able to find, much less set it.
print(self.account.account_id)
print(self.account.relationhip)
print(self.account.properties)
print(self.account.primaryjoin)
Any ideas?
[Edit- added exception above]
Use the Account class to define the relationship, and add the backref keyword argument:
from sqlalchemy.orm import relationship
class User(Base):
accounts_id = Column(Integer, ForeignKey('account.id'))
class Account(Base):
users = relationship('User', backref='account')
When the backref keyword is used on a single relationship, it’s exactly the same as if the above two relationships were created individually using back_populates on each.
References
Linking Relationships with Backref
Controlling Cascade on Backrefs
SQLAlchemy ORM Examples

SqlAlchemy table inheritance and primary keys

I have an inherited table in SqlAlchemy which complains about not having a primary key. Oddly, the parent table has a primary key. Here is the situation:
Parent(Base)
__tablename__= 'parents'
id = Column(INT, primary_key=True, autoincrement=True)
Child(Parent)
__tablename__= 'children'
birthday = Column(TIMESTAMP)
parentId = Column(INT, ForeignKey('parents.uid', onupdate="CASCADE", ondelete="CASCADE"))
parent = relationship("User", backref=backref('CommandsQueued'))
Note that the parent table has a primary key, and that the child table is inheriting that. Despite this setup, I'm getting the following error:
SAWarning: Could not assemble any primary keys for locally mapped table 'children' - no rows will be persisted in this Table.
self._configure_pks()
I don't understand why SA doesn't recognize that the table does have a primary key. Does anyone know what is going on here? Am I misunderstanding the inheritance behaviour of SA?
I guess it's just a minimal example, it doesn't make much sense for Child to be a subclass of Parent. I'd expect both to be subclasses of Person, or something like that. In that case, you might want to take a look at polymorphic identity too.
Anyway, SQLAlchemy inheritance doesn't work like that. In the way you declared, It expects that your Child class and table declares its own primary key because it's a separate table, but if you try to do that with the same attribute name, you'll get a conflict. Try declaring the base id column using sqlalchemy.orm.column_property and it should do what you expect.
So, do something like this on the Child:
id = sqlalchemy.orm.column_property(Column(INT, primary_key=True), Parent.id)
And it should work as you expect.

Database Relationship - Syntax

A question on the syntax involved in SQLAlchemy.
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
child_id = Column(Integer, ForeignKey('child.id'))
child = relationship("Child")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
Why is it ForeignKey('child.id') and not ForeignKey("Child.id")?
Why is it relationship("Child") and not relationship("child")? Is there something fundamental about how databases and SQLAlchemy work that I don't understand which is why I have to ask this question? Thanks!
relationship(Child) is also valid. By capitalising inside string, sqlalchemy will look for respective model.
Relationship isn't sql standard so SQLAlchemy is using its own convention, whereas ForeignKey is SQL Standard so tablename.column is used.
In general: A relationship is defined on orm level while ForeignKey represents a database model. Now, it well might be the case that sqlalchemy is smart enough to figure from from the other, but if you keep this separation in mind, you are safe.
Specifically to your question: just read the documentation. Extract below (verbatim)
From relationship:
argument – a mapped class, or actual Mapper instance, representing the
target of the relationship.
argument may also be passed as a callable function which is evaluated
at mapper initialization time, and may be passed as a Python-evaluable
string when using Declarative.
From ForeignKey
column – A single target column for the key relationship. A Column
object or a column name as a string: tablename.columnkey or
schema.tablename.columnkey. columnkey is the key which has been
assigned to the column (defaults to the column name itself), unless
link_to_name is True in which case the rendered name of the column is
used.

Categories

Resources