websockets icon indicating copy to clipboard operation
websockets copied to clipboard

Lazy import

Open CaedenPH opened this issue 3 years ago • 11 comments

In the __init__.py the function lazy_import is used to import local modules. The problem with this is that typehints are not showing up which makes coding the project a lot more difficult and less professional. I'm not entirely sure how lazy_import is importing these modules but whatever it is doing is not registering through my typechecker. I am using Pylance and Python visual studio code as my typechecker. image

CaedenPH avatar Jun 13 '22 17:06 CaedenPH

From https://websockets.readthedocs.io/en/stable/reference/index.html:

For convenience, many public APIs can be imported from the websockets package. However, this feature is incompatible with static code analysis. It breaks autocompletion in an IDE or type checking with mypy. If you’re using such tools, use the real import paths.

e.g. websockets.client.WebSocketClientProtocol

aaugustin avatar Jun 13 '22 19:06 aaugustin

websockets.client.WebSocketClientProtocol This still shows up as Any

CaedenPH avatar Jun 13 '22 19:06 CaedenPH

It works for me. I suspect it's an issue with your mypy setup.

myk@mYk:websockets/src ‹main›$ cat issue1183.py
import websockets.client

reveal_type(websockets.client.WebSocketClientProtocol)
myk@mYk:websockets/src ‹main›$ mypy issue1183.py
issue1183.py:3: note: Revealed type is "def (*, logger: Union[logging.Logger, logging.LoggerAdapter[Any], None] =, origin: Union[websockets.typing.Origin, None] =, extensions: Union[typing.Sequence[websockets.extensions.base.ClientExtensionFactory], None] =, subprotocols: Union[typing.Sequence[websockets.typing.Subprotocol], None] =, extra_headers: Union[websockets.datastructures.Headers, typing.Mapping[builtins.str, builtins.str], typing.Iterable[Tuple[builtins.str, builtins.str]], websockets.datastructures.SupportsKeysAndGetItem, None] =, **kwargs: Any) -> websockets.legacy.client.WebSocketClientProtocol"
Success: no issues found in 1 source file

aaugustin avatar Jun 13 '22 21:06 aaugustin

Wait, you're using pylance, not mypy. I don't know much about pylance. I still suspect that it's failing to import websocktes.

aaugustin avatar Jun 13 '22 21:06 aaugustin

Wait, you're using pylance, not mypy. I don't know much about pylance. I still suspect that it's failing to import websocktes.

Pylance is very popular when it comes to python development, so I doubt that I am the only person with this problem. Perhaps you could do some testing or just import the modules normally in the __init__?

CaedenPH avatar Jun 13 '22 21:06 CaedenPH

I will do some testing before reverting the conditional import mechanism. (FYI it's designed to provide an implementation on top of trio without making trio a hard dependency of websockets. But that's a few years in the future, probably...)

Could you provide reproduction instructions please?

I expect something along these lines:

  • Create a virtualenv and install websockets
    • What are you using? poetry? python -m venv?
  • Open a project in Visual Studio Code
    • Any specific setup? Extensions to install/enable?
  • Tell Visual Studio Code to load the virtualenv
    • Not sure if anything specific is needed here?

aaugustin avatar Jun 14 '22 06:06 aaugustin

I will do some testing before reverting the conditional import mechanism. (FYI it's designed to provide an implementation on top of trio without making trio a hard dependency of websockets. But that's a few years in the future, probably...)

Could you provide reproduction instructions please?

I expect something along these lines:

  • Create a virtualenv and install websockets

    • What are you using? poetry? python -m venv?
  • Open a project in Visual Studio Code

    • Any specific setup? Extensions to install/enable?
  • Tell Visual Studio Code to load the virtualenv

    • Not sure if anything specific is needed here?

Sorry for the late response. Visual studio code is more of a text based editor than an ide so no complicated steps are required. Just install visual studio code and the python extension which will also install pylance. Then just install websockets with pip (no venv required) and view the type hints. Pylance is a great type checker and I have had no problems with any modules except for this one.

CaedenPH avatar Jun 15 '22 21:06 CaedenPH

Pylance is a slight edit of pyright, the note in docs about:

For convenience, many public APIs can be imported from the websockets package. However, this feature is incompatible with static code analysis. It breaks autocompletion in an IDE or type checking with mypy. If you’re using such tools, use the real import paths.

which matches this behaviour, I don't see the difference.

This package can just import directly from the other modules though with

# __init__.py

from .file import Member

ooliver1 avatar Jul 10 '22 17:07 ooliver1

Thanks to PEP 690, I will be able drop the lazy imports mechanism when I drop support for Python < 3.12. Given that Python 3.7 is still supported and that Python gets a minor version every other year (I believe?) that's about 10 years from now :-/

aaugustin avatar Jul 10 '22 20:07 aaugustin

@CaedenPH This works for me:

Screenshot 2022-07-29 at 08 58 25

I suspect your original problem was the same as this one (which I'm demonstrating with the standard library to rule out websockets):

Screenshot 2022-07-29 at 08 59 42

You need to import websockets.client if you want autocompletion inside this module, not just import websockets.


Yes, for backwards-compatibility reasons, there are "magic" imports in websockets. If I could send a message to myself-from-eight-years-ago, I wouldn't expose all API in the root package. Now it's too late :-(

aaugustin avatar Jul 29 '22 07:07 aaugustin

Reopening to check if I can clarify the documentation further or make it easier to discover.

aaugustin avatar Jul 29 '22 07:07 aaugustin

Thanks to PEP 690, I will be able drop the lazy imports mechanism when I drop support for Python < 3.12. Given that Python 3.7 is still supported and that Python gets a minor version every other year (I believe?) that's about 10 years from now :-/

I don't see how that mindset even works, I said there is no need for "lazy imports", you can just use normal imports, no? It has the same maintainability as "lazy imports", just does not need to be so special and actually works with typing.

ooliver1 avatar Oct 09 '22 14:10 ooliver1

Yes, I couldn't agree more. Just use normal imports. This is what the documentation that I just added recommends.

aaugustin avatar Oct 09 '22 14:10 aaugustin

Then what is https://github.com/aaugustin/websockets/blob/main/src/websockets/init.py#L56 still there for? Just use relative imports to add them to the main package

ooliver1 avatar Oct 09 '22 14:10 ooliver1

It's that way because:

  1. Long ago the library was simple and small and it was OK to cram everything in the toplevel namespace.
  2. Now it's growing to support several implementations — Sans-I/O, asyncio - 2 versions, sockets & threads, trio, perhaps curio. Some of this work is public, other parts aren't. But I don't want trio to be a hard dependency of websockets.
  3. I want to keep backwards compatibility for importing from the root package.

aaugustin avatar Oct 09 '22 14:10 aaugustin

Ah, one more reason — as the library was undergoing significant restructuring, managing renames and deprecation warnings in a central place was much easier.

aaugustin avatar Oct 09 '22 14:10 aaugustin

439dafa6 should fix it going forwards.

aaugustin avatar Oct 01 '23 15:10 aaugustin