-
Notifications
You must be signed in to change notification settings - Fork 62
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
Fix CloudPathMeta.__call__ return type #330
Fix CloudPathMeta.__call__ return type #330
Conversation
Since pyright==1.1.307, instances of the subclasses of CloudPath stopped being recognized as instances of their own class; a possible fix is to type hint CloudPathMeta.__call__, but it creates a host of mypy errors
@ringohoffman Thanks for looking in to this. Are these new problems because of #327? Do things work as expected on an earlier release? Looking at that PR may help with the mypy issues you are seeing. |
Fix mypy errors by making use of object.__new__ and type assertions
Incompatible return value type (got "CloudPath", expected "CloudPathT"); not a clear fix here without a more general refactor
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## master #330 +/- ##
========================================
- Coverage 94.3% 93.9% -0.4%
========================================
Files 22 22
Lines 1466 1473 +7
========================================
+ Hits 1383 1384 +1
- Misses 83 89 +6
|
@pjbull let me know how this works for you, it seems to give us the better behavior than we saw before: import cloudpathlib
# pyright 1.1.306 (master)
x = cloudpathlib.S3Path("s3://bucket/key")
# reveal_type(x) # pyright:Type of "x" is "S3Path"
y = cloudpathlib.CloudPath(x)
# reveal_type(y) # pyright: Type of "y" is "CloudPath"
z = cloudpathlib.CloudPath("s3://bucket/key")
# reveal_type(z) # pyright: Type of "z" is "CloudPath"
# pyright 1.1.307 (master)
x = cloudpathlib.S3Path("s3://bucket/key")
# reveal_type(x) # pyright: Type of "x" is "CloudPath | Unknown | Self@__new__"
# reveal_type(x) # mypy: Revealed type is "cloudpathlib.s3.s3path.S3Path"
y = cloudpathlib.CloudPath(x)
# reveal_type(y) # pyright: Type of "y" is "CloudPath | Unknown | Self@__new__"
# reveal_type(y) # mypy: Revealed type is "cloudpathlib.cloudpath.CloudPath"
z = cloudpathlib.CloudPath("s3://bucket/key")
# reveal_type(z) # pyright: Type of "z" is "CloudPath | Unknown | Self@__new__"
# reveal_type(z) # mypy: Revealed type is "cloudpathlib.cloudpath.CloudPath"
# pyright 1.1.307 (this PR)
x = cloudpathlib.S3Path("s3://bucket/key")
# reveal_type(x) # pyright: Type of "x" is "S3Path"
# reveal_type(x) # mypy: Revealed type is "cloudpathlib.s3.s3path.S3Path"
y = cloudpathlib.CloudPath(x)
# reveal_type(y) # pyright: Type of "y" is "S3Path"
# reveal_type(y) # mypy: Revealed type is "cloudpathlib.cloudpath.CloudPath"
z = cloudpathlib.CloudPath("s3://bucket/key")
# reveal_type(z) # pyright: Type of "z" is "CloudPath"
# reveal_type(z) # mypy: Revealed type is "cloudpathlib.cloudpath.CloudPath" It's probably worth pointing out that
I do feel like this pattern is pretty abnormal and I wonder if we couldn't employ a more standard design that doesn't involve this custom metaclass. Alternate constructor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ringohoffman! This change looks good except removing one assert
. When that is done, I'll bring it over to a local branch to run the cloud tests.
#328 is tracking the mypy
issue as you note, which seems like an actual bug with mypy
: python/mypy#14122 🤞 for an upstream fix there.
I do feel like this pattern is pretty abnormal and I wonder if we couldn't employ a more standard design that doesn't involve this custom metaclass. Alternate constructor classmethods might be a slightly more standard and better supported approach: https://stackoverflow.com/a/141777/7059681. We could also write a decorator to copy the docstrings of pathlib methods as well.
May be worth opening an issue to discuss this, but my general opinion is that we shouldn't sacrifice what feels like a ergonomic API design because of type hinting. It's nice to have CloudPath(p)
dispatch to the right subclass no matter the value of p
, and I wouldn't be in favor of changing it just because the type hinting can go wonky. There's no implementation in which a static type checker could ever know the dispatched class at statically. If people are only dealing with one provider, I would recommend they use the provider-specific class (e.g., S3Path
) if they want type checking guarantees. Otherwise, best case scenario is they get the generic CloudPath
hints.
This use case is the reason that metaclasses exist as a language feature, so I don't think it is really abnormal—just a tension between features for a dynamic language and static type hinting. I'm not sure there's another implementation that let's us have our cake and eat it too, but open to discussing options if there are.
Also make issubclass(cls, CloudPath) explicit by raising TypeError
@pjbull I went ahead and applied your suggestion. I also found this example of metaclass hinting that gives the same behavior as before without any needing any I think the latest way is the best way to hint this method. |
For some reason this leads to mypy type-var errors in the meta __call__ (probably erroneous)
@pjbull any thoughts about this latest change? |
* Fix CloudPathMeta.__call__ return type Since pyright==1.1.307, instances of the subclasses of CloudPath stopped being recognized as instances of their own class; a possible fix is to type hint CloudPathMeta.__call__, but it creates a host of mypy errors * Refactor CloudPathMeta.__call__ Fix mypy errors by making use of object.__new__ and type assertions * Add type ignore on CloudPathMeta.__call__ return Incompatible return value type (got "CloudPath", expected "CloudPathT"); not a clear fix here without a more general refactor * Refactor CloudPathMeta.__call__ signature to use overload Also make issubclass(cls, CloudPath) explicit by raising TypeError * Run black * Change back __init__ to use Self type For some reason this leads to mypy type-var errors in the meta __call__ (probably erroneous) Co-authored-by: Matthew Hoffman <matthew@protopia.ai>
Since
pyright==1.1.307
, instances of the subclasses ofCloudPath
stopped being recognized as instances of their own class; a possible fix is to type hintCloudPathMeta.__call__
, but it creates a host ofmypy
errors (5)...Here is the typing bug starting in the newest version of pyright and the fix with this proposed change:
These are the mypy bugs that arise:
I'm really not sure how to begin to solve these... it seems like they were there to begin with but weren't being raised (due to the lack of typing present on the signature, I assume)... I could definitely use some help in solving these. Would we consider
type: ignore
?