Note: since this answer keeps getting upvoted - while there are still use cases for TypedDict, I'd consider using a dataclass instead today.
class BackupData(TypedDict, total=False):
archive_name: str
archive_size: int
transfer_size: int
transfer_time: float
error: str
def to_backup_data(data: Mapping[str, Any]) -> BackupData:
result = BackupData()
if 'archive_name' in data:
result['archive_name'] = str(data['archive_name'])
if 'archive_size' in data:
result['archive_size'] = int(data['archive_size'])
if 'transfer_size' in data:
result['transfer_size'] = int(data['transfer_size'])
if 'transfer_time' in data:
result['transfer_time'] = int(data['transfer_time'])
if 'error' in data:
result['error'] = str(data['error'])
return result
i.e I have a TypedDict with optional keys and want a TypedDict instance.
The code above is redundant and non-functional (in terms of functional programming) because I have to write names four times, types twice and result has to be mutable.
Sadly TypedDict can't have methods otherwise I could write s.th. like
backup_data = BackupData.from(json.loads({...}))
Is there something I'm missing regarding TypeDict? Can this be written in a nice, non-redundant way?
When you use a TypedDict, all information is stored in the __annotations__ field.
For your example:
BackupData.__annotations__
returns:
{'archive_name': <class 'str'>, 'archive_size': <class 'int'>, 'transfer_size': <class 'int'>, 'transfer_time': <class 'float'>, 'error': <class 'str'>}
Now we can use that dictionary to iterate over the data and use the values for type casting:
def to_backup_data(data: Mapping[str, Any]) -> BackupData:
result = BackupData()
for key, key_type in BackupData.__annotations__.items():
if key not in data:
raise ValueError(f"Key: {key} is not available in data.")
result[key] = key_type(data[key])
return result
Note that I throw an error when the data is not available, this can be changed at your discretion.
With the following test code:
data = dict(
archive_name="my archive",
archive_size="50",
transfer_size="100",
transfer_time="2.3",
error=None,
)
for key, value in result.items():
print(f"Key: {key.ljust(15)}, type: {str(type(value)).ljust(15)}, value: {value!r}")
The result will be:
Key: archive_name , type: <class 'str'> , value: 'my archive'
Key: archive_size , type: <class 'int'> , value: 50
Key: transfer_size , type: <class 'int'> , value: 100
Key: transfer_time , type: <class 'float'>, value: 2.3
Key: error , type: <class 'str'> , value: 'None'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With