'Using mypy `NewType` with type aliases or protocols
I want to define a NewType like this:
from typing import NewType
from os import PathLike
AnyPath = PathLike[str] | str
RepoPath = NewType("RepoPath", AnyPath) # ERR: Argument 2 to NewType(...) must be subclassable (got "Union[PathLike[str], str]")
# RepoPath = NewType("RepoPath", PathLike[str]) # ERR: NewType cannot be used with protocol classes
Basically so that later I can pass a raw path "str" or a "pathlib.Path" to functions, and they can enforce this is specifically a path to a "Repo" rather than a random path. This is useful because there are a lot of paths and urls etc in my code and I don't want them to get mixed up (I don't want to use (Apps) hungarian notation especially either).
Is there a good way to get the type checker to do this for me?
Solution 1:[1]
Ok, here's a solution without Protocol - i.e. rather than accepting anything defining __fspath__ per os.PathLike, this code only allows concrete pathlib.Path or str.
Basically make two NewTypes then accept a union of them rather than a single NewType which is a union of subtypes.
from typing import NewType, overload, TypeAlias # py3.10 +
from pathlib import Path
#from os import PathLike # can't get this to work with NewType
AnyPath: TypeAlias = Path | str
RepoPathP = NewType("RepoPathP", Path)
RepoPathS = NewType("RepoPathS", str)
AnyRepoPath: TypeAlias = RepoPathP | RepoPathS
@overload
def RepoPath(path: str) -> RepoPathS: ...
@overload
def RepoPath(path: Path) -> RepoPathP: ...
def RepoPath(path: AnyPath) -> AnyRepoPath:
if isinstance(path, str):
return RepoPathS(path)
else:
return RepoPathP(path)
def foo(repo: AnyRepoPath) -> None:
print(repo)
foo("bad") # Argument 1 to "foo" has incompatible type "str"; expected "Union[RepoPathP, RepoPathS]"
foo(Path("still bad")) # Argument 1 to "foo" has incompatible type "Path"; expected "Union[RepoPathP, RepoPathS]"
foo(RepoPath("good")) # Pass
foo(RepoPath(Path("also good"))) # Pass
As mentioned this isn't perfect, as:
class MyCustomPath():
def __fspath__(self) -> str:
return r"C:/my/custom/dir"
path: os.PathLike = MyCustomPath() #fine, as expected
repo = RepoPath(path) #fails, since RepoPath accepts only str|Path, not str|PathLike
where ideally I'd like it to succeed
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Greedo |
