bpo-47088: Add typing.LiteralString (PEP 675) (GH-32064)

Co-authored-by: Nick Pope <nick@nickpope.me.uk>
This commit is contained in:
Jelle Zijlstra 2022-04-05 07:21:03 -07:00 committed by GitHub
parent a7551247e7
commit cfb849a326
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 116 additions and 2 deletions

View file

@ -27,7 +27,7 @@ from typing import NamedTuple, TypedDict
from typing import IO, TextIO, BinaryIO
from typing import Pattern, Match
from typing import Annotated, ForwardRef
from typing import Self
from typing import Self, LiteralString
from typing import TypeAlias
from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs
from typing import TypeGuard
@ -265,6 +265,60 @@ class SelfTests(BaseTestCase):
self.assertEqual(get_args(alias_3), (Self,))
class LiteralStringTests(BaseTestCase):
def test_equality(self):
self.assertEqual(LiteralString, LiteralString)
self.assertIs(LiteralString, LiteralString)
self.assertNotEqual(LiteralString, None)
def test_basics(self):
class Foo:
def bar(self) -> LiteralString: ...
class FooStr:
def bar(self) -> 'LiteralString': ...
class FooStrTyping:
def bar(self) -> 'typing.LiteralString': ...
for target in [Foo, FooStr, FooStrTyping]:
with self.subTest(target=target):
self.assertEqual(gth(target.bar), {'return': LiteralString})
self.assertIs(get_origin(LiteralString), None)
def test_repr(self):
self.assertEqual(repr(LiteralString), 'typing.LiteralString')
def test_cannot_subscript(self):
with self.assertRaises(TypeError):
LiteralString[int]
def test_cannot_subclass(self):
with self.assertRaises(TypeError):
class C(type(LiteralString)):
pass
with self.assertRaises(TypeError):
class C(LiteralString):
pass
def test_cannot_init(self):
with self.assertRaises(TypeError):
LiteralString()
with self.assertRaises(TypeError):
type(LiteralString)()
def test_no_isinstance(self):
with self.assertRaises(TypeError):
isinstance(1, LiteralString)
with self.assertRaises(TypeError):
issubclass(int, LiteralString)
def test_alias(self):
alias_1 = Tuple[LiteralString, LiteralString]
alias_2 = List[LiteralString]
alias_3 = ClassVar[LiteralString]
self.assertEqual(get_args(alias_1), (LiteralString, LiteralString))
self.assertEqual(get_args(alias_2), (LiteralString,))
self.assertEqual(get_args(alias_3), (LiteralString,))
class TypeVarTests(BaseTestCase):
def test_basic_plain(self):
T = TypeVar('T')

View file

@ -126,6 +126,7 @@ __all__ = [
'get_origin',
'get_type_hints',
'is_typeddict',
'LiteralString',
'Never',
'NewType',
'no_type_check',
@ -180,7 +181,7 @@ def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms=
if (isinstance(arg, _GenericAlias) and
arg.__origin__ in invalid_generic_forms):
raise TypeError(f"{arg} is not valid as type argument")
if arg in (Any, NoReturn, Never, Self, TypeAlias):
if arg in (Any, LiteralString, NoReturn, Never, Self, TypeAlias):
return arg
if allow_special_forms and arg in (ClassVar, Final):
return arg
@ -523,6 +524,34 @@ def Self(self, parameters):
raise TypeError(f"{self} is not subscriptable")
@_SpecialForm
def LiteralString(self, parameters):
"""Represents an arbitrary literal string.
Example::
from typing import LiteralString
def run_query(sql: LiteralString) -> ...
...
def caller(arbitrary_string: str, literal_string: LiteralString) -> None:
run_query("SELECT * FROM students") # ok
run_query(literal_string) # ok
run_query("SELECT * FROM " + literal_string) # ok
run_query(arbitrary_string) # type checker error
run_query( # type checker error
f"SELECT * FROM students WHERE name = {arbitrary_string}"
)
Only string literals and other LiteralStrings are compatible
with LiteralString. This provides a tool to help prevent
security issues such as SQL injection.
"""
raise TypeError(f"{self} is not subscriptable")
@_SpecialForm
def ClassVar(self, parameters):
"""Special type construct to mark class variables.