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.LiteralString`.
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
- `Annotated` can now wrap `ClassVar` and `Final`. Backport from
bpo-46491. Patch by Gregory Beauregard (@GBeauregard).
- Add missed `Required` and `NotRequired` to `__all__`. Patch by
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)
- ``NotRequired`` (see PEP 655)
- ``Required`` (see PEP 655)
- ``Self`` (see PEP 673)
Expand Down
48 changes: 48 additions & 0 deletions typing_extensions/src/test_typing_extensions.py
Expand Up @@ -22,6 +22,8 @@
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 LiteralString

try:
from typing_extensions import get_type_hints
except ImportError:
Expand Down Expand Up @@ -2229,6 +2231,52 @@ 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

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

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

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


class SelfTests(BaseTestCase):
def test_basics(self):
class Foo:
Expand Down
95 changes: 95 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 @@ -2095,6 +2096,100 @@ def __eq__(self, other):

TypeGuard = _TypeGuard(_root=True)


if hasattr(typing, "LiteralString"):
LiteralString = typing.LiteralString
elif sys.version_info[:2] >= (3, 7):
# Vendored from cpython typing._SpecialFrom
class _SpecialForm(typing._Final, _root=True):
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
__slots__ = ('_name', '__doc__', '_getitem')

def __init__(self, getitem):
self._getitem = getitem
self._name = getitem.__name__
self.__doc__ = getitem.__doc__

def __getattr__(self, item):
if item in {'__name__', '__qualname__'}:
return self._name

raise AttributeError(item)

def __mro_entries__(self, bases):
raise TypeError(f"Cannot subclass {self!r}")

def __repr__(self):
return f'typing_extensions.{self._name}'

def __reduce__(self):
return self._name

def __call__(self, *args, **kwds):
raise TypeError(f"Cannot instantiate {self!r}")

def __or__(self, other):
return typing.Union[self, other]

def __ror__(self, other):
return typing.Union[other, self]

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()")

@typing._tp_cache
def __getitem__(self, parameters):
return self._getitem(self, parameters)

@_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