Mypy: Literal treats strings as types

Created on 25 Jul 2020  路  5Comments  路  Source: python/mypy

Bug I found yesterday, here is the Minimal, Complete, Reproducible example:

# test.py
try:
    from typing import Literal  # type: ignore
except ImportError:
    from typing_extensions import Literal  # type: ignore

def get(
    table: Literal["cabinets", "feeders", "objects"]
) -> bool:
    return table in ("cabinets", "feeders", "objects")

Install Python3.7 virtualenv with mypy and Literal via install bash script:

#!/bin/bash

rm -rf venv
python3.7 -m virtualenv venv

./venv/bin/pip install --no-cache-dir typing_extensions mypy

Run mypy checks via test bash script:

#!/bin/bash

./venv/bin/mypy test.py --ignore-missing-imports --disallow-untyped-defs --show-error-codes

And you will get:

test.py:7: error: Name 'cabinets' is not defined  [name-defined]
test.py:7: error: Name 'feeders' is not defined  [name-defined]
test.py:7: error: Name 'objects' is not defined  [name-defined]
Found 3 errors in 1 file (checked 1 source file)

If you define classes above the get function - it will not give you those errors:

class cabinets: pass
class feeders: pass
class objects: pass

If you define type alias:

try:
    from typing import Literal  # type: ignore
except ImportError:
    from typing_extensions import Literal  # type: ignore

TableType = Literal["cabinets", "feeders", "objects"]

def get(
    table: TableType
) -> bool:
    return table in ("cabinets", "feeders", "objects")

It will give you this error:

test.py:9: error: Variable "test.TableType" is not valid as a type  [valid-type]
test.py:9: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases

Expected behaviour

It just works and check that table is either "cabinets", "feeders" or "objects" string.

mypy and Python versions

./venv/bin/python --version
Python 3.7.5

./venv/bin/mypy --version
mypy 0.782

Maybe I am not using Literal correctly? But mypy don't give me errors when I use it with pydantic models.

All 5 comments

New info: using Python 3.8, where from typing import Literal is used, issue no longer exist

You need to import Literal like this:

import sys
if sys.version_info >= (3, 8):
    from typing import Literal
else:
    from typing_extensions import Literal

@Akuli thanks a lot! Can you tell what the problem was with try/catch import?

I don't know. Maybe mypy doesn't support try,except imports?

I'm guessing the issue is that mypy goes with the first import, from typing, but you're on 3.7 and typing.Literal doesn't exist, so it just gets turned into Any, and mypy thinks you're doing a normal generic and interprets the arguments as types, not strings.

Was this page helpful?
0 / 5 - 0 ratings