Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loader option error messages #4433

Closed
zzzeek opened this issue Jan 9, 2019 · 2 comments
Closed

loader option error messages #4433

zzzeek opened this issue Jan 9, 2019 · 2 comments
Labels
bug Something isn't working loader options ORM options like joinedload(), load_only(), these are complicated and have a lot of issues orm
Milestone

Comments

@zzzeek
Copy link
Member

zzzeek commented Jan 9, 2019

see if we can get every mistake here to make a clear error:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class A(Base):
    __tablename__ = 'a'

    id = Column(Integer, primary_key=True)
    data = Column(String)
    bs = relationship("B")


class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))
    data = Column(String)

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)

s.add(A(bs=[B()]))
s.commit()

case one:

(Pdb) !s.query(A, B).options(load_only(A.id, B.data))
*** sqlalchemy.exc.ArgumentError: Wildcard loader can only be used with exactly one entity.  Use Load(ent) to specify specific entities.

they don't see the wildcard. Can we catch that and re-state, or can we detect this within load_only() and not defer() ?

(Pdb) !s.query(A).options(load_only('id', 'bs'))
<sqlalchemy.orm.query.Query object at 0x7f9c411a95f8>

doesn't raise. why not? it's wrong. only if we call .all():

(Pdb) !s.query(A).options(load_only('id', 'bs')).all()
*** Exception: can't locate strategy for <class 'sqlalchemy.orm.relationships.RelationshipProperty'> (('deferred', False), ('instrument', True))

there's the error. two levels of bad, first off, it should be, "undefer() only applies to column based attributes, does not apply to relationship attribute "bs"". Secondly, like before, it should be stated in terms of load_only().

(Pdb) !s.query(A).options(load_only(A.id, B.data))
*** sqlalchemy.exc.ArgumentError: Can't find property 'data' on any entity specified in this Query.  Note the full path from root (Mapper|A|a) to target entity must be specified.

I just stated "data" is from "B", that's where it's from! you need to say, "Attribute B.data is in terms of class "B", but this query has no entity "B"". Also, "note full path from root mapper() must be speciied", please state the class, not the mapper

(Pdb) !s.query(A, B).options(load_only(A.id, B.data))
*** sqlalchemy.exc.ArgumentError: Wildcard loader can only be used with exactly one entity.  Use Load(ent) to specify specific entities.

same double issue again - the first level of error message should be, "can't use wildcard loader against multiple entities A, B; please use Load(A).undefer(), Load(B).undefer" - and then, wrong again, the error has to be in terms of load only (so thats two test cases)

(Pdb) !s.query(A).options(load_only(A.bs), subqueryload(A.bs).load_only("data"))
<sqlalchemy.orm.query.Query object at 0x7feef10a54e0>

this query "works" because the subqueryload wipes out the load_only(A.bs) - but this should raise immediately, like above

amazingly, it doesnt' even work from Load(), just ignores it:

(Pdb) !s.query(A).options(Load(A).load_only(A.bs), subqueryload(A.bs).load_only("data"))

all the mapper options need to be doing a check up front for the right kind of attribute, at least when they get sent to query.options().

problem exists in the other direction too:

(Pdb) !s.query(A).options(joinedload(A.data))
<sqlalchemy.orm.query.Query object at 0x7feef09b8828>
(Pdb) !s.query(A).options(joinedload(A.data)).all()
*** Exception: can't locate strategy for <class 'sqlalchemy.orm.properties.ColumnProperty'> (('lazy', 'joined'),)
@zzzeek zzzeek added bug Something isn't working orm loader options ORM options like joinedload(), load_only(), these are complicated and have a lot of issues labels Jan 9, 2019
@zzzeek zzzeek added this to the 1.3 milestone Jan 9, 2019
@sqla-tester
Copy link
Collaborator

Mike Bayer has proposed a fix for this issue in the master branch:

Improve error messages in the area of loader options https://gerrit.sqlalchemy.org/1082

@zzzeek
Copy link
Member Author

zzzeek commented Jan 9, 2019

here are some cases to add in

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class A(Base):
    __tablename__ = 'a'

    id = Column(Integer, primary_key=True)
    data = Column(String)
    bs = relationship("B")


class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))
    data = Column(String)

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)

s.add(A(bs=[B()]))
s.commit()

def case1():
    s.query(A, B).options(load_only(A.id, B.data))

def case1a():
    s.query(A, B).options(undefer('bs'))

def case2():
    s.query(A).options(load_only('id', 'bs'))

def case3():
    s.query(A).options(load_only('id', 'bs')).all()

def case4():
    s.query(A).options(load_only(A.id, B.data))

def case4a():
    s.query(A).options(load_only("id", "data_ne"))

def case4b():
    s.query(aliased(A)).options(load_only("id", "data_ne"))

def case5():
    s.query(A, B).options(load_only(A.id, B.data))


def case6():
    s.query(A).options(load_only(A.bs), subqueryload(A.bs).load_only("data"))

def case7():
    s.query(A).options(Load(A).load_only(A.bs), subqueryload(A.bs).load_only("data"))

def case8():
    s.query(A).options(joinedload(A.data))

def case9():
    s.query(A).options(joinedload(A.data)).all()

for case in [fn for key, fn in locals().items() if key.startswith("case")]:
    try:
        line = open(case.__code__.co_filename).read().splitlines()[
            case.__code__.co_firstlineno]
        print("-----------------------------------")
        print("Running code: %s" % line)
        case()
        assert False
    except Exception as e:
        print("Produced exception: %s" % e)
-----------------------------------
Running code:     s.query(A, B).options(load_only(A.id, B.data))
Produced exception: Can't apply wildcard ('*') or load_only() loader option to multiple entities mapped class A, mapped class B. Specify loader options for each entity individually, such as Load(mapped class A).some_option('*'), Load(mapped class B).some_option('*').
-----------------------------------
Running code:     s.query(A, B).options(undefer('bs'))
Produced exception: Can't apply "column loader" strategy to property "A.bs", which is a "relationship property"; this loader strategy is intended to be used with a "column property".
-----------------------------------
Running code:     s.query(A).options(load_only('id', 'bs'))
Produced exception: Can't apply "column loader" strategy to property "A.bs", which is a "relationship property"; this loader strategy is intended to be used with a "column property".
-----------------------------------
Running code:     s.query(A).options(load_only('id', 'bs')).all()
Produced exception: Can't apply "column loader" strategy to property "A.bs", which is a "relationship property"; this loader strategy is intended to be used with a "column property".
-----------------------------------
Running code:     s.query(A).options(load_only(A.id, B.data))
Produced exception: Mapped attribute "B.data" does not apply to any of the root entities in this query, e.g. mapped class A.   Please specify the full path from one of the root entities to the target attribute. 
-----------------------------------
Running code:     s.query(A).options(load_only("id", "data_ne"))
Produced exception: Can't find property named 'data_ne' on mapped class A in this Query. 
-----------------------------------
Running code:     s.query(aliased(A)).options(load_only("id", "data_ne"))
Produced exception: Can't find property named 'data_ne' on aliased(A) in this Query. 
-----------------------------------
Running code:     s.query(A, B).options(load_only(A.id, B.data))
Produced exception: Can't apply wildcard ('*') or load_only() loader option to multiple entities mapped class A, mapped class B. Specify loader options for each entity individually, such as Load(mapped class A).some_option('*'), Load(mapped class B).some_option('*').
-----------------------------------
Running code:     s.query(A).options(load_only(A.bs), subqueryload(A.bs).load_only("data"))
Produced exception: Can't apply "column loader" strategy to property "A.bs", which is a "relationship property"; this loader strategy is intended to be used with a "column property".
-----------------------------------
Running code:     s.query(A).options(Load(A).load_only(A.bs), subqueryload(A.bs).load_only("data"))
Produced exception: Can't apply "column loader" strategy to property "A.bs", which is a "relationship property"; this loader strategy is intended to be used with a "column property".
-----------------------------------
Running code:     s.query(A).options(joinedload(A.data))
Produced exception: Can't apply "joined loader" strategy to property "A.data", which is a "column property"; this loader strategy is intended to be used with a "relationship property".
-----------------------------------
Running code:     s.query(A).options(joinedload(A.data)).all()
Produced exception: Can't apply "joined loader" strategy to property "A.data", which is a "column property"; this loader strategy is intended to be used with a "relationship property".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working loader options ORM options like joinedload(), load_only(), these are complicated and have a lot of issues orm
Projects
None yet
Development

No branches or pull requests

2 participants