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 1372 sqla attributes not current when read minimal #1379

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
57 changes: 32 additions & 25 deletions .travis-data/test_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
ParameterData = DataFactory('parameter')

codename = 'doubler@torquessh'
timeout_secs = 4 * 60 # 4 minutes
number_calculations = 30 # Number of calculations to submit
number_workchains = 30 # Number of workchains to submit
timeout_secs = 4 * 60 # 4 minutes
number_calculations = 30 # Number of calculations to submit
number_workchains = 30 # Number of workchains to submit


def print_daemon_log():
home = os.environ['HOME']
Expand All @@ -35,22 +36,25 @@ def print_daemon_log():
except subprocess.CalledProcessError as e:
print "Note: the command failed, message: {}".format(e.message)


def jobs_have_finished(pks):
finished_list = [load_node(pk).has_finished() for pk in pks]
num_finished = len([_ for _ in finished_list if _])
print "{}/{} finished".format(num_finished, len(finished_list))
return not (False in finished_list)


def print_logshow(pk):
print "Output of 'verdi calculation logshow {}':".format(pk)
try:
print subprocess.check_output(
["verdi", "calculation", "logshow", "{}".format(pk)],
stderr=subprocess.STDOUT,
)
)
except subprocess.CalledProcessError as e2:
print "Note: the command failed, message: {}".format(e2.message)


def validate_calculations(expected_results):
valid = True
actual_dict = {}
Expand Down Expand Up @@ -81,6 +85,7 @@ def validate_calculations(expected_results):

return valid


def validate_workchains(expected_results):
valid = True
for pk, expected_value in expected_results.iteritems():
Expand All @@ -98,6 +103,7 @@ def validate_workchains(expected_results):

return valid


def validate_cached(cached_calcs):
"""
Check that the calculations with created with caching are indeed cached.
Expand All @@ -108,21 +114,22 @@ def validate_cached(cached_calcs):
for calc in cached_calcs
)


def create_calculation(code, counter, inputval, use_cache=False):
parameters = ParameterData(dict={'value': inputval})
template = ParameterData(dict={
## The following line adds a significant sleep time.
## I set it to 1 second to speed up tests
## I keep it to a non-zero value because I want
## To test the case when AiiDA finds some calcs
## in a queued state
#'cmdline_params': ["{}".format(counter % 3)], # Sleep time
'cmdline_params': ["1"],
'input_file_template': "{value}", # File just contains the value to double
'input_file_name': 'value_to_double.txt',
'output_file_name': 'output.txt',
'retrieve_temporary_files': ['triple_value.tmp']
})
## The following line adds a significant sleep time.
## I set it to 1 second to speed up tests
## I keep it to a non-zero value because I want
## To test the case when AiiDA finds some calcs
## in a queued state
# 'cmdline_params': ["{}".format(counter % 3)], # Sleep time
'cmdline_params': ["1"],
'input_file_template': "{value}", # File just contains the value to double
'input_file_name': 'value_to_double.txt',
'output_file_name': 'output.txt',
'retrieve_temporary_files': ['triple_value.tmp']
})
calc = code.new_calc()
calc.set_max_wallclock_seconds(5 * 60) # 5 min
calc.set_resources({"num_machines": 1})
Expand All @@ -138,10 +145,10 @@ def create_calculation(code, counter, inputval, use_cache=False):
'triple_value.tmp': str(inputval * 3)
}
}
print "[{}] created calculation {}, pk={}".format(
counter, calc.uuid, calc.dbnode.pk)
print "[{}] created calculation {}, pk={}".format(counter, calc.uuid, calc.pk)
return calc, expected_result


def submit_calculation(code, counter, inputval):
calc, expected_result = create_calculation(
code=code, counter=counter, inputval=inputval
Expand All @@ -150,15 +157,16 @@ def submit_calculation(code, counter, inputval):
print "[{}] calculation submitted.".format(counter)
return calc, expected_result


def create_cache_calc(code, counter, inputval):
calc, expected_result = create_calculation(
code=code, counter=counter, inputval=inputval, use_cache=True
)
print "[{}] created cached calculation.".format(counter)
return calc, expected_result

def main():

def main():
# Submitting the Calculations
print "Submitting {} calculations to the daemon".format(number_calculations)
code = Code.get_from_string(codename)
Expand All @@ -178,7 +186,6 @@ def main():
future = submit(ParentWorkChain, inp=inp)
expected_results_workchains[future.pid] = index * 2


calculation_pks = sorted(expected_results_calculations.keys())
workchains_pks = sorted(expected_results_workchains.keys())
pks = calculation_pks + workchains_pks
Expand All @@ -187,14 +194,14 @@ def main():
start_time = time.time()
exited_with_timeout = True
while time.time() - start_time < timeout_secs:
time.sleep(15) # Wait a few seconds
time.sleep(15) # Wait a few seconds

# Print some debug info, both for debugging reasons and to avoid
# that the test machine is shut down because there is no output

print "#"*78
print "#" * 78
print "####### TIME ELAPSED: {} s".format(time.time() - start_time)
print "#"*78
print "#" * 78
print "Output of 'verdi calculation list -a':"
try:
print subprocess.check_output(
Expand Down Expand Up @@ -244,8 +251,8 @@ def main():
cached_calcs.append(calc)
expected_results_calculations[calc.pk] = expected_result
if (validate_calculations(expected_results_calculations)
and validate_workchains(expected_results_workchains)
and validate_cached(cached_calcs)):
and validate_workchains(expected_results_workchains)
and validate_cached(cached_calcs)):
print_daemon_log()
print ""
print "OK, all calculations have the expected parsed result"
Expand Down
2 changes: 1 addition & 1 deletion aiida/backends/djsite/db/subtests/djangomigrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_unexpected_calc_states(self):
job = JobCalculation(**calc_params)
job.store()
# Now save the errant state
DbCalcState(dbnode=job.dbnode, state=state).save()
DbCalcState(dbnode=job._dbnode, state=state).save()

time_before_fix = timezone.now()

Expand Down
20 changes: 9 additions & 11 deletions aiida/backends/djsite/db/subtests/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from aiida.orm.node import Node



class TestComputer(AiidaTestCase):
"""
Test the Computer class.
Expand All @@ -42,8 +41,6 @@ def test_deletion(self):

_ = JobCalculation(**calc_params).store()

#print "Node stored with pk:", _.dbnode.pk

# This should fail, because there is at least a calculation
# using this computer (the one created just above)
with self.assertRaises(InvalidOperation):
Expand Down Expand Up @@ -123,31 +120,32 @@ class TestDbExtrasDjango(AiidaTestCase):
"""
Test DbAttributes.
"""

def test_replacement_1(self):
from aiida.backends.djsite.db.models import DbExtra

n1 = Node().store()
n2 = Node().store()

DbExtra.set_value_for_node(n1.dbnode, "pippo", [1, 2, 'a'])
DbExtra.set_value_for_node(n1.dbnode, "pippobis", [5, 6, 'c'])
DbExtra.set_value_for_node(n2.dbnode, "pippo2", [3, 4, 'b'])
DbExtra.set_value_for_node(n1._dbnode, "pippo", [1, 2, 'a'])
DbExtra.set_value_for_node(n1._dbnode, "pippobis", [5, 6, 'c'])
DbExtra.set_value_for_node(n2._dbnode, "pippo2", [3, 4, 'b'])

self.assertEquals(n1.get_extras(), {'pippo': [1, 2, 'a'],
'pippobis': [5, 6, 'c'],
'_aiida_hash': n1.get_hash()
})
'pippobis': [5, 6, 'c'],
'_aiida_hash': n1.get_hash()
})
self.assertEquals(n2.get_extras(), {'pippo2': [3, 4, 'b'],
'_aiida_hash': n2.get_hash()
})

new_attrs = {"newval1": "v", "newval2": [1, {"c": "d", "e": 2}]}

DbExtra.reset_values_for_node(n1.dbnode, attributes=new_attrs)
DbExtra.reset_values_for_node(n1._dbnode, attributes=new_attrs)
self.assertEquals(n1.get_extras(), new_attrs)
self.assertEquals(n2.get_extras(), {'pippo2': [3, 4, 'b'], '_aiida_hash': n2.get_hash()})

DbExtra.del_value_for_node(n1.dbnode, key='newval2')
DbExtra.del_value_for_node(n1._dbnode, key='newval2')
del new_attrs['newval2']
self.assertEquals(n1.get_extras(), new_attrs)
# Also check that other nodes were not damaged
Expand Down
3 changes: 1 addition & 2 deletions aiida/backends/sqlalchemy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

# The next two serve as 'global' variables, set in the load_dbenv
# call. They are properly reset upon forking.
engine = None
engine = None
scopedsessionclass = None


Expand All @@ -28,4 +28,3 @@ def get_scoped_session():
s = scopedsessionclass()

return s

7 changes: 4 additions & 3 deletions aiida/backends/sqlalchemy/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@
import aiida.backends.sqlalchemy
from aiida.common.exceptions import InvalidOperation


# Taken from
# https://github.com/mitsuhiko/flask-sqlalchemy/blob/master/flask_sqlalchemy/__init__.py#L491




class _QueryProperty(object):

def __init__(self, query_class=orm.Query):
Expand Down Expand Up @@ -63,8 +62,8 @@ def __iter__(self):

from aiida.backends.sqlalchemy import get_scoped_session

class Model(object):

class Model(object):
query = _QueryProperty()

session = _SessionProperty()
Expand All @@ -81,4 +80,6 @@ def delete(self, commit=True):
sess.delete(self)
if commit:
sess.commit()


Base = declarative_base(cls=Model, name='Model')
38 changes: 17 additions & 21 deletions aiida/backends/sqlalchemy/models/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class DbNode(Base):
# User
user = relationship(
'DbUser',
backref=backref('dbnodes', passive_deletes='all', cascade='merge',)
backref=backref('dbnodes', passive_deletes='all', cascade='merge', )
)

# outputs via db_dblink table
Expand All @@ -127,14 +127,6 @@ class DbNode(Base):
passive_deletes=True
)

@property
def outputs(self):
return self.outputs_q.all()

@property
def inputs(self):
return self.inputs_q.all()

def __init__(self, *args, **kwargs):
super(DbNode, self).__init__(*args, **kwargs)

Expand All @@ -144,6 +136,13 @@ def __init__(self, *args, **kwargs):
if self.extras is None:
self.extras = dict()

@property
def outputs(self):
return self.outputs_q.all()

@property
def inputs(self):
return self.inputs_q.all()

# XXX repetition between django/sqlalchemy here.
def get_aiida_class(self):
Expand Down Expand Up @@ -215,8 +214,7 @@ def del_extra(self, key):
@staticmethod
def _set_attr(d, key, value):
if '.' in key:
raise ValueError(
"We don't know how to treat key with dot in it yet")
raise ValueError("We don't know how to treat key with dot in it yet")

d[key] = value

Expand Down Expand Up @@ -273,10 +271,9 @@ def computer_name(cls):
database)
"""
return select([DbComputer.name]).where(DbComputer.id ==
cls.dbcomputer_id).label(
cls.dbcomputer_id).label(
'computer_name')


@hybrid_property
def state(self):
"""
Expand All @@ -286,10 +283,10 @@ def state(self):
return None
all_states = DbCalcState.query.filter(DbCalcState.dbnode_id == self.id).all()
if all_states:
#return max((st.time, st.state) for st in all_states)[1]
# return max((st.time, st.state) for st in all_states)[1]
return sort_states(((dbcalcstate.state, dbcalcstate.state.value)
for dbcalcstate in all_states),
use_key=True)[0]
use_key=True)[0]
else:
return None

Expand All @@ -306,7 +303,7 @@ def state(cls):
in enumerate(_sorted_datastates[::-1], start=1)}
custom_sort_order = case(value=DbCalcState.state,
whens=whens,
else_=100) # else: high value to put it at the bottom
else_=100) # else: high value to put it at the bottom

# Add numerical state to string, to allow to sort them
states_with_num = select([
Expand All @@ -329,7 +326,7 @@ def state(cls):
DbCalcState.state.label('state_string'),
calc_state_num.c.recent_state.label('recent_state'),
custom_sort_order.label('num_state'),
]).select_from(#DbCalcState).alias().join(
]).select_from( # DbCalcState).alias().join(
join(DbCalcState, calc_state_num, DbCalcState.dbnode_id == calc_state_num.c.dbnode_id)).alias()

# Get the association between each calc and only its corresponding most-recent-state row
Expand All @@ -339,10 +336,10 @@ def state(cls):
]).select_from(all_states_q).where(all_states_q.c.num_state == all_states_q.c.recent_state).alias()

# Final filtering for the actual query
return select([subq.c.state]).\
return select([subq.c.state]). \
where(
subq.c.dbnode_id == cls.id,
).\
subq.c.dbnode_id == cls.id,
). \
label('laststate')


Expand Down Expand Up @@ -388,4 +385,3 @@ def __str__(self):
self.output.get_simple_name(invalid_result="Unknown node"),
self.output.pk
)

4 changes: 2 additions & 2 deletions aiida/backends/sqlalchemy/models/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def set_value(self, arg):
raise ValueError("Cannot add an unstored node as an "
"attribute of a Workflow!")
sess = get_scoped_session()
self.aiida_obj = sess.merge(arg.dbnode, load=True)
self.aiida_obj = sess.merge(arg._dbnode, load=True)
self.value_type = wf_data_value_types.AIIDA
self.save()
else:
Expand Down Expand Up @@ -325,7 +325,7 @@ def add_calculation(self, step_calculation):
raise ValueError("Cannot add a non-Calculation object to a workflow step")

try:
self.calculations.append(step_calculation.dbnode)
self.calculations.append(step_calculation._dbnode)
except:
raise ValueError("Error adding calculation to step")

Expand Down
Loading