Avoid PyCharm Type-Warning when using typing.NewType

Viewed 153

After slowly getting the hang of typing in collaboration with PyCharm, I find myself using the package a lot. I like its flexibility and how it can be adapted to almost any use case.

While playing with the package (and also because it'd be really useful) I thought about creating a "wrapper-type" for certain str ids I get from a database and use as dictionary keys throughout the project.

That way, if a function was passing a dictionary using ids gotten directly from the database as keys, the type hints would not just hint at the required type, but also provide some information on the dataflow and key structures, if not just one, but two or more database ids were used, to make up a dictionary key.

I thought the facility of choice would be NewType, since it actually creates a new type with a name that can be shown in PyCharms tooltip (Unlike TypeAlias or just PrimaryKey = str). However, as predicted in the documentation, PyCharm's static type checker does not accept the created NewType as str and raises a type warning.

"Expect '{NewType xy}' got 'str' instead".

So my question: Is there any way around that?

# instead of:
some_info: Mapping[Tuple[str, str], List[Any]] = dict()  # obviously works

# it'd look like this:
PrimaryKeyA = NewType('PrimaryKeyA', str)
PrimaryKeyB = NewType('PrimaryKeyB', str)

some_info: Mapping[Tuple[PrimaryKeyA, PrimaryKeyB], List[Any]] = dict()

some_info['AKey_01', 'BKey_01'] = 'example_data'  # raises type warning

# I could do: 
some_info[PrimaryKeyA('AKey_01'), PrimaryKeyB('BKey_01')] = 'example_data'
# But that doesn't feel more readable

In the project I'm currently involved with, this would be a giant improvement for readability since the whole thing is collecting stuff on different levels of detail, each level identified by a database primary key. But if I can't get it to work with PyCharm, I don't think it'd be worth the trouble.

1 Answers

The main goal of NewType is to help detect logical errors, like not passing in a PrimaryKey were you should. so I think the best way to do it is how you showed last.

some_info[PrimaryKeyA('AKey_01'), PrimaryKeyB('BKey_01')] = 'example_data'

one thing you could do is define those strings before in the program.

key_1 = PrimaryKeyA('AKey_01')
key_2 = PrimaryKeyB('BKey_01')

some_info[key_1, key_2]

or take them as arguments to a function depending on the context. as this should work:

def foo(key_1: PrimaryKeyA, key_2: PrimaryKeyB) -> None:
    some_info[key_1, key_2] = 'example_data'

Not actually a new type on runtime

since it actually creates a new type with a name

lastly I just want to point out that this is actually not correct, atleast not at runtime. type checkers will treat it as a new type yes, but at runtime it just returns whatever given to it.

In [1]: from typing import NewType             

In [2]: A = NewType("A", int)                  

In [3]: A("abc")       
Out[3]: 'abc'
Related