diff --git a/aiida/common/hashing.py b/aiida/common/hashing.py index 61dbbaf1c4..9e6fa721b2 100644 --- a/aiida/common/hashing.py +++ b/aiida/common/hashing.py @@ -19,9 +19,9 @@ from functools import singledispatch from itertools import chain from operator import itemgetter - import pytz +from aiida.common.utils import DatetimePrecision from aiida.common.constants import AIIDA_FLOAT_PRECISION from aiida.common.exceptions import HashingError @@ -279,6 +279,15 @@ def _(val, **kwargs): return [_single_digest('uuid', val.bytes)] +@_make_hash.register(DatetimePrecision) +def _(datetime_precision, **kwargs): + """ Hashes for DatetimePrecision object + """ + return [_single_digest('dt_prec')] + list( + chain.from_iterable(_make_hash(i, **kwargs) for i in [datetime_precision.dtobj, datetime_precision.precision]) + ) + [_END_DIGEST] + + @_make_hash.register(Folder) def _(folder, **kwargs): """ diff --git a/aiida/common/utils.py b/aiida/common/utils.py index 7de837beb7..521bcdc98b 100644 --- a/aiida/common/utils.py +++ b/aiida/common/utils.py @@ -15,7 +15,7 @@ import re import sys from uuid import UUID - +from datetime import datetime from .lang import classproperty @@ -595,3 +595,27 @@ def result(self, raise_error=Exception): def raise_errors(self, raise_cls): if not self.success(): raise raise_cls(f'The following errors were encountered: {self.errors}') + + +class DatetimePrecision: + """ + A simple class which stores a datetime object with its precision. No + internal check is done (cause itis not possible). + + precision: 1 (only full date) + 2 (date plus hour) + 3 (date + hour + minute) + 4 (dare + hour + minute +second) + """ + + def __init__(self, dtobj, precision): + """ Constructor to check valid datetime object and precision """ + + if not isinstance(dtobj, datetime): + raise TypeError('dtobj argument has to be a datetime object') + + if not isinstance(precision, int): + raise TypeError('precision argument has to be an integer') + + self.dtobj = dtobj + self.precision = precision diff --git a/aiida/restapi/common/utils.py b/aiida/restapi/common/utils.py index bd9e259f53..034db7ba20 100644 --- a/aiida/restapi/common/utils.py +++ b/aiida/restapi/common/utils.py @@ -15,6 +15,7 @@ from flask.json import JSONEncoder from wrapt import decorator +from aiida.common.utils import DatetimePrecision from aiida.common.exceptions import InputValidationError, ValidationError from aiida.manage.manager import get_manager from aiida.restapi.common.exceptions import RestValidationError, \ @@ -62,30 +63,6 @@ def default(self, o): return JSONEncoder.default(self, o) -class DatetimePrecision: - """ - A simple class which stores a datetime object with its precision. No - internal check is done (cause itis not possible). - - precision: 1 (only full date) - 2 (date plus hour) - 3 (date + hour + minute) - 4 (dare + hour + minute +second) - """ - - def __init__(self, dtobj, precision): - """ Constructor to check valid datetime object and precision """ - - if not isinstance(dtobj, datetime): - raise TypeError('dtobj argument has to be a datetime object') - - if not isinstance(precision, int): - raise TypeError('precision argument has to be an integer') - - self.dtobj = dtobj - self.precision = precision - - class Utils: """ A class that gathers all the utility functions for parsing URI, diff --git a/tests/common/test_hashing.py b/tests/common/test_hashing.py index 76a85dddec..915a8f4fbf 100644 --- a/tests/common/test_hashing.py +++ b/tests/common/test_hashing.py @@ -25,6 +25,7 @@ except ImportError: import unittest +from aiida.common.utils import DatetimePrecision from aiida.common.exceptions import HashingError from aiida.common.hashing import make_hash, float_to_text, chunked_file_hash from aiida.common.folders import SandboxFolder @@ -169,6 +170,12 @@ def test_datetime(self): 'be7c7c7faaff07d796db4cbef4d3d07ed29fdfd4a38c9aded00a4c2da2b89b9c' ) + def test_datetime_precision_hashing(self): + dt_prec = DatetimePrecision(datetime(2018, 8, 18, 8, 18), 10) + self.assertEqual(make_hash(dt_prec), '837ab70b3b7bd04c1718834a0394a2230d81242c442e4aa088abeab15622df37') + dt_prec_utc = DatetimePrecision(datetime.utcfromtimestamp(0), 0) + self.assertEqual(make_hash(dt_prec_utc), '8c756ee99eaf9655bb00166839b9d40aa44eac97684b28f6e3c07d4331ae644e') + def test_numpy_types(self): self.assertEqual( make_hash(np.float64(3.141)), 'b3302aad550413e14fe44d5ead10b3aeda9884055fca77f9368c48517916d4be' diff --git a/tests/restapi/test_routes.py b/tests/restapi/test_routes.py index e6c45ff78f..d9876a245d 100644 --- a/tests/restapi/test_routes.py +++ b/tests/restapi/test_routes.py @@ -10,6 +10,7 @@ # pylint: disable=too-many-lines """Unittests for REST API.""" import io +from datetime import date from flask_cors.core import ACL_ORIGIN @@ -979,6 +980,33 @@ def test_nodes_full_type_filter(self): for node in response['data']['nodes']: self.assertIn(node['uuid'], expected_node_uuids) + def test_nodes_time_filters(self): + """ + Get the list of node filtered by time + """ + today = date.today().strftime('%Y-%m-%d') + + expected_node_uuids = [] + data = self.get_dummy_data() + for calc in data['calculations']: + expected_node_uuids.append(calc['uuid']) + + # ctime filter test + url = f"{self.get_url_prefix()}/nodes/?ctime={today}&full_type=\"process.calculation.calcjob.CalcJobNode.|\"" + with self.app.test_client() as client: + rv_obj = client.get(url) + response = json.loads(rv_obj.data) + for node in response['data']['nodes']: + self.assertIn(node['uuid'], expected_node_uuids) + + # mtime filter test + url = f"{self.get_url_prefix()}/nodes/?mtime={today}&full_type=\"process.calculation.calcjob.CalcJobNode.|\"" + with self.app.test_client() as client: + rv_obj = client.get(url) + response = json.loads(rv_obj.data) + for node in response['data']['nodes']: + self.assertIn(node['uuid'], expected_node_uuids) + ############### Structure visualization and download ############# def test_structure_derived_properties(self): """