stkvtflw
stkvtflw

Reputation: 13507

Python typing. TypedDict. How to turn a non-required key into a required one?

The code below defines a TypedDict with optional keys. The if statement in the middle raises an exception if a certain key is absent, which means that the next lines will be executed only if that key is present. It seems Pylance can't figure out this logic automatically. How am I supposed to handle this? Reassign a new typing to the same Dict somehow?

output_dict_type = TypedDict(
  'some description',
  {
    'id': str
    'name': str
  },
  total=False
)
my_dict:output_dict_type = {
  'id': 'foo'
}
if not my_dict.get('name'):
  raise Exception('name is required')

if my_dict['name'] == 'John': # type error: "name" is not a required key...
  do_stuff()

Upvotes: 0

Views: 1560

Answers (1)

Dunes
Dunes

Reputation: 40683

This might be a good place to practice "asking for forgiveness is easier then seeking permission". By which I mean wrapping the key fetch in a try/except block. If the key isn't present then throw an exception, and the code that follows later will be guaranteed that the fetched key exists.

from typing import Optional, TypedDict

class OutputType(TypedDict, total=False):
    id: str
    name: Optional[str]

my_dict: OutputType = ...

# guards
try:
    name = my_dict['name']
except KeyError:
    # name local var not initialised here
    raise Exception('name is required')
# name local var guaranteed to be initialised here

# since you cannot guarantee that name will be a str, we still have 
# to do a type check
if not isinstance(name, str):
    raise Exception('name must be str')

# happy path code
if name == 'John':
    ...

If you cannot rely upon your input data being in precise format, then you should write some custom derserialisation logic. This logic can be tucked away in another function and can handle all the type checking and other validation. It can then return a dataclass which is fully typed and you can just get on with processing the data.

from dataclasses import dataclass

@dataclass
class OutputType:
    id: str
    name: str

   @classmethod
   def deserialise(cls, id=None, name=None) -> 'OutputType':
       assert isinstance(id, str), 'id must be present and a string'
       assert isinstance(name, str), 'name must be present and a string'
       return Output(id, name)

output_dict: dict[str, Any] = ...

output = OutputType.deserialise(**output_dict)

if output.name == 'John':
    ...

Upvotes: 1

Related Questions