def test_instance(self, C): """ Raises `TypeError` on non-classes. """ with pytest.raises(TypeError) as e: fields(C(1, 2)) assert "Passed object must be a class." == e.value.args[0]
def test_empty_metadata_singleton(self, list_of_attrs): """ All empty metadata attributes share the same empty metadata dict. """ C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs))) for a in fields(C)[1:]: assert a.metadata is fields(C)[0].metadata
def test_copies(self, C): """ Returns a new list object with new `Attribute` objects. """ assert C.__attrs_attrs__ is not fields(C) assert all(new == original and new is not original for new, original in zip(fields(C), C.__attrs_attrs__))
def test_handler_non_attrs_class(self, C): """ Raises `ValueError` if passed a non-``attrs`` instance. """ with pytest.raises(ValueError) as e: fields(object) assert ( "{o!r} is not an attrs-decorated class.".format(o=object) ) == e.value.args[0]
def test_fields_dict(self, C): """ Returns an ordered dict of ``{attribute_name: Attribute}``. """ d = fields_dict(C) assert isinstance(d, ordered_dict) assert list(fields(C)) == list(d.values()) assert [a.name for a in fields(C)] == [field_name for field_name in d]
def test_handler_non_attrs_class(self, C): """ Raises `ValueError` if passed a non-``attrs`` instance. """ with pytest.raises(NotAnAttrsClassError) as e: fields(object) assert ("{o!r} is not an attrs-decorated class.".format( o=object)) == e.value.args[0]
def test_change(self, C, val): """ Changes work. """ # Take the first attribute, and change it. assume(fields(C)) # Skip classes with no attributes. original = C() attribute = fields(C)[0] changed = assoc(original, **{attribute.name: val}) assert getattr(changed, attribute.name) == val
def test_not_none_metadata(self, list_of_attrs): """ Non-empty metadata attributes exist as fields after ``@attr.s``. """ C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs))) assert len(fields(C)) > 0 for cls_a, raw_a in zip(fields(C), list_of_attrs): assert cls_a.metadata != {} assert cls_a.metadata == raw_a.metadata
def test_types(self): """ Sets the `Attribute.type` attr from type argument. """ @attr.s class C(object): x = attr.ib(type=int) y = attr.ib(type=str) z = attr.ib() assert int is fields(C).x.type assert str is fields(C).y.type assert None is fields(C).z.type
def test_change(self, C, data): """ Changes work. """ # Take the first attribute, and change it. assume(fields(C)) # Skip classes with no attributes. field_names = [a.name for a in fields(C)] original = C() chosen_names = data.draw(st.sets(st.sampled_from(field_names))) change_dict = {name: data.draw(st.integers()) for name in chosen_names} changed = assoc(original, **change_dict) for k, v in change_dict.items(): assert getattr(changed, k) == v
def assert_proper_col_class(obj, obj_tuple): # Iterate over all attributes, and if they are lists or mappings # in the original, assert they are the same class in the dumped. for index, field in enumerate(fields(obj.__class__)): field_val = getattr(obj, field.name) if has(field_val.__class__): # This field holds a class, recurse the assertions. assert_proper_col_class(field_val, obj_tuple[index]) elif isinstance(field_val, (list, tuple)): # This field holds a sequence of something. expected_type = type(obj_tuple[index]) assert type(field_val) is expected_type # noqa: E721 for obj_e, obj_tuple_e in zip(field_val, obj_tuple[index]): if has(obj_e.__class__): assert_proper_col_class(obj_e, obj_tuple_e) elif isinstance(field_val, dict): orig = field_val tupled = obj_tuple[index] assert type(orig) is type(tupled) # noqa: E721 for obj_e, obj_tuple_e in zip(orig.items(), tupled.items()): if has(obj_e[0].__class__): # Dict key assert_proper_col_class(obj_e[0], obj_tuple_e[0]) if has(obj_e[1].__class__): # Dict value assert_proper_col_class(obj_e[1], obj_tuple_e[1])
def assert_proper_tuple_class(obj, obj_tuple): assert isinstance(obj_tuple, tuple_class) for index, field in enumerate(fields(obj.__class__)): field_val = getattr(obj, field.name) if has(field_val.__class__): # This field holds a class, recurse the assertions. assert_proper_tuple_class(field_val, obj_tuple[index])
def test_asdict_preserve_order(self, cls): """ Field order should be preserved when dumping to OrderedDicts. """ instance = cls() dict_instance = asdict(instance, dict_factory=OrderedDict) assert [a.name for a in fields(cls)] == list(dict_instance.keys())
def convert_key_to_attr_names(cls, original): """ convert key names to their corresponding attribute names """ attrs = fields(cls) updated = {} for a in attrs: key_name = a.metadata.get('key') or a.name if key_name in original: updated[a.name] = original.get(key_name) return updated
def test_deprecated_convert(self): """ Using *convert* raises a DeprecationWarning and sets the converter field. """ def conv(v): return v with pytest.warns(DeprecationWarning) as wi: @attr.s class C(object): x = attr.ib(convert=conv) convert = fields(C).x.convert assert 2 == len(wi.list) w = wi.pop() assert conv == fields(C).x.converter == convert assert ( "The `convert` argument is deprecated in favor of `converter`. " "It will be removed after 2019/01.", ) == w.message.args assert __file__ == w.filename
def test_validator_slots(self): """ If a validator is passed, call it with the preliminary instance, the Attribute, and the argument. """ class VException(Exception): pass def raiser(*args): raise VException(*args) C = make_class("C", {"a": attr("a", validator=raiser)}, slots=True) with pytest.raises(VException) as e: C(42) assert (fields(C)[0], 42,) == e.value.args[1:] assert isinstance(e.value.args[0], C)
def related_obj_to_dict(obj, **kwargs): """ Covert a known related object to a dictionary. """ # Explicitly discard formatter kwarg, should not be cascaded down. kwargs.pop('formatter', None) # If True, remove fields that start with an underscore (e.g. _secret) suppress_private_attr = kwargs.get("suppress_private_attr", False) # if True, don't store fields with None values into dictionary. suppress_empty_values = kwargs.get("suppress_empty_values", False) # get list of attrs fields attrs = fields(obj.__class__) # instantiate return dict, use OrderedDict type by default return_dict = kwargs.get("dict_factory", OrderedDict)() for a in attrs: # skip if private attr and flag tells you to skip if suppress_private_attr and a.name.startswith("_"): continue metadata = a.metadata or {} # formatter is a related-specific `attrs` meta field # see fields.DateField formatter = metadata.get('formatter') # get value and call to_dict on it, passing the kwargs/formatter value = getattr(obj, a.name) value = to_dict(value, formatter=formatter, **kwargs) # check flag, skip None values if suppress_empty_values and value is None: continue # field name can be overridden by the metadata field key_name = a.metadata.get('key') or a.name # store converted / formatted value into return dictionary return_dict[key_name] = value return return_dict
def assert_proper_dict_class(obj, obj_dict): assert isinstance(obj_dict, dict_class) for field in fields(obj.__class__): field_val = getattr(obj, field.name) if has(field_val.__class__): # This field holds a class, recurse the assertions. assert_proper_dict_class(field_val, obj_dict[field.name]) elif isinstance(field_val, Sequence): dict_val = obj_dict[field.name] for item, item_dict in zip(field_val, dict_val): if has(item.__class__): assert_proper_dict_class(item, item_dict) elif isinstance(field_val, Mapping): # This field holds a dictionary. assert isinstance(obj_dict[field.name], dict_class) for key, val in field_val.items(): if has(val.__class__): assert_proper_dict_class(val, obj_dict[key])
def convert_key_to_attr_names(cls, original): """ convert key names to their corresponding attribute names """ attrs = fields(cls) updated = {} keys_pulled = set() for a in attrs: key_name = a.metadata.get('key') or a.name if key_name in original: updated[a.name] = original.get(key_name) keys_pulled.add(key_name) if getattr(cls, '__related_strict__', False): extra = set(original.keys()) - keys_pulled if len(extra): raise ValueError("Extra keys (strict mode): {}".format(extra)) return updated
def test_metadata_present(self, list_of_attrs): """ Assert dictionaries are copied and present. """ C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs))) for hyp_attr, class_attr in zip(list_of_attrs, fields(C)): if hyp_attr.metadata is None: # The default is a singleton empty dict. assert class_attr.metadata is not None assert len(class_attr.metadata) == 0 else: assert hyp_attr.metadata == class_attr.metadata # Once more, just to assert getting items and iteration. for k in class_attr.metadata: assert hyp_attr.metadata[k] == class_attr.metadata[k] assert ( hyp_attr.metadata.get(k) == class_attr.metadata.get(k))
def test_metadata_present(self, list_of_attrs): """ Assert dictionaries are copied and present. """ C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs))) for hyp_attr, class_attr in zip(list_of_attrs, fields(C)): if hyp_attr.metadata is None: # The default is a singleton empty dict. assert class_attr.metadata is not None assert len(class_attr.metadata) == 0 else: assert hyp_attr.metadata == class_attr.metadata # Once more, just to assert getting items and iteration. for k in class_attr.metadata: assert hyp_attr.metadata[k] == class_attr.metadata[k] assert (hyp_attr.metadata.get(k) == class_attr.metadata.get(k))
def from_config(cls, cfg): # TODO - create a nicer error message - see above # Verbose unrecognized field # for k in kwargs.keys(): # if k not in cls.REQ_FIELDS + cls.OPT_FIELDS: # raise ValueError("Unrecognized field in info: '{f}'. Avaiable fields are: {l}". # format(f=k, l=cls.REQ_FIELDS)) # # Verbose undefined field # undefined_set = set(cls.REQ_FIELDS) - kwargs.keys() # if undefined_set: # raise ValueError("The following arguments were not specified: {0}. Please specify them.". # format(undefined_set)) attrs = fields(cls) cls_keys = {a.metadata.get('key') or a.name for a in attrs} cfg_keys = set(cfg.keys()) extra_keys = cfg_keys - cls_keys if len(extra_keys) > 0: raise ValueError("Unrecognized fields: {0}. Available fields are {1}".format(extra_keys, cls_keys)) return related.to_model(cls, cfg)
def test_metadata_immutability(self, C, string): """ The metadata dict should be best-effort immutable. """ for a in fields(C): with pytest.raises(TypeError): a.metadata[string] = string with pytest.raises(AttributeError): a.metadata.update({string: string}) with pytest.raises(AttributeError): a.metadata.clear() with pytest.raises(AttributeError): a.metadata.setdefault(string, string) for k in a.metadata: # For some reason, Python 3's MappingProxyType throws an # IndexError for deletes on a large integer key. with pytest.raises((TypeError, IndexError)): del a.metadata[k] with pytest.raises(AttributeError): a.metadata.pop(k) with pytest.raises(AttributeError): a.metadata.popitem()
def test_allow(self, excl, value): """ Return True if class or attribute is not blacklisted. """ e = exclude(*excl) assert e(fields(C).a, value) is True
def test_allow(self, incl, value): """ Return True if a class or attribute is whitelisted. """ i = include(*incl) assert i(fields(C).a, value) is True
def test_drop_class(self, excl, value): """ Return True on non-blacklisted classes and attributes. """ e = exclude(*excl) assert e(fields(C).a, value) is False
def asjsonld( inst, recurse=True, filter=None, dict_factory=dict, retain_collection_types=False, export_context=True, ): """Dump a JSON-LD class to the JSON with generated ``@context`` field.""" attrs = fields(inst.__class__) rv = dict_factory() def convert_value(v): """Convert special types.""" if isinstance(v, Path): return str(v) return v for a in attrs: v = getattr(inst, a.name) # do not export context for containers ec = export_context and KEY_CLS not in a.metadata if filter is not None and not filter(a, v): continue if recurse is True: if has(v.__class__): rv[a.name] = asjsonld(v, recurse=True, filter=filter, dict_factory=dict_factory) elif isinstance(v, (tuple, list, set)): cf = v.__class__ if retain_collection_types is True else list rv[a.name] = cf([ asjsonld( i, recurse=True, filter=filter, dict_factory=dict_factory, export_context=ec, ) if has(i.__class__) else i for i in v ]) elif isinstance(v, dict): df = dict_factory rv[a.name] = df(( asjsonld(kk, dict_factory=df) if has(kk.__class__) else kk, asjsonld(vv, dict_factory=df, export_context=ec ) if has(vv.__class__) else vv) for kk, vv in iteritems(v)) else: rv[a.name] = convert_value(v) else: rv[a.name] = convert_value(v) inst_cls = type(inst) if export_context: rv['@context'] = deepcopy(inst_cls._jsonld_context) if inst_cls._jsonld_type: rv['@type'] = inst_cls._jsonld_type return rv
def asjsonld( inst, recurse=True, filter=None, dict_factory=dict, retain_collection_types=False, add_context=True, use_scoped_type_form=False, basedir=None, ): """Dump a JSON-LD class to the JSON with generated ``@context`` field.""" jsonld_fields = inst.__class__._jsonld_fields attrs = tuple( field for field in fields(inst.__class__) if field.name in jsonld_fields ) rv = dict_factory() def convert_value(value): """Convert non-serializable types.""" if isinstance(value, Path): result = str(value) if basedir: result = os.path.relpath(result, str(basedir)) return result if isinstance(value, datetime): if not value.tzinfo: # set timezone to local timezone tz = datetime.now(timezone.utc).astimezone().tzinfo value = value.replace(tzinfo=tz) return value.isoformat() return value inst_cls = type(inst) for a in attrs: v = getattr(inst, a.name) scoped = a.name in inst_cls._scoped_properties # skip proxies if isinstance(v, weakref.ReferenceType): continue if filter is not None and not filter(a, v): continue if recurse is True: if has(v.__class__): rv[a.name] = asjsonld( v, recurse=True, filter=filter, dict_factory=dict_factory, add_context=False, use_scoped_type_form=scoped, basedir=basedir, ) elif isinstance(v, (tuple, list, set)): cf = v.__class__ if retain_collection_types is True else list rv[a.name] = cf([ asjsonld( i, recurse=True, filter=filter, dict_factory=dict_factory, add_context=False, use_scoped_type_form=scoped, basedir=basedir, ) if has(i.__class__) else i for i in v ]) elif isinstance(v, dict): df = dict_factory rv[a.name] = df(( asjsonld( kk, dict_factory=df, add_context=False, basedir=basedir, ) if has(kk.__class__) else convert_value(kk), asjsonld( vv, dict_factory=df, add_context=False, basedir=basedir, ) if has(vv.__class__) else vv ) for kk, vv in iteritems(v)) else: rv[a.name] = convert_value(v) else: rv[a.name] = convert_value(v) if add_context: rv['@context'] = deepcopy(inst_cls._jsonld_context) rv_type = [] if inst_cls._jsonld_type: if isinstance(inst_cls._jsonld_type, (list, tuple, set)): rv_type.extend(inst_cls._jsonld_type) else: rv_type.append(inst_cls._jsonld_type) if use_scoped_type_form: rv_type = [ '{}_{}'.format(inst_cls._renku_type, t) for t in rv_type ] rv['@type'] = rv_type[0] if len(rv_type) == 1 else rv_type return rv
def fix_keys(data): """Fix names of keys.""" for a in fields(cls): a_name = a.name.rstrip('_') if a_name in data: yield a.name, data[a_name]
def ascwl( inst, recurse=True, filter=None, dict_factory=dict, retain_collection_types=False, basedir=None, ): """Return the ``attrs`` attribute values of *inst* as a dict. Support ``jsonldPredicate`` in a field metadata for generating mappings from lists. Adapted from ``attr._funcs``. """ attrs = fields(inst.__class__) rv = dict_factory() def convert_value(v): """Convert special types.""" if isinstance(v, Path): v = str(v) return os.path.relpath(v, str(basedir)) if basedir else v return v for a in attrs: if a.name.startswith('__'): continue a_name = a.name.rstrip('_') v = getattr(inst, a.name) if filter is not None and not filter(a, v): continue if recurse is True: if has(v.__class__): rv[a_name] = ascwl( v, recurse=True, filter=filter, dict_factory=dict_factory, basedir=basedir, ) elif isinstance(v, (tuple, list, set)): cf = v.__class__ if retain_collection_types is True else list rv[a_name] = cf([ ascwl( i, recurse=True, filter=filter, dict_factory=dict_factory, basedir=basedir, ) if has(i.__class__) else i for i in v ]) if 'jsonldPredicate' in a.metadata: k = a.metadata['jsonldPredicate'].get('mapSubject') if k: vv = dict_factory() for i in rv[a_name]: kk = i.pop(k) vv[kk] = i rv[a_name] = vv elif isinstance(v, dict): df = dict_factory rv[a_name] = df((ascwl( kk, dict_factory=df, basedir=basedir, ) if has(kk.__class__) else convert_value(kk), ascwl( vv, dict_factory=df, basedir=basedir, ) if has(vv.__class__) else vv) for kk, vv in iteritems(v)) else: rv[a_name] = convert_value(v) else: rv[a_name] = convert_value(v) if isinstance(inst, CWLClass): rv['class'] = inst.__class__.__name__ return rv
def asjsonld( inst, recurse=True, filter=None, dict_factory=dict, retain_collection_types=False, export_context=True, basedir=None, ): """Dump a JSON-LD class to the JSON with generated ``@context`` field.""" jsonld_fields = inst.__class__._jsonld_fields attrs = tuple(field for field in fields(inst.__class__) if field.name in jsonld_fields) rv = dict_factory() def convert_value(v): """Convert special types.""" if isinstance(v, Path): v = str(v) return os.path.relpath(v, str(basedir)) if basedir else v return v for a in attrs: v = getattr(inst, a.name) # skip proxies if isinstance(v, weakref.ReferenceType): continue # do not export context for containers ec = export_context and KEY_CLS not in a.metadata if filter is not None and not filter(a, v): continue if recurse is True: if has(v.__class__): rv[a.name] = asjsonld( v, recurse=True, filter=filter, dict_factory=dict_factory, basedir=basedir, ) elif isinstance(v, (tuple, list, set)): cf = v.__class__ if retain_collection_types is True else list rv[a.name] = cf([ asjsonld( i, recurse=True, filter=filter, dict_factory=dict_factory, export_context=ec, basedir=basedir, ) if has(i.__class__) else i for i in v ]) elif isinstance(v, dict): df = dict_factory rv[a.name] = df((asjsonld( kk, dict_factory=df, basedir=basedir, ) if has(kk.__class__) else convert_value(kk), asjsonld( vv, dict_factory=df, export_context=ec, basedir=basedir, ) if has(vv.__class__) else vv) for kk, vv in iteritems(v)) else: rv[a.name] = convert_value(v) else: rv[a.name] = convert_value(v) inst_cls = type(inst) if export_context: rv['@context'] = deepcopy(inst_cls._jsonld_context) if inst_cls._jsonld_type: rv['@type'] = inst_cls._jsonld_type return rv
def test_fields(self, C): """ Returns a list of `Attribute`a. """ assert all(isinstance(a, Attribute) for a in fields(C))
def test_fields_properties(self, C): """ Fields returns a tuple with properties. """ for attribute in fields(C): assert getattr(fields(C), attribute.name) is attribute
def test_drop_class(self, incl, value): """ Return False on non-whitelisted classes and attributes. """ i = include(*incl) assert i(fields(C).a, value) is False