def test_datetime_attribute(self): from aiida.utils.timezone import (get_current_timezone, is_naive, make_aware, now) a = Node() date = now() a._set_attr('some_date', date) a.store() retrieved = a.get_attr('some_date') if is_naive(date): date_to_compare = make_aware(date, get_current_timezone()) else: date_to_compare = date # Do not compare microseconds (they are not stored in the case of MySQL) date_to_compare = date_to_compare.replace(microsecond=0) retrieved = retrieved.replace(microsecond=0) self.assertEquals(date_to_compare, retrieved)
def _deserialize_attribute(mainitem, subitems, sep, original_class=None, original_pk=None, lesserrors=False): """ Deserialize a single attribute. :param mainitem: the main item (either the attribute itself for base types (None, string, ...) or the main item for lists and dicts. Must contain the 'key' key and also the following keys: datatype, tval, fval, ival, bval, dval. NOTE that a type check is not performed! tval is expected to be a string, dval a date, etc. :param subitems: must be a dictionary of dictionaries. In the top-level dictionary, the key must be the key of the attribute, stripped of all prefixes (i.e., if the mainitem has key 'a.b' and we pass subitems 'a.b.0', 'a.b.1', 'a.b.1.c', their keys must be '0', '1', '1.c'). It must be None if the value is not iterable (int, str, float, ...). It is an empty dictionary if there are no subitems. :param sep: a string, the separator between subfields (to separate the name of a dictionary from the keys it contains, for instance) :param original_class: if these elements come from a specific subclass of DbMultipleValueAttributeBaseClass, pass here the class (note: the class, not the instance!). This is used only in case the wrong number of elements is found in the raw data, to print a more meaningful message (if the class has a dbnode associated to it) :param original_pk: if the elements come from a specific subclass of DbMultipleValueAttributeBaseClass that has a dbnode associated to it, pass here the PK integer. This is used only in case the wrong number of elements is found in the raw data, to print a more meaningful message :param lesserrors: If set to True, in some cases where the content of the DB is not consistent but data is still recoverable, it will just log the message rather than raising an exception (e.g. if the number of elements of a dictionary is different from the number declared in the ival field). :return: the deserialized value :raise DeserializationError: if an error occurs """ # from aiida.common import aiidalogger if mainitem['datatype'] == 'none': if subitems: raise DeserializationException("'{}' is of a base type, " "but has subitems!".format( mainitem.key)) return None elif mainitem['datatype'] == 'bool': if subitems: raise DeserializationException("'{}' is of a base type, " "but has subitems!".format( mainitem.key)) return mainitem['bval'] elif mainitem['datatype'] == 'int': if subitems: raise DeserializationException("'{}' is of a base type, " "but has subitems!".format( mainitem.key)) return mainitem['ival'] elif mainitem['datatype'] == 'float': if subitems: raise DeserializationException("'{}' is of a base type, " "but has subitems!".format( mainitem.key)) return mainitem['fval'] elif mainitem['datatype'] == 'txt': if subitems: raise DeserializationException("'{}' is of a base type, " "but has subitems!".format( mainitem.key)) return mainitem['tval'] elif mainitem['datatype'] == 'date': if subitems: raise DeserializationException("'{}' is of a base type, " "but has subitems!".format( mainitem.key)) if is_naive(mainitem['dval']): return make_aware(mainitem['dval'], get_current_timezone()) else: return mainitem['dval'] return mainitem['dval'] elif mainitem['datatype'] == 'list': # subitems contains all subitems, here I store only those of # deepness 1, i.e. if I have subitems '0', '1' and '1.c' I # store only '0' and '1' firstlevelsubdict = { k: v for k, v in subitems.iteritems() if sep not in k } # For checking, I verify the expected values expected_set = set(["{:d}".format(i) for i in range(mainitem['ival'])]) received_set = set(firstlevelsubdict.keys()) # If there are more entries than expected, but all expected # ones are there, I just issue an error but I do not stop. if not expected_set.issubset(received_set): if (original_class is not None and original_class._subspecifier_field_name is not None): subspecifier_string = "{}={} and ".format( original_class._subspecifier_field_name, original_pk) else: subspecifier_string = "" if original_class is None: sourcestr = "the data passed" else: sourcestr = original_class.__name__ raise DeserializationException( "Wrong list elements stored in {} for " "{}key='{}' ({} vs {})".format(sourcestr, subspecifier_string, mainitem['key'], expected_set, received_set)) if expected_set != received_set: if (original_class is not None and original_class._subspecifier_field_name is not None): subspecifier_string = "{}={} and ".format( original_class._subspecifier_field_name, original_pk) else: subspecifier_string = "" if original_class is None: sourcestr = "the data passed" else: sourcestr = original_class.__name__ msg = ("Wrong list elements stored in {} for " "{}key='{}' ({} vs {})".format(sourcestr, subspecifier_string, mainitem['key'], expected_set, received_set)) if lesserrors: print msg #~ aiidalogger.error(msg) else: raise DeserializationException(msg) # I get the values in memory as a dictionary tempdict = {} for firstsubk, firstsubv in firstlevelsubdict.iteritems(): # I call recursively the same function to get subitems newsubitems = { k[len(firstsubk) + len(sep):]: v for k, v in subitems.iteritems() if k.startswith(firstsubk + sep) } tempdict[firstsubk] = _deserialize_attribute( mainitem=firstsubv, subitems=newsubitems, sep=sep, original_class=original_class, original_pk=original_pk) # And then I put them in a list retlist = [tempdict["{:d}".format(i)] for i in range(mainitem['ival'])] return retlist elif mainitem['datatype'] == 'dict': # subitems contains all subitems, here I store only those of # deepness 1, i.e. if I have subitems '0', '1' and '1.c' I # store only '0' and '1' firstlevelsubdict = { k: v for k, v in subitems.iteritems() if sep not in k } if len(firstlevelsubdict) != mainitem['ival']: if (original_class is not None and original_class._subspecifier_field_name is not None): subspecifier_string = "{}={} and ".format( original_class._subspecifier_field_name, original_pk) else: subspecifier_string = "" if original_class is None: sourcestr = "the data passed" else: sourcestr = original_class.__name__ msg = ("Wrong dict length stored in {} for " "{}key='{}' ({} vs {})".format(sourcestr, subspecifier_string, mainitem['key'], len(firstlevelsubdict), mainitem['ival'])) if lesserrors: print msg #~ aiidalogger.error(msg) else: raise DeserializationException(msg) # I get the values in memory as a dictionary tempdict = {} for firstsubk, firstsubv in firstlevelsubdict.iteritems(): # I call recursively the same function to get subitems newsubitems = { k[len(firstsubk) + len(sep):]: v for k, v in subitems.iteritems() if k.startswith(firstsubk + sep) } tempdict[firstsubk] = _deserialize_attribute( mainitem=firstsubv, subitems=newsubitems, sep=sep, original_class=original_class, original_pk=original_pk) return tempdict elif mainitem['datatype'] == 'json': try: return json.loads(mainitem['tval']) except ValueError: raise DeserializationException( "Error in the content of the json field") else: raise DeserializationException( "The type field '{}' is not recognized".format( mainitem['datatype']))
def _add_filter(self, key, filtername, value, negate, dbtable, querieslist, attrdict, relnode, relnodeclass): """ Internal method to apply a filter either on Extras or Attributes, to avoid to repeat the same code in a DRY spirit. """ valid_filters = { '': '', None: '', '=': '__exact', 'exact': '__exact', 'iexact': '__iexact', 'contains': '__contains', 'icontains': '__icontains', 'startswith': '__startswith', 'istartswith': '__istartswith', 'endswith': '__endswith', 'iendsswith': '__iendswith', '<': '__lt', 'lt': '__lt', 'lte': '__lte', 'le': '__lte', '<=': '__lte', '>': '__gt', 'gt': '__gt', 'gte': '__gte', 'ge': '__gte', '>=': '__gte', } querylist = [] querydict = {} querydict['key'] = key try: internalfilter = valid_filters[filtername] except KeyError: raise ValueError( "Filter '{}' is not a supported filter".format(filtername)) if value is None: querydict['datatype'] = 'none' elif isinstance(value, bool): querydict['datatype'] = 'bool' if negate: querylist.append(~Q( **{'bval{}'.format(internalfilter): value})) else: querydict['bval{}'.format(internalfilter)] = value elif isinstance(value, (int, long)): querydict['datatype'] = 'int' querydict['ival{}'.format(internalfilter)] = value elif isinstance(value, float): querydict['datatype'] = 'float' querydict['fval{}'.format(internalfilter)] = value elif isinstance(value, basestring): querydict['datatype'] = 'txt' if negate: querylist.append(~Q( **{'tval{}'.format(internalfilter): value})) else: querydict['tval{}'.format(internalfilter)] = value elif isinstance(value, datetime.datetime): # current timezone is taken from the settings file of django if is_naive(value): value_aware = make_aware(value, get_current_timezone()) else: value_aware = value querydict['datatype'] = 'date' querydict['dval{}'.format(internalfilter)] = value_aware # elif isinstance(value, list): # # new_entry.datatype = 'list' # new_entry.ival = length #elif isinstance(value, dict): # new_entry.datatype = 'dict' # new_entry.ival = len(value) else: raise TypeError("Only basic datatypes are supported in queries!") reldata = {} if relnode is not None: if (relnodeclass is not None and not isinstance(relnodeclass, Node) and not issubclass(relnodeclass, Node)): raise TypeError("relnodeclass must be an AiiDA node") if relnodeclass is None: reldata['nodeclass'] = None else: reldata['nodeclass'] = relnodeclass._query_type_string if relnode == 'res': reldata['relation'] = "__output" reldata['linkname'] = "output_parameters" elif relnode.startswith('out.'): reldata['relation'] = "__output" reldata['linkname'] = relnode[4:] elif relnode.startswith('inp.'): reldata['relation'] = "__input" reldata['linkname'] = relnode[4:] else: raise NotImplementedError( "Implemented only for 'out.' and 'inp.' for the time being!" ) else: if relnodeclass is not None: raise ValueError( "cannot pass relnodeclass if no relnode is specified") # We are changing the query, clear the cache self._queryobject = None querieslist.append((dbtable.objects.filter(*querylist, **querydict), reldata)) if reldata: pass else: attrdict[key] = reldata