Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/glom/core.py: 58%

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

1233 statements  

1"""*glom gets results.* 

2 

3The ``glom`` package has one central entrypoint, 

4:func:`glom.glom`. Everything else in the package revolves around that 

5one function. Sometimes, big things come in small packages. 

6 

7A couple of conventional terms you'll see repeated many times below: 

8 

9* **target** - glom is built to work on any data, so we simply 

10 refer to the object being accessed as the *"target"* 

11* **spec** - *(aka "glomspec", short for specification)* The 

12 accompanying template used to specify the structure of the return 

13 value. 

14 

15Now that you know the terms, let's take a look around glom's powerful 

16semantics. 

17 

18""" 

19 

20 

21import os 

22import sys 

23import pdb 

24import copy 

25import warnings 

26import weakref 

27import operator 

28from abc import ABCMeta 

29from pprint import pprint 

30import string 

31from collections import OrderedDict 

32import traceback 

33 

34from face.helpers import get_wrap_width 

35from boltons.typeutils import make_sentinel 

36from boltons.iterutils import is_iterable 

37#from boltons.funcutils import format_invocation 

38 

39basestring = str 

40_AbstractIterableBase = ABCMeta('_AbstractIterableBase', (object,), {}) 

41from collections import ChainMap 

42from reprlib import Repr, recursive_repr 

43 

44GLOM_DEBUG = os.getenv('GLOM_DEBUG', '').strip().lower() 

45GLOM_DEBUG = False if (GLOM_DEBUG in ('', '0', 'false')) else True 

46 

47TRACE_WIDTH = max(get_wrap_width(max_width=110), 50) # min width 

48 

49PATH_STAR = True 

50# should * and ** be interpreted as parallel traversal in Path.from_text()? 

51# Changed to True in 23.1, this option to disable will go away soon 

52 

53_type_type = type 

54 

55_MISSING = make_sentinel('_MISSING') 

56SKIP = make_sentinel('SKIP') 

57SKIP.__doc__ = """ 

58The ``SKIP`` singleton can be returned from a function or included 

59via a :class:`~glom.Val` to cancel assignment into the output 

60object. 

61 

62>>> target = {'a': 'b'} 

63>>> spec = {'a': lambda t: t['a'] if t['a'] == 'a' else SKIP} 

64>>> glom(target, spec) 

65{} 

66>>> target = {'a': 'a'} 

67>>> glom(target, spec) 

68{'a': 'a'} 

69 

70Mostly used to drop keys from dicts (as above) or filter objects from 

71lists. 

72 

73.. note:: 

74 

75 SKIP was known as OMIT in versions 18.3.1 and prior. Versions 19+ 

76 will remove the OMIT alias entirely. 

77""" 

78OMIT = SKIP # backwards compat, remove in 19+ 

79 

80STOP = make_sentinel('STOP') 

81STOP.__doc__ = """ 

82The ``STOP`` singleton can be used to halt iteration of a list or 

83execution of a tuple of subspecs. 

84 

85>>> target = range(10) 

86>>> spec = [lambda x: x if x < 5 else STOP] 

87>>> glom(target, spec) 

88[0, 1, 2, 3, 4] 

89""" 

90 

91LAST_CHILD_SCOPE = make_sentinel('LAST_CHILD_SCOPE') 

92LAST_CHILD_SCOPE.__doc__ = """ 

93Marker that can be used by parents to keep track of the last child 

94scope executed. Useful for "lifting" results out of child scopes 

95for scopes that want to chain the scopes of their children together 

96similar to tuple. 

97""" 

98 

99NO_PYFRAME = make_sentinel('NO_PYFRAME') 

100NO_PYFRAME.__doc__ = """ 

101Used internally to mark scopes which are no longer wrapped 

102in a recursive glom() call, so that they can be cleaned up correctly 

103in case of exceptions 

104""" 

105 

106MODE = make_sentinel('MODE') 

107 

108MIN_MODE = make_sentinel('MIN_MODE') 

109 

110CHILD_ERRORS = make_sentinel('CHILD_ERRORS') 

111CHILD_ERRORS.__doc__ = """ 

112``CHILD_ERRORS`` is used by glom internals to keep track of 

113failed child branches of the current scope. 

114""" 

115 

116CUR_ERROR = make_sentinel('CUR_ERROR') 

117CUR_ERROR.__doc__ = """ 

118``CUR_ERROR`` is used by glom internals to keep track of 

119thrown exceptions. 

120""" 

121 

122_PKG_DIR_PATH = os.path.dirname(os.path.abspath(__file__)) 

123 

124class GlomError(Exception): 

125 """The base exception for all the errors that might be raised from 

126 :func:`glom` processing logic. 

127 

128 By default, exceptions raised from within functions passed to glom 

129 (e.g., ``len``, ``sum``, any ``lambda``) will not be wrapped in a 

130 GlomError. 

131 """ 

132 @classmethod 

133 def wrap(cls, exc): 

134 # TODO: need to test this against a wide array of exception types 

135 # this approach to wrapping errors works for exceptions 

136 # defined in pure-python as well as C 

137 exc_type = type(exc) 

138 bases = (GlomError,) if issubclass(GlomError, exc_type) else (exc_type, GlomError) 

139 exc_wrapper_type = type(f"GlomError.wrap({exc_type.__name__})", bases, {}) 

140 try: 

141 wrapper = exc_wrapper_type(*exc.args) 

142 wrapper.__wrapped = exc 

143 return wrapper 

144 except Exception: # maybe exception can't be re-created 

145 return exc 

146 

147 def _set_wrapped(self, exc): 

148 self.__wrapped = exc 

149 

150 def _finalize(self, scope): 

151 # careful when changing how this functionality works; pytest seems to mess with 

152 # the traceback module or sys.exc_info(). we saw different stacks when originally 

153 # developing this in June 2020. 

154 etype, evalue, _ = sys.exc_info() 

155 tb_lines = traceback.format_exc().strip().splitlines() 

156 limit = 0 

157 for line in reversed(tb_lines): 

158 if _PKG_DIR_PATH in line: 

159 limit -= 1 

160 break 

161 limit += 1 

162 self._tb_lines = tb_lines[-limit:] 

163 # if the first line is trying to put a caret at a byte-code location on a line that 

164 # isn't being displayed, skip it 

165 if set(self._tb_lines[0]) <= {' ', '^', '~'}: 

166 self._tb_lines = self._tb_lines[1:] 

167 self._scope = scope 

168 

169 def __str__(self): 

170 if getattr(self, '_finalized_str', None): 

171 return self._finalized_str 

172 elif getattr(self, '_scope', None) is not None: 

173 self._target_spec_trace = format_target_spec_trace(self._scope, self.__wrapped) 

174 parts = ["error raised while processing, details below.", 

175 " Target-spec trace (most recent last):", 

176 self._target_spec_trace] 

177 parts.extend(self._tb_lines) 

178 self._finalized_str = "\n".join(parts) 

179 return self._finalized_str 

180 

181 # else, not finalized 

182 try: 

183 exc_get_message = self.get_message 

184 except AttributeError: 

185 exc_get_message = super().__str__ 

186 return exc_get_message() 

187 

188 

189def _unpack_stack(scope, only_errors=True): 

190 """ 

191 convert scope to [[scope, spec, target, error, [children]]] 

192 

193 this is a convenience method for printing stacks 

194 

195 only_errors=True means ignore branches which may still be hanging around 

196 which were not involved in the stack trace of the error 

197 

198 only_errors=False could be useful for debugger / introspection (similar 

199 to traceback.print_stack()) 

200 """ 

201 stack = [] 

202 scope = scope.maps[0] 

203 while LAST_CHILD_SCOPE in scope: 

204 child = scope[LAST_CHILD_SCOPE] 

205 branches = scope[CHILD_ERRORS] 

206 if branches == [child]: 

207 branches = [] # if there's only one branch, count it as linear 

208 stack.append([scope, scope[Spec], scope[T], scope.get(CUR_ERROR), branches]) 

209 

210 # NB: this id() business is necessary to avoid a 

211 # nondeterministic bug in abc's __eq__ see #189 for details 

212 if id(child) in [id(b) for b in branches]: 

213 break # if child already covered by branches, stop the linear descent 

214 

215 scope = child.maps[0] 

216 else: # if break executed above, cur scope was already added 

217 stack.append([scope, scope[Spec], scope[T], scope.get(CUR_ERROR), []]) 

218 # push errors "down" to where they were first raised / first observed 

219 for i in range(len(stack) - 1): 

220 cur, nxt = stack[i], stack[i + 1] 

221 if cur[3] == nxt[3]: 

222 cur[3] = None 

223 if only_errors: # trim the stack to the last error 

224 # leave at least 1 to not break formatting func below 

225 # TODO: make format_target_spec_trace() tolerate an "empty" stack cleanly 

226 while len(stack) > 1 and stack[-1][3] is None: 

227 stack.pop() 

228 return stack 

229 

230 

231def _format_trace_value(value, maxlen): 

232 s = bbrepr(value).replace("\\'", "'") 

233 if len(s) > maxlen: 

234 try: 

235 suffix = '... (len=%s)' % len(value) 

236 except Exception: 

237 suffix = '...' 

238 s = s[:maxlen - len(suffix)] + suffix 

239 return s 

240 

241 

242def format_target_spec_trace(scope, root_error, width=TRACE_WIDTH, depth=0, prev_target=_MISSING, last_branch=True): 

243 """ 

244 unpack a scope into a multi-line but short summary 

245 """ 

246 segments = [] 

247 indent = " " + "|" * depth 

248 tick = "| " if depth else "- " 

249 def mk_fmt(label, t=None): 

250 pre = indent + (t or tick) + label + ": " 

251 fmt_width = width - len(pre) 

252 return lambda v: pre + _format_trace_value(v, fmt_width) 

253 fmt_t = mk_fmt("Target") 

254 fmt_s = mk_fmt("Spec") 

255 fmt_b = mk_fmt("Spec", "+ ") 

256 recurse = lambda s, last=False: format_target_spec_trace(s, root_error, width, depth + 1, prev_target, last) 

257 tb_exc_line = lambda e: "".join(traceback.format_exception_only(type(e), e))[:-1] 

258 fmt_e = lambda e: indent + tick + tb_exc_line(e) 

259 for scope, spec, target, error, branches in _unpack_stack(scope): 

260 if target is not prev_target: 

261 segments.append(fmt_t(target)) 

262 prev_target = target 

263 if branches: 

264 segments.append(fmt_b(spec)) 

265 segments.extend([recurse(s) for s in branches[:-1]]) 

266 segments.append(recurse(branches[-1], last_branch)) 

267 else: 

268 segments.append(fmt_s(spec)) 

269 if error is not None and error is not root_error: 

270 last_line_error = True 

271 segments.append(fmt_e(error)) 

272 else: 

273 last_line_error = False 

274 if depth: # \ on first line, X on last line 

275 remark = lambda s, m: s[:depth + 1] + m + s[depth + 2:] 

276 segments[0] = remark(segments[0], "\\") 

277 if not last_branch or last_line_error: 

278 segments[-1] = remark(segments[-1], "X") 

279 return "\n".join(segments) 

280 

281 

282# TODO: not used (yet) 

283def format_oneline_trace(scope): 

284 """ 

285 unpack a scope into a single line summary 

286 (shortest summary possible) 

287 """ 

288 # the goal here is to do a kind of delta-compression -- 

289 # if the target is the same, don't repeat it 

290 segments = [] 

291 prev_target = _MISSING 

292 for scope, spec, target, error, branches in _unpack_stack(scope, only_errors=False): 

293 segments.append('/') 

294 if type(spec) in (TType, Path): 

295 segments.append(bbrepr(spec)) 

296 else: 

297 segments.append(type(spec).__name__) 

298 if target != prev_target: 

299 segments.append('!') 

300 segments.append(type(target).__name__) 

301 if Path in scope: 

302 segments.append('<') 

303 segments.append('->'.join([str(p) for p in scope[Path]])) 

304 segments.append('>') 

305 prev_target = target 

306 

307 return "".join(segments) 

308 

309 

310class PathAccessError(GlomError, AttributeError, KeyError, IndexError): 

311 """This :exc:`GlomError` subtype represents a failure to access an 

312 attribute as dictated by the spec. The most commonly-seen error 

313 when using glom, it maintains a copy of the original exception and 

314 produces a readable error message for easy debugging. 

315 

316 If you see this error, you may want to: 

317 

318 * Check the target data is accurate using :class:`~glom.Inspect` 

319 * Catch the exception and return a semantically meaningful error message 

320 * Use :class:`glom.Coalesce` to specify a default 

321 * Use the top-level ``default`` kwarg on :func:`~glom.glom()` 

322 

323 In any case, be glad you got this error and not the one it was 

324 wrapping! 

325 

326 Args: 

327 exc (Exception): The error that arose when we tried to access 

328 *path*. Typically an instance of KeyError, AttributeError, 

329 IndexError, or TypeError, and sometimes others. 

330 path (Path): The full Path glom was in the middle of accessing 

331 when the error occurred. 

332 part_idx (int): The index of the part of the *path* that caused 

333 the error. 

334 

335 >>> target = {'a': {'b': None}} 

336 >>> glom(target, 'a.b.c') 

337 Traceback (most recent call last): 

338 ... 

339 PathAccessError: could not access 'c', part 2 of Path('a', 'b', 'c'), got error: ... 

340 

341 """ 

342 def __init__(self, exc, path, part_idx): 

343 self.exc = exc 

344 self.path = path 

345 self.part_idx = part_idx 

346 

347 def get_message(self): 

348 try: 

349 path_part = self.path.values()[self.part_idx] 

350 except (AttributeError, IndexError): 

351 path_part = self.path 

352 return ('could not access %r, part %r of %r, got error: %r' 

353 % (path_part, self.part_idx, self.path, self.exc)) 

354 

355 def __repr__(self): 

356 cn = self.__class__.__name__ 

357 return f'{cn}({self.exc!r}, {self.path!r}, {self.part_idx!r})' 

358 

359 

360class PathAssignError(GlomError): 

361 """This :exc:`GlomError` subtype is raised when an assignment fails, 

362 stemming from an :func:`~glom.assign` call or other 

363 :class:`~glom.Assign` usage. 

364 

365 One example would be assigning to an out-of-range position in a list:: 

366 

367 >>> assign(["short", "list"], Path(5), 'too far') # doctest: +SKIP 

368 Traceback (most recent call last): 

369 ... 

370 PathAssignError: could not assign 5 on object at Path(), got error: IndexError(... 

371 

372 Other assignment failures could be due to assigning to an 

373 ``@property`` or exception being raised inside a ``__setattr__()``. 

374 

375 """ 

376 def __init__(self, exc, path, dest_name): 

377 self.exc = exc 

378 self.path = path 

379 self.dest_name = dest_name 

380 

381 def get_message(self): 

382 return ('could not assign %r on object at %r, got error: %r' 

383 % (self.dest_name, self.path, self.exc)) 

384 

385 def __repr__(self): 

386 cn = self.__class__.__name__ 

387 return f'{cn}({self.exc!r}, {self.path!r}, {self.dest_name!r})' 

388 

389 

390class CoalesceError(GlomError): 

391 """This :exc:`GlomError` subtype is raised from within a 

392 :class:`Coalesce` spec's processing, when none of the subspecs 

393 match and no default is provided. 

394 

395 The exception object itself keeps track of several values which 

396 may be useful for processing: 

397 

398 Args: 

399 coal_obj (Coalesce): The original failing spec, see 

400 :class:`Coalesce`'s docs for details. 

401 skipped (list): A list of ignored values and exceptions, in the 

402 order that their respective subspecs appear in the original 

403 *coal_obj*. 

404 path: Like many GlomErrors, this exception knows the path at 

405 which it occurred. 

406 

407 >>> target = {} 

408 >>> glom(target, Coalesce('a', 'b')) 

409 Traceback (most recent call last): 

410 ... 

411 CoalesceError: no valid values found. Tried ('a', 'b') and got (PathAccessError, PathAccessError) ... 

412 

413 .. note:: 

414 

415 Coalesce is a *branching* specifier type, so as of v20.7.0, its 

416 exception messages feature an error tree. See 

417 :ref:`branched-exceptions` for details on how to interpret these 

418 exceptions. 

419 

420 """ 

421 def __init__(self, coal_obj, skipped, path): 

422 self.coal_obj = coal_obj 

423 self.skipped = skipped 

424 self.path = path 

425 

426 def __repr__(self): 

427 cn = self.__class__.__name__ 

428 return f'{cn}({self.coal_obj!r}, {self.skipped!r}, {self.path!r})' 

429 

430 def get_message(self): 

431 missed_specs = tuple(self.coal_obj.subspecs) 

432 skipped_vals = [v.__class__.__name__ 

433 if isinstance(v, self.coal_obj.skip_exc) 

434 else '<skipped %s>' % v.__class__.__name__ 

435 for v in self.skipped] 

436 msg = ('no valid values found. Tried %r and got (%s)' 

437 % (missed_specs, ', '.join(skipped_vals))) 

438 if self.coal_obj.skip is not _MISSING: 

439 msg += f', skip set to {self.coal_obj.skip!r}' 

440 if self.coal_obj.skip_exc is not GlomError: 

441 msg += f', skip_exc set to {self.coal_obj.skip_exc!r}' 

442 if self.path is not None: 

443 msg += f' (at path {self.path!r})' 

444 return msg 

445 

446 

447class BadSpec(GlomError, TypeError): 

448 """Raised when a spec structure is malformed, e.g., when a specifier 

449 type is invalid for the current mode.""" 

450 

451 

452class UnregisteredTarget(GlomError): 

453 """This :class:`GlomError` subtype is raised when a spec calls for an 

454 unsupported action on a target type. For instance, trying to 

455 iterate on an non-iterable target: 

456 

457 >>> glom(object(), ['a.b.c']) 

458 Traceback (most recent call last): 

459 ... 

460 UnregisteredTarget: target type 'object' not registered for 'iterate', expected one of registered types: (...) 

461 

462 It should be noted that this is a pretty uncommon occurrence in 

463 production glom usage. See the :ref:`setup-and-registration` 

464 section for details on how to avoid this error. 

465 

466 An UnregisteredTarget takes and tracks a few values: 

467 

468 Args: 

469 op (str): The name of the operation being performed ('get' or 'iterate') 

470 target_type (type): The type of the target being processed. 

471 type_map (dict): A mapping of target types that do support this operation 

472 path: The path at which the error occurred. 

473 

474 """ 

475 def __init__(self, op, target_type, type_map, path): 

476 self.op = op 

477 self.target_type = target_type 

478 self.type_map = type_map 

479 self.path = path 

480 super().__init__(op, target_type, type_map, path) 

481 

482 def __repr__(self): 

483 cn = self.__class__.__name__ 

484 # <type %r> is because Python 3 inexplicably changed the type 

485 # repr from <type *> to <class *> 

486 return ('%s(%r, <type %r>, %r, %r)' 

487 % (cn, self.op, self.target_type.__name__, self.type_map, self.path)) 

488 

489 def get_message(self): 

490 if not self.type_map: 

491 return ("glom() called without registering any types for operation '%s'. see" 

492 " glom.register() or Glommer's constructor for details." % (self.op,)) 

493 reg_types = sorted([t.__name__ for t, h in self.type_map.items() if h]) 

494 reg_types_str = '()' if not reg_types else ('(%s)' % ', '.join(reg_types)) 

495 msg = ("target type %r not registered for '%s', expected one of" 

496 " registered types: %s" % (self.target_type.__name__, self.op, reg_types_str)) 

497 if self.path: 

498 msg += f' (at {self.path!r})' 

499 return msg 

500 

501 

502if getattr(__builtins__, '__dict__', None) is not None: 

503 # pypy's __builtins__ is a module, as is CPython's REPL, but at 

504 # normal execution time it's a dict? 

505 __builtins__ = __builtins__.__dict__ 

506 

507 

508_BUILTIN_ID_NAME_MAP = {id(v): k 

509 for k, v in __builtins__.items()} 

510 

511 

512class _BBRepr(Repr): 

513 """A better repr for builtins, when the built-in repr isn't 

514 roundtrippable. 

515 """ 

516 def __init__(self): 

517 super().__init__() 

518 # turn up all the length limits very high 

519 for name in self.__dict__: 

520 if not isinstance(getattr(self, name), int): 

521 continue 

522 setattr(self, name, 1024) 

523 

524 def repr1(self, x, level): 

525 ret = Repr.repr1(self, x, level) 

526 if not ret.startswith('<'): 

527 return ret 

528 return _BUILTIN_ID_NAME_MAP.get(id(x), ret) 

529 

530 

531bbrepr = recursive_repr()(_BBRepr().repr) 

532 

533 

534class _BBReprFormatter(string.Formatter): 

535 """ 

536 allow format strings to be evaluated where {!r} will use bbrepr 

537 instead of repr 

538 """ 

539 def convert_field(self, value, conversion): 

540 if conversion == 'r': 

541 return bbrepr(value).replace("\\'", "'") 

542 return super().convert_field(value, conversion) 

543 

544 

545bbformat = _BBReprFormatter().format 

546 

547 

548# TODO: push this back up to boltons with repr kwarg 

549def format_invocation(name='', args=(), kwargs=None, **kw): 

550 """Given a name, positional arguments, and keyword arguments, format 

551 a basic Python-style function call. 

552 

553 >>> print(format_invocation('func', args=(1, 2), kwargs={'c': 3})) 

554 func(1, 2, c=3) 

555 >>> print(format_invocation('a_func', args=(1,))) 

556 a_func(1) 

557 >>> print(format_invocation('kw_func', kwargs=[('a', 1), ('b', 2)])) 

558 kw_func(a=1, b=2) 

559 

560 """ 

561 _repr = kw.pop('repr', bbrepr) 

562 if kw: 

563 raise TypeError('unexpected keyword args: %r' % ', '.join(kw.keys())) 

564 kwargs = kwargs or {} 

565 a_text = ', '.join([_repr(a) for a in args]) 

566 if isinstance(kwargs, dict): 

567 kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)] 

568 else: 

569 kwarg_items = kwargs 

570 kw_text = ', '.join([f'{k}={_repr(v)}' for k, v in kwarg_items]) 

571 

572 all_args_text = a_text 

573 if all_args_text and kw_text: 

574 all_args_text += ', ' 

575 all_args_text += kw_text 

576 

577 return f'{name}({all_args_text})' 

578 

579 

580class Path: 

581 """Path objects specify explicit paths when the default 

582 ``'a.b.c'``-style general access syntax won't work or isn't 

583 desirable. Use this to wrap ints, datetimes, and other valid 

584 keys, as well as strings with dots that shouldn't be expanded. 

585 

586 >>> target = {'a': {'b': 'c', 'd.e': 'f', 2: 3}} 

587 >>> glom(target, Path('a', 2)) 

588 3 

589 >>> glom(target, Path('a', 'd.e')) 

590 'f' 

591 

592 Paths can be used to join together other Path objects, as 

593 well as :data:`~glom.T` objects: 

594 

595 >>> Path(T['a'], T['b']) 

596 T['a']['b'] 

597 >>> Path(Path('a', 'b'), Path('c', 'd')) 

598 Path('a', 'b', 'c', 'd') 

599 

600 Paths also support indexing and slicing, with each access 

601 returning a new Path object: 

602 

603 >>> path = Path('a', 'b', 1, 2) 

604 >>> path[0] 

605 Path('a') 

606 >>> path[-2:] 

607 Path(1, 2) 

608 

609 To build a Path object from a string, use :meth:`Path.from_text()`.  

610 This is the default behavior when the top-level :func:`~glom.glom`  

611 function gets a string spec. 

612 """ 

613 def __init__(self, *path_parts): 

614 if not path_parts: 

615 self.path_t = T 

616 return 

617 if isinstance(path_parts[0], TType): 

618 path_t = path_parts[0] 

619 offset = 1 

620 else: 

621 path_t = T 

622 offset = 0 

623 for part in path_parts[offset:]: 

624 if isinstance(part, Path): 

625 part = part.path_t 

626 if isinstance(part, TType): 

627 sub_parts = part.__ops__ 

628 if sub_parts[0] is not T: 

629 raise ValueError('path segment must be path from T, not %r' 

630 % sub_parts[0]) 

631 i = 1 

632 while i < len(sub_parts): 

633 path_t = _t_child(path_t, sub_parts[i], sub_parts[i + 1]) 

634 i += 2 

635 else: 

636 path_t = _t_child(path_t, 'P', part) 

637 self.path_t = path_t 

638 

639 _CACHE = {True: {}, False: {}} 

640 _MAX_CACHE = 10000 

641 _STAR_WARNED = False 

642 

643 @classmethod 

644 def from_text(cls, text): 

645 """Make a Path from .-delimited text: 

646 

647 >>> Path.from_text('a.b.c') 

648 Path('a', 'b', 'c') 

649 

650 This is the default behavior when :func:`~glom.glom` gets a string spec. 

651 """ 

652 def create(): 

653 segs = text.split('.') 

654 if PATH_STAR: 

655 segs = [ 

656 _T_STAR if seg == '*' else 

657 _T_STARSTAR if seg == '**' else seg 

658 for seg in segs] 

659 elif not cls._STAR_WARNED: 

660 if '*' in segs or '**' in segs: 

661 warnings.warn( 

662 "'*' and '**' have changed behavior in glom version 23.1." 

663 " Recommend switch to T['*'] or T['**'].") 

664 cls._STAR_WARNED = True 

665 return cls(*segs) 

666 

667 cache = cls._CACHE[PATH_STAR] # remove this when PATH_STAR is default 

668 if text not in cache: 

669 if len(cache) > cls._MAX_CACHE: 

670 return create() 

671 cache[text] = create() 

672 return cache[text] 

673 

674 def glomit(self, target, scope): 

675 # The entrypoint for the Path extension 

676 return _t_eval(target, self.path_t, scope) 

677 

678 def __len__(self): 

679 return (len(self.path_t.__ops__) - 1) // 2 

680 

681 def __eq__(self, other): 

682 if type(other) is Path: 

683 return self.path_t.__ops__ == other.path_t.__ops__ 

684 elif type(other) is TType: 

685 return self.path_t.__ops__ == other.__ops__ 

686 return False 

687 

688 def __ne__(self, other): 

689 return not self == other 

690 

691 def values(self): 

692 """ 

693 Returns a tuple of values referenced in this path. 

694 

695 >>> Path(T.a.b, 'c', T['d']).values() 

696 ('a', 'b', 'c', 'd') 

697 """ 

698 cur_t_path = self.path_t.__ops__ 

699 return cur_t_path[2::2] 

700 

701 def items(self): 

702 """ 

703 Returns a tuple of (operation, value) pairs. 

704 

705 >>> Path(T.a.b, 'c', T['d']).items() 

706 (('.', 'a'), ('.', 'b'), ('P', 'c'), ('[', 'd')) 

707 

708 """ 

709 cur_t_path = self.path_t.__ops__ 

710 return tuple(zip(cur_t_path[1::2], cur_t_path[2::2])) 

711 

712 def startswith(self, other): 

713 if isinstance(other, basestring): 

714 other = Path(other) 

715 if isinstance(other, Path): 

716 other = other.path_t 

717 if not isinstance(other, TType): 

718 raise TypeError('can only check if Path starts with string, Path or T') 

719 o_path = other.__ops__ 

720 return self.path_t.__ops__[:len(o_path)] == o_path 

721 

722 def from_t(self): 

723 '''return the same path but starting from T''' 

724 t_path = self.path_t.__ops__ 

725 if t_path[0] is S: 

726 new_t = TType() 

727 new_t.__ops__ = (T,) + t_path[1:] 

728 return Path(new_t) 

729 return self 

730 

731 def __getitem__(self, i): 

732 cur_t_path = self.path_t.__ops__ 

733 try: 

734 step = i.step 

735 start = i.start if i.start is not None else 0 

736 stop = i.stop 

737 

738 start = (start * 2) + 1 if start >= 0 else (start * 2) + len(cur_t_path) 

739 if stop is not None: 

740 stop = (stop * 2) + 1 if stop >= 0 else (stop * 2) + len(cur_t_path) 

741 except AttributeError: 

742 step = 1 

743 start = (i * 2) + 1 if i >= 0 else (i * 2) + len(cur_t_path) 

744 if start < 0 or start >= len(cur_t_path): 

745 raise IndexError('Path index %d out of range for Path of length %d' % (i, (len(cur_t_path) - 1) // 2)) 

746 stop = ((i + 1) * 2) + 1 if i >= 0 else ((i + 1) * 2) + len(cur_t_path) 

747 

748 new_t = TType() 

749 new_path = cur_t_path[start:stop] 

750 if step is not None and step != 1: 

751 new_path = tuple(zip(new_path[::2], new_path[1::2]))[::step] 

752 new_path = sum(new_path, ()) 

753 new_t.__ops__ = (cur_t_path[0],) + new_path 

754 return Path(new_t) 

755 

756 def __repr__(self): 

757 return _format_path(self.path_t.__ops__[1:]) 

758 

759 

760def _format_path(t_path): 

761 path_parts, cur_t_path = [], [] 

762 i = 0 

763 while i < len(t_path): 

764 op, arg = t_path[i], t_path[i + 1] 

765 i += 2 

766 if op == 'P': 

767 if cur_t_path: 

768 path_parts.append(cur_t_path) 

769 cur_t_path = [] 

770 path_parts.append(arg) 

771 else: 

772 cur_t_path.append(op) 

773 cur_t_path.append(arg) 

774 if path_parts and cur_t_path: 

775 path_parts.append(cur_t_path) 

776 

777 if path_parts or not cur_t_path: 

778 return 'Path(%s)' % ', '.join([_format_t(part) 

779 if type(part) is list else repr(part) 

780 for part in path_parts]) 

781 return _format_t(cur_t_path) 

782 

783 

784class Spec: 

785 """Spec objects serve three purposes, here they are, roughly ordered 

786 by utility: 

787 

788 1. As a form of compiled or "curried" glom call, similar to 

789 Python's built-in :func:`re.compile`. 

790 2. A marker as an object as representing a spec rather than a 

791 literal value in certain cases where that might be ambiguous. 

792 3. A way to update the scope within another Spec. 

793 

794 In the second usage, Spec objects are the complement to 

795 :class:`~glom.Val`, wrapping a value and marking that it 

796 should be interpreted as a glom spec, rather than a literal value. 

797 This is useful in places where it would be interpreted as a value 

798 by default. (Such as T[key], Call(func) where key and func are 

799 assumed to be literal values and not specs.) 

800 

801 Args: 

802 spec: The glom spec. 

803 scope (dict): additional values to add to the scope when 

804 evaluating this Spec 

805 

806 """ 

807 def __init__(self, spec, scope=None): 

808 self.spec = spec 

809 self.scope = scope or {} 

810 

811 def glom(self, target, **kw): 

812 scope = dict(self.scope) 

813 scope.update(kw.get('scope', {})) 

814 kw['scope'] = ChainMap(scope) 

815 glom_ = scope.get(glom, glom) 

816 return glom_(target, self.spec, **kw) 

817 

818 def glomit(self, target, scope): 

819 scope.update(self.scope) 

820 return scope[glom](target, self.spec, scope) 

821 

822 def __repr__(self): 

823 cn = self.__class__.__name__ 

824 if self.scope: 

825 return f'{cn}({bbrepr(self.spec)}, scope={self.scope!r})' 

826 return f'{cn}({bbrepr(self.spec)})' 

827 

828 

829class Coalesce: 

830 """Coalesce objects specify fallback behavior for a list of 

831 subspecs. 

832 

833 Subspecs are passed as positional arguments, and keyword arguments 

834 control defaults. Each subspec is evaluated in turn, and if none 

835 match, a :exc:`CoalesceError` is raised, or a default is returned, 

836 depending on the options used. 

837 

838 .. note:: 

839 

840 This operation may seem very familar if you have experience with 

841 `SQL`_ or even `C# and others`_. 

842 

843 

844 In practice, this fallback behavior's simplicity is only surpassed 

845 by its utility: 

846 

847 >>> target = {'c': 'd'} 

848 >>> glom(target, Coalesce('a', 'b', 'c')) 

849 'd' 

850 

851 glom tries to get ``'a'`` from ``target``, but gets a 

852 KeyError. Rather than raise a :exc:`~glom.PathAccessError` as usual, 

853 glom *coalesces* into the next subspec, ``'b'``. The process 

854 repeats until it gets to ``'c'``, which returns our value, 

855 ``'d'``. If our value weren't present, we'd see: 

856 

857 >>> target = {} 

858 >>> glom(target, Coalesce('a', 'b')) 

859 Traceback (most recent call last): 

860 ... 

861 CoalesceError: no valid values found. Tried ('a', 'b') and got (PathAccessError, PathAccessError) ... 

862 

863 Same process, but because ``target`` is empty, we get a 

864 :exc:`CoalesceError`. 

865 

866 .. note:: 

867 

868 Coalesce is a *branching* specifier type, so as of v20.7.0, its 

869 exception messages feature an error tree. See 

870 :ref:`branched-exceptions` for details on how to interpret these 

871 exceptions. 

872 

873 

874 If we want to avoid an exception, and we know which value we want 

875 by default, we can set *default*: 

876 

877 >>> target = {} 

878 >>> glom(target, Coalesce('a', 'b', 'c'), default='d-fault') 

879 'd-fault' 

880 

881 ``'a'``, ``'b'``, and ``'c'`` weren't present so we got ``'d-fault'``. 

882 

883 Args: 

884 

885 subspecs: One or more glommable subspecs 

886 default: A value to return if no subspec results in a valid value 

887 default_factory: A callable whose result will be returned as a default 

888 skip: A value, tuple of values, or predicate function 

889 representing values to ignore 

890 skip_exc: An exception or tuple of exception types to catch and 

891 move on to the next subspec. Defaults to :exc:`GlomError`, the 

892 parent type of all glom runtime exceptions. 

893 

894 If all subspecs produce skipped values or exceptions, a 

895 :exc:`CoalesceError` will be raised. For more examples, check out 

896 the :doc:`tutorial`, which makes extensive use of Coalesce. 

897 

898 .. _SQL: https://en.wikipedia.org/w/index.php?title=Null_(SQL)&oldid=833093792#COALESCE 

899 .. _C# and others: https://en.wikipedia.org/w/index.php?title=Null_coalescing_operator&oldid=839493322#C# 

900 

901 """ 

902 def __init__(self, *subspecs, **kwargs): 

903 self.subspecs = subspecs 

904 self._orig_kwargs = dict(kwargs) 

905 self.default = kwargs.pop('default', _MISSING) 

906 self.default_factory = kwargs.pop('default_factory', _MISSING) 

907 if self.default and self.default_factory: 

908 raise ValueError('expected one of "default" or "default_factory", not both') 

909 self.skip = kwargs.pop('skip', _MISSING) 

910 if self.skip is _MISSING: 

911 self.skip_func = lambda v: False 

912 elif callable(self.skip): 

913 self.skip_func = self.skip 

914 elif isinstance(self.skip, tuple): 

915 self.skip_func = lambda v: v in self.skip 

916 else: 

917 self.skip_func = lambda v: v == self.skip 

918 self.skip_exc = kwargs.pop('skip_exc', GlomError) 

919 if kwargs: 

920 raise TypeError(f'unexpected keyword args: {sorted(kwargs.keys())!r}') 

921 

922 def glomit(self, target, scope): 

923 skipped = [] 

924 for subspec in self.subspecs: 

925 try: 

926 ret = scope[glom](target, subspec, scope) 

927 if not self.skip_func(ret): 

928 break 

929 skipped.append(ret) 

930 except self.skip_exc as e: 

931 skipped.append(e) 

932 continue 

933 else: 

934 if self.default is not _MISSING: 

935 ret = arg_val(target, self.default, scope) 

936 elif self.default_factory is not _MISSING: 

937 ret = self.default_factory() 

938 else: 

939 raise CoalesceError(self, skipped, scope[Path]) 

940 return ret 

941 

942 def __repr__(self): 

943 cn = self.__class__.__name__ 

944 return format_invocation(cn, self.subspecs, self._orig_kwargs, repr=bbrepr) 

945 

946 

947class Inspect: 

948 """The :class:`~glom.Inspect` specifier type provides a way to get 

949 visibility into glom's evaluation of a specification, enabling 

950 debugging of those tricky problems that may arise with unexpected 

951 data. 

952 

953 :class:`~glom.Inspect` can be inserted into an existing spec in one of two 

954 ways. First, as a wrapper around the spec in question, or second, 

955 as an argument-less placeholder wherever a spec could be. 

956 

957 :class:`~glom.Inspect` supports several modes, controlled by 

958 keyword arguments. Its default, no-argument mode, simply echos the 

959 state of the glom at the point where it appears: 

960 

961 >>> target = {'a': {'b': {}}} 

962 >>> val = glom(target, Inspect('a.b')) # wrapping a spec 

963 --- 

964 path: ['a.b'] 

965 target: {'a': {'b': {}}} 

966 output: {} 

967 --- 

968 

969 Debugging behavior aside, :class:`~glom.Inspect` has no effect on 

970 values in the target, spec, or result. 

971 

972 Args: 

973 echo (bool): Whether to print the path, target, and output of 

974 each inspected glom. Defaults to True. 

975 recursive (bool): Whether or not the Inspect should be applied 

976 at every level, at or below the spec that it wraps. Defaults 

977 to False. 

978 breakpoint (bool): This flag controls whether a debugging prompt 

979 should appear before evaluating each inspected spec. Can also 

980 take a callable. Defaults to False. 

981 post_mortem (bool): This flag controls whether exceptions 

982 should be caught and interactively debugged with :mod:`pdb` on 

983 inspected specs. 

984 

985 All arguments above are keyword-only to avoid overlap with a 

986 wrapped spec. 

987 

988 .. note:: 

989 

990 Just like ``pdb.set_trace()``, be careful about leaving stray 

991 ``Inspect()`` instances in production glom specs. 

992 

993 """ 

994 def __init__(self, *a, **kw): 

995 self.wrapped = a[0] if a else Path() 

996 self.recursive = kw.pop('recursive', False) 

997 self.echo = kw.pop('echo', True) 

998 breakpoint = kw.pop('breakpoint', False) 

999 if breakpoint is True: 

1000 breakpoint = pdb.set_trace 

1001 if breakpoint and not callable(breakpoint): 

1002 raise TypeError('breakpoint expected bool or callable, not: %r' % breakpoint) 

1003 self.breakpoint = breakpoint 

1004 post_mortem = kw.pop('post_mortem', False) 

1005 if post_mortem is True: 

1006 post_mortem = pdb.post_mortem 

1007 if post_mortem and not callable(post_mortem): 

1008 raise TypeError('post_mortem expected bool or callable, not: %r' % post_mortem) 

1009 self.post_mortem = post_mortem 

1010 

1011 def __repr__(self): 

1012 return '<INSPECT>' 

1013 

1014 def glomit(self, target, scope): 

1015 # stash the real handler under Inspect, 

1016 # and replace the child handler with a trace callback 

1017 scope[Inspect] = scope[glom] 

1018 scope[glom] = self._trace 

1019 return scope[glom](target, self.wrapped, scope) 

1020 

1021 def _trace(self, target, spec, scope): 

1022 if not self.recursive: 

1023 scope[glom] = scope[Inspect] 

1024 if self.echo: 

1025 print('---') 

1026 # TODO: switch from scope[Path] to the Target-Spec format trace above 

1027 # ... but maybe be smart about only printing deltas instead of the whole 

1028 # thing 

1029 print('path: ', scope[Path] + [spec]) 

1030 print('target:', target) 

1031 if self.breakpoint: 

1032 # TODO: real debugger here? 

1033 self.breakpoint() 

1034 try: 

1035 ret = scope[Inspect](target, spec, scope) 

1036 except Exception: 

1037 if self.post_mortem: 

1038 self.post_mortem() 

1039 raise 

1040 if self.echo: 

1041 print('output:', ret) 

1042 print('---') 

1043 return ret 

1044 

1045 

1046class Call: 

1047 """:class:`Call` specifies when a target should be passed to a function, 

1048 *func*. 

1049 

1050 :class:`Call` is similar to :func:`~functools.partial` in that 

1051 it is no more powerful than ``lambda`` or other functions, but 

1052 it is designed to be more readable, with a better ``repr``. 

1053 

1054 Args: 

1055 func (callable): a function or other callable to be called with 

1056 the target 

1057 

1058 :class:`Call` combines well with :attr:`~glom.T` to construct objects. For 

1059 instance, to generate a dict and then pass it to a constructor: 

1060 

1061 >>> class ExampleClass(object): 

1062 ... def __init__(self, attr): 

1063 ... self.attr = attr 

1064 ... 

1065 >>> target = {'attr': 3.14} 

1066 >>> glom(target, Call(ExampleClass, kwargs=T)).attr 

1067 3.14 

1068 

1069 This does the same as ``glom(target, lambda target: 

1070 ExampleClass(**target))``, but it's easy to see which one reads 

1071 better. 

1072 

1073 .. note:: 

1074 

1075 ``Call`` is mostly for functions. Use a :attr:`~glom.T` object 

1076 if you need to call a method. 

1077 

1078 .. warning:: 

1079 

1080 :class:`Call` has a successor with a fuller-featured API, new 

1081 in 19.10.0: the :class:`Invoke` specifier type. 

1082 """ 

1083 def __init__(self, func=None, args=None, kwargs=None): 

1084 if func is None: 

1085 func = T 

1086 if not (callable(func) or isinstance(func, (Spec, TType))): 

1087 raise TypeError('expected func to be a callable or T' 

1088 ' expression, not: %r' % (func,)) 

1089 if args is None: 

1090 args = () 

1091 if kwargs is None: 

1092 kwargs = {} 

1093 self.func, self.args, self.kwargs = func, args, kwargs 

1094 

1095 def glomit(self, target, scope): 

1096 'run against the current target' 

1097 r = lambda spec: arg_val(target, spec, scope) 

1098 return r(self.func)(*r(self.args), **r(self.kwargs)) 

1099 

1100 def __repr__(self): 

1101 cn = self.__class__.__name__ 

1102 return f'{cn}({bbrepr(self.func)}, args={self.args!r}, kwargs={self.kwargs!r})' 

1103 

1104 

1105def _is_spec(obj, strict=False): 

1106 # a little util for codifying the spec type checking in glom 

1107 if isinstance(obj, TType): 

1108 return True 

1109 if strict: 

1110 return type(obj) is Spec 

1111 

1112 return _has_callable_glomit(obj) # pragma: no cover 

1113 

1114 

1115class Invoke: 

1116 """Specifier type designed for easy invocation of callables from glom. 

1117 

1118 Args: 

1119 func (callable): A function or other callable object. 

1120 

1121 ``Invoke`` is similar to :func:`functools.partial`, but with the 

1122 ability to set up a "templated" call which interleaves constants and 

1123 glom specs. 

1124 

1125 For example, the following creates a spec which can be used to 

1126 check if targets are integers: 

1127 

1128 >>> is_int = Invoke(isinstance).specs(T).constants(int) 

1129 >>> glom(5, is_int) 

1130 True 

1131 

1132 And this composes like any other glom spec: 

1133 

1134 >>> target = [7, object(), 9] 

1135 >>> glom(target, [is_int]) 

1136 [True, False, True] 

1137 

1138 Another example, mixing positional and keyword arguments: 

1139 

1140 >>> spec = Invoke(sorted).specs(T).constants(key=int, reverse=True) 

1141 >>> target = ['10', '5', '20', '1'] 

1142 >>> glom(target, spec) 

1143 ['20', '10', '5', '1'] 

1144 

1145 Invoke also helps with evaluating zero-argument functions: 

1146 

1147 >>> glom(target={}, spec=Invoke(int)) 

1148 0 

1149 

1150 (A trivial example, but from timestamps to UUIDs, zero-arg calls do come up!) 

1151 

1152 .. note:: 

1153 

1154 ``Invoke`` is mostly for functions, object construction, and callable 

1155 objects. For calling methods, consider the :attr:`~glom.T` object. 

1156 

1157 """ 

1158 def __init__(self, func): 

1159 if not callable(func) and not _is_spec(func, strict=True): 

1160 raise TypeError('expected func to be a callable or Spec instance,' 

1161 ' not: %r' % (func,)) 

1162 self.func = func 

1163 self._args = () 

1164 # a registry of every known kwarg to its freshest value as set 

1165 # by the methods below. the **kw dict is used as a unique marker. 

1166 self._cur_kwargs = {} 

1167 

1168 @classmethod 

1169 def specfunc(cls, spec): 

1170 """Creates an :class:`Invoke` instance where the function is 

1171 indicated by a spec. 

1172 

1173 >>> spec = Invoke.specfunc('func').constants(5) 

1174 >>> glom({'func': range}, (spec, list)) 

1175 [0, 1, 2, 3, 4] 

1176 

1177 """ 

1178 return cls(Spec(spec)) 

1179 

1180 def constants(self, *a, **kw): 

1181 """Returns a new :class:`Invoke` spec, with the provided positional 

1182 and keyword argument values stored for passing to the 

1183 underlying function. 

1184 

1185 >>> spec = Invoke(T).constants(5) 

1186 >>> glom(range, (spec, list)) 

1187 [0, 1, 2, 3, 4] 

1188 

1189 Subsequent positional arguments are appended: 

1190 

1191 >>> spec = Invoke(T).constants(2).constants(10, 2) 

1192 >>> glom(range, (spec, list)) 

1193 [2, 4, 6, 8] 

1194 

1195 Keyword arguments also work as one might expect: 

1196 

1197 >>> round_2 = Invoke(round).constants(ndigits=2).specs(T) 

1198 >>> glom(3.14159, round_2) 

1199 3.14 

1200 

1201 :meth:`~Invoke.constants()` and other :class:`Invoke` 

1202 methods may be called multiple times, just remember that every 

1203 call returns a new spec. 

1204 """ 

1205 ret = self.__class__(self.func) 

1206 ret._args = self._args + ('C', a, kw) 

1207 ret._cur_kwargs = dict(self._cur_kwargs) 

1208 ret._cur_kwargs.update({k: kw for k, _ in kw.items()}) 

1209 return ret 

1210 

1211 def specs(self, *a, **kw): 

1212 """Returns a new :class:`Invoke` spec, with the provided positional 

1213 and keyword arguments stored to be interpreted as specs, with 

1214 the results passed to the underlying function. 

1215 

1216 >>> spec = Invoke(range).specs('value') 

1217 >>> glom({'value': 5}, (spec, list)) 

1218 [0, 1, 2, 3, 4] 

1219 

1220 Subsequent positional arguments are appended: 

1221 

1222 >>> spec = Invoke(range).specs('start').specs('end', 'step') 

1223 >>> target = {'start': 2, 'end': 10, 'step': 2} 

1224 >>> glom(target, (spec, list)) 

1225 [2, 4, 6, 8] 

1226 

1227 Keyword arguments also work as one might expect: 

1228 

1229 >>> multiply = lambda x, y: x * y 

1230 >>> times_3 = Invoke(multiply).constants(y=3).specs(x='value') 

1231 >>> glom({'value': 5}, times_3) 

1232 15 

1233 

1234 :meth:`~Invoke.specs()` and other :class:`Invoke` 

1235 methods may be called multiple times, just remember that every 

1236 call returns a new spec. 

1237 

1238 """ 

1239 ret = self.__class__(self.func) 

1240 ret._args = self._args + ('S', a, kw) 

1241 ret._cur_kwargs = dict(self._cur_kwargs) 

1242 ret._cur_kwargs.update({k: kw for k, _ in kw.items()}) 

1243 return ret 

1244 

1245 def star(self, args=None, kwargs=None): 

1246 """Returns a new :class:`Invoke` spec, with *args* and/or *kwargs* 

1247 specs set to be "starred" or "star-starred" (respectively) 

1248 

1249 >>> spec = Invoke(zip).star(args='lists') 

1250 >>> target = {'lists': [[1, 2], [3, 4], [5, 6]]} 

1251 >>> list(glom(target, spec)) 

1252 [(1, 3, 5), (2, 4, 6)] 

1253 

1254 Args: 

1255 args (spec): A spec to be evaluated and "starred" into the 

1256 underlying function. 

1257 kwargs (spec): A spec to be evaluated and "star-starred" into 

1258 the underlying function. 

1259 

1260 One or both of the above arguments should be set. 

1261 

1262 The :meth:`~Invoke.star()`, like other :class:`Invoke` 

1263 methods, may be called multiple times. The *args* and *kwargs* 

1264 will be stacked in the order in which they are provided. 

1265 """ 

1266 if args is None and kwargs is None: 

1267 raise TypeError('expected one or both of args/kwargs to be passed') 

1268 ret = self.__class__(self.func) 

1269 ret._args = self._args + ('*', args, kwargs) 

1270 ret._cur_kwargs = dict(self._cur_kwargs) 

1271 return ret 

1272 

1273 def __repr__(self): 

1274 base_fname = self.__class__.__name__ 

1275 fname_map = {'C': 'constants', 'S': 'specs', '*': 'star'} 

1276 if type(self.func) is Spec: 

1277 base_fname += '.specfunc' 

1278 args = (self.func.spec,) 

1279 else: 

1280 args = (self.func,) 

1281 chunks = [format_invocation(base_fname, args, repr=bbrepr)] 

1282 

1283 for i in range(len(self._args) // 3): 

1284 op, args, _kwargs = self._args[i * 3: i * 3 + 3] 

1285 fname = fname_map[op] 

1286 if op in ('C', 'S'): 

1287 kwargs = [(k, v) for k, v in _kwargs.items() 

1288 if self._cur_kwargs[k] is _kwargs] 

1289 else: 

1290 kwargs = {} 

1291 if args: 

1292 kwargs['args'] = args 

1293 if _kwargs: 

1294 kwargs['kwargs'] = _kwargs 

1295 args = () 

1296 

1297 chunks.append('.' + format_invocation(fname, args, kwargs, repr=bbrepr)) 

1298 

1299 return ''.join(chunks) 

1300 

1301 def glomit(self, target, scope): 

1302 all_args = [] 

1303 all_kwargs = {} 

1304 

1305 recurse = lambda spec: scope[glom](target, spec, scope) 

1306 func = recurse(self.func) if _is_spec(self.func, strict=True) else self.func 

1307 

1308 for i in range(len(self._args) // 3): 

1309 op, args, kwargs = self._args[i * 3: i * 3 + 3] 

1310 if op == 'C': 

1311 all_args.extend(args) 

1312 all_kwargs.update({k: v for k, v in kwargs.items() 

1313 if self._cur_kwargs[k] is kwargs}) 

1314 elif op == 'S': 

1315 all_args.extend([recurse(arg) for arg in args]) 

1316 all_kwargs.update({k: recurse(v) for k, v in kwargs.items() 

1317 if self._cur_kwargs[k] is kwargs}) 

1318 elif op == '*': 

1319 if args is not None: 

1320 all_args.extend(recurse(args)) 

1321 if kwargs is not None: 

1322 all_kwargs.update(recurse(kwargs)) 

1323 

1324 return func(*all_args, **all_kwargs) 

1325 

1326 

1327class Ref: 

1328 """Name a part of a spec and refer to it elsewhere in the same spec, 

1329 useful for trees and other self-similar data structures. 

1330 

1331 Args: 

1332 name (str): The name of the spec to reference. 

1333 subspec: Pass a spec to name it *name*, or leave unset to refer 

1334 to an already-named spec. 

1335 """ 

1336 def __init__(self, name, subspec=_MISSING): 

1337 self.name, self.subspec = name, subspec 

1338 

1339 def glomit(self, target, scope): 

1340 subspec = self.subspec 

1341 scope_key = (Ref, self.name) 

1342 if subspec is _MISSING: 

1343 subspec = scope[scope_key] 

1344 else: 

1345 scope[scope_key] = subspec 

1346 return scope[glom](target, subspec, scope) 

1347 

1348 def __repr__(self): 

1349 if self.subspec is _MISSING: 

1350 args = bbrepr(self.name) 

1351 else: 

1352 args = bbrepr((self.name, self.subspec))[1:-1] 

1353 return "Ref(" + args + ")" 

1354 

1355 

1356class TType: 

1357 """``T``, short for "target". A singleton object that enables 

1358 object-oriented expression of a glom specification. 

1359 

1360 .. note:: 

1361 

1362 ``T`` is a singleton, and does not need to be constructed. 

1363 

1364 Basically, think of ``T`` as your data's stunt double. Everything 

1365 that you do to ``T`` will be recorded and executed during the 

1366 :func:`glom` call. Take this example: 

1367 

1368 >>> spec = T['a']['b']['c'] 

1369 >>> target = {'a': {'b': {'c': 'd'}}} 

1370 >>> glom(target, spec) 

1371 'd' 

1372 

1373 So far, we've relied on the ``'a.b.c'``-style shorthand for 

1374 access, or used the :class:`~glom.Path` objects, but if you want 

1375 to explicitly do attribute and key lookups, look no further than 

1376 ``T``. 

1377 

1378 But T doesn't stop with unambiguous access. You can also call 

1379 methods and perform almost any action you would with a normal 

1380 object: 

1381 

1382 >>> spec = ('a', (T['b'].items(), list)) # reviewed below 

1383 >>> glom(target, spec) 

1384 [('c', 'd')] 

1385 

1386 A ``T`` object can go anywhere in the spec. As seen in the example 

1387 above, we access ``'a'``, use a ``T`` to get ``'b'`` and iterate 

1388 over its ``items``, turning them into a ``list``. 

1389 

1390 You can even use ``T`` with :class:`~glom.Call` to construct objects: 

1391 

1392 >>> class ExampleClass(object): 

1393 ... def __init__(self, attr): 

1394 ... self.attr = attr 

1395 ... 

1396 >>> target = {'attr': 3.14} 

1397 >>> glom(target, Call(ExampleClass, kwargs=T)).attr 

1398 3.14 

1399 

1400 On a further note, while ``lambda`` works great in glom specs, and 

1401 can be very handy at times, ``T`` and :class:`~glom.Call` 

1402 eliminate the need for the vast majority of ``lambda`` usage with 

1403 glom. 

1404 

1405 Unlike ``lambda`` and other functions, ``T`` roundtrips 

1406 beautifully and transparently: 

1407 

1408 >>> T['a'].b['c']('success') 

1409 T['a'].b['c']('success') 

1410 

1411 ``T``-related access errors raise a :exc:`~glom.PathAccessError` 

1412 during the :func:`~glom.glom` call. 

1413 

1414 .. note:: 

1415 

1416 While ``T`` is clearly useful, powerful, and here to stay, its 

1417 semantics are still being refined. Currently, operations beyond 

1418 method calls and attribute/item access are considered 

1419 experimental and should not be relied upon. 

1420 

1421 .. note:: 

1422 

1423 ``T`` attributes starting with __ are reserved to avoid 

1424 colliding with many built-in Python behaviors, current and 

1425 future. The ``T.__()`` method is available for cases where 

1426 they are needed. For example, ``T.__('class__')`` is 

1427 equivalent to accessing the ``__class__`` attribute. 

1428 

1429 """ 

1430 __slots__ = ('__ops__',) 

1431 

1432 def __getattr__(self, name): 

1433 if name.startswith('__'): 

1434 raise AttributeError('T instances reserve dunder attributes.' 

1435 ' To access the "{name}" attribute, use' 

1436 ' T.__("{d_name}")'.format(name=name, d_name=name[2:])) 

1437 return _t_child(self, '.', name) 

1438 

1439 def __getitem__(self, item): 

1440 return _t_child(self, '[', item) 

1441 

1442 def __call__(self, *args, **kwargs): 

1443 if self is S: 

1444 if args: 

1445 raise TypeError(f'S() takes no positional arguments, got: {args!r}') 

1446 if not kwargs: 

1447 raise TypeError('S() expected at least one kwarg, got none') 

1448 # TODO: typecheck kwarg vals? 

1449 return _t_child(self, '(', (args, kwargs)) 

1450 

1451 def __star__(self): 

1452 return _t_child(self, 'x', None) 

1453 

1454 def __starstar__(self): 

1455 return _t_child(self, 'X', None) 

1456 

1457 def __stars__(self): 

1458 """how many times the result will be wrapped in extra lists""" 

1459 t_ops = self.__ops__[1::2] 

1460 return t_ops.count('x') + t_ops.count('X') 

1461 

1462 def __add__(self, arg): 

1463 return _t_child(self, '+', arg) 

1464 

1465 def __sub__(self, arg): 

1466 return _t_child(self, '-', arg) 

1467 

1468 def __mul__(self, arg): 

1469 return _t_child(self, '*', arg) 

1470 

1471 def __floordiv__(self, arg): 

1472 return _t_child(self, '#', arg) 

1473 

1474 def __truediv__(self, arg): 

1475 return _t_child(self, '/', arg) 

1476 

1477 __div__ = __truediv__ 

1478 

1479 def __mod__(self, arg): 

1480 return _t_child(self, '%', arg) 

1481 

1482 def __pow__(self, arg): 

1483 return _t_child(self, ':', arg) 

1484 

1485 def __and__(self, arg): 

1486 return _t_child(self, '&', arg) 

1487 

1488 def __or__(self, arg): 

1489 return _t_child(self, '|', arg) 

1490 

1491 def __xor__(self, arg): 

1492 return _t_child(self, '^', arg) 

1493 

1494 def __invert__(self): 

1495 return _t_child(self, '~', None) 

1496 

1497 def __neg__(self): 

1498 return _t_child(self, '_', None) 

1499 

1500 def __(self, name): 

1501 return _t_child(self, '.', '__' + name) 

1502 

1503 def __repr__(self): 

1504 t_path = self.__ops__ 

1505 return _format_t(t_path[1:], t_path[0]) 

1506 

1507 def __getstate__(self): 

1508 t_path = self.__ops__ 

1509 return tuple(({T: 'T', S: 'S', A: 'A'}[t_path[0]],) + t_path[1:]) 

1510 

1511 def __setstate__(self, state): 

1512 self.__ops__ = ({'T': T, 'S': S, 'A': A}[state[0]],) + state[1:] 

1513 

1514 

1515def _t_child(parent, operation, arg): 

1516 base = parent.__ops__ 

1517 if base[0] is A and operation not in ('.', '[', 'P'): 

1518 # whitelist rather than blacklist assignment friendly operations 

1519 # TODO: error type? 

1520 raise BadSpec("operation not allowed on A assignment path") 

1521 t = TType() 

1522 t.__ops__ = base + (operation, arg) 

1523 return t 

1524 

1525 

1526def _s_first_magic(scope, key, _t): 

1527 """ 

1528 enable S.a to do S['a'] or S['a'].val as a special 

1529 case for accessing user defined string variables 

1530 """ 

1531 err = None 

1532 try: 

1533 cur = scope[key] 

1534 except KeyError as e: 

1535 err = PathAccessError(e, Path(_t), 0) # always only one level depth, hence 0 

1536 if err: 

1537 raise err 

1538 return cur 

1539 

1540 

1541def _t_eval(target, _t, scope): 

1542 t_path = _t.__ops__ 

1543 i = 1 

1544 fetch_till = len(t_path) 

1545 root = t_path[0] 

1546 if root is T: 

1547 cur = target 

1548 elif root is S or root is A: 

1549 # A is basically the same as S, but last step is assign 

1550 if root is A: 

1551 fetch_till -= 2 

1552 if fetch_till < 1: 

1553 raise BadSpec('cannot assign without destination') 

1554 cur = scope 

1555 if fetch_till > 1 and t_path[1] in ('.', 'P'): 

1556 cur = _s_first_magic(cur, t_path[2], _t) 

1557 i += 2 

1558 elif root is S and fetch_till > 1 and t_path[1] == '(': 

1559 # S(var='spec') style assignment 

1560 _, kwargs = t_path[2] 

1561 scope.update({ 

1562 k: arg_val(target, v, scope) for k, v in kwargs.items()}) 

1563 return target 

1564 

1565 else: 

1566 raise ValueError('TType instance with invalid root') # pragma: no cover 

1567 pae = None 

1568 while i < fetch_till: 

1569 op, arg = t_path[i], t_path[i + 1] 

1570 arg = arg_val(target, arg, scope) 

1571 if op == '.': 

1572 try: 

1573 cur = getattr(cur, arg) 

1574 except AttributeError as e: 

1575 pae = PathAccessError(e, Path(_t), i // 2) 

1576 elif op == '[': 

1577 try: 

1578 cur = cur[arg] 

1579 except (KeyError, IndexError, TypeError) as e: 

1580 pae = PathAccessError(e, Path(_t), i // 2) 

1581 elif op == 'P': 

1582 # Path type stuff (fuzzy match) 

1583 get = scope[TargetRegistry].get_handler('get', cur, path=t_path[2:i+2:2]) 

1584 try: 

1585 cur = get(cur, arg) 

1586 except Exception as e: 

1587 pae = PathAccessError(e, Path(_t), i // 2) 

1588 elif op in 'xX': 

1589 nxt = [] 

1590 get_handler = scope[TargetRegistry].get_handler 

1591 if op == 'x': # increases arity of cur each time through 

1592 # TODO: so many try/except -- could scope[TargetRegistry] stuff be cached on type? 

1593 _extend_children(nxt, cur, get_handler) 

1594 elif op == 'X': 

1595 sofar = set() 

1596 _extend_children(nxt, cur, get_handler) 

1597 for item in nxt: 

1598 if id(item) not in sofar: 

1599 sofar.add(id(item)) 

1600 _extend_children(nxt, item, get_handler) 

1601 nxt.insert(0, cur) 

1602 # handle the rest of the t_path in recursive calls 

1603 cur = [] 

1604 todo = TType() 

1605 todo.__ops__ = (root,) + t_path[i+2:] 

1606 for child in nxt: 

1607 try: 

1608 cur.append(_t_eval(child, todo, scope)) 

1609 except PathAccessError: 

1610 pass 

1611 break # we handled the rest in recursive call, break loop 

1612 elif op == '(': 

1613 args, kwargs = arg 

1614 scope[Path] += t_path[2:i+2:2] 

1615 cur = scope[glom]( 

1616 target, Call(cur, args, kwargs), scope) 

1617 # call with target rather than cur, 

1618 # because it is probably more intuitive 

1619 # if args to the call "reset" their path 

1620 # e.g. "T.a" should mean the same thing 

1621 # in both of these specs: T.a and T.b(T.a) 

1622 else: # arithmetic operators 

1623 try: 

1624 if op == '+': 

1625 cur = cur + arg 

1626 elif op == '-': 

1627 cur = cur - arg 

1628 elif op == '*': 

1629 cur = cur * arg 

1630 #elif op == '#': 

1631 # cur = cur // arg # TODO: python 2 friendly approach? 

1632 elif op == '/': 

1633 cur = cur / arg 

1634 elif op == '%': 

1635 cur = cur % arg 

1636 elif op == ':': 

1637 cur = cur ** arg 

1638 elif op == '&': 

1639 cur = cur & arg 

1640 elif op == '|': 

1641 cur = cur | arg 

1642 elif op == '^': 

1643 cur = cur ^ arg 

1644 elif op == '~': 

1645 cur = ~cur 

1646 elif op == '_': 

1647 cur = -cur 

1648 except (TypeError, ZeroDivisionError) as e: 

1649 pae = PathAccessError(e, Path(_t), i // 2) 

1650 if pae: 

1651 raise pae 

1652 i += 2 

1653 if root is A: 

1654 op, arg = t_path[-2:] 

1655 if cur is scope: 

1656 op = '[' # all assignment on scope is setitem 

1657 _assign_op(dest=cur, op=op, arg=arg, val=target, path=_t, scope=scope) 

1658 return target # A should not change the target 

1659 return cur 

1660 

1661 

1662def _assign_op(dest, op, arg, val, path, scope): 

1663 """helper method for doing the assignment on a T operation""" 

1664 if op == '[': 

1665 dest[arg] = val 

1666 elif op == '.': 

1667 setattr(dest, arg, val) 

1668 elif op == 'P': 

1669 _assign = scope[TargetRegistry].get_handler('assign', dest) 

1670 try: 

1671 _assign(dest, arg, val) 

1672 except Exception as e: 

1673 raise PathAssignError(e, path, arg) 

1674 else: # pragma: no cover 

1675 raise ValueError('unsupported T operation for assignment') 

1676 

1677 

1678def _extend_children(children, item, get_handler): 

1679 try: # dict or obj-like 

1680 keys = get_handler('keys', item) 

1681 get = get_handler('get', item) 

1682 except UnregisteredTarget: 

1683 try: 

1684 iterate = get_handler('iterate', item) 

1685 except UnregisteredTarget: 

1686 pass 

1687 else: 

1688 try: # list-like 

1689 children.extend(iterate(item)) 

1690 except Exception: 

1691 pass 

1692 else: 

1693 try: 

1694 for key in keys(item): 

1695 try: 

1696 children.append(get(item, key)) 

1697 except Exception: 

1698 pass 

1699 except Exception: 

1700 pass 

1701 

1702 

1703T = TType() # target aka Mr. T aka "this" 

1704S = TType() # like T, but means grab stuff from Scope, not Target 

1705A = TType() # like S, but shorthand to assign target to scope 

1706 

1707T.__ops__ = (T,) 

1708S.__ops__ = (S,) 

1709A.__ops__ = (A,) 

1710 

1711_T_STAR = T.__star__() # helper constant for Path.from_text 

1712_T_STARSTAR = T.__starstar__() # helper constant for Path.from_text 

1713 

1714UP = make_sentinel('UP') 

1715ROOT = make_sentinel('ROOT') 

1716 

1717 

1718def _format_slice(x): 

1719 if type(x) is not slice: 

1720 return bbrepr(x) 

1721 fmt = lambda v: "" if v is None else bbrepr(v) 

1722 if x.step is None: 

1723 return fmt(x.start) + ":" + fmt(x.stop) 

1724 return fmt(x.start) + ":" + fmt(x.stop) + ":" + fmt(x.step) 

1725 

1726 

1727def _format_t(path, root=T): 

1728 prepr = [{T: 'T', S: 'S', A: 'A'}[root]] 

1729 i = 0 

1730 while i < len(path): 

1731 op, arg = path[i], path[i + 1] 

1732 if op == '.': 

1733 prepr.append('.' + arg) 

1734 elif op == '[': 

1735 if type(arg) is tuple: 

1736 index = ", ".join([_format_slice(x) for x in arg]) 

1737 else: 

1738 index = _format_slice(arg) 

1739 prepr.append(f"[{index}]") 

1740 elif op == '(': 

1741 args, kwargs = arg 

1742 prepr.append(format_invocation(args=args, kwargs=kwargs, repr=bbrepr)) 

1743 elif op == 'P': 

1744 return _format_path(path) 

1745 elif op == 'x': 

1746 prepr.append(".__star__()") 

1747 elif op == 'X': 

1748 prepr.append(".__starstar__()") 

1749 elif op in ('_', '~'): # unary arithmetic operators 

1750 if any([o in path[:i] for o in '+-/%:&|^~_']): 

1751 prepr = ['('] + prepr + [')'] 

1752 prepr = ['-' if op == '_' else op] + prepr 

1753 else: # binary arithmetic operators 

1754 formatted_arg = bbrepr(arg) 

1755 if type(arg) is TType: 

1756 arg_path = arg.__ops__ 

1757 if any([o in arg_path for o in '+-/%:&|^~_']): 

1758 formatted_arg = '(' + formatted_arg + ')' 

1759 prepr.append(' ' + ('**' if op == ':' else op) + ' ') 

1760 prepr.append(formatted_arg) 

1761 i += 2 

1762 return "".join(prepr) 

1763 

1764 

1765class Val: 

1766 """Val objects are specs which evaluate to the wrapped *value*. 

1767 

1768 >>> target = {'a': {'b': 'c'}} 

1769 >>> spec = {'a': 'a.b', 'readability': Val('counts')} 

1770 >>> pprint(glom(target, spec)) 

1771 {'a': 'c', 'readability': 'counts'} 

1772 

1773 Instead of accessing ``'counts'`` as a key like it did with 

1774 ``'a.b'``, :func:`~glom.glom` just unwrapped the Val and 

1775 included the value. 

1776 

1777 :class:`~glom.Val` takes one argument, the value to be returned. 

1778 

1779 .. note:: 

1780 

1781 :class:`Val` was named ``Literal`` in versions of glom before 

1782 20.7.0. An alias has been preserved for backwards 

1783 compatibility, but reprs have changed. 

1784 

1785 """ 

1786 def __init__(self, value): 

1787 self.value = value 

1788 

1789 def glomit(self, target, scope): 

1790 return self.value 

1791 

1792 def __repr__(self): 

1793 cn = self.__class__.__name__ 

1794 return f'{cn}({bbrepr(self.value)})' 

1795 

1796 

1797Literal = Val # backwards compat for pre-20.7.0 

1798 

1799 

1800class ScopeVars: 

1801 """This is the runtime partner of :class:`Vars` -- this is what 

1802 actually lives in the scope and stores runtime values. 

1803 

1804 While not part of the importable API of glom, it's half expected 

1805 that some folks may write sepcs to populate and export scopes, at 

1806 which point this type makes it easy to access values by attribute 

1807 access or by converting to a dict. 

1808 

1809 """ 

1810 def __init__(self, base, defaults): 

1811 self.__dict__ = dict(base) 

1812 self.__dict__.update(defaults) 

1813 

1814 def __iter__(self): 

1815 return iter(self.__dict__.items()) 

1816 

1817 def __repr__(self): 

1818 return f"{self.__class__.__name__}({bbrepr(self.__dict__)})" 

1819 

1820 

1821class Vars: 

1822 """ 

1823 :class:`Vars` is a helper that can be used with **S** in order to 

1824 store shared mutable state. 

1825 

1826 Takes the same arguments as :class:`dict()`. 

1827 

1828 Arguments here should be thought of the same way as default arguments 

1829 to a function. Each time the spec is evaluated, the same arguments 

1830 will be referenced; so, think carefully about mutable data structures. 

1831 """ 

1832 def __init__(self, base=(), **kw): 

1833 dict(base) # ensure it is a dict-compatible first arg 

1834 self.base = base 

1835 self.defaults = kw 

1836 

1837 def glomit(self, target, spec): 

1838 return ScopeVars(self.base, self.defaults) 

1839 

1840 def __repr__(self): 

1841 ret = format_invocation(self.__class__.__name__, 

1842 args=(self.base,) if self.base else (), 

1843 kwargs=self.defaults, 

1844 repr=bbrepr) 

1845 return ret 

1846 

1847 

1848class Let: 

1849 """ 

1850 Deprecated, kept for backwards compat. Use S(x='y') instead. 

1851 

1852 >>> target = {'data': {'val': 9}} 

1853 >>> spec = (Let(value=T['data']['val']), {'val': S['value']}) 

1854 >>> glom(target, spec) 

1855 {'val': 9} 

1856 

1857 """ 

1858 def __init__(self, **kw): 

1859 if not kw: 

1860 raise TypeError('expected at least one keyword argument') 

1861 self._binding = kw 

1862 

1863 def glomit(self, target, scope): 

1864 scope.update({ 

1865 k: scope[glom](target, v, scope) for k, v in self._binding.items()}) 

1866 return target 

1867 

1868 def __repr__(self): 

1869 cn = self.__class__.__name__ 

1870 return format_invocation(cn, kwargs=self._binding, repr=bbrepr) 

1871 

1872 

1873class Auto: 

1874 """ 

1875 Switch to Auto mode (the default) 

1876 

1877 TODO: this seems like it should be a sub-class of class Spec() -- 

1878 if Spec() could help define the interface for new "modes" or dialects 

1879 that would also help make match mode feel less duct-taped on 

1880 """ 

1881 def __init__(self, spec=None): 

1882 self.spec = spec 

1883 

1884 def glomit(self, target, scope): 

1885 scope[MODE] = AUTO 

1886 return scope[glom](target, self.spec, scope) 

1887 

1888 def __repr__(self): 

1889 cn = self.__class__.__name__ 

1890 rpr = '' if self.spec is None else bbrepr(self.spec) 

1891 return f'{cn}({rpr})' 

1892 

1893 

1894class _AbstractIterable(_AbstractIterableBase): 

1895 __metaclass__ = ABCMeta 

1896 @classmethod 

1897 def __subclasshook__(cls, C): 

1898 if C in (str, bytes): 

1899 return False 

1900 return callable(getattr(C, "__iter__", None)) 

1901 

1902 

1903class _ObjStyleKeysMeta(type): 

1904 def __instancecheck__(cls, C): 

1905 return hasattr(C, "__dict__") and hasattr(C.__dict__, "keys") 

1906 

1907 

1908class _ObjStyleKeys(_ObjStyleKeysMeta('_AbstractKeys', (object,), {})): 

1909 __metaclass__ = _ObjStyleKeysMeta 

1910 

1911 @staticmethod 

1912 def get_keys(obj): 

1913 ret = obj.__dict__.keys() 

1914 return ret 

1915 

1916 

1917def _get_sequence_item(target, index): 

1918 return target[int(index)] 

1919 

1920 

1921# handlers are 3-arg callables, with args (spec, target, scope) 

1922# spec is the first argument for convenience in the case 

1923# that the handler is a method of the spec type 

1924def _handle_dict(target, spec, scope): 

1925 ret = type(spec)() # TODO: works for dict + ordereddict, but sufficient for all? 

1926 for field, subspec in spec.items(): 

1927 val = scope[glom](target, subspec, scope) 

1928 if val is SKIP: 

1929 continue 

1930 if type(field) in (Spec, TType): 

1931 field = scope[glom](target, field, scope) 

1932 ret[field] = val 

1933 return ret 

1934 

1935 

1936def _handle_list(target, spec, scope): 

1937 subspec = spec[0] 

1938 iterate = scope[TargetRegistry].get_handler('iterate', target, path=scope[Path]) 

1939 try: 

1940 iterator = iterate(target) 

1941 except Exception as e: 

1942 raise TypeError('failed to iterate on instance of type %r at %r (got %r)' 

1943 % (target.__class__.__name__, Path(*scope[Path]), e)) 

1944 ret = [] 

1945 base_path = scope[Path] 

1946 for i, t in enumerate(iterator): 

1947 scope[Path] = base_path + [i] 

1948 val = scope[glom](t, subspec, scope) 

1949 if val is SKIP: 

1950 continue 

1951 if val is STOP: 

1952 break 

1953 ret.append(val) 

1954 return ret 

1955 

1956 

1957def _handle_tuple(target, spec, scope): 

1958 res = target 

1959 for subspec in spec: 

1960 scope = chain_child(scope) 

1961 nxt = scope[glom](res, subspec, scope) 

1962 if nxt is SKIP: 

1963 continue 

1964 if nxt is STOP: 

1965 break 

1966 res = nxt 

1967 if not isinstance(subspec, list): 

1968 scope[Path] += [getattr(subspec, '__name__', subspec)] 

1969 return res 

1970 

1971 

1972class Pipe: 

1973 """Evaluate specs one after the other, passing the result of 

1974 the previous evaluation in as the target of the next spec: 

1975 

1976 >>> glom({'a': {'b': -5}}, Pipe('a', 'b', abs)) 

1977 5 

1978 

1979 Same behavior as ``Auto(tuple(steps))``, but useful for explicit 

1980 usage in other modes. 

1981 """ 

1982 def __init__(self, *steps): 

1983 self.steps = steps 

1984 

1985 def glomit(self, target, scope): 

1986 return _handle_tuple(target, self.steps, scope) 

1987 

1988 def __repr__(self): 

1989 return self.__class__.__name__ + bbrepr(self.steps) 

1990 

1991 

1992class TargetRegistry: 

1993 ''' 

1994 responsible for registration of target types for iteration 

1995 and attribute walking 

1996 ''' 

1997 def __init__(self, register_default_types=True): 

1998 self._op_type_map = {} 

1999 self._op_type_tree = {} # see _register_fuzzy_type for details 

2000 self._type_cache = {} 

2001 

2002 self._op_auto_map = OrderedDict() # op name to function that returns handler function 

2003 

2004 self._register_builtin_ops() 

2005 

2006 if register_default_types: 

2007 self._register_default_types() 

2008 return 

2009 

2010 def get_handler(self, op, obj, path=None, raise_exc=True): 

2011 """for an operation and object **instance**, obj, return the 

2012 closest-matching handler function, raising UnregisteredTarget 

2013 if no handler can be found for *obj* (or False if 

2014 raise_exc=False) 

2015 

2016 """ 

2017 ret = False 

2018 obj_type = type(obj) 

2019 cache_key = (obj_type, op) 

2020 if cache_key not in self._type_cache: 

2021 type_map = self.get_type_map(op) 

2022 if type_map: 

2023 try: 

2024 ret = type_map[obj_type] 

2025 except KeyError: 

2026 type_tree = self._op_type_tree.get(op, {}) 

2027 closest = self._get_closest_type(obj, type_tree=type_tree) 

2028 if closest is None: 

2029 ret = False 

2030 else: 

2031 ret = type_map[closest] 

2032 

2033 if ret is False and raise_exc: 

2034 raise UnregisteredTarget(op, obj_type, type_map=type_map, path=path) 

2035 

2036 self._type_cache[cache_key] = ret 

2037 return self._type_cache[cache_key] 

2038 

2039 def get_type_map(self, op): 

2040 try: 

2041 return self._op_type_map[op] 

2042 except KeyError: 

2043 return OrderedDict() 

2044 

2045 def _get_closest_type(self, obj, type_tree): 

2046 default = None 

2047 for cur_type, sub_tree in type_tree.items(): 

2048 if isinstance(obj, cur_type): 

2049 sub_type = self._get_closest_type(obj, type_tree=sub_tree) 

2050 ret = cur_type if sub_type is None else sub_type 

2051 return ret 

2052 return default 

2053 

2054 def _register_default_types(self): 

2055 self.register(object) 

2056 self.register(dict, get=operator.getitem) 

2057 self.register(dict, keys=dict.keys) 

2058 self.register(list, get=_get_sequence_item) 

2059 self.register(tuple, get=_get_sequence_item) 

2060 self.register(OrderedDict, get=operator.getitem) 

2061 self.register(OrderedDict, keys=OrderedDict.keys) 

2062 self.register(_AbstractIterable, iterate=iter) 

2063 self.register(_ObjStyleKeys, keys=_ObjStyleKeys.get_keys) 

2064 

2065 def _register_fuzzy_type(self, op, new_type, _type_tree=None): 

2066 """Build a "type tree", an OrderedDict mapping registered types to 

2067 their subtypes 

2068 

2069 The type tree's invariant is that a key in the mapping is a 

2070 valid parent type of all its children. 

2071 

2072 Order is preserved such that non-overlapping parts of the 

2073 subtree take precedence by which was most recently added. 

2074 """ 

2075 if _type_tree is None: 

2076 try: 

2077 _type_tree = self._op_type_tree[op] 

2078 except KeyError: 

2079 _type_tree = self._op_type_tree[op] = OrderedDict() 

2080 

2081 registered = False 

2082 for cur_type, sub_tree in list(_type_tree.items()): 

2083 if issubclass(cur_type, new_type): 

2084 sub_tree = _type_tree.pop(cur_type) # mutation for recursion brevity 

2085 try: 

2086 _type_tree[new_type][cur_type] = sub_tree 

2087 except KeyError: 

2088 _type_tree[new_type] = OrderedDict({cur_type: sub_tree}) 

2089 registered = True 

2090 elif issubclass(new_type, cur_type): 

2091 _type_tree[cur_type] = self._register_fuzzy_type(op, new_type, _type_tree=sub_tree) 

2092 registered = True 

2093 if not registered: 

2094 _type_tree[new_type] = OrderedDict() 

2095 return _type_tree 

2096 

2097 def register(self, target_type, **kwargs): 

2098 if not isinstance(target_type, type): 

2099 raise TypeError(f'register expected a type, not an instance: {target_type!r}') 

2100 exact = kwargs.pop('exact', None) 

2101 new_op_map = dict(kwargs) 

2102 

2103 for op_name in sorted(set(self._op_auto_map.keys()) | set(new_op_map.keys())): 

2104 cur_type_map = self._op_type_map.setdefault(op_name, OrderedDict()) 

2105 

2106 if op_name in new_op_map: 

2107 handler = new_op_map[op_name] 

2108 elif target_type in cur_type_map: 

2109 handler = cur_type_map[target_type] 

2110 else: 

2111 try: 

2112 handler = self._op_auto_map[op_name](target_type) 

2113 except Exception as e: 

2114 raise TypeError('error while determining support for operation' 

2115 ' "%s" on target type: %s (got %r)' 

2116 % (op_name, target_type.__name__, e)) 

2117 if handler is not False and not callable(handler): 

2118 raise TypeError('expected handler for op "%s" to be' 

2119 ' callable or False, not: %r' % (op_name, handler)) 

2120 new_op_map[op_name] = handler 

2121 

2122 for op_name, handler in new_op_map.items(): 

2123 self._op_type_map[op_name][target_type] = handler 

2124 

2125 if not exact: 

2126 for op_name in new_op_map: 

2127 self._register_fuzzy_type(op_name, target_type) 

2128 

2129 self._type_cache = {} # reset type cache 

2130 

2131 return 

2132 

2133 def register_op(self, op_name, auto_func=None, exact=False): 

2134 """add operations beyond the builtins ('get' and 'iterate' at the time 

2135 of writing). 

2136 

2137 auto_func is a function that when passed a type, returns a 

2138 handler associated with op_name if it's supported, or False if 

2139 it's not. 

2140 

2141 See glom.core.register_op() for the global version used by 

2142 extensions. 

2143 """ 

2144 if not isinstance(op_name, basestring): 

2145 raise TypeError(f'expected op_name to be a text name, not: {op_name!r}') 

2146 if auto_func is None: 

2147 auto_func = lambda t: False 

2148 elif not callable(auto_func): 

2149 raise TypeError(f'expected auto_func to be callable, not: {auto_func!r}') 

2150 

2151 # determine support for any previously known types 

2152 known_types = set(sum([list(m.keys()) for m 

2153 in self._op_type_map.values()], [])) 

2154 type_map = self._op_type_map.get(op_name, OrderedDict()) 

2155 type_tree = self._op_type_tree.get(op_name, OrderedDict()) 

2156 for t in sorted(known_types, key=lambda t: t.__name__): 

2157 if t in type_map: 

2158 continue 

2159 try: 

2160 handler = auto_func(t) 

2161 except Exception as e: 

2162 raise TypeError('error while determining support for operation' 

2163 ' "%s" on target type: %s (got %r)' 

2164 % (op_name, t.__name__, e)) 

2165 if handler is not False and not callable(handler): 

2166 raise TypeError('expected handler for op "%s" to be' 

2167 ' callable or False, not: %r' % (op_name, handler)) 

2168 type_map[t] = handler 

2169 

2170 if not exact: 

2171 for t in known_types: 

2172 self._register_fuzzy_type(op_name, t, _type_tree=type_tree) 

2173 

2174 self._op_type_map[op_name] = type_map 

2175 self._op_type_tree[op_name] = type_tree 

2176 self._op_auto_map[op_name] = auto_func 

2177 

2178 def _register_builtin_ops(self): 

2179 def _get_iterable_handler(type_obj): 

2180 return iter if callable(getattr(type_obj, '__iter__', None)) else False 

2181 

2182 self.register_op('iterate', _get_iterable_handler) 

2183 self.register_op('get', lambda _: getattr) 

2184 

2185 

2186_DEFAULT_SCOPE = ChainMap({}) 

2187 

2188 

2189def glom(target, spec, **kwargs): 

2190 """Access or construct a value from a given *target* based on the 

2191 specification declared by *spec*. 

2192 

2193 Accessing nested data, aka deep-get: 

2194 

2195 >>> target = {'a': {'b': 'c'}} 

2196 >>> glom(target, 'a.b') 

2197 'c' 

2198 

2199 Here the *spec* was just a string denoting a path, 

2200 ``'a.b'``. As simple as it should be. You can also use  

2201 :mod:`glob`-like wildcard selectors: 

2202 

2203 >>> target = {'a': [{'k': 'v1'}, {'k': 'v2'}]} 

2204 >>> glom(target, 'a.*.k') 

2205 ['v1', 'v2'] 

2206 

2207 In addition to ``*``, you can also use ``**`` for recursive access: 

2208 

2209 >>> target = {'a': [{'k': 'v3'}, {'k': 'v4'}], 'k': 'v0'} 

2210 >>> glom(target, '**.k') 

2211 ['v0', 'v3', 'v4'] 

2212  

2213 The next example shows how to use nested data to  

2214 access many fields at once, and make a new nested structure. 

2215 

2216 Constructing, or restructuring more-complicated nested data: 

2217 

2218 >>> target = {'a': {'b': 'c', 'd': 'e'}, 'f': 'g', 'h': [0, 1, 2]} 

2219 >>> spec = {'a': 'a.b', 'd': 'a.d', 'h': ('h', [lambda x: x * 2])} 

2220 >>> output = glom(target, spec) 

2221 >>> pprint(output) 

2222 {'a': 'c', 'd': 'e', 'h': [0, 2, 4]} 

2223 

2224 ``glom`` also takes a keyword-argument, *default*. When set, 

2225 if a ``glom`` operation fails with a :exc:`GlomError`, the 

2226 *default* will be returned, very much like 

2227 :meth:`dict.get()`: 

2228 

2229 >>> glom(target, 'a.xx', default='nada') 

2230 'nada' 

2231 

2232 The *skip_exc* keyword argument controls which errors should 

2233 be ignored. 

2234 

2235 >>> glom({}, lambda x: 100.0 / len(x), default=0.0, skip_exc=ZeroDivisionError) 

2236 0.0 

2237 

2238 Args: 

2239 target (object): the object on which the glom will operate. 

2240 spec (object): Specification of the output object in the form 

2241 of a dict, list, tuple, string, other glom construct, or 

2242 any composition of these. 

2243 default (object): An optional default to return in the case 

2244 an exception, specified by *skip_exc*, is raised. 

2245 skip_exc (Exception): An optional exception or tuple of 

2246 exceptions to ignore and return *default* (None if 

2247 omitted). If *skip_exc* and *default* are both not set, 

2248 glom raises errors through. 

2249 scope (dict): Additional data that can be accessed 

2250 via S inside the glom-spec. Read more: :ref:`scope`. 

2251 

2252 It's a small API with big functionality, and glom's power is 

2253 only surpassed by its intuitiveness. Give it a whirl! 

2254 

2255 """ 

2256 # TODO: check spec up front 

2257 default = kwargs.pop('default', None if 'skip_exc' in kwargs else _MISSING) 

2258 skip_exc = kwargs.pop('skip_exc', () if default is _MISSING else GlomError) 

2259 glom_debug = kwargs.pop('glom_debug', GLOM_DEBUG) 

2260 scope = _DEFAULT_SCOPE.new_child({ 

2261 Path: kwargs.pop('path', []), 

2262 Inspect: kwargs.pop('inspector', None), 

2263 MODE: AUTO, 

2264 MIN_MODE: None, 

2265 CHILD_ERRORS: [], 

2266 'globals': ScopeVars({}, {}), 

2267 }) 

2268 scope[UP] = scope 

2269 scope[ROOT] = scope 

2270 scope[T] = target 

2271 scope.update(kwargs.pop('scope', {})) 

2272 err = None 

2273 if kwargs: 

2274 raise TypeError('unexpected keyword args: %r' % sorted(kwargs.keys())) 

2275 try: 

2276 try: 

2277 ret = _glom(target, spec, scope) 

2278 except skip_exc: 

2279 if default is _MISSING: 

2280 raise 

2281 ret = default # should this also be arg_val'd? 

2282 except Exception as e: 

2283 if glom_debug: 

2284 raise 

2285 if isinstance(e, GlomError): 

2286 # need to change id or else py3 seems to not let us truncate the 

2287 # stack trace with the explicit "raise err" below 

2288 err = copy.copy(e) 

2289 err._set_wrapped(e) 

2290 else: 

2291 err = GlomError.wrap(e) 

2292 if isinstance(err, GlomError): 

2293 err._finalize(scope[LAST_CHILD_SCOPE]) 

2294 else: # wrapping failed, fall back to default behavior 

2295 raise 

2296 

2297 if err: 

2298 raise err 

2299 return ret 

2300 

2301 

2302def chain_child(scope): 

2303 """ 

2304 used for specs like Auto(tuple), Switch(), etc 

2305 that want to chain their child scopes together 

2306 

2307 returns a new scope that can be passed to 

2308 the next recursive glom call, e.g. 

2309 

2310 scope[glom](target, spec, chain_child(scope)) 

2311 """ 

2312 if LAST_CHILD_SCOPE not in scope.maps[0]: 

2313 return scope # no children yet, nothing to do 

2314 # NOTE: an option here is to drill down on LAST_CHILD_SCOPE; 

2315 # this would have some interesting consequences for scoping 

2316 # of tuples 

2317 nxt_in_chain = scope[LAST_CHILD_SCOPE] 

2318 nxt_in_chain.maps[0][NO_PYFRAME] = True 

2319 # previous failed branches are forgiven as the 

2320 # scope is re-wired into a new stack 

2321 del nxt_in_chain.maps[0][CHILD_ERRORS][:] 

2322 return nxt_in_chain 

2323 

2324 

2325unbound_methods = {type(str.__len__)} #, type(Ref.glomit)]) 

2326 

2327 

2328def _has_callable_glomit(obj): 

2329 glomit = getattr(obj, 'glomit', None) 

2330 return callable(glomit) and not isinstance(obj, type) 

2331 

2332 

2333def _glom(target, spec, scope): 

2334 parent = scope 

2335 pmap = parent.maps[0] 

2336 scope = scope.new_child({ 

2337 T: target, 

2338 Spec: spec, 

2339 UP: parent, 

2340 CHILD_ERRORS: [], 

2341 MODE: pmap[MODE], 

2342 MIN_MODE: pmap[MIN_MODE], 

2343 }) 

2344 pmap[LAST_CHILD_SCOPE] = scope 

2345 

2346 try: 

2347 if type(spec) is TType: # must go first, due to callability 

2348 scope[MIN_MODE] = None # None is tombstone 

2349 return _t_eval(target, spec, scope) 

2350 elif _has_callable_glomit(spec): 

2351 scope[MIN_MODE] = None 

2352 return spec.glomit(target, scope) 

2353 

2354 return (scope.maps[0][MIN_MODE] or scope.maps[0][MODE])(target, spec, scope) 

2355 except Exception as e: 

2356 scope.maps[1][CHILD_ERRORS].append(scope) 

2357 scope.maps[0][CUR_ERROR] = e 

2358 if NO_PYFRAME in scope.maps[1]: 

2359 cur_scope = scope[UP] 

2360 while NO_PYFRAME in cur_scope.maps[0]: 

2361 cur_scope.maps[1][CHILD_ERRORS].append(cur_scope) 

2362 cur_scope.maps[0][CUR_ERROR] = e 

2363 cur_scope = cur_scope[UP] 

2364 raise 

2365 

2366 

2367def AUTO(target, spec, scope): 

2368 if type(spec) is str: # shortcut to make deep-get use case faster 

2369 return _t_eval(target, Path.from_text(spec).path_t, scope) 

2370 if isinstance(spec, dict): 

2371 return _handle_dict(target, spec, scope) 

2372 elif isinstance(spec, list): 

2373 return _handle_list(target, spec, scope) 

2374 elif isinstance(spec, tuple): 

2375 return _handle_tuple(target, spec, scope) 

2376 elif isinstance(spec, basestring): 

2377 return Path.from_text(spec).glomit(target, scope) 

2378 elif callable(spec): 

2379 return spec(target) 

2380 

2381 raise TypeError('expected spec to be dict, list, tuple, callable, string,' 

2382 ' or other Spec-like type, not: %r' % (spec,)) 

2383 

2384 

2385_DEFAULT_SCOPE.update({ 

2386 glom: _glom, 

2387 TargetRegistry: TargetRegistry(register_default_types=True), 

2388}) 

2389 

2390 

2391def register(target_type, **kwargs): 

2392 """Register *target_type* so :meth:`~Glommer.glom()` will 

2393 know how to handle instances of that type as targets. 

2394 

2395 Here's an example of adding basic iterabile support for Django's ORM: 

2396 

2397 .. code-block:: python 

2398 

2399 import glom 

2400 import django.db.models 

2401 

2402 glom.register(django.db.models.Manager, iterate=lambda m: m.all()) 

2403 glom.register(django.db.models.QuerySet, iterate=lambda qs: qs.all()) 

2404 

2405 

2406 

2407 Args: 

2408 target_type (type): A type expected to appear in a glom() 

2409 call target 

2410 get (callable): A function which takes a target object and 

2411 a name, acting as a default accessor. Defaults to 

2412 :func:`getattr`. 

2413 iterate (callable): A function which takes a target object 

2414 and returns an iterator. Defaults to :func:`iter` if 

2415 *target_type* appears to be iterable. 

2416 exact (bool): Whether or not to match instances of subtypes 

2417 of *target_type*. 

2418 

2419 .. note:: 

2420 

2421 The module-level :func:`register()` function affects the 

2422 module-level :func:`glom()` function's behavior. If this 

2423 global effect is undesirable for your application, or 

2424 you're implementing a library, consider instantiating a 

2425 :class:`Glommer` instance, and using the 

2426 :meth:`~Glommer.register()` and :meth:`Glommer.glom()` 

2427 methods instead. 

2428 

2429 """ 

2430 _DEFAULT_SCOPE[TargetRegistry].register(target_type, **kwargs) 

2431 return 

2432 

2433 

2434def register_op(op_name, **kwargs): 

2435 """For extension authors needing to add operations beyond the builtin 

2436 'get', 'iterate', 'keys', 'assign', and 'delete' to the default scope.  

2437 See TargetRegistry for more details. 

2438 """ 

2439 _DEFAULT_SCOPE[TargetRegistry].register_op(op_name, **kwargs) 

2440 return 

2441 

2442 

2443class Glommer: 

2444 """The :class:`Glommer` type mostly serves to encapsulate type 

2445 registration context so that advanced uses of glom don't need to 

2446 worry about stepping on each other. 

2447 

2448 Glommer objects are lightweight and, once instantiated, provide 

2449 a :func:`glom()` method: 

2450 

2451 >>> glommer = Glommer() 

2452 >>> glommer.glom({}, 'a.b.c', default='d') 

2453 'd' 

2454 >>> Glommer().glom({'vals': list(range(3))}, ('vals', len)) 

2455 3 

2456 

2457 Instances also provide :meth:`~Glommer.register()` method for 

2458 localized control over type handling. 

2459 

2460 Args: 

2461 register_default_types (bool): Whether or not to enable the 

2462 handling behaviors of the default :func:`glom()`. These 

2463 default actions include dict access, list and iterable 

2464 iteration, and generic object attribute access. Defaults to 

2465 True. 

2466 

2467 """ 

2468 def __init__(self, **kwargs): 

2469 register_default_types = kwargs.pop('register_default_types', True) 

2470 scope = kwargs.pop('scope', _DEFAULT_SCOPE) 

2471 

2472 # this "freezes" the scope in at the time of construction 

2473 self.scope = ChainMap(dict(scope)) 

2474 self.scope[TargetRegistry] = TargetRegistry(register_default_types=register_default_types) 

2475 

2476 def register(self, target_type, **kwargs): 

2477 """Register *target_type* so :meth:`~Glommer.glom()` will 

2478 know how to handle instances of that type as targets. 

2479 

2480 Args: 

2481 target_type (type): A type expected to appear in a glom() 

2482 call target 

2483 get (callable): A function which takes a target object and 

2484 a name, acting as a default accessor. Defaults to 

2485 :func:`getattr`. 

2486 iterate (callable): A function which takes a target object 

2487 and returns an iterator. Defaults to :func:`iter` if 

2488 *target_type* appears to be iterable. 

2489 exact (bool): Whether or not to match instances of subtypes 

2490 of *target_type*. 

2491 

2492 .. note:: 

2493 

2494 The module-level :func:`register()` function affects the 

2495 module-level :func:`glom()` function's behavior. If this 

2496 global effect is undesirable for your application, or 

2497 you're implementing a library, consider instantiating a 

2498 :class:`Glommer` instance, and using the 

2499 :meth:`~Glommer.register()` and :meth:`Glommer.glom()` 

2500 methods instead. 

2501 

2502 """ 

2503 exact = kwargs.pop('exact', False) 

2504 self.scope[TargetRegistry].register(target_type, exact=exact, **kwargs) 

2505 return 

2506 

2507 def glom(self, target, spec, **kwargs): 

2508 return glom(target, spec, scope=self.scope, **kwargs) 

2509 

2510 

2511class Fill: 

2512 """A specifier type which switches to glom into "fill-mode". For the 

2513 spec contained within the Fill, glom will only interpret explicit 

2514 specifier types (including T objects). Whereas the default mode 

2515 has special interpretations for each of these builtins, fill-mode 

2516 takes a lighter touch, making Fill great for "filling out" Python 

2517 literals, like tuples, dicts, sets, and lists. 

2518 

2519 >>> target = {'data': [0, 2, 4]} 

2520 >>> spec = Fill((T['data'][2], T['data'][0])) 

2521 >>> glom(target, spec) 

2522 (4, 0) 

2523 

2524 As you can see, glom's usual built-in tuple item chaining behavior 

2525 has switched into a simple tuple constructor. 

2526 

2527 (Sidenote for Lisp fans: Fill is like glom's quasi-quoting.) 

2528 

2529 """ 

2530 def __init__(self, spec=None): 

2531 self.spec = spec 

2532 

2533 def glomit(self, target, scope): 

2534 scope[MODE] = FILL 

2535 return scope[glom](target, self.spec, scope) 

2536 

2537 def fill(self, target): 

2538 return glom(target, self) 

2539 

2540 def __repr__(self): 

2541 cn = self.__class__.__name__ 

2542 rpr = '' if self.spec is None else bbrepr(self.spec) 

2543 return f'{cn}({rpr})' 

2544 

2545 

2546def FILL(target, spec, scope): 

2547 # TODO: register an operator or two for the following to allow 

2548 # extension. This operator can probably be shared with the 

2549 # upcoming traversal/remap feature. 

2550 recurse = lambda val: scope[glom](target, val, scope) 

2551 if type(spec) is dict: 

2552 return {recurse(key): recurse(val) for key, val in spec.items()} 

2553 if type(spec) in (list, tuple, set, frozenset): 

2554 result = [recurse(val) for val in spec] 

2555 if type(spec) is list: 

2556 return result 

2557 return type(spec)(result) 

2558 if callable(spec): 

2559 return spec(target) 

2560 return spec 

2561 

2562class _ArgValuator: 

2563 def __init__(self): 

2564 self.cache = {} 

2565 

2566 def mode(self, target, spec, scope): 

2567 """ 

2568 similar to FILL, but without function calling; 

2569 useful for default, scope assignment, call/invoke, etc 

2570 """ 

2571 recur = lambda val: scope[glom](target, val, scope) 

2572 result = spec 

2573 if type(spec) in (list, dict): # can contain themselves 

2574 if id(spec) in self.cache: 

2575 return self.cache[id(spec)] 

2576 result = self.cache[id(spec)] = type(spec)() 

2577 if type(spec) is dict: 

2578 result.update({recur(key): recur(val) for key, val in spec.items()}) 

2579 else: 

2580 result.extend([recur(val) for val in spec]) 

2581 if type(spec) in (tuple, set, frozenset): # cannot contain themselves 

2582 result = type(spec)([recur(val) for val in spec]) 

2583 return result 

2584 

2585 

2586def arg_val(target, arg, scope): 

2587 """ 

2588 evaluate an argument to find its value 

2589 (arg_val phonetically similar to "eval" -- evaluate as an arg) 

2590 """ 

2591 mode = scope[MIN_MODE] 

2592 scope[MIN_MODE] = _ArgValuator().mode 

2593 result = scope[glom](target, arg, scope) 

2594 scope[MIN_MODE] = mode 

2595 return result