Skip to content

Commit

Permalink
Expose the possibility to query for installation date
Browse files Browse the repository at this point in the history
The DB can now be queried for specs that have been installed in a given
time window. This query possibility is exposed to command line via two
new options of the `find` command.
  • Loading branch information
alalazo committed Feb 23, 2018
1 parent d4a7a7c commit 526055c
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 30 deletions.
16 changes: 16 additions & 0 deletions lib/spack/spack/cmd/find.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import llnl.util.tty as tty
import spack
import spack.database
import spack.cmd.common.arguments as arguments
from spack.cmd import display_specs

Expand Down Expand Up @@ -96,6 +97,14 @@ def setup_parser(subparser):
action='store_true',
help='show fully qualified package names')

subparser.add_argument(
'--start-date',
help='earliest date of installation [YYYY-MM-DD HH:MM:SS]'
)
subparser.add_argument(
'--end-date', help='latest date of installation [YYYY-MM-DD HH:MM:SS]'
)

arguments.add_common_arguments(subparser, ['constraint'])


Expand All @@ -114,6 +123,13 @@ def query_arguments(args):
if args.implicit:
explicit = False
q_args = {'installed': installed, 'known': known, "explicit": explicit}

# Time window of installation
for attribute in ('start_date', 'end_date'):
date = getattr(args, attribute)
if date:
q_args[attribute] = spack.database.str2datetime(date)

return q_args


Expand Down
115 changes: 86 additions & 29 deletions lib/spack/spack/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ def _today():
return today.strftime(_time_format)


def str2datetime(datetime_str):
"""Parses a date string and return an appropriate datetime object
Args:
datetime_str (str): string with date and time
Returns:
Corresponding ``datetime`` object
"""
return datetime.datetime.strptime(datetime_str, _time_format)


def _autospec(function):
"""Decorator that automatically converts the argument of a single-arg
function to a Spec."""
Expand Down Expand Up @@ -816,45 +828,64 @@ def activated_extensions_for(self, extendee_spec, extensions_layout=None):
continue
# TODO: conditional way to do this instead of catching exceptions

def query(self, query_spec=any, known=any, installed=True, explicit=any):
"""Run a query on the database.
def query(self, query_spec=any, **kwargs):
"""Run a query on the database
Args:
query_spec: queries iterate through specs in the database and
return those that satisfy the supplied ``query_spec``. If
query_spec is `any`, This will match all specs in the
database. If it is a spec, we'll evaluate
``spec.satisfies(query_spec)``
**kwargs: optional constraint arguments
known
Possible values: True, False, any
``query_spec``
Queries iterate through specs in the database and return
those that satisfy the supplied ``query_spec``. If
query_spec is `any`, This will match all specs in the
database. If it is a spec, we'll evaluate
``spec.satisfies(query_spec)``.
Specs that are "known" are those for which Spack can
locate a ``package.py`` file -- i.e., Spack "knows" how to
install them. Specs that are unknown may represent
packages that existed in a previous version of Spack, but
have since either changed their name or been removed
The query can be constrained by two additional attributes:
installed
Possible values: True, False, any
``known``
Possible values: True, False, any
Specs for which a prefix exists are "installed". A spec
that is NOT installed will be in the database if some
other spec depends on it but its installation has gone
away since Spack installed it.
Specs that are "known" are those for which Spack can
locate a ``package.py`` file -- i.e., Spack "knows" how to
install them. Specs that are unknown may represent
packages that existed in a previous version of Spack, but
have since either changed their name or been removed.
explicit
Possible values: True, False, any
``installed``
Possible values: True, False, any
A spec that was installed following a specific user
request is marked as explicit. If instead it was
pulled-in as a dependency of a user requested spec
it's considered implicit.
Specs for which a prefix exists are "installed". A spec
that is NOT installed will be in the database if some
other spec depends on it but its installation has gone
away since Spack installed it.
start_date
date in format YYYY-MM-DD HH:MM:SS
TODO: Specs are a lot like queries. Should there be a
wildcard spec object, and should specs have attributes
like installed and known that can be queried? Or are
these really special cases that only belong here?
Filters the query discarding specs that have been
installed before ``start_date``.
end_date
date in format YYYY-MM-DD HH:MM:SS
Filters the query discarding specs that have been
installed after ``end_date``.
Returns:
list of specs that match the query
"""
# TODO: Specs are a lot like queries. Should there be a
# TODO: wildcard spec object, and should specs have attributes
# TODO: like installed and known that can be queried? Or are
# TODO: these really special cases that only belong here?
with self.read_transaction():
# Just look up concrete specs with hashes; no fancy search.
if (isinstance(query_spec, spack.spec.Spec) and
query_spec._concrete):
if isinstance(query_spec, spack.spec.Spec) and query_spec.concrete:

hash_key = query_spec.dag_hash()
if hash_key in self._data:
Expand All @@ -865,14 +896,38 @@ def query(self, query_spec=any, known=any, installed=True, explicit=any):
# Abstract specs require more work -- currently we test
# against everything.
results = []

# Take care of managing the keyword arguments to the query
known = kwargs.pop('known', any)
installed = kwargs.pop('installed', True)
explicit = kwargs.pop('explicit', any)
start_date = kwargs.pop('start_date', datetime.datetime.min)
end_date = kwargs.pop('end_date', datetime.datetime.max)

# Raise an exception if we have still entries in kwargs
# Mimic what python would do in case of a single unexpected
# keyword argument
if kwargs:
msg = 'spack.database.query() got unexpected keyword arguments'
keys = ["'{0}'".format(x) for x in kwargs]
msg += ' {0}'.format(', '.join(keys))
raise TypeError(msg)

for key, rec in self._data.items():
if installed is not any and rec.installed != installed:
continue

if explicit is not any and rec.explicit != explicit:
continue

if known is not any and spack.repo.exists(
rec.spec.name) != known:
continue

inst_date = str2datetime(rec.installation_datetime)
if not (start_date < inst_date < end_date):
continue

if query_spec is any or rec.spec.satisfies(query_spec):
results.append(rec.spec)

Expand All @@ -885,7 +940,9 @@ def query_one(self, query_spec, known=any, installed=True):
query. Returns None if no installed package matches.
"""
concrete_specs = self.query(query_spec, known, installed)
concrete_specs = self.query(
query_spec, known=known, installed=installed
)
assert len(concrete_specs) <= 1
return concrete_specs[0] if concrete_specs else None

Expand Down
6 changes: 5 additions & 1 deletion lib/spack/spack/test/cmd/find.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ def test_query_arguments():
missing=False,
unknown=False,
explicit=False,
implicit=False
implicit=False,
start_date="2018-02-23 14:36:00",
end_date=None
)

q_args = query_arguments(args)
Expand All @@ -72,6 +74,8 @@ def test_query_arguments():
assert q_args['installed'] is True
assert q_args['known'] is any
assert q_args['explicit'] is any
assert 'start_date' in q_args
assert 'end_date' not in q_args

# Check that explicit works correctly
args.explicit = True
Expand Down
7 changes: 7 additions & 0 deletions lib/spack/spack/test/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
These tests check the database is functioning properly,
both in memory and in its file
"""
import datetime
import multiprocessing
import os
import pytest
Expand Down Expand Up @@ -293,6 +294,12 @@ def test_050_basic_query(database):
assert len(install_db.query('mpileaks ^mpich2')) == 1
assert len(install_db.query('mpileaks ^zmpi')) == 1

# Query by date
assert len(install_db.query(start_date=datetime.datetime.min)) == 16
assert len(install_db.query(start_date=datetime.datetime.max)) == 0
assert len(install_db.query(end_date=datetime.datetime.min)) == 0
assert len(install_db.query(end_date=datetime.datetime.max)) == 16


def test_060_remove_and_add_root_package(database):
install_db = database.mock.db
Expand Down

0 comments on commit 526055c

Please sign in to comment.