[sqlalchemy] Re: Hybrid property "can't set attribute"

2019-12-19 Thread YKdvd
Ah, thanks very much, that's fixed it.  I must have missed that in the docs 
- I think this started out as a Python property and they later added the 
hybrid decorator.

On Thursday, December 19, 2019 at 4:34:34 PM UTC-4, YKdvd wrote:
>
> We have a "Users" model with this, which was a hybrid property to wrap the 
> "email" column temporarily.  The database column (MySQL 5.7)  is "email", 
> but defined by ORM as "_email", with an "email" hybrid property to access 
> and set:
>
> _email = Column(u'email', String(255))
> ...
>
> @hybrid_property
> def email(self):
>  return self._email.replace("olddomain.com", "newdomain.com")
> @email.setter
> def email_setter(self, val):
>  self._email = val
>
>
> In 1.1.18, something like "self.email = someEmailAddress" works fine.  We're 
> testing an upgrade to 1.3.11, and that now throws an "AttributeError: can't 
> set attribute" from hybrid.py __set__(). 
>
> That seems to be at a simple check "if self.fset is None", so it's almost as 
> if the decorator never stored the setter function?  I'm digging into the 
> hybrid docs, and it seems a pretty innocuous setter, but there might be 
> something about it that
>
> 1.3 is being stricter about?  I don't see any SAwarnings at startup that 
> might apply.  I changed the getter to simply return self._email in case that 
> was a problem, but that didn't help.
>
>

-- 
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 view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/5b3e36b5-67d7-484b-aed9-2e4ae3ba94e6%40googlegroups.com.


Re: [sqlalchemy] Hybrid property "can't set attribute"

2019-12-19 Thread Mike Bayer
here's the migration note that includes this information:

https://docs.sqlalchemy.org/en/13/changelog/migration_12.html#hybrid-attributes-support-reuse-among-subclasses-redefinition-of-getter

On Thu, Dec 19, 2019, at 7:18 PM, Mike Bayer wrote:
> hiya -
> 
> going to say right off this is the naming issue, there's an unfortunately 
> stalled PR to make sure the documentation emphasizes this at 
> https://github.com/sqlalchemy/sqlalchemy/pull/4970 . basically the same note 
> as you see in 
> https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html#defining-expression-behavior-distinct-from-attribute-behavior.
>  basically you are assigning your setter function to a brand new hybrid 
> called "email_setter". name it "def email(self, val)" instead.
> 
> 
> On Thu, Dec 19, 2019, at 3:34 PM, YKdvd wrote:
>> We have a "Users" model with this, which was a hybrid property to wrap the 
>> "email" column temporarily. The database column (MySQL 5.7) is "email", but 
>> defined by ORM as "_email", with an "email" hybrid property to access and 
>> set:
>> 
>> _email = Column(*u'email'*, String(255))
>> ...
>> @hybrid_property
>> def email(self):
>> return self._email.replace("olddomain.com", "newdomain.com")
>> @email.setter
>> def email_setter(self, val):
>> self._email = val
>> 
>> In 1.1.18, something like "self.email = someEmailAddress" works fine.  We're 
>> testing an upgrade to 1.3.11, and that now throws an "AttributeError: can't 
>> set attribute" from hybrid.py __set__(). 
>> That seems to be at a simple check "if self.fset *is *None", so it's almost 
>> as if the decorator never stored the setter function?  I'm digging into the 
>> hybrid docs, and it seems a pretty innocuous setter, but there might be 
>> something about it that
>> 1.3 is being stricter about?  I don't see any SAwarnings at startup that 
>> might apply.  I changed the getter to simply return self._email in case that 
>> was a problem, but that didn't help.
>> 

>> --
>> 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 view this discussion on the web visit 
>> https://groups.google.com/d/msgid/sqlalchemy/eb31b100-a9e1-4ddc-9b76-d8a7651bb4dc%40googlegroups.com
>>  
>> .
> 
> 

> --
>  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 view this discussion on the web visit 
> https://groups.google.com/d/msgid/sqlalchemy/7ac22728-ef48-4b33-83d5-12efcb7b66a4%40www.fastmail.com
>  
> .

-- 
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 view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/d5cc7995-0d29-4ce4-8648-af8eb05f49ad%40www.fastmail.com.


Re: [sqlalchemy] Hybrid property "can't set attribute"

2019-12-19 Thread Mike Bayer
hiya -

going to say right off this is the naming issue, there's an unfortunately 
stalled PR to make sure the documentation emphasizes this at 
https://github.com/sqlalchemy/sqlalchemy/pull/4970 . basically the same note as 
you see in 
https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html#defining-expression-behavior-distinct-from-attribute-behavior.
 basically you are assigning your setter function to a brand new hybrid called 
"email_setter". name it "def email(self, val)" instead.


On Thu, Dec 19, 2019, at 3:34 PM, YKdvd wrote:
> We have a "Users" model with this, which was a hybrid property to wrap the 
> "email" column temporarily. The database column (MySQL 5.7) is "email", but 
> defined by ORM as "_email", with an "email" hybrid property to access and set:
> 
> _email = Column(*u'email'*, String(255))
> ...
> @hybrid_property
> def email(self):
> return self._email.replace("olddomain.com", "newdomain.com")
> @email.setter
> def email_setter(self, val):
> self._email = val
> 
> In 1.1.18, something like "self.email = someEmailAddress" works fine.  We're 
> testing an upgrade to 1.3.11, and that now throws an "AttributeError: can't 
> set attribute" from hybrid.py __set__(). 
> That seems to be at a simple check "if self.fset *is *None", so it's almost 
> as if the decorator never stored the setter function?  I'm digging into the 
> hybrid docs, and it seems a pretty innocuous setter, but there might be 
> something about it that
> 1.3 is being stricter about?  I don't see any SAwarnings at startup that 
> might apply.  I changed the getter to simply return self._email in case that 
> was a problem, but that didn't help.
> 

> --
>  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 view this discussion on the web visit 
> https://groups.google.com/d/msgid/sqlalchemy/eb31b100-a9e1-4ddc-9b76-d8a7651bb4dc%40googlegroups.com
>  
> .

-- 
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 view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/7ac22728-ef48-4b33-83d5-12efcb7b66a4%40www.fastmail.com.


[sqlalchemy] Hybrid property "can't set attribute"

2019-12-19 Thread YKdvd
We have a "Users" model with this, which was a hybrid property to wrap the 
"email" column temporarily.  The database column (MySQL 5.7)  is "email", 
but defined by ORM as "_email", with an "email" hybrid property to access 
and set:

_email = Column(u'email', String(255))
...

@hybrid_property
def email(self):
 return self._email.replace("olddomain.com", "newdomain.com")
@email.setter
def email_setter(self, val):
 self._email = val


In 1.1.18, something like "self.email = someEmailAddress" works fine.  We're 
testing an upgrade to 1.3.11, and that now throws an "AttributeError: can't set 
attribute" from hybrid.py __set__(). 

That seems to be at a simple check "if self.fset is None", so it's almost as if 
the decorator never stored the setter function?  I'm digging into the hybrid 
docs, and it seems a pretty innocuous setter, but there might be something 
about it that

1.3 is being stricter about?  I don't see any SAwarnings at startup that might 
apply.  I changed the getter to simply return self._email in case that was a 
problem, but that didn't help.

-- 
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 view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/eb31b100-a9e1-4ddc-9b76-d8a7651bb4dc%40googlegroups.com.


Re: [sqlalchemy] refresh_flush instance event not called for PK attributes

2019-12-19 Thread Mike Bayer


On Thu, Dec 19, 2019, at 8:51 AM, Chris Wilson wrote:
> Dear Mike and SQLAlchemy users,

> 

> I think I’ve discovered a confusing (and undocumented) limitation of the 
> refresh_flush event. It’s called when non-PK columns are populated after an 
> INSERT or UPDATE (e.g. from a server-side default), but not for PK values.

> 

> The  documentation 
> 
>  says:

> 

> “This event is the same as InstanceEvents.refresh() 
> 
>  except it is invoked within the unit of work flush process, and the values 
> here typically come from the process of handling an INSERT or UPDATE, such as 
> via the RETURNING clause or from Python-side default values.”

> 

> With Postgres and SQLite at least, the primary key (e.g. the ID column) of a 
> newly created object is returned with a RETURNING clause. But it doesn’t 
> trigger a refresh_flush event, because it’s skipped by this code in 
> _postfetch:

> 

> if returning_cols:

>  row = result.context.returned_defaults

> if row is not None:

> for col in returning_cols:

> # pk cols returned from insert are handled

> # distinctly, don't step on the values here

> if col.primary_key and result.context.isinsert:

> continue



the refresh_flush event is intended more towards getting access to columns that 
have server defaults or triggers on them, not including the primary key. It's 
mostly an artifact that this event applies to INSERT operations at all.

To get at the state of a newly inserted row you should use the 
pending_to_persistent event: 
https://docs.sqlalchemy.org/en/13/orm/events.html?highlight=refresh_flush#sqlalchemy.orm.events.SessionEvents.pending_to_persistent


> 

> I can see that the ID is assigned to the object’s state in the caller 
> (_emit_insert_statements):

>  
>     primary_key = result.context.inserted_primary_key
>  
>     if primary_key is not None:
>     # set primary key attributes
>     for pk, col in zip(
>     primary_key, mapper._pks_by_table[table]
>     ):
>     prop = mapper_rec._columntoproperty[col]
>     if state_dict.get(prop.key) is None:
>     state_dict[prop.key] = pk
>  
> But no event is called when this happens (AFAICS). The after_flush and 
> after_flush_postexec events are called soon after that.

> 

> It would be nice if at least the documentation made this clear, and even 
> better if we could use refresh_flush for all flush-context events, including 
> PK assignment. What do you think?


i will update the docs to state that refresh_flush is oriented towards UPDATE, 
not INSERT, and that primary key values are explicitly not part of this event.


> 

> If an example is useful, here is a trivial one. The receive_refresh_flush 
> handler is never called:

> 

> from sqlalchemy import Column, Integer, Text, create_engine, event
> from sqlalchemy.ext.declarative import declarative_base
> from sqlalchemy.orm import sessionmaker
>  
> Base = declarative_base()
>  
> class Dog(Base):
>     __tablename__ = 'dog'
>     id = Column(Integer, primary_key=True)
>     name = Column(Text)
>  
> engine = create_engine('sqlite://')
> # engine.echo = True
> Base.metadata.create_all(engine)
>  
> DBSession = sessionmaker(bind=engine)
>  
> session = DBSession(autocommit=True)
>  
> @event.listens_for(Dog, 'refresh_flush')
> def receive_refresh_flush(target, flush_context, attrs):
>     print("Dog was assigned an ID: {attrs}")
>  
> with session.begin() as trans:
>     session.add(Dog(name="fido"))
> 

> Thanks, Chris.

> 
> 
> 
> *This email is confidential. If you are not the intended recipient, please 
> advise us immediately and delete this message. The registered name of Cantab- 
> part of GAM Systematic is Cantab Capital Partners LLP. See - 
> http://www.gam.com/en/Legal/Email-disclosure-EU 
>  for further information on 
> confidentiality, the risks of non-secure electronic communication, and 
> certain disclosures which we are required to make in accordance with 
> applicable legislation and regulations. If you cannot access this link, 
> please notify us by reply message and we will send the contents to you.
> 
> GAM Holding AG and its subsidiaries (Cantab – GAM Systematic) will collect 
> and use information about you in the course of your interactions with us. 
> Full details about the data types we collect and what we use this for and 
> your related rights is set out in our online privacy policy at 
> https://www.gam.com/en/legal/privacy-policy. Please familiarise yourself with 
> this policy and check it from time to time for updates as it supplements this 
> notice ** * 
> 

> --
>  SQLAlchemy - 
>  The Python SQL Toolkit and 

[sqlalchemy] refresh_flush instance event not called for PK attributes

2019-12-19 Thread Chris Wilson
Dear Mike and SQLAlchemy users,

I think I've discovered a confusing (and undocumented) limitation of the 
refresh_flush event. It's called when non-PK columns are populated after an 
INSERT or UPDATE (e.g. from a server-side default), but not for PK values.

The 
documentation
 says:

"This event is the same as 
InstanceEvents.refresh()
 except it is invoked within the unit of work flush process, and the values 
here typically come from the process of handling an INSERT or UPDATE, such as 
via the RETURNING clause or from Python-side default values."

With Postgres and SQLite at least, the primary key (e.g. the ID column) of a 
newly created object is returned with a RETURNING clause. But it doesn't 
trigger a refresh_flush event, because it's skipped by this code in _postfetch:

if returning_cols:
row = result.context.returned_defaults
if row is not None:
for col in returning_cols:
# pk cols returned from insert are handled
# distinctly, don't step on the values here
if col.primary_key and result.context.isinsert:
   continue

I can see that the ID is assigned to the object's state in the caller 
(_emit_insert_statements):



primary_key = result.context.inserted_primary_key



if primary_key is not None:

# set primary key attributes

for pk, col in zip(

primary_key, mapper._pks_by_table[table]

):

prop = mapper_rec._columntoproperty[col]

if state_dict.get(prop.key) is None:

state_dict[prop.key] = pk


But no event is called when this happens (AFAICS). The after_flush and 
after_flush_postexec events are called soon after that.

It would be nice if at least the documentation made this clear, and even better 
if we could use refresh_flush for all flush-context events, including PK 
assignment. What do you think?

If an example is useful, here is a trivial one. The receive_refresh_flush 
handler is never called:


from sqlalchemy import Column, Integer, Text, create_engine, event

from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.orm import sessionmaker



Base = declarative_base()



class Dog(Base):

__tablename__ = 'dog'

id = Column(Integer, primary_key=True)

name = Column(Text)



engine = create_engine('sqlite://')

# engine.echo = True

Base.metadata.create_all(engine)



DBSession = sessionmaker(bind=engine)



session = DBSession(autocommit=True)



@event.listens_for(Dog, 'refresh_flush')

def receive_refresh_flush(target, flush_context, attrs):

print("Dog was assigned an ID: {attrs}")



with session.begin() as trans:

session.add(Dog(name="fido"))

Thanks, Chris.



This email is confidential. If you are not the intended recipient, please 
advise us immediately and delete this message. 
The registered name of Cantab- part of GAM Systematic is Cantab Capital 
Partners LLP. 
See - http://www.gam.com/en/Legal/Email+disclosures+EU for further information 
on confidentiality, the risks of non-secure electronic communication, and 
certain disclosures which we are required to make in accordance with applicable 
legislation and regulations. 
If you cannot access this link, please notify us by reply message and we will 
send the contents to you.

GAM Holding AG and its subsidiaries (Cantab – GAM Systematic) will collect and 
use information about you in the course of your interactions with us. 
Full details about the data types we collect and what we use this for and your 
related rights is set out in our online privacy policy at 
https://www.gam.com/en/legal/privacy-policy. 
Please familiarise yourself with this policy and check it from time to time for 
updates as it supplements this notice.

-- 
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 view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/BCCA73C2165E8947A2E786EC482564DE0167FDC820%40CCPMAILDAG03.cantab.local.