Repository navigation
Strange name substitution on upgrading to >= 0.900 from 0.812 #10661
Description
Bug Report
I want to apologize in advance for the somewhat vague nature of this bug report. I've tried for hours in order to create a reproducible example, and have failed. Unfortunately, the bug occurs in some proprietary code, so I can't release it to you all to look at.
I went to upgrade my project to use mypy 0.902 and after fixing all the expected errors related to third party stub libraries, I am left with the following two errors:
buyfair/model/Warehouse.py:312: error: Invalid index type "buyfair.model.Warehouse.Warehouse" for "Dict[Warehouse, WarehouseProduct]"; expected type "buyfair.model.warehouse.Warehouse"
buyfair/model/Product.py:222: error: Argument 1 to "obeys_maximums" of "WarehouseSource" has incompatible type "Dict[buyfair.model.Product.Product, buyfair.model.Product.Unit]"; expected "Dict[buyfair.model.product.Product, buyfair.model.product.Unit]"
These errors are really strange, because there isn't any class called buyfair.model.Warehouse.Warehouse or buyfair.model.Product.Product. I shrugged my shoulders and added # type: ignore to each of these, and tried to run mypy again only to receive the following errors:
buyfair/model/warehouse.py:312: error: unused "type: ignore" comment
buyfair/model/product.py:222: error: unused "type: ignore" comment
I've set warn_unused_ignores=False and will move on, but figured this is at least worth reporting.
To Reproduce
Like I said above, I tried to reproduce this error in a smaller more self-contained example for a few hours, but wasn't successful. I'll try to give more details around the context in the error messages above:
buyfair/model/warehouse.py
from typing import Dict, AbstractSet, TYPE_CHECKING
if TYPE_CHECKING:
from buyfair.model.product import Product, Unit
class Warehouse:
warehouse_sources: Dict["Source", "WarehouseSource"]
class WarehouseProduct:
some_bool_value: bool
class WarehouseGroup(AbstractSet[Warehouse]):
def some_bool_value(self, product: "Product") -> bool:
return any(product.warehouse_products[warehouse].some_bool_value for warehouse in self) # <-- problematic line
class WarehouseSource:
def obeys_maximums(self, products: Dict["Product", "Unit"]) -> bool:
...
buyfair/model/product.py
from typing import Dict, NewType, TYPE_CHECKING
if TYPE_CHECKING:
from buyfair.model.warehouse import Warehouse, WarehouseProduct, WarehouseSource
Unit = NewType("Unit", int)
class Product:
warehouse_products: "Dict[Warehouse, WarehouseProduct]"
def some_other_bool_value(warehouse: "Warehouse") -> bool:
warehouse_source: "WarehouseSource" = ...
products = {self: Unit(0)}
if warehouse_source.obeys_maximums(products): # <-- Other problematic line
...
In both these cases, mypy is replacing the module name with the class name.
buyfair.model.product.Product becomes buyfair.model.Product.Product and buyfair.model.warehouse.Warehouse becomes buyfair.model.Warehouse.Warehouse
Expected Behavior
In both these cases, the types work out just fine, and mypy should recognize the types just fine.
Actual Behavior
mypy fails to recognize the types correctly and creates a new made up type.
(Write what happened.)
Your Environment
- Mypy version used: 0.900, 0.901, 0.902
- Mypy command-line flags:
- Mypy configuration options from
mypy.ini(and other config files):python_version=3.6 warn_unused_ignores=False check_untyped_defs=True strict_optional=False - Python version used: 3.6
- Operating system and version: Centos 7.5 (running inside docker)
Activity
emmatyping commentedon Jun 17, 2021
Thank you for giving a detailed bug report. My guess is there is an issue with some kind of import cycle confusing the analyzer, but that wouldn't make sense why it would think the classes contain themselves...
Could you check if you can get attributes off of the replaced types? Also perhaps try using reveal_type() before?
choogeboom commentedon Jun 17, 2021
I added
reveal_typeas follows:in
buyfair/model/warehouse.pyand in
buyfair/model/product.pyStrangely, mypy reveals each line twice: once correctly, and once incorrectly:
I'm not sure what you mean by that
JelleZijlstra commentedon Jun 17, 2021
Interesting that one case has lowercase
warehouse.pyand one has uppercaseWarehouse.py. That's probably a clue to where the problem is.NeilGirdhar commentedon Jun 17, 2021
Maybe his file system is case insensitive, but some functions are normalizing filenames?
choogeboom commentedon Jun 17, 2021
This is running in CentOS, so the file system is definitely case sensitive
JelleZijlstra commentedon Jun 17, 2021
And just to make sure, you don't have both a
warehouse.pyand aWarehouse.py?choogeboom commentedon Jun 17, 2021
that is correct!
JelleZijlstra commentedon Jun 17, 2021
Strange, then I wonder where mypy gets the uppercase filenames from. If your filesystem isn't case-insensitive, maybe something in the mypy cache is?
paulcwatts commentedon Jun 24, 2021
I'm experiencing this issue as well in my Django projects.
Configuration:
I'm getting pretty much what @choogeboom is getting:
Even with the strange duplicate
reveal_typeissue.Funny thing is, my coworkers that are using Windows as host do not have the same error. It also doesn't occur on our CI machines.
I also have an inkling that it has something to do with importing these classes through
__init__.py, though I can't be certain yet.paulcwatts commentedon Jun 24, 2021
What I have is three files:
And a file
users/models/__init__.py:All of my code imports from
users.models, not any of the submodules. I can confirm that if I combine all of these files into oneusers/models.py, the error goes away.choogeboom commentedon Jun 24, 2021
We have a similar structure in our codebase.
Everything outside
modelsonly importsmodels, but internally, the subpackages/submodules do import each other to prevent cyclical dependencies.JukkaL commentedon Jun 24, 2021
Can you give the full module hierarchy + all the names defined in the relevant modules? Also I'd like to see the relevant
from .x importstatements.Does the removal of
__all__have any affect?Is there both a class
mod.Thingand a modulemod.thing(lower case version of the first one)?Can you double check that your file system is not case insensitive? Even on Linux it's possible to have case insensitive file systems, I think.
paulcwatts commentedon Jun 24, 2021
This is about as simple as a test as I can come up with: https://github.com/paulcwatts/mypytest
It definitely is strange. I can't repro the issue with just the code -- it has to be within the Docker container. What's more, the code itself has to be mounted as a volume.
First build the image:
Then, if you run mypy with code in the image:
If you run mypy with the code mounted on a volume:
It seems like it might have something to do with what gets stored in the mypy cache:
As opposed to:
paulcwatts commentedon Jun 24, 2021
And to answer your question, @JukkaL , the host file system is case insensitive (APFS), but the container file system (ext4) is not. That might have something to do with it?
paulcwatts commentedon Jun 24, 2021
It looks like this might have something to do with it? #10093 .
"On Linux we skip all logic, since the file system is almost always case sensitive. "
This is one case where that assumption doesn't apply.
SteVwonder commentedon Aug 11, 2021
Just wanted to chime in that I ran into this exact issue, and I believe I have independently confirmed both of @paulcwatts 's conclusions.
I was working on some python code on a mac with a case-insensitive filesystem using VS Code's container remote extension. The VS Code remote extension automatically bind-mounts the source code on the case-insensitive mac into the linux container. Issue on the related repo with the errors: ExaWorks/psij-python#47
I tested the same codebase with mypy v0.800 (before the linked PR #10093 was merge) and the issue disappeared.
It seems that in order to handle these edge cases, there needs to be someway to turn off the "platform == Linux" short-circuit for the case-sensitivity checking, since a case-insensitive filesystem could still be mounted into a linux VM/container. If anyone has any ideas/pointers for how to accomplish that (I'm unfamiliar with mypy's codebase/configuration/practices), I'd be willing to take a stab at implementing it and submitting a PR. Would a command-line flag be acceptable? Any other ideas?
SteVwonder commentedon Aug 11, 2021
Realizing that this test wasn't all that rigorous, I went ahead and grabbed the latest mypy from git and the problem is still around:
Removed the linux shortcircuit (see git diff below) and the problem went away:
So I think that is pretty conclusive.
To be more specific, would a environment-specific configuration option like
assume_case_insensitive_fsbe appropriate to add to a mypy configuration file? My (very ignorant) impression is that mypy configuration options are meant to be checked into version control and applied to an entire codebase as opposed to a particular dev environment. Would a command line flag be more appropriate? Do all command line flags have a corresponding config option in mypy?Maybe the best option is to just remove the short-circuit all together so that this issue is guaranteed to not occur, regardless of system configuration?
zidarsk8 commentedon Aug 31, 2021
And we have the same issue in docker on a mac, and reproducible with https://github.com/zidarsk8/mypy-mac-docker
Remove assumption of case insensitivity
Remove assumption of case sensitivity (#11708)
Remove assumption of case sensitivity (python#11708)