Coder Social home page Coder Social logo

Comments (11)

zzzeek avatar zzzeek commented on May 27, 2024

I was just trying out the new stubs on an existing project if mine, and it looks like you did some awesome work there - I can get rid of a lot of workarounds. Thanks! Nevertheless, there's one issue I wanted to share with you.

Describe the bug
There are two ways to pass the first parameter of a relationship: by class or by str. When using the class directly everything works as expected, the type is detected when using uselist or relationsship. And I don't find any false positives afterwards.

When I use a str to configure my relationship things get pretty messy. First, I need to type the annotations manually, I wasn't able to find any way to configure it such that auto-detection works.

at the moment I'm not sure if auto-detection is going to be possible when the given arg is a string because the mypy plugin can't replicate how SQLAlchemy's registry does the lookups. That said there are ongoing bugs in the "string" version of things here anyway so not sure if @bryanforbes would find a way to manufacture a type for that case based on the string.

[SQLAlchemy Mypy plugin] Can't infer type from ORM mapped expression assigned to attribute 'addresses'; please specify a Python type or Mapped[<python type>] on the left hand side.

yes, so, I feel like if this is as good as we can get with that, this is still pretty good because it means you could have all your relationships passing under mypy. but onto where you talk about the bugs which looks to be things we already know about.


Second, even when annotating it manually, I'll have to cast it every single time I use it (when it's some kind of Iterable as shown in the example below).

error: "List?[Address?]" has no attribute "iter" (not iterable)
error: "List?[Address?]" has no attribute "append"

yeah this is the bug. this is sqlalchemy/sqlalchemy#6255 so this report is a dupe.


**Expected behavior**
Type detection works for relationship independent of type of first argument.

**To Reproduce**

```python
from __future__ import annotations

from typing import List

from sqlalchemy import Integer, Column, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship

Base = declarative_base()


class User(Base):

    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses: List[Address] = relationship("Address", back_populates="user", uselist=True)

    def clone(self) -> User:
        new_user = User(name=self.name)

        for existing_addr in self.addresses:
            new_user.addresses.append(
                Address(address=existing_addr.address)
            )
        return new_user


class Address(Base):

    __tablename__ = "addresses"

    id = Column(Integer, primary_key=True)
    address = Column(String)
    
    user_id = Column(Integer, ForeignKey("users.id"))
    
    user = relationship(User, back_populates="addresses", uselist=False)

thanks for the test case! unfortunately we are in this space with two different repos for the plugin/stubs so it's hard to find existing issues but this is the current big problem we are hoping to solve since there is no reaonable workaround.

from sqlalchemy2-stubs.

TilmanK avatar TilmanK commented on May 27, 2024

Thanks for the explanations. Is there any possibility to use the class directly in the relationsships?

from sqlalchemy2-stubs.

zzzeek avatar zzzeek commented on May 27, 2024

not sure what you mean. if you have relationship(Class), then it works. relationship("Class") is when it isn't importable yet. do you mean the mypy plugin could change the argument? overall the issue is that the plugin isn't able to tel mypy strongly enough that "yes, this is the type, please use it", so maybe. not really sure why it doesnt work already, mypy is very difficult to understand.

from sqlalchemy2-stubs.

TilmanK avatar TilmanK commented on May 27, 2024

No, that's not what I meant. The question was, if you are aware of any option to use the class when it isn't importable yet, some kind of "hack". I'm working a lot with back_populates - this means I'd have to use casts a lot in my code.

from sqlalchemy2-stubs.

MaicoTimmerman avatar MaicoTimmerman commented on May 27, 2024

As a workaround, you might be able to use something like:

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from models import Address
else:
    Address = "Address"

During type checking circular imports are not a problem.

from sqlalchemy2-stubs.

TilmanK avatar TilmanK commented on May 27, 2024

@MaicoTimmerman Thanks for the idea, I had something similar in mind. Your example brings us closer to a solution:


from typing import List, TYPE_CHECKING

from sqlalchemy import Integer, Column, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship

Base = declarative_base()


class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    name = Column(String)

    if not TYPE_CHECKING:
        Address = "Address"

    addresses: List[Address] = relationship(
        Address, back_populates="user", uselist=True
    )

    def clone(self) -> User:
        new_user = User(name=self.name)

        for existing_addr in self.addresses:
            new_user.addresses.append(Address(address=existing_addr.address))
        return new_user


class Address(Base):
    __tablename__ = "addresses"

    id = Column(Integer, primary_key=True)
    address = Column(String)

    user_id = Column(Integer, ForeignKey("users.id"))

    user = relationship(User, back_populates="addresses", uselist=False)

Nevertheless, one issue persists:
Incompatible types in assignment (expression has type "RelationshipProperty[<nothing>]", variable has type "List[Address]")

from sqlalchemy2-stubs.

zzzeek avatar zzzeek commented on May 27, 2024

OK here's a workaround:

class User(Base):

    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    name = Column(String)

    if TYPE_CHECKING:
        addresses: Mapped[List[Address]]
    else:
        addresses: List[Address] = relationship("Address", back_populates="user", uselist=True)

    def clone(self) -> User:
        new_user = User(name=self.name)

        for existing_addr in self.addresses:
            new_user.addresses.append(
                Address(address=existing_addr.address)
            )
        return new_user

what's super frustrating is I cant get the plugin to do that, if i change the class to express that code exactly it still fails.

from sqlalchemy2-stubs.

zzzeek avatar zzzeek commented on May 27, 2024

good news I foudn where it's breaking. stay tuned

from sqlalchemy2-stubs.

TilmanK avatar TilmanK commented on May 27, 2024

I'm totally excited. ;)

Thanks for looking into it!

from sqlalchemy2-stubs.

AlexanderWells-diamond avatar AlexanderWells-diamond commented on May 27, 2024

I'm also in the same boat - I have a lot of back_populates in use and so a lot of strings used as types. Any update would be much appreciated :)

from sqlalchemy2-stubs.

CaselIT avatar CaselIT commented on May 27, 2024

this seems to be fixed now

from sqlalchemy2-stubs.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.