Uh oh!
There was an error while loading. Please reload this page.
Use TypeIs in is_dataclass - #11929
Conversation
This comment has been minimized.
This comment has been minimized.
NeilGirdhar
commented
May 17, 2024
@erictraut Is this a problem with Pyright? |
erictraut
commented
May 17, 2024
@NeilGirdhar, can you be more specific? I'm not sure what you're asking. |
JelleZijlstra
commented
May 17, 2024
I think it's about this CI failure: I haven't looked at this code in detail to figure out whether pyright is correct here. |
So, I removed one of the overloads, but from my testing it didn't seem necessary anymore. I'll quickly try replacing it to see if it makes a difference. (Edit: still broken. Removing it since it doesn't belong anymore IMO.) The failure in PyRIght seems to be because Pyright isn't evaluating the overloads for |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
erictraut
commented
May 18, 2024
@NeilGirdhar, PEP 742 allows some variation between type checkers. What you're seeing here is an example of this. Pyright's evaluation is arguably more accurate (narrower) and more consistent than mypy's, but both results are defensible. I think you'll need to change your test case to accommodate this variation. PEP 742 says:
If you try this self-contained code sample in both pyright and mypy, you'll see that mypy is inconsistent but pyright is consistent. fromdataclassesimportFieldfromtypingimportAny, ClassVar, Protocol, runtime_checkablefromtyping_extensionsimportTypeIs@runtime_checkableclassDataclassInstance(Protocol):
__dataclass_fields__: ClassVar[dict[str, Field[Any]]]
defis_dataclass(obj: type) ->TypeIs[type[DataclassInstance]]: ...
deftest1(x: type) ->None:
ifisinstance(x, DataclassInstance):
reveal_type(x) # Both mypy and pyright reveal <subclass of "type" and "DataclassInstance">deftest2(x: type) ->None:
ifis_dataclass(x):
reveal_type(x) # Mypy reveals "DataclassInstance" but pyright gives results consistent with "isinstance" |
@erictraut Sorry, but I don't understand how these two functions are supposed to be the same. In the first function, you are checking against If you change Similarly, if you change the isinstance line to Therefore, I believe this is a bug in Pyright even if as you say PEP 742 doesn't specify this case. |
erictraut
commented
May 18, 2024
Ah yes, you're correct. This is a bug in pyright. |
erictraut
commented
May 18, 2024
@NeilGirdhar, this will be fixed in the next release of pyright. I typically publish a new version every Tuesday evening PST. You could either wait until then to land this PR or you could temporarily comment out this test case. |
This comment has been minimized.
This comment has been minimized.
NeilGirdhar
commented
May 22, 2024
@JelleZijlstra There appear to be some errors with updating PyRight. Would you mind updating PyRight when you have a chance? |
srittau
commented
May 22, 2024
Dependencies are updated automatically by renovate each UTC night. |
Diff from mypy_primer, showing the effect of this PR on open source code: pydantic (https://github.com/samuelcolvin/pydantic)
+ pydantic/v1/json.py:80: error: No overload variant of "asdict" matches argument type "type[DataclassInstance]" [call-overload]+ pydantic/v1/json.py:80: note: Possible overload variants:+ pydantic/v1/json.py:80: note: def asdict(obj: DataclassInstance) -> dict[str, Any]+ pydantic/v1/json.py:80: note: def [_T] asdict(obj: DataclassInstance, *, dict_factory: Callable[[list[tuple[str, Any]]], _T]) -> _T+ pydantic/deprecated/json.py:98: error: No overload variant of "asdict" matches argument type "type[DataclassInstance]" [call-overload]+ pydantic/deprecated/json.py:98: note: Possible overload variants:+ pydantic/deprecated/json.py:98: note: def asdict(obj: DataclassInstance) -> dict[str, Any]+ pydantic/deprecated/json.py:98: note: def [_T] asdict(obj: DataclassInstance, *, dict_factory: Callable[[list[tuple[str, Any]]], _T]) -> _T
hydra-zen (https://github.com/mit-ll-responsible-ai/hydra-zen)
- src/hydra_zen/structured_configs/_implementations.py:1129: error: Argument 2 to "builds" of "BuildsFn" has incompatible type "**dict[str, int | float | Path | DataClass_ | type[DataClass_] | Enum | Any | Sequence[HydraSupportedType] | Mapping[Any, HydraSupportedType] | None]"; expected "Literal[False] | None" [arg-type]- src/hydra_zen/structured_configs/_implementations.py:1129: error: Argument 2 to "builds" of "BuildsFn" has incompatible type "**dict[str, int | float | Path | DataClass_ | type[DataClass_] | Enum | Any | Sequence[HydraSupportedType] | Mapping[Any, HydraSupportedType] | None]"; expected "bool" [arg-type]
- src/hydra_zen/structured_configs/_implementations.py:1129: error: Argument 2 to "builds" of "BuildsFn" has incompatible type "**dict[str, int | float | Path | DataClass_ | type[DataClass_] | Enum | Any | Sequence[HydraSupportedType] | Mapping[Any, HydraSupportedType] | None]"; expected "Builds[Callable[[Callable[..., Any]], Callable[..., Any]]] | ZenPartialBuilds[Callable[[Callable[..., Any]], Callable[..., Any]]] | HydraPartialBuilds[Callable[[Callable[..., Any]], Callable[..., Any]]] | Just[Callable[[Callable[..., Any]], Callable[..., Any]]] | type[Builds[Callable[[Callable[..., Any]], Callable[..., Any]]]] | type[ZenPartialBuilds[Callable[[Callable[..., Any]], Callable[..., Any]]]] | type[HydraPartialBuilds[Callable[[Callable[..., Any]], Callable[..., Any]]]] | type[Just[Callable[[Callable[..., Any]], Callable[..., Any]]]] | Callable[[Callable[..., Any]], Callable[..., Any]] | str | None | Sequence[Builds[Callable[[Callable[..., Any]], Callable[..., Any]]] | ZenPartialBuilds[Callable[[Callable[..., Any]], Callable[..., Any]]] | HydraPartialBuilds[Callable[[Callable[..., Any]], Callable[..., Any]]] | Just[Callable[[Callable[..., Any]], Callable[..., Any]]] | type[Builds[Callable[[Callable[..., Any]], Callable[..., Any]]]] | type[ZenPartialBuilds[Callable[[Callable[..., Any]], Callable[..., Any]]]] | type[HydraPartialBuilds[Callable[[Callable[..., Any]], Callable[..., Any]]]] | type[Just[Callable[[Callable[..., Any]], Callable[..., Any]]]] | Callable[[Callable[..., Any]], Callable[..., Any]] | str | None]" [arg-type]
- src/hydra_zen/structured_configs/_implementations.py:1129: error: Argument 2 to "builds" of "BuildsFn" has incompatible type "**dict[str, int | float | Path | DataClass_ | type[DataClass_] | Enum | Any | Sequence[HydraSupportedType] | Mapping[Any, HydraSupportedType] | None]"; expected "Mapping[str, SupportedPrimitive] | None" [arg-type]- src/hydra_zen/structured_configs/_implementations.py:1129: error: Argument 2 to "builds" of "BuildsFn" has incompatible type "**dict[str, int | float | Path | DataClass_ | type[DataClass_] | Enum | Any | Sequence[HydraSupportedType] | Mapping[Any, HydraSupportedType] | None]"; expected "list[str | DataClass_ | type[DataClass_] | Mapping[str, str | Sequence[str] | None]] | None" [arg-type]- src/hydra_zen/structured_configs/_implementations.py:1129: error: Argument 2 to "builds" of "BuildsFn" has incompatible type "**dict[str, int | float | Path | DataClass_ | type[DataClass_] | Enum | Any | Sequence[HydraSupportedType] | Mapping[Any, HydraSupportedType] | None]"; expected "str | None" [arg-type]- src/hydra_zen/structured_configs/_implementations.py:1129: error: Argument 2 to "builds" of "BuildsFn" has incompatible type "**dict[str, int | float | Path | DataClass_ | type[DataClass_] | Enum | Any | Sequence[HydraSupportedType] | Mapping[Any, HydraSupportedType] | None]"; expected "tuple[type[DataClass_], ...]" [arg-type]- src/hydra_zen/structured_configs/_implementations.py:1129: error: Argument 2 to "builds" of "BuildsFn" has incompatible type "**dict[str, int | float | Path | DataClass_ | type[DataClass_] | Enum | Any | Sequence[HydraSupportedType] | Mapping[Any, HydraSupportedType] | None]"; expected "ZenConvert | None" [arg-type]- src/hydra_zen/structured_configs/_implementations.py:1129: error: Argument 2 to "builds" of "BuildsFn" has incompatible type "**dict[str, int | float | Path | DataClass_ | type[DataClass_] | Enum | Any | Sequence[HydraSupportedType] | Mapping[Any, HydraSupportedType] | None]"; expected "T" [arg-type]- src/hydra_zen/structured_configs/_implementations.py:1161: error: Incompatible types in assignment (expression has type "Just", variable has type "type[Builds[Any]]") [assignment]
pytest (https://github.com/pytest-dev/pytest)
+ src/_pytest/_io/pprint.py:117: error: Right operand of "and" is never evaluated [unreachable]+ src/_pytest/_io/pprint.py:125: error: Statement is unreachable [unreachable]
streamlit (https://github.com/streamlit/streamlit)
+ lib/streamlit/runtime/caching/hashing.py: note: In member "_to_bytes" of class "_CacheFuncHasher":+ lib/streamlit/runtime/caching/hashing.py:409:34: error: No overload variant of "asdict" matches argument type "Type[DataclassInstance]" [call-overload]+ lib/streamlit/runtime/caching/hashing.py:409:34: note: Possible overload variants:+ lib/streamlit/runtime/caching/hashing.py:409:34: note: def asdict(obj: DataclassInstance) -> Dict[str, Any]+ lib/streamlit/runtime/caching/hashing.py:409:34: note: def [_T] asdict(obj: DataclassInstance, *, dict_factory: Callable[[List[Tuple[str, Any]]], _T]) -> _T |
Updated. Ready for pull. |
srittau
commented
May 24, 2024
Looking at the primer output: pydantic and streamlit are true positives. They use pytest is a false positive: It's basically complaining about this code (where ifis_dataclass(object) andnotisinstance(object, type): ...I'm unsure why that is. Maybe it's assuming that |
NeilGirdhar
commented
May 24, 2024
Definitely a weird choice. So, can the PR be pulled, or should we wait for the dependent projects to fix bugs? |
srittau
commented
May 25, 2024
I'm fine with merging this, despite the false positive, but I'll leave it open for a day or two if someone has an idea how to fix/work around it. |
NeilGirdhar
commented
May 25, 2024
Sounds good 😄 ! |
NeilGirdhar
commented
May 26, 2024
Thanks for the quick merge. If anyone has time, it may be worth mentioning that most of the other uses of |
I'm seeing a lot of new errors on work codebase from code like the case srittau mentions: Not sure if anyone has clever suggestions here |
DanielNoord
commented
Jul 24, 2024
I also see a lot of errors on this at work and it has been reported for I also see (what I would call) weird behaviour between Code sample in pyright playground importdataclassesfromtypingimportreveal_typedeffunc(x: object) ->None:
ifdataclasses.is_dataclass(x):
reveal_type(x)
print(dataclasses.asdict(x))Gives: Type of "x" is "DataclassInstance | type[DataclassInstance]"
Argument of type"DataclassInstance | type[DataclassInstance]" cannot be assigned to parameter "obj" of type"DataclassInstance"infunction"asdict"
Type "DataclassInstance | type[DataclassInstance]" is incompatible with type"DataclassInstance""__dataclass_fields__" is defined as a ClassVar in protocol (reportArgumentType)That seems fair and is in line with Which gives (on 1.11): Now I want to narrow Code sample in pyright playground importdataclassesfromtypingimportreveal_typedeffunc(x: object) ->None:
ifdataclasses.is_dataclass(x) andisinstance(x, object):
reveal_type(x)
print(dataclasses.asdict(x))This has no errors on
main.py:5: note: Revealed type is "Union[_typeshed.DataclassInstance, type[_typeshed.DataclassInstance]]"
main.py:6: error: Argument 1 to "asdict" has incompatible type"DataclassInstance | type[DataclassInstance]"; expected "DataclassInstance" [arg-type]
Found 1 error in 1 file (checked 1 source file)Even if it changes the behaviour I don't really understand why narrowing I don't want to point fingers as I have deep respect for the much more extensive knowledge that you maintainers have of the Python typing system but if I were to summarize this I would say:
|
erictraut
commented
Jul 24, 2024
@DanielNoord, I agree this looks like a bug in pyright. An |
There is a second bug in pyright here as well. This one is related to its overload matching logic. When overload matching is ambiguous because of an Mypy appears to have the same bug here. I've filed a separate bug in the mypy issue tracker. |
DanielNoord
commented
Jul 24, 2024
Thanks @erictraut (I must say I'm always amazed at the speed at which you and the I'm wondering what you think of the second issue I "identified". With the fixes marked for "addressed in next version" merged it seems we still don't have a good way to type guard an |
erictraut
commented
Jul 24, 2024
Once these bugs are fixed in pyright, you will be able to check for a TypedDict instance in a type-safe manner by verifying that importdataclassesdeffunc(x: object) ->None:
ifdataclasses.is_dataclass(x) andnotisinstance(x, type):
print(dataclasses.asdict(x))It's unfortunate that |
JelleZijlstra
commented
Jul 24, 2024
We could consider adding |
DanielNoord
commented
Jul 25, 2024
Thanks Eric, that does indeed seem to work in the playgrounds. I don't think I'll be able to convince people to add an additional dependency to our stack for such a small utility. It is indeed too bad that the API is like this. |
Fixes#9723