Skip to content

Commit 91c1c0b

Browse files
🐛 FIX: REST API date-time query (#4959)
This was excepting because the `DatetimePrecision` class did not have an assigned hashing function. Here we move this class to `aiida.common`, to avoid cyclic import, and add the hash function. Co-authored-by: Chris Sewell <[email protected]>
1 parent 6606a5b commit 91c1c0b

File tree

5 files changed

+71
-26
lines changed

5 files changed

+71
-26
lines changed

aiida/common/hashing.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
from functools import singledispatch
2020
from itertools import chain
2121
from operator import itemgetter
22-
2322
import pytz
2423

24+
from aiida.common.utils import DatetimePrecision
2525
from aiida.common.constants import AIIDA_FLOAT_PRECISION
2626
from aiida.common.exceptions import HashingError
2727

@@ -279,6 +279,15 @@ def _(val, **kwargs):
279279
return [_single_digest('uuid', val.bytes)]
280280

281281

282+
@_make_hash.register(DatetimePrecision)
283+
def _(datetime_precision, **kwargs):
284+
""" Hashes for DatetimePrecision object
285+
"""
286+
return [_single_digest('dt_prec')] + list(
287+
chain.from_iterable(_make_hash(i, **kwargs) for i in [datetime_precision.dtobj, datetime_precision.precision])
288+
) + [_END_DIGEST]
289+
290+
282291
@_make_hash.register(Folder)
283292
def _(folder, **kwargs):
284293
"""

aiida/common/utils.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import re
1616
import sys
1717
from uuid import UUID
18-
18+
from datetime import datetime
1919
from .lang import classproperty
2020

2121

@@ -595,3 +595,27 @@ def result(self, raise_error=Exception):
595595
def raise_errors(self, raise_cls):
596596
if not self.success():
597597
raise raise_cls(f'The following errors were encountered: {self.errors}')
598+
599+
600+
class DatetimePrecision:
601+
"""
602+
A simple class which stores a datetime object with its precision. No
603+
internal check is done (cause itis not possible).
604+
605+
precision: 1 (only full date)
606+
2 (date plus hour)
607+
3 (date + hour + minute)
608+
4 (dare + hour + minute +second)
609+
"""
610+
611+
def __init__(self, dtobj, precision):
612+
""" Constructor to check valid datetime object and precision """
613+
614+
if not isinstance(dtobj, datetime):
615+
raise TypeError('dtobj argument has to be a datetime object')
616+
617+
if not isinstance(precision, int):
618+
raise TypeError('precision argument has to be an integer')
619+
620+
self.dtobj = dtobj
621+
self.precision = precision

aiida/restapi/common/utils.py

+1-24
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from flask.json import JSONEncoder
1616
from wrapt import decorator
1717

18+
from aiida.common.utils import DatetimePrecision
1819
from aiida.common.exceptions import InputValidationError, ValidationError
1920
from aiida.manage.manager import get_manager
2021
from aiida.restapi.common.exceptions import RestValidationError, \
@@ -62,30 +63,6 @@ def default(self, o):
6263
return JSONEncoder.default(self, o)
6364

6465

65-
class DatetimePrecision:
66-
"""
67-
A simple class which stores a datetime object with its precision. No
68-
internal check is done (cause itis not possible).
69-
70-
precision: 1 (only full date)
71-
2 (date plus hour)
72-
3 (date + hour + minute)
73-
4 (dare + hour + minute +second)
74-
"""
75-
76-
def __init__(self, dtobj, precision):
77-
""" Constructor to check valid datetime object and precision """
78-
79-
if not isinstance(dtobj, datetime):
80-
raise TypeError('dtobj argument has to be a datetime object')
81-
82-
if not isinstance(precision, int):
83-
raise TypeError('precision argument has to be an integer')
84-
85-
self.dtobj = dtobj
86-
self.precision = precision
87-
88-
8966
class Utils:
9067
"""
9168
A class that gathers all the utility functions for parsing URI,

tests/common/test_hashing.py

+7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
except ImportError:
2626
import unittest
2727

28+
from aiida.common.utils import DatetimePrecision
2829
from aiida.common.exceptions import HashingError
2930
from aiida.common.hashing import make_hash, float_to_text, chunked_file_hash
3031
from aiida.common.folders import SandboxFolder
@@ -169,6 +170,12 @@ def test_datetime(self):
169170
'be7c7c7faaff07d796db4cbef4d3d07ed29fdfd4a38c9aded00a4c2da2b89b9c'
170171
)
171172

173+
def test_datetime_precision_hashing(self):
174+
dt_prec = DatetimePrecision(datetime(2018, 8, 18, 8, 18), 10)
175+
self.assertEqual(make_hash(dt_prec), '837ab70b3b7bd04c1718834a0394a2230d81242c442e4aa088abeab15622df37')
176+
dt_prec_utc = DatetimePrecision(datetime.utcfromtimestamp(0), 0)
177+
self.assertEqual(make_hash(dt_prec_utc), '8c756ee99eaf9655bb00166839b9d40aa44eac97684b28f6e3c07d4331ae644e')
178+
172179
def test_numpy_types(self):
173180
self.assertEqual(
174181
make_hash(np.float64(3.141)), 'b3302aad550413e14fe44d5ead10b3aeda9884055fca77f9368c48517916d4be'

tests/restapi/test_routes.py

+28
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# pylint: disable=too-many-lines
1111
"""Unittests for REST API."""
1212
import io
13+
from datetime import date
1314

1415
from flask_cors.core import ACL_ORIGIN
1516

@@ -979,6 +980,33 @@ def test_nodes_full_type_filter(self):
979980
for node in response['data']['nodes']:
980981
self.assertIn(node['uuid'], expected_node_uuids)
981982

983+
def test_nodes_time_filters(self):
984+
"""
985+
Get the list of node filtered by time
986+
"""
987+
today = date.today().strftime('%Y-%m-%d')
988+
989+
expected_node_uuids = []
990+
data = self.get_dummy_data()
991+
for calc in data['calculations']:
992+
expected_node_uuids.append(calc['uuid'])
993+
994+
# ctime filter test
995+
url = f"{self.get_url_prefix()}/nodes/?ctime={today}&full_type=\"process.calculation.calcjob.CalcJobNode.|\""
996+
with self.app.test_client() as client:
997+
rv_obj = client.get(url)
998+
response = json.loads(rv_obj.data)
999+
for node in response['data']['nodes']:
1000+
self.assertIn(node['uuid'], expected_node_uuids)
1001+
1002+
# mtime filter test
1003+
url = f"{self.get_url_prefix()}/nodes/?mtime={today}&full_type=\"process.calculation.calcjob.CalcJobNode.|\""
1004+
with self.app.test_client() as client:
1005+
rv_obj = client.get(url)
1006+
response = json.loads(rv_obj.data)
1007+
for node in response['data']['nodes']:
1008+
self.assertIn(node['uuid'], expected_node_uuids)
1009+
9821010
############### Structure visualization and download #############
9831011
def test_structure_derived_properties(self):
9841012
"""

0 commit comments

Comments
 (0)