Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py: 50%

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

1186 statements  

1# util/langhelpers.py 

2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: https://www.opensource.org/licenses/mit-license.php 

7# mypy: allow-untyped-defs, allow-untyped-calls 

8 

9"""Routines to help with the creation, loading and introspection of 

10modules, classes, hierarchies, attributes, functions, and methods. 

11 

12""" 

13 

14from __future__ import annotations 

15 

16import collections 

17import enum 

18from functools import update_wrapper 

19import importlib.metadata 

20import importlib.util 

21import inspect 

22import itertools 

23import linecache 

24import operator 

25import re 

26import sys 

27import textwrap 

28import threading 

29import types 

30from types import CodeType 

31from types import ModuleType 

32from typing import Any 

33from typing import Callable 

34from typing import cast 

35from typing import Dict 

36from typing import FrozenSet 

37from typing import Generic 

38from typing import Iterator 

39from typing import List 

40from typing import Literal 

41from typing import NoReturn 

42from typing import Optional 

43from typing import overload 

44from typing import Sequence 

45from typing import Set 

46from typing import Tuple 

47from typing import Type 

48from typing import TYPE_CHECKING 

49from typing import TypeVar 

50from typing import Union 

51import warnings 

52import weakref 

53 

54from . import _collections 

55from . import compat 

56from .. import exc 

57 

58_T = TypeVar("_T") 

59_T_co = TypeVar("_T_co", covariant=True) 

60_F = TypeVar("_F", bound=Callable[..., Any]) 

61_MA = TypeVar("_MA", bound="HasMemoized.memoized_attribute[Any]") 

62_M = TypeVar("_M", bound=ModuleType) 

63 

64 

65def restore_annotations( 

66 cls: type, new_annotations: dict[str, Any] 

67) -> Callable[[], None]: 

68 """apply alternate annotations to a class, with a callable to restore 

69 the pristine state of the former. 

70 This is used strictly to provide dataclasses on a mapped class, where 

71 in some cases where are making dataclass fields based on an attribute 

72 that is actually a python descriptor on a superclass which we called 

73 to get a value. 

74 if dataclasses were to give us a way to achieve this without swapping 

75 __annotations__, that would be much better. 

76 """ 

77 delattr_ = object() 

78 

79 # pep-649 means classes have "__annotate__", and it's a callable. if it's 

80 # there and is None, we're in "legacy future mode", where it's python 3.14 

81 # or higher and "from __future__ import annotations" is set. in "legacy 

82 # future mode" we have to do the same steps we do for older pythons, 

83 # __annotate__ can be ignored 

84 is_pep649 = hasattr(cls, "__annotate__") and cls.__annotate__ is not None 

85 

86 if is_pep649: 

87 memoized = { 

88 "__annotate__": getattr(cls, "__annotate__", delattr_), 

89 } 

90 else: 

91 memoized = { 

92 "__annotations__": getattr(cls, "__annotations__", delattr_) 

93 } 

94 

95 cls.__annotations__ = new_annotations 

96 

97 def restore(): 

98 for k, v in memoized.items(): 

99 if v is delattr_: 

100 delattr(cls, k) 

101 else: 

102 setattr(cls, k, v) 

103 

104 return restore 

105 

106 

107def md5_hex(x: Any) -> str: 

108 x = x.encode("utf-8") 

109 m = compat.md5_not_for_security() 

110 m.update(x) 

111 return cast(str, m.hexdigest()) 

112 

113 

114class safe_reraise: 

115 """Reraise an exception after invoking some 

116 handler code. 

117 

118 Stores the existing exception info before 

119 invoking so that it is maintained across a potential 

120 coroutine context switch. 

121 

122 e.g.:: 

123 

124 try: 

125 sess.commit() 

126 except: 

127 with safe_reraise(): 

128 sess.rollback() 

129 

130 TODO: we should at some point evaluate current behaviors in this regard 

131 based on current greenlet, gevent/eventlet implementations in Python 3, and 

132 also see the degree to which our own asyncio (based on greenlet also) is 

133 impacted by this. .rollback() will cause IO / context switch to occur in 

134 all these scenarios; what happens to the exception context from an 

135 "except:" block if we don't explicitly store it? Original issue was #2703. 

136 

137 """ 

138 

139 __slots__ = ("_exc_info",) 

140 

141 _exc_info: Union[ 

142 None, 

143 Tuple[ 

144 Type[BaseException], 

145 BaseException, 

146 types.TracebackType, 

147 ], 

148 Tuple[None, None, None], 

149 ] 

150 

151 def __enter__(self) -> None: 

152 self._exc_info = sys.exc_info() 

153 

154 def __exit__( 

155 self, 

156 type_: Optional[Type[BaseException]], 

157 value: Optional[BaseException], 

158 traceback: Optional[types.TracebackType], 

159 ) -> NoReturn: 

160 assert self._exc_info is not None 

161 # see #2703 for notes 

162 if type_ is None: 

163 exc_type, exc_value, exc_tb = self._exc_info 

164 assert exc_value is not None 

165 self._exc_info = None # remove potential circular references 

166 raise exc_value.with_traceback(exc_tb) 

167 else: 

168 self._exc_info = None # remove potential circular references 

169 assert value is not None 

170 raise value.with_traceback(traceback) 

171 

172 

173def walk_subclasses(cls: Type[_T]) -> Iterator[Type[_T]]: 

174 seen: Set[Any] = set() 

175 

176 stack = [cls] 

177 while stack: 

178 cls = stack.pop() 

179 if cls in seen: 

180 continue 

181 else: 

182 seen.add(cls) 

183 stack.extend(cls.__subclasses__()) 

184 yield cls 

185 

186 

187def string_or_unprintable(element: Any) -> str: 

188 if isinstance(element, str): 

189 return element 

190 else: 

191 try: 

192 return str(element) 

193 except Exception: 

194 return "unprintable element %r" % element 

195 

196 

197def clsname_as_plain_name( 

198 cls: Type[Any], use_name: Optional[str] = None 

199) -> str: 

200 name = use_name or cls.__name__ 

201 return " ".join(n.lower() for n in re.findall(r"([A-Z][a-z]+|SQL)", name)) 

202 

203 

204def method_is_overridden( 

205 instance_or_cls: Union[Type[Any], object], 

206 against_method: Callable[..., Any], 

207) -> bool: 

208 """Return True if the two class methods don't match.""" 

209 

210 if not isinstance(instance_or_cls, type): 

211 current_cls = instance_or_cls.__class__ 

212 else: 

213 current_cls = instance_or_cls 

214 

215 method_name = against_method.__name__ 

216 

217 current_method: types.MethodType = getattr(current_cls, method_name) 

218 

219 return current_method != against_method 

220 

221 

222def decode_slice(slc: slice) -> Tuple[Any, ...]: 

223 """decode a slice object as sent to __getitem__. 

224 

225 takes into account the 2.5 __index__() method, basically. 

226 

227 """ 

228 ret: List[Any] = [] 

229 for x in slc.start, slc.stop, slc.step: 

230 if hasattr(x, "__index__"): 

231 x = x.__index__() 

232 ret.append(x) 

233 return tuple(ret) 

234 

235 

236def _unique_symbols(used: Sequence[str], *bases: str) -> Iterator[str]: 

237 used_set = set(used) 

238 for base in bases: 

239 pool = itertools.chain( 

240 (base,), 

241 map(lambda i: base + str(i), range(1000)), 

242 ) 

243 for sym in pool: 

244 if sym not in used_set: 

245 used_set.add(sym) 

246 yield sym 

247 break 

248 else: 

249 raise NameError("exhausted namespace for symbol base %s" % base) 

250 

251 

252def map_bits(fn: Callable[[int], Any], n: int) -> Iterator[Any]: 

253 """Call the given function given each nonzero bit from n.""" 

254 

255 while n: 

256 b = n & (~n + 1) 

257 yield fn(b) 

258 n ^= b 

259 

260 

261_Fn = TypeVar("_Fn", bound="Callable[..., Any]") 

262 

263# this seems to be in flux in recent mypy versions 

264 

265 

266def decorator(target: Callable[..., Any]) -> Callable[[_Fn], _Fn]: 

267 """A signature-matching decorator factory.""" 

268 

269 def decorate(fn: _Fn) -> _Fn: 

270 if not inspect.isfunction(fn) and not inspect.ismethod(fn): 

271 raise Exception("not a decoratable function") 

272 

273 # Python 3.14 defer creating __annotations__ until its used. 

274 # We do not want to create __annotations__ now. 

275 annofunc = getattr(fn, "__annotate__", None) 

276 if annofunc is not None: 

277 fn.__annotate__ = None # type: ignore[union-attr] 

278 try: 

279 spec = compat.inspect_getfullargspec(fn) 

280 finally: 

281 fn.__annotate__ = annofunc # type: ignore[union-attr] 

282 else: 

283 spec = compat.inspect_getfullargspec(fn) 

284 

285 # Do not generate code for annotations. 

286 # update_wrapper() copies the annotation from fn to decorated. 

287 # We use dummy defaults for code generation to avoid having 

288 # copy of large globals for compiling. 

289 # We copy __defaults__ and __kwdefaults__ from fn to decorated. 

290 empty_defaults = (None,) * len(spec.defaults or ()) 

291 empty_kwdefaults = dict.fromkeys(spec.kwonlydefaults or ()) 

292 spec = spec._replace( 

293 annotations={}, 

294 defaults=empty_defaults, 

295 kwonlydefaults=empty_kwdefaults, 

296 ) 

297 

298 names = ( 

299 tuple(cast("Tuple[str, ...]", spec[0])) 

300 + cast("Tuple[str, ...]", spec[1:3]) 

301 + (fn.__name__,) 

302 ) 

303 targ_name, fn_name = _unique_symbols(names, "target", "fn") 

304 

305 metadata: Dict[str, Optional[str]] = dict(target=targ_name, fn=fn_name) 

306 metadata.update(format_argspec_plus(spec, grouped=False)) 

307 metadata["name"] = fn.__name__ 

308 

309 if inspect.iscoroutinefunction(fn): 

310 metadata["prefix"] = "async " 

311 metadata["target_prefix"] = "await " 

312 metadata["target_suffix"] = "" 

313 elif inspect.isgeneratorfunction(fn): 

314 # a generator function has to remain a generator function 

315 # after decoration; tools such as pytest fixtures test for 

316 # inspect.isgeneratorfunction() and will otherwise never 

317 # iterate the function at all 

318 metadata["prefix"] = "" 

319 metadata["target_prefix"] = "(yield from " 

320 metadata["target_suffix"] = ")" 

321 else: 

322 metadata["prefix"] = "" 

323 metadata["target_prefix"] = "" 

324 metadata["target_suffix"] = "" 

325 

326 # look for __ positional arguments. This is a convention in 

327 # SQLAlchemy that arguments should be passed positionally 

328 # rather than as keyword 

329 # arguments. note that apply_pos doesn't currently work in all cases 

330 # such as when a kw-only indicator "*" is present, which is why 

331 # we limit the use of this to just that case we can detect. As we add 

332 # more kinds of methods that use @decorator, things may have to 

333 # be further improved in this area 

334 if "__" in repr(spec[0]): 

335 code = """\ 

336%(prefix)sdef %(name)s%(grouped_args)s: 

337 return %(target_prefix)s%(target)s(%(fn)s, %(apply_pos)s)%(target_suffix)s 

338""" % metadata 

339 else: 

340 code = """\ 

341%(prefix)sdef %(name)s%(grouped_args)s: 

342 return %(target_prefix)s%(target)s(%(fn)s, %(apply_kw)s)%(target_suffix)s 

343""" % metadata 

344 

345 env: Dict[str, Any] = { 

346 targ_name: target, 

347 fn_name: fn, 

348 "__name__": fn.__module__, 

349 } 

350 

351 # the target's name is part of the description because decorators 

352 # built here do get stacked (see ValuesBase.values()), and 

353 # update_wrapper() gives every layer the same __qualname__; without 

354 # it the outer layer would claim the inner layer's source. 

355 decorated = cast( 

356 types.FunctionType, 

357 exec_code_in_env( 

358 code, 

359 env, 

360 fn.__name__, 

361 f"{target.__name__}() wrapper for " 

362 f"{fn.__module__}.{fn.__qualname__}", 

363 ), 

364 ) 

365 decorated.__defaults__ = fn.__defaults__ 

366 decorated.__kwdefaults__ = fn.__kwdefaults__ # type: ignore[union-attr] # noqa: E501 

367 return update_wrapper(decorated, fn) # type: ignore[return-value] 

368 

369 return update_wrapper(decorate, target) # type: ignore[return-value] 

370 

371 

372_LinecacheEntry = Tuple[int, None, List[str], str] 

373 

374 

375def _linecache_cache_getter(): 

376 """safe getter for linecache.cache 

377 

378 linecache.cache despite being non-underscored and widely used is 

379 nonetheless not documented by cPython. Therefore we cannot trust that it 

380 it's present in third party Python distributions, or that it wont 

381 suddenly be removed or changed. Guard against this such that linecache 

382 features will be silently disabled if this should happen. Unit tests in 

383 test/base/test_utils.py ensures linecache.cache remains available for new 

384 releases. 

385 

386 """ 

387 try: 

388 linecache_cache = linecache.cache 

389 except AttributeError: 

390 raise 

391 else: 

392 if not isinstance(linecache_cache, dict): 

393 raise AttributeError("linecache has changed from being a dict") 

394 return linecache_cache 

395 

396 

397def _remove_linecache_entry(filename: str, entry: _LinecacheEntry) -> None: 

398 """Discard a ``linecache`` entry made by :func:`.exec_code_in_env`, if 

399 it is still the live one. 

400 

401 Runs from a weakref finalizer. 

402 

403 This function is only established if we actually added an entry to the 

404 linecache within exec_code_in_env. 

405 

406 """ 

407 try: 

408 linecache_cache = _linecache_cache_getter() 

409 except AttributeError: 

410 # linecache.cache is gone or is no longer a dict; as this runs 

411 # from a finalizer, an exception here would only surface as an 

412 # unraisable error 

413 return 

414 

415 if linecache_cache.get(filename) is entry: 

416 linecache_cache.pop(filename, None) 

417 

418 

419def exec_code_in_env( 

420 code: Union[str, types.CodeType], 

421 env: Dict[str, Any], 

422 fn_name: str, 

423 description: Optional[str] = None, 

424) -> Callable[..., Any]: 

425 """Exec generated ``code`` in ``env`` and return the function it defines. 

426 

427 If ``description`` is passed, the code is compiled against a synthetic 

428 filename which is registered with :mod:`linecache`, allowing traceback 

429 frames to reference the actual source code being referenced. 

430 

431 entries are placed in the cache without an expiration time and a 

432 weakref.finalize() is applied to the function to remove the linecache 

433 entry if and when the function is garbage collected. 

434 

435 """ 

436 filename: Optional[str] = None 

437 entry: Optional[_LinecacheEntry] = None 

438 

439 if description is not None: 

440 assert isinstance(code, str), ( 

441 "a description is only meaningful for source that has not " 

442 "already been compiled" 

443 ) 

444 filename = f"<sqlalchemy generated {description}>" 

445 

446 try: 

447 linecache_cache = _linecache_cache_getter() 

448 except AttributeError: 

449 pass 

450 else: 

451 entry = (len(code), None, code.splitlines(True), filename) 

452 linecache_cache[filename] = entry 

453 code = compile(code, filename, "exec") 

454 

455 exec(code, env) 

456 fn = env[fn_name] 

457 

458 if filename is not None and entry is not None: 

459 # apply a finalizer that addresses on-the-fly functions, ORM mapped 

460 # classes, etc. which might be garbage collected 

461 finalizer = weakref.finalize( 

462 fn, _remove_linecache_entry, filename, entry 

463 ) 

464 # atexit is a property on the C implementation; typeshed 

465 # renders finalize with an empty __slots__ 

466 finalizer.atexit = False 

467 

468 return fn # type: ignore[no-any-return] 

469 

470 

471_PF = TypeVar("_PF") 

472_TE = TypeVar("_TE") 

473 

474 

475class PluginLoader: 

476 def __init__( 

477 self, group: str, auto_fn: Optional[Callable[..., Any]] = None 

478 ): 

479 self.group = group 

480 self.impls: Dict[str, Any] = {} 

481 self.auto_fn = auto_fn 

482 

483 def clear(self): 

484 self.impls.clear() 

485 

486 def load(self, name: str) -> Any: 

487 if name in self.impls: 

488 return self.impls[name]() 

489 

490 if self.auto_fn: 

491 loader = self.auto_fn(name) 

492 if loader: 

493 self.impls[name] = loader 

494 return loader() 

495 

496 for impl in compat.importlib_metadata_get(self.group): 

497 if impl.name == name: 

498 self.impls[name] = impl.load 

499 return impl.load() 

500 

501 raise exc.NoSuchModuleError( 

502 "Can't load plugin: %s:%s" % (self.group, name) 

503 ) 

504 

505 def register(self, name: str, modulepath: str, objname: str) -> None: 

506 def load(): 

507 mod = __import__(modulepath) 

508 for token in modulepath.split(".")[1:]: 

509 mod = getattr(mod, token) 

510 return getattr(mod, objname) 

511 

512 self.impls[name] = load 

513 

514 def deregister(self, name: str) -> None: 

515 del self.impls[name] 

516 

517 

518def _inspect_func_args(fn): 

519 try: 

520 co_varkeywords = inspect.CO_VARKEYWORDS 

521 except AttributeError: 

522 # https://docs.python.org/3/library/inspect.html 

523 # The flags are specific to CPython, and may not be defined in other 

524 # Python implementations. Furthermore, the flags are an implementation 

525 # detail, and can be removed or deprecated in future Python releases. 

526 spec = compat.inspect_getfullargspec(fn) 

527 return spec[0], bool(spec[2]) 

528 else: 

529 # use fn.__code__ plus flags to reduce method call overhead 

530 co = fn.__code__ 

531 nargs = co.co_argcount 

532 return ( 

533 list(co.co_varnames[:nargs]), 

534 bool(co.co_flags & co_varkeywords), 

535 ) 

536 

537 

538@overload 

539def get_cls_kwargs( 

540 cls: type, 

541 *, 

542 _set: Optional[Set[str]] = None, 

543 raiseerr: Literal[True] = ..., 

544) -> Set[str]: ... 

545 

546 

547@overload 

548def get_cls_kwargs( 

549 cls: type, *, _set: Optional[Set[str]] = None, raiseerr: bool = False 

550) -> Optional[Set[str]]: ... 

551 

552 

553def get_cls_kwargs( 

554 cls: type, *, _set: Optional[Set[str]] = None, raiseerr: bool = False 

555) -> Optional[Set[str]]: 

556 r"""Return the full set of inherited kwargs for the given `cls`. 

557 

558 Probes a class's __init__ method, collecting all named arguments. If the 

559 __init__ defines a \**kwargs catch-all, then the constructor is presumed 

560 to pass along unrecognized keywords to its base classes, and the 

561 collection process is repeated recursively on each of the bases. 

562 

563 Uses a subset of inspect.getfullargspec() to cut down on method overhead, 

564 as this is used within the Core typing system to create copies of type 

565 objects which is a performance-sensitive operation. 

566 

567 No anonymous tuple arguments please ! 

568 

569 """ 

570 toplevel = _set is None 

571 if toplevel: 

572 _set = set() 

573 assert _set is not None 

574 

575 ctr = cls.__dict__.get("__init__", False) 

576 

577 has_init = ( 

578 ctr 

579 and isinstance(ctr, types.FunctionType) 

580 and isinstance(ctr.__code__, types.CodeType) 

581 ) 

582 

583 if has_init: 

584 names, has_kw = _inspect_func_args(ctr) 

585 _set.update(names) 

586 

587 if not has_kw and not toplevel: 

588 if raiseerr: 

589 raise TypeError( 

590 f"given cls {cls} doesn't have an __init__ method" 

591 ) 

592 else: 

593 return None 

594 else: 

595 has_kw = False 

596 

597 if not has_init or has_kw: 

598 for c in cls.__bases__: 

599 if get_cls_kwargs(c, _set=_set) is None: 

600 break 

601 

602 _set.discard("self") 

603 return _set 

604 

605 

606def get_func_kwargs(func: Callable[..., Any]) -> List[str]: 

607 """Return the set of legal kwargs for the given `func`. 

608 

609 Uses getargspec so is safe to call for methods, functions, 

610 etc. 

611 

612 """ 

613 

614 return compat.inspect_getfullargspec(func)[0] 

615 

616 

617def get_callable_argspec( 

618 fn: Callable[..., Any], no_self: bool = False, _is_init: bool = False 

619) -> compat.FullArgSpec: 

620 """Return the argument signature for any callable. 

621 

622 All pure-Python callables are accepted, including 

623 functions, methods, classes, objects with __call__; 

624 builtins and other edge cases like functools.partial() objects 

625 raise a TypeError. 

626 

627 """ 

628 if inspect.isbuiltin(fn): 

629 raise TypeError("Can't inspect builtin: %s" % fn) 

630 elif inspect.isfunction(fn) or ( 

631 hasattr(fn, "__code__") 

632 and not inspect.isclass(fn) 

633 and not inspect.ismethod(fn) 

634 ): 

635 if _is_init and no_self: 

636 spec = compat.inspect_getfullargspec(fn) 

637 return compat.FullArgSpec( 

638 spec.args[1:], 

639 spec.varargs, 

640 spec.varkw, 

641 spec.defaults, 

642 spec.kwonlyargs, 

643 spec.kwonlydefaults, 

644 spec.annotations, 

645 ) 

646 else: 

647 return compat.inspect_getfullargspec(fn) 

648 elif inspect.ismethod(fn): 

649 if no_self and (_is_init or fn.__self__): 

650 spec = compat.inspect_getfullargspec(fn.__func__) 

651 return compat.FullArgSpec( 

652 spec.args[1:], 

653 spec.varargs, 

654 spec.varkw, 

655 spec.defaults, 

656 spec.kwonlyargs, 

657 spec.kwonlydefaults, 

658 spec.annotations, 

659 ) 

660 else: 

661 return compat.inspect_getfullargspec(fn.__func__) 

662 elif inspect.isclass(fn): 

663 return get_callable_argspec( 

664 fn.__init__, no_self=no_self, _is_init=True 

665 ) 

666 elif hasattr(fn, "__func__"): 

667 return compat.inspect_getfullargspec(fn.__func__) 

668 elif hasattr(fn, "__call__"): 

669 if inspect.ismethod(fn.__call__): 

670 return get_callable_argspec(fn.__call__, no_self=no_self) 

671 else: 

672 raise TypeError("Can't inspect callable: %s" % fn) 

673 else: 

674 raise TypeError("Can't inspect callable: %s" % fn) 

675 

676 

677def format_argspec_plus( 

678 fn: Union[Callable[..., Any], compat.FullArgSpec], grouped: bool = True 

679) -> Dict[str, Optional[str]]: 

680 """Returns a dictionary of formatted, introspected function arguments. 

681 

682 A enhanced variant of inspect.formatargspec to support code generation. 

683 

684 fn 

685 An inspectable callable or tuple of inspect getargspec() results. 

686 grouped 

687 Defaults to True; include (parens, around, argument) lists 

688 

689 Returns: 

690 

691 args 

692 Full inspect.formatargspec for fn 

693 self_arg 

694 The name of the first positional argument, varargs[0], or None 

695 if the function defines no positional arguments. 

696 apply_pos 

697 args, re-written in calling rather than receiving syntax. Arguments are 

698 passed positionally. 

699 apply_kw 

700 Like apply_pos, except keyword-ish args are passed as keywords. 

701 apply_pos_proxied 

702 Like apply_pos but omits the self/cls argument 

703 

704 Example:: 

705 

706 >>> format_argspec_plus(lambda self, a, b, c=3, **d: 123) 

707 {'grouped_args': '(self, a, b, c=3, **d)', 

708 'self_arg': 'self', 

709 'apply_kw': '(self, a, b, c=c, **d)', 

710 'apply_pos': '(self, a, b, c, **d)'} 

711 

712 """ 

713 if callable(fn): 

714 spec = compat.inspect_getfullargspec(fn) 

715 else: 

716 spec = fn 

717 

718 args = compat.inspect_formatargspec(*spec) 

719 

720 apply_pos = compat.inspect_formatargspec( 

721 spec[0], spec[1], spec[2], None, spec[4] 

722 ) 

723 

724 if spec[0]: 

725 self_arg = spec[0][0] 

726 

727 apply_pos_proxied = compat.inspect_formatargspec( 

728 spec[0][1:], spec[1], spec[2], None, spec[4] 

729 ) 

730 

731 elif spec[1]: 

732 # I'm not sure what this is 

733 self_arg = "%s[0]" % spec[1] 

734 

735 apply_pos_proxied = apply_pos 

736 else: 

737 self_arg = None 

738 apply_pos_proxied = apply_pos 

739 

740 num_defaults = 0 

741 if spec[3]: 

742 num_defaults += len(cast(Tuple[Any], spec[3])) 

743 if spec[4]: 

744 num_defaults += len(spec[4]) 

745 

746 name_args = spec[0] + spec[4] 

747 

748 defaulted_vals: Union[List[str], Tuple[()]] 

749 

750 if num_defaults: 

751 defaulted_vals = name_args[0 - num_defaults :] 

752 else: 

753 defaulted_vals = () 

754 

755 apply_kw = compat.inspect_formatargspec( 

756 name_args, 

757 spec[1], 

758 spec[2], 

759 defaulted_vals, 

760 formatvalue=lambda x: "=" + str(x), 

761 ) 

762 

763 if spec[0]: 

764 apply_kw_proxied = compat.inspect_formatargspec( 

765 name_args[1:], 

766 spec[1], 

767 spec[2], 

768 defaulted_vals, 

769 formatvalue=lambda x: "=" + str(x), 

770 ) 

771 else: 

772 apply_kw_proxied = apply_kw 

773 

774 if grouped: 

775 return dict( 

776 grouped_args=args, 

777 self_arg=self_arg, 

778 apply_pos=apply_pos, 

779 apply_kw=apply_kw, 

780 apply_pos_proxied=apply_pos_proxied, 

781 apply_kw_proxied=apply_kw_proxied, 

782 ) 

783 else: 

784 return dict( 

785 grouped_args=args, 

786 self_arg=self_arg, 

787 apply_pos=apply_pos[1:-1], 

788 apply_kw=apply_kw[1:-1], 

789 apply_pos_proxied=apply_pos_proxied[1:-1], 

790 apply_kw_proxied=apply_kw_proxied[1:-1], 

791 ) 

792 

793 

794def format_argspec_init(method, grouped=True): 

795 """format_argspec_plus with considerations for typical __init__ methods 

796 

797 Wraps format_argspec_plus with error handling strategies for typical 

798 __init__ cases: 

799 

800 .. sourcecode:: text 

801 

802 object.__init__ -> (self) 

803 other unreflectable (usually C) -> (self, *args, **kwargs) 

804 

805 """ 

806 if method is object.__init__: 

807 grouped_args = "(self)" 

808 args = "(self)" if grouped else "self" 

809 proxied = "()" if grouped else "" 

810 else: 

811 try: 

812 return format_argspec_plus(method, grouped=grouped) 

813 except TypeError: 

814 grouped_args = "(self, *args, **kwargs)" 

815 args = grouped_args if grouped else "self, *args, **kwargs" 

816 proxied = "(*args, **kwargs)" if grouped else "*args, **kwargs" 

817 return dict( 

818 self_arg="self", 

819 grouped_args=grouped_args, 

820 apply_pos=args, 

821 apply_kw=args, 

822 apply_pos_proxied=proxied, 

823 apply_kw_proxied=proxied, 

824 ) 

825 

826 

827def create_proxy_methods( 

828 target_cls: Type[Any], 

829 target_cls_sphinx_name: str, 

830 proxy_cls_sphinx_name: str, 

831 classmethods: Sequence[str] = (), 

832 methods: Sequence[str] = (), 

833 attributes: Sequence[str] = (), 

834 use_intermediate_variable: Sequence[str] = (), 

835) -> Callable[[_T], _T]: 

836 """A class decorator indicating attributes should refer to a proxy 

837 class. 

838 

839 This decorator is now a "marker" that does nothing at runtime. Instead, 

840 it is consumed by the tools/generate_proxy_methods.py script to 

841 statically generate proxy methods and attributes that are fully 

842 recognized by typing tools such as mypy. 

843 

844 """ 

845 

846 def decorate(cls): 

847 return cls 

848 

849 return decorate 

850 

851 

852def getargspec_init(method): 

853 """inspect.getargspec with considerations for typical __init__ methods 

854 

855 Wraps inspect.getargspec with error handling for typical __init__ cases: 

856 

857 .. sourcecode:: text 

858 

859 object.__init__ -> (self) 

860 other unreflectable (usually C) -> (self, *args, **kwargs) 

861 

862 """ 

863 try: 

864 return compat.inspect_getfullargspec(method) 

865 except TypeError: 

866 if method is object.__init__: 

867 return (["self"], None, None, None) 

868 else: 

869 return (["self"], "args", "kwargs", None) 

870 

871 

872def unbound_method_to_callable(func_or_cls): 

873 """Adjust the incoming callable such that a 'self' argument is not 

874 required. 

875 

876 """ 

877 

878 if isinstance(func_or_cls, types.MethodType) and not func_or_cls.__self__: 

879 return func_or_cls.__func__ 

880 else: 

881 return func_or_cls 

882 

883 

884class GenericRepr: 

885 """Encapsulates the logic for creating a generic __repr__() string. 

886 

887 This class allows for the repr structure to be created, then modified 

888 (e.g., changing the class name), before being rendered as a string. 

889 

890 .. versionadded:: 2.1 

891 """ 

892 

893 __slots__ = ( 

894 "_obj", 

895 "_additional_kw", 

896 "_to_inspect", 

897 "_omit_kwarg", 

898 "_class_name", 

899 ) 

900 

901 _obj: Any 

902 _additional_kw: Sequence[Tuple[str, Any]] 

903 _to_inspect: List[object] 

904 _omit_kwarg: Sequence[str] 

905 _class_name: Optional[str] 

906 

907 def __init__( 

908 self, 

909 obj: Any, 

910 additional_kw: Sequence[Tuple[str, Any]] = (), 

911 to_inspect: Optional[Union[object, List[object]]] = None, 

912 omit_kwarg: Sequence[str] = (), 

913 ): 

914 """Create a GenericRepr object. 

915 

916 :param obj: The object being repr'd 

917 :param additional_kw: Additional keyword arguments to check for in 

918 the repr, as a sequence of 2-tuples of (name, default_value) 

919 :param to_inspect: One or more objects whose __init__ signature 

920 should be inspected. If not provided, defaults to [obj]. 

921 :param omit_kwarg: Sequence of keyword argument names to omit from 

922 the repr output 

923 """ 

924 self._obj = obj 

925 self._additional_kw = additional_kw 

926 self._to_inspect = ( 

927 [obj] if to_inspect is None else _collections.to_list(to_inspect) 

928 ) 

929 self._omit_kwarg = omit_kwarg 

930 self._class_name = None 

931 

932 def set_class_name(self, class_name: str) -> GenericRepr: 

933 """Set the class name to be used in the repr. 

934 

935 By default, the class name is taken from obj.__class__.__name__. 

936 This method allows it to be overridden. 

937 

938 :param class_name: The class name to use 

939 :return: self, for method chaining 

940 """ 

941 self._class_name = class_name 

942 return self 

943 

944 def __str__(self) -> str: 

945 """Produce the __repr__() string based on the configured parameters.""" 

946 obj = self._obj 

947 to_inspect = self._to_inspect 

948 additional_kw = self._additional_kw 

949 omit_kwarg = self._omit_kwarg 

950 

951 missing = object() 

952 

953 pos_args = [] 

954 kw_args: _collections.OrderedDict[str, Any] = ( 

955 _collections.OrderedDict() 

956 ) 

957 vargs = None 

958 for i, insp in enumerate(to_inspect): 

959 try: 

960 spec = compat.inspect_getfullargspec(insp.__init__) # type: ignore[misc] # noqa: E501 

961 except TypeError: 

962 continue 

963 else: 

964 default_len = len(spec.defaults) if spec.defaults else 0 

965 if i == 0: 

966 if spec.varargs: 

967 vargs = spec.varargs 

968 if default_len: 

969 pos_args.extend(spec.args[1:-default_len]) 

970 else: 

971 pos_args.extend(spec.args[1:]) 

972 else: 

973 kw_args.update( 

974 [(arg, missing) for arg in spec.args[1:-default_len]] 

975 ) 

976 

977 if default_len: 

978 assert spec.defaults 

979 kw_args.update( 

980 [ 

981 (arg, default) 

982 for arg, default in zip( 

983 spec.args[-default_len:], spec.defaults 

984 ) 

985 ] 

986 ) 

987 output: List[str] = [] 

988 

989 output.extend(repr(getattr(obj, arg, None)) for arg in pos_args) 

990 

991 if vargs is not None and hasattr(obj, vargs): 

992 output.extend([repr(val) for val in getattr(obj, vargs)]) 

993 

994 for arg, defval in kw_args.items(): 

995 if arg in omit_kwarg: 

996 continue 

997 try: 

998 val = getattr(obj, arg, missing) 

999 if val is not missing and val != defval: 

1000 output.append("%s=%r" % (arg, val)) 

1001 except Exception: 

1002 pass 

1003 

1004 if additional_kw: 

1005 for arg, defval in additional_kw: 

1006 try: 

1007 val = getattr(obj, arg, missing) 

1008 if val is not missing and val != defval: 

1009 output.append("%s=%r" % (arg, val)) 

1010 except Exception: 

1011 pass 

1012 

1013 class_name = ( 

1014 self._class_name 

1015 if self._class_name is not None 

1016 else obj.__class__.__name__ 

1017 ) 

1018 return "%s(%s)" % (class_name, ", ".join(output)) 

1019 

1020 

1021def generic_repr( 

1022 obj: Any, 

1023 additional_kw: Sequence[Tuple[str, Any]] = (), 

1024 to_inspect: Optional[Union[object, List[object]]] = None, 

1025 omit_kwarg: Sequence[str] = (), 

1026) -> str: 

1027 """Produce a __repr__() based on direct association of the __init__() 

1028 specification vs. same-named attributes present. 

1029 

1030 """ 

1031 return str( 

1032 GenericRepr( 

1033 obj, 

1034 additional_kw=additional_kw, 

1035 to_inspect=to_inspect, 

1036 omit_kwarg=omit_kwarg, 

1037 ) 

1038 ) 

1039 

1040 

1041def class_hierarchy(cls): 

1042 """Return an unordered sequence of all classes related to cls. 

1043 

1044 Traverses diamond hierarchies. 

1045 

1046 Fibs slightly: subclasses of builtin types are not returned. Thus 

1047 class_hierarchy(class A(object)) returns (A, object), not A plus every 

1048 class systemwide that derives from object. 

1049 Likewise, class_hierarchy(class A(Generic[T])) returns 

1050 (A, Generic, object), not every class systemwide that derives from Generic. 

1051 

1052 """ 

1053 

1054 hier = {cls} 

1055 process = list(cls.__mro__) 

1056 while process: 

1057 c = process.pop() 

1058 bases = (_ for _ in c.__bases__ if _ not in hier) 

1059 

1060 for b in bases: 

1061 process.append(b) 

1062 hier.add(b) 

1063 

1064 if ( 

1065 c is Generic 

1066 or c.__module__ == "builtins" 

1067 or not hasattr(c, "__subclasses__") 

1068 ): 

1069 continue 

1070 

1071 for s in [ 

1072 _ 

1073 for _ in ( 

1074 c.__subclasses__() 

1075 if not issubclass(c, type) 

1076 else c.__subclasses__(c) 

1077 ) 

1078 if _ not in hier 

1079 ]: 

1080 process.append(s) 

1081 hier.add(s) 

1082 return list(hier) 

1083 

1084 

1085def iterate_attributes(cls): 

1086 """iterate all the keys and attributes associated 

1087 with a class, without using getattr(). 

1088 

1089 Does not use getattr() so that class-sensitive 

1090 descriptors (i.e. property.__get__()) are not called. 

1091 

1092 """ 

1093 keys = dir(cls) 

1094 for key in keys: 

1095 for c in cls.__mro__: 

1096 if key in c.__dict__: 

1097 yield (key, c.__dict__[key]) 

1098 break 

1099 

1100 

1101def monkeypatch_proxied_specials( 

1102 into_cls, 

1103 from_cls, 

1104 skip=None, 

1105 only=None, 

1106 name="self.proxy", 

1107 from_instance=None, 

1108): 

1109 """Automates delegation of __specials__ for a proxying type.""" 

1110 

1111 if only: 

1112 dunders = only 

1113 else: 

1114 if skip is None: 

1115 skip = ( 

1116 "__slots__", 

1117 "__del__", 

1118 "__getattribute__", 

1119 "__metaclass__", 

1120 "__getstate__", 

1121 "__setstate__", 

1122 ) 

1123 dunders = [ 

1124 m 

1125 for m in dir(from_cls) 

1126 if ( 

1127 m.startswith("__") 

1128 and m.endswith("__") 

1129 and not hasattr(into_cls, m) 

1130 and m not in skip 

1131 ) 

1132 ] 

1133 

1134 for method in dunders: 

1135 try: 

1136 maybe_fn = getattr(from_cls, method) 

1137 if not hasattr(maybe_fn, "__call__"): 

1138 continue 

1139 maybe_fn = getattr(maybe_fn, "__func__", maybe_fn) 

1140 fn = cast(types.FunctionType, maybe_fn) 

1141 

1142 except AttributeError: 

1143 continue 

1144 try: 

1145 spec = compat.inspect_getfullargspec(fn) 

1146 fn_args = compat.inspect_formatargspec(spec[0]) 

1147 d_args = compat.inspect_formatargspec(spec[0][1:]) 

1148 except TypeError: 

1149 fn_args = "(self, *args, **kw)" 

1150 d_args = "(*args, **kw)" 

1151 

1152 py = ( 

1153 "def %(method)s%(fn_args)s: " 

1154 "return %(name)s.%(method)s%(d_args)s" % locals() 

1155 ) 

1156 

1157 env: Dict[str, types.FunctionType] = ( 

1158 from_instance is not None and {name: from_instance} or {} 

1159 ) 

1160 # the generated source is derived entirely from from_cls and the method 

1161 # name, and into_cls is a throwaway per-descriptor class whose 

1162 # __qualname__ is the same for every one of them, so this is keyed on 

1163 # from_cls rather than into_cls 

1164 proxied = exec_code_in_env( 

1165 py, 

1166 env, 

1167 method, 

1168 f"{method} proxying to " 

1169 f"{from_cls.__module__}.{from_cls.__qualname__}", 

1170 ) 

1171 try: 

1172 proxied.__defaults__ = fn.__defaults__ 

1173 except AttributeError: 

1174 pass 

1175 setattr(into_cls, method, proxied) 

1176 

1177 

1178def methods_equivalent(meth1, meth2): 

1179 """Return True if the two methods are the same implementation.""" 

1180 

1181 return getattr(meth1, "__func__", meth1) is getattr( 

1182 meth2, "__func__", meth2 

1183 ) 

1184 

1185 

1186def as_interface(obj, cls=None, methods=None, required=None): 

1187 """Ensure basic interface compliance for an instance or dict of callables. 

1188 

1189 Checks that ``obj`` implements public methods of ``cls`` or has members 

1190 listed in ``methods``. If ``required`` is not supplied, implementing at 

1191 least one interface method is sufficient. Methods present on ``obj`` that 

1192 are not in the interface are ignored. 

1193 

1194 If ``obj`` is a dict and ``dict`` does not meet the interface 

1195 requirements, the keys of the dictionary are inspected. Keys present in 

1196 ``obj`` that are not in the interface will raise TypeErrors. 

1197 

1198 Raises TypeError if ``obj`` does not meet the interface criteria. 

1199 

1200 In all passing cases, an object with callable members is returned. In the 

1201 simple case, ``obj`` is returned as-is; if dict processing kicks in then 

1202 an anonymous class is returned. 

1203 

1204 obj 

1205 A type, instance, or dictionary of callables. 

1206 cls 

1207 Optional, a type. All public methods of cls are considered the 

1208 interface. An ``obj`` instance of cls will always pass, ignoring 

1209 ``required``.. 

1210 methods 

1211 Optional, a sequence of method names to consider as the interface. 

1212 required 

1213 Optional, a sequence of mandatory implementations. If omitted, an 

1214 ``obj`` that provides at least one interface method is considered 

1215 sufficient. As a convenience, required may be a type, in which case 

1216 all public methods of the type are required. 

1217 

1218 """ 

1219 if not cls and not methods: 

1220 raise TypeError("a class or collection of method names are required") 

1221 

1222 if isinstance(cls, type) and isinstance(obj, cls): 

1223 return obj 

1224 

1225 interface = set(methods or [m for m in dir(cls) if not m.startswith("_")]) 

1226 implemented = set(dir(obj)) 

1227 

1228 complies = operator.ge 

1229 if isinstance(required, type): 

1230 required = interface 

1231 elif not required: 

1232 required = set() 

1233 complies = operator.gt 

1234 else: 

1235 required = set(required) 

1236 

1237 if complies(implemented.intersection(interface), required): 

1238 return obj 

1239 

1240 # No dict duck typing here. 

1241 if not isinstance(obj, dict): 

1242 qualifier = complies is operator.gt and "any of" or "all of" 

1243 raise TypeError( 

1244 "%r does not implement %s: %s" 

1245 % (obj, qualifier, ", ".join(interface)) 

1246 ) 

1247 

1248 class AnonymousInterface: 

1249 """A callable-holding shell.""" 

1250 

1251 if cls: 

1252 AnonymousInterface.__name__ = "Anonymous" + cls.__name__ 

1253 found = set() 

1254 

1255 for method, impl in dictlike_iteritems(obj): 

1256 if method not in interface: 

1257 raise TypeError("%r: unknown in this interface" % method) 

1258 if not callable(impl): 

1259 raise TypeError("%r=%r is not callable" % (method, impl)) 

1260 setattr(AnonymousInterface, method, staticmethod(impl)) 

1261 found.add(method) 

1262 

1263 if complies(found, required): 

1264 return AnonymousInterface 

1265 

1266 raise TypeError( 

1267 "dictionary does not contain required keys %s" 

1268 % ", ".join(required - found) 

1269 ) 

1270 

1271 

1272_GFD = TypeVar("_GFD", bound="generic_fn_descriptor[Any]") 

1273 

1274 

1275class generic_fn_descriptor(Generic[_T_co]): 

1276 """Descriptor which proxies a function when the attribute is not 

1277 present in dict 

1278 

1279 This superclass is organized in a particular way with "memoized" and 

1280 "non-memoized" implementation classes that are hidden from type checkers, 

1281 as Mypy seems to not be able to handle seeing multiple kinds of descriptor 

1282 classes used for the same attribute. 

1283 

1284 """ 

1285 

1286 fget: Callable[..., _T_co] 

1287 __doc__: Optional[str] 

1288 __name__: str 

1289 

1290 def __init__(self, fget: Callable[..., _T_co], doc: Optional[str] = None): 

1291 self.fget = fget 

1292 self.__doc__ = doc or fget.__doc__ 

1293 self.__name__ = fget.__name__ 

1294 

1295 @overload 

1296 def __get__(self: _GFD, obj: None, cls: Any) -> _GFD: ... 

1297 

1298 @overload 

1299 def __get__(self, obj: object, cls: Any) -> _T_co: ... 

1300 

1301 def __get__(self: _GFD, obj: Any, cls: Any) -> Union[_GFD, _T_co]: 

1302 raise NotImplementedError() 

1303 

1304 if TYPE_CHECKING: 

1305 

1306 def __set__(self, instance: Any, value: Any) -> None: ... 

1307 

1308 def __delete__(self, instance: Any) -> None: ... 

1309 

1310 def _reset(self, obj: Any) -> None: 

1311 raise NotImplementedError() 

1312 

1313 @classmethod 

1314 def reset(cls, obj: Any, name: str) -> None: 

1315 raise NotImplementedError() 

1316 

1317 

1318class _non_memoized_property(generic_fn_descriptor[_T_co]): 

1319 """a plain descriptor that proxies a function. 

1320 

1321 primary rationale is to provide a plain attribute that's 

1322 compatible with memoized_property which is also recognized as equivalent 

1323 by mypy. 

1324 

1325 """ 

1326 

1327 if not TYPE_CHECKING: 

1328 

1329 def __get__(self, obj, cls): 

1330 if obj is None: 

1331 return self 

1332 return self.fget(obj) 

1333 

1334 

1335class _memoized_property(generic_fn_descriptor[_T_co]): 

1336 """A read-only @property that is only evaluated once.""" 

1337 

1338 if not TYPE_CHECKING: 

1339 

1340 def __get__(self, obj, cls): 

1341 if obj is None: 

1342 return self 

1343 obj.__dict__[self.__name__] = result = self.fget(obj) 

1344 return result 

1345 

1346 def _reset(self, obj): 

1347 _memoized_property.reset(obj, self.__name__) 

1348 

1349 @classmethod 

1350 def reset(cls, obj, name): 

1351 obj.__dict__.pop(name, None) 

1352 

1353 

1354# despite many attempts to get Mypy to recognize an overridden descriptor 

1355# where one is memoized and the other isn't, there seems to be no reliable 

1356# way other than completely deceiving the type checker into thinking there 

1357# is just one single descriptor type everywhere. Otherwise, if a superclass 

1358# has non-memoized and subclass has memoized, that requires 

1359# "class memoized(non_memoized)". but then if a superclass has memoized and 

1360# superclass has non-memoized, the class hierarchy of the descriptors 

1361# would need to be reversed; "class non_memoized(memoized)". so there's no 

1362# way to achieve this. 

1363# additional issues, RO properties: 

1364# https://github.com/python/mypy/issues/12440 

1365if TYPE_CHECKING: 

1366 # allow memoized and non-memoized to be freely mixed by having them 

1367 # be the same class 

1368 memoized_property = generic_fn_descriptor 

1369 non_memoized_property = generic_fn_descriptor 

1370 

1371 # for read only situations, mypy only sees @property as read only. 

1372 # read only is needed when a subtype specializes the return type 

1373 # of a property, meaning assignment needs to be disallowed 

1374 ro_memoized_property = property 

1375 ro_non_memoized_property = property 

1376 

1377else: 

1378 memoized_property = ro_memoized_property = _memoized_property 

1379 non_memoized_property = ro_non_memoized_property = _non_memoized_property 

1380 

1381 

1382def memoized_instancemethod(fn: _F) -> _F: 

1383 """Decorate a method memoize its return value. 

1384 

1385 Best applied to no-arg methods: memoization is not sensitive to 

1386 argument values, and will always return the same value even when 

1387 called with different arguments. 

1388 

1389 """ 

1390 

1391 def oneshot(self, *args, **kw): 

1392 result = fn(self, *args, **kw) 

1393 

1394 def memo(*a, **kw): 

1395 return result 

1396 

1397 memo.__name__ = fn.__name__ 

1398 memo.__doc__ = fn.__doc__ 

1399 self.__dict__[fn.__name__] = memo 

1400 return result 

1401 

1402 return update_wrapper(oneshot, fn) # type: ignore[return-value] 

1403 

1404 

1405class HasMemoized: 

1406 """A mixin class that maintains the names of memoized elements in a 

1407 collection for easy cache clearing, generative, etc. 

1408 

1409 """ 

1410 

1411 if not TYPE_CHECKING: 

1412 # support classes that want to have __slots__ with an explicit 

1413 # slot for __dict__. not sure if that requires base __slots__ here. 

1414 __slots__ = () 

1415 

1416 _memoized_keys: FrozenSet[str] = frozenset() 

1417 

1418 def _reset_memoizations(self) -> None: 

1419 for elem in self._memoized_keys: 

1420 self.__dict__.pop(elem, None) 

1421 

1422 def _assert_no_memoizations(self) -> None: 

1423 for elem in self._memoized_keys: 

1424 assert elem not in self.__dict__ 

1425 

1426 def _set_memoized_attribute(self, key: str, value: Any) -> None: 

1427 self.__dict__[key] = value 

1428 self._memoized_keys |= {key} 

1429 

1430 class memoized_attribute(memoized_property[_T]): 

1431 """A read-only @property that is only evaluated once. 

1432 

1433 :meta private: 

1434 

1435 """ 

1436 

1437 fget: Callable[..., _T] 

1438 __doc__: Optional[str] 

1439 __name__: str 

1440 

1441 def __init__(self, fget: Callable[..., _T], doc: Optional[str] = None): 

1442 self.fget = fget 

1443 self.__doc__ = doc or fget.__doc__ 

1444 self.__name__ = fget.__name__ 

1445 

1446 @overload 

1447 def __get__(self: _MA, obj: None, cls: Any) -> _MA: ... 

1448 

1449 @overload 

1450 def __get__(self, obj: Any, cls: Any) -> _T: ... 

1451 

1452 def __get__(self, obj, cls): 

1453 if obj is None: 

1454 return self 

1455 obj.__dict__[self.__name__] = result = self.fget(obj) 

1456 obj._memoized_keys |= {self.__name__} 

1457 return result 

1458 

1459 @classmethod 

1460 def memoized_instancemethod(cls, fn: _F) -> _F: 

1461 """Decorate a method memoize its return value. 

1462 

1463 :meta private: 

1464 

1465 """ 

1466 

1467 def oneshot(self: Any, *args: Any, **kw: Any) -> Any: 

1468 result = fn(self, *args, **kw) 

1469 

1470 def memo(*a, **kw): 

1471 return result 

1472 

1473 memo.__name__ = fn.__name__ 

1474 memo.__doc__ = fn.__doc__ 

1475 self.__dict__[fn.__name__] = memo 

1476 self._memoized_keys |= {fn.__name__} 

1477 return result 

1478 

1479 return update_wrapper(oneshot, fn) # type: ignore[return-value] 

1480 

1481 

1482if TYPE_CHECKING: 

1483 HasMemoized_ro_memoized_attribute = property 

1484else: 

1485 HasMemoized_ro_memoized_attribute = HasMemoized.memoized_attribute 

1486 

1487 

1488class MemoizedSlots: 

1489 """Apply memoized items to an object using a __getattr__ scheme. 

1490 

1491 This allows the functionality of memoized_property and 

1492 memoized_instancemethod to be available to a class using __slots__. 

1493 

1494 The memoized get is not threadsafe under freethreading and the 

1495 creator method may in extremely rare cases be called more than once. 

1496 

1497 """ 

1498 

1499 __slots__ = () 

1500 

1501 def _fallback_getattr(self, key): 

1502 raise AttributeError(key) 

1503 

1504 def __getattr__(self, key: str) -> Any: 

1505 if key.startswith("_memoized_attr_") or key.startswith( 

1506 "_memoized_method_" 

1507 ): 

1508 raise AttributeError(key) 

1509 # to avoid recursion errors when interacting with other __getattr__ 

1510 # schemes that refer to this one, when testing for memoized method 

1511 # look at __class__ only rather than going into __getattr__ again. 

1512 elif hasattr(self.__class__, f"_memoized_attr_{key}"): 

1513 value = getattr(self, f"_memoized_attr_{key}")() 

1514 setattr(self, key, value) 

1515 return value 

1516 elif hasattr(self.__class__, f"_memoized_method_{key}"): 

1517 meth = getattr(self, f"_memoized_method_{key}") 

1518 

1519 def oneshot(*args, **kw): 

1520 result = meth(*args, **kw) 

1521 

1522 def memo(*a, **kw): 

1523 return result 

1524 

1525 memo.__name__ = meth.__name__ 

1526 memo.__doc__ = meth.__doc__ 

1527 setattr(self, key, memo) 

1528 return result 

1529 

1530 oneshot.__doc__ = meth.__doc__ 

1531 return oneshot 

1532 else: 

1533 return self._fallback_getattr(key) 

1534 

1535 

1536# from paste.deploy.converters 

1537def asbool(obj: Any) -> bool: 

1538 if isinstance(obj, str): 

1539 obj = obj.strip().lower() 

1540 if obj in ["true", "yes", "on", "y", "t", "1"]: 

1541 return True 

1542 elif obj in ["false", "no", "off", "n", "f", "0"]: 

1543 return False 

1544 else: 

1545 raise ValueError("String is not true/false: %r" % obj) 

1546 return bool(obj) 

1547 

1548 

1549def bool_or_str(*text: str) -> Callable[[str], Union[str, bool]]: 

1550 """Return a callable that will evaluate a string as 

1551 boolean, or one of a set of "alternate" string values. 

1552 

1553 """ 

1554 

1555 def bool_or_value(obj: str) -> Union[str, bool]: 

1556 if obj in text: 

1557 return obj 

1558 else: 

1559 return asbool(obj) 

1560 

1561 return bool_or_value 

1562 

1563 

1564def asint(value: Any) -> Optional[int]: 

1565 """Coerce to integer.""" 

1566 

1567 if value is None: 

1568 return value 

1569 return int(value) 

1570 

1571 

1572def coerce_kw_type( 

1573 kw: Dict[str, Any], 

1574 key: str, 

1575 type_: Type[Any], 

1576 flexi_bool: bool = True, 

1577 dest: Optional[Dict[str, Any]] = None, 

1578) -> None: 

1579 r"""If 'key' is present in dict 'kw', coerce its value to type 'type\_' if 

1580 necessary. If 'flexi_bool' is True, the string '0' is considered false 

1581 when coercing to boolean. 

1582 """ 

1583 

1584 if dest is None: 

1585 dest = kw 

1586 

1587 if ( 

1588 key in kw 

1589 and (not isinstance(type_, type) or not isinstance(kw[key], type_)) 

1590 and kw[key] is not None 

1591 ): 

1592 if type_ is bool and flexi_bool: 

1593 dest[key] = asbool(kw[key]) 

1594 else: 

1595 dest[key] = type_(kw[key]) 

1596 

1597 

1598def constructor_key(obj: Any, cls: Type[Any]) -> Tuple[Any, ...]: 

1599 """Produce a tuple structure that is cacheable using the __dict__ of 

1600 obj to retrieve values 

1601 

1602 """ 

1603 names = get_cls_kwargs(cls) 

1604 return (cls,) + tuple( 

1605 (k, obj.__dict__[k]) for k in names if k in obj.__dict__ 

1606 ) 

1607 

1608 

1609def constructor_copy(obj: _T, cls: Type[_T], *args: Any, **kw: Any) -> _T: 

1610 """Instantiate cls using the __dict__ of obj as constructor arguments. 

1611 

1612 Uses inspect to match the named arguments of ``cls``. 

1613 

1614 """ 

1615 

1616 names = get_cls_kwargs(cls) 

1617 kw.update( 

1618 (k, obj.__dict__[k]) for k in names.difference(kw) if k in obj.__dict__ 

1619 ) 

1620 return cls(*args, **kw) 

1621 

1622 

1623def counter() -> Callable[[], int]: 

1624 """Return a threadsafe counter function.""" 

1625 

1626 lock = threading.Lock() 

1627 counter = itertools.count(1) 

1628 

1629 # avoid the 2to3 "next" transformation... 

1630 def _next(): 

1631 with lock: 

1632 return next(counter) 

1633 

1634 return _next 

1635 

1636 

1637def duck_type_collection( 

1638 specimen: Any, default: Optional[Type[Any]] = None 

1639) -> Optional[Type[Any]]: 

1640 """Given an instance or class, guess if it is or is acting as one of 

1641 the basic collection types: list, set and dict. If the __emulates__ 

1642 property is present, return that preferentially. 

1643 """ 

1644 

1645 if hasattr(specimen, "__emulates__"): 

1646 # canonicalize set vs sets.Set to a standard: the builtin set 

1647 if specimen.__emulates__ is not None and issubclass( 

1648 specimen.__emulates__, set 

1649 ): 

1650 return set 

1651 else: 

1652 return specimen.__emulates__ # type: ignore[no-any-return] 

1653 

1654 isa = issubclass if isinstance(specimen, type) else isinstance 

1655 if isa(specimen, list): 

1656 return list 

1657 elif isa(specimen, set): 

1658 return set 

1659 elif isa(specimen, dict): 

1660 return dict 

1661 

1662 if hasattr(specimen, "append"): 

1663 return list 

1664 elif hasattr(specimen, "add"): 

1665 return set 

1666 elif hasattr(specimen, "set"): 

1667 return dict 

1668 else: 

1669 return default 

1670 

1671 

1672def assert_arg_type( 

1673 arg: Any, argtype: Union[Tuple[Type[Any], ...], Type[Any]], name: str 

1674) -> Any: 

1675 if isinstance(arg, argtype): 

1676 return arg 

1677 else: 

1678 if isinstance(argtype, tuple): 

1679 raise exc.ArgumentError( 

1680 "Argument '%s' is expected to be one of type %s, got '%s'" 

1681 % (name, " or ".join("'%s'" % a for a in argtype), type(arg)) 

1682 ) 

1683 else: 

1684 raise exc.ArgumentError( 

1685 "Argument '%s' is expected to be of type '%s', got '%s'" 

1686 % (name, argtype, type(arg)) 

1687 ) 

1688 

1689 

1690def dictlike_iteritems(dictlike): 

1691 """Return a (key, value) iterator for almost any dict-like object.""" 

1692 

1693 if hasattr(dictlike, "items"): 

1694 return list(dictlike.items()) 

1695 

1696 getter = getattr(dictlike, "__getitem__", getattr(dictlike, "get", None)) 

1697 if getter is None: 

1698 raise TypeError("Object '%r' is not dict-like" % dictlike) 

1699 

1700 if hasattr(dictlike, "iterkeys"): 

1701 

1702 def iterator(): 

1703 for key in dictlike.iterkeys(): 

1704 assert getter is not None 

1705 yield key, getter(key) 

1706 

1707 return iterator() 

1708 elif hasattr(dictlike, "keys"): 

1709 return iter((key, getter(key)) for key in dictlike.keys()) 

1710 else: 

1711 raise TypeError("Object '%r' is not dict-like" % dictlike) 

1712 

1713 

1714class classproperty(property): 

1715 """A decorator that behaves like @property except that operates 

1716 on classes rather than instances. 

1717 

1718 The decorator is currently special when using the declarative 

1719 module, but note that the 

1720 :class:`~.sqlalchemy.ext.declarative.declared_attr` 

1721 decorator should be used for this purpose with declarative. 

1722 

1723 """ 

1724 

1725 fget: Callable[[Any], Any] 

1726 

1727 def __init__(self, fget: Callable[[Any], Any], *arg: Any, **kw: Any): 

1728 super().__init__(fget, *arg, **kw) 

1729 self.__doc__ = fget.__doc__ 

1730 

1731 def __get__(self, obj: Any, cls: Optional[type] = None) -> Any: 

1732 return self.fget(cls) 

1733 

1734 

1735class hybridproperty(Generic[_T]): 

1736 def __init__(self, func: Callable[..., _T]): 

1737 self.func = func 

1738 self.clslevel = func 

1739 

1740 def __get__(self, instance: Any, owner: Any) -> _T: 

1741 if instance is None: 

1742 clsval = self.clslevel(owner) 

1743 return clsval 

1744 else: 

1745 return self.func(instance) 

1746 

1747 def classlevel(self, func: Callable[..., Any]) -> hybridproperty[_T]: 

1748 self.clslevel = func 

1749 return self 

1750 

1751 

1752class rw_hybridproperty(Generic[_T]): 

1753 def __init__(self, func: Callable[..., _T]): 

1754 self.func = func 

1755 self.clslevel = func 

1756 self.setfn: Optional[Callable[..., Any]] = None 

1757 

1758 def __get__(self, instance: Any, owner: Any) -> _T: 

1759 if instance is None: 

1760 clsval = self.clslevel(owner) 

1761 return clsval 

1762 else: 

1763 return self.func(instance) 

1764 

1765 def __set__(self, instance: Any, value: Any) -> None: 

1766 assert self.setfn is not None 

1767 self.setfn(instance, value) 

1768 

1769 def setter(self, func: Callable[..., Any]) -> rw_hybridproperty[_T]: 

1770 self.setfn = func 

1771 return self 

1772 

1773 def classlevel(self, func: Callable[..., Any]) -> rw_hybridproperty[_T]: 

1774 self.clslevel = func 

1775 return self 

1776 

1777 

1778class hybridmethod(Generic[_T]): 

1779 """Decorate a function as cls- or instance- level.""" 

1780 

1781 def __init__(self, func: Callable[..., _T]): 

1782 self.func = self.__func__ = func 

1783 self.clslevel = func 

1784 

1785 def __get__(self, instance: Any, owner: Any) -> Callable[..., _T]: 

1786 if instance is None: 

1787 return self.clslevel.__get__( # type: ignore[no-any-return] 

1788 owner, owner.__class__ 

1789 ) 

1790 else: 

1791 return self.func.__get__( # type: ignore[no-any-return] 

1792 instance, owner 

1793 ) 

1794 

1795 def classlevel(self, func: Callable[..., Any]) -> hybridmethod[_T]: 

1796 self.clslevel = func 

1797 return self 

1798 

1799 

1800class symbol(int): 

1801 """A constant symbol. 

1802 

1803 >>> symbol("foo") is symbol("foo") 

1804 True 

1805 >>> symbol("foo") 

1806 <symbol 'foo> 

1807 

1808 A slight refinement of the MAGICCOOKIE=object() pattern. The primary 

1809 advantage of symbol() is its repr(). They are also singletons. 

1810 

1811 Repeated calls of symbol('name') will all return the same instance. 

1812 

1813 """ 

1814 

1815 name: str 

1816 

1817 symbols: Dict[str, symbol] = {} 

1818 _lock = threading.Lock() 

1819 

1820 def __new__( 

1821 cls, 

1822 name: str, 

1823 doc: Optional[str] = None, 

1824 canonical: Optional[int] = None, 

1825 ) -> symbol: 

1826 with cls._lock: 

1827 sym = cls.symbols.get(name) 

1828 if sym is None: 

1829 assert isinstance(name, str) 

1830 if canonical is None: 

1831 canonical = hash(name) 

1832 sym = int.__new__(symbol, canonical) 

1833 sym.name = name 

1834 if doc: 

1835 sym.__doc__ = doc 

1836 

1837 # NOTE: we should ultimately get rid of this global thing, 

1838 # however, currently it is to support pickling. The best 

1839 # change would be when we are on py3.11 at a minimum, we 

1840 # switch to stdlib enum.IntFlag. 

1841 cls.symbols[name] = sym 

1842 else: 

1843 if canonical and canonical != sym: 

1844 raise TypeError( 

1845 f"Can't replace canonical symbol for {name!r} " 

1846 f"with new int value {canonical}" 

1847 ) 

1848 return sym 

1849 

1850 def __reduce__(self): 

1851 return symbol, (self.name, "x", int(self)) 

1852 

1853 def __str__(self): 

1854 return repr(self) 

1855 

1856 def __repr__(self): 

1857 return f"symbol({self.name!r})" 

1858 

1859 

1860class _IntFlagMeta(type): 

1861 def __init__( 

1862 cls, 

1863 classname: str, 

1864 bases: Tuple[Type[Any], ...], 

1865 dict_: Dict[str, Any], 

1866 **kw: Any, 

1867 ) -> None: 

1868 items: List[symbol] 

1869 cls._items = items = [] 

1870 for k, v in dict_.items(): 

1871 if re.match(r"^__.*__$", k): 

1872 continue 

1873 if isinstance(v, int): 

1874 sym = symbol(k, canonical=v) 

1875 elif not k.startswith("_"): 

1876 raise TypeError("Expected integer values for IntFlag") 

1877 else: 

1878 continue 

1879 setattr(cls, k, sym) 

1880 items.append(sym) 

1881 

1882 cls.__members__ = _collections.immutabledict( 

1883 {sym.name: sym for sym in items} 

1884 ) 

1885 

1886 def __iter__(self) -> Iterator[symbol]: 

1887 raise NotImplementedError( 

1888 "iter not implemented to ensure compatibility with " 

1889 "Python 3.11 IntFlag. Please use __members__. See " 

1890 "https://github.com/python/cpython/issues/99304" 

1891 ) 

1892 

1893 

1894class _FastIntFlag(metaclass=_IntFlagMeta): 

1895 """An 'IntFlag' copycat that isn't slow when performing bitwise 

1896 operations. 

1897 

1898 the ``FastIntFlag`` class will return ``enum.IntFlag`` under TYPE_CHECKING 

1899 and ``_FastIntFlag`` otherwise. 

1900 

1901 """ 

1902 

1903 

1904if TYPE_CHECKING: 

1905 from enum import IntFlag 

1906 

1907 FastIntFlag = IntFlag 

1908else: 

1909 FastIntFlag = _FastIntFlag 

1910 

1911 

1912_E = TypeVar("_E", bound=enum.Enum) 

1913 

1914 

1915def parse_user_argument_for_enum( 

1916 arg: Any, 

1917 choices: Dict[_E, List[Any]], 

1918 name: str, 

1919 resolve_symbol_names: bool = False, 

1920) -> Optional[_E]: 

1921 """Given a user parameter, parse the parameter into a chosen value 

1922 from a list of choice objects, typically Enum values. 

1923 

1924 The user argument can be a string name that matches the name of a 

1925 symbol, or the symbol object itself, or any number of alternate choices 

1926 such as True/False/ None etc. 

1927 

1928 :param arg: the user argument. 

1929 :param choices: dictionary of enum values to lists of possible 

1930 entries for each. 

1931 :param name: name of the argument. Used in an :class:`.ArgumentError` 

1932 that is raised if the parameter doesn't match any available argument. 

1933 

1934 """ 

1935 for enum_value, choice in choices.items(): 

1936 if arg is enum_value: 

1937 return enum_value 

1938 elif resolve_symbol_names and arg == enum_value.name: 

1939 return enum_value 

1940 elif arg in choice: 

1941 return enum_value 

1942 

1943 if arg is None: 

1944 return None 

1945 

1946 raise exc.ArgumentError(f"Invalid value for '{name}': {arg!r}") 

1947 

1948 

1949_creation_order = 1 

1950 

1951 

1952def set_creation_order(instance: Any) -> None: 

1953 """Assign a '_creation_order' sequence to the given instance. 

1954 

1955 This allows multiple instances to be sorted in order of creation 

1956 (typically within a single thread; the counter is not particularly 

1957 threadsafe). 

1958 

1959 """ 

1960 global _creation_order 

1961 instance._creation_order = _creation_order 

1962 _creation_order += 1 

1963 

1964 

1965def warn_exception(func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any: 

1966 """executes the given function, catches all exceptions and converts to 

1967 a warning. 

1968 

1969 """ 

1970 try: 

1971 return func(*args, **kwargs) 

1972 except Exception: 

1973 warn("%s('%s') ignored" % sys.exc_info()[0:2]) 

1974 

1975 

1976def ellipses_string(value, len_=25): 

1977 try: 

1978 if len(value) > len_: 

1979 return "%s..." % value[0:len_] 

1980 else: 

1981 return value 

1982 except TypeError: 

1983 return value 

1984 

1985 

1986class _hash_limit_string(str): 

1987 """A string subclass that can only be hashed on a maximum amount 

1988 of unique values. 

1989 

1990 This is used for warnings so that we can send out parameterized warnings 

1991 without the __warningregistry__ of the module, or the non-overridable 

1992 "once" registry within warnings.py, overloading memory, 

1993 

1994 

1995 """ 

1996 

1997 _hash: int 

1998 

1999 def __new__( 

2000 cls, value: str, num: int, args: Sequence[Any] 

2001 ) -> _hash_limit_string: 

2002 interpolated = (value % args) + ( 

2003 " (this warning may be suppressed after %d occurrences)" % num 

2004 ) 

2005 self = super().__new__(cls, interpolated) 

2006 self._hash = hash("%s_%d" % (value, hash(interpolated) % num)) 

2007 return self 

2008 

2009 def __hash__(self) -> int: 

2010 return self._hash 

2011 

2012 def __eq__(self, other: Any) -> bool: 

2013 return hash(self) == hash(other) 

2014 

2015 

2016def warn(msg: str, code: Optional[str] = None) -> None: 

2017 """Issue a warning. 

2018 

2019 If msg is a string, :class:`.exc.SAWarning` is used as 

2020 the category. 

2021 

2022 """ 

2023 if code: 

2024 _warnings_warn(exc.SAWarning(msg, code=code)) 

2025 else: 

2026 _warnings_warn(msg, exc.SAWarning) 

2027 

2028 

2029def warn_limited(msg: str, args: Sequence[Any]) -> None: 

2030 """Issue a warning with a parameterized string, limiting the number 

2031 of registrations. 

2032 

2033 """ 

2034 if args: 

2035 msg = _hash_limit_string(msg, 10, args) 

2036 _warnings_warn(msg, exc.SAWarning) 

2037 

2038 

2039_warning_tags: Dict[CodeType, Tuple[str, Type[Warning]]] = {} 

2040 

2041 

2042def tag_method_for_warnings( 

2043 message: str, category: Type[Warning] 

2044) -> Callable[[_F], _F]: 

2045 def go(fn): 

2046 _warning_tags[fn.__code__] = (message, category) 

2047 return fn 

2048 

2049 return go 

2050 

2051 

2052_not_sa_pattern = re.compile(r"^(?:sqlalchemy\.(?!testing)|alembic\.)") 

2053 

2054 

2055def _warnings_warn( 

2056 message: Union[str, Warning], 

2057 category: Optional[Type[Warning]] = None, 

2058 stacklevel: int = 2, 

2059) -> None: 

2060 

2061 if category is None and isinstance(message, Warning): 

2062 category = type(message) 

2063 

2064 # adjust the given stacklevel to be outside of SQLAlchemy 

2065 try: 

2066 frame = sys._getframe(stacklevel) 

2067 except ValueError: 

2068 # being called from less than 3 (or given) stacklevels, weird, 

2069 # but don't crash 

2070 stacklevel = 0 

2071 except: 

2072 # _getframe() doesn't work, weird interpreter issue, weird, 

2073 # ok, but don't crash 

2074 stacklevel = 0 

2075 else: 

2076 stacklevel_found = warning_tag_found = False 

2077 while frame is not None: 

2078 # using __name__ here requires that we have __name__ in the 

2079 # __globals__ of the decorated string functions we make also. 

2080 # we generate this using {"__name__": fn.__module__} 

2081 if not stacklevel_found and not re.match( 

2082 _not_sa_pattern, frame.f_globals.get("__name__", "") 

2083 ): 

2084 # stop incrementing stack level if an out-of-SQLA line 

2085 # were found. 

2086 stacklevel_found = True 

2087 

2088 # however, for the warning tag thing, we have to keep 

2089 # scanning up the whole traceback 

2090 

2091 if frame.f_code in _warning_tags: 

2092 warning_tag_found = True 

2093 _suffix, _category = _warning_tags[frame.f_code] 

2094 category = category or _category 

2095 message = f"{message} ({_suffix})" 

2096 

2097 frame = frame.f_back # type: ignore[assignment] 

2098 

2099 if not stacklevel_found: 

2100 stacklevel += 1 

2101 elif stacklevel_found and warning_tag_found: 

2102 break 

2103 

2104 if category is not None: 

2105 warnings.warn(message, category, stacklevel=stacklevel + 1) 

2106 else: 

2107 warnings.warn(message, stacklevel=stacklevel + 1) 

2108 

2109 

2110def only_once( 

2111 fn: Callable[..., _T], retry_on_exception: bool 

2112) -> Callable[..., Optional[_T]]: 

2113 """Decorate the given function to be a no-op after it is called exactly 

2114 once.""" 

2115 

2116 once = [fn] 

2117 

2118 def go(*arg: Any, **kw: Any) -> Optional[_T]: 

2119 # strong reference fn so that it isn't garbage collected, 

2120 # which interferes with the event system's expectations 

2121 strong_fn = fn # noqa 

2122 if once: 

2123 once_fn = once.pop() 

2124 try: 

2125 return once_fn(*arg, **kw) 

2126 except: 

2127 if retry_on_exception: 

2128 once.insert(0, once_fn) 

2129 raise 

2130 

2131 return None 

2132 

2133 return go 

2134 

2135 

2136_SQLA_RE = re.compile(r"sqlalchemy/([a-z_]+/){0,2}[a-z_]+\.py") 

2137_UNITTEST_RE = re.compile(r"unit(?:2|test2?/)") 

2138 

2139 

2140def chop_traceback( 

2141 tb: List[str], 

2142 exclude_prefix: re.Pattern[str] = _UNITTEST_RE, 

2143 exclude_suffix: re.Pattern[str] = _SQLA_RE, 

2144) -> List[str]: 

2145 """Chop extraneous lines off beginning and end of a traceback. 

2146 

2147 :param tb: 

2148 a list of traceback lines as returned by ``traceback.format_stack()`` 

2149 

2150 :param exclude_prefix: 

2151 a regular expression object matching lines to skip at beginning of 

2152 ``tb`` 

2153 

2154 :param exclude_suffix: 

2155 a regular expression object matching lines to skip at end of ``tb`` 

2156 """ 

2157 start = 0 

2158 end = len(tb) - 1 

2159 while start <= end and exclude_prefix.search(tb[start]): 

2160 start += 1 

2161 while start <= end and exclude_suffix.search(tb[end]): 

2162 end -= 1 

2163 return tb[start : end + 1] 

2164 

2165 

2166def attrsetter(attrname): 

2167 code = "def set(obj, value): obj.%s = value" % attrname 

2168 env = locals().copy() 

2169 exec(code, env) 

2170 return env["set"] 

2171 

2172 

2173dunders_re = re.compile("^__.+__$") 

2174 

2175 

2176class TypingOnly: 

2177 """A mixin class that marks a class as 'typing only', meaning it has 

2178 absolutely no methods, attributes, or runtime functionality whatsoever. 

2179 

2180 """ 

2181 

2182 __slots__ = () 

2183 

2184 def __init_subclass__(cls, **kw: Any) -> None: 

2185 if TypingOnly in cls.__bases__: 

2186 remaining = { 

2187 name for name in cls.__dict__ if not dunders_re.match(name) 

2188 } 

2189 if remaining: 

2190 raise AssertionError( 

2191 f"Class {cls} directly inherits TypingOnly but has " 

2192 f"additional attributes {remaining}." 

2193 ) 

2194 super().__init_subclass__(**kw) 

2195 

2196 

2197class EnsureKWArg: 

2198 r"""Apply translation of functions to accept \**kw arguments if they 

2199 don't already. 

2200 

2201 Used to ensure cross-compatibility with third party legacy code, for things 

2202 like compiler visit methods that need to accept ``**kw`` arguments, 

2203 but may have been copied from old code that didn't accept them. 

2204 

2205 """ 

2206 

2207 ensure_kwarg: str 

2208 """a regular expression that indicates method names for which the method 

2209 should accept ``**kw`` arguments. 

2210 

2211 The class will scan for methods matching the name template and decorate 

2212 them if necessary to ensure ``**kw`` parameters are accepted. 

2213 

2214 """ 

2215 

2216 def __init_subclass__(cls) -> None: 

2217 fn_reg = cls.ensure_kwarg 

2218 clsdict = cls.__dict__ 

2219 if fn_reg: 

2220 for key in clsdict: 

2221 m = re.match(fn_reg, key) 

2222 if m: 

2223 fn = clsdict[key] 

2224 spec = compat.inspect_getfullargspec(fn) 

2225 if not spec.varkw: 

2226 wrapped = cls._wrap_w_kw(fn) 

2227 setattr(cls, key, wrapped) 

2228 super().__init_subclass__() 

2229 

2230 @classmethod 

2231 def _wrap_w_kw(cls, fn: Callable[..., Any]) -> Callable[..., Any]: 

2232 def wrap(*arg: Any, **kw: Any) -> Any: 

2233 return fn(*arg) 

2234 

2235 return update_wrapper(wrap, fn) 

2236 

2237 

2238def wrap_callable(wrapper, fn): 

2239 """Augment functools.update_wrapper() to work with objects with 

2240 a ``__call__()`` method. 

2241 

2242 :param fn: 

2243 object with __call__ method 

2244 

2245 """ 

2246 if hasattr(fn, "__name__"): 

2247 return update_wrapper(wrapper, fn) 

2248 else: 

2249 _f = wrapper 

2250 _f.__name__ = fn.__class__.__name__ 

2251 if hasattr(fn, "__module__"): 

2252 _f.__module__ = fn.__module__ 

2253 

2254 if hasattr(fn.__call__, "__doc__") and fn.__call__.__doc__: 

2255 _f.__doc__ = fn.__call__.__doc__ 

2256 elif fn.__doc__: 

2257 _f.__doc__ = fn.__doc__ 

2258 

2259 return _f 

2260 

2261 

2262def find_matching_paren(text: str, start: int = 0) -> Optional[int]: 

2263 """Return the index of the ``)`` that matches the ``(`` at ``start``. 

2264 

2265 The walk skips single-quoted (``'...'``) and double-quoted (``"..."``) 

2266 string literals, so parentheses inside string literals do not affect 

2267 the depth counter. ``''`` and ``""`` are treated as escaped quotes 

2268 inside their respective contexts, matching PostgreSQL/SQLite literal 

2269 conventions. 

2270 

2271 Returns ``None`` if the opening parenthesis is never closed 

2272 (unbalanced). The character at ``text[start]`` must be ``(``. 

2273 

2274 Note for SQLite use, SQLite also supports MySQL backtick-style quotes as 

2275 well as SQL Server bracket style quotes; the latter has different escaping 

2276 behaviors. A follow-up patch could add support for these two additional 

2277 styles (consider using an enum like QuotingStyle.DOUBLE | 

2278 QuotingStyle.BRACKET, etc.) 

2279 

2280 E.g.:: 

2281 

2282 >>> find_matching_paren("(a + b)") 

2283 6 

2284 >>> find_matching_paren("((a)(b))") 

2285 7 

2286 >>> find_matching_paren("(a = '(' AND b = ')')") 

2287 20 

2288 

2289 """ 

2290 assert text[start] == "(", "start index must point at an open paren" 

2291 

2292 depth = 0 

2293 in_single = False 

2294 in_double = False 

2295 n = len(text) 

2296 i = start 

2297 while i < n: 

2298 ch = text[i] 

2299 if in_single: 

2300 if ch == "'": 

2301 if i + 1 < n and text[i + 1] == "'": 

2302 i += 2 

2303 continue 

2304 in_single = False 

2305 elif in_double: 

2306 if ch == '"': 

2307 if i + 1 < n and text[i + 1] == '"': 

2308 i += 2 

2309 continue 

2310 in_double = False 

2311 elif ch == "'": 

2312 in_single = True 

2313 elif ch == '"': 

2314 in_double = True 

2315 elif ch == "(": 

2316 depth += 1 

2317 elif ch == ")": 

2318 depth -= 1 

2319 if depth == 0: 

2320 return i 

2321 i += 1 

2322 return None 

2323 

2324 

2325def strip_outer_parens(text: str) -> str: 

2326 """Remove one layer of outer parentheses from ``text`` if they wrap the 

2327 entire (stripped) string. 

2328 

2329 Whitespace is preserved if the parentheses do not wrap the whole 

2330 expression. String literals are honored via :func:`find_matching_paren`, 

2331 so ``"(a = '(' AND b = ')')"`` correctly strips to 

2332 ``"a = '(' AND b = ')'"`` rather than being interpreted as two separate 

2333 paren groups. 

2334 

2335 E.g.:: 

2336 

2337 >>> strip_outer_parens("(a IS NOT NULL)") 

2338 'a IS NOT NULL' 

2339 >>> strip_outer_parens("(a) AND (b)") 

2340 '(a) AND (b)' 

2341 >>> strip_outer_parens("a NOT NULL") 

2342 'a NOT NULL' 

2343 

2344 """ 

2345 stripped = text.strip() 

2346 lstripped = len(stripped) 

2347 if lstripped < 2 or stripped[0] != "(" or stripped[-1] != ")": 

2348 return text 

2349 close = find_matching_paren(stripped, 0) 

2350 if close is not None and close == lstripped - 1: 

2351 return stripped[1:-1] 

2352 return text 

2353 

2354 

2355def quoted_token_parser(value): 

2356 """Parse a dotted identifier with accommodation for quoted names. 

2357 

2358 Includes support for SQL-style double quotes as a literal character. 

2359 

2360 E.g.:: 

2361 

2362 >>> quoted_token_parser("name") 

2363 ["name"] 

2364 >>> quoted_token_parser("schema.name") 

2365 ["schema", "name"] 

2366 >>> quoted_token_parser('"Schema"."Name"') 

2367 ['Schema', 'Name'] 

2368 >>> quoted_token_parser('"Schema"."Name""Foo"') 

2369 ['Schema', 'Name""Foo'] 

2370 

2371 """ 

2372 

2373 if '"' not in value: 

2374 return value.split(".") 

2375 

2376 # 0 = outside of quotes 

2377 # 1 = inside of quotes 

2378 state = 0 

2379 result: List[List[str]] = [[]] 

2380 idx = 0 

2381 lv = len(value) 

2382 while idx < lv: 

2383 char = value[idx] 

2384 if char == '"': 

2385 if state == 1 and idx < lv - 1 and value[idx + 1] == '"': 

2386 result[-1].append('"') 

2387 idx += 1 

2388 else: 

2389 state ^= 1 

2390 elif char == "." and state == 0: 

2391 result.append([]) 

2392 else: 

2393 result[-1].append(char) 

2394 idx += 1 

2395 

2396 return ["".join(token) for token in result] 

2397 

2398 

2399def add_parameter_text(params: Any, text: str) -> Callable[[_F], _F]: 

2400 params = _collections.to_list(params) 

2401 

2402 def decorate(fn): 

2403 doc = fn.__doc__ is not None and fn.__doc__ or "" 

2404 if doc: 

2405 doc = inject_param_text(doc, {param: text for param in params}) 

2406 fn.__doc__ = doc 

2407 return fn 

2408 

2409 return decorate 

2410 

2411 

2412def _dedent_docstring(text: str) -> str: 

2413 split_text = text.split("\n", 1) 

2414 if len(split_text) == 1: 

2415 return text 

2416 else: 

2417 firstline, remaining = split_text 

2418 if not firstline.startswith(" "): 

2419 return firstline + "\n" + textwrap.dedent(remaining) 

2420 else: 

2421 return textwrap.dedent(text) 

2422 

2423 

2424def inject_docstring_text( 

2425 given_doctext: Optional[str], injecttext: str, pos: int 

2426) -> str: 

2427 doctext: str = _dedent_docstring(given_doctext or "") 

2428 lines = doctext.split("\n") 

2429 if len(lines) == 1: 

2430 lines.append("") 

2431 injectlines = textwrap.dedent(injecttext).split("\n") 

2432 if injectlines[0]: 

2433 injectlines.insert(0, "") 

2434 

2435 blanks = [num for num, line in enumerate(lines) if not line.strip()] 

2436 blanks.insert(0, 0) 

2437 

2438 inject_pos = blanks[min(pos, len(blanks) - 1)] 

2439 

2440 lines = lines[0:inject_pos] + injectlines + lines[inject_pos:] 

2441 return "\n".join(lines) 

2442 

2443 

2444_param_reg = re.compile(r"(\s+):param (.+?):") 

2445 

2446 

2447def inject_param_text(doctext: str, inject_params: Dict[str, str]) -> str: 

2448 doclines = collections.deque(doctext.splitlines()) 

2449 lines = [] 

2450 

2451 # TODO: this is not working for params like ":param case_sensitive=True:" 

2452 

2453 to_inject = None 

2454 while doclines: 

2455 line = doclines.popleft() 

2456 

2457 m = _param_reg.match(line) 

2458 

2459 if to_inject is None: 

2460 if m: 

2461 param = m.group(2).lstrip("*") 

2462 if param in inject_params: 

2463 # default indent to that of :param: plus one 

2464 indent = " " * len(m.group(1)) + " " 

2465 

2466 # but if the next line has text, use that line's 

2467 # indentation 

2468 if doclines: 

2469 m2 = re.match(r"(\s+)\S", doclines[0]) 

2470 if m2: 

2471 indent = " " * len(m2.group(1)) 

2472 

2473 to_inject = indent + inject_params[param] 

2474 elif m: 

2475 lines.extend(["\n", to_inject, "\n"]) 

2476 to_inject = None 

2477 elif not line.rstrip(): 

2478 lines.extend([line, to_inject, "\n"]) 

2479 to_inject = None 

2480 elif line.endswith("::"): 

2481 # TODO: this still won't cover if the code example itself has 

2482 # blank lines in it, need to detect those via indentation. 

2483 lines.extend([line, doclines.popleft()]) 

2484 continue 

2485 lines.append(line) 

2486 

2487 return "\n".join(lines) 

2488 

2489 

2490def repr_tuple_names(names: List[str]) -> Optional[str]: 

2491 """Trims a list of strings from the middle and return a string of up to 

2492 four elements. Strings greater than 11 characters will be truncated""" 

2493 if len(names) == 0: 

2494 return None 

2495 flag = len(names) <= 4 

2496 names = names[0:4] if flag else names[0:3] + names[-1:] 

2497 res = ["%s.." % name[:11] if len(name) > 11 else name for name in names] 

2498 if flag: 

2499 return ", ".join(res) 

2500 else: 

2501 return "%s, ..., %s" % (", ".join(res[0:3]), res[-1]) 

2502 

2503 

2504def has_compiled_ext(raise_=False): 

2505 from ._has_cython import HAS_CYEXTENSION 

2506 

2507 if HAS_CYEXTENSION: 

2508 return True 

2509 elif raise_: 

2510 raise ImportError( 

2511 "cython extensions were expected to be installed, " 

2512 "but are not present" 

2513 ) 

2514 else: 

2515 return False 

2516 

2517 

2518def load_uncompiled_module(module: _M) -> _M: 

2519 """Load the non-compied version of a module that is also 

2520 compiled with cython. 

2521 """ 

2522 full_name = module.__name__ 

2523 assert module.__spec__ 

2524 parent_name = module.__spec__.parent 

2525 assert parent_name 

2526 parent_module = sys.modules[parent_name] 

2527 assert parent_module.__spec__ 

2528 package_path = parent_module.__spec__.origin 

2529 assert package_path and package_path.endswith("__init__.py") 

2530 

2531 name = full_name.split(".")[-1] 

2532 module_path = package_path.replace("__init__.py", f"{name}.py") 

2533 

2534 py_spec = importlib.util.spec_from_file_location(full_name, module_path) 

2535 assert py_spec 

2536 py_module = importlib.util.module_from_spec(py_spec) 

2537 assert py_spec.loader 

2538 py_spec.loader.exec_module(py_module) 

2539 return cast(_M, py_module) 

2540 

2541 

2542_pre_release_normalize = { 

2543 "a": "a", 

2544 "alpha": "a", 

2545 "b": "b", 

2546 "beta": "b", 

2547 "c": "rc", 

2548 "pre": "rc", 

2549 "preview": "rc", 

2550 "rc": "rc", 

2551} 

2552 

2553_version_string_re = re.compile( 

2554 r""" 

2555 \s* 

2556 (?:[a-z][a-z0-9]*[-_])? # ignored prefix, "py3-" 

2557 v? 

2558 (?P<release>\d+(?:\.\d+)*) 

2559 (?: # pre-release 

2560 [-_.]? 

2561 (?P<pre_l>alpha|beta|preview|pre|rc|a|b|c) 

2562 [-_.]? 

2563 (?P<pre_n>\d+)? 

2564 )? 

2565 (?: # post-release 

2566 [-_.]? 

2567 (?P<post_l>post|rev|r) 

2568 [-_.]? 

2569 (?P<post_n>\d+)? 

2570 )? 

2571 (?: # developmental release 

2572 [-_.]? 

2573 (?P<dev_l>dev) 

2574 [-_.]? 

2575 (?P<dev_n>\d+)? 

2576 )? 

2577 """, 

2578 re.X | re.I, 

2579) 

2580 

2581_VersionSortKey = Tuple[ 

2582 Tuple[int, ...], 

2583 Tuple[int, str, int], 

2584 Tuple[int, int], 

2585 Tuple[int, int], 

2586] 

2587 

2588 

2589def _version_sort_key( 

2590 release: Tuple[int, ...], 

2591 pre: Optional[Tuple[str, int]], 

2592 post: Optional[int], 

2593 dev: Optional[int], 

2594) -> _VersionSortKey: 

2595 if pre is None and post is None and dev is not None: 

2596 # a dev release with no other qualifiers precedes every 

2597 # pre-release of the same release number 

2598 pre_key = (-1, "", 0) 

2599 elif pre is None: 

2600 pre_key = (1, "", 0) 

2601 else: 

2602 pre_key = (0, pre[0], pre[1]) 

2603 

2604 return ( 

2605 release, 

2606 pre_key, 

2607 (0, 0) if post is None else (1, post), 

2608 (1, 0) if dev is None else (0, dev), 

2609 ) 

2610 

2611 

2612def _version_comparison( 

2613 op: Callable[[Any, Any], bool], 

2614) -> Callable[[VersionInfo, Any], Any]: 

2615 """Build one of :class:`.VersionInfo`'s comparison methods. 

2616 

2617 Comparison takes place against the sort key rather than the tuple 

2618 itself, so that pre-release and similar qualifiers are taken into 

2619 account. A plain tuple is interpreted as the release segment of a 

2620 final release; anything else is not comparable. 

2621 

2622 """ 

2623 

2624 def compare(self: VersionInfo, other: Any) -> Any: 

2625 if isinstance(other, VersionInfo): 

2626 other_key = other._sort_key 

2627 elif isinstance(other, tuple): 

2628 other_key = _version_sort_key(other, None, None, None) 

2629 else: 

2630 return NotImplemented 

2631 return op(self._sort_key, other_key) 

2632 

2633 return compare 

2634 

2635 

2636class VersionInfo(Tuple[int, ...]): 

2637 """A version number, as a tuple of integers. 

2638 

2639 :class:`.VersionInfo` is a ``tuple`` subclass consisting of the 

2640 numeric "release" segment of a version only, e.g. ``2.0.0rc1`` 

2641 is the tuple ``(2, 0, 0)``. Ordering however takes any 

2642 pre-release, post-release and developmental qualifiers into account 

2643 as described by :pep:`440`, so that ``2.0.0rc1`` compares as less than 

2644 ``2.0.0``, including when compared against a plain tuple such as 

2645 ``(2, 0, 0)``. 

2646 

2647 Plain tuples are interpreted as final releases when compared against 

2648 a :class:`.VersionInfo`. 

2649 

2650 .. versionadded:: 2.1 

2651 

2652 """ 

2653 

2654 string: Optional[str] 

2655 """the string from which this version was parsed, if any.""" 

2656 

2657 pre: Optional[Tuple[str, int]] 

2658 """normalized pre-release qualifier, e.g. ``("rc", 1)``.""" 

2659 

2660 post: Optional[int] 

2661 """post-release number, if any.""" 

2662 

2663 dev: Optional[int] 

2664 """developmental release number, if any.""" 

2665 

2666 _sort_key: _VersionSortKey 

2667 

2668 def __new__( 

2669 cls, 

2670 release: Sequence[int] = (), 

2671 *, 

2672 string: Optional[str] = None, 

2673 pre: Optional[Tuple[str, int]] = None, 

2674 post: Optional[int] = None, 

2675 dev: Optional[int] = None, 

2676 ) -> VersionInfo: 

2677 # __new__ is needed as the release segment has to be passed to 

2678 # tuple.__new__(); the remaining state is set up in __init__ 

2679 return tuple.__new__(cls, release) 

2680 

2681 def __init__( 

2682 self, 

2683 release: Sequence[int] = (), 

2684 *, 

2685 string: Optional[str] = None, 

2686 pre: Optional[Tuple[str, int]] = None, 

2687 post: Optional[int] = None, 

2688 dev: Optional[int] = None, 

2689 ): 

2690 self.string = string 

2691 self.pre = pre 

2692 self.post = post 

2693 self.dev = dev 

2694 self._sort_key = _version_sort_key(tuple(self), pre, post, dev) 

2695 

2696 def __repr__(self) -> str: 

2697 if self.string is not None: 

2698 return f"VersionInfo({tuple(self)!r}, string={self.string!r})" 

2699 else: 

2700 return f"VersionInfo({tuple(self)!r})" 

2701 

2702 def __str__(self) -> str: 

2703 if self.string is not None: 

2704 return self.string 

2705 else: 

2706 return ".".join(str(num) for num in self) 

2707 

2708 # every comparison has to be stated explicitly; ``tuple`` implements 

2709 # all six of them, so ``functools.total_ordering`` fills in nothing 

2710 # here and the ones left out would silently compare as plain tuples 

2711 __eq__ = _version_comparison(operator.eq) 

2712 __ne__ = _version_comparison(operator.ne) 

2713 __lt__ = _version_comparison(operator.lt) 

2714 __le__ = _version_comparison(operator.le) 

2715 __gt__ = _version_comparison(operator.gt) 

2716 __ge__ = _version_comparison(operator.ge) 

2717 

2718 def __hash__(self) -> int: 

2719 return hash(self._sort_key) 

2720 

2721 

2722def parse_version_string(version: Optional[str]) -> VersionInfo: 

2723 """Parse a DBAPI version string into a :class:`.VersionInfo`. 

2724 

2725 Leading characters that are not part of the version itself are 

2726 ignored, as are trailing characters following the version, so that 

2727 strings such as ``"py3-4.0.19-beta4"`` and 

2728 ``"2.9.10 (dt dec pq3 ext lo64)"`` parse correctly. 

2729 

2730 An empty :class:`.VersionInfo` is returned if no version number can be 

2731 located at all. 

2732 

2733 Parsing is deliberately more tolerant than that of :pep:`440`, which 

2734 the version strings published by DBAPIs frequently do not conform to; 

2735 a strict implementation such as that of the ``packaging`` library 

2736 rejects each of the above outright. 

2737 

2738 .. versionadded:: 2.1 

2739 

2740 """ 

2741 

2742 if not version: 

2743 return VersionInfo((), string=version) 

2744 

2745 m = _version_string_re.match(version) 

2746 if m is None: 

2747 return VersionInfo((), string=version) 

2748 

2749 release = tuple(int(x) for x in m.group("release").split(".")) 

2750 

2751 pre_l = m.group("pre_l") 

2752 pre: Optional[Tuple[str, int]] 

2753 if pre_l is not None: 

2754 pre = ( 

2755 _pre_release_normalize[pre_l.lower()], 

2756 int(m.group("pre_n") or 0), 

2757 ) 

2758 else: 

2759 pre = None 

2760 

2761 return VersionInfo( 

2762 release, 

2763 string=version, 

2764 pre=pre, 

2765 post=( 

2766 int(m.group("post_n") or 0) 

2767 if m.group("post_l") is not None 

2768 else None 

2769 ), 

2770 dev=( 

2771 int(m.group("dev_n") or 0) 

2772 if m.group("dev_l") is not None 

2773 else None 

2774 ), 

2775 ) 

2776 

2777 

2778def parse_version_from_metadata(distribution: str) -> VersionInfo: 

2779 """Return the version of an installed distribution as a 

2780 :class:`.VersionInfo`. 

2781 

2782 This is intended for use by dialects whose DBAPI module does not 

2783 itself publish a version number, such as ``asyncmy``. As the 

2784 distribution name is not necessarily the same as the module name, and 

2785 the installed distribution is not necessarily the module that was 

2786 imported, this should not be used when the DBAPI module provides a 

2787 version of its own. 

2788 

2789 An empty :class:`.VersionInfo` is returned if the distribution is not 

2790 installed. 

2791 

2792 .. versionadded:: 2.1 

2793 

2794 """ 

2795 

2796 try: 

2797 version = importlib.metadata.version(distribution) 

2798 except importlib.metadata.PackageNotFoundError: 

2799 return VersionInfo() 

2800 else: 

2801 return parse_version_string(version) 

2802 

2803 

2804class _Missing(enum.Enum): 

2805 Missing = enum.auto() 

2806 

2807 

2808Missing = _Missing.Missing 

2809MissingOr = Union[_T, Literal[_Missing.Missing]]