Can I use a typed function definition from one stub file in another? E.g., I have my __init__.pyi and also extractor.pyi.
We want to reference some methods from extractor.py, so we keep compatibility and not redefine types.
In extractor.pyi I have:
def _func(arg: str) -> str: ...
Now I use in __init__.pyi:
from .extractor import _func
However, my linter is complaining.
Is it, in general, possible to import stubs from other .pyi files? If yes, how should I do it correctly?
Related
I tried typing.IO as suggested in Type hint for a file or file-like object?, but it doesn't work:
from __future__ import annotations
from tempfile import NamedTemporaryFile
from typing import IO
def example(tmp: IO) -> str:
print(tmp.file)
return tmp.name
print(example(NamedTemporaryFile()))
for this, mypy tells me:
test.py:6: error: "IO[Any]" has no attribute "file"; maybe "fileno"?
and Python runs fine. So the code is ok.
I don't think this can be easily type hinted.
If you check the definition of NamedTemporaryFile, you'll see that it's a function that ends in:
return _TemporaryFileWrapper(file, name, delete)
And _TemporaryFileWrapper is defined as:
class _TemporaryFileWrapper:
Which means there isn't a super-class that can be indicated, and _TemporaryFileWrapper is "module-private". It also doesn't look like it has any members that make it a part of an existing Protocol * (except for Iterable and ContextManager; but you aren't using those methods here).
I think you'll need to use _TemporaryFileWrapper and ignore the warnings:
from tempfile import _TemporaryFileWrapper # Weak error
def example(tmp: _TemporaryFileWrapper) -> str:
print(tmp.file)
return tmp.name
If you really want a clean solution, you could implement your own Protocol that includes the attributes you need, and have it also inherit from Iterable and ContextManager. Then you can type-hint using your custom Protocol.
* It was later pointed out that it does fulfil IO, but the OP requires attributes that aren't in IO, so that can't be used.
I am writing type stubs for a proprietary Python platform. The platform often defines classes in some modules and re-export them from other modules. I need my type stubs to reflect that the type exists in more than one file.
For example
_foo.py:
class SomeType:
# Implementation here
foo.py:
from _foo import SomeType
How do I write type stubs for these files?
I have tried this:
_foo.pyi:
class SomeType:
...
foo.pyi:
from _foo import SomeType
But it doesn't work, mypy throws this error:
error: name 'foo.SomeType' is not defined
Is there a way to make this work without copy-pasting the type stub into both files?
Please check out the import section of type stub document, it should provide the answer for what you needed.
__all__ is actually useful if you want to export private variables / types (prefixed with underscore). If you really don't like that, you can use the import as form:
from _foo import SomeType as SomeType
It guarantees SomeType to be re-exported.
After some trial and error, I noticed this works:
foo.pyi:
from _foo import SomeType
__all__ = ["SomeType"]
I am not thrilled with this though, as it looks like it could have side effects, but for now it works.
I'm trying to implement a facade pattern to access two different file types, but I keep on running into circular dependencies. Is this common and if so what is the standard way to avoid it?
I have two file-types (A and B) in separate modules which are accessed by a facade in another separate interface.py module. The facade module needs to import the FileType classes from each module in order to return the objects, and also implements a method determine_file_type(path) and a custom error class IncorrectFileType.
I now wish to add an add_data_from_another_file method to FileTypeA. The first thing it needs to do is determine what type of file it's adding data from, but it can't access the interface.determine_file_type method without creating a circular dependency. I'm also unable to raise the IncorrectFileType error from within either file_type_a,b module for the same reason.
## Module: interface.py
from file_type_a import FileTypeA
from file_type_b import FileTypeB
def get_file(path):
if determine_type(path) == "typeA":
return FileTypeA(path)
elif determine_type(path) == "typeB":
return FileTypeB(path)
def determine_file_type(path):
...
return file_type
class IncorrectFileTypeError(ValueError):
pass
## Module: file_type_a.py
class FileTypeA():
def add_data_from_another_file(self, path):
file_type = determine_file_type(path) ### CAN'T IMPORT THIS
if file_type == "typeB":
raise IncorrectFileTypeError() ### CAN'T IMPORT THIS
## Module: file_type_b.py
class FileTypeB():
pass
One solution could be to implement the determine_file_type as a staticmethod on a AbstractFileType class but this doesn't help me if I need to raise an exception in one of the concrete classes. (It also feels like it might be messy in my real example, but that's probably a separate issue!)
This feels like a classic use of the Facade pattern, so what is the obvious trick I'm missing here?
I want to know how I can add type hints (for PyCharm IDE code completion support) to a method def links(self) -> List[str] that I monkey-patched to an existing module's class:
My function
def issue_links(self) -> List[str]:
links = []
# ...
return links
Monkey-patching the function to the Issue class of python-jira
from jira.resources import Issue
# {...} my defined function code is somewhere here
Issue.links = issue_links
Now I have the problem that PyCharm obviously not recognise this method when I call it on an Issue object. I tried to follow PEP 484 Stub Files and using the typing module in a .pyi file to get the IDE to find the definition of my monkey-patched function.
Issue.pyi
from typing import List
class Issue:
def links(self) -> List[str]: ...
However, it won't work. My assumption is that the file structure is somehow not correct:
File Locations
jira-python module >> site packages (downloaded with pip install jira)
myFile.py >> /temp/myFile.py
Issue.pyi >> /temp/jira/resources/Issue.pyi folder
Say you have something like this:
class Point:
def compute_line(self, point: 'Point') -> 'Line':
# computes line connecting self to point
...
class Line:
def compute_intersection(self, line: 'Line') -> 'Point':
# computes point formed from intersection of self and line
...
PyCharm can parse the str typing hints to auto-complete for outputs of either method.
But what if I want to put Point and Line in separate files? Is there some way to still get auto-completion?
The problem is that Line isn't a name for a type at all within point.py, and Point isn't a name for a type within line.py. Putting it in quotes doesn't help; that just delays when the string is resolved. It still has to eventually resolve to a type, so you're just delaying things to a point where they resolve to a NameError, which doesn't help thing.
If you do an import line in Point and import point in Line, then point.Point and line.Line become types, which solves that problem. But of course it creates a new problem: a circular import.
In some cases, as explained in PEP 484, you can resolve that just by doing a conditional "static-typing-only" import, like this:
import typing
if typing.TYPE_CHECKING:
import line
… and then using 'line.Line' in the type annotation.
See the docs on TYPE_CHECKING for more details. (In particular, if you need compatibility with Python 3.5 before 3.5.2, this will give you a NameError instead of False at runtime, which is a pain… but not many people need to run on 3.5.1.)
If that doesn't solve the problem, you need some scheme to avoid the circular import, the same way you would for a runtime circular import.
For example, you can go with the traditional "interface" solution where the first type depends on the second, but the second doesn't depend on the first, it depends only on a superclass for the first.
Typically, this superclass would be an ABC, to indicate that it's serving only as an interface to some real class defined elsewhere. (And that way, you know, and Python and your static checker can enforce, that anything that types as the ABC must be an instance of one of the concrete subclasses of that ABC—of which there's only one.)
# pointbase.py
import abc
class PointBase(abc.ABC):
#abc.abstractmethod
def method_that_does_not_need_line(self):
pass
# point.py
import pointbase
import line
class Point(pointbase.PointBase):
def method_that_does_not_need_line(self):
do_stuff()
def method_that_does_need_line(self, line: line.Line):
do_stuff(line)
# line.py
import pointbase
class Line:
def method_that_needs_point(self, point: pointbase.PointBase):
do_stuff(point)
You can have an assertion for that type, PyCharm will recognize it as the type you asserted it to be.
assert isinstance(point, Point)