- 
      
 - 
        
Save datavudeja/c1962a910f74d5f334a4b58899b2de24 to your computer and use it in GitHub Desktop.  
    Shared via mypy Playground
  
        
  
    
      This file contains hidden or 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
    
  
  
    
  | # This example is copied verbatim from one of the codebases I contribute to. | |
| # Unfortunately, I can't copy it, so I tried to extract the scenario as it was, | |
| # as an example that I could share publicly. | |
| # | |
| # I'm sure the example could be simplified, but I thought it would be better to | |
| # show it as it is, and leave any conclusions to whomever is going to read it. | |
| # | |
| from typing import Union, TypeVar, Callable | |
| from typing_extensions import TypeAlias, Literal, overload | |
| from dataclasses import dataclass | |
| _T = TypeVar("_T") | |
| _U = TypeVar("_U") | |
| SomeEnumStr: TypeAlias = Union[ | |
| Literal["value-a"], | |
| Literal["value-b"], | |
| Literal["value-c"], | |
| ] | |
| @dataclass | |
| class MyConfig: | |
| value: SomeEnumStr | None | |
| @overload | |
| def _required( | |
| value: _T | None, | |
| ) -> _T | None: ... | |
| @overload | |
| def _required( | |
| value: _T | None, | |
| *, | |
| default: _U, | |
| ) -> _T | _U: ... | |
| @overload | |
| def _required( | |
| value: _T | None, | |
| *, | |
| default_factory: Callable[[], _U], | |
| ) -> _T | _U: ... | |
| # NOTE: Please ignore the "missing return statements", I just want to show how | |
| # types are deduced later on in the example. Focus here is on the returned | |
| # static types. In the original codebase, all of the functions are implemented. | |
| # | |
| def _required( | |
| value: _T | None, | |
| *, | |
| default: _U | None = None, | |
| default_factory: Callable[[], _U] | None = None, | |
| ) -> _T | _U: ... | |
| def ensure_optional_some_enum_str(value: Any, default: _T) -> SomeEnumStr | _T: ... | |
| def get_some_cli_arg() -> str | None: ... | |
| def get_sys_config() -> MyConfig: ... | |
| # Suppose I have a very simple routine, where a configuration can come from | |
| # the CLI (as an argument, parsed somewhere), from a system-dependent location, | |
| # or from a fixed hard-coded value. Precedence is given as CLI, System and | |
| # hardcoded. This `_required` method is used as a helper, to identify when the | |
| # value is not given (in this case, modeled simply as a `None` value). | |
| # | |
| # `ensure_optional_some_enum_str` guarantees that the value provided at the CLI | |
| # respects the ones valid for `SomeEnumStr`. If the CLI's value is `None`, it | |
| # just returns `None`. | |
| # | |
| cli_config = ensure_optional_some_enum_str(get_some_cli_arg(), None) | |
| sys_config = get_sys_config().value | |
| default_config: SomeEnumStr = "value-c" | |
| # `value` is supposed to be the result of the resolution process I described | |
| # earlier. It should have type `SomeEnumStr`. | |
| # | |
| value = _required( | |
| cli_config, | |
| default_factory=lambda: _required( | |
| sys_config, | |
| default=default_config, | |
| ), | |
| ) | |
| # However, according to PyRight, the type of `value` is just `str`. | |
| # | |
| # My expectation is the same as MyPy. None of the values involved are raw strings. | |
| # All of them are narrowed to `SomeEnumStr`, or to `SomeEnumStr | None`. | |
| # Somewhere in the middle of static checking, some value is being widened to `str` | |
| # for some reason -- and I suppose it's happening at the method parameter type | |
| # checking, either on `default` or `default_factory`. | |
| # | |
| # Perhaps it's the usage of `TypeVar` that's causing this issue? I could force | |
| # a `bound` (e.g. `_T = TypeVar("_T", bound=SomeEnumStr)`), but that would go | |
| # counter to the objective of the TypeVar in the first place, which is to allow | |
| # `_required` to receive a default (or factory's return type) of any type, to | |
| # be used when the value being verified is `None`. | |
| # | |
| reveal_type(value) # ==> [PyLance] Type of "value" is "str" | |
| # ==> note: Revealed type is "Union[Literal['value-a'], Literal['value-b'], Literal['value-c']]" | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment