On Mon, Apr 29, 2019 at 7:22 AM Lyla Fischer <lylafi...@gmail.com> wrote:
>
> If I can comment on what seems instinctive to someone who just recently read 
> the documentation, and made assumptions based off of it: My understanding was 
> that the relationship abstractions were supposed to be about making foreign 
> keys easier to keep track of, and being able to go both ways on a 
> relationship in a object-oriented way. The fact that there were any 
> implications at all for persistence was surprising to me, and it took me a 
> significant amount of time to understand that SQL Alchemy was trying to do 
> some magic behind the scenes when it came to persistence. I support the idea 
> of making cascade_backrefs=False by default, because it keeps the separate 
> objects separate, it makes me less nervous about eventual performance (which 
> is a constant concern when there is magic I didn't intend happening in a 
> library), and it's basically just working the way that I expected things to 
> work initially. I'm fine with dealing with errors that might come about from 
> the implications of updating of objects independently. It seems like part of 
> the responsibilities of dealing with persistence, ever.

I'm trying to gather the rationale that you are referring towards in
your comment, however I'm not able to work with the reason "magic I
didn't intend", because that reason doesn't actually say anything.
Of course every issue is about telling a library what your intent is,
and the library fulfullling that intent or not.    But to refer to the
intents that you didn't expect as "magic" and the intents that you
*did* expect as "not magic", I guess the latter is implicit, only
illustrates how much the library is doing completely correctly such
that you aren't noticing it.

The thing that relationship() does is coordinating Python objects that
are linked together in terms of a foreign key relationship in a
database.    Then there is the backref concept, which expands on the
relationship concept, to more fully emulate the behavior of relational
database foreign keys with Python objects.   When working completely
normally, SQLAlchemy produces Python object behavior as follows:

    object_a = ObjectA()
    object_b = ObjectB()
    object_a.some_collection.append(object_b)
    assert object_b.parent is object_a

Someone working only with Python objects would probably call most of
the above interaction as "magic", that ObjectA has a collection
automatically instantiated on that (this incidentally is also going to
be scaled back in SQLAlchemy 2.0) and that when I append an object to
this collection, the object now has a ".parent" that refers back to
that original object.    The reason it has to do that in SQLAlchemy is
because we are emulating a relational database foreign key
relationship, where containment on one side implies association in the
other direction.

Where SQLAlchemy works hard to acknowledge that the above behaviors
are not normal for Python (that is, are reasonably perceived as
"magic"), is that the user explicitly configures attributes named
"some_collection" and "parent" on their classes, an they even have to
point them together at each other, and that the foreign key and
primary key columns that are involved in the persistence for the above
are also explicitly configured.       This is to reduce the amount of
assumptions and implicit decisions SQLAlchemy has to make thereby
reducing the possibility of surprise.

Now to the issue of casade_backrefs.   This flag is in a tough spot,
because either way, it leads to a situation that can be non-intutive
(that is, surprising).   Let's assume above we've turned it off, and
we do this:

    object_a = ObjectA()
    object_b = ObjectB()
    session.add(object_b)
    object_a.some_collection.append(object_b)
    assert object_b.parent is object_a

What happens above when we persist object_b by calling
session.commit()?    In fact the operation will fail, either silently
if the foreign key column is nullable, or explicitly if the column is
not nullable, because object_a will *not* be persisted and the foreign
key constraint on object_b cannot be satisfied.

The reason we get into this issue in the first place is that
SQLAlchemy also has an explicit concept of a Session, e.g. a database
transaction, where objects must be explicitly associated with that
transaction, and that this Session uses a unit of work pattern, which
is exactly the thing here that reduces the exposure to "persistence
implications" that you refer towards.   If we were working like other
ORMs, we'd just say "object_b.save()", where it likely would raise a
constraint error because we didn't call "object_a.save()" first, which
in SQLAlchemy's view is a significant persistence detail that is
needlessly exposed.      SQLAlchemy's very first version in fact
didn't have a Session, and all of object_a/ object_b would be
automatically associated with an implicit database connection, very
much the way active record .save() style ORMs work.     That was also
too magical.

So the "cascade_backrefs" flag is kind of a "seam" in an otherwise
smooth surface.  I would not characterize it as any more "magical"
than anything else, and instead the discussion should be about which
use case is less surprising.





>
> </my two cents>
>
> -Lyla
>
> On Sun, Apr 28, 2019 at 10:54 PM James Fennell <jamespfenn...@gmail.com> 
> wrote:
>>
>> Thanks for the explanation Mike! Seeing it now, I actually think there’s a 
>> decent reason to want the current backerefs:
>>
>> My understanding is that with session.merge in SQL Alchemy it’s possible to 
>> draw a very clean line between entities that are persisted (or about to be 
>> persisted on the next flush) and entities which will never be persisted. 
>> This is owing to the design choice whereby SQL Alchemy doesn’t persist the 
>> entity you pass into the merge; instead, that is kept alone and a new entity 
>> is created.
>>
>> With this in mind, there are two ways to see Lyla’s example.
>>
>> One way: as soon as the tassel_thread was related to the persisted my_head 
>> (persisted because of the line my_head=session.merge(my_head)) then 
>> tassel_thread should be seen as in the session already. In this view, the 
>> merge is necessary and possibly error-prone, as here.
>>
>> Another way: instead of assigning my_head=session.merge(my_head), keep the 
>> unpersisted head around with say persisted_head = session.merge(my_head). 
>> Then relating the new tassel_thread to my_head won’t add it to the session. 
>> To get a record into the DB, then do a session.merge on it - everything 
>> works correctly this way.
>>
>>
>> In both cases, there is the idea of a persisted object graph and a distinct 
>> unpersisted object graph. Once you relate a new entity to something in the 
>> persisted object graph, it becomes persistent.
>>
>> --
>> SQLAlchemy -
>> The Python SQL Toolkit and Object Relational Mapper
>>
>> http://www.sqlalchemy.org/
>>
>> To post example code, please provide an MCVE: Minimal, Complete, and 
>> Verifiable Example.  See  http://stackoverflow.com/help/mcve for a full 
>> description.
>> ---
>> You received this message because you are subscribed to a topic in the 
>> Google Groups "sqlalchemy" group.
>> To unsubscribe from this topic, visit 
>> https://groups.google.com/d/topic/sqlalchemy/oVVdbCzsNQg/unsubscribe.
>> To unsubscribe from this group and all its topics, send an email to 
>> sqlalchemy+unsubscr...@googlegroups.com.
>> To post to this group, send email to sqlalchemy@googlegroups.com.
>> Visit this group at https://groups.google.com/group/sqlalchemy.
>> For more options, visit https://groups.google.com/d/optout.
>
> --
> SQLAlchemy -
> The Python SQL Toolkit and Object Relational Mapper
>
> http://www.sqlalchemy.org/
>
> To post example code, please provide an MCVE: Minimal, Complete, and 
> Verifiable Example. See http://stackoverflow.com/help/mcve for a full 
> description.
> ---
> You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to sqlalchemy+unsubscr...@googlegroups.com.
> To post to this group, send email to sqlalchemy@googlegroups.com.
> Visit this group at https://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/d/optout.

-- 
SQLAlchemy - 
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to