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
[Feature Request] Support for os.PathLike
#314
Comments
...heh. Oh, S = TypeVar('S', bound=Union[str, bytes])
class PathLike(Generic[S], extra=os.PathLike): # or Protocol[S] in future
def __fspath__(self) -> S:
... Given that, @beartype can effectively type-check a type hint
isinstance(obj, os.PathLike) and
isinstance(obj.__fspath__(), T) That is to say:
Complications Arise, YoOf course, there are complications. The >>> from os import PathLike
>>> PathLike[list[int]]
PathLike[list[int]] # <-- uhh. wut? this makes no sense, Python. *facepalm* Supporting
That's it. I think? Still, that's pretty annoying. That's yet more bureaucratic minutiae that @beartype has to manually implement, because CPython refused to do its job here.
Definitely Feasible, BroThat's... definitely feasible. Rejoice, everyone. Sadly, I also kinda have no volunteer time to do this. It doesn't help that CPython itself fails to validate For now, would you just mind dropping the subscription StrPath = str | PathLike That should absolutely work out-of-the-box without requiring any changes to @beartype itself.
@beartype definitely supports ABCs like |
Thank you for your kind comment @leycec!
The import os
from pathlib import Path
from beartype import beartype
StrPath = str | os.PathLike
@beartype
def foo(path: StrPath):
print(os.fspath(path))
foo("test") # it works (obviously)
foo(Path("test")) # okay
class MyPath:
def __init__(self, path):
self.path = path
def __fspath__(self):
return self.path
foo(MyPath(b"test")) # it also works... So, I implemented a custom from typing import TypeVar, Protocol, runtime_checkable
T = TypeVar("T", str, bytes)
@runtime_checkable
class PathLike(Protocol[T]):
def __fspath__(self) -> T:
...
StrPath = str | PathLike[str]
@beartype
def foo(path: StrPath):
print(os.fspath(path))
foo("test")
foo(Path("test"))
# foo(b"test") # `foo` blocked `bytes` (finally!)
class BytePath:
def __init__(self, path: str):
self.path = bytes(path, encoding="UTF-8")
def __fspath__(self) -> bytes:
return self.path
foo(BytePath("test")) # no..! it works! I know that all methods of the |
Yuppers. The custom
Tragically, not at the moment. When @beartype fails to do what you want out-of-the-box, extra validators save you from @beartype's personal failings. Validators save you from us. Thankfully, extra validators would allow you to both have your typing cake and eat it too. Since there are only two meaningful subscriptions of Behold! from beartype.vale import Is
from os import PathLike
from typing import TYPE_CHECKING, Annotated
# PathLike[str], but actually supported by everybody.
if TYPE_CHECKING:
PathLikeStr = PathLike[str]
else:
PathLikeStr = Annotated[PathLike, Is[
lambda path_like: isinstance(path_like.__fspath__(), str)]]
# This is the way.
StrPath = str | PathLikeStr Now, let's prove that actually does what @leycec promises that does: # Import boring stuff.
>>> from beartype import beartype
>>> from os import fspath
>>> from pathlib import Path
# Prove that good always prevails.
>>> @beartype
... def foo(path: StrPath) -> str:
... return fspath(path)
>>> foo("test")
'test' # <-- good.
>>> foo(Path("more test"))
'more test' # <-- GOOD.
# Now for the coup de grace.
>>> class BytePath(object):
... def __init__(self, path: str):
... self.path = bytes(path, encoding="UTF-8")
...
... def __fspath__(self) -> bytes:
... return self.path
>>> foo(BytePath("ugly test"))
Traceback (most recent call last): # <-- Suck it, "BytePath". Just suck it.
File "/home/leycec/tmp/mopy.py", line 31, in <module>
foo(BytePath("ugly test"))
File "<@beartype(__main__.foo) at 0x7f3acf351c60>", line 32, in foo
beartype.roar.BeartypeCallHintParamViolation: Function __main__.foo() parameter path=<__main__.BytePath object at 0x7f3acec014c0> violates type hint typing.Union[str, typing.Annotated[os.PathLike, Is[lambda path_like: isinstance(path_like.__fspath__(), str)]]], as <class "__main__.BytePath"> <__main__.BytePath object at 0x7f3acec014c0>:
* Not str.
* <class "__main__.BytePath"> <__main__.BytePath object at 0x7f3acec014c0> violates validator Is[lambda path_like: isinstance(path_like.__fspath__(), str)]:
False == Is[lambda path_like: isinstance(path_like.__fspath__(), str)]. Typing cake: it tastes delicious. 😋 |
Oh... this is the best way in the current situation... |
You're most welcome. As a gentle reminder to myself to eventually implement this properly for everybody, would you mind if I quietly reopen this feature request? With any luck, I'll tackle stuff like this in 2024. Let the hype train begin! 🎆 |
Sure! Thank you for your attention! 🙏 |
This commit is the first in a commit chain adding support for **unrecognized subscripted builtin type hints** (i.e., C-based type hints that are *not* isinstanceable types, instantiated by subscripting pure-Python origin classes subclassing the C-based `types.GenericAlias` superclass such that those classes are unrecognized by @beartype and thus *not* type-checkable as is), en-route to partially resolving both feature requests #219 kindly submitted by extreme Google X guru @patrick-kidger (Patrick Kidger) *and* #314 kindly submitted by adorable black hat ML fiend @kaparoo (Jaewoo Park). Specifically, this commit adds support for: * Internally detecting such hints. * Internally reducing such hints to their unsubscripted origin classes (which are almost always pure-Python isinstanceable types and thus type-checkable as is). Naturally, nothing is tested; everything is suspicious. (*Really boring jam in a boreal jamboree!*)
This commit is the next in a commit chain adding support for **unrecognized subscripted builtin type hints** (i.e., C-based type hints that are *not* isinstanceable types, instantiated by subscripting pure-Python origin classes subclassing the C-based `types.GenericAlias` superclass such that those classes are unrecognized by @beartype and thus *not* type-checkable as is), partially resolving both feature requests #219 kindly submitted by extreme Google X guru @patrick-kidger (Patrick Kidger) *and* #314 kindly submitted by adorable black hat ML fiend @kaparoo (Jaewoo Park). Specifically, this commit finalizes working support for shallowly type-checking PEP-noncompliant type hints bundled with the `typeshed` -- including: * `os.PathLike[...]` type hints. * `weakref.weakref[...]` type hints. This commit also extensively tests that @beartype now shallowly type-checks `os.PathLike[...]` type hints. (*Unitary Unitarians are IT units!*)
Partially resolved by fe5b23d. @beartype's upcoming 0.17.0 release ...to be released before Santa Bear Claws gives us all the video games shallowly type-checks In fact, @beartype 0.17.0 shallowly type-checks all weirdo non-standard type hints in Python's This means that your above verbose definition of from beartype.vale import Is
from os import PathLike
from typing import Annotated
# PathLike[str], but actually supported by everybody.
PathLikeStr = Annotated[PathLike[str], Is[ # <-- much simpler, much terser, much gooder
lambda path_like: isinstance(path_like.__fspath__(), str)]]
# This is the way.
StrPath = str | PathLikeStr Rejoice! Santa Bear Claws has heard your prayers. 🎅 |
Brilliant! I miss Santa already 🎅 |
This commit is the next in a commit chain adding support for **unrecognized subscripted builtin type hints** (i.e., C-based type hints that are *not* isinstanceable types, instantiated by subscripting pure-Python origin classes subclassing the C-based `types.GenericAlias` superclass such that those classes are unrecognized by @beartype and thus *not* type-checkable as is), partially resolving both feature requests #219 kindly submitted by extreme Google X guru @patrick-kidger (Patrick Kidger) *and* #314 kindly submitted by adorable black hat ML fiend @kaparoo (Jaewoo Park). Specifically, this commit extensively tests that @beartype now shallowly type-checks `weakref.ref[...]` type hints. (*Sanguinary exsanguination extinguishes a distinguished canary!*)
This commit is the next in a commit chain adding support for **unrecognized subscripted builtin type hints** (i.e., C-based type hints that are *not* isinstanceable types, instantiated by subscripting pure-Python origin classes subclassing the C-based `types.GenericAlias` superclass such that those classes are unrecognized by @beartype and thus *not* type-checkable as is), partially resolving both feature requests #219 kindly submitted by extreme Google X guru @patrick-kidger (Patrick Kidger) *and* #314 kindly submitted by adorable black hat ML fiend @kaparoo (Jaewoo Park). Specifically, this commit resolves a Python 3.9-specific issue concerning `weakref.ref[...]` type hints. Notably, the `weakref.ref` class improperly advertises itself as a builtin type under *only* Python 3.9. @beartype now correctly detects and circumvents this misadvertisement. (*Considerable concern sidles idly by Admiral Admirable!*)
TL;DR
How can I make this code work?
Content
There is a type alias
StrPath
defined in typeshed asBecause the
typeshed
is not accessible at runtime, I tried to defineStrPath
myself and use it with@beartype
like this:The function
foo
worked as expected for "test.py" (string), but it raisedbeartype.roar.BeartypeDecorHintNonpepException
forPath("test.py")
which is evaluated - at least inmypy
- asPathLike[str]
:I presume this is because
os.PathLike
uses the way below to provide an interface for Generic:Therefore I hope beartype supports ABCs like
os.PathLike
. Currently, I useStrPath = str | Path
and there is no problem in 99% of my cases, but it would be better to base it onos.PathLike
.The text was updated successfully, but these errors were encountered: