Skip to content

How to use an implicit-optional library in a no_implicit_optional codebase #9208

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

Closed
PeterJCLaw opened this issue Jul 26, 2020 · 8 comments · Fixed by #9247
Closed

How to use an implicit-optional library in a no_implicit_optional codebase #9208

PeterJCLaw opened this issue Jul 26, 2020 · 8 comments · Fixed by #9247
Assignees

Comments

@PeterJCLaw
Copy link
Contributor

PeterJCLaw commented Jul 26, 2020

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

@PeterJCLaw PeterJCLaw changed the title How to use a no_implicit_optional library in a strictly-optional codebase How to use an implicit-optional library in a no_implicit_optional codebase Jul 26, 2020
@JukkaL
Copy link
Collaborator

JukkaL commented Jul 31, 2020

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.

@PeterJCLaw
Copy link
Contributor Author

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?

@gvanrossum
Copy link
Member

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.

@PeterJCLaw
Copy link
Contributor Author

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.

@gvanrossum gvanrossum self-assigned this Aug 2, 2020
@gvanrossum
Copy link
Member

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.

@PeterJCLaw
Copy link
Contributor Author

Amazing, thanks!

gvanrossum added a commit that referenced this issue Aug 2, 2020
Before this, per-module flags that are handled in fastparse (in
particular no_implicit_optional) were seeing the flag's global value.

Fixes #9208
@gvanrossum
Copy link
Member

@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.

JukkaL pushed a commit that referenced this issue Aug 3, 2020
Before this, per-module flags that are handled in fastparse (in
particular no_implicit_optional) were seeing the flag's global value.

Fixes #9208.
@PeterJCLaw
Copy link
Contributor Author

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants