Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 FIX: REST API date-time query #4959

Merged
merged 6 commits into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion aiida/common/hashing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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):
"""
Expand Down
26 changes: 25 additions & 1 deletion aiida/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import re
import sys
from uuid import UUID

from datetime import datetime
from .lang import classproperty


Expand Down Expand Up @@ -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
25 changes: 1 addition & 24 deletions aiida/restapi/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions tests/common/test_hashing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'
Expand Down
28 changes: 28 additions & 0 deletions tests/restapi/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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):
"""
Expand Down