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

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

1104 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 operator 

24import re 

25import sys 

26import textwrap 

27import threading 

28import types 

29from types import CodeType 

30from types import ModuleType 

31from typing import Any 

32from typing import Callable 

33from typing import cast 

34from typing import Dict 

35from typing import FrozenSet 

36from typing import Generic 

37from typing import Iterator 

38from typing import List 

39from typing import Literal 

40from typing import NoReturn 

41from typing import Optional 

42from typing import overload 

43from typing import Sequence 

44from typing import Set 

45from typing import Tuple 

46from typing import Type 

47from typing import TYPE_CHECKING 

48from typing import TypeVar 

49from typing import Union 

50import warnings 

51 

52from . import _collections 

53from . import compat 

54from .. import exc 

55 

56_T = TypeVar("_T") 

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

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

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

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

61 

62 

63def restore_annotations( 

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

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

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

67 the pristine state of the former. 

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

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

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

71 to get a value. 

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

73 __annotations__, that would be much better. 

74 """ 

75 delattr_ = object() 

76 

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

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

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

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

81 # __annotate__ can be ignored 

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

83 

84 if is_pep649: 

85 memoized = { 

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

87 } 

88 else: 

89 memoized = { 

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

91 } 

92 

93 cls.__annotations__ = new_annotations 

94 

95 def restore(): 

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

97 if v is delattr_: 

98 delattr(cls, k) 

99 else: 

100 setattr(cls, k, v) 

101 

102 return restore 

103 

104 

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

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

107 m = compat.md5_not_for_security() 

108 m.update(x) 

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

110 

111 

112class safe_reraise: 

113 """Reraise an exception after invoking some 

114 handler code. 

115 

116 Stores the existing exception info before 

117 invoking so that it is maintained across a potential 

118 coroutine context switch. 

119 

120 e.g.:: 

121 

122 try: 

123 sess.commit() 

124 except: 

125 with safe_reraise(): 

126 sess.rollback() 

127 

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

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

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

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

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

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

134 

135 """ 

136 

137 __slots__ = ("_exc_info",) 

138 

139 _exc_info: Union[ 

140 None, 

141 Tuple[ 

142 Type[BaseException], 

143 BaseException, 

144 types.TracebackType, 

145 ], 

146 Tuple[None, None, None], 

147 ] 

148 

149 def __enter__(self) -> None: 

150 self._exc_info = sys.exc_info() 

151 

152 def __exit__( 

153 self, 

154 type_: Optional[Type[BaseException]], 

155 value: Optional[BaseException], 

156 traceback: Optional[types.TracebackType], 

157 ) -> NoReturn: 

158 assert self._exc_info is not None 

159 # see #2703 for notes 

160 if type_ is None: 

161 exc_type, exc_value, exc_tb = self._exc_info 

162 assert exc_value is not None 

163 self._exc_info = None # remove potential circular references 

164 raise exc_value.with_traceback(exc_tb) 

165 else: 

166 self._exc_info = None # remove potential circular references 

167 assert value is not None 

168 raise value.with_traceback(traceback) 

169 

170 

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

172 seen: Set[Any] = set() 

173 

174 stack = [cls] 

175 while stack: 

176 cls = stack.pop() 

177 if cls in seen: 

178 continue 

179 else: 

180 seen.add(cls) 

181 stack.extend(cls.__subclasses__()) 

182 yield cls 

183 

184 

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

186 if isinstance(element, str): 

187 return element 

188 else: 

189 try: 

190 return str(element) 

191 except Exception: 

192 return "unprintable element %r" % element 

193 

194 

195def clsname_as_plain_name( 

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

197) -> str: 

198 name = use_name or cls.__name__ 

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

200 

201 

202def method_is_overridden( 

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

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

205) -> bool: 

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

207 

208 if not isinstance(instance_or_cls, type): 

209 current_cls = instance_or_cls.__class__ 

210 else: 

211 current_cls = instance_or_cls 

212 

213 method_name = against_method.__name__ 

214 

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

216 

217 return current_method != against_method 

218 

219 

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

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

222 

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

224 

225 """ 

226 ret: List[Any] = [] 

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

228 if hasattr(x, "__index__"): 

229 x = x.__index__() 

230 ret.append(x) 

231 return tuple(ret) 

232 

233 

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

235 used_set = set(used) 

236 for base in bases: 

237 pool = itertools.chain( 

238 (base,), 

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

240 ) 

241 for sym in pool: 

242 if sym not in used_set: 

243 used_set.add(sym) 

244 yield sym 

245 break 

246 else: 

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

248 

249 

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

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

252 

253 while n: 

254 b = n & (~n + 1) 

255 yield fn(b) 

256 n ^= b 

257 

258 

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

260 

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

262 

263 

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

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

266 

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

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

269 raise Exception("not a decoratable function") 

270 

271 # Python 3.14 defer creating __annotations__ until its used. 

272 # We do not want to create __annotations__ now. 

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

274 if annofunc is not None: 

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

276 try: 

277 spec = compat.inspect_getfullargspec(fn) 

278 finally: 

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

280 else: 

281 spec = compat.inspect_getfullargspec(fn) 

282 

283 # Do not generate code for annotations. 

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

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

286 # copy of large globals for compiling. 

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

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

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

290 spec = spec._replace( 

291 annotations={}, 

292 defaults=empty_defaults, 

293 kwonlydefaults=empty_kwdefaults, 

294 ) 

295 

296 names = ( 

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

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

299 + (fn.__name__,) 

300 ) 

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

302 

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

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

305 metadata["name"] = fn.__name__ 

306 

307 if inspect.iscoroutinefunction(fn): 

308 metadata["prefix"] = "async " 

309 metadata["target_prefix"] = "await " 

310 else: 

311 metadata["prefix"] = "" 

312 metadata["target_prefix"] = "" 

313 

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

315 # SQLAlchemy that arguments should be passed positionally 

316 # rather than as keyword 

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

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

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

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

321 # be further improved in this area 

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

323 code = """\ 

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

325 return %(target_prefix)s%(target)s(%(fn)s, %(apply_pos)s) 

326""" % metadata 

327 else: 

328 code = """\ 

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

330 return %(target_prefix)s%(target)s(%(fn)s, %(apply_kw)s) 

331""" % metadata 

332 

333 env: Dict[str, Any] = { 

334 targ_name: target, 

335 fn_name: fn, 

336 "__name__": fn.__module__, 

337 } 

338 

339 decorated = cast( 

340 types.FunctionType, 

341 _exec_code_in_env(code, env, fn.__name__), 

342 ) 

343 decorated.__defaults__ = fn.__defaults__ 

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

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

346 

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

348 

349 

350def _exec_code_in_env( 

351 code: Union[str, types.CodeType], env: Dict[str, Any], fn_name: str 

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

353 exec(code, env) 

354 return env[fn_name] # type: ignore[no-any-return] 

355 

356 

357_PF = TypeVar("_PF") 

358_TE = TypeVar("_TE") 

359 

360 

361class PluginLoader: 

362 def __init__( 

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

364 ): 

365 self.group = group 

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

367 self.auto_fn = auto_fn 

368 

369 def clear(self): 

370 self.impls.clear() 

371 

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

373 if name in self.impls: 

374 return self.impls[name]() 

375 

376 if self.auto_fn: 

377 loader = self.auto_fn(name) 

378 if loader: 

379 self.impls[name] = loader 

380 return loader() 

381 

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

383 if impl.name == name: 

384 self.impls[name] = impl.load 

385 return impl.load() 

386 

387 raise exc.NoSuchModuleError( 

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

389 ) 

390 

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

392 def load(): 

393 mod = __import__(modulepath) 

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

395 mod = getattr(mod, token) 

396 return getattr(mod, objname) 

397 

398 self.impls[name] = load 

399 

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

401 del self.impls[name] 

402 

403 

404def _inspect_func_args(fn): 

405 try: 

406 co_varkeywords = inspect.CO_VARKEYWORDS 

407 except AttributeError: 

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

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

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

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

412 spec = compat.inspect_getfullargspec(fn) 

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

414 else: 

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

416 co = fn.__code__ 

417 nargs = co.co_argcount 

418 return ( 

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

420 bool(co.co_flags & co_varkeywords), 

421 ) 

422 

423 

424@overload 

425def get_cls_kwargs( 

426 cls: type, 

427 *, 

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

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

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

431 

432 

433@overload 

434def get_cls_kwargs( 

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

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

437 

438 

439def get_cls_kwargs( 

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

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

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

443 

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

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

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

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

448 

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

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

451 objects which is a performance-sensitive operation. 

452 

453 No anonymous tuple arguments please ! 

454 

455 """ 

456 toplevel = _set is None 

457 if toplevel: 

458 _set = set() 

459 assert _set is not None 

460 

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

462 

463 has_init = ( 

464 ctr 

465 and isinstance(ctr, types.FunctionType) 

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

467 ) 

468 

469 if has_init: 

470 names, has_kw = _inspect_func_args(ctr) 

471 _set.update(names) 

472 

473 if not has_kw and not toplevel: 

474 if raiseerr: 

475 raise TypeError( 

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

477 ) 

478 else: 

479 return None 

480 else: 

481 has_kw = False 

482 

483 if not has_init or has_kw: 

484 for c in cls.__bases__: 

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

486 break 

487 

488 _set.discard("self") 

489 return _set 

490 

491 

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

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

494 

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

496 etc. 

497 

498 """ 

499 

500 return compat.inspect_getfullargspec(func)[0] 

501 

502 

503def get_callable_argspec( 

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

505) -> compat.FullArgSpec: 

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

507 

508 All pure-Python callables are accepted, including 

509 functions, methods, classes, objects with __call__; 

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

511 raise a TypeError. 

512 

513 """ 

514 if inspect.isbuiltin(fn): 

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

516 elif inspect.isfunction(fn) or ( 

517 hasattr(fn, "__code__") 

518 and not inspect.isclass(fn) 

519 and not inspect.ismethod(fn) 

520 ): 

521 if _is_init and no_self: 

522 spec = compat.inspect_getfullargspec(fn) 

523 return compat.FullArgSpec( 

524 spec.args[1:], 

525 spec.varargs, 

526 spec.varkw, 

527 spec.defaults, 

528 spec.kwonlyargs, 

529 spec.kwonlydefaults, 

530 spec.annotations, 

531 ) 

532 else: 

533 return compat.inspect_getfullargspec(fn) 

534 elif inspect.ismethod(fn): 

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

536 spec = compat.inspect_getfullargspec(fn.__func__) 

537 return compat.FullArgSpec( 

538 spec.args[1:], 

539 spec.varargs, 

540 spec.varkw, 

541 spec.defaults, 

542 spec.kwonlyargs, 

543 spec.kwonlydefaults, 

544 spec.annotations, 

545 ) 

546 else: 

547 return compat.inspect_getfullargspec(fn.__func__) 

548 elif inspect.isclass(fn): 

549 return get_callable_argspec( 

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

551 ) 

552 elif hasattr(fn, "__func__"): 

553 return compat.inspect_getfullargspec(fn.__func__) 

554 elif hasattr(fn, "__call__"): 

555 if inspect.ismethod(fn.__call__): 

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

557 else: 

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

559 else: 

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

561 

562 

563def format_argspec_plus( 

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

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

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

567 

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

569 

570 fn 

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

572 grouped 

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

574 

575 Returns: 

576 

577 args 

578 Full inspect.formatargspec for fn 

579 self_arg 

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

581 if the function defines no positional arguments. 

582 apply_pos 

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

584 passed positionally. 

585 apply_kw 

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

587 apply_pos_proxied 

588 Like apply_pos but omits the self/cls argument 

589 

590 Example:: 

591 

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

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

594 'self_arg': 'self', 

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

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

597 

598 """ 

599 if callable(fn): 

600 spec = compat.inspect_getfullargspec(fn) 

601 else: 

602 spec = fn 

603 

604 args = compat.inspect_formatargspec(*spec) 

605 

606 apply_pos = compat.inspect_formatargspec( 

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

608 ) 

609 

610 if spec[0]: 

611 self_arg = spec[0][0] 

612 

613 apply_pos_proxied = compat.inspect_formatargspec( 

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

615 ) 

616 

617 elif spec[1]: 

618 # I'm not sure what this is 

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

620 

621 apply_pos_proxied = apply_pos 

622 else: 

623 self_arg = None 

624 apply_pos_proxied = apply_pos 

625 

626 num_defaults = 0 

627 if spec[3]: 

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

629 if spec[4]: 

630 num_defaults += len(spec[4]) 

631 

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

633 

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

635 

636 if num_defaults: 

637 defaulted_vals = name_args[0 - num_defaults :] 

638 else: 

639 defaulted_vals = () 

640 

641 apply_kw = compat.inspect_formatargspec( 

642 name_args, 

643 spec[1], 

644 spec[2], 

645 defaulted_vals, 

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

647 ) 

648 

649 if spec[0]: 

650 apply_kw_proxied = compat.inspect_formatargspec( 

651 name_args[1:], 

652 spec[1], 

653 spec[2], 

654 defaulted_vals, 

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

656 ) 

657 else: 

658 apply_kw_proxied = apply_kw 

659 

660 if grouped: 

661 return dict( 

662 grouped_args=args, 

663 self_arg=self_arg, 

664 apply_pos=apply_pos, 

665 apply_kw=apply_kw, 

666 apply_pos_proxied=apply_pos_proxied, 

667 apply_kw_proxied=apply_kw_proxied, 

668 ) 

669 else: 

670 return dict( 

671 grouped_args=args, 

672 self_arg=self_arg, 

673 apply_pos=apply_pos[1:-1], 

674 apply_kw=apply_kw[1:-1], 

675 apply_pos_proxied=apply_pos_proxied[1:-1], 

676 apply_kw_proxied=apply_kw_proxied[1:-1], 

677 ) 

678 

679 

680def format_argspec_init(method, grouped=True): 

681 """format_argspec_plus with considerations for typical __init__ methods 

682 

683 Wraps format_argspec_plus with error handling strategies for typical 

684 __init__ cases: 

685 

686 .. sourcecode:: text 

687 

688 object.__init__ -> (self) 

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

690 

691 """ 

692 if method is object.__init__: 

693 grouped_args = "(self)" 

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

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

696 else: 

697 try: 

698 return format_argspec_plus(method, grouped=grouped) 

699 except TypeError: 

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

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

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

703 return dict( 

704 self_arg="self", 

705 grouped_args=grouped_args, 

706 apply_pos=args, 

707 apply_kw=args, 

708 apply_pos_proxied=proxied, 

709 apply_kw_proxied=proxied, 

710 ) 

711 

712 

713def create_proxy_methods( 

714 target_cls: Type[Any], 

715 target_cls_sphinx_name: str, 

716 proxy_cls_sphinx_name: str, 

717 classmethods: Sequence[str] = (), 

718 methods: Sequence[str] = (), 

719 attributes: Sequence[str] = (), 

720 use_intermediate_variable: Sequence[str] = (), 

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

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

723 class. 

724 

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

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

727 statically generate proxy methods and attributes that are fully 

728 recognized by typing tools such as mypy. 

729 

730 """ 

731 

732 def decorate(cls): 

733 return cls 

734 

735 return decorate 

736 

737 

738def getargspec_init(method): 

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

740 

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

742 

743 .. sourcecode:: text 

744 

745 object.__init__ -> (self) 

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

747 

748 """ 

749 try: 

750 return compat.inspect_getfullargspec(method) 

751 except TypeError: 

752 if method is object.__init__: 

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

754 else: 

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

756 

757 

758def unbound_method_to_callable(func_or_cls): 

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

760 required. 

761 

762 """ 

763 

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

765 return func_or_cls.__func__ 

766 else: 

767 return func_or_cls 

768 

769 

770class GenericRepr: 

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

772 

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

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

775 

776 .. versionadded:: 2.1 

777 """ 

778 

779 __slots__ = ( 

780 "_obj", 

781 "_additional_kw", 

782 "_to_inspect", 

783 "_omit_kwarg", 

784 "_class_name", 

785 ) 

786 

787 _obj: Any 

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

789 _to_inspect: List[object] 

790 _omit_kwarg: Sequence[str] 

791 _class_name: Optional[str] 

792 

793 def __init__( 

794 self, 

795 obj: Any, 

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

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

798 omit_kwarg: Sequence[str] = (), 

799 ): 

800 """Create a GenericRepr object. 

801 

802 :param obj: The object being repr'd 

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

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

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

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

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

808 the repr output 

809 """ 

810 self._obj = obj 

811 self._additional_kw = additional_kw 

812 self._to_inspect = ( 

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

814 ) 

815 self._omit_kwarg = omit_kwarg 

816 self._class_name = None 

817 

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

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

820 

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

822 This method allows it to be overridden. 

823 

824 :param class_name: The class name to use 

825 :return: self, for method chaining 

826 """ 

827 self._class_name = class_name 

828 return self 

829 

830 def __str__(self) -> str: 

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

832 obj = self._obj 

833 to_inspect = self._to_inspect 

834 additional_kw = self._additional_kw 

835 omit_kwarg = self._omit_kwarg 

836 

837 missing = object() 

838 

839 pos_args = [] 

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

841 _collections.OrderedDict() 

842 ) 

843 vargs = None 

844 for i, insp in enumerate(to_inspect): 

845 try: 

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

847 except TypeError: 

848 continue 

849 else: 

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

851 if i == 0: 

852 if spec.varargs: 

853 vargs = spec.varargs 

854 if default_len: 

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

856 else: 

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

858 else: 

859 kw_args.update( 

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

861 ) 

862 

863 if default_len: 

864 assert spec.defaults 

865 kw_args.update( 

866 [ 

867 (arg, default) 

868 for arg, default in zip( 

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

870 ) 

871 ] 

872 ) 

873 output: List[str] = [] 

874 

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

876 

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

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

879 

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

881 if arg in omit_kwarg: 

882 continue 

883 try: 

884 val = getattr(obj, arg, missing) 

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

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

887 except Exception: 

888 pass 

889 

890 if additional_kw: 

891 for arg, defval in additional_kw: 

892 try: 

893 val = getattr(obj, arg, missing) 

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

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

896 except Exception: 

897 pass 

898 

899 class_name = ( 

900 self._class_name 

901 if self._class_name is not None 

902 else obj.__class__.__name__ 

903 ) 

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

905 

906 

907def generic_repr( 

908 obj: Any, 

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

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

911 omit_kwarg: Sequence[str] = (), 

912) -> str: 

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

914 specification vs. same-named attributes present. 

915 

916 """ 

917 return str( 

918 GenericRepr( 

919 obj, 

920 additional_kw=additional_kw, 

921 to_inspect=to_inspect, 

922 omit_kwarg=omit_kwarg, 

923 ) 

924 ) 

925 

926 

927def class_hierarchy(cls): 

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

929 

930 Traverses diamond hierarchies. 

931 

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

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

934 class systemwide that derives from object. 

935 

936 """ 

937 

938 hier = {cls} 

939 process = list(cls.__mro__) 

940 while process: 

941 c = process.pop() 

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

943 

944 for b in bases: 

945 process.append(b) 

946 hier.add(b) 

947 

948 if c.__module__ == "builtins" or not hasattr(c, "__subclasses__"): 

949 continue 

950 

951 for s in [ 

952 _ 

953 for _ in ( 

954 c.__subclasses__() 

955 if not issubclass(c, type) 

956 else c.__subclasses__(c) 

957 ) 

958 if _ not in hier 

959 ]: 

960 process.append(s) 

961 hier.add(s) 

962 return list(hier) 

963 

964 

965def iterate_attributes(cls): 

966 """iterate all the keys and attributes associated 

967 with a class, without using getattr(). 

968 

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

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

971 

972 """ 

973 keys = dir(cls) 

974 for key in keys: 

975 for c in cls.__mro__: 

976 if key in c.__dict__: 

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

978 break 

979 

980 

981def monkeypatch_proxied_specials( 

982 into_cls, 

983 from_cls, 

984 skip=None, 

985 only=None, 

986 name="self.proxy", 

987 from_instance=None, 

988): 

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

990 

991 if only: 

992 dunders = only 

993 else: 

994 if skip is None: 

995 skip = ( 

996 "__slots__", 

997 "__del__", 

998 "__getattribute__", 

999 "__metaclass__", 

1000 "__getstate__", 

1001 "__setstate__", 

1002 ) 

1003 dunders = [ 

1004 m 

1005 for m in dir(from_cls) 

1006 if ( 

1007 m.startswith("__") 

1008 and m.endswith("__") 

1009 and not hasattr(into_cls, m) 

1010 and m not in skip 

1011 ) 

1012 ] 

1013 

1014 for method in dunders: 

1015 try: 

1016 maybe_fn = getattr(from_cls, method) 

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

1018 continue 

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

1020 fn = cast(types.FunctionType, maybe_fn) 

1021 

1022 except AttributeError: 

1023 continue 

1024 try: 

1025 spec = compat.inspect_getfullargspec(fn) 

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

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

1028 except TypeError: 

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

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

1031 

1032 py = ( 

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

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

1035 ) 

1036 

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

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

1039 ) 

1040 exec(py, env) 

1041 try: 

1042 env[method].__defaults__ = fn.__defaults__ 

1043 except AttributeError: 

1044 pass 

1045 setattr(into_cls, method, env[method]) 

1046 

1047 

1048def methods_equivalent(meth1, meth2): 

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

1050 

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

1052 meth2, "__func__", meth2 

1053 ) 

1054 

1055 

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

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

1058 

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

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

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

1062 are not in the interface are ignored. 

1063 

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

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

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

1067 

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

1069 

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

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

1072 an anonymous class is returned. 

1073 

1074 obj 

1075 A type, instance, or dictionary of callables. 

1076 cls 

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

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

1079 ``required``.. 

1080 methods 

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

1082 required 

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

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

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

1086 all public methods of the type are required. 

1087 

1088 """ 

1089 if not cls and not methods: 

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

1091 

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

1093 return obj 

1094 

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

1096 implemented = set(dir(obj)) 

1097 

1098 complies = operator.ge 

1099 if isinstance(required, type): 

1100 required = interface 

1101 elif not required: 

1102 required = set() 

1103 complies = operator.gt 

1104 else: 

1105 required = set(required) 

1106 

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

1108 return obj 

1109 

1110 # No dict duck typing here. 

1111 if not isinstance(obj, dict): 

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

1113 raise TypeError( 

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

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

1116 ) 

1117 

1118 class AnonymousInterface: 

1119 """A callable-holding shell.""" 

1120 

1121 if cls: 

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

1123 found = set() 

1124 

1125 for method, impl in dictlike_iteritems(obj): 

1126 if method not in interface: 

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

1128 if not callable(impl): 

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

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

1131 found.add(method) 

1132 

1133 if complies(found, required): 

1134 return AnonymousInterface 

1135 

1136 raise TypeError( 

1137 "dictionary does not contain required keys %s" 

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

1139 ) 

1140 

1141 

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

1143 

1144 

1145class generic_fn_descriptor(Generic[_T_co]): 

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

1147 present in dict 

1148 

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

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

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

1152 classes used for the same attribute. 

1153 

1154 """ 

1155 

1156 fget: Callable[..., _T_co] 

1157 __doc__: Optional[str] 

1158 __name__: str 

1159 

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

1161 self.fget = fget 

1162 self.__doc__ = doc or fget.__doc__ 

1163 self.__name__ = fget.__name__ 

1164 

1165 @overload 

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

1167 

1168 @overload 

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

1170 

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

1172 raise NotImplementedError() 

1173 

1174 if TYPE_CHECKING: 

1175 

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

1177 

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

1179 

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

1181 raise NotImplementedError() 

1182 

1183 @classmethod 

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

1185 raise NotImplementedError() 

1186 

1187 

1188class _non_memoized_property(generic_fn_descriptor[_T_co]): 

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

1190 

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

1192 compatible with memoized_property which is also recognized as equivalent 

1193 by mypy. 

1194 

1195 """ 

1196 

1197 if not TYPE_CHECKING: 

1198 

1199 def __get__(self, obj, cls): 

1200 if obj is None: 

1201 return self 

1202 return self.fget(obj) 

1203 

1204 

1205class _memoized_property(generic_fn_descriptor[_T_co]): 

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

1207 

1208 if not TYPE_CHECKING: 

1209 

1210 def __get__(self, obj, cls): 

1211 if obj is None: 

1212 return self 

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

1214 return result 

1215 

1216 def _reset(self, obj): 

1217 _memoized_property.reset(obj, self.__name__) 

1218 

1219 @classmethod 

1220 def reset(cls, obj, name): 

1221 obj.__dict__.pop(name, None) 

1222 

1223 

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

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

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

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

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

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

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

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

1232# way to achieve this. 

1233# additional issues, RO properties: 

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

1235if TYPE_CHECKING: 

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

1237 # be the same class 

1238 memoized_property = generic_fn_descriptor 

1239 non_memoized_property = generic_fn_descriptor 

1240 

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

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

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

1244 ro_memoized_property = property 

1245 ro_non_memoized_property = property 

1246 

1247else: 

1248 memoized_property = ro_memoized_property = _memoized_property 

1249 non_memoized_property = ro_non_memoized_property = _non_memoized_property 

1250 

1251 

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

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

1254 

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

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

1257 called with different arguments. 

1258 

1259 """ 

1260 

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

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

1263 

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

1265 return result 

1266 

1267 memo.__name__ = fn.__name__ 

1268 memo.__doc__ = fn.__doc__ 

1269 self.__dict__[fn.__name__] = memo 

1270 return result 

1271 

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

1273 

1274 

1275class HasMemoized: 

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

1277 collection for easy cache clearing, generative, etc. 

1278 

1279 """ 

1280 

1281 if not TYPE_CHECKING: 

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

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

1284 __slots__ = () 

1285 

1286 _memoized_keys: FrozenSet[str] = frozenset() 

1287 

1288 def _reset_memoizations(self) -> None: 

1289 for elem in self._memoized_keys: 

1290 self.__dict__.pop(elem, None) 

1291 

1292 def _assert_no_memoizations(self) -> None: 

1293 for elem in self._memoized_keys: 

1294 assert elem not in self.__dict__ 

1295 

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

1297 self.__dict__[key] = value 

1298 self._memoized_keys |= {key} 

1299 

1300 class memoized_attribute(memoized_property[_T]): 

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

1302 

1303 :meta private: 

1304 

1305 """ 

1306 

1307 fget: Callable[..., _T] 

1308 __doc__: Optional[str] 

1309 __name__: str 

1310 

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

1312 self.fget = fget 

1313 self.__doc__ = doc or fget.__doc__ 

1314 self.__name__ = fget.__name__ 

1315 

1316 @overload 

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

1318 

1319 @overload 

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

1321 

1322 def __get__(self, obj, cls): 

1323 if obj is None: 

1324 return self 

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

1326 obj._memoized_keys |= {self.__name__} 

1327 return result 

1328 

1329 @classmethod 

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

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

1332 

1333 :meta private: 

1334 

1335 """ 

1336 

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

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

1339 

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

1341 return result 

1342 

1343 memo.__name__ = fn.__name__ 

1344 memo.__doc__ = fn.__doc__ 

1345 self.__dict__[fn.__name__] = memo 

1346 self._memoized_keys |= {fn.__name__} 

1347 return result 

1348 

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

1350 

1351 

1352if TYPE_CHECKING: 

1353 HasMemoized_ro_memoized_attribute = property 

1354else: 

1355 HasMemoized_ro_memoized_attribute = HasMemoized.memoized_attribute 

1356 

1357 

1358class MemoizedSlots: 

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

1360 

1361 This allows the functionality of memoized_property and 

1362 memoized_instancemethod to be available to a class using __slots__. 

1363 

1364 The memoized get is not threadsafe under freethreading and the 

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

1366 

1367 """ 

1368 

1369 __slots__ = () 

1370 

1371 def _fallback_getattr(self, key): 

1372 raise AttributeError(key) 

1373 

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

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

1376 "_memoized_method_" 

1377 ): 

1378 raise AttributeError(key) 

1379 # to avoid recursion errors when interacting with other __getattr__ 

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

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

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

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

1384 setattr(self, key, value) 

1385 return value 

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

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

1388 

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

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

1391 

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

1393 return result 

1394 

1395 memo.__name__ = meth.__name__ 

1396 memo.__doc__ = meth.__doc__ 

1397 setattr(self, key, memo) 

1398 return result 

1399 

1400 oneshot.__doc__ = meth.__doc__ 

1401 return oneshot 

1402 else: 

1403 return self._fallback_getattr(key) 

1404 

1405 

1406# from paste.deploy.converters 

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

1408 if isinstance(obj, str): 

1409 obj = obj.strip().lower() 

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

1411 return True 

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

1413 return False 

1414 else: 

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

1416 return bool(obj) 

1417 

1418 

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

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

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

1422 

1423 """ 

1424 

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

1426 if obj in text: 

1427 return obj 

1428 else: 

1429 return asbool(obj) 

1430 

1431 return bool_or_value 

1432 

1433 

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

1435 """Coerce to integer.""" 

1436 

1437 if value is None: 

1438 return value 

1439 return int(value) 

1440 

1441 

1442def coerce_kw_type( 

1443 kw: Dict[str, Any], 

1444 key: str, 

1445 type_: Type[Any], 

1446 flexi_bool: bool = True, 

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

1448) -> None: 

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

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

1451 when coercing to boolean. 

1452 """ 

1453 

1454 if dest is None: 

1455 dest = kw 

1456 

1457 if ( 

1458 key in kw 

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

1460 and kw[key] is not None 

1461 ): 

1462 if type_ is bool and flexi_bool: 

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

1464 else: 

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

1466 

1467 

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

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

1470 obj to retrieve values 

1471 

1472 """ 

1473 names = get_cls_kwargs(cls) 

1474 return (cls,) + tuple( 

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

1476 ) 

1477 

1478 

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

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

1481 

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

1483 

1484 """ 

1485 

1486 names = get_cls_kwargs(cls) 

1487 kw.update( 

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

1489 ) 

1490 return cls(*args, **kw) 

1491 

1492 

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

1494 """Return a threadsafe counter function.""" 

1495 

1496 lock = threading.Lock() 

1497 counter = itertools.count(1) 

1498 

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

1500 def _next(): 

1501 with lock: 

1502 return next(counter) 

1503 

1504 return _next 

1505 

1506 

1507def duck_type_collection( 

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

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

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

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

1512 property is present, return that preferentially. 

1513 """ 

1514 

1515 if hasattr(specimen, "__emulates__"): 

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

1517 if specimen.__emulates__ is not None and issubclass( 

1518 specimen.__emulates__, set 

1519 ): 

1520 return set 

1521 else: 

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

1523 

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

1525 if isa(specimen, list): 

1526 return list 

1527 elif isa(specimen, set): 

1528 return set 

1529 elif isa(specimen, dict): 

1530 return dict 

1531 

1532 if hasattr(specimen, "append"): 

1533 return list 

1534 elif hasattr(specimen, "add"): 

1535 return set 

1536 elif hasattr(specimen, "set"): 

1537 return dict 

1538 else: 

1539 return default 

1540 

1541 

1542def assert_arg_type( 

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

1544) -> Any: 

1545 if isinstance(arg, argtype): 

1546 return arg 

1547 else: 

1548 if isinstance(argtype, tuple): 

1549 raise exc.ArgumentError( 

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

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

1552 ) 

1553 else: 

1554 raise exc.ArgumentError( 

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

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

1557 ) 

1558 

1559 

1560def dictlike_iteritems(dictlike): 

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

1562 

1563 if hasattr(dictlike, "items"): 

1564 return list(dictlike.items()) 

1565 

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

1567 if getter is None: 

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

1569 

1570 if hasattr(dictlike, "iterkeys"): 

1571 

1572 def iterator(): 

1573 for key in dictlike.iterkeys(): 

1574 assert getter is not None 

1575 yield key, getter(key) 

1576 

1577 return iterator() 

1578 elif hasattr(dictlike, "keys"): 

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

1580 else: 

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

1582 

1583 

1584class classproperty(property): 

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

1586 on classes rather than instances. 

1587 

1588 The decorator is currently special when using the declarative 

1589 module, but note that the 

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

1591 decorator should be used for this purpose with declarative. 

1592 

1593 """ 

1594 

1595 fget: Callable[[Any], Any] 

1596 

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

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

1599 self.__doc__ = fget.__doc__ 

1600 

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

1602 return self.fget(cls) 

1603 

1604 

1605class hybridproperty(Generic[_T]): 

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

1607 self.func = func 

1608 self.clslevel = func 

1609 

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

1611 if instance is None: 

1612 clsval = self.clslevel(owner) 

1613 return clsval 

1614 else: 

1615 return self.func(instance) 

1616 

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

1618 self.clslevel = func 

1619 return self 

1620 

1621 

1622class rw_hybridproperty(Generic[_T]): 

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

1624 self.func = func 

1625 self.clslevel = func 

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

1627 

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

1629 if instance is None: 

1630 clsval = self.clslevel(owner) 

1631 return clsval 

1632 else: 

1633 return self.func(instance) 

1634 

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

1636 assert self.setfn is not None 

1637 self.setfn(instance, value) 

1638 

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

1640 self.setfn = func 

1641 return self 

1642 

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

1644 self.clslevel = func 

1645 return self 

1646 

1647 

1648class hybridmethod(Generic[_T]): 

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

1650 

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

1652 self.func = self.__func__ = func 

1653 self.clslevel = func 

1654 

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

1656 if instance is None: 

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

1658 owner, owner.__class__ 

1659 ) 

1660 else: 

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

1662 instance, owner 

1663 ) 

1664 

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

1666 self.clslevel = func 

1667 return self 

1668 

1669 

1670class symbol(int): 

1671 """A constant symbol. 

1672 

1673 >>> symbol("foo") is symbol("foo") 

1674 True 

1675 >>> symbol("foo") 

1676 <symbol 'foo> 

1677 

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

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

1680 

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

1682 

1683 """ 

1684 

1685 name: str 

1686 

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

1688 _lock = threading.Lock() 

1689 

1690 def __new__( 

1691 cls, 

1692 name: str, 

1693 doc: Optional[str] = None, 

1694 canonical: Optional[int] = None, 

1695 ) -> symbol: 

1696 with cls._lock: 

1697 sym = cls.symbols.get(name) 

1698 if sym is None: 

1699 assert isinstance(name, str) 

1700 if canonical is None: 

1701 canonical = hash(name) 

1702 sym = int.__new__(symbol, canonical) 

1703 sym.name = name 

1704 if doc: 

1705 sym.__doc__ = doc 

1706 

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

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

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

1710 # switch to stdlib enum.IntFlag. 

1711 cls.symbols[name] = sym 

1712 else: 

1713 if canonical and canonical != sym: 

1714 raise TypeError( 

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

1716 f"with new int value {canonical}" 

1717 ) 

1718 return sym 

1719 

1720 def __reduce__(self): 

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

1722 

1723 def __str__(self): 

1724 return repr(self) 

1725 

1726 def __repr__(self): 

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

1728 

1729 

1730class _IntFlagMeta(type): 

1731 def __init__( 

1732 cls, 

1733 classname: str, 

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

1735 dict_: Dict[str, Any], 

1736 **kw: Any, 

1737 ) -> None: 

1738 items: List[symbol] 

1739 cls._items = items = [] 

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

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

1742 continue 

1743 if isinstance(v, int): 

1744 sym = symbol(k, canonical=v) 

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

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

1747 else: 

1748 continue 

1749 setattr(cls, k, sym) 

1750 items.append(sym) 

1751 

1752 cls.__members__ = _collections.immutabledict( 

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

1754 ) 

1755 

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

1757 raise NotImplementedError( 

1758 "iter not implemented to ensure compatibility with " 

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

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

1761 ) 

1762 

1763 

1764class _FastIntFlag(metaclass=_IntFlagMeta): 

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

1766 operations. 

1767 

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

1769 and ``_FastIntFlag`` otherwise. 

1770 

1771 """ 

1772 

1773 

1774if TYPE_CHECKING: 

1775 from enum import IntFlag 

1776 

1777 FastIntFlag = IntFlag 

1778else: 

1779 FastIntFlag = _FastIntFlag 

1780 

1781 

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

1783 

1784 

1785def parse_user_argument_for_enum( 

1786 arg: Any, 

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

1788 name: str, 

1789 resolve_symbol_names: bool = False, 

1790) -> Optional[_E]: 

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

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

1793 

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

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

1796 such as True/False/ None etc. 

1797 

1798 :param arg: the user argument. 

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

1800 entries for each. 

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

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

1803 

1804 """ 

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

1806 if arg is enum_value: 

1807 return enum_value 

1808 elif resolve_symbol_names and arg == enum_value.name: 

1809 return enum_value 

1810 elif arg in choice: 

1811 return enum_value 

1812 

1813 if arg is None: 

1814 return None 

1815 

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

1817 

1818 

1819_creation_order = 1 

1820 

1821 

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

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

1824 

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

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

1827 threadsafe). 

1828 

1829 """ 

1830 global _creation_order 

1831 instance._creation_order = _creation_order 

1832 _creation_order += 1 

1833 

1834 

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

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

1837 a warning. 

1838 

1839 """ 

1840 try: 

1841 return func(*args, **kwargs) 

1842 except Exception: 

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

1844 

1845 

1846def ellipses_string(value, len_=25): 

1847 try: 

1848 if len(value) > len_: 

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

1850 else: 

1851 return value 

1852 except TypeError: 

1853 return value 

1854 

1855 

1856class _hash_limit_string(str): 

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

1858 of unique values. 

1859 

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

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

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

1863 

1864 

1865 """ 

1866 

1867 _hash: int 

1868 

1869 def __new__( 

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

1871 ) -> _hash_limit_string: 

1872 interpolated = (value % args) + ( 

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

1874 ) 

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

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

1877 return self 

1878 

1879 def __hash__(self) -> int: 

1880 return self._hash 

1881 

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

1883 return hash(self) == hash(other) 

1884 

1885 

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

1887 """Issue a warning. 

1888 

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

1890 the category. 

1891 

1892 """ 

1893 if code: 

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

1895 else: 

1896 _warnings_warn(msg, exc.SAWarning) 

1897 

1898 

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

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

1901 of registrations. 

1902 

1903 """ 

1904 if args: 

1905 msg = _hash_limit_string(msg, 10, args) 

1906 _warnings_warn(msg, exc.SAWarning) 

1907 

1908 

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

1910 

1911 

1912def tag_method_for_warnings( 

1913 message: str, category: Type[Warning] 

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

1915 def go(fn): 

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

1917 return fn 

1918 

1919 return go 

1920 

1921 

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

1923 

1924 

1925def _warnings_warn( 

1926 message: Union[str, Warning], 

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

1928 stacklevel: int = 2, 

1929) -> None: 

1930 

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

1932 category = type(message) 

1933 

1934 # adjust the given stacklevel to be outside of SQLAlchemy 

1935 try: 

1936 frame = sys._getframe(stacklevel) 

1937 except ValueError: 

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

1939 # but don't crash 

1940 stacklevel = 0 

1941 except: 

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

1943 # ok, but don't crash 

1944 stacklevel = 0 

1945 else: 

1946 stacklevel_found = warning_tag_found = False 

1947 while frame is not None: 

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

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

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

1951 if not stacklevel_found and not re.match( 

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

1953 ): 

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

1955 # were found. 

1956 stacklevel_found = True 

1957 

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

1959 # scanning up the whole traceback 

1960 

1961 if frame.f_code in _warning_tags: 

1962 warning_tag_found = True 

1963 _suffix, _category = _warning_tags[frame.f_code] 

1964 category = category or _category 

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

1966 

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

1968 

1969 if not stacklevel_found: 

1970 stacklevel += 1 

1971 elif stacklevel_found and warning_tag_found: 

1972 break 

1973 

1974 if category is not None: 

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

1976 else: 

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

1978 

1979 

1980def only_once( 

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

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

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

1984 once.""" 

1985 

1986 once = [fn] 

1987 

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

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

1990 # which interferes with the event system's expectations 

1991 strong_fn = fn # noqa 

1992 if once: 

1993 once_fn = once.pop() 

1994 try: 

1995 return once_fn(*arg, **kw) 

1996 except: 

1997 if retry_on_exception: 

1998 once.insert(0, once_fn) 

1999 raise 

2000 

2001 return None 

2002 

2003 return go 

2004 

2005 

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

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

2008 

2009 

2010def chop_traceback( 

2011 tb: List[str], 

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

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

2014) -> List[str]: 

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

2016 

2017 :param tb: 

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

2019 

2020 :param exclude_prefix: 

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

2022 ``tb`` 

2023 

2024 :param exclude_suffix: 

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

2026 """ 

2027 start = 0 

2028 end = len(tb) - 1 

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

2030 start += 1 

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

2032 end -= 1 

2033 return tb[start : end + 1] 

2034 

2035 

2036def attrsetter(attrname): 

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

2038 env = locals().copy() 

2039 exec(code, env) 

2040 return env["set"] 

2041 

2042 

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

2044 

2045 

2046class TypingOnly: 

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

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

2049 

2050 """ 

2051 

2052 __slots__ = () 

2053 

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

2055 if TypingOnly in cls.__bases__: 

2056 remaining = { 

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

2058 } 

2059 if remaining: 

2060 raise AssertionError( 

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

2062 f"additional attributes {remaining}." 

2063 ) 

2064 super().__init_subclass__(**kw) 

2065 

2066 

2067class EnsureKWArg: 

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

2069 don't already. 

2070 

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

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

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

2074 

2075 """ 

2076 

2077 ensure_kwarg: str 

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

2079 should accept ``**kw`` arguments. 

2080 

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

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

2083 

2084 """ 

2085 

2086 def __init_subclass__(cls) -> None: 

2087 fn_reg = cls.ensure_kwarg 

2088 clsdict = cls.__dict__ 

2089 if fn_reg: 

2090 for key in clsdict: 

2091 m = re.match(fn_reg, key) 

2092 if m: 

2093 fn = clsdict[key] 

2094 spec = compat.inspect_getfullargspec(fn) 

2095 if not spec.varkw: 

2096 wrapped = cls._wrap_w_kw(fn) 

2097 setattr(cls, key, wrapped) 

2098 super().__init_subclass__() 

2099 

2100 @classmethod 

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

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

2103 return fn(*arg) 

2104 

2105 return update_wrapper(wrap, fn) 

2106 

2107 

2108def wrap_callable(wrapper, fn): 

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

2110 a ``__call__()`` method. 

2111 

2112 :param fn: 

2113 object with __call__ method 

2114 

2115 """ 

2116 if hasattr(fn, "__name__"): 

2117 return update_wrapper(wrapper, fn) 

2118 else: 

2119 _f = wrapper 

2120 _f.__name__ = fn.__class__.__name__ 

2121 if hasattr(fn, "__module__"): 

2122 _f.__module__ = fn.__module__ 

2123 

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

2125 _f.__doc__ = fn.__call__.__doc__ 

2126 elif fn.__doc__: 

2127 _f.__doc__ = fn.__doc__ 

2128 

2129 return _f 

2130 

2131 

2132def quoted_token_parser(value): 

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

2134 

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

2136 

2137 E.g.:: 

2138 

2139 >>> quoted_token_parser("name") 

2140 ["name"] 

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

2142 ["schema", "name"] 

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

2144 ['Schema', 'Name'] 

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

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

2147 

2148 """ 

2149 

2150 if '"' not in value: 

2151 return value.split(".") 

2152 

2153 # 0 = outside of quotes 

2154 # 1 = inside of quotes 

2155 state = 0 

2156 result: List[List[str]] = [[]] 

2157 idx = 0 

2158 lv = len(value) 

2159 while idx < lv: 

2160 char = value[idx] 

2161 if char == '"': 

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

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

2164 idx += 1 

2165 else: 

2166 state ^= 1 

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

2168 result.append([]) 

2169 else: 

2170 result[-1].append(char) 

2171 idx += 1 

2172 

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

2174 

2175 

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

2177 params = _collections.to_list(params) 

2178 

2179 def decorate(fn): 

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

2181 if doc: 

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

2183 fn.__doc__ = doc 

2184 return fn 

2185 

2186 return decorate 

2187 

2188 

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

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

2191 if len(split_text) == 1: 

2192 return text 

2193 else: 

2194 firstline, remaining = split_text 

2195 if not firstline.startswith(" "): 

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

2197 else: 

2198 return textwrap.dedent(text) 

2199 

2200 

2201def inject_docstring_text( 

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

2203) -> str: 

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

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

2206 if len(lines) == 1: 

2207 lines.append("") 

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

2209 if injectlines[0]: 

2210 injectlines.insert(0, "") 

2211 

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

2213 blanks.insert(0, 0) 

2214 

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

2216 

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

2218 return "\n".join(lines) 

2219 

2220 

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

2222 

2223 

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

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

2226 lines = [] 

2227 

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

2229 

2230 to_inject = None 

2231 while doclines: 

2232 line = doclines.popleft() 

2233 

2234 m = _param_reg.match(line) 

2235 

2236 if to_inject is None: 

2237 if m: 

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

2239 if param in inject_params: 

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

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

2242 

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

2244 # indentation 

2245 if doclines: 

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

2247 if m2: 

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

2249 

2250 to_inject = indent + inject_params[param] 

2251 elif m: 

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

2253 to_inject = None 

2254 elif not line.rstrip(): 

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

2256 to_inject = None 

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

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

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

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

2261 continue 

2262 lines.append(line) 

2263 

2264 return "\n".join(lines) 

2265 

2266 

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

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

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

2270 if len(names) == 0: 

2271 return None 

2272 flag = len(names) <= 4 

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

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

2275 if flag: 

2276 return ", ".join(res) 

2277 else: 

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

2279 

2280 

2281def has_compiled_ext(raise_=False): 

2282 from ._has_cython import HAS_CYEXTENSION 

2283 

2284 if HAS_CYEXTENSION: 

2285 return True 

2286 elif raise_: 

2287 raise ImportError( 

2288 "cython extensions were expected to be installed, " 

2289 "but are not present" 

2290 ) 

2291 else: 

2292 return False 

2293 

2294 

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

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

2297 compiled with cython. 

2298 """ 

2299 full_name = module.__name__ 

2300 assert module.__spec__ 

2301 parent_name = module.__spec__.parent 

2302 assert parent_name 

2303 parent_module = sys.modules[parent_name] 

2304 assert parent_module.__spec__ 

2305 package_path = parent_module.__spec__.origin 

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

2307 

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

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

2310 

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

2312 assert py_spec 

2313 py_module = importlib.util.module_from_spec(py_spec) 

2314 assert py_spec.loader 

2315 py_spec.loader.exec_module(py_module) 

2316 return cast(_M, py_module) 

2317 

2318 

2319_pre_release_normalize = { 

2320 "a": "a", 

2321 "alpha": "a", 

2322 "b": "b", 

2323 "beta": "b", 

2324 "c": "rc", 

2325 "pre": "rc", 

2326 "preview": "rc", 

2327 "rc": "rc", 

2328} 

2329 

2330_version_string_re = re.compile( 

2331 r""" 

2332 \s* 

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

2334 v? 

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

2336 (?: # pre-release 

2337 [-_.]? 

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

2339 [-_.]? 

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

2341 )? 

2342 (?: # post-release 

2343 [-_.]? 

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

2345 [-_.]? 

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

2347 )? 

2348 (?: # developmental release 

2349 [-_.]? 

2350 (?P<dev_l>dev) 

2351 [-_.]? 

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

2353 )? 

2354 """, 

2355 re.X | re.I, 

2356) 

2357 

2358_VersionSortKey = Tuple[ 

2359 Tuple[int, ...], 

2360 Tuple[int, str, int], 

2361 Tuple[int, int], 

2362 Tuple[int, int], 

2363] 

2364 

2365 

2366def _version_sort_key( 

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

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

2369 post: Optional[int], 

2370 dev: Optional[int], 

2371) -> _VersionSortKey: 

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

2373 # a dev release with no other qualifiers precedes every 

2374 # pre-release of the same release number 

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

2376 elif pre is None: 

2377 pre_key = (1, "", 0) 

2378 else: 

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

2380 

2381 return ( 

2382 release, 

2383 pre_key, 

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

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

2386 ) 

2387 

2388 

2389def _version_comparison( 

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

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

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

2393 

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

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

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

2397 final release; anything else is not comparable. 

2398 

2399 """ 

2400 

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

2402 if isinstance(other, VersionInfo): 

2403 other_key = other._sort_key 

2404 elif isinstance(other, tuple): 

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

2406 else: 

2407 return NotImplemented 

2408 return op(self._sort_key, other_key) 

2409 

2410 return compare 

2411 

2412 

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

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

2415 

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

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

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

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

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

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

2422 ``(2, 0, 0)``. 

2423 

2424 Plain tuples are interpreted as final releases when compared against 

2425 a :class:`.VersionInfo`. 

2426 

2427 .. versionadded:: 2.1 

2428 

2429 """ 

2430 

2431 string: Optional[str] 

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

2433 

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

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

2436 

2437 post: Optional[int] 

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

2439 

2440 dev: Optional[int] 

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

2442 

2443 _sort_key: _VersionSortKey 

2444 

2445 def __new__( 

2446 cls, 

2447 release: Sequence[int] = (), 

2448 *, 

2449 string: Optional[str] = None, 

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

2451 post: Optional[int] = None, 

2452 dev: Optional[int] = None, 

2453 ) -> VersionInfo: 

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

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

2456 return tuple.__new__(cls, release) 

2457 

2458 def __init__( 

2459 self, 

2460 release: Sequence[int] = (), 

2461 *, 

2462 string: Optional[str] = None, 

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

2464 post: Optional[int] = None, 

2465 dev: Optional[int] = None, 

2466 ): 

2467 self.string = string 

2468 self.pre = pre 

2469 self.post = post 

2470 self.dev = dev 

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

2472 

2473 def __repr__(self) -> str: 

2474 if self.string is not None: 

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

2476 else: 

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

2478 

2479 def __str__(self) -> str: 

2480 if self.string is not None: 

2481 return self.string 

2482 else: 

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

2484 

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

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

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

2488 __eq__ = _version_comparison(operator.eq) 

2489 __ne__ = _version_comparison(operator.ne) 

2490 __lt__ = _version_comparison(operator.lt) 

2491 __le__ = _version_comparison(operator.le) 

2492 __gt__ = _version_comparison(operator.gt) 

2493 __ge__ = _version_comparison(operator.ge) 

2494 

2495 def __hash__(self) -> int: 

2496 return hash(self._sort_key) 

2497 

2498 

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

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

2501 

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

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

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

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

2506 

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

2508 located at all. 

2509 

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

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

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

2513 rejects each of the above outright. 

2514 

2515 .. versionadded:: 2.1 

2516 

2517 """ 

2518 

2519 if not version: 

2520 return VersionInfo((), string=version) 

2521 

2522 m = _version_string_re.match(version) 

2523 if m is None: 

2524 return VersionInfo((), string=version) 

2525 

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

2527 

2528 pre_l = m.group("pre_l") 

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

2530 if pre_l is not None: 

2531 pre = ( 

2532 _pre_release_normalize[pre_l.lower()], 

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

2534 ) 

2535 else: 

2536 pre = None 

2537 

2538 return VersionInfo( 

2539 release, 

2540 string=version, 

2541 pre=pre, 

2542 post=( 

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

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

2545 else None 

2546 ), 

2547 dev=( 

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

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

2550 else None 

2551 ), 

2552 ) 

2553 

2554 

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

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

2557 :class:`.VersionInfo`. 

2558 

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

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

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

2562 the installed distribution is not necessarily the module that was 

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

2564 version of its own. 

2565 

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

2567 installed. 

2568 

2569 .. versionadded:: 2.1 

2570 

2571 """ 

2572 

2573 try: 

2574 version = importlib.metadata.version(distribution) 

2575 except importlib.metadata.PackageNotFoundError: 

2576 return VersionInfo() 

2577 else: 

2578 return parse_version_string(version) 

2579 

2580 

2581class _Missing(enum.Enum): 

2582 Missing = enum.auto() 

2583 

2584 

2585Missing = _Missing.Missing 

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