Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly annotate a ContextManager in PyCharm?

How can I annotate the yield type of a contextmanager in PyCharm so that it properly guesses the type of the value used in the with clauses - just as it guesses that the f created in with open(...) as f is a file?

For example, I have a context manager like this:

@contextlib.contextmanager
def temp_borders_file(geometry: GEOSGeometry, name='borders.json'):
    with TemporaryDirectory() as temp_dir:
        borders_file = Path(dir) / name
        with borders_file.open('w+') as f:
            f.write(geometry.json)
        yield borders_file

with temp_borders_file(my_geom) as borders_f:
    do_some_code_with(borders_f...)

How do I let PyCharm know that every borders_f created like this is a pathlib.Path (and thus enable the autocompletion for the Path methods on border_f)? Of course, I can make a comment like # type: Path after every with statement, but it seems that this can be done by properly annotating temp_border_file.

I tried Path, typing.Iterator[Path] and typing.Generator[Path, None, None] as the return type of temp_border_file, as well as adding # type: Path on borders_file within the context manager's code, but it seems like it doesn't help.

like image 608
Aleph Aleph Avatar asked Mar 17 '18 11:03

Aleph Aleph


People also ask

When using the Contextmanager from the Contextlib module What decorator do you use?

ContextDecorator makes it possible to use a context manager in both an ordinary with statement and also as a function decorator. Note that there is one additional limitation when using context managers as function decorators: there's no way to access the return value of __enter__() .

What is Contextmanager Python?

A context manager usually takes care of setting up some resource, e.g. opening a connection, and automatically handles the clean up when we are done with it. Probably, the most common use case is opening a file. with open('/path/to/file.txt', 'r') as f: for line in f: print(line)

What is Contextlib?

Overview. The contextlib module of Python's standard library provides utilities for resource allocation to the with statement. The with statement in Python is used for resource management and exception handling. Therefore, it serves as a good Context Manager.

Which module helps in creating a context manager using decorator Contextmanager?

We can simply make any function as a context manager with the help of contextlib. contextmanager decorator without having to write a separate class or __enter__ and __exit__ functions.


2 Answers

I believe you can use ContextManager from typing, e.g.:

import contextlib
from typing import ContextManager
from pathlib import Path


@contextlib.contextmanager
def temp_borders_file() -> ContextManager[Path]:
    pass


with temp_borders_file() as borders_f:
    borders_f  # has type Path here
like image 163
Pavel Karateev Avatar answered Oct 10 '22 01:10

Pavel Karateev


This is a current PyCharm issue: PY-36444

A workaround for the issue is to rewrite an example code of:

from contextlib import contextmanager

@contextmanager
def generator_function():
    yield "some value"

with generator_function() as value:
    print(value.upper())  # no PyCharm autocompletion

to

from contextlib import contextmanager
from typing import ContextManager

def wrapper() -> ContextManager[str]:
    @contextmanager
    def generator_function():
        yield "some value"

    return generator_function()

with wrapper() as value:
    print(value.upper())  # PyCharm autocompletion works

There is also an easier workaround of annotating the return type with ContextManager[str] but there are multiple reasons against this:

  • mypy will correctly emit errors for this annotation, as described in more detail in the PyCharm issue.
  • this is not guaranteed to work in the future because PyCharm will hopefully fix the issue and therefore break this workaround
like image 26
Thomas Avatar answered Oct 10 '22 01:10

Thomas