Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/sphinx/util/typing.py: 21%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

305 statements  

1"""The composite types for Sphinx.""" 

2 

3from __future__ import annotations 

4 

5import dataclasses 

6import sys 

7import types 

8import typing 

9from collections.abc import Sequence 

10from contextvars import Context, ContextVar, Token 

11from struct import Struct 

12from typing import ( 

13 TYPE_CHECKING, 

14 Annotated, 

15 Any, 

16 Callable, 

17 ForwardRef, 

18 TypedDict, 

19 TypeVar, 

20 Union, 

21) 

22 

23from docutils import nodes 

24from docutils.parsers.rst.states import Inliner 

25 

26if TYPE_CHECKING: 

27 from collections.abc import Mapping 

28 from typing import Final, Literal, Protocol 

29 

30 from typing_extensions import TypeAlias, TypeIs 

31 

32 from sphinx.application import Sphinx 

33 

34 _RestifyMode: TypeAlias = Literal[ 

35 'fully-qualified-except-typing', 

36 'smart', 

37 ] 

38 _StringifyMode: TypeAlias = Literal[ 

39 'fully-qualified-except-typing', 

40 'fully-qualified', 

41 'smart', 

42 ] 

43 

44if sys.version_info >= (3, 10): 

45 from types import UnionType 

46else: 

47 UnionType = None 

48 

49# classes that have an incorrect .__module__ attribute 

50_INVALID_BUILTIN_CLASSES: Final[Mapping[object, str]] = { 

51 Context: 'contextvars.Context', # Context.__module__ == '_contextvars' 

52 ContextVar: 'contextvars.ContextVar', # ContextVar.__module__ == '_contextvars' 

53 Token: 'contextvars.Token', # Token.__module__ == '_contextvars' 

54 Struct: 'struct.Struct', # Struct.__module__ == '_struct' 

55 # types in 'types' with <type>.__module__ == 'builtins': 

56 types.AsyncGeneratorType: 'types.AsyncGeneratorType', 

57 types.BuiltinFunctionType: 'types.BuiltinFunctionType', 

58 types.BuiltinMethodType: 'types.BuiltinMethodType', 

59 types.CellType: 'types.CellType', 

60 types.ClassMethodDescriptorType: 'types.ClassMethodDescriptorType', 

61 types.CodeType: 'types.CodeType', 

62 types.CoroutineType: 'types.CoroutineType', 

63 types.FrameType: 'types.FrameType', 

64 types.FunctionType: 'types.FunctionType', 

65 types.GeneratorType: 'types.GeneratorType', 

66 types.GetSetDescriptorType: 'types.GetSetDescriptorType', 

67 types.LambdaType: 'types.LambdaType', 

68 types.MappingProxyType: 'types.MappingProxyType', 

69 types.MemberDescriptorType: 'types.MemberDescriptorType', 

70 types.MethodDescriptorType: 'types.MethodDescriptorType', 

71 types.MethodType: 'types.MethodType', 

72 types.MethodWrapperType: 'types.MethodWrapperType', 

73 types.ModuleType: 'types.ModuleType', 

74 types.TracebackType: 'types.TracebackType', 

75 types.WrapperDescriptorType: 'types.WrapperDescriptorType', 

76} 

77 

78 

79def is_invalid_builtin_class(obj: Any) -> bool: 

80 """Check *obj* is an invalid built-in class.""" 

81 try: 

82 return obj in _INVALID_BUILTIN_CLASSES 

83 except TypeError: # unhashable type 

84 return False 

85 

86 

87# Text like nodes which are initialized with text and rawsource 

88TextlikeNode = Union[nodes.Text, nodes.TextElement] 

89 

90# type of None 

91NoneType = type(None) 

92 

93# path matcher 

94PathMatcher = Callable[[str], bool] 

95 

96# common role functions 

97if TYPE_CHECKING: 

98 class RoleFunction(Protocol): 

99 def __call__( 

100 self, 

101 name: str, 

102 rawtext: str, 

103 text: str, 

104 lineno: int, 

105 inliner: Inliner, 

106 /, 

107 options: dict[str, Any] | None = None, 

108 content: Sequence[str] = (), 

109 ) -> tuple[list[nodes.Node], list[nodes.system_message]]: 

110 ... 

111else: 

112 RoleFunction = Callable[ 

113 [str, str, str, int, Inliner, dict[str, Any], Sequence[str]], 

114 tuple[list[nodes.Node], list[nodes.system_message]], 

115 ] 

116 

117# A option spec for directive 

118OptionSpec = dict[str, Callable[[str], Any]] 

119 

120# title getter functions for enumerable nodes (see sphinx.domains.std) 

121TitleGetter = Callable[[nodes.Node], str] 

122 

123# inventory data on memory 

124InventoryItem = tuple[ 

125 str, # project name 

126 str, # project version 

127 str, # URL 

128 str, # display name 

129] 

130Inventory = dict[str, dict[str, InventoryItem]] 

131 

132 

133class ExtensionMetadata(TypedDict, total=False): 

134 """The metadata returned by an extension's ``setup()`` function. 

135 

136 See :ref:`ext-metadata`. 

137 """ 

138 

139 version: str 

140 """The extension version (default: ``'unknown version'``).""" 

141 env_version: int 

142 """An integer that identifies the version of env data added by the extension.""" 

143 parallel_read_safe: bool 

144 """Indicate whether parallel reading of source files is supported 

145 by the extension. 

146 """ 

147 parallel_write_safe: bool 

148 """Indicate whether parallel writing of output files is supported 

149 by the extension (default: ``True``). 

150 """ 

151 

152 

153if TYPE_CHECKING: 

154 _ExtensionSetupFunc = Callable[[Sphinx], ExtensionMetadata] 

155 

156 

157def get_type_hints( 

158 obj: Any, 

159 globalns: dict[str, Any] | None = None, 

160 localns: dict[str, Any] | None = None, 

161 include_extras: bool = False, 

162) -> dict[str, Any]: 

163 """Return a dictionary containing type hints for a function, method, module or class 

164 object. 

165 

166 This is a simple wrapper of `typing.get_type_hints()` that does not raise an error on 

167 runtime. 

168 """ 

169 from sphinx.util.inspect import safe_getattr # lazy loading 

170 

171 try: 

172 return typing.get_type_hints(obj, globalns, localns, include_extras=include_extras) 

173 except NameError: 

174 # Failed to evaluate ForwardRef (maybe TYPE_CHECKING) 

175 return safe_getattr(obj, '__annotations__', {}) 

176 except AttributeError: 

177 # Failed to evaluate ForwardRef (maybe not runtime checkable) 

178 return safe_getattr(obj, '__annotations__', {}) 

179 except TypeError: 

180 # Invalid object is given. But try to get __annotations__ as a fallback. 

181 return safe_getattr(obj, '__annotations__', {}) 

182 except KeyError: 

183 # a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084) 

184 return {} 

185 

186 

187def is_system_TypeVar(typ: Any) -> bool: 

188 """Check *typ* is system defined TypeVar.""" 

189 modname = getattr(typ, '__module__', '') 

190 return modname == 'typing' and isinstance(typ, TypeVar) 

191 

192 

193def _is_annotated_form(obj: Any) -> TypeIs[Annotated[Any, ...]]: 

194 """Check if *obj* is an annotated type.""" 

195 return typing.get_origin(obj) is Annotated or str(obj).startswith('typing.Annotated') 

196 

197 

198def _is_unpack_form(obj: Any) -> bool: 

199 """Check if the object is :class:`typing.Unpack` or equivalent.""" 

200 if sys.version_info >= (3, 11): 

201 from typing import Unpack 

202 

203 # typing_extensions.Unpack != typing.Unpack for 3.11, but we assume 

204 # that typing_extensions.Unpack should not be used in that case 

205 return typing.get_origin(obj) is Unpack 

206 

207 # 3.9 and 3.10 require typing_extensions.Unpack 

208 origin = typing.get_origin(obj) 

209 return ( 

210 getattr(origin, '__module__', None) == 'typing_extensions' 

211 and _typing_internal_name(origin) == 'Unpack' 

212 ) 

213 

214 

215def _typing_internal_name(obj: Any) -> str | None: 

216 if sys.version_info[:2] >= (3, 10): 

217 try: 

218 return obj.__name__ 

219 except AttributeError: 

220 # e.g. ParamSpecArgs, ParamSpecKwargs 

221 return '' 

222 return getattr(obj, '_name', None) 

223 

224 

225def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> str: 

226 """Convert a type-like object to a reST reference. 

227 

228 :param mode: Specify a method how annotations will be stringified. 

229 

230 'fully-qualified-except-typing' 

231 Show the module name and qualified name of the annotation except 

232 the "typing" module. 

233 'smart' 

234 Show the name of the annotation. 

235 """ 

236 from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading 

237 from sphinx.util import inspect # lazy loading 

238 

239 valid_modes = {'fully-qualified-except-typing', 'smart'} 

240 if mode not in valid_modes: 

241 valid = ', '.join(map(repr, sorted(valid_modes))) 

242 msg = f'mode must be one of {valid}; got {mode!r}' 

243 raise ValueError(msg) 

244 

245 # things that are not types 

246 if cls is None or cls == NoneType: 

247 return ':py:obj:`None`' 

248 if cls is Ellipsis: 

249 return '...' 

250 if isinstance(cls, str): 

251 return cls 

252 

253 cls_module_is_typing = getattr(cls, '__module__', '') == 'typing' 

254 

255 # If the mode is 'smart', we always use '~'. 

256 # If the mode is 'fully-qualified-except-typing', 

257 # we use '~' only for the objects in the ``typing`` module. 

258 module_prefix = '~' if mode == 'smart' or cls_module_is_typing else '' 

259 

260 try: 

261 if ismockmodule(cls): 

262 return f':py:class:`{module_prefix}{cls.__name__}`' 

263 elif ismock(cls): 

264 return f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`' 

265 elif is_invalid_builtin_class(cls): 

266 # The above predicate never raises TypeError but should not be 

267 # evaluated before determining whether *cls* is a mocked object 

268 # or not; instead of two try-except blocks, we keep it here. 

269 return f':py:class:`{module_prefix}{_INVALID_BUILTIN_CLASSES[cls]}`' 

270 elif _is_annotated_form(cls): 

271 args = restify(cls.__args__[0], mode) 

272 meta_args = [] 

273 for m in cls.__metadata__: 

274 if isinstance(m, type): 

275 meta_args.append(restify(m, mode)) 

276 elif dataclasses.is_dataclass(m): 

277 # use restify for the repr of field values rather than repr 

278 d_fields = ', '.join([ 

279 fr"{f.name}=\ {restify(getattr(m, f.name), mode)}" 

280 for f in dataclasses.fields(m) if f.repr 

281 ]) 

282 meta_args.append(fr'{restify(type(m), mode)}\ ({d_fields})') 

283 else: 

284 meta_args.append(repr(m)) 

285 meta = ', '.join(meta_args) 

286 if sys.version_info[:2] <= (3, 11): 

287 # Hardcoded to fix errors on Python 3.11 and earlier. 

288 return fr':py:class:`~typing.Annotated`\ [{args}, {meta}]' 

289 return (f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`' 

290 fr'\ [{args}, {meta}]') 

291 elif inspect.isNewType(cls): 

292 if sys.version_info[:2] >= (3, 10): 

293 # newtypes have correct module info since Python 3.10+ 

294 return f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`' 

295 return f':py:class:`{cls.__name__}`' 

296 elif UnionType and isinstance(cls, UnionType): 

297 # Union types (PEP 585) retain their definition order when they 

298 # are printed natively and ``None``-like types are kept as is. 

299 return ' | '.join(restify(a, mode) for a in cls.__args__) 

300 elif cls.__module__ in ('__builtin__', 'builtins'): 

301 if hasattr(cls, '__args__'): 

302 if not cls.__args__: # Empty tuple, list, ... 

303 return fr':py:class:`{cls.__name__}`\ [{cls.__args__!r}]' 

304 

305 concatenated_args = ', '.join(restify(arg, mode) for arg in cls.__args__) 

306 return fr':py:class:`{cls.__name__}`\ [{concatenated_args}]' 

307 return f':py:class:`{cls.__name__}`' 

308 elif (inspect.isgenericalias(cls) 

309 and cls_module_is_typing 

310 and cls.__origin__ is Union): 

311 # *cls* is defined in ``typing``, and thus ``__args__`` must exist 

312 return ' | '.join(restify(a, mode) for a in cls.__args__) 

313 elif inspect.isgenericalias(cls): 

314 # A generic alias always has an __origin__, but it is difficult to 

315 # use a type guard on inspect.isgenericalias() 

316 # (ideally, we would use ``TypeIs`` introduced in Python 3.13). 

317 cls_name = _typing_internal_name(cls) 

318 

319 if isinstance(cls.__origin__, typing._SpecialForm): 

320 # ClassVar; Concatenate; Final; Literal; Unpack; TypeGuard; TypeIs 

321 # Required/NotRequired 

322 text = restify(cls.__origin__, mode) 

323 elif cls_name: 

324 text = f':py:class:`{module_prefix}{cls.__module__}.{cls_name}`' 

325 else: 

326 text = restify(cls.__origin__, mode) 

327 

328 __args__ = getattr(cls, '__args__', ()) 

329 if not __args__: 

330 return text 

331 if all(map(is_system_TypeVar, __args__)): 

332 # Don't print the arguments; they're all system defined type variables. 

333 return text 

334 

335 # Callable has special formatting 

336 if ( 

337 (cls_module_is_typing and _typing_internal_name(cls) == 'Callable') 

338 or (cls.__module__ == 'collections.abc' and cls.__name__ == 'Callable') 

339 ): 

340 args = ', '.join(restify(a, mode) for a in __args__[:-1]) 

341 returns = restify(__args__[-1], mode) 

342 return fr'{text}\ [[{args}], {returns}]' 

343 

344 if cls_module_is_typing and _typing_internal_name(cls.__origin__) == 'Literal': 

345 args = ', '.join(_format_literal_arg_restify(a, mode=mode) 

346 for a in cls.__args__) 

347 return fr'{text}\ [{args}]' 

348 

349 # generic representation of the parameters 

350 args = ', '.join(restify(a, mode) for a in __args__) 

351 return fr'{text}\ [{args}]' 

352 elif isinstance(cls, typing._SpecialForm): 

353 cls_name = _typing_internal_name(cls) 

354 return f':py:obj:`~{cls.__module__}.{cls_name}`' 

355 elif sys.version_info[:2] >= (3, 11) and cls is typing.Any: 

356 # handle bpo-46998 

357 return f':py:obj:`~{cls.__module__}.{cls.__name__}`' 

358 elif hasattr(cls, '__qualname__'): 

359 return f':py:class:`{module_prefix}{cls.__module__}.{cls.__qualname__}`' 

360 elif isinstance(cls, ForwardRef): 

361 return f':py:class:`{cls.__forward_arg__}`' 

362 else: 

363 # not a class (ex. TypeVar) but should have a __name__ 

364 return f':py:obj:`{module_prefix}{cls.__module__}.{cls.__name__}`' 

365 except (AttributeError, TypeError): 

366 return inspect.object_description(cls) 

367 

368 

369def _format_literal_arg_restify(arg: Any, /, *, mode: str) -> str: 

370 from sphinx.util.inspect import isenumattribute # lazy loading 

371 

372 if isenumattribute(arg): 

373 enum_cls = arg.__class__ 

374 if mode == 'smart' or enum_cls.__module__ == 'typing': 

375 # MyEnum.member 

376 return f':py:attr:`~{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}`' 

377 # module.MyEnum.member 

378 return f':py:attr:`{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}`' 

379 return repr(arg) 

380 

381 

382def stringify_annotation( 

383 annotation: Any, 

384 /, 

385 mode: _StringifyMode = 'fully-qualified-except-typing', 

386) -> str: 

387 """Stringify type annotation object. 

388 

389 :param annotation: The annotation to stringified. 

390 :param mode: Specify a method how annotations will be stringified. 

391 

392 'fully-qualified-except-typing' 

393 Show the module name and qualified name of the annotation except 

394 the "typing" module. 

395 'smart' 

396 Show the name of the annotation. 

397 'fully-qualified' 

398 Show the module name and qualified name of the annotation. 

399 """ 

400 from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading 

401 from sphinx.util.inspect import isNewType # lazy loading 

402 

403 valid_modes = {'fully-qualified-except-typing', 'fully-qualified', 'smart'} 

404 if mode not in valid_modes: 

405 valid = ', '.join(map(repr, sorted(valid_modes))) 

406 msg = f'mode must be one of {valid}; got {mode!r}' 

407 raise ValueError(msg) 

408 

409 # things that are not types 

410 if annotation is None or annotation == NoneType: 

411 return 'None' 

412 if annotation is Ellipsis: 

413 return '...' 

414 if isinstance(annotation, str): 

415 if annotation.startswith("'") and annotation.endswith("'"): 

416 # Might be a double Forward-ref'ed type. Go unquoting. 

417 return annotation[1:-1] 

418 return annotation 

419 if not annotation: 

420 return repr(annotation) 

421 

422 module_prefix = '~' if mode == 'smart' else '' 

423 

424 # The values below must be strings if the objects are well-formed. 

425 annotation_qualname: str = getattr(annotation, '__qualname__', '') 

426 annotation_module: str = getattr(annotation, '__module__', '') 

427 annotation_name: str = getattr(annotation, '__name__', '') 

428 annotation_module_is_typing = annotation_module == 'typing' 

429 

430 # Extract the annotation's base type by considering formattable cases 

431 if isinstance(annotation, TypeVar) and not _is_unpack_form(annotation): 

432 # typing_extensions.Unpack is incorrectly determined as a TypeVar 

433 if annotation_module_is_typing and mode in {'fully-qualified-except-typing', 'smart'}: 

434 return annotation_name 

435 return module_prefix + f'{annotation_module}.{annotation_name}' 

436 elif isNewType(annotation): 

437 if sys.version_info[:2] >= (3, 10): 

438 # newtypes have correct module info since Python 3.10+ 

439 return module_prefix + f'{annotation_module}.{annotation_name}' 

440 return annotation_name 

441 elif ismockmodule(annotation): 

442 return module_prefix + annotation_name 

443 elif ismock(annotation): 

444 return module_prefix + f'{annotation_module}.{annotation_name}' 

445 elif is_invalid_builtin_class(annotation): 

446 return module_prefix + _INVALID_BUILTIN_CLASSES[annotation] 

447 elif _is_annotated_form(annotation): # for py39+ 

448 pass 

449 elif annotation_module == 'builtins' and annotation_qualname: 

450 args = getattr(annotation, '__args__', None) 

451 if args is None: 

452 return annotation_qualname 

453 

454 # PEP 585 generic 

455 if not args: # Empty tuple, list, ... 

456 return repr(annotation) 

457 

458 concatenated_args = ', '.join(stringify_annotation(arg, mode) for arg in args) 

459 return f'{annotation_qualname}[{concatenated_args}]' 

460 else: 

461 # add other special cases that can be directly formatted 

462 pass 

463 

464 module_prefix = f'{annotation_module}.' 

465 annotation_forward_arg: str | None = getattr(annotation, '__forward_arg__', None) 

466 if annotation_qualname or (annotation_module_is_typing and not annotation_forward_arg): 

467 if mode == 'smart': 

468 module_prefix = f'~{module_prefix}' 

469 if annotation_module_is_typing and mode == 'fully-qualified-except-typing': 

470 module_prefix = '' 

471 elif _is_unpack_form(annotation) and annotation_module == 'typing_extensions': 

472 module_prefix = '~' if mode == 'smart' else '' 

473 else: 

474 module_prefix = '' 

475 

476 if annotation_module_is_typing: 

477 if annotation_forward_arg: 

478 # handle ForwardRefs 

479 qualname = annotation_forward_arg 

480 else: 

481 if internal_name := _typing_internal_name(annotation): 

482 qualname = internal_name 

483 elif annotation_qualname: 

484 qualname = annotation_qualname 

485 else: 

486 # in this case, we know that the annotation is a member 

487 # of ``typing`` and all of them define ``__origin__`` 

488 qualname = stringify_annotation( 

489 annotation.__origin__, 'fully-qualified-except-typing', 

490 ).replace('typing.', '') # ex. Union 

491 elif annotation_qualname: 

492 qualname = annotation_qualname 

493 elif hasattr(annotation, '__origin__'): 

494 # instantiated generic provided by a user 

495 qualname = stringify_annotation(annotation.__origin__, mode) 

496 elif UnionType and isinstance(annotation, UnionType): # types.UnionType (for py3.10+) 

497 qualname = 'types.UnionType' 

498 else: 

499 # we weren't able to extract the base type, appending arguments would 

500 # only make them appear twice 

501 return repr(annotation) 

502 

503 # Process the generic arguments (if any). 

504 # They must be a list or a tuple, otherwise they are considered 'broken'. 

505 annotation_args = getattr(annotation, '__args__', ()) 

506 if annotation_args and isinstance(annotation_args, (list, tuple)): 

507 if ( 

508 qualname in {'Union', 'types.UnionType'} 

509 and all(getattr(a, '__origin__', ...) is typing.Literal for a in annotation_args) 

510 ): 

511 # special case to flatten a Union of Literals into a literal 

512 flattened_args = typing.Literal[annotation_args].__args__ # type: ignore[attr-defined] 

513 args = ', '.join(_format_literal_arg_stringify(a, mode=mode) 

514 for a in flattened_args) 

515 return f'{module_prefix}Literal[{args}]' 

516 if qualname in {'Optional', 'Union', 'types.UnionType'}: 

517 return ' | '.join(stringify_annotation(a, mode) for a in annotation_args) 

518 elif qualname == 'Callable': 

519 args = ', '.join(stringify_annotation(a, mode) for a in annotation_args[:-1]) 

520 returns = stringify_annotation(annotation_args[-1], mode) 

521 return f'{module_prefix}Callable[[{args}], {returns}]' 

522 elif qualname == 'Literal': 

523 args = ', '.join(_format_literal_arg_stringify(a, mode=mode) 

524 for a in annotation_args) 

525 return f'{module_prefix}Literal[{args}]' 

526 elif _is_annotated_form(annotation): # for py39+ 

527 args = stringify_annotation(annotation_args[0], mode) 

528 meta_args = [] 

529 for m in annotation.__metadata__: 

530 if isinstance(m, type): 

531 meta_args.append(stringify_annotation(m, mode)) 

532 elif dataclasses.is_dataclass(m): 

533 # use stringify_annotation for the repr of field values rather than repr 

534 d_fields = ', '.join([ 

535 f"{f.name}={stringify_annotation(getattr(m, f.name), mode)}" 

536 for f in dataclasses.fields(m) if f.repr 

537 ]) 

538 meta_args.append(f'{stringify_annotation(type(m), mode)}({d_fields})') 

539 else: 

540 meta_args.append(repr(m)) 

541 meta = ', '.join(meta_args) 

542 if sys.version_info[:2] <= (3, 9): 

543 if mode == 'smart': 

544 return f'~typing.Annotated[{args}, {meta}]' 

545 if mode == 'fully-qualified': 

546 return f'typing.Annotated[{args}, {meta}]' 

547 if sys.version_info[:2] <= (3, 11): 

548 if mode == 'fully-qualified-except-typing': 

549 return f'Annotated[{args}, {meta}]' 

550 module_prefix = module_prefix.replace('builtins', 'typing') 

551 return f'{module_prefix}Annotated[{args}, {meta}]' 

552 return f'{module_prefix}Annotated[{args}, {meta}]' 

553 elif all(is_system_TypeVar(a) for a in annotation_args): 

554 # Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT]) 

555 return module_prefix + qualname 

556 else: 

557 args = ', '.join(stringify_annotation(a, mode) for a in annotation_args) 

558 return f'{module_prefix}{qualname}[{args}]' 

559 

560 return module_prefix + qualname 

561 

562 

563def _format_literal_arg_stringify(arg: Any, /, *, mode: str) -> str: 

564 from sphinx.util.inspect import isenumattribute # lazy loading 

565 

566 if isenumattribute(arg): 

567 enum_cls = arg.__class__ 

568 if mode == 'smart' or enum_cls.__module__ == 'typing': 

569 # MyEnum.member 

570 return f'{enum_cls.__qualname__}.{arg.name}' 

571 # module.MyEnum.member 

572 return f'{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}' 

573 return repr(arg) 

574 

575 

576# deprecated name -> (object to return, canonical path or empty string, removal version) 

577_DEPRECATED_OBJECTS: dict[str, tuple[Any, str, tuple[int, int]]] = { 

578 'stringify': (stringify_annotation, 'sphinx.util.typing.stringify_annotation', (8, 0)), 

579} 

580 

581 

582def __getattr__(name: str) -> Any: 

583 if name not in _DEPRECATED_OBJECTS: 

584 msg = f'module {__name__!r} has no attribute {name!r}' 

585 raise AttributeError(msg) 

586 

587 from sphinx.deprecation import _deprecation_warning 

588 

589 deprecated_object, canonical_name, remove = _DEPRECATED_OBJECTS[name] 

590 _deprecation_warning(__name__, name, canonical_name, remove=remove) 

591 return deprecated_object