jamiet
jamiet

Reputation: 12334

Can I inform mypy that an expression will not return an Optional?

I have the following code:

def extract_table_date(bucket_path: str) -> str:
    event_date = re.search(r"date=([^/]+)", bucket_path)
    return event_date.group(1)[0:10].replace("-", "")

mypy throws error on the last line:

Item "None" of "Optional[Match[str]]" has no attribute "group"

I think I can solve that by assigning a type to event_date, and I can:

from typing import Match

def extract_table_date(bucket_path: str) -> str:
    event_date: Match = re.search(r"date=([^/]+)", bucket_path)
    return event_date.group(1)[0:10].replace("-", "")

but mypy now throws another error on the first line of the function:

Incompatible types in assignment (expression has type "Optional[Match[Any]]", variable has type "Match[Any]")

I don't really know how to inform mypy that the result won't be optional but nonetheless I followed the advice at Optional types and the None type by adding an assert:

from typing import Match

def extract_table_date(bucket_path: str) -> str:
    assert bucket_path is not None
    event_date: Match = re.search(r"date=([^/]+)", bucket_path)
    return event_date.group(1)[0:10].replace("-", "")

but mypy still raises the same error.

I try to fix by changing the type defined for event_date:

from typing import Match, optional, Any

def extract_table_date(bucket_path: str) -> str:
    assert bucket_path is not None
    event_date: Optional[Match[Any]] = re.search(r"date=([^/]+)", bucket_path)
    return event_date.group(1)[0:10].replace("-", "")

but (as expected) I'm now back to almost the same original error:

Item "None" of "Optional[Match[Any]]" has no attribute "group"

Any advice on how to fix this?

Upvotes: 12

Views: 10937

Answers (2)

Chiel
Chiel

Reputation: 2179

Another possibility would be to use isinstance.

def extract_table_date(bucket_path: str) -> str:
    event_date = re.search(r"date=([^/]+)", bucket_path)
    if isinstance(event_date, str):
        return event_date.group(1)[0:10].replace("-", "")
    return ""

Here you would return "" instead of None.

Upvotes: -2

Samwise
Samwise

Reputation: 71512

The thing that's Optional is event_date, because re.search is not guaranteed to return a match. mypy is warning you that this will raise an AttributeError if that's the case. You can tell it "no, I'm very confident that will not be the case" by doing an assert to that effect:

def extract_table_date(bucket_path: str) -> str:
    event_date = re.search(r"date=([^/]+)", bucket_path)
    assert event_date is not None
    return event_date.group(1)[0:10].replace("-", "")

If you're wrong, this code will still raise an exception (AssertionError, because your assert will fail), but mypy will no longer error because there is now no way for event_date to be None when you access its group attribute.

Note that there is no need to assert on bucket_path because it's already explicitly typed as str.

Upvotes: 20

Related Questions