@@ -991,13 +991,16 @@ def __index__(self) -> int:
991
991
pass
992
992
993
993
994
- if sys . version_info >= ( 3 , 9 , 2 ):
994
+ if hasattr ( typing , "Required" ):
995
995
# The standard library TypedDict in Python 3.8 does not store runtime information
996
996
# about which (if any) keys are optional. See https://bugs.python.org/issue38834
997
997
# The standard library TypedDict in Python 3.9.0/1 does not honour the "total"
998
998
# keyword with old-style TypedDict(). See https://bugs.python.org/issue42059
999
+ # The standard library TypedDict below Python 3.11 does not store runtime
1000
+ # information about optional and required keys when using Required or NotRequired.
999
1001
TypedDict = typing .TypedDict
1000
1002
_TypedDictMeta = typing ._TypedDictMeta
1003
+ is_typeddict = typing .is_typeddict
1001
1004
else :
1002
1005
def _check_fails (cls , other ):
1003
1006
try :
@@ -1081,7 +1084,6 @@ def __new__(cls, name, bases, ns, total=True):
1081
1084
1082
1085
annotations = {}
1083
1086
own_annotations = ns .get ('__annotations__' , {})
1084
- own_annotation_keys = set (own_annotations .keys ())
1085
1087
msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
1086
1088
own_annotations = {
1087
1089
n : typing ._type_check (tp , msg ) for n , tp in own_annotations .items ()
@@ -1095,10 +1097,29 @@ def __new__(cls, name, bases, ns, total=True):
1095
1097
optional_keys .update (base .__dict__ .get ('__optional_keys__' , ()))
1096
1098
1097
1099
annotations .update (own_annotations )
1098
- if total :
1099
- required_keys .update (own_annotation_keys )
1100
+ if PEP_560 :
1101
+ for annotation_key , annotation_type in own_annotations .items ():
1102
+ annotation_origin = get_origin (annotation_type )
1103
+ if annotation_origin is Annotated :
1104
+ annotation_args = get_args (annotation_type )
1105
+ if annotation_args :
1106
+ annotation_type = annotation_args [0 ]
1107
+ annotation_origin = get_origin (annotation_type )
1108
+
1109
+ if annotation_origin is Required :
1110
+ required_keys .add (annotation_key )
1111
+ elif annotation_origin is NotRequired :
1112
+ optional_keys .add (annotation_key )
1113
+ elif total :
1114
+ required_keys .add (annotation_key )
1115
+ else :
1116
+ optional_keys .add (annotation_key )
1100
1117
else :
1101
- optional_keys .update (own_annotation_keys )
1118
+ own_annotation_keys = set (own_annotations .keys ())
1119
+ if total :
1120
+ required_keys .update (own_annotation_keys )
1121
+ else :
1122
+ optional_keys .update (own_annotation_keys )
1102
1123
1103
1124
tp_dict .__annotations__ = annotations
1104
1125
tp_dict .__required_keys__ = frozenset (required_keys )
@@ -1141,10 +1162,6 @@ class Point2D(TypedDict):
1141
1162
syntax forms work for Python 2.7 and 3.2+
1142
1163
"""
1143
1164
1144
-
1145
- if hasattr (typing , "is_typeddict" ):
1146
- is_typeddict = typing .is_typeddict
1147
- else :
1148
1165
if hasattr (typing , "_TypedDictMeta" ):
1149
1166
_TYPEDDICT_TYPES = (typing ._TypedDictMeta , _TypedDictMeta )
1150
1167
else :
@@ -1163,11 +1180,83 @@ class Film(TypedDict):
1163
1180
"""
1164
1181
return isinstance (tp , tuple (_TYPEDDICT_TYPES ))
1165
1182
1183
+ if hasattr (typing , "Required" ):
1184
+ get_type_hints = typing .get_type_hints
1185
+ elif PEP_560 :
1186
+ import functools
1187
+ import types
1166
1188
1167
- # Python 3.9+ has PEP 593 (Annotated and modified get_type_hints)
1189
+ # replaces _strip_annotations()
1190
+ def _strip_extras (t ):
1191
+ """Strips Annotated, Required and NotRequired from a given type."""
1192
+ if isinstance (t , _AnnotatedAlias ):
1193
+ return _strip_extras (t .__origin__ )
1194
+ if hasattr (t , "__origin__" ) and t .__origin__ in (Required , NotRequired ):
1195
+ return _strip_extras (t .__args__ [0 ])
1196
+ if isinstance (t , typing ._GenericAlias ):
1197
+ stripped_args = tuple (_strip_extras (a ) for a in t .__args__ )
1198
+ if stripped_args == t .__args__ :
1199
+ return t
1200
+ return t .copy_with (stripped_args )
1201
+ if hasattr (types , "GenericAlias" ) and isinstance (t , types .GenericAlias ):
1202
+ stripped_args = tuple (_strip_extras (a ) for a in t .__args__ )
1203
+ if stripped_args == t .__args__ :
1204
+ return t
1205
+ return types .GenericAlias (t .__origin__ , stripped_args )
1206
+ if hasattr (types , "UnionType" ) and isinstance (t , types .UnionType ):
1207
+ stripped_args = tuple (_strip_extras (a ) for a in t .__args__ )
1208
+ if stripped_args == t .__args__ :
1209
+ return t
1210
+ return functools .reduce (operator .or_ , stripped_args )
1211
+
1212
+ return t
1213
+
1214
+ def get_type_hints (obj , globalns = None , localns = None , include_extras = False ):
1215
+ """Return type hints for an object.
1216
+
1217
+ This is often the same as obj.__annotations__, but it handles
1218
+ forward references encoded as string literals, adds Optional[t] if a
1219
+ default value equal to None is set and recursively replaces all
1220
+ 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T'
1221
+ (unless 'include_extras=True').
1222
+
1223
+ The argument may be a module, class, method, or function. The annotations
1224
+ are returned as a dictionary. For classes, annotations include also
1225
+ inherited members.
1226
+
1227
+ TypeError is raised if the argument is not of a type that can contain
1228
+ annotations, and an empty dictionary is returned if no annotations are
1229
+ present.
1230
+
1231
+ BEWARE -- the behavior of globalns and localns is counterintuitive
1232
+ (unless you are familiar with how eval() and exec() work). The
1233
+ search order is locals first, then globals.
1234
+
1235
+ - If no dict arguments are passed, an attempt is made to use the
1236
+ globals from obj (or the respective module's globals for classes),
1237
+ and these are also used as the locals. If the object does not appear
1238
+ to have globals, an empty dictionary is used.
1239
+
1240
+ - If one dict argument is passed, it is used for both globals and
1241
+ locals.
1242
+
1243
+ - If two dict arguments are passed, they specify globals and
1244
+ locals, respectively.
1245
+ """
1246
+ if hasattr (typing , "Annotated" ):
1247
+ hint = typing .get_type_hints (
1248
+ obj , globalns = globalns , localns = localns , include_extras = True
1249
+ )
1250
+ else :
1251
+ hint = typing .get_type_hints (obj , globalns = globalns , localns = localns )
1252
+ if include_extras :
1253
+ return hint
1254
+ return {k : _strip_extras (t ) for k , t in hint .items ()}
1255
+
1256
+
1257
+ # Python 3.9+ has PEP 593 (Annotated)
1168
1258
if hasattr (typing , 'Annotated' ):
1169
1259
Annotated = typing .Annotated
1170
- get_type_hints = typing .get_type_hints
1171
1260
# Not exported and not a public API, but needed for get_origin() and get_args()
1172
1261
# to work.
1173
1262
_AnnotatedAlias = typing ._AnnotatedAlias
@@ -1269,56 +1358,6 @@ def __init_subclass__(cls, *args, **kwargs):
1269
1358
raise TypeError (
1270
1359
f"Cannot subclass { cls .__module__ } .Annotated"
1271
1360
)
1272
-
1273
- def _strip_annotations (t ):
1274
- """Strips the annotations from a given type.
1275
- """
1276
- if isinstance (t , _AnnotatedAlias ):
1277
- return _strip_annotations (t .__origin__ )
1278
- if isinstance (t , typing ._GenericAlias ):
1279
- stripped_args = tuple (_strip_annotations (a ) for a in t .__args__ )
1280
- if stripped_args == t .__args__ :
1281
- return t
1282
- res = t .copy_with (stripped_args )
1283
- res ._special = t ._special
1284
- return res
1285
- return t
1286
-
1287
- def get_type_hints (obj , globalns = None , localns = None , include_extras = False ):
1288
- """Return type hints for an object.
1289
-
1290
- This is often the same as obj.__annotations__, but it handles
1291
- forward references encoded as string literals, adds Optional[t] if a
1292
- default value equal to None is set and recursively replaces all
1293
- 'Annotated[T, ...]' with 'T' (unless 'include_extras=True').
1294
-
1295
- The argument may be a module, class, method, or function. The annotations
1296
- are returned as a dictionary. For classes, annotations include also
1297
- inherited members.
1298
-
1299
- TypeError is raised if the argument is not of a type that can contain
1300
- annotations, and an empty dictionary is returned if no annotations are
1301
- present.
1302
-
1303
- BEWARE -- the behavior of globalns and localns is counterintuitive
1304
- (unless you are familiar with how eval() and exec() work). The
1305
- search order is locals first, then globals.
1306
-
1307
- - If no dict arguments are passed, an attempt is made to use the
1308
- globals from obj (or the respective module's globals for classes),
1309
- and these are also used as the locals. If the object does not appear
1310
- to have globals, an empty dictionary is used.
1311
-
1312
- - If one dict argument is passed, it is used for both globals and
1313
- locals.
1314
-
1315
- - If two dict arguments are passed, they specify globals and
1316
- locals, respectively.
1317
- """
1318
- hint = typing .get_type_hints (obj , globalns = globalns , localns = localns )
1319
- if include_extras :
1320
- return hint
1321
- return {k : _strip_annotations (t ) for k , t in hint .items ()}
1322
1361
# 3.6
1323
1362
else :
1324
1363
0 commit comments