Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add LiteralString #1053

Merged
merged 11 commits into from Feb 11, 2022
1 change: 1 addition & 0 deletions typing_extensions/CHANGELOG
@@ -1,5 +1,6 @@
# Release 4.x.x

- Runtime support for PEP 675 and `typing_extensions.LiteralString`.
- Add `Never` and `assert_never`. Backport from bpo-46475.
- `ParamSpec` args and kwargs are now equal to themselves. Backport from
bpo-46676. Patch by Gregory Beauregard (@GBeauregard).
Expand Down
1 change: 1 addition & 0 deletions typing_extensions/README.rst
Expand Up @@ -37,6 +37,7 @@ This module currently contains the following:

- Experimental features

- ``LiteralString`` (see PEP 675)
- ``@dataclass_transform()`` (see PEP 681)
- ``NotRequired`` (see PEP 655)
- ``Required`` (see PEP 655)
Expand Down
76 changes: 74 additions & 2 deletions typing_extensions/src/test_typing_extensions.py
Expand Up @@ -22,7 +22,7 @@
from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard
from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired
from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict
from typing_extensions import dataclass_transform, reveal_type, Never, assert_never
from typing_extensions import dataclass_transform, reveal_type, Never, assert_never, LiteralString
try:
from typing_extensions import get_type_hints
except ImportError:
Expand Down Expand Up @@ -111,6 +111,11 @@ def test_cannot_instantiate(self):
with self.assertRaises(TypeError):
type(self.bottom_type)()

def test_pickle(self):
for proto in range(pickle.HIGHEST_PROTOCOL):
pickled = pickle.dumps(self.bottom_type, protocol=proto)
self.assertIs(self.bottom_type, pickle.loads(pickled))


class NoReturnTests(BottomTypeTestsMixin, BaseTestCase):
bottom_type = NoReturn
Expand Down Expand Up @@ -1896,7 +1901,8 @@ def test_cannot_check_subclass(self):
def test_pickle(self):
samples = [typing.Any, typing.Union[int, str],
typing.Optional[str], Tuple[int, ...],
typing.Callable[[str], bytes]]
typing.Callable[[str], bytes],
Self, LiteralString, Never]

for t in samples:
x = Annotated[t, "a"]
Expand Down Expand Up @@ -2290,6 +2296,67 @@ def test_no_isinstance(self):
issubclass(int, TypeGuard)


class LiteralStringTests(BaseTestCase):
def test_basics(self):
class Foo:
def bar(self) -> LiteralString: ...
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
def baz(self) -> "LiteralString": ...

self.assertEqual(gth(Foo.bar), {'return': LiteralString})
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
self.assertEqual(gth(Foo.baz), {'return': LiteralString})

@skipUnless(PEP_560, "Python 3.7+ required")
def test_get_origin(self):
from typing_extensions import get_origin
self.assertIsNone(get_origin(LiteralString))

def test_repr(self):
if hasattr(typing, 'LiteralString'):
mod_name = 'typing'
else:
mod_name = 'typing_extensions'
self.assertEqual(repr(LiteralString), '{}.LiteralString'.format(mod_name))

def test_cannot_subscript(self):
with self.assertRaises(TypeError):
LiteralString[int]

def test_cannot_subclass(self):
with self.assertRaises(TypeError):
class C(type(LiteralString)):
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
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):
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
StringTuple = Tuple[LiteralString, LiteralString]
class Alias:
def return_tuple(self) -> StringTuple:
return ("foo", "pep" + "675")

def test_typevar(self):
StrT = TypeVar("StrT", bound=LiteralString)
self.assertIs(StrT.__bound__, LiteralString)
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved

def test_pickle(self):
for proto in range(pickle.HIGHEST_PROTOCOL):
pickled = pickle.dumps(LiteralString, protocol=proto)
self.assertIs(LiteralString, pickle.loads(pickled))


class SelfTests(BaseTestCase):
def test_basics(self):
class Foo:
Expand Down Expand Up @@ -2331,6 +2398,11 @@ class Alias:
def return_tuple(self) -> TupleSelf:
return (self, self)

def test_pickle(self):
for proto in range(pickle.HIGHEST_PROTOCOL):
pickled = pickle.dumps(Self, protocol=proto)
self.assertIs(Self, pickle.loads(pickled))


class FinalDecoratorTests(BaseTestCase):
def test_final_unmodified(self):
Expand Down
51 changes: 51 additions & 0 deletions typing_extensions/src/typing_extensions.py
Expand Up @@ -44,6 +44,7 @@ def _check_generic(cls, parameters):
'ClassVar',
'Concatenate',
'Final',
'LiteralString',
'ParamSpec',
'Self',
'Type',
Expand Down Expand Up @@ -2155,6 +2156,56 @@ def __getitem__(self, parameters):
return self._getitem(self, parameters)


if hasattr(typing, "LiteralString"):
LiteralString = typing.LiteralString
elif sys.version_info[:2] >= (3, 7):
@_SpecialForm
def LiteralString(self, params):
"""Represents an arbitrary literal string.

Example::

from typing_extensions import LiteralString

def query(sql: LiteralString) -> ...:
...

query("SELECT * FROM table") # ok
query(f"SELECT * FROM {input()}") # not ok

See PEP 675 for details.

"""
raise TypeError(f"{self} is not subscriptable")
else:
class _LiteralString(typing._FinalTypingBase, _root=True):
"""Represents an arbitrary literal string.

Example::

from typing_extensions import LiteralString

def query(sql: LiteralString) -> ...:
...

query("SELECT * FROM table") # ok
query(f"SELECT * FROM {input()}") # not ok

See PEP 675 for details.

"""

__slots__ = ()

def __instancecheck__(self, obj):
raise TypeError(f"{self} cannot be used with isinstance().")

def __subclasscheck__(self, cls):
raise TypeError(f"{self} cannot be used with issubclass().")

LiteralString = _LiteralString(_root=True)


if hasattr(typing, "Self"):
Self = typing.Self
elif sys.version_info[:2] >= (3, 7):
Expand Down