typing icon indicating copy to clipboard operation
typing copied to clipboard

Allow creating type aliases with type checked metadata attached

Open joslarson opened this issue 6 years ago • 6 comments

Annotations in Python have many use cases beyond static type checking. I'd really like to be able to create a type that is an alias to another type while also attaching metadata to it. The idea is to be able to use MyPy's type system but also use annotations for other use cases such as runtime validation at the same time. I'm thinking something along these lines (though I have no idea how this would work in practice):

from datetime import datetime
from mypy_extensions import TypedDict
from somewhere import AliasWithMetadata

# define metadata type
Options = TypedDict('Options', {'min': int, 'max': int})
# define type alias with allowed metadata
MinMaxInt = AliasWithMetadata(int, Options)

# define age arg as type int with metadata attached
def get_rough_birth_year(age: MinMaxInt(min=0, max=120)):
    return datetime.today().year - age

# call function with int without mypy complaining
get_rough_birth_year(105)

joslarson avatar Apr 25 '18 17:04 joslarson

To be clear, I'm not expecting MyPy to do anything with the metadata other than type check the metadata object itself.

joslarson avatar Apr 25 '18 17:04 joslarson

This is essentially fixed with PEP 593, Annotated is already available in typing_extensions (also mypy will likely support it in near future).

ilevkivskyi avatar Jun 20 '19 00:06 ilevkivskyi

@ilevkivskyi I just saw this got closed. This is great news. Question though, is it possible yet to create something like MinMaxInt(min=0, max=120) above where you define the structure of an annotated type then can dynamically pass in different metadata values per usage site?

joslarson avatar Nov 18 '19 15:11 joslarson

The currently supported syntax is a bit more verbose, but I think this is possible. For example:

from typing_extensions import Annotated as An

def get_rough_birth_year(age: An[int, Range(min=0, max=120)]):
    return datetime.today().year - age

The problem with the original proposal is that it is hard to define a general enough API/syntax for user-defined annotations.

ilevkivskyi avatar Nov 18 '19 16:11 ilevkivskyi

@ilevkivskyi That makes sense. The part that is missing that would be really valuable as a library creator would be to be able to define and export strictly typed annotation types (if that makes sense). In your example the user could pass in something other than Range(...), and the type checker wouldn't care.

What if you could define a generic annotation type like this:

MinMaxOptions = TypedDict('MinMaxOptions', {'min': int, 'max': int})

def Range(min: int, max: int) -> MinMaxOptions:
    return {'min': min, 'max': max}

MinMaxInt = GenericAnnotation[int, MinMaxOptions]

def get_rough_birth_year(age: MinMaxInt[Range(min=0, max=120)]):
    return datetime.today().year - age

With that the shape of the annotation could be anything, but once defined could be enforced. Or am I still missing something?

joslarson avatar Nov 26 '19 20:11 joslarson

With that the shape of the annotation could be anything, but once defined could be enforced. Or am I still missing something?

Yes, this is something that goes beyond PEP 593. But it is also a harder sale. Anyway, let's re-open to track this particular case.

ilevkivskyi avatar Nov 27 '19 17:11 ilevkivskyi