On Wed, Dec 19, 2018 at 8:03 AM Chris Wilson
<[email protected]> wrote:
>
> Dear Michael and SQLAlchemy users,
>
>
>
> I have what may be a minor bug report, and a feature request.
>
>
>
> The possible bug is that creating an instance of a mapped association object 
> adds it to an attribute-mapped collection, but using the wrong key. In the 
> following example, although the key is a date which is initialised, the key 
> used is None instead.
>
>
>
> The feature request is for some way to use an association_proxy on such a 
> collection to return the instances of the collection (as though it was a 
> relationship to the target table), instead of a dictionary from keys to 
> target instances. I think it might be possible by supplying a proxy_factory 
> and a getset_factory, but I can’t find any examples, and it’s quite difficult 
> to set up in any case.
>
>
>
> Here is the sample code illustrating both:
>
>
>
> from datetime import date
>
>
>
> from sqlalchemy import Column, Date, ForeignKey, Integer, Text, create_engine
>
> from sqlalchemy.ext.associationproxy import association_proxy
>
> from sqlalchemy.ext.declarative import declarative_base
>
> from sqlalchemy.orm import relationship, sessionmaker
>
> from sqlalchemy.orm.collections import attribute_mapped_collection
>
>
>
> Base = declarative_base()
>
>
>
> class Pet(Base):
>
>     __tablename__ = 'pet'
>
>     id = Column(Integer, primary_key=True)
>
>     name = Column(Text)
>
>
>
> class HousePet(Base):
>
>     __tablename__ = 'house_pet'
>
>     id_pet = Column(Integer, ForeignKey(Pet.id), primary_key=True)
>
>     id_house = Column(Integer, ForeignKey("house.id"), primary_key=True)
>
>     date_moved_in = Column(Date)
>
>     pet = relationship(Pet)
>
>     # house backref created by House.date_to_new_pet, not here:
>
>     # house = relationship("House")
>
>
>
> class House(Base):
>
>     __tablename__ = 'house'
>
>     id = Column(Integer, primary_key=True)
>
>     address = Column(Text)
>
>
>
>     date_to_new_pet = relationship(HousePet, cascade='all, delete-orphan',
>
>                                    
> collection_class=attribute_mapped_collection("date_moved_in"),
>
>                                    backref=__tablename__)
>
>
>
>     pets = association_proxy('date_to_new_pet', 'pet')
>
>
>
> engine = create_engine('sqlite://')
>
> Base.metadata.create_all(engine)
>
>
>
> DBSession = sessionmaker(bind=engine)
>
> session = DBSession(autocommit=True)
>
>
>
> downing = House(address="10 Downing Street")
>
> larry = Pet(name="Larry the Cat")
>
> larry_at_home = HousePet(pet=larry, house=downing, date_moved_in=date(2007, 
> 1, 1))

it seems clear that the pet/house attributes are being populated
before date_moved_in which is why you have your None key. So you need
to implement either a real __init__() method for this class or use the
@event.listens_for("init") event hook to ensure that date_moved_in is
set first.  However, the larger pattern here that you want to have a
dictionary-mapped collection yet you want to create HousePet objects
manually is already a bit awkward.   Usually it's best to just pick a
single method of mutating your collection and stick with it so if you
want to create HousePet objects manually I'd consider moving to a
read-only dictionary view instead of collection_class=dict.

>
>
> # How can we make House.pets be a list-like collection instead of dict-like?
>
> try:
>
>     assert downing.pets == [larry], str(downing.pets)
>
> except AssertionError as exc:
>
>     print(exc)
>
>
>
> # It would be nice to be able to update this collection as well:
>
> downing.pets = []
>
> assert downing.date_to_new_pet == {}, str(downing.date_to_new_pet)
>
> downing.pets = [larry]
>
> assert downing.date_to_new_pet == {larry_at_home.date_moved_in: larry}, 
> str(downing.date_to_new_pet)
>
>
>
> I would like the latter to work because the documentation for association 
> objects warns about the dangers of unsynchronised collections, and suggests 
> using an association_proxy instead of a relationship with a secondary table, 
> which works fine for normal relationship collections, but is very unnatural 
> for attribute-mapped ones.

this is again where it seems you want multiple ways of mutating the
collection.   Pick just one and stick with it.   Why not get rid of
collection_class=dict altogether, use a plain list-based relationship,
and then for your dictionary view just use straight @property (e.g.
@property def my_dict(self):  return {hp.key: hp for hp in
self.house_pets} )?  that would be simpler and there are also Python
recipes for making the dictionary a read-only mapping (see
https://legacy.python.org/dev/peps/pep-0416/#existing-implementations).
    Building a custom association proxy class is doable but it's not
easy to get right.  lf you truly insist that you have three ways of
mutating the same collection, e.g. standalone object, list, *and*
dict, I can try to help you come up with that but you'd want to write
a lot of tests for it. There is also the area of SQL expression
support which is what makes associationproxy so complicated in the
first place, that would be another area you'd need to decide if you
need that to work or not.


>
>
>
> Thanks in advance for your consideration.
>
>
>
> Cheers, 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 [email protected].
> To post to this group, send email to [email protected].
> 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 [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to