Reputation: 40918
I am using the yarl
library's URL
object.
It has a quasi-private attribute, ._val
, which is a urllib.parse.SplitResult
object but has no type annotation in yarl/__init__.pyi
. (Understandably so, if the developer does not want to formally make it part of the public API.)
However, I have chosen to use URL._val
at my own risk. A dummy example:
# urltest.py
from urllib.parse import SplitResult
from typing import Tuple
from yarl import URL
def foo(u: URL) -> Tuple[str, str, str]:
sr: SplitResult = u._val
return sr[:3]
But mypy
doesn't like this, because it complains:
$ mypy urltest.py
"URL" has no attribute "_val"
So, how can I, within my own project, "tack on" (or extend) an instance attribute annotation to URL
so that it can be used through the rest of my project? i.e.
from yarl import URL
URL._val: SplitResult
# ...
(mypy does not like this either; "Type cannot be declared in assignment to non-self attribute.")
I've tried creating a new stub file, in stubs/yarl/__init__.pyi
:
from urllib.parse import SplitResult
class URL:
_val: SplitResult
And then setting export MYPYPATH='.../stubs'
as described in stub files. However, this overrides, not extends, the existing annotations, so everything but ._val
throws an error:
error: "URL" has no attribute "with_scheme"
error: "URL" has no attribute "host"
error: "URL" has no attribute "fragment"
...and so on.
Upvotes: 14
Views: 7705
Reputation: 5653
Unfortunately, I don't think there's really a way of making "partial" changes to the type hints for some 3rd party library -- at least, not with mypy.
Actually, there is. Per PEP 561, the first place type checkers "SHOULD" look for stubs is in the $PATH
:
- Stubs or Python source manually put in the beginning of the path. Type checkers SHOULD provide this to allow the user complete control of which stubs to use, and to patch broken stubs/inline types from packages. In mypy the $MYPYPATH environment variable can be used for this.
Hence, fill $MYPYPATH
with a list of paths to extra directories where mypy
should look for stubs and put your fixes there. You "SHOULD" be able to simply overwrite the section that is failing with proper types. Per the mypy docs:
These stub files do not need to be complete! A good strategy is to use stubgen, a program that comes bundled with mypy, to generate a first rough draft of the stubs. You can then iterate on just the parts of the library you need.
You "SHOULDN'T" even have to use stubgen
, but try it out (you may have to use stubgen
if you need the other type hints from the package, though I'm not sure). Even if you do, worst case, run stubgen
on the file and overwrite the part of the stub that's broken.
Upvotes: 3
Reputation: 4883
One option is to create a new class, based on the class you want to 'extend'. I do this for Pandas DataFrame
objects when I want autocomplete for the data I'm working with.
import pandas as pd
class TitanicDataFrame(pd.DataFrame):
PassengerId: pd.Series
Survived: pd.Series
Name: pd.Series
Sex: pd.Series
Age: pd.Series
df: TitanicDataFrame = pd.read_csv('data/titanic.csv')
mean_age = df.Age.mean()
Note that the TitanicDataFrame
class isn't actually used (as a class), it's only used as the type (thus ignored at runtime).
Upvotes: 2
Reputation: 64228
Unfortunately, I don't think there's really a way of making "partial" changes to the type hints for some 3rd party library -- at least, not with mypy.
I would instead try one of the following three options:
Just # type: ignore
the attribute access:
def foo(u: URL) -> Tuple[str, str, str]:
sr: SplitResult = u._val # type: ignore
return sr[:3]
This type-ignore will suppress any error messages that are generated on that line. If you're going to take this approach, I'd also recommend running mypy with the --warn-unused-ignores
flag, which will report any redundant and unused # type: ignore
statements. It's unlikely this particular # type: ignore
will become redundant as mypy updates/as the stubs for your third party library updates, but it's a nice flag to enable just in general.
Talk to the maintainer of this library and see if they're willing to either add a type hint for this attribute (even if it's private), or to expose this information via some new API.
If it helps, there is some precedent for adding type hints even for private or undocumented attributes in Typeshed, the repository of types for the standard library -- see the "What to include" section in their contribution guidelines.
If the library maintainer isn't willing to add this attribute, you could always just fork the stubs for this library, make the change to the forked stubs, and start using that.
I would personally try solution 2 first, followed by solution 1, but that's just me.
Upvotes: 4
Reputation: 531948
One possibility is to simply ignore the type of u
for this assignment:
def foo(u: URL) -> Tuple[str, str, str, str]:
sr: SplitResult = typing.cast(typing.Any, u)._val
return sr[:3]
mypy
will assume you know what you are doing, and that u
has a _val
attribute with type str
.
Upvotes: 1