Search code examples
pythonpython-typingmypy

How to resolve 'Incompatible types in assignment' when converting dictionary values from str to datetime in Python?


I am working with a dictionary in Python where a key ("expiryTime") initially holds a str value in ISO 8601 format (e.g., "2025-01-23T12:34:56"). At some point in my code, I convert this string into a datetime object using datetime.strptime. However, I encounter a mypy error during type checking:

error: Incompatible types in assignment (expression has type "datetime", target has type "str")  [assignment]

Here is a simplified version of the code:

from datetime import datetime

# Initial dictionary with string value
token_dict: dict[str, str] = {"expiryTime": "2025-01-23T12:34:56"}

# Convert the string to datetime
expiry_time_dt = datetime.strptime(token_dict["expiryTime"], "%Y-%m-%dT%H:%M:%S")
token_dict["expiryTime"] = expiry_time_dt  # Error here: incompatible types

I understand that mypy complains because the dictionary's value was initially declared as a str, and assigning a datetime object violates the declared type. However, I need to store the datetime object for further processing in my code.


My Question:

What is the best way to handle this situation while maintaining type safety with mypy? Should I:

  1. Suppress the type-checking error with # type: ignore?
  2. Refactor my code to use a Union[str, datetime] type for the dictionary values, despite the strptime error?
  3. Utilize an alternate approach I may not be aware of?

The goal is to ensure the code remains type-safe while handling the necessary conversions between str and datetime. Any advice or recommendations for improving this workflow would be appreciated!

What I Have Tried:

  1. Suppressed the Error: I used # type: ignore to bypass the issue temporarily:

    token_dict["expiryTime"] = expiry_time_dt  # type: ignore
    

    While this works, it's not a clean or type-safe solution.

  2. Using Union[str, datetime]: I updated the type hint of the dictionary:

    token_dict: dict[str, Union[str, datetime]] = {"expiryTime": "2025-01-23T12:34:56"}
    

    This was the only viable solution until I encountered the following mypy error:

    error: Argument 1 to "strptime" of "datetime" has incompatible type "Union[str, datetime]"; expected "str"  [arg-type]
    

    The error occurs when I try to pass a Union[str, datetime] value to strptime, which expects a str. This creates additional complexity because mypy requires explicit type checks for every access to token_dict["expiryTime"], making it harder to maintain type correctness.

  3. Conversion to str for Mocking: When mocking requests that interact with this dictionary, I must ensure the value is serialized back to a string (since datetime is not JSON-serializable). This back-and-forth conversion is unavoidable, and I need to ensure type correctness in my code.


Problem Context (for Reference):

The key "expiryTime" is part of a token dictionary that represents metadata for authentication. When mocking requests, I serialize the dictionary using JSON, which does not support datetime objects. This necessitates the conversion of datetime values back to strings before serialization. The back-and-forth conversions, combined with type annotations, have made this problem challenging to address cleanly.


Solution

  • For your options...

    1. You probably don't want to suppress the error, it's there for a reason :)
    2. The Union type doesn't enforce that your dictionary is all of one type or another, meaning it is hard to consume that variable and having to add handling for both types
    3. [I might need more context on your mocking to fully understand what you're trying to do]

    I recommend not mutating the object at all, and processing it into a new dictionary:

    new_token_dict: dict[str, datetime] = {k: datetime.strptime(v, "%Y-%m-%dT%H:%M:%S") for k,v in token_dict.items()}
    

    Mutating the variable (and even worse, its type), adds extra complexity to your code since you now need to handle that variable differently depending on whether you've performed your manipulation or not. You've lost the benefits of having the static types.

    By creating a separate variable, you can keep the concepts separate with their own purpose.