Last active
August 30, 2024 06:30
-
-
Save dimaqq/f308257af8c3c4ecbe6f84953eeaac90 to your computer and use it in GitHub Desktop.
Type tests using pyright
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from dataclasses import dataclass | |
from typing import Optional, Protocol, Type | |
import ops | |
class CallableWithCharmClassOnly(Protocol): | |
"""Encapsulate main function type for simple charms. | |
Supports: | |
- ops.main(SomeCharm) | |
- ops.main(charm_class=SomeCharm) | |
""" | |
def __call__(self, charm_class: Type[ops.charm.CharmBase]): ... | |
class CallableWithCharmClassAndStorageFlag(Protocol): | |
"""Encapsulate main function type for advanced charms. | |
Supports permutations of: | |
- ops.main(SomeCharm, False) | |
- ops.main(charm_class=SomeCharm, use_juju_for_storage=False) | |
""" | |
def __call__( | |
self, charm_class: Type[ops.charm.CharmBase], use_juju_for_storage: Optional[bool] = None | |
): ... | |
class CallableWithoutArguments(Protocol): | |
"""Bad charm code should be caught by type checker. | |
For example: | |
- ops.main() | |
""" | |
def __call__(self): ... | |
@dataclass | |
class MainCalls: | |
simple: CallableWithCharmClassOnly | |
full: CallableWithCharmClassAndStorageFlag | |
bad: CallableWithoutArguments | |
sink = MainCalls(None, None, None) # type: ignore | |
def top_level_import() -> None: | |
import ops | |
sink.full = ops.main | |
sink.full = ops.main.main | |
sink.simple = ops.main | |
sink.simple = ops.main.main | |
sink.bad = ops.main # type: ignore[assignment] | |
sink.bad = ops.main.main # type: ignore[assignment] | |
def submodule_import() -> None: | |
import ops.main | |
sink.full = ops.main # type: ignore # https://github.com/microsoft/pyright/issues/8830 | |
sink.full = ops.main.main | |
sink.simple = ops.main # type: ignore # https://github.com/microsoft/pyright/issues/8830 | |
sink.simple = ops.main.main | |
sink.bad = ops.main # type: ignore[assignment] | |
sink.bad = ops.main.main # type: ignore[assignment] | |
def import_from_top_level_module() -> None: | |
from ops import main | |
sink.full = main | |
sink.full = main.main | |
sink.simple = main | |
sink.simple = main.main | |
sink.bad = main # type: ignore[assignment] | |
sink.bad = main.main # type: ignore[assignment] | |
def import_from_submodule() -> None: | |
from ops.main import main | |
sink.full = main | |
sink.simple = main | |
sink.bad = main # type: ignore[assignment] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Caveats:
Supports main decorated by stdlib decorators, behaves weird for proxies like
def foo(*args, **kw)
.Works when type is correct.
Shows reasonable, albeit verbose error when type is wrong:
Call proxies:
Swallows fully untyped functions:
Complains correctly if some types are provided: