Mypy: How to use an implicit-optional library in a no_implicit_optional codebase

Created on 26 Jul 2020  路  8Comments  路  Source: python/mypy

Given a library which uses no_implicit_optional = False (the default) and thus defines functions like:

def foo(bar: str = None): ...

(where bar is implicitly Optional[str])

I'm trying to work out how to call such a function in a project where I want to have set no_implicit_optional = True.

def myfunc(bar: Optional[str] = None):
    # Argument 1 to "foo" has incompatible type "Optional[str]"; expected "str"
    that_library.foo(bar)

My current workaround is to cast the value of bar to the non-optional type before passing it on, however this doesn't feel like a good solution.

I've also tried configuring mypy with a different setting for the library, via the following in my setup.cfg:

[mypy]
no_implicit_optional = True

[mypy-that_library]
no_implicit_optional = False

What is the recommended approach for this?

It's worth noting that the signature reported by reveal_type also changes in response to the local value of this setting.
Is it intentional that such library APIs change their signature based on the local value of no_implicit_optional?


I'm using mypy 0.782 on Python 3.6.

In case it's useful, the actual place I'm hitting this is in using httpx: https://github.com/PeterJCLaw/code-submitter/blob/79d5cc7539d47f7effd27e7d8c630badf75d9e0d/code_submitter/auth.py#L94-L106

Most helpful comment

What is the recommended approach for this?

Setting this in the configuration file for the library is the recommended approach.

Is it intentional that such library APIs change their signature based on the local value of no_implicit_optional?

I guess so, though it's questionable if this is desirable.

We are probably going to turn no_implicit_optional to true by default in a future mypy releases and deprecate the current default eventually.

All 8 comments

What is the recommended approach for this?

Setting this in the configuration file for the library is the recommended approach.

Is it intentional that such library APIs change their signature based on the local value of no_implicit_optional?

I guess so, though it's questionable if this is desirable.

We are probably going to turn no_implicit_optional to true by default in a future mypy releases and deprecate the current default eventually.

What is the recommended approach for this?

Setting this in the configuration file for the library is the recommended approach.

Thanks :)

As that doesn't seem to work (I've also hit it not working with other settings, notably implicit_reexport), should I raise a different issue, or are you happy to use this to track it?

As that doesn't seem to work (I've also hit it not working with other settings, notably implicit_reexport), should I raise a different issue, or are you happy to use this to track it?

Let's continue here. Show us exactly what you did (what you put in your config file and on the mypy command line), which mypy version, and some of the code and errors that makes you believe it doesn't work.

Sure. Here's the cut-down I'm using to explore this:

# Setup venv
python3 -m venv venv
.  venv/bin/activate
pip install mypy

# Create that_library
mkdir venv/lib/python3.8/site-packages/that_library/
touch venv/lib/python3.8/site-packages/that_library/py.typed
cat - > venv/lib/python3.8/site-packages/that_library/__init__.py <<EOF
def foo(bar: str = None): ...
EOF

# Setup local project
cat - > setup.cfg <<EOF
[mypy]
no_implicit_optional = True

[mypy-that_library]
no_implicit_optional = False
EOF

cat - > mine.py <<EOF
from typing import Optional
import that_library

def myfunc(bar: Optional[str] = None):
    that_library.foo(bar)
EOF

# Run mypy
mypy mine.py

With outputs:

$ mypy mine.py
mine.py:5: error: Argument 1 to "foo" has incompatible type "Optional[str]"; expected "str"
Found 1 error in 1 file (checked 1 source file)

This is the error I'm trying to solve by adding the mypy-that_library config.

I'm not sure if I was expecting the config I added would help solve this, however that was the cause of my original question.

I'm guessing that mypy is evaluating the config for mine.py at this point, rather than considering that the that_library.foo call is part of that_library and should therefore have implicit-optional behaviour?


I'm using mypy 0.782 and have tried both Python 3.6 and 3.8.

Awesome. I reproduced this and I think I have diagnosed it, but I don't have a fix yet. It seems that when mypy parses the file it passes in the wrong Options object. It correctly sets State.options to the module-cloned Options object (where the flag is False) but it passes BuildManager.options to the parse() function, and that is still the global set of options, where the flag is True.

I will come up with a PR to fix this in the coming days.

Amazing, thanks!

@PeterJCLaw If you have the same issue with implicit_reexport could you open a separate issue for that? My fix only fixes this for flags that are processed by fastparse.py, and implicit_reexport is handled somewhere else.

I've also hit it not working with other settings, notably implicit_reexport

@PeterJCLaw If you have the same issue with implicit_reexport could you open a separate issue for that? My fix only fixes this for flags that are processed by fastparse.py, and implicit_reexport is handled somewhere else.

Just for completeness: I've since tested the above but varied for implicit_reexport and I can't find an issue there. I must have been mistaken about that also being an issue.

Thanks for fixing this so quickly!

Was this page helpful?
0 / 5 - 0 ratings