def test_scope_spec(): scope_spec = Spec(S) assert scope_spec.glom(5, scope={'cat': 1})['cat'] == 1 cat_scope_spec = Spec(scope_spec, scope={'cat': 1}) assert 'cat' in repr(cat_scope_spec) assert cat_scope_spec.glom(5)['cat'] == 1 # test that Spec overrides the scope for its sub-tree assert glom(5, cat_scope_spec, scope={'cat': 2})['cat'] == 1
def test_spec(): assert glom(5, T) == 5 # check assumption about echo behavior echo = Spec(T) assert echo.glom(5) == 5 assert glom(5, echo) == 5 echo2 = Spec(echo) assert echo2.glom(5) == 5 with pytest.raises(TypeError, match='expected spec to be'): glom({}, object()) return
def test_spec_and_recursion(): assert repr(Spec('a.b.c')) == "Spec('a.b.c')" # Call doesn't normally recurse, but Spec can make it do so assert glom(['a', 'b', 'c'], Call(list, args=(Spec(Call( reversed, args=(Spec(T), ))), ))) == ['c', 'b', 'a'] assert glom(['cat', {'cat': 1}], T[1][T[0]]) == 1 assert glom([['ab', 'cd', 'ef'], ''.join], Call(T[1], args=(Spec((T[0], [T[1:]])), ))) == 'bdf' # test that spec works on the left of a dict spec assert glom({'a': 'A'}, {Spec('a'): 'a', 'a': 'a'}) == {'A': 'A', 'a': 'A'}
def listings_from_html(html): def mk_extractor_for(k): def extractor(x): t = x.find(name='div' if k != 'link' else 'h2', attrs={'class': f'listing__{k}'}) if t is not None: return t.text else: return t extractor.__name__ = f"{k}_extractor" return extractor def tags_extractor(x): return [t.text for t in x.find_all(name='a', attrs={'class': 'listing__tag-item'})] spec = {a: mk_extractor_for(a) for a in ['date', 'link', 'position', 'office', 'copy']} spec['tags'] = tags_extractor extractor = Spec(spec).glom t = BeautifulSoup(html, features="lxml") tt = t.find('section', {'id': 'content'}) if tt is not None: w = tt.find_all('tr') def listings_info_gen(w): for ww in w: yield extractor(ww) yield from listings_info_gen(w)
def update_inplace(_conf: Dict, path: _Path_t) -> Dict: """Invoke the bound function to reassign matching items FIXME: possibly this can be simplified using functools.partial """ glom_spec = _path_to_glom_spec(path) _config_t = Spec(Invoke(func).constants(path[-1]).specs(glom_spec)) return glom(_conf, Assign(_path_to_glom_spec(path), _config_t))
def from_yaml(cls, yaml_path: Union[str, Path]) -> _ConfigIO: # FIXME: type checking is ignored for the return statement because mypy # doesn't seem to know this is an abstract base class, and the argument # unpacking makes sense when instantiating any of the derived classes. # read in the config conf = read_yaml(yaml_path) # apply all transformations as stored in the cls for path, trans in cls.trans_dict.items(): glom(conf, Assign(path, Spec((path, trans)))) return cls(**conf) # type: ignore
def mk_serializer_and_deserializer(spec, mk_inv_spec=None): if not isinstance(spec, Spec): spec = Spec(spec) def serialize(o): return glom(o, spec) if mk_inv_spec is None: return serialize else: def deserialize(o): return glom(o, mk_inv_spec(o)) return serialize, deserialize
def test_assign_spec_val(): output = glom({'b': 'c'}, Assign('a', Spec('b'))) assert output['a'] == output['b'] == 'c'
def if_not_empty(obj, if_empty_val=None): if obj != Parameter.empty: return obj else: return if_empty_val none_if_not_empty = partial(if_not_empty, if_not_empty=None) func_info_spec = Spec( { 'name': '__name__', 'qualname': '__qualname__', 'module': '__module__', 'return_annotation': (signature, 'return_annotation', none_if_not_empty,), 'params': (signature, 'parameters'), } ) def py_obj_info(obj): return func_info_spec.glom(obj) def conditional_logger(verbose=False, log_func=print): if verbose: return log_func else: