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

mypy bug with try/except conditional imports #1153

Closed
timabbott opened this issue Jan 25, 2016 · 34 comments
Closed

mypy bug with try/except conditional imports #1153

timabbott opened this issue Jan 25, 2016 · 34 comments
Labels
false-positive mypy gave an error on correct code feature priority-1-normal

Comments

@timabbott
Copy link

try:
    import simplejson
except ImportError:
    import json as simplejson

resulst in the error error: Name 'simplejson' already defined

@refi64
Copy link
Contributor

refi64 commented Jan 25, 2016

Pretty sure this is a dup, but I can't find the issue.

@refi64
Copy link
Contributor

refi64 commented Jan 25, 2016

Related to #649.

@gvanrossum
Copy link
Member

Note that #649 was closed; a buch of "leftover" bugs were opened in its
place, but I don't see one that matches this pattern, so I think it's a new
case.

On Mon, Jan 25, 2016 at 3:19 PM, Ryan Gonzalez notifications@github.com
wrote:

Related to #649 #649.


Reply to this email directly or view it on GitHub
#1153 (comment).

--Guido van Rossum (python.org/~guido)

@gvanrossum
Copy link
Member

Closed by mistake.

@JukkaL
Copy link
Collaborator

JukkaL commented Oct 14, 2016

Another example from #2251 (reported by @RitwikGupta):

try:
    # Python 3
    from urllib.request import urlopen
except ImportError:
    # Python 2
    from urllib2 import urlopen

@gvanrossum
Copy link
Member

And another from #2253:

This python code generates an error. It's a common idiom and it would be good if mypy could check for it in some way.

try:
    import cPickle as pickle
except ImportError:
    import pickle
$ mypy --py2 --silent-imports bad.py
bad.py:4: error: Name 'pickle' already defined

@JukkaL
Copy link
Collaborator

JukkaL commented Oct 14, 2016

The current workaround is to add a # type: ignore comment. For example:

try:
    import cPickle as pickle
except ImportError:
    import pickle  # type: ignore     # <<-- add this

@posita
Copy link
Contributor

posita commented Jan 25, 2017

FYI, this doesn't appear to work with sub-modules:

# This will error
try:
    import foo.bar
except ImportError:
    foo = None  # type:ignore

This seems to work though:

# This will pass
try:
    import doesnt.exist  # type:ignore
except ImportError:
    doesnt = None

# So will this
try:
    import collections.abc  # type:ignore
except ImportError:
    collections = None

assert doesnt is None
assert collections is not None

@gvanrossum gvanrossum removed this from the 0.5 milestone Mar 29, 2017
@quodlibetor
Copy link
Contributor

This is particularly difficult when the import that's happening is a typing import.

For example, this works:

$ mypy --version
mypy 0.530

$ cat no_try.py
from typing import Dict, Any

JSON = Dict[str, Any]

def accept(obj: JSON) -> Any:
    return obj.pop('hello')

$ mypy --ignore-missing-imports no_try.py
$ cat with_try.py
try:
    from typing import Dict, Any
except:
    from backports.typing import Dict, Any  # type: ignore

JSON = Dict[str, Any]

def accept(obj: JSON) -> Any:
    return obj.pop('hello')

$ mypy --ignore-missing-imports with_try.py
with_try.py:10: error: Invalid type "with_try.JSON"
with_try.py:11: error: JSON? has no attribute "pop"

This program should typecheck.

@ilevkivskyi
Copy link
Member

@quodlibetor I don't think your particular case is a bug. mypy has special treatment of typing module, so your imports should be written as:

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from typing import Dict, Any
else:
    try:
        from typing import Dict, Any
    except ImportError:
        from backports.typing import Dict, Any

If your version of typing doesn't have TYPE_CHECKING, then use:

MYPY = False
if MYPY:
    # special imports from typing
else:
    # actual runtime implementation

mypy will understand this.

@quodlibetor
Copy link
Contributor

Hmm, okay, I've verified that that works.

$ cat with_try.py
MYPY = False
if MYPY:
    from typing import Dict, Any
else:
    try:
        from typing import Dict, Any
    except:
        from backports.typing import Dict, Any
JSON = Dict[str, Any]
def accept(obj: JSON) -> Any:
    return obj.pop('hello')

$ mypy with_try.py

I think that I would still be inclined to describe the current behavior as a bug that has a workaround. Would you agree that it is at least as a UX problem?

The if MYPY trick brings the total number of lines of boilerplate to type check a module up to 8, for what would be a single-line import if you were only supporting the most modern python. Even just supporting python 3.5 if you want new items from the typing module means that we jump from 1 to 8 lines of code. It's not even "simple" code. It's also not the obvious first way that I would write that code... obviously.

@JelleZijlstra
Copy link
Member

Why do you need backports.typing? The typing module on PyPI supports all Python versions that mypy itself supports.

@quodlibetor
Copy link
Contributor

I did not know about the typing package on pypi. The last time I looked the only option was backports.typing. That that really ought to fix all my problems, or at least cause interesting new problems ;-) Thank you!

@gvanrossum
Copy link
Member

gvanrossum commented Oct 12, 2017 via email

jlitzingerdev added a commit to jlitzingerdev/klein that referenced this issue Nov 17, 2017
The unit tests require mock but don't specify it, which causes
problems for any usages outside tox (e.g. testing that changes to
twisted don't break klein).  Use PEP508 to specify environment markers
and only install mock when required.  Tests can then conditionally
import unittest.mock and fall back to mock.

The conditional imports do raise a mypy error, discussed here:
    python/mypy#1153

So an annotation is required to silence this specific warning.
hardbyte added a commit to data61/clkhash that referenced this issue Mar 5, 2018
@adam-grant-hendry
Copy link

Building on an answer from @cjerdonek, would this be an acceptable workaround instead of # type: ignore?:

try:
    from py3_pkg import module as _module
except:
    from py2_pkg import module
else:
    module = _module

@atrigent
Copy link

atrigent commented Aug 7, 2022

Is this going to be fixed anytime soon?

@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Aug 7, 2022

No, it's not, and it's not clear that it should.

A majority of cases I've seen here are to work around Python stdlib changes, especially Python 2 to Python 3 stuff. sys.version_info checks are the preferred way to do this. Also Python 2 is dead.

if sys.version_info >= (3, 8):
    import importlib.metadata as importlib_metadata
else:
    import importlib_metadata

For the remaining cases, it's often not clear that it's sound, especially given nominal typing. Even if the API is structurally the same, nominal isinstance checks could result in surprising behaviour from mypy.

The only thing we should do in this space is #5018 where you can explicitly define exactly the structure you want with a Protocol, and mypy will confirm.

For all other cases, if you want to lie to the typechecker, please, go ahead and lie to the type checker (or type ignore):

if TYPE_CHECKING:
    import json
else:
    try:
        import simplejson as json
    except ImportError:
        import json

jpmckinney added a commit to open-contracting/yapw that referenced this issue Aug 23, 2022
…'s not worth the effort to define a common protocol)
lilydjwg added a commit to lilydjwg/nvchecker that referenced this issue May 9, 2023
The following error will be reported without if:

  error: Name "tomllib" already defined (by an import)

While this can be silenced by a "# type: ignore", in some case[2] mypy
will report the following error:

  error: Unused "type: ignore" comment

[1]: python/mypy#1153
[2]: https://github.com/lilydjwg/nvchecker/actions/runs/4916840821/jobs/8793454970
@lilydjwg
Copy link

lilydjwg commented May 9, 2023

FYI "type ignore" doesn't work well because in some cases it will cause Unused "type: ignore" comment errors elsewhere (e.g. GitHub Actions).

@cebtenzzre
Copy link

@lilydjwg Issue #8823 was fixed last week and covers what you just described - unused-ignore errors in unreachable code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
false-positive mypy gave an error on correct code feature priority-1-normal
Projects
None yet
Development

No branches or pull requests