Skip to content

Commit f6e8272

Browse files
add LiteralString (PEP 675) (#1053)
Co-authored-by: Nikita Sobolev <[email protected]>
1 parent a53957c commit f6e8272

File tree

4 files changed

+127
-2
lines changed

4 files changed

+127
-2
lines changed

typing_extensions/CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Release 4.x.x
22

3+
- Runtime support for PEP 675 and `typing_extensions.LiteralString`.
34
- Add `Never` and `assert_never`. Backport from bpo-46475.
45
- `ParamSpec` args and kwargs are now equal to themselves. Backport from
56
bpo-46676. Patch by Gregory Beauregard (@GBeauregard).

typing_extensions/README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ This module currently contains the following:
3737

3838
- Experimental features
3939

40+
- ``LiteralString`` (see PEP 675)
4041
- ``@dataclass_transform()`` (see PEP 681)
4142
- ``NotRequired`` (see PEP 655)
4243
- ``Required`` (see PEP 655)

typing_extensions/src/test_typing_extensions.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard
2323
from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired
2424
from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict
25-
from typing_extensions import dataclass_transform, reveal_type, Never, assert_never
25+
from typing_extensions import dataclass_transform, reveal_type, Never, assert_never, LiteralString
2626
try:
2727
from typing_extensions import get_type_hints
2828
except ImportError:
@@ -111,6 +111,11 @@ def test_cannot_instantiate(self):
111111
with self.assertRaises(TypeError):
112112
type(self.bottom_type)()
113113

114+
def test_pickle(self):
115+
for proto in range(pickle.HIGHEST_PROTOCOL):
116+
pickled = pickle.dumps(self.bottom_type, protocol=proto)
117+
self.assertIs(self.bottom_type, pickle.loads(pickled))
118+
114119

115120
class NoReturnTests(BottomTypeTestsMixin, BaseTestCase):
116121
bottom_type = NoReturn
@@ -1896,7 +1901,8 @@ def test_cannot_check_subclass(self):
18961901
def test_pickle(self):
18971902
samples = [typing.Any, typing.Union[int, str],
18981903
typing.Optional[str], Tuple[int, ...],
1899-
typing.Callable[[str], bytes]]
1904+
typing.Callable[[str], bytes],
1905+
Self, LiteralString, Never]
19001906

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

22922298

2299+
class LiteralStringTests(BaseTestCase):
2300+
def test_basics(self):
2301+
class Foo:
2302+
def bar(self) -> LiteralString: ...
2303+
def baz(self) -> "LiteralString": ...
2304+
2305+
self.assertEqual(gth(Foo.bar), {'return': LiteralString})
2306+
self.assertEqual(gth(Foo.baz), {'return': LiteralString})
2307+
2308+
@skipUnless(PEP_560, "Python 3.7+ required")
2309+
def test_get_origin(self):
2310+
from typing_extensions import get_origin
2311+
self.assertIsNone(get_origin(LiteralString))
2312+
2313+
def test_repr(self):
2314+
if hasattr(typing, 'LiteralString'):
2315+
mod_name = 'typing'
2316+
else:
2317+
mod_name = 'typing_extensions'
2318+
self.assertEqual(repr(LiteralString), '{}.LiteralString'.format(mod_name))
2319+
2320+
def test_cannot_subscript(self):
2321+
with self.assertRaises(TypeError):
2322+
LiteralString[int]
2323+
2324+
def test_cannot_subclass(self):
2325+
with self.assertRaises(TypeError):
2326+
class C(type(LiteralString)):
2327+
pass
2328+
with self.assertRaises(TypeError):
2329+
class C(LiteralString):
2330+
pass
2331+
2332+
def test_cannot_init(self):
2333+
with self.assertRaises(TypeError):
2334+
LiteralString()
2335+
with self.assertRaises(TypeError):
2336+
type(LiteralString)()
2337+
2338+
def test_no_isinstance(self):
2339+
with self.assertRaises(TypeError):
2340+
isinstance(1, LiteralString)
2341+
with self.assertRaises(TypeError):
2342+
issubclass(int, LiteralString)
2343+
2344+
def test_alias(self):
2345+
StringTuple = Tuple[LiteralString, LiteralString]
2346+
class Alias:
2347+
def return_tuple(self) -> StringTuple:
2348+
return ("foo", "pep" + "675")
2349+
2350+
def test_typevar(self):
2351+
StrT = TypeVar("StrT", bound=LiteralString)
2352+
self.assertIs(StrT.__bound__, LiteralString)
2353+
2354+
def test_pickle(self):
2355+
for proto in range(pickle.HIGHEST_PROTOCOL):
2356+
pickled = pickle.dumps(LiteralString, protocol=proto)
2357+
self.assertIs(LiteralString, pickle.loads(pickled))
2358+
2359+
22932360
class SelfTests(BaseTestCase):
22942361
def test_basics(self):
22952362
class Foo:
@@ -2331,6 +2398,11 @@ class Alias:
23312398
def return_tuple(self) -> TupleSelf:
23322399
return (self, self)
23332400

2401+
def test_pickle(self):
2402+
for proto in range(pickle.HIGHEST_PROTOCOL):
2403+
pickled = pickle.dumps(Self, protocol=proto)
2404+
self.assertIs(Self, pickle.loads(pickled))
2405+
23342406

23352407
class FinalDecoratorTests(BaseTestCase):
23362408
def test_final_unmodified(self):

typing_extensions/src/typing_extensions.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def _check_generic(cls, parameters):
4444
'ClassVar',
4545
'Concatenate',
4646
'Final',
47+
'LiteralString',
4748
'ParamSpec',
4849
'Self',
4950
'Type',
@@ -2155,6 +2156,56 @@ def __getitem__(self, parameters):
21552156
return self._getitem(self, parameters)
21562157

21572158

2159+
if hasattr(typing, "LiteralString"):
2160+
LiteralString = typing.LiteralString
2161+
elif sys.version_info[:2] >= (3, 7):
2162+
@_SpecialForm
2163+
def LiteralString(self, params):
2164+
"""Represents an arbitrary literal string.
2165+
2166+
Example::
2167+
2168+
from typing_extensions import LiteralString
2169+
2170+
def query(sql: LiteralString) -> ...:
2171+
...
2172+
2173+
query("SELECT * FROM table") # ok
2174+
query(f"SELECT * FROM {input()}") # not ok
2175+
2176+
See PEP 675 for details.
2177+
2178+
"""
2179+
raise TypeError(f"{self} is not subscriptable")
2180+
else:
2181+
class _LiteralString(typing._FinalTypingBase, _root=True):
2182+
"""Represents an arbitrary literal string.
2183+
2184+
Example::
2185+
2186+
from typing_extensions import LiteralString
2187+
2188+
def query(sql: LiteralString) -> ...:
2189+
...
2190+
2191+
query("SELECT * FROM table") # ok
2192+
query(f"SELECT * FROM {input()}") # not ok
2193+
2194+
See PEP 675 for details.
2195+
2196+
"""
2197+
2198+
__slots__ = ()
2199+
2200+
def __instancecheck__(self, obj):
2201+
raise TypeError(f"{self} cannot be used with isinstance().")
2202+
2203+
def __subclasscheck__(self, cls):
2204+
raise TypeError(f"{self} cannot be used with issubclass().")
2205+
2206+
LiteralString = _LiteralString(_root=True)
2207+
2208+
21582209
if hasattr(typing, "Self"):
21592210
Self = typing.Self
21602211
elif sys.version_info[:2] >= (3, 7):

0 commit comments

Comments
 (0)