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

Circular import + reexport = "error: Module '...' has no attribute '..."? #4049

Closed
jstasiak opened this issue Oct 3, 2017 · 6 comments · Fixed by #4695
Closed

Circular import + reexport = "error: Module '...' has no attribute '..."? #4049

jstasiak opened this issue Oct 3, 2017 · 6 comments · Fixed by #4695
Labels

Comments

@jstasiak
Copy link
Contributor

jstasiak commented Oct 3, 2017

This is a minimal test case replicating what I'm experiencing in a larger project:

% tree
.
└── asd
    ├── __init__.py
    ├── one.py
    └── two.py

2 directories, 6 files

% cat asd/__init__.py 
from asd.one import One
from asd.two import Two

% cat asd/one.py 
class One:
    pass

% cat asd/two.py 
from asd import One

class Two:
    pass

Python can work with it:

% python -c 'from asd import Two; print(Two)'
<class 'asd.two.Two'>

mypy, however, doesn't like it:

% mypy .
asd/two.py:1: error: Module 'asd' has no attribute 'One'

It fails all the same when I change __init__.py to use import .. as .. (I saw this pattern mentioned in #3981):

% cat asd/__init__.py 
from asd.one import One as One
from asd.two import Two as Two

Apologies if there's already an issue filled for this, I searched for one briefly and haven't found any.

mypy 0.521, CPython 3.6.2

@ilevkivskyi
Copy link
Member

I see the same error on master. There are few other issues related to import cycles. Maybe we can process situation like this or #3277 in the third pass (similar to forward references). @JukkaL what do you think?

@JukkaL
Copy link
Collaborator

JukkaL commented Oct 3, 2017

Yes, creating some sort of forward references for imported names seems like a potential way to fix this. Another thing that might work is improving the processing order of modules within cycles.

We should also make sure that any fix will also work in fine-grained incremental checking.

@bbarker
Copy link

bbarker commented Nov 20, 2017

Is there a workaround? I tried doing something like the following in database/__init__.py:

from browse.services.database.models import dbx
#...
db: SQLAlchemy = dbx

However, in my test code I still get errors like:

tests/test_database_service.py:22: error: Module has no attribute "db"  

for code like:

    def setUp(self) -> None:
        # ...
        from browse.services import database
        self.database_service = database

        #
        self.database_service.db.init_app(mock_app) #accessing db here causes the error

Edit: I found the discussion on forward references ... so I should look into that next.

@bbarker
Copy link

bbarker commented Nov 28, 2017

@JukkaL After looking into it more, I now realize you probably meant that forward references might provide a fix internally to mypy - I couldn't see how to use them myself to avoid the error I mentioned above. Is there currently a workaround?

@JukkaL
Copy link
Collaborator

JukkaL commented Nov 29, 2017

@bbarker Yes, I was talking how we could fix this in mypy internally. Your workaround should work, if you access the attribute directly and not through a "module alias":

from browse.services.database import db
db.init_app(...)

You seem to be assigning a module to an instance attribute, which isn't supported right now (#4291).

danpalmer added a commit to thread/routemaster that referenced this issue Jan 3, 2018
There's an open issue on mypy for when there are circular imports AND
re-exporting a name. This appears to be what we're seeing here.

python/mypy#4049
@carljm
Copy link
Member

carljm commented Jan 20, 2018

We're running into this problem at Instagram, too. It's a high priority for us since it's causing type errors we can't easily work around, so I'm looking into it. It seems like we already have a sort of internal "forward reference" for imported names, in the form of node.kind == UNBOUND_IMPORTED. In the problematic cases, we are visiting an import in semantic analysis pass 2 where the name we are importing is still of kind UNBOUND_IMPORTED in the module we are importing it from. Looking into whether we can just let UNBOUND_IMPORTED propagate in pass 2 (instead of immediately erroring) and then resolve it in pass 3.

(It's hard to workaround in our case because the error is raised in generated code, and the code generator has no way to tell whether this particular module will be involved in an import cycle, so it doesn't know whether to generate a # type: ignore comment on the import line; and we consider unused # type: ignore to be an error, too.)

carljm added a commit to carljm/mypy that referenced this issue Jan 20, 2018
JukkaL added a commit that referenced this issue Mar 8, 2018
This adds supports for some cases of importing an imported name
within an import cycle. Originally they could result in false positives
or false negatives.

The idea is to use a new node type ImportedName in semantic
analysis pass 1 to represent an indirect reference to a name in another
module. It will get resolved in semantic analysis pass 2.

ImportedName is not yet used everywhere where it could make
sense and this doesn't fix all related issues with import cycles.

Also did a bit of refactoring of type semantic analysis to avoid passing
multiple callback functions.

Fixes #4049.
Fixes #4429.
Fixes #4682.

Inspired by (and borrowed test cases from) #4495 by @carljm.

Supersedes #4495
yedpodtrzitko pushed a commit to kiwicom/mypy that referenced this issue Mar 15, 2018
This adds supports for some cases of importing an imported name
within an import cycle. Originally they could result in false positives
or false negatives.

The idea is to use a new node type ImportedName in semantic
analysis pass 1 to represent an indirect reference to a name in another
module. It will get resolved in semantic analysis pass 2.

ImportedName is not yet used everywhere where it could make
sense and this doesn't fix all related issues with import cycles.

Also did a bit of refactoring of type semantic analysis to avoid passing
multiple callback functions.

Fixes python#4049.
Fixes python#4429.
Fixes python#4682.

Inspired by (and borrowed test cases from) python#4495 by @carljm.

Supersedes python#4495
bdc34 pushed a commit to arXiv/arxiv-browse that referenced this issue May 11, 2018
attempting to use sqlalchemy stubs

fleshed out mypy.ini - verified works with vscode; updated mypy

improved support for mypy-sqlalchemy

questionable_model_change

fixed issues except python/mypy#4049

adding some mypy workarounds for mypy #4049 and #4291
mhl10 pushed a commit to arXiv/arxiv-browse that referenced this issue Oct 25, 2018
attempting to use sqlalchemy stubs

fleshed out mypy.ini - verified works with vscode; updated mypy

improved support for mypy-sqlalchemy

questionable_model_change

fixed issues except python/mypy#4049

adding some mypy workarounds for mypy #4049 and #4291
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants